Dynavera/ARCHITECTURE.md

332 lines
24 KiB
Markdown
Raw Normal View History

# Agent System Architecture Diagram
## System Overview (current stack)
Docker Compose (dev) services on one network:
- `web` (Vite dev) :5173
- `api` (Django + Channels) :8000
- `celery` worker shares Django code
- `fyp-redis` broker/channel :6379
- `mcp-agent-server` MCP runtime :8001 (HTTP)
MCP wiring:
- `MCP_AGENT_URL=http://mcp-agent-server:8001` (required)
- MCP server runs in HTTP mode, exposes `/execute` and `/health` endpoints
- All agent execution delegates to the remote MCP server (no local LLM fallback)
Flow: Frontend → API (HTTP), Frontend ↔ AgentConsumer (WS), API queues Celery, Celery calls MCP server over HTTP, events return via Redis → Channels → WS.
```
┌──────────────────────────────────────────────────────────────────────────┐
│ FRONTEND (Vue 3 + TypeScript) │
├──────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────┐ ┌──────────────────────┐ ┌─────────────────────┐ │
│ │ Agents.vue │ │ AgentDetail.vue │ │ agentStore.ts │ │
│ ├─────────────────┤ ├──────────────────────┤ ├─────────────────────┤ │
│ │ • List agents │ │ • Run execution │ │ • WebSocket connect │ │
│ │ • Fetch from API│ │ • JSON input │ │ • Event handling │ │
│ │ • Show status │ │ • Live log display │ │ • State management │ │
│ └────────┬────────┘ │ • Stop button │ │ • Auto-reconnect │ │
│ │ │ • Status indicator │ │ • Type-safe API │ │
│ │ └────────┬─────────────┘ └─────────────────────┘ │
│ │ │ │
└───────────┼────────────────────┼─────────────────────────────────────────┘
│ │
│ │ WebSocket
│ HTTP/REST │
▼ ▼
┌──────────────────────────────────────────────────────────────────────────┐
│ BACKEND (Django + Channels) │
├──────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────┐ ┌──────────────────────────┐ ┌─────────────────┐ │
│ │ AgentViewSet │ │ AgentConsumer │ │ Middleware │ │
│ ├─────────────────┤ ├──────────────────────────┤ ├─────────────────┤ │
│ │ REST API: │ │ WebSocket Handler: │ │ • Auth check │ │
│ │ • GET /agent/ │ │ • connect() │ │ • User validate │ │
│ │ • POST /agent/ │ │ • receive() │ │ • Group mgmt │ │
│ │ • GET /agent/id │ │ • handle_start_agent() │ └─────────────────┘ │
│ │ │ │ • handle_stop_agent() │ │
│ │ Returns: Agent │ │ • agent_event() │ ┌─────────────────┐ │
│ │ metadata │ │ • agent_completed() │ │ Serializers │ │
│ └────────┬────────┘ │ • agent_error() │ ├─────────────────┤ │
│ │ └────────┬─────────────────┘ │ • AgentSerializer │
│ │ │ │ • ExecutionSer. │ │
│ │ │ │ • EventSerializer │
│ │ │ └────────┬────────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ Django Database (SQLite/PostgreSQL) │ │
│ ├─────────────────────────────────────────────────────────────────┤ │
│ │ • Agent (uuid, name, description, status, user) │ │
│ │ • AgentExecution (uuid, input_data, output_data, status) │ │
│ │ • AgentEvent (uuid, event_type, content, timestamp) │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
└──────────────────────┬───────────────────────────────────────────────────┘
│ Celery Task Queue
┌──────────────────────────────────────────────────────────────────────────┐
│ CELERY WORKER PROCESS │
├──────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────────────────────────────────────────────────────────┐ │
│ │ start_agent_task_mcp() [MCP-only execution] │ │
│ ├──────────────────────────────────────────────────────────────────┤ │
│ │ ┌──────────────────────────────────────────────────────────┐ │ │
│ │ │ 1. Initialize & Output "started" event │ │ │
│ │ │ │ │ │
│ │ │ 2. Call MCPAgentClient.execute_agent() │ │ │
│ │ │ └─ POST to {MCP_AGENT_URL}/execute │ │ │
│ │ │ with: agent_id, query, input_data │ │ │
│ │ │ │ │ │
│ │ │ 3. Await response from MCP server │ │ │
│ │ │ (handles all RAG, LLM, context retrieval) │ │ │
│ │ │ │ │ │
│ │ │ 4. Forward any events from MCP to WebSocket │ │ │
│ │ │ └─ Progress, message, step events displayed live │ │ │
│ │ │ │ │ │
│ │ │ 5. Save result & Output "completed" event │ │ │
│ │ │ └─ Send via Channel Layer to WebSocket Group │ │ │
│ │ │ │ │ │
│ │ └──────────────────────────────────────────────────────────┘ │ │
│ └──────────────────────────────────────────────────────────────────┘ │
│ │
└──────────────────────┬───────────────────────────────────────────────────┘
│ Channel Layer Broadcast
┌──────────────────────────────────────────────────────────────────────────┐
│ REDIS (Message Broker & Cache) │
├──────────────────────────────────────────────────────────────────────────┤
│ • Celery task queue │
│ • Channel layer for WebSocket group communication │
│ • Session cache (optional) │
└──────────────────────────────────────────────────────────────────────────┘
```
MCP Runtime (HTTP mode)
- Service: `mcp-agent-server` (container `dynavera-mcp-agent`)
- Listens on `0.0.0.0:8001` with `/execute` and `/health` HTTP endpoints
- Handles all agent execution: RAG retrieval, LLM inference, context management
- Shares code and `build/rag_db` read-only from host
- Completely separate from Django/Celery; communicates only via HTTP
## Execution Flow Sequence
```
Frontend Backend LLM System
│ │ │
├─ User Input ──────────────>│ │
│ (JSON via WebSocket) │ │
│ │ │
│ ├─ Create Execution │
│ │ Record │
│ │ │
│ ├─ Queue Celery Task │
│ │ │
<── execution_started ──────┤ │
│ (WebSocket message) │ │
│ │ │
│ ├─ Output "initializing" │
<── agent_event ────────────┤ │
│ (WebSocket) │ │
│ │ │
│ ├─ Load GPT4All Model │
│ │ │
│ │─────────────────────────>│ Load Model
│ │ (~10-30 seconds) │
│ │<───────────────────────── Model Ready
│ │ │
│ ├─ Output "retrieving" │
<── agent_event ────────────┤ │
│ (WebSocket) │ │
│ │ │
│ ├─ Query RAG DB (if exists) │
│ │ (ChromaDB) │
│ │ │
│ ├─ Output "generating" │
<── agent_event ────────────┤ │
│ (WebSocket) │ │
│ │ │
│ │───────────────────────────>│ Generate
│ │ generate(prompt, │ Response
│ │ max_tokens=200) │
│ │<───────────────────────── Response
│ │ (5-30 seconds) │
│ │ │
<── execution_completed ────┤ │
│ (WebSocket with output) │ │
│ │ │
└ Display Log & Result │ │
```
## Data Flow: Input to Output
```
User Enters JSON
{"query": "What is fNIRS?"}
Frontend sends via WebSocket
┌─────────────────────────────────────┐
│ AgentConsumer.receive(text_data) │
└──────────────┬──────────────────────┘
├─ Parse JSON
├─ Validate action
└─ Route to handler
┌─────────────────────────────────────┐
│ handle_start_agent() │
├─────────────────────────────────────┤
│ • Get agent from DB │
│ • Create AgentExecution │
│ • Queue Celery task │
└──────────────┬──────────────────────┘
├─ Send execution_started event
│ (back to WebSocket)
├─ Queue in Celery/Redis
┌──────────────┴──────────────────────┐
│ │
▼ ▼
Celery Worker Database Updated
│ │
├─ Fetch execution │
├─ Initialize models │
├─ Send progress events │
│ │
├─ Query RAG (if available) │
│ ├─ Load embedder │
│ ├─ Connect to ChromaDB │
│ └─ Query for context │
│ │
├─ Initialize LLM │
│ ├─ Load GPT4All model │
│ └─ Prepare prompt │
│ │
├─ Generate response │
│ └─ model.generate() │
│ │
├─ Create result dict │
│ │
├─ Save to AgentExecution │
│ ├─ output_data │
│ ├─ status = 'completed' │
│ └─ completed_at │
│ │
└─ Send via Channel Layer
to WebSocket Group
├─ event_type: agent_event
├─ event_type: agent_completed
Frontend receives
├─ Update agentStore
├─ Push to eventLog
├─ Display in UI
User sees result
```
## Component Interaction
```
┌─────────────────────────────────────────────────────────┐
│ FRONTEND STATE MANAGEMENT │
├─────────────────────────────────────────────────────────┤
│ │
│ agentStore (Pinia) │
│ ├─ socket: WebSocket connection │
│ ├─ isConnected: boolean │
│ ├─ agentId: UUID │
│ ├─ currentExecutionId: UUID │
│ ├─ executionStatus: 'idle'|'running'|'completed' │
│ ├─ events: Array<AgentEvent>
│ │ │
│ ├─ connect(agentId) │
│ ├─ startAgent(inputData) │
│ ├─ stopAgent() │
│ ├─ disconnect() │
│ └─ handleMessage(data) │
│ │
│ AgentDetail.vue (Uses Store) │
│ ├─ Subscribes to: │
│ │ ├─ agentStore.isConnected │
│ │ ├─ agentStore.executionStatus │
│ │ └─ agentStore.eventLog │
│ │ │
│ └─ Calls: │
│ ├─ agentStore.connect() on mount │
│ ├─ agentStore.startAgent() on button click │
│ ├─ agentStore.disconnect() on unmount │
│ └─ agentStore.stopAgent() on stop button │
│ │
└─────────────────────────────────────────────────────────┘
```
## Message Type Mapping
```
WebSocket Message Type → Handler Function → Event Display
"execution_started" → handleMessage → "Started" tag + message
"agent_event" → handleMessage → Event type specific
├─ "progress" → Display stage → [PROGRESS] stage: message
├─ "message" → Display text → [MESSAGE] content
└─ "step" → Display step → [STEP] content
"execution_completed" → handleMessage → "Completed" tag + output
"execution_error" → handleMessage → "Error" tag + message
"execution_stopped" → handleMessage → "Stopped" tag + message
"error" → handleMessage → "Error" tag + message
"connection" → handleMessage → Console log
```
## Database Schema Relationships
```
User (from auth)
├─────── (1:N) ─────────> Agent
│ ├─ uuid (PK)
│ ├─ name
│ ├─ description
│ ├─ status
│ ├─ created_at
│ └─ updated_at
│ │
│ ├─────── (1:N) ──────────> AgentExecution
│ ├─ uuid (PK)
│ ├─ status
│ ├─ input_data (JSON)
│ ├─ output_data (JSON)
│ ├─ error_message
│ ├─ created_at
│ ├─ started_at
│ ├─ completed_at
│ │ │
│ │ ├─ (1:N) ──> AgentEvent
│ │ ├─ uuid (PK)
│ │ ├─ event_type
│ │ ├─ content (JSON)
│ │ └─ timestamp
│ │
│ └─ user_id (FK)
│ │
└──────────────────────────────────────────────────────────────────┘
```