This repository documents my journey through the AI Engineering Sprint 2026, moving from basic LLM calls to complex, production-ready AI agents.
- Language: Python 3.13
- Model: Google Gemini 3.0 Flash (via
google-genai) - Environment Management: Virtual Environments (
venv) &python-dotenv
Goal: Transform messy transcripts into machine-readable data.
- Folder:
/01_meeting_extractor - Core Concept: Using Pydantic and Instructor to enforce a schema on LLM outputs.
- Outcome: Extracts tasks, owners, priorities, and meeting sentiment into a structured JSON format.
Goal: Chat with long documents without hitting context limits.
- Folder:
/02_chat_with_transcript - Core Concept: Retrieval-Augmented Generation (RAG).
- Key Features:
- Recursive Chunking: Splitting text into overlapping segments using
langchain-text-splitters. - Contextual Retrieval: A keyword-based search engine to find the most relevant transcript part for a user's question.
- Grounded Responses: The AI is instructed to answer only based on the provided transcript chunks, reducing hallucinations.
- Recursive Chunking: Splitting text into overlapping segments using
Goal: Transition from text-based processing to native audio "listening" and analysis.
- Folder:
/03_audio_processor - Core Concept: Native Multimodality. Instead of using a separate Speech-to-Text (STT) model, we leverage Gemini’s ability to process raw audio waves directly for better context and tone detection.
- Key Features:
- Asynchronous File Handling: Implementing a state-check loop to manage the
PROCESSINGstatus of large media files in the Google File API. - Automated Speaker Diarization: Identifying and labeling different speakers (Speaker 1, Speaker 2) based on vocal characteristics.
- Temporal Logic: Generating precise
[MM:SS]timestamps linked to specific transcript segments.
- Asynchronous File Handling: Implementing a state-check loop to manage the
Goal: Merge Function Calling, MCP Standards, and ReAct Reasoning into a single autonomous agent.
- Folder:
/04_agentic_foundations - Core Concepts:
- Function Calling: Defined local Python tools that the model can trigger to interact with the real world.
- MCP (Model Context Protocol): Designed standardized tool interfaces to allow for clean, interoperable data exchange between the AI and backend.
- ReAct Pattern: Implemented the
Thought -> Action -> Observationloop, ensuring the model reasons through complex, multi-step tasks before answering.
- Thought: AI identifies that it needs stock data and shipping times.
- Action: AI triggers
get_product_inventoryandcalculate_shipping_time. - Observation: AI sees that Monitors are out of stock (0) and shipping to Dublin takes 3 days.
- Final Answer: AI informs the user about the laptop and the monitor shortage specifically.
Goal: Bridge the gap between temporary chat context and permanent episodic recall by implementing a tiered memory architecture.
This milestone demonstrates two distinct "temporal" layers of an AI brain:
- Mechanism: Utilizes the Gemini
ChatSessionto manage the immediate context window. - Function: Maintains the "thread" of a conversation, allowing the model to resolve pronouns (e.g., "it", "that") and follow-up on previous sentences.
- Technical Note: In the 2026
google-genaiSDK, the session history is managed via an internal_historystate that grows by 2 entries (User + Model) for every interaction.
- Mechanism: A persistent JSON-based "Diary" combined with autonomous tool-calling.
- Dual-Tooling:
commit_to_diary: Triggered when the AI identifies significant facts or preferences to save them toepisodic_diary.json.search_diary: Enables the agent to perform targeted keyword searches over past interactions.
- Advantage: By using Selective Recall, we avoid "context stuffing." The agent only retrieves relevant memories, saving tokens and maintaining high reasoning quality.
- Persistence: The agent now remembers user preferences (like names, project goals, or travel plans) even after the script is restarted.
- Autonomous Decision Making: The agent decides what is worth remembering and when it needs to search its past.
- Scalable Architecture: Laid the foundation for Semantic Search (Day 6) by separating storage from retrieval logic.
Goal: Build a production-grade RAG (Retrieval-Augmented Generation) system that autonomously ingests web data, stores it in a vector database, and synthesizes it with live system status.
This project implements a "Full-Stack" Agentic workflow involving three core layers:
- Main Content Extraction: Uses
trafilaturato strip away "web noise" (HTML boilerplate, ads, navbars) to ensure only high-signal text is fed to the model. - Semantic Chunking: Documents are split by paragraph boundaries (
\n\n) to preserve the semantic integrity of the information.
- Vector Embeddings: Text chunks are converted into 768-dimensional vectors using Google's
text-embedding-004. - Persistent Storage: Utilizes a local
chromadbinstance, allowing the agent to retain "learned" knowledge indefinitely across script restarts. - Vector Search: Enables the agent to find information based on conceptual meaning rather than literal keyword matches.
- Multi-Tool Synthesis: The agent can autonomously decide to:
- Scrape a new URL to update its knowledge.
- Search the existing vector database for historical context.
- Query a live "System Status" function to compare research with real-time reality.
Goal: Advance from "Simple RAG" to "Agentic RAG" by implementing a self-critique loop that identifies information gaps and corrects hallucinations before responding.
In Day 6, the agent blindly trusted its first search result. In Day 7, we introduced Cognitive Reflection. The agent now follows an internal "Standard Operating Procedure" (SOP):
- Detect Gaps: Evaluates if the current memory (Vector DB) is sufficient.
- Autonomous Research: Triggers the scraper if more data is required.
- Draft & Critique: Writes a response, then "proofreads" it against the source text to catch errors (like temporal contradictions).
The core of Day 7 is moving logic out of Python loops and into the System Instruction. This allows the model to manage its own tool-use and reflection phases natively.
SYSTEM_PROMPT = """
YOU ARE A SELF-CORRECTING RESEARCH ANALYST.
WORKFLOW:
1. SEARCH: Always search memory first.
2. EVALUATE: If results are insufficient, use 'add_knowledge'.
3. REFLECT: Critique your draft for hallucinations or logic errors.
4. FINAL: Present the refined answer with citations.
"""Goal: Transition from manual Python loops to a professional orchestration framework by mastering the 5 core graph patterns in LangGraph.
This milestone marks the shift from "scripting" to System Architecture. By decoupling the workflow logic (the "Conveyor Belt") from the model's intelligence (the "Worker"), I’ve built a robust skeleton for deterministic state management.
- Centralized Schema: Defined a structured
Statedictionary that serves as the "single source of truth" passed between nodes. - Context Preservation: Ensures every step of the process has access to updated variables and history without manual hand-offs.
I implemented five foundational patterns using pure Python logic to verify the infrastructure:
- Single & Multi-Input: Mastering state initialization and complex data schemas.
- Sequential: Orchestrating deterministic "Assembly Line" pipelines.
- Conditional (Routing): Implementing logic-based decision paths to navigate the graph.
- Looping (Cyclic): Creating the "Agentic" engine that allows for retries, refinement, and self-correction.
- Nodes as Workers: Each node is a discrete function responsible for one specific state transformation.
- Edges as Orchestrators: Defines the "road map," controlling the exact flow of execution based on specific conditions.
- Deterministic Reliability: Verified that complex branching and looping logic works with 100% predictability before adding LLM uncertainty.
- Modular Design: Moved away from monolithic scripts to a modular graph that can scale with new tools and personas.
- Architectural Readiness: Prepared the system to host any LLM (Gemini, Claude, or local) as a "plug-and-play" component within the graph.
Goal: Implement a self-correcting state machine that loops between reasoning and tool execution to satisfy complex, multi-step goals.
This milestone implements the ReAct (Reasoning + Acting) pattern. By using LangGraph's cyclic capabilities, the agent can now perform internal loops to gather information or process data before ever returning a final result to the user.
- Tool Binding: Integrated Gemini 3.0 with a set of arithmetic tools. The model chooses tools based on the semantic intent of the user prompt.
- System Instructions: Injected a
SystemMessageto maintain persona and reliability throughout the cycle.
- Nodes: -
our_agent: The LLM reasoning node.tools: A dedicated execution node for running Python functions.
- Conditional Edges: A
should_continuerouter that inspectstool_callsto decide if the graph should cycle back to the agent or terminate.
add_messagesReducer: Prevents message overwriting, allowing the agent to "remember" the results of tool executions from previous cycles.- Sequence Tracking: Maintained a clean flow of
HumanMessage->AIMessage (Tool Call)->ToolMessage (Result)->AIMessage (Final Answer).
- Cyclic Autonomy: Built an agent capable of looping as many times as necessary to solve a problem (e.g., chained math operations).
- Non-Linear Execution: Moved away from "Step A to Step B" and into a true state-machine that routes data based on the AI's internal logic.
- Stream Visualization: Implemented state streaming to observe the AI's "thought process" and tool usage in real-time.
Goal: Standardize agent actions by integrating LangGraph's pre-built ToolNode and implementing robust exit conditions for autonomous document management.
I evolved the Drafter agent to delegate execution to a specialized ToolNode. This ensures the agent follows a strict ReAct (Reasoning and Acting) pattern:
- Reasoning: The agent (Gemini 3.0) determines if it needs to update or save a file.
- Acting: The
ToolNodeexecutes the Python functions (updateorsave). - Observation: The graph cycles back to the agent or terminates based on the tool's success metadata.
- Structured Tool Calling: Integrated Gemini 3.0 with
updateandsavetools. The model autonomously decides which tool to use based on the semantic intent of the user prompt. - Contextual Awareness: Injected a
SystemMessagethat provides the current document state, allowing the AI to understand the context of modifications.
- Nodes: -
agent: The LLM reasoning node.tools: A dedicated execution node (ToolNode) for running Python functions.
- Conditional Edges: A
should_continuerouter that inspectsToolMessagecontents to decide if the graph should cycle back to the agent or terminate.
add_messagesReducer: Prevents message overwriting, allowing the agent to "remember" the results of tool executions from previous cycles.- Workflow State: Maintains a clean flow of interaction: Human Input -> Reasoning -> Tool Execution -> Verification.
- ToolNode Integration: Replaced manual
if/elsetool routing withlanggraph.prebuilt.ToolNode, significantly reducing code complexity. - Robust Exit Conditions: Implemented a
should_continuefunction to parse tool outputs and reliably break the autonomous loop upon successful file saving. - Context Retention: Successfully maintained state across multiple reasoning cycles, enabling iterative document updates.
Goal: Build a Retrieval-Augmented Generation (RAG) agent that autonomously decides when to consult external PDF documents to answer complex queries.
This milestone moves beyond the agent's internal training data. By integrating a Vector Database, the agent can now perform "Open-Book" exams on specific datasets (Stock Market Performance 2024).
- Ingestion: PDFs are loaded via
PyPDFLoader, split into semantic chunks, and embedded usinggemini-embedding-001. - Storage: Vectors are persisted locally in ChromaDB, allowing for lightning-fast semantic retrieval.
- Agentic Retrieval: The LLM doesn't just "get context." It chooses to use the
retriever_toolonly when the user's query requires specific data from the document.
- Persistent Vector Store: Implemented disk-based storage for embeddings, ensuring the knowledge base doesn't vanish between sessions.
- Dynamic Tool Usage: The agent can call the retriever multiple times with different search queries to "triangulate" the best answer.
- Source Attribution: Configured system prompts to ensure the agent cites specific document sections, increasing factual reliability and reducing hallucinations.
Goal: Build an autonomous agent capable of writing Python code, executing it in a real environment, and using real-time error feedback to self-correct until a valid solution is reached.
Today’s milestone introduces the Cyclic Reasoning Pattern. Unlike traditional linear pipelines, this agent operates within a "Loop of Truth"—it cannot provide a final answer until its generated code executes successfully.
- Generation Node (
call_model): The LLM acts as a Senior Developer, interpreting the user's prompt to architect a Python solution. - Execution Node (
python_executor): A custom environment that runs the code and captures the output or the exact Stack Trace if it fails. - Self-Correction Loop: If a failure is detected, the full history—including the faulty code and the specific error message—is sent back to the LLM for analysis.
- State Management: The system tracks "Execution Iterations" to ensure the agent has a set budget (e.g., 5 attempts) to fix the bug, preventing infinite loops and managing API costs.
- Operational Feedback Loops: Moved beyond static prompting. The agent now uses external "ground truth" (terminal output) to validate its own reasoning.
- Recursive Debugging: Developed the logic for the agent to analyze tracebacks, identify syntax or logical errors, and provide iterative fixes autonomously.
- Stateful Iteration Tracking: Implemented an "Agentic Kill-Switch" within the StateGraph to manage computational resources and ensure system stability.
- Dynamic Tool Calling: Orchestrated a seamless transition between the "Thinker" (LLM) and the "Doer" (REPL Tool).
Goal: Transform the self-correcting agent into a production-ready system by migrating from volatile RAM-based memory to a persistent MySQL database backend.
Today’s milestone introduces Durable State Management. By integrating a relational database, the agent's conversation history and internal reasoning (checkpoints) are preserved even if the script crashes or the server restarts.
-
Persistence Layer (PyMySQLSaver): Replaces the temporary MemorySaver. This layer connects the LangGraph workflow to a dedicated MySQL schema (langgraph_db).
-
Checkpointing Engine: After every node execution (e.g., call_model or tools), a binary "snapshot" of the entire AgentState is serialized and saved to SQL tables.
-
Thread-Based Retrieval: Uses a unique thread_id to act as a lookup key. This allows the agent to distinguish between different users and resume specific conversations instantly.
-
Schema Automation: Implemented checkpointer.setup(), which automatically architects the required relational tables (checkpoints, checkpoint_blobs, checkpoint_writes) within the database.
-
Long-Term Memory: Successfully moved the agent's "brain" from temporary memory to a permanent disk-based storage system.
-
Session Resumption: Enabled the ability to stop the Python process and resume a complex debugging task hours later without losing progress.
-
Environment-Driven Security: Decoupled sensitive database credentials from the logic layer by implementing a secure .env configuration.
-
Multi-User Scalability: Established a foundation where unique thread_id values allow one agent instance to manage hundreds of independent, persistent conversations.
Goal: Build a production-ready autonomous research agent that leverages real-time web browsing and automated file persistence to synthesize complex topics into structured notes.
Today’s milestone marks the transition from simple chat loops to a Multi-Tool Orchestration system. The agent acts as a controller, deciding which tools to call and when the research objective has been met.
- Search Node (
duckduckgo_search): Provides the agent with live access to the internet, bypassing the LLM's static knowledge cutoff. - Logic Engine (Gemini 1.5 Flash): Acts as the "Reasoning Layer." It evaluates search results to determine if the user's query is fully answered or if further searching is required.
- Persistence Node (
save_research_note): A custom tool decorated with@toolthat allows the agent to interact with the local file system to save markdown notes. - Custom Router: A manual routing node that inspects the state for
tool_calls. This determines the flow: Agent ➔ Router ➔ Action (Tools) ➔ Agent.
- Autonomous Tool Use: Successfully implemented the ReAct (Reasoning and Acting) pattern where the LLM independently decides to use search or save tools.
- Dynamic File Management: The agent demonstrated "Creative Agency" by intelligently renaming files (e.g.,
ai_trends.md) based on research context rather than just using generic defaults. - Manual Routing Logic: Built a custom router function to manage the graph flow, providing more transparency and control than prebuilt conditions.
- Persistent Research: Continued using the MySQL Checkpointer from Day 13, ensuring that even complex, multi-step research sessions are durable and resumable.
Goal: Transition from a "Swiss Army Knife" single agent to a professional "Kitchen Staff" architecture. Today's goal was to build a system where a central Supervisor coordinates specialized Researcher and Writer agents to produce a technical report.
In this design, agents don't talk to each other directly (Choreography); instead, they report back to a central "Brain" (Orchestration).
- User Input: "Research 2026 tech trends and save to report.md."
- Supervisor: Analyzes the state and delegates the task to the Researcher.
- Researcher: Executes parallel web searches and returns the data.
- Supervisor: Sees the data is ready and delegates the task to the Writer.
- Writer: Formats the findings and uses the
write_filetool. - Supervisor: Detects completion and signals the end of the process.
Problem: Gemini's API enforces a strict "User-Assistant-Tool" sequence. In multi-agent loops, the history often results in multiple "Assistant" turns in a row, causing a 400 INVALID_ARGUMENT error.
Fix: Implemented a Context Reset. Before invoking a worker, we wrap the relevant history into a fresh HumanMessage. This "tricks" the model into seeing a new user turn, ensuring API compliance.
Problem: The Researcher often calls multiple tools simultaneously. LangGraph stores these as a list of messages, which caused an AttributeError when trying to access .content directly.
Fix: Added a robust check in the Supervisor to detect list objects and join the contents into a single string for analysis.
- Iteration Control: Added a safety counter to prevent infinite loops (set to 1 full cycle).
- Specialized Prompting: Each worker has a narrow scope, increasing accuracy and reducing "context dilution."
- State Management: Uses a shared
TypedDictstate to pass the "baton" between agents.
Goal: Transition from manual graph-based agents to a high-level Agentic Framework. Today, I built a two-agent "Crew" consisting of a Senior Research Analyst and a Tech Content Strategist to automate the end-to-end process of researching and reporting on emerging tech trends.
Unlike simple chains, CrewAI uses a "Role-Playing" architecture where agents are defined by their Role, Goal, and Backstory. This provides a much deeper cognitive context for the LLM.
- Senior Research Analyst: - Goal: Uncover cutting-edge developments in a specific topic.
- Tools: Powered by
SerperDevToolfor real-time Google Search access.
- Tools: Powered by
- Tech Content Strategist: - Goal: Translate complex research into an engaging, 3-point blog post.
- Handoff: Automatically receives the Researcher's output as its input.
I implemented a Process.sequential workflow. This ensures a strict linear progression:
- Task 1 (Research): Analyzes 2026 breakthroughs and outputs 3 key findings.
- Task 2 (Writing): Takes those specific findings and formats them into a professional markdown report.
By using the langchain_google_genai provider, I integrated Gemini 1.5 Flash as the brain for both agents. This allows for high-speed reasoning while maintaining low token costs.
Instead of just printing to the console, I configured the final task with the output_file="crew_report.md" parameter. This ensures the agentic workflow results in a tangible asset saved directly to the local workspace.
- Framework over Logic: CrewAI abstracts away the "state management" and "router" logic needed in LangGraph, allowing the developer to focus on Agent Personas.
- Expected Output: Defining the
expected_outputfor each task is the most critical step to prevent agent "hallucination" or scope creep. - Tooling: Adding the
SerperDevTooleffectively gave the agents "eyes" on the current internet, bridging the gap between training data and real-time facts.
Goal: Implement a "Concierge Pattern" using LangGraph. Today, I built a system that uses an LLM-based Router to dynamically triage user requests to specialized expert nodes (Math vs. Creative) using the modern Command pattern.
Unlike basic linear chains, this graph uses a "Zero-Edge" approach for internal routing.
- Router (Concierge): Uses Gemini 3 Flash to analyze intent. Instead of hard-coded keywords, it intelligently understands whether a query requires logic/math or creative writing.
- Specialists: Two distinct nodes (
math_expertandcreative_expert) that only execute when called by the Router. - Command Pattern: Utilized the
langgraph.types.Commandobject to handle both the state update and the navigation (goto) in a single return statement.
-
LLM-Based Triage Moved away from fragile
if "math" in querychecks. By using a small "Router Prompt," the system can now handle complex natural language (e.g., "What is 15% of 450?") and route it correctly. -
Handling Multimodal Content Blocks Navigated the Gemini 3 Flash output structure. Since the model returns a
list[dict]for content (to support text + image blocks), I implemented direct indexing to extract thedecision_textcleanly. -
State Management Used the built-in
MessagesStateto maintain a clean chat history while allowing the specialists to access the original human query through simple list indexing (state["messages"][0]).
Today, I moved from simple "Agents" to a "Self-Healing Organization." My system uses a Hierarchical Process nested inside a CrewAI Flow.
- Orchestration: CrewAI Flows (Stateful logic)
- Management: Hierarchical Process (Manager LLM)
- Validation: LLM-based Router for automated Quality Assurance.
- Template Strings: Learned why plain strings are safer than f-strings when passing data from
kickofftoTasks. - Stateful Memory: Used
Pydanticto maintain aretry_countandfeedbackloop across multiple execution attempts. - Encapsulation: Used
__init__to hire agents only when the department is called.
Goal: To build an automated SDLC (Software Development Life Cycle) using a multi-agent "Department" nested within a stateful Flow.
I implemented a Creator vs. Critic pattern to ensure high-quality, peer-reviewed output.
- Coder Agent (Senior Dev): Writes the implementation based on requirements.
- Reviewer Agent (Security/QA): A skeptical peer hunting for bugs, PEP8 issues, and security holes.
- The Flow (Project Manager): Tracks
DevState(retry counts, feedback, and code). - The Loop: If the Reviewer finds issues, the Flow captures the
review_feedbackand triggers a re-coding phase.
- Stateful Memory: Used
Pydanticto carry feedback across loops so the Coder learns from mistakes. - Nested Hierarchy: Utilized
Process.hierarchicalinside the Crew to allow a Manager LLM to oversee the handoff. - Refactor Logic: An automated
@routerdetermines if the code is "Deployable" or "Needs Fix."
Quality increases exponentially when you give one agent the explicit goal to find faults. By setting the Reviewer's backstory to "Paranoid Security Engineer," the final code is documented, type-hinted, and robust.
Goal: To migrate the SDLC workflow from CrewAI to LangGraph to implement enterprise-grade Human-in-the-Loop (HITL) and state persistence.
I shifted from simple orchestration to a State Machine architecture where the human acts as the final gatekeeper.
- State Management: Defined a
TypedDictstate to track requirements, code snippets, and approval status across the lifecycle. - The Checkpointer: Integrated
MemorySaverto provide "Time Travel" capabilities—the graph can be saved and resumed without losing context. - The Breakpoint: Configured
interrupt_beforeon a dedicatedhuman_approvalnode, forcing the AI to stop and wait for manual verification. - The Feedback Loop: If the human provides feedback instead of approval, the graph uses
update_stateto inject comments and "rewinds" the execution pointer.
- Node-Based Logic: Rebuilt Agents as functional nodes, allowing for precise control over input/output data structures.
- Conditional Routing: Implemented a logic-based router that evaluates boolean state variables to determine if the graph finishes or recurses.
- State Injection: Mastered the use of
as_nodein state updates to manually trigger specific paths in the graph's logic. - Defensive Parsing: Created a robust streaming loop to handle Gemini's multi-modal message formats (handling both list and string content types).
The transition from "Scripting" to "Graph Engineering" is the bridge to production AI. By using Checkpoints and Interrupts, I've moved away from a "Black Box" agent toward a transparent, auditable system where a human can steer the AI's logic in real-time. This level of control is what separates a prototype from a professional AI product.
Goal: To build a self-correcting "Social Media Manager" using the ReAct (Reasoning + Acting) pattern and DuckDuckGo search integration.
I moved beyond static generation by giving the agent "hands" to fetch real-time data.
- Researcher Node: Evaluates the topic and determines if it needs more information. It triggers tool calls dynamically.
- Tool Node (DuckDuckGo): A dedicated node that executes search queries and returns observations back to the graph state.
- The "Circuit Breaker" Logic: Implemented a search counter to prevent infinite loops, forcing a transition to the Creator node if the agent gets stuck in a "search spiral."
- State Reducers: Used
Annotated[list, add_messages]to ensure the agent maintains a continuous memory of its research findings.
- Gemini Turn-Order Fix: Solved the
ValueError: contents are requiredby ensuring every tool request is preceded by aHumanMessageto satisfy Gemini's strict turn-taking requirements. - Conditional Routing: Implemented
tools_conditionfrom LangGraph's prebuilt library to manage the handoff between the LLM and the search tool. - Recursive Debugging: Configured the graph to allow the Researcher to see its own previous results, enabling it to "refine" its search if the first results were insufficient.
The most difficult part of Tool Augmentation isn't the API call—it's State Management. Ensuring the agent "remembers" the tool's output and doesn't get caught in an infinite loop requires precise control over the message history and the use of message reducers.
Goal: To transition from "black box" development to a data-driven engineering workflow by implementing full-stack observability and automated testing with LangSmith.
I moved from simply running code to "auditing" every decision the LLM makes through a dedicated observability pipeline.
-
Manual Graph Construction: Avoided deprecated agent executors to build a raw
StateGraph. This allows for a granular view in LangSmith, where each node (Agent vs. Tools) is timestamped and tracked individually. -
Explicit Router Logic: Instead of using "magic" prebuilt conditions, I mapped the
tools_conditionto explicitENDandtoolsedges. This ensures the trace accurately reflects the branching logic of the ReAct pattern. -
The "Golden" Dataset: Captured successful traces and converted them into a version-controlled benchmark. This creates a "ground truth" that the agent must satisfy even as the underlying model or prompts change.
-
LLM-as-a-Judge: Implemented an automated evaluation script (
eval_test.py) using Gemini 2.5 Flash as a "Judge" to grade the performance of Gemini 3 Flash. This provides a quantitative "Relevance" score for every run. -
Path-Aware Dotenv Loading: Solved directory-scoping issues by implementing explicit pathing for
.envfiles, ensuring that the tracing configuration is active regardless of where the script is executed.
The real shift in Day 22 was realizing that AI Engineering is 20% prompting and 80% evaluation. Without observability, you are just "vibing" with your prompts. By building a baseline dataset and an automated judge, I can now mathematically prove if a prompt change actually improves the system or just changes the style.
Goal: Today’s focus was transitioning from an autonomous agent to a steerable, safe agent. I implemented a system that pauses for human intervention and utilizes hard-coded guardrails to prevent tool-calling hallucinations or policy violations.
Unlike a standard ReAct loop, this workflow introduces a Stateful Checkpointer and an Interrupt Node.
- Persistence Layer: Used
InMemorySaverto enable state persistence. This allows the graph to be paused and resumed across different sessions using athread_id. - The Interrupt Pattern: Implemented the
interrupt()function to halt execution before sensitive tool calls (Social Media posting). - Neurosymbolic Guardrail: A hybrid approach using Python logic (
output_guardrail) to filter LLM-generated tool arguments for banned keywords before they ever reach the user for approval.
The agent now follows a "Trust but Verify" model. When it decides to use a tool, it doesn't execute immediately. Instead, it enters a human_approval node that triggers an __interrupt__.
I implemented a safety filter that acts as a "hard law" the LLM cannot bypass. If the agent tries to post about "crypto-scams" or "spam," the guardrail triggers an automatic rejection.
-
Safe Execution: Valid posts require a "yes" to proceed.
-
Auto-Rejection: Banned words trigger an immediate "Rewrite" loop without human effort.
-
Audit Trail: LangSmith's Trace Tree shows the exact gap where the human review occurred.
Today I implemented Entity Memory, moving beyond linear chat history to building a persistent, structured profile of the user.
I developed two versions to compare data handling:
- Unstructured (Text): LLM summarizes facts into a descriptive paragraph.
- Structured (JSON): Uses Pydantic to categorize facts into a strict schema (Interests, Location, Restrictions).
- Extraction Node: An LLM-based "Secretary" node that runs before the agent to update the user profile.
- Pydantic Enforcement: Forces the AI to extract facts into a predictable dictionary format.
- Context Injection: The structured profile is injected into the System Message, ensuring the agent never "forgets" key user traits during long sessions.
- Personalization: The agent now connects dots across messages (e.g., suggesting a restaurant based on a location mentioned 20 turns ago).
- Deterministic Logic: Because the memory is JSON, I can now use standard Python logic (if/else) based on user attributes.
Goal: Today I implemented a CFO for my AI. In production, using a high-reasoning model for a simple "Hello" is a waste of resources. I built an Intelligent Router that tiers LLM workloads based on task complexity.
The system acts as a traffic controller, directing queries to the most cost-effective "Specialist":
- Gemini 2.5 Flash Lite (The Dispatcher): An ultra-fast, low-cost model that classifies user intent.
- Gemini 3 Flash (The Specialist): A higher-reasoning model invoked only when the task requires coding, analysis, or complex logic.
- Classification Node: A "Gatekeeper" node that prompts the small model to return a single-word decision:
easyorcomplex. - Conditional Branching: Leveraged LangGraph's
add_conditional_edgesto physically route the state to different specialist nodes. - Dynamic State: The
complexitykey in the state dictionary dictates the path, ensuring the "Large" model is only billed when necessary.
- Cost Efficiency: Simple greetings and basic Q&A now run at a fraction of the cost of complex reasoning tasks.
- Reduced Latency: Flash Lite provides near-instant routing decisions, making the overall system feel significantly faster for the end-user.
- Scalability: This architecture allows for adding even more tiers (e.g., an "Ultra" tier for math-heavy tasks) without re-engineering the entire graph.
Goal: Today I transitioned the LangGraph agent from a local script into a multi-service API architecture. I built a custom FastAPI backend for client interactions and a LangServe instance for developer tools.
I decoupled the core logic from the interface to allow for independent scaling and testing:
agent.py: The "Brain." Contains the LangGraph definition and the logic for clearing message history.main.py: The "Client API." A custom FastAPI instance running on Port 8000 for standard user requests.main_langserve.py: The "Developer API." A LangServe instance running on Port 8001 for native streaming and a visual Playground.
I successfully deployed two parallel FastAPI applications to isolate development tools from user traffic:
- Port 8000: Clean REST endpoints (
POST /chatandDELETE /chat). - Port 8001: Automatic
/agent/playgroundfor real-time visual debugging.
I implemented a "Nuclear Reset" function that wipes conversation history from the checkpointer.
- Method: Used
RemoveMessageto target and delete specific message IDs. - Verification: Verified via terminal logs:
Memory Status: 0 messages remaining.
Integrated thread_id into all API calls to ensure the agent can maintain isolated conversation states for multiple users simultaneously.
| Endpoint | Method | Action |
|---|---|---|
http://localhost:8000/chat |
POST | Sends a message to the agent using a unique thread_id. |
http://localhost:8000/chat/{id} |
DELETE | Wipes the entire memory for a specific user ID. |
http://localhost:8001/agent/playground |
GET | Opens the visual UI to watch the agent execute graph nodes. |
Goal: Today I solved the "Long Wait" problem. Instead of making users wait for the entire AI completion, I implemented Server-Sent Events (SSE) to stream tokens in real-time.
-
FastAPI
StreamingResponse: Configured the API to hold an open connection using thetext/event-streammedia type. -
astream_events(v2): Leveraged LangGraph's event-driven streaming to filter foron_chat_model_streamevents, ensuring only raw LLM content is sent to the UI. -
Asynchronous Generators: Used
async forandyieldto push data chunks without blocking the server. -
Backend: Used
astream_events(version="v2")to intercept LLM tokens. -
Data Extraction: Handled Gemini's multimodal chunk format (
[{'text': '...'}]) by extracting the raw string in the FastAPI generator. -
Protocol: Implemented Server-Sent Events (SSE) with
StreamingResponse. -
Frontend: Built a JavaScript consumer that uses
fetch,Reader, andJSON.parseto decode and append text chunks to the UI dynamically.
- TTFT (Time to First Token): Reduced from ~5-10 seconds to <200ms.
- UX: Added a smooth, character-by-character typing experience.
Goal: Today I kicked off the Final Capstone Project. I transitioned from a basic chatbot to a functional Autonomous Agent that can browse the live web, research companies, and draft personalized outreach emails.
- Manual ReAct Graph: Built a custom state machine from scratch using LangGraph, avoiding high-level abstractions to gain full control over the "Reasoning + Acting" cycle.
- Tool Binding: Integrated DuckDuckGo Search directly into the Gemini 1.5 Flash model using
.bind_tools(), allowing the LLM to autonomously decide when it needs external data. - Stateful Messaging: Implemented
MessagesStatewithadd_messagesto ensure the agent "remembers" its research findings while drafting the final email. - Conditional Routing: Created a
should_continuelogic gate that inspects LLM outputs fortool_callsand routes the flow between the "Brain" (Model) and the "Hands" (Tools).
- The Brain (
call_model): Injected a specializedSystemMessageto define the agent's persona as a Corporate Researcher, ensuring it follows a "Research → Analyze → Draft" workflow. - The Hands (
call_tool): Developed a manual execution node that iterates through LLM-generated search queries, fetches live 2026 data, and returnsToolMessageobjects to the graph. - Streaming Feedback: Enhanced the FastAPI backend to yield
on_tool_startevents, allowing the UI to show a "🔍 Searching..." status indicator during web latency. - FastAPI Integration: Separated the core agent logic (
agent.py) from the delivery layer (main.py) for a professional, modular architecture.
- Autonomy: The agent successfully researches topics it wasn't trained on (e.g., NVIDIA's 2026 Rubin architecture) and synthesizes them into context-aware emails
- Efficiency: Reduced a 15-minute manual research task into a <15-second automated workflow.
- Reliability: By using a manual graph, I eliminated "looping" bugs and ensured the agent always returns to the user with a final answer.
Goal: Today I finalized the Capstone by building a professional-grade frontend and a secondary "Auditor" agent. I transitioned from a basic HTML interface to a Reactive Streamlit Dashboard that features real-time token streaming and an automated quality guardrail system.
- Reactive Frontend: Built a multi-component UI using Streamlit, implementing
st.chat_messageandst.emptyto handle live-streaming text blocks directly from the backend. - Server-Sent Events (SSE): Optimized the FastAPI layer to process generator-based responses, allowing the UI to "type out" the agent's research results in real-time rather than waiting for a full buffer.
- Dual-Agent Architecture: Implemented a "Judge-on-the-Side" pattern where a second LLM instance evaluates the first agent's output for Fact Density and Professionalism scores.
- Defensive JSON Parsing: Developed a robust "No-Regex" cleaning logic using
.find()and.rfind()to extract structured data from LLM responses, ensuring the UI never crashes on "chatty" AI outputs.
- The Cockpit (
app_ui.py): Designed a dashboard with a persistent Sidebar Report Card, providing users with instant transparency into the agent's performance metrics (1-10 scale). - The Auditor (
evaluate_output): Engineered a specialized evaluation function that operates attemperature=0.1to provide objective, consistent grading of partnership emails. - Streaming Logic: Used
requests.post(stream=True)to bridge the gap between the FastAPI/chatendpoint and the Streamlit frontend, maintaining a low-latency user experience. - Safety Guardrails: Integrated a "Security Block" check that identifies non-corporate or high-risk prompts (like "hacking"), preventing the agent from executing tools on malicious intent.
- Production Quality: Created a decoupled architecture (Frontend/Backend) that mimics real-world AI software deployments.
- Self-Correction: The "Agent Report Card" successfully identifies when research is thin, flagging drafts that need more specific "SMART" goals before being sent.
- Resilience: The system successfully handles complex JSON outputs and "Silent Failures," providing clear
st.errororst.warningmessages instead of crashing.
Developed by Makarand Thorat