Skip to content

Latest commit

 

History

History
154 lines (119 loc) · 6.69 KB

LANGGRAPH_FLOW.md

File metadata and controls

154 lines (119 loc) · 6.69 KB

Multi-Agent Architecture & LangGraph Flow

This document explains the complete execution flow of your AI Security & Quality Scanner. It breaks down how LangChain and LangGraph work together to orchestrate multiple AI agents, manage shared memory, and execute tools.


1. The Core Concept: The Graph and The State

In LangGraph, your application is a State Machine.

  • Nodes represent the agents or Python functions doing the work.
  • Edges define the flow of execution from one node to the next.
  • State is the shared memory that gets passed along the edges to every node.

The AgentState (Shared Memory)

Agents do not "talk" to each other directly like humans in a chatroom. Instead, they communicate by reading from and writing to a shared state object.

from typing import TypedDict
from pydantic import BaseModel

class Agents(BaseModel):
    security_agent: bool
    logic_agent: bool
    code_quality_agent: bool

class AgentState(TypedDict):
    user_query: str          # Written by Chat node
    project_path: str        # Written by CLI
    agents_required: Agents  # Written by Planner node
    
    project_map: str         # Written by Scanner node
    plan: str                # Written by Planner node

    security_report: str     # Written by Security agent
    logic_report: str        # Written by Logic agent
    quality_report: str      # Written by Code Quality agent

    final_report: str        # Written by Report node

How State Updates Work: When a node finishes, it returns a Python dictionary. LangGraph takes that dictionary and merges it into the global state. For example, if the state is currently {"project_path": "/my/app"} and the Scanner node returns {"project_map": "file1.py, file2.py"}, the new state becomes: {"project_path": "/my/app", "project_map": "file1.py, file2.py"}.


2. Step-by-Step Execution Flow

Here is the exact journey of a single user request through your system:

Step 1: CLI Collection (Outside the Graph)

  1. The user runs python cli.py.
  2. The user chats with the chat_agent to clarify their problem.
  3. The user provides the project_path.
  4. The CLI packages this into the initial state and starts the graph: app_graph.stream({"user_query": query, "project_path": path})

Step 2: The Chat Node (Summarization)

The graph starts at START and flows into the chat node.

def chat(state: AgentState):
    # Reads: state["user_query"] (The raw chat history)
    # Action: Asks an LLM to summarize the history into a concise goal.
    # Writes: state["user_query"] (Overwrites it with the clean summary)
    return {"user_query": clean_summary}

Step 3: The Scanner Node

The graph flows from chat to scanner.

def project_scanner_node(state: AgentState):
    # Reads: state["user_query"] and state["project_path"]
    # Action: The scanner agent looks at the codebase to understand the structure.
    # Writes: state["project_map"]
    return {"project_map": result}

Step 4: The Planner Node & Conditional Routing

The graph flows from scanner to planner. This is where the magic happens.

  1. The planner_agent reads the project_map and writes a step-by-step plan.
  2. We use .with_structured_output(Agents) to force an LLM to read that plan and flip boolean switches for which agents are actually needed.
def planner_node(state: AgentState):
    # Reads: state["user_query"] and state["project_map"]
    # Writes: state["plan"] and state["agents_required"]
    return {"plan": plan_text, "agents_required": agents_required_object}

The Conditional Edge: Instead of a normal edge, the planner uses a conditional edge. LangGraph calls the route_to_agents function:

def route_to_agents(state: AgentState):
    destinations = []
    # Reads the boolean flags saved by the planner
    if state["agents_required"].security_agent:
        destinations.append("security")
    # ... checks logic and code_quality ...
    
    return destinations # LangGraph routes to these nodes in PARALLEL

Step 5: Specialist Agents (Parallel Execution)

If the planner requested security and logic, LangGraph spins them up at the same time.

  • Security Node reads the project_map and plan, hunts for vulnerabilities, and returns {"security_report": "..."}
  • Logic Node reads the project_map and plan, looks for business logic bugs, and returns {"logic_report": "..."}

Step 6: The Report Node

Once all active specialist agents finish, the graph converges on the report node.

def report_node(state: AgentState):
    # Reads: ALL the reports generated by the specialists
    # Action: Combines them into one giant markdown string
    # Writes: state["final_report"]
    return {"final_report": report_text}

The graph hits END, and the CLI prints the final_report to the screen.


3. How Agents & Tools Work

Inside the nodes, you invoke actual Agents (like security_agent). But what exactly is an Agent?

An Agent is an LLM that has been given a system_prompt and a list of tools.

security_agent = create_agent(
    model="google_genai:gemini-3.1-flash-lite",
    tools=[run_terminal],
    system_prompt="You are a Security Agent..."
)

The Tool Execution Loop:

When you call security_agent.invoke(...), the LLM doesn't just spit out text immediately. It enters a ReAct (Reason + Act) loop:

  1. Think: The LLM reads the user prompt and its system prompt. It realizes it needs to search the codebase for API keys.
  2. Act (Tool Call): The LLM outputs a JSON command saying: "Execute run_terminal with the argument grep -r 'API_KEY' ."
  3. Execute: The LangChain AgentExecutor catches this request, pauses the LLM, runs your Python run_terminal function, and captures the terminal output.
  4. Observe: The AgentExecutor feeds the terminal output back into the LLM as a new message.
  5. Repeat: The LLM reads the output. If it needs more information, it calls another tool. If it has enough information, it writes the final security report.

This all happens invisibly inside the result = security_agent.invoke(...) call in your nodes!


Summary of the Architecture

  1. LangGraph acts as the manager. It holds the shared state and decides which Python function (node) runs next based on the edges.
  2. Python Nodes act as the wrappers. They take the current state, package it into a prompt, and hand it to an Agent.
  3. LangChain Agents do the heavy lifting. They receive the prompt, use Tools to interact with the real world (running terminal commands, reading files), and return an answer.
  4. The wrapper Node takes that answer and updates the State, passing the baton to the next node in the graph.