Saltar al contenido
# 🏗️ Arquitectura — klaude-proxy ## 🤔 ¿Qué hago? ¿Cómo lo hago? ¿Y para qué lo hago? **¿Qué hago?** Soy un proxy HTTP que se interpone entre Claude Code y la API oficial de Anthropic. Cada petición pasa primero por mí antes de llegar a `api.anthropic.com`. **¿Cómo lo hago?** Genero un embedding semántico de cada conversación usando Fastembed local (`nomic-ai/nomic-embed-text-v1.5`, sin API externa) y lo busco en Qdrant (vector DB). Si la similitud con una conversación anterior supera el umbral configurado, devuelvo la respuesta cacheada sin llamar a Anthropic. Si no hay hit, reenvío la petición, capturo la respuesta y la almaceno para futuros hits. **¿Y para qué lo hago?** Para reducir el coste de llamadas a Anthropic, añadir memoria persistente semántica al trabajo diario y permitir que múltiples instancias de Claude Code compartan la misma caché. --- ## 📐 Diagrama de flujo ```mermaid flowchart TD A[Claude Code cliente] -->|POST /v1/messages| B[klaude-proxy :8080] B --> C{Generar embedding\nFastembed nomic-v1.5} C --> D{klaude_cache\nCosine search} D -->|score ≥ 0.98\nDIRECT HIT| E[Devolver respuesta\ncacheada] E --> K[X-Cache: HIT] D -->|0.87 ≤ score < 0.98\nCACHE RAG| CRAG[Inyectar respuesta\ncacheada en system] D -->|score < 0.87\nMISS| KB{klaude_knowledge\nBúsqueda híbrida\nBM25 + dense RRF} KB -->|chunks ≥ 0.75| KRAG[Inyectar chunks KB\nen system prompt] KB -->|sin resultados| SRC{klaude_sources\nBúsqueda densa} SRC -->|chunks ≥ 0.75| SRAG[Inyectar chunks Sources\nen system prompt] SRC -->|sin resultados| F_PLAIN[Forward a\napi.anthropic.com / Ollama] CRAG --> F[Forward a\napi.anthropic.com / Ollama] KRAG --> F SRAG --> F F --> G{¿Respuesta\ncacheable?} G -->|end_turn + solo texto| H[Almacenar en Qdrant\nklaude_cache] G -->|tool_use / no end_turn| I[Devolver sin cachear] H --> J[Devolver respuesta] F_PLAIN --> G J --> L[X-Cache: MISS] I --> L F --> RAG_HDR[X-Cache: *-RAG\nsegún fuentes activas] subgraph Podman B M[(Qdrant\nklaude_cache\nklaude_knowledge\nklaude_sources)] end D <--> M KB <--> M SRC <--> M H --> M ``` --- ## 🧩 Componentes | Componente | Archivo | Responsabilidad | | --- | --- | --- | | **FastAPI app** | `proxy/main.py` | Enrutamiento, orquestación RAG, respuestas, dashboard HTML | | **Configuración** | `proxy/config.py` | Variables de entorno con defaults seguros | | **Modelos** | `proxy/models.py` | Pydantic schemas mirroring Anthropic API | | **Embeddings** | `proxy/embeddings.py` | Serialización de request + Fastembed local | | **Cache** | `proxy/cache.py` | Colección `klaude_cache`: init, search (dual-threshold), store, stats, flush | | **Knowledge base** | `proxy/knowledge.py` | Colección `klaude_knowledge`: búsqueda híbrida BM25+dense (RRF), ingest, stats, flush | | **Sources** | `proxy/sources.py` | Colección `klaude_sources`: ingest web/markdown/PDF, búsqueda dense, stats, flush | | **Cliente Anthropic** | `proxy/anthropic_client.py` | Forward blocking/stream + reconstrucción SSE | | **Cliente Ollama** | `proxy/ollama_client.py` | Forward a servidor Ollama local, compatible con Anthropic SDK | | **Crawl4AI client** | `proxy/crawl4ai_client.py` | Crawl de URLs via Crawl4AI REST (sync + polling fallback) | | **Analytics** | `proxy/analytics.py` | Costes, tokens, SQLite persistente, endpoints `/analytics/*` | | **Log buffer** | `proxy/log_buffer.py` | Ring buffer de logs para SSE dashboard | | **Podman Compose** | `podman/compose.yaml` | Orquestación Qdrant + proxy + Crawl4AI + volumen analytics-data | --- ## 🔑 Decisiones de diseño clave ### ¿Qué se cachea? Solo respuestas con `stop_reason = "end_turn"` y content blocks exclusivamente de tipo `text`. Las respuestas con `tool_use` son **stateful** (dependen del estado actual del sistema de ficheros, output de comandos, etc.) y no se pueden reutilizar de forma segura. ### Clave de embedding Se serializa: `model + system + messages[]` concatenados. Esto captura el contexto completo de la conversación, no solo el último mensaje. Una misma pregunta con diferente contexto previo genera un embedding diferente → entradas diferentes en la caché. ### Umbral de similitud — dual threshold El caché semántico tiene dos umbrales independientes: | Variable | Default | Comportamiento | | --- | --- | --- | | `CACHE_RAG_DIRECT_THRESHOLD` | `0.98` | score ≥ 0.98 → retorno directo de caché (`X-Cache: HIT`), sin llamar al LLM | | `SIMILARITY_THRESHOLD` | `0.87` | score ≥ 0.87 y < 0.98 → inyecta la respuesta cacheada como RAG context y llama al LLM (`X-Cache: CACHE-RAG`) | | — | — | score < 0.87 → MISS, continúa a Knowledge y Sources search | Knowledge y Sources tienen su propio umbral (`KNOWLEDGE_SIMILARITY_THRESHOLD=0.75`, `SOURCES_SIMILARITY_THRESHOLD=0.75`) sobre la búsqueda dense (Prefetch de la query híbrida en knowledge, búsqueda directa en sources). ### Streaming transparente En cache **hit** con `stream=true`: el proxy reconstruye el formato SSE completo de Anthropic (events: `message_start`, `content_block_start`, `content_block_delta`..., `message_stop`) para que Claude Code no note diferencia con una respuesta real. En cache **miss** con `stream=true`: el proxy hace "tee" del stream — lo reenvía al cliente en tiempo real y simultáneamente captura los chunks para almacenar la respuesta completa al finalizar. ### Multicliente Qdrant expone el puerto 6333 en la red del host de Podman. Cualquier instancia de Claude Code en la misma red (LAN, VPN, mismo host) puede apuntar al proxy y compartir la caché. --- ## 🔗 Documentos relacionados - [proxy.md](proxy.md) — Detalles de la API del proxy - [cache.md](cache.md) — Operaciones de caché y estrategia de embeddings - [setup.md](setup.md) — Guía de instalación y configuración