chatbot / src /Agentic_System /docs /agent_architecture_diagrams.html
jawadsaghir12's picture
new update
a66d4bd
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Agent System Architecture β€” Complete Blueprint</title>
<script src="https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.min.js"></script>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: #0d1117;
color: #e6edf3;
padding: 40px 60px;
line-height: 1.6;
}
h1 {
font-size: 2.2rem;
margin-bottom: 8px;
background: linear-gradient(90deg, #58a6ff, #bc8cff);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.subtitle {
color: #8b949e;
font-size: 1rem;
margin-bottom: 40px;
border-bottom: 1px solid #21262d;
padding-bottom: 20px;
}
.diagram-section {
background: #161b22;
border: 1px solid #30363d;
border-radius: 12px;
padding: 32px;
margin-bottom: 40px;
page-break-inside: avoid;
}
.diagram-section h2 {
font-size: 1.5rem;
color: #58a6ff;
margin-bottom: 8px;
}
.diagram-section p {
color: #8b949e;
margin-bottom: 20px;
font-size: 0.95rem;
}
.mermaid {
background: #0d1117;
border-radius: 8px;
padding: 20px;
overflow-x: auto;
}
table {
width: 100%;
border-collapse: collapse;
margin: 16px 0;
}
th, td {
padding: 10px 16px;
text-align: left;
border: 1px solid #30363d;
}
th {
background: #21262d;
color: #58a6ff;
font-weight: 600;
}
td { color: #c9d1d9; }
.file-ref {
font-family: 'Cascadia Code', 'Fira Code', monospace;
background: #21262d;
padding: 2px 8px;
border-radius: 4px;
font-size: 0.85rem;
color: #bc8cff;
}
.timing-table td:last-child {
font-family: monospace;
color: #7ee787;
}
.legend {
display: flex;
gap: 20px;
flex-wrap: wrap;
margin-top: 12px;
}
.legend-item {
display: flex;
align-items: center;
gap: 6px;
font-size: 0.85rem;
color: #8b949e;
}
.legend-dot {
width: 12px;
height: 12px;
border-radius: 3px;
}
.print-btn {
position: fixed;
top: 20px;
right: 20px;
background: #238636;
color: white;
border: none;
padding: 10px 24px;
border-radius: 8px;
cursor: pointer;
font-size: 0.95rem;
font-weight: 600;
z-index: 100;
transition: background 0.2s;
}
.print-btn:hover { background: #2ea043; }
@media print {
body { background: white; color: #1f2328; padding: 20px; }
.diagram-section { border-color: #d1d9e0; background: #f6f8fa; }
.mermaid { background: white; }
h1 { background: none; -webkit-text-fill-color: #1f2328; }
.diagram-section h2 { color: #0969da; }
.subtitle { color: #656d76; border-color: #d1d9e0; }
th { background: #f6f8fa; color: #0969da; }
td { color: #1f2328; }
.print-btn { display: none; }
}
</style>
</head>
<body>
<button class="print-btn" onclick="window.print()">πŸ“„ Save as PDF</button>
<h1>Agent System Architecture β€” Complete Blueprint</h1>
<p class="subtitle">
Multi-Agent Google Workspace System &nbsp;|&nbsp;
OpenAI Agent SDK + MCP Protocol + OpenRouter LLM &nbsp;|&nbsp;
Generated February 2026
</p>
<!-- ═══════════════════════════════════════════════════════════════════ -->
<!-- DIAGRAM 1: High-Level Overview -->
<!-- ═══════════════════════════════════════════════════════════════════ -->
<div class="diagram-section">
<h2>1. High-Level Architecture Overview</h2>
<p>
The complete request flow from user to Google API and back.
Two nested Agent loops β€” an <strong>Outer Orchestrator</strong> with 6 function_tools,
and <strong>Inner Sub-Agents</strong> with MCP tools β€” both talk to the same LLM.
</p>
<pre class="mermaid">
flowchart TB
subgraph USER["πŸ‘€ User Layer"]
UI["Browser / Chat UI"]
API["FastAPI Backend\n/api/chat"]
end
subgraph ORCH["🧠 ORCHESTRATOR AGENT β€” Outer Agent"]
direction TB
SVC["service(query)"]
CACHE["LRU Cache\n100 entries Β· 5min TTL"]
ENSURE["_ensure_connection()"]
AGENT["Agent(\nname='Assistant'\nmodel=trinity-large-preview:free\ntools=6 function_tools\n)"]
RUNNER["Runner.run(agent, query)\n→ sends to OpenRouter LLM"]
end
subgraph TOOLS["πŸ”§ 6 function_tool Wrappers β€” Registered on Outer Agent"]
direction LR
FT1["google_sheets_task()"]
FT2["google_docs_task()"]
FT3["google_drive_task()"]
FT4["google_calendar_task()"]
FT5["gmail_task()"]
FT6["google_slides_task()"]
end
subgraph INNER["πŸ“¦ Inner Sub-Agent β€” e.g. GmailAgent"]
direction TB
INIT["GmailAgent.__init__()\nCreate AsyncOpenAI client"]
MCPOBJ["create_google_mcp_server()\n→ MCPServerStdio object"]
SUBPROCESS["async with mcp_server:\n→ Spawn MCP subprocess"]
LISTTOOL["mcp_server.list_tools()\n→ 15 Gmail tools"]
INNERAGENT["Agent(\nname='Gmail Agent'\nmcp_servers=[mcp_server]\nmodel=trinity-large-preview:free\n)"]
INNERRUN["Runner.run(inner_agent, query)\n→ LLM calls MCP tools"]
end
subgraph MCP["βš™οΈ Google MCP Server β€” Subprocess via stdio"]
direction TB
MAIN["main.py --tools gmail --single-user"]
OAUTH["OAuth Credential Store\n~/.google_workspace_mcp/credentials/"]
GAPI["Google Gmail API\nvia googleapis"]
end
subgraph LLM["☁️ OpenRouter LLM"]
MODEL["arcee-ai/trinity-large-preview:free\nhttps://openrouter.ai/api/v1"]
end
UI -->|"HTTP POST /chat"| API
API -->|"await service(query)"| SVC
SVC --> CACHE
CACHE -->|"miss"| ENSURE
ENSURE --> AGENT
AGENT --> RUNNER
RUNNER <-->|"Turn 1: query + 6 tool defs"| MODEL
MODEL -->|"function_call: gmail_task(query)"| FT5
FT5 --> INIT
INIT --> MCPOBJ
MCPOBJ --> SUBPROCESS
SUBPROCESS --> LISTTOOL
LISTTOOL --> INNERAGENT
INNERAGENT --> INNERRUN
INNERRUN <-->|"Turn 1: query + 15 MCP tool defs"| MODEL
MODEL -->|"function_call: search_gmail_messages"| SUBPROCESS
SUBPROCESS -->|"stdio JSON-RPC"| MAIN
MAIN --> OAUTH
OAUTH -->|"auto-refresh token"| GAPI
GAPI -->|"Gmail results"| MAIN
MAIN -->|"stdio response"| SUBPROCESS
SUBPROCESS -->|"tool result β†’ Turn 2"| MODEL
MODEL -->|"final text answer"| INNERRUN
INNERRUN -->|"result.final_output"| FT5
FT5 -->|"return string"| RUNNER
RUNNER -->|"Turn 2 with tool result"| MODEL
MODEL -->|"final formatted answer"| RUNNER
RUNNER -->|"result.final_output"| SVC
SVC -->|"cache + return"| API
API -->|"JSON response"| UI
</pre>
</div>
<!-- ═══════════════════════════════════════════════════════════════════ -->
<!-- DIAGRAM 2: Detailed Sequence -->
<!-- ═══════════════════════════════════════════════════════════════════ -->
<div class="diagram-section">
<h2>2. Detailed Request Sequence β€” "Show my unread emails"</h2>
<p>
Step-by-step message flow with exact timing from real test runs (~20s total).
</p>
<table class="timing-table">
<tr><th>Phase</th><th>What Happens</th><th>Time</th></tr>
<tr><td>Outer Turn 1</td><td>LLM receives query + 6 tool schemas β†’ picks <code>gmail_task</code></td><td>~5s</td></tr>
<tr><td>Inner Setup</td><td>GmailAgent spawns MCP subprocess, loads 15 tools</td><td>~2.5s</td></tr>
<tr><td>Inner Turn 1</td><td>LLM receives query + 15 Gmail tool schemas β†’ picks <code>search_gmail_messages</code></td><td>~4s</td></tr>
<tr><td>MCP Execution</td><td>JSON-RPC over stdio β†’ OAuth auto-refresh β†’ Gmail API call</td><td>~1s</td></tr>
<tr><td>Inner Turn 2</td><td>LLM formats raw Gmail results into readable text</td><td>~4s</td></tr>
<tr><td>Outer Turn 2</td><td>LLM wraps sub-agent response for the user</td><td>~5s</td></tr>
</table>
<pre class="mermaid">
sequenceDiagram
autonumber
participant U as πŸ‘€ User
participant API as FastAPI
participant SVC as service()
participant Cache as LRU Cache
participant OA as Outer Agent<br/>(Assistant)
participant OR as OpenRouter LLM<br/>(trinity-large)
participant FT as gmail_task()<br/>function_tool
participant GA as GmailAgent
participant MF as MCP Factory<br/>google_mcp_config
participant MS as MCPServerStdio<br/>(subprocess)
participant GS as google-mcp-server<br/>main.py
participant OAuth as OAuth Store<br/>credentials/
participant Gmail as Google Gmail<br/>API
U->>API: POST /api/chat {message: "show unread emails"}
API->>SVC: await service(query)
SVC->>Cache: _get_cached_response(query)
Cache-->>SVC: None (cache miss)
SVC->>OA: _ensure_connection() β†’ Agent created once
Note over OA: Agent with 6 function_tools:<br/>sheets, docs, drive,<br/>calendar, gmail, slides
SVC->>OR: Runner.run(agent, query)<br/>Turn 1: query + 6 tool JSON schemas
Note over OR: LLM analyzes query:<br/>"show unread emails"<br/>β†’ matches gmail_task
OR-->>SVC: ResponseFunctionToolCall<br/>{name: "gmail_task", args: {query: "..."}}
Note over SVC: SDK auto-invokes<br/>the function_tool
SVC->>FT: gmail_task(query="Search unread emails...")
FT->>GA: GmailAgent() β†’ __init__
GA->>GA: Create AsyncOpenAI client<br/>(OpenRouter endpoint)
FT->>GA: await agent.run(query)
GA->>MF: create_google_mcp_server("gmail", GMAIL_TOOLS)
Note over MF: MCPServerStdio(<br/>command: python main.py<br/>args: --tools gmail --single-user<br/>cwd: google-mcp-server/<br/>tool_filter: 15 gmail tools<br/>)
MF-->>GA: MCPServerStdio object (not started yet)
GA->>MS: async with mcp_server: (START subprocess)
MS->>GS: spawn: python main.py --tools gmail --single-user
Note over GS: FastMCP server boots<br/>loads gmail tools only<br/>stdio transport ready<br/>(~2.5 seconds)
GS-->>MS: subprocess ready (stdio pipe open)
GA->>MS: await mcp_server.list_tools()
MS->>GS: JSON-RPC: tools/list
GS-->>MS: 15 tool definitions
MS-->>GA: [search_gmail_messages, get_gmail_message_content, ...]
Note over GA: create_static_tool_filter<br/>filters to exactly 15 allowed tools
GA->>GA: Create inner Agent<br/>(name="Gmail Agent", mcp_servers=[ms])
GA->>OR: Runner.run(inner_agent, query)<br/>Turn 1: query + 15 MCP tool schemas
Note over OR: LLM sees 15 Gmail tools<br/>picks: search_gmail_messages<br/>args: {query: "is:unread"}
OR-->>GA: ResponseFunctionToolCall<br/>{name: "search_gmail_messages"}
Note over GA: SDK auto-invokes<br/>MCP tool via call_tool()
GA->>MS: call_tool("search_gmail_messages", {query: "is:unread"})
MS->>GS: JSON-RPC: tools/call {search_gmail_messages}
GS->>OAuth: get_credential("aiwithjawadsaghir@gmail.com")
OAuth-->>GS: credentials (auto-refresh if expired)
GS->>Gmail: Gmail API: messages.list(q="is:unread")
Gmail-->>GS: {messages: [{id, threadId}, ...]}
GS-->>MS: JSON-RPC response: 10 unread messages
MS-->>GA: tool result: "Found 10 messages..."
GA->>OR: Turn 2: tool result + conversation history
Note over OR: LLM formats the<br/>Gmail results into<br/>human-readable text
OR-->>GA: "I found 10 unread emails..."
GA-->>FT: return result.final_output
MS->>MS: subprocess exits (async with ends)
FT-->>SVC: "I found 10 unread emails..."
SVC->>OR: Turn 2: function_tool result
Note over OR: LLM wraps the sub-agent<br/>response for the user
OR-->>SVC: Final formatted response
SVC->>Cache: _set_cached_response(query, response)
SVC-->>API: return response string
API-->>U: JSON {response: "I found 10 unread emails..."}
</pre>
</div>
<!-- ═══════════════════════════════════════════════════════════════════ -->
<!-- DIAGRAM 3: Component Relationships -->
<!-- ═══════════════════════════════════════════════════════════════════ -->
<div class="diagram-section">
<h2>3. Component & Module Relationships</h2>
<p>
How the source files, classes, and external services connect.
<span class="file-ref">google_mcp_config.py</span> is the shared config,
<span class="file-ref">Gmail_Agent.py</span> (and 5 siblings) are inner agents,
<span class="file-ref">Orchestrator_Agent.py</span> is the outer agent.
</p>
<pre class="mermaid">
flowchart TB
subgraph CONFIG["google_mcp_config.py β€” Shared Config"]
direction TB
C1["MCP_SERVER_DIR = ../google-mcp-server/"]
C2["MCP_PYTHON = .venv/Scripts/python.exe"]
C3["OPENROUTER_API_KEY / BASE_URL"]
C4["MODEL_NAME = arcee-ai/trinity-large-preview:free"]
C5["GMAIL_TOOLS = 15 tool names"]
C6["SHEETS_TOOLS = 14 tool names"]
C7["DOCS_TOOLS = 19 tool names"]
C8["DRIVE_TOOLS = 17 tool names"]
C9["CALENDAR_TOOLS = 6 tool names"]
C10["SLIDES_TOOLS = 9 tool names"]
CF["create_google_mcp_server(service, tool_names)\n→ MCPServerStdio"]
end
subgraph AGENTS["6 Specialized Agent Classes"]
direction TB
A1["GoogleSheetsAgent β€” 14 tools via MCP"]
A2["GoogleDocsAgent β€” 19 tools via MCP"]
A3["GoogleDriveAgent β€” 17 tools via MCP"]
A4["GoogleCalendarAgent β€” 6 tools via MCP"]
A5["GmailAgent β€” 15 tools via MCP"]
A6["GoogleSlidesAgent β€” 9 tools via MCP"]
end
subgraph ORCH_FILE["Orchestrator_Agent.py"]
direction TB
O1["@function_tool google_sheets_task"]
O2["@function_tool google_docs_task"]
O3["@function_tool google_drive_task"]
O4["@function_tool google_calendar_task"]
O5["@function_tool gmail_task"]
O6["@function_tool google_slides_task"]
OC["_create_agent() β†’ Agent with 6 tools"]
OS["service(query) β†’ response string"]
OSS["service_streaming(query) β†’ async generator"]
end
subgraph SDK["OpenAI Agent SDK"]
direction TB
S1["Agent β€” wraps model + tools + instructions"]
S2["Runner.run() β€” multi-turn loop"]
S3["MCPServerStdio β€” subprocess manager"]
S4["create_static_tool_filter β€” whitelist tools"]
S5["function_tool β€” decorator for Python functions"]
S6["OpenAIChatCompletionsModel β€” LLM adapter"]
end
subgraph EXTERNAL["External Services"]
direction LR
E1["OpenRouter API\n(LLM inference)"]
E2["Google APIs\n(Gmail, Drive, Docs, etc.)"]
E3["OAuth2 Credentials\n(local file store)"]
end
CF --> A1 & A2 & A3 & A4 & A5 & A6
O1 --> A1
O2 --> A2
O3 --> A3
O4 --> A4
O5 --> A5
O6 --> A6
OC --> O1 & O2 & O3 & O4 & O5 & O6
OS --> OC
A5 --> S3
S3 --> S4
OC --> S1
OS --> S2
S2 --> S6
S6 --> E1
S3 --> E2
S3 --> E3
</pre>
</div>
<!-- ═══════════════════════════════════════════════════════════════════ -->
<!-- DIAGRAM 4: Runner.run() Turn Loop -->
<!-- ═══════════════════════════════════════════════════════════════════ -->
<div class="diagram-section">
<h2>4. Runner.run() Turn Loop β€” How the Agent Loops Internally</h2>
<p>
<code>Runner.run()</code> is a <strong>multi-turn loop</strong>. It sends the query + tool schemas to the LLM,
checks if the response is text or a tool call, executes tools, and loops back. This loop runs
<strong>twice</strong> β€” once for the Outer Orchestrator (function_tools) and once inside the Inner Sub-Agent (MCP tools).
</p>
<pre class="mermaid">
flowchart TD
START(["User query arrives"]) --> CHECK_CACHE{"Cache hit?"}
CHECK_CACHE -->|Yes| RETURN_CACHED["Return cached response"]
CHECK_CACHE -->|No| OUTER_T1
subgraph OUTER["OUTER AGENT LOOP β€” Orchestrator"]
OUTER_T1["Turn 1 β†’ LLM\nSend: query + 6 tool schemas"]
OUTER_T1 --> OUTER_RESP1{"LLM response type?"}
OUTER_RESP1 -->|"text message"| OUTER_DONE["Return text as final_output"]
OUTER_RESP1 -->|"function_call"| OUTER_INVOKE["SDK invokes function_tool\ne.g. gmail_task(query)"]
OUTER_INVOKE --> INNER_START
subgraph INNER["INNER AGENT LOOP β€” e.g. GmailAgent"]
INNER_START["1. create_google_mcp_server()"]
INNER_START --> INNER_SPAWN["2. async with mcp_server:\n Spawn subprocess\n ~2.5s startup"]
INNER_SPAWN --> INNER_LIST["3. list_tools()\n Get 15 Gmail tools"]
INNER_LIST --> INNER_AGENT["4. Create inner Agent\n with mcp_servers=[server]"]
INNER_AGENT --> INNER_T1["5. Runner.run(agent, query)\n Turn 1 β†’ LLM\n Send: query + 15 MCP tool schemas"]
INNER_T1 --> INNER_RESP{"LLM response?"}
INNER_RESP -->|"text"| INNER_DONE["Return final_output"]
INNER_RESP -->|"MCP tool call"| MCP_CALL
subgraph MCP_EXEC["MCP Tool Execution"]
MCP_CALL["SDK calls mcp_server.call_tool()\ne.g. search_gmail_messages"]
MCP_CALL --> STDIO["JSON-RPC over stdio pipe\n→ google-mcp-server process"]
STDIO --> CRED["Load OAuth credentials\nauto-refresh if expired"]
CRED --> GAPI["Call Google API\ngmail.users.messages.list"]
GAPI --> RESULT["Return API result\nmessage IDs, subjects, etc."]
end
RESULT --> INNER_T2["Turn 2 β†’ LLM\nSend: tool result + history"]
INNER_T2 --> INNER_RESP2{"LLM response?"}
INNER_RESP2 -->|"more tool calls"| MCP_CALL
INNER_RESP2 -->|"text"| INNER_DONE
end
INNER_DONE --> OUTER_T2["Turn 2 β†’ LLM\nSend: function_tool result"]
OUTER_T2 --> OUTER_RESP2{"LLM response?"}
OUTER_RESP2 -->|"more function_calls"| OUTER_INVOKE
OUTER_RESP2 -->|"text"| OUTER_DONE
end
OUTER_DONE --> CACHE_SET["Cache response\n5min TTL"]
CACHE_SET --> RESPOND(["Return to user"])
RETURN_CACHED --> RESPOND
</pre>
</div>
<!-- ═══════════════════════════════════════════════════════════════════ -->
<!-- LEGEND -->
<!-- ═══════════════════════════════════════════════════════════════════ -->
<div class="diagram-section">
<h2>Key Files</h2>
<table>
<tr><th>File</th><th>Role</th><th>Key Exports</th></tr>
<tr>
<td><span class="file-ref">Orchestrator_Agent.py</span></td>
<td>Outer Agent β€” routes queries to specialist sub-agents</td>
<td><code>service()</code>, <code>service_streaming()</code>, 6Γ— <code>@function_tool</code></td>
</tr>
<tr>
<td><span class="file-ref">Gmail_Agent.py</span></td>
<td>Inner Agent β€” Gmail specialist with 15 MCP tools</td>
<td><code>GmailAgent.run(query)</code></td>
</tr>
<tr>
<td><span class="file-ref">google_mcp_config.py</span></td>
<td>Shared config β€” MCP factory, tool lists, LLM settings</td>
<td><code>create_google_mcp_server()</code>, tool name constants</td>
</tr>
<tr>
<td><span class="file-ref">google-mcp-server/main.py</span></td>
<td>MCP Server β€” runs as subprocess, provides Google API tools</td>
<td>FastMCP <code>@server.tool()</code> functions (80 total across 6 services)</td>
</tr>
</table>
<h2 style="margin-top: 24px;">Tool Counts Per Service</h2>
<table>
<tr><th>Service</th><th>Agent Class</th><th>Tools</th><th>Capabilities</th></tr>
<tr><td>Gmail</td><td>GmailAgent</td><td>15</td><td>Search, read, send, draft, labels, filters, threads, attachments</td></tr>
<tr><td>Docs</td><td>GoogleDocsAgent</td><td>19</td><td>Create, edit, find-replace, tables, images, PDF export, comments</td></tr>
<tr><td>Drive</td><td>GoogleDriveAgent</td><td>17</td><td>Search, upload, share, permissions, copy, download, ownership</td></tr>
<tr><td>Sheets</td><td>GoogleSheetsAgent</td><td>14</td><td>Read, write, format, conditional formatting, comments</td></tr>
<tr><td>Slides</td><td>GoogleSlidesAgent</td><td>9</td><td>Create, update, thumbnails, comments</td></tr>
<tr><td>Calendar</td><td>GoogleCalendarAgent</td><td>6</td><td>List calendars, CRUD events, free/busy</td></tr>
<tr><th colspan="2">Total</th><th>80</th><th></th></tr>
</table>
</div>
<script>
mermaid.initialize({
startOnLoad: true,
theme: 'dark',
themeVariables: {
primaryColor: '#238636',
primaryTextColor: '#e6edf3',
primaryBorderColor: '#30363d',
lineColor: '#58a6ff',
secondaryColor: '#161b22',
tertiaryColor: '#21262d',
fontFamily: 'Segoe UI, sans-serif',
fontSize: '14px',
},
flowchart: { curve: 'basis', padding: 20 },
sequence: { mirrorActors: false, messageMargin: 40 },
});
</script>
</body>
</html>