Files
agenticSystem/README.md
Jordan Diaz 6978764540 Tests unitarios: 51 tests para compactor, key_data, fingerprint y costes
- tests/test_compactor.py: 24 tests (estimate_tokens, extract_facts,
  build_summary, summarize_tool_output, compact_sections)
- tests/test_key_data_extraction.py: 11 tests (extracción de tables,
  records, sections, modules, pages desde tool executions)
- tests/test_fingerprint.py: 8 tests (deduplicación MD5, sort_keys,
  nested args)
- tests/test_cost_calculation.py: 8 tests (pricing formula, custom
  pricing, rounding)
- README.md: sección Tests con instrucciones de ejecución

Todos offline, sin Docker/Redis/LLM. Ejecutar: python3 -m pytest tests/ -v

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 14:28:32 +00:00

464 lines
18 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Agentic Microservice
Microservicio de orquestación agéntica con context engineering avanzado, multi-agent orchestration, integración MCP, SSE streaming y knowledge base. Diseñado como backend para Acai Code.
## Arquitectura
```
Client → API (FastAPI) → Orchestrator → Subagents (planner/coder/collector/reviewer)
↓ ↓
Context Engine Model Adapter (Claude / OpenAI)
↓ ↓
Memory Store + KB MCP Client (stdio)
Redis (sessions, events, knowledge)
```
**Principios:**
- **Context over chat history** — el modelo recibe estado estructurado, nunca raw tool outputs
- **Semantic search** — embeddings (OpenAI text-embedding-3-small) para seleccionar los docs más relevantes por similitud semántica
- **Task history compaction** — al completar una tarea, se comprime a ~50 tokens; en sesiones largas el agente recuerda todo sin desbordar el contexto
- **Artifact summarization** — outputs de tools resumidos antes de entrar al contexto
- **Error recovery** — steps fallidos se saltan, timeout global, sesión nunca se queda en executing
- **Concurrency lock** — Redis SETNX impide ejecución simultánea en la misma sesión
## Quick Start
### Con Docker Compose
```bash
# 1. Configurar
cp .env.example .env
# Editar .env — al menos una API key (ANTHROPIC o OPENAI)
# 2. Levantar todo (Redis + microservicio)
docker compose up --build
# 3. Solo Redis (para desarrollo local)
docker compose up redis -d
```
### Desarrollo local
```bash
# 1. Redis
docker compose up redis -d
# 2. Dependencias
pip install -r requirements.txt
# 3. Configurar .env
AGENTIC_OPENAI_API_KEY=sk-...
AGENTIC_DEFAULT_MODEL_PROVIDER=openai
AGENTIC_DEFAULT_MODEL_ID=gpt-4o
AGENTIC_REDIS_PORT=6380
# 4. Arrancar
python3 -m uvicorn src.main:app --reload --port 8001
# 5. Dashboard en http://localhost:8001/dashboard/
```
### Tests
```bash
# Ejecutar todos los tests unitarios (no necesita Docker, Redis ni LLM)
pip install pytest
python3 -m pytest tests/ -v
# Ejecutar un archivo específico
python3 -m pytest tests/test_compactor.py -v
# Ejecutar un test específico
python3 -m pytest tests/test_cost_calculation.py::TestCostCalculation::test_1m_input_tokens -v
```
Los tests validan: compactación de contexto, extracción de key_data para historial, fingerprinting de tool calls, y cálculo de costes. Son 100% offline — no consumen tokens ni necesitan servicios externos.
### Cargar Knowledge Base
```bash
# Cargar docs + generar embeddings para semantic search
curl -X POST http://localhost:8001/api/v1/knowledge/load \
-H "Content-Type: application/json" \
-d '{"docs_path": "docs"}'
# → {"status": "loaded", "count": 13, "embeddings": true, ...}
```
Los embeddings se generan en batch via OpenAI `text-embedding-3-small` (~$0.001 por los 13 docs) y se almacenan en Redis. Solo se recalculan al hacer `/knowledge/load` de nuevo.
## Dashboard
Interfaz web para testing integrada en el microservicio. Se accede en `/dashboard/`.
**Features:**
- Gestión de sesiones (crear, eliminar, seleccionar)
- Chat con envío sync (Send) o streaming (Stream)
- Event Log en tiempo real con filtros por categoría y export JSON
- Inspector de estado (task, plan, facts, constraints, timeline)
- Context Debug — muestra exactamente qué recibe cada agente (secciones, tokens, previews)
- Dark/light mode
- Responsive (3 columnas → 1 columna en móvil)
**Atajos de teclado:**
- `Enter` → Send
- `Ctrl+Enter` → Stream
- `Shift+Enter` → Nueva línea
## API Reference
Base URL: `/api/v1`
### Sesiones
```bash
# Crear sesión
curl -X POST http://localhost:8001/api/v1/sessions \
-H "Content-Type: application/json" \
-d '{
"project_profile": {"name": "mi-proyecto", "tech_stack": ["python", "twig"]},
"immutable_rules": ["Responde siempre en español", "Usa Tailwind CSS"],
"metadata": {}
}'
# → {"session_id": "abc123...", "status": "idle"}
# Obtener estado
curl http://localhost:8001/api/v1/sessions/{session_id}
# Eliminar
curl -X DELETE http://localhost:8001/api/v1/sessions/{session_id}
```
### Mensajes
```bash
# Sync — espera la respuesta completa
curl -X POST http://localhost:8001/api/v1/sessions/{session_id}/messages \
-H "Content-Type: application/json" \
-d '{"message": "Crea un módulo FAQ con acordeón"}'
# Streaming — retorna inmediatamente, resultados vía SSE
curl -X POST http://localhost:8001/api/v1/sessions/{session_id}/messages \
-H "Content-Type: application/json" \
-d '{"message": "Crea un módulo FAQ con acordeón", "stream": true}'
```
Respuesta sync:
```json
{
"session_id": "...",
"task_id": "...",
"content": "### Step 1\n...\n### Review\n...",
"steps_completed": 5,
"steps_failed": [],
"artifacts_count": 0,
"review": "...",
"status": "completed"
}
```
### SSE Streaming
```bash
curl -N http://localhost:8001/api/v1/sessions/{session_id}/stream
```
Tipos de evento:
| Evento | Cuándo |
|--------|--------|
| `session.created` | Sesión inicializada |
| `execution.started` | Pipeline arranca |
| `subagent.assigned` | Step ruteado a un agente |
| `agent.delta` | Chunk de texto del agente |
| `tool.started` | Herramienta MCP ejecutándose |
| `tool.completed` | Herramienta terminó |
| `execution.completed` | Task completado |
| `error` | Error en step, planning, o timeout |
### Knowledge Base
```bash
# Cargar docs desde directorio
curl -X POST http://localhost:8001/api/v1/knowledge/load \
-H "Content-Type: application/json" \
-d '{"docs_path": "docs"}'
# Cargar desde ruta absoluta
curl -X POST http://localhost:8001/api/v1/knowledge/load \
-H "Content-Type: application/json" \
-d '{"docs_path": "/ruta/a/mis/docs"}'
# Listar docs cargados
curl http://localhost:8001/api/v1/knowledge
# Eliminar un doc
curl -X DELETE http://localhost:8001/api/v1/knowledge/{doc_id}
```
Los docs se cargan como `*.md` del directorio. Se sobreescriben si ya existen. No requiere reinicio — la siguiente conversación usa los docs actualizados. Al cargar se generan embeddings automáticamente para semantic search.
### Debug
```bash
# Historial de eventos (persistidos en Redis)
curl http://localhost:8001/api/v1/sessions/{session_id}/events
# Context debug — qué recibió cada agente
curl http://localhost:8001/api/v1/sessions/{session_id}/context-debug
# Health check
curl http://localhost:8001/health
```
## Context Engine
El componente más crítico del sistema. Ensambla el prompt que recibe el modelo con secciones priorizadas:
| Sección | Prioridad | Contenido |
|---------|-----------|-----------|
| `immutable_rules` | 100 | System prompt del agente + reglas de sesión. Nunca se recorta |
| `project_profile` | 80 | Perfil del proyecto (nombre, tech stack) |
| `knowledge_base` | 60 | Docs relevantes por semantic search + índice de títulos de todos |
| `task_history` | 55 | Resúmenes compactos de tareas anteriores (~50 tokens c/u) |
| `task_state` | 70 | Objetivo, plan, step actual, facts, constraints |
| `artifact_memory` | 50 | Resúmenes de outputs de herramientas (solo tarea actual) |
| `working_context` | 30 | Items de trabajo recientes entre steps |
**Semantic search:** genera embedding del objetivo/step del usuario y busca por cosine similarity contra los embeddings de todos los docs. Los más relevantes entran completos (budget 15k tokens), el resto aparece como título + summary para que el agente sepa que existen y pueda pedir rehidratación.
**Task history compaction:** al completar cada tarea, sus artifacts, facts y resultados se comprimen en un bloque de ~50 tokens que se guarda en `task_history`. Los artifacts viejos se borran de Redis. En una sesión de 20 tareas, el historial ocupa ~1000 tokens vs los miles que ocuparían los artifacts completos. El agente nunca pierde contexto de lo que hizo antes.
**Token counting:** usa `tiktoken` (encoding `cl100k_base`) para conteo real. Fallback a estimación si tiktoken no está instalado.
**Compaction:** si el total excede el context window (120k tokens default), las secciones de menor prioridad se comprimen o eliminan. `immutable_rules` nunca se toca.
**Presupuesto típico tras 20 tareas:**
```
immutable_rules: ~250 tokens (fijo)
project_profile: ~15 tokens (fijo)
knowledge_base: ~15,000 tokens (semantic search)
task_history: ~1,000 tokens (20 tareas × ~50 tokens)
task_state: ~400 tokens (tarea actual)
artifact_memory: ~500 tokens (solo tarea actual)
working_context: ~500 tokens (solo step actual)
─────────────────────────────────────
TOTAL: ~17,700 tokens / 120,000 disponibles
```
## Orchestrator Pipeline
```
mensaje → planner → [step₁ → step₂ → ... → stepₙ] → reviewer → respuesta
```
1. **Planner** descompone el mensaje en pasos con agent role asignado
2. **Router** rutea cada step al agente apropiado (keyword matching: implement→coder, buscar→collector, etc.)
3. **Subagent** ejecuta el step con su propio contexto controlado
4. Si un step **falla**, se marca como failed y el pipeline continúa con el siguiente
5. **Reviewer** valida el trabajo si hubo >1 step
6. **Timeout global** de 5 min (configurable) — si se excede, la sesión pasa a error
**Concurrency:** Redis lock (SETNX) con TTL de 5 min. Si llega un segundo mensaje mientras uno se ejecuta → `{"status": "busy"}`. El lock se libera automáticamente si el proceso muere.
## MCP (Model Context Protocol)
Arquitectura **per-session**: el microservicio corre como un Docker único, y cada sesión arranca sus propios subprocesos MCP con las env vars del proyecto del usuario.
### Modelo de operación
```
Docker (microservicio — uno solo corriendo)
└── mcp.json ← template: QUÉ servers arrancar (global)
└── mcp-server/ ← código MCP (baked-in)
POST /sessions { mcp_env: { ACAI_WEB_URL: "https://tienda.com", ... } }
→ Arranca subprocesos MCP con esas env vars
→ Session aislada con su propio MCPManager
→ 55 tools descubiertas (acai-code: 33, playwright: 21, fetch: 1)
DELETE /sessions/{id}
→ Mata los subprocesos MCP de esa sesión
```
### Configuración
**1. Template global** (`mcp.json` en la raíz — define QUÉ servers existen):
```json
{
"mcpServers": {
"acai-code": {
"command": "node",
"args": ["mcp-server/stdio.js"],
"env": {},
"timeout": 30,
"startup_timeout": 10
},
"playwright": {
"command": "npx",
"args": ["@playwright/mcp", "--headless"],
"timeout": 30,
"startup_timeout": 15
},
"fetch": {
"command": "uvx",
"args": ["mcp-server-fetch"],
"timeout": 30,
"startup_timeout": 15
}
}
}
```
```bash
# En .env
AGENTIC_MCP_CONFIG_PATH=mcp.json
```
**2. Per-session env vars** (cada usuario/proyecto pasa las suyas al crear sesión):
```bash
curl -X POST http://localhost:8001/api/v1/sessions \
-H "Content-Type: application/json" \
-d '{
"project_profile": {"name": "tienda-online"},
"mcp_env": {
"ACAI_WEB_URL": "https://superadmin_tienda.forge.acaisuite.com/",
"ACAI_WEBSITE": "tienda.com",
"ACAI_PROJECT_DIR": "/projects/tienda"
}
}'
```
Las env vars de `mcp_env` se fusionan con las del template y se pasan al subproceso MCP. El MCP server lee el token desde el `.acai` del proyecto automáticamente.
**3. Legacy** (un solo server global, sin `mcp.json`):
```bash
AGENTIC_MCP_SERVER_COMMAND=node
AGENTIC_MCP_SERVER_ARGS=["mcp-server/stdio.js"]
```
### Tool namespacing
En modo multi-server, los tools se namespean con `__` (compatible con OpenAI que no acepta puntos): `acai_code__compile_module`, `playwright__browser_navigate`. En modo single-server los tools mantienen su nombre original.
### API de gestión MCP
```bash
# Ver estado de todos los MCP servers activos (por sesión)
curl http://localhost:8001/api/v1/mcp/status
# Hot-reload del template (re-lee mcp.json, no afecta sesiones activas)
curl -X POST http://localhost:8001/api/v1/mcp/reload
```
### Ciclo de vida
1. `POST /sessions` con `mcp_env` → arranca subprocesos MCP para esa sesión
2. `POST /sessions/{id}/messages` → el agente usa los tools del MCP de esa sesión
3. Si el server se reinicia y la sesión sigue en Redis → el MCP se reconecta automáticamente al siguiente mensaje
4. `DELETE /sessions/{id}` → mata subprocesos MCP de esa sesión
Los resultados de tools **nunca** entran al contexto como raw output — se resumen como artifacts.
## Configuración
Variables de entorno con prefijo `AGENTIC_`:
| Variable | Default | Descripción |
|----------|---------|-------------|
| `AGENTIC_ANTHROPIC_API_KEY` | — | API key de Anthropic |
| `AGENTIC_OPENAI_API_KEY` | — | API key de OpenAI |
| `AGENTIC_DEFAULT_MODEL_PROVIDER` | `claude` | `claude` o `openai` |
| `AGENTIC_DEFAULT_MODEL_ID` | `claude-sonnet-4-20250514` | Modelo por defecto |
| `AGENTIC_REDIS_HOST` | `localhost` | Host de Redis |
| `AGENTIC_REDIS_PORT` | `6379` | Puerto de Redis |
| `AGENTIC_REDIS_DB` | `0` | Base de datos Redis |
| `AGENTIC_REDIS_PASSWORD` | — | Password de Redis |
| `AGENTIC_MAX_TOKENS` | `4096` | Max tokens de salida por llamada |
| `AGENTIC_CONTEXT_MAX_TOKENS` | `120000` | Max context window |
| `AGENTIC_MAX_EXECUTION_STEPS` | `25` | Max steps por task |
| `AGENTIC_MAX_EXECUTION_TIMEOUT_SECONDS` | `300` | Timeout global (5 min) |
| `AGENTIC_SUBAGENT_MAX_STEPS` | `10` | Max iterations por subagent |
| `AGENTIC_MCP_CONFIG_PATH` | — | Ruta a `mcp.json` (multi-MCP per-session) |
| `AGENTIC_MCP_SERVER_COMMAND` | — | Legacy: comando del servidor MCP (single) |
| `AGENTIC_MCP_SERVER_ARGS` | `[]` | Legacy: argumentos del servidor MCP |
| `AGENTIC_MCP_TIMEOUT_SECONDS` | `30` | Timeout por tool call |
| `AGENTIC_DEBUG` | `false` | Logging verbose |
## Redis Key Structure
```
agentic:session:{id} → SessionState JSON (TTL 24h)
agentic:session:{id}:artifacts → Hash de ArtifactSummary
agentic:session:{id}:events → Lista de SSE events (cap 500)
agentic:session:{id}:lock → Execution lock (SETNX, TTL 5min)
agentic:sessions:index → Set de session IDs activos
agentic:memory:knowledge:{doc_id} → MemoryDocument JSON
agentic:memory:knowledge:_index → Set de doc IDs
agentic:embeddings:knowledge:{doc_id} → Embedding vector (1536 floats JSON)
agentic:memory:_tag:{tag} → Set de doc IDs por tag
agentic:memory:_type:{type} → Set de doc IDs por tipo
```
## Project Structure
```
.
├── src/
│ ├── main.py # FastAPI app, lifecycle, wiring
│ ├── config.py # Pydantic settings
│ ├── models/ # Pydantic v2 data models
│ │ ├── session.py # SessionState, TaskState, TaskStep
│ │ ├── context.py # ContextPackage, MemoryDocument
│ │ ├── agent.py # AgentProfile, SubAgentDefinition
│ │ ├── artifacts.py # ArtifactSummary
│ │ └── tools.py # ToolExecution, ToolDefinition
│ ├── context/ # Context Engine (core)
│ │ ├── engine.py # Prompt assembly, knowledge filtering, debug
│ │ └── compactor.py # Compaction, summarization, tiktoken
│ ├── memory/ # Persistent memory
│ │ ├── store.py # Redis-backed memory + similarity search
│ │ └── embeddings.py # OpenAI embedding service
│ ├── adapters/ # Model provider adapters
│ │ ├── base.py # ModelAdapter interface
│ │ ├── claude_adapter.py # Anthropic Claude (streaming)
│ │ └── openai_adapter.py # OpenAI GPT (streaming)
│ ├── mcp/ # MCP (per-session, multi-server)
│ │ ├── client.py # stdio transport, tool registry
│ │ ├── config.py # mcp.json parser (Pydantic)
│ │ ├── manager.py # MCPManager (aggregates tools, routes calls)
│ │ └── registry.py # Per-session MCP lifecycle
│ ├── orchestrator/ # Agent orchestration
│ │ ├── engine.py # Pipeline + error recovery + timeout
│ │ ├── router.py # Step-to-agent routing
│ │ └── agents/
│ │ ├── base.py # Shared execution loop
│ │ ├── planner.py # Plan decomposition
│ │ ├── coder.py # Implementation
│ │ ├── collector.py # Context gathering
│ │ └── reviewer.py # Validation
│ ├── streaming/ # SSE streaming
│ │ └── sse.py # Event emitter + Redis persistence
│ ├── storage/ # Persistence
│ │ └── redis.py # Sessions, events, locks
│ └── api/ # REST endpoints
│ └── routes.py # Sessions, messages, knowledge, debug
├── dashboard/ # Testing UI (vanilla JS, zero build)
│ ├── index.html
│ ├── css/main.css
│ └── js/
│ ├── app.js # State, SSE handlers
│ ├── api.js # Fetch + EventSource
│ └── components/ # Sidebar, chat, event log, inspector, timeline
├── mcp-server/ # Acai MCP server (Node.js, stdio)
├── docs/ # Knowledge base documents (*.md)
├── mcp.json # MCP server template (qué servers arrancar)
├── mcp.json.example # Ejemplo con múltiples servers
├── Dockerfile
├── docker-compose.yml
├── requirements.txt
└── .env.example
```