Saltar al contenido
# 🕸️ Entity Graph K* — Linking Cross-Collection ## 🤔 ¿Qué hago? ¿Cómo lo hago? ¿Y para qué lo hago? **¿Qué hago?** Proporciono una capa de **entity linking** sobre las tres colecciones Qdrant del proxy K* (`klaude_knowledge`, `klaude_sources`, `klaude_cache`). Extraigo entidades nombradas de los chunks indexados y construyo un grafo en memoria que conecta chunks relacionados entre colecciones distintas. **¿Cómo lo hago?** Uso un modelo Ollama local (por defecto `qwen2.5:3b`) para extraer entidades de cada chunk durante el rebuild. Las entidades se almacenan en el payload de Qdrant bajo el campo `graph_entities: [...]`. En tiempo de consulta, tras el vector search paralelo de KB y Sources, busco chunks relacionados por entidades compartidas y los añado al contexto RAG. **¿Y para qué lo hago?** Para que una pregunta sobre "klaude-proxy" no solo encuentre chunks con alta similitud coseno, sino también chunks sobre "Qdrant", "cache semántico" o "RAG pipeline" que estén conectados conceptualmente aunque su similitud vectorial sea menor. El resultado es un contexto RAG más rico y cross-collection. --- ## 🏗️ Arquitectura ```mermaid flowchart TD A[/v1/messages — query] --> B[Embedding] B --> C{Vector Search Paralelo} C --> D[klaude_knowledge<br/>search_with_expansion] C --> E[klaude_sources<br/>search] D --> F[kb_chunks] E --> G[src_chunks] F & G --> H[graph.expand_chunks] H --> I{graph built?} I -- No --> J[retorna vacío] I -- Sí --> K[Colecta graph_entities<br/>de los chunks] K --> L[Busca point_ids<br/>relacionados en índice] L --> M[Qdrant retrieve<br/>puntos adicionales] M --> N[graph_chunks] F --> O[_rag_blocks] G --> O N --> O O --> P[System prompt enriquecido] R[POST /graph/rebuild] --> S[EntityExtractor<br/>Ollama NER] S --> T[Scroll klaude_knowledge<br/>+ klaude_sources] T --> U[set_payload graph_entities] U --> V[In-memory index<br/>entity → set of point_ids] ``` --- ## 📁 Módulo: `proxy/graph.py` ### Clases principales | Clase | Responsabilidad | |---|---| | `EntityExtractor` | Llama a Ollama `/api/generate` con prompt NER, parsea JSON array de entidades | | `EntityGraph` | Índice in-memory `dict[str, set[tuple[str, str]]]` — entity → {(collection, point_id)} | ### Funciones de módulo | Función | Descripción | |---|---| | `get_graph()` | Singleton `EntityGraph` | | `get_extractor()` | Singleton `EntityExtractor` | | `build_rag_context(chunks)` | Formatea chunks expandidos como bloque Markdown para system prompt | ### Métodos de `EntityGraph` | Método | Descripción | |---|---| | `expand_chunks(initial_chunks, client)` | Devuelve SOLO los chunks adicionales encontrados vía entity graph | | `rebuild(extractor)` | Escanea colecciones, extrae entidades, actualiza payloads, reconstruye índice | | `stats()` | Devuelve estado del grafo (built, unique_entities, total_links) | --- ## ⚙️ Configuración Variables en `.env` (o por defecto en `config.py`): | Variable | Default | Descripción | |---|---|---| | `GRAPH_OLLAMA_MODEL` | `qwen2.5:3b` | Modelo Ollama para extracción NER | | `GRAPH_MAX_EXPANSION` | `5` | Máximo de chunks adicionales por request | | `OLLAMA_BASE_URL` | `http://localhost:11434` | URL base Ollama (compartida con el resto del proxy) | > ⚠️ El modelo `qwen2.5:3b` debe estar disponible en Ollama: `ollama pull qwen2.5:3b` --- ## 🔌 Endpoints ### `GET /graph/stats` Devuelve el estado actual del grafo en memoria. ```json { "built": true, "unique_entities": 1247, "total_links": 8932, "collections_indexed": ["klaude_knowledge", "klaude_sources"] } ``` ### `POST /graph/rebuild` Requiere header `x-api-key`. Dispara un rebuild completo (puede tardar varios minutos dependiendo del volumen de chunks). ```bash curl -X POST http://localhost:8080/graph/rebuild -H "x-api-key: your-key" ``` Respuesta: ```json { "collections": { "klaude_knowledge": {"points": 450, "updated": 120, "errors": 0}, "klaude_sources": {"points": 89, "updated": 34, "errors": 0} }, "total_points": 539, "updated_points": 154, "errors": 0 } ``` --- ## 🔄 Flujo de datos en `/v1/messages` ``` query_vector → asyncio.gather(kb.search_with_expansion, src.search) → kb_chunks + src_chunks → graph.expand_chunks(all_chunks) ← NUEVO → _rag_blocks = [CACHE?, KB?, SOURCES?, GRAPH?] → system prompt enriquecido → X-Cache: KNOWLEDGE+SOURCES+GRAPH-RAG ``` El header `X-Cache` reflejará el source `GRAPH` cuando el entity graph aporte chunks adicionales. --- ## 📦 Payload Qdrant — campo `graph_entities` Tras el rebuild, cada punto en `klaude_knowledge` y `klaude_sources` tendrá: ```json { "content": "...", "file_path": "proxy/cache.py", "graph_entities": ["qdrant", "asyncqdrantclient", "get_client", "cache", "similarity_threshold"] } ``` Este campo es idempotente — rebuild nunca sobreescribe entidades ya existentes, solo añade las que faltan. --- ## 🚀 Comportamiento en startup Al arrancar el proxy, se lanza un background task no bloqueante que ejecuta `graph.rebuild()`. Si Ollama no está disponible en ese momento, el rebuild falla silenciosamente (log `WARNING`) y el grafo queda vacío. En ese caso, `expand_chunks()` devuelve lista vacía sin afectar el pipeline RAG. --- ## 🔗 Documentos relacionados - [proxy.md](proxy.md) — arquitectura general del proxy y pipeline RAG - [knowledge_base.md](knowledge_base.md) — colección `klaude_knowledge`, hybrid search, indexación - [sources.md](sources.md) — colección `klaude_sources`, ingesta web/markdown/PDF - [cache.md](cache.md) — colección `klaude_cache`, semantic cache Q&A