Saltar al contenido
Blog
1 de julio de 20268 min de lectura

KC-RAG: recuperar código con inteligencia estructural

Cómo usar AST, grafos de dependencias y búsqueda híbrida para indexar código correctamente

RAGASTLLMArquitecturaAgentes de CódigoBúsqueda Híbrida
KC-RAG: recuperar código con inteligencia estructural

Después de diagnosticar por qué el chunking tradicional falla en RAG de código, la pregunta obvia es: ¿existe una alternativa real?

La respuesta es sí. Y no requiere inventar nada nuevo.

Existe un enfoque arquitectónico que reconoce una verdad simple: el código no es texto plano, es estructura. Las funciones tienen firmas. Se llaman entre ellas. Los módulos importan dependencias. Como expliqué en Por qué el chunking rompe a tu agente de código, cuando cortas por tokens pierdes exactamente eso — y el modelo empieza a alucinar sobre lo que le falta.

Knowledge Context RAG: la alternativa estructural

KC-RAG es un enfoque donde partes el código donde tiene sentido, no donde se te acaba la ventana de tokens.

En lugar de fragmentos arbitrarios, trabajas con cápsulas semánticas: unidades completas que el código ya entiende de forma natural. Una función entera. Una clase. Un módulo. Nada cortado a la mitad.

El cambio es pequeño en concepto, grande en práctica.

AspectoChunking tradicionalKC-RAG
Unidad baseN tokens consecutivosNodos de AST (funciones, clases, módulos)
LímitesArbitrarios, por ventanaEstructurales, semánticamente válidos
MetadataPosición en tokensParámetros, retorno, docstring, dependencias
RelacionesNinguna explícitaGrafo de quién llama a quién
RecuperaciónSolo semánticaExacta + semántica + estructural
ContextoFijo para toda tareaAdaptativo según tipo de query

Esto se sostiene en cuatro pilares.

Pilar 1: AST parsing — estructura como dato

Todo código ejecutable es un árbol. Cuando escribes una función, un parser la ve así: un nodo con ramas — la firma (nombre, parámetros, tipo de retorno), el docstring, el cuerpo. El cuerpo mismo es otro árbol: condicionales, bucles, llamadas a otras funciones.

Eso es un Abstract Syntax Tree (AST): tu código convertido en una estructura donde cada nodo representa una decisión semántica real, no una posición de texto.

El valor está en que, al recorrer ese árbol, extraes unidades completas. No fragmentos. verifyUser() entera, con su nombre, sus parámetros, su docstring si existe, su cuerpo íntegro. Sin cortes a mitad de función.

Herramientas para esto ya existen y son maduras: el módulo ast nativo de Python, tree-sitter para más de 60 lenguajes, los parsers de Babel/TypeScript para JS/TS, go/ast para Go.

El proceso, a grandes rasgos: leer el código fuente, pasarlo por el parser para obtener el árbol sintáctico, recorrerlo extrayendo los nodos relevantes (funciones, clases, módulos) y capturar, por cada uno, su metadata — nombre, parámetros, retorno, docstring, cuerpo.

El resultado es una lista de cápsulas completas, cada una una unidad semántica válida por sí sola. Cuando el LLM recibe verifyUser(), la recibe entera — no partida por verify y User() por un límite arbitrario de tokens.

Pilar 2: grafo de dependencias — el eslabón perdido del chunking

Aquí es donde KC-RAG diverge de verdad del chunking tradicional. El chunking trata cada cápsula como una isla desconectada: indexa la función A, indexa la función B, pero nunca registra que A llama a B.

Un grafo de dependencias mapea exactamente eso. Se construye recorriendo cada cápsula, identificando las llamadas a otras funciones dentro de su cuerpo, y registrando la relación en ambos sentidos: verifyUser → hashPassword, y también hashPassword ← verifyUser.

Técnicamente es un recorrido BFS o DFS sobre el código ya parseado, que detecta imports, llamadas a funciones y referencias directas, y produce un grafo donde cada nodo es una cápsula y cada arista es una relación real — no una similitud de texto.

¿Por qué importa? Query: "¿cómo valido usuarios?". Sin grafo, recuperas verifyUser() porque coincide con la pregunta, pero hashPassword() — donde de hecho está el bug — vive en otro rincón del índice y no aparece. Con grafo, recuperas verifyUser() junto con todo lo que llama y todo lo que la llama. El contexto deja de ser semántico y pasa a ser estructural: no es "se parece", es "está conectado de verdad".

Pilar 3: búsqueda híbrida — lo exacto y lo semántico

Una query llega: "refactor authentication". ¿Dónde buscas? KC-RAG corre dos motores en paralelo.

El motor exacto busca por nombre de función, archivo o palabra clave literal — authenticate, verify, login — y devuelve matches sin ambigüedad. El motor semántico convierte la query en un embedding y busca funciones con significado cercano, cubriendo lo que no tiene un nombre exacto pero está relacionado.

La razón para no confiar solo en lo semántico: verifyEmail y verifyUser tienen embeddings casi idénticos, pero son funciones completamente distintas. La búsqueda exacta resuelve esa ambigüedad de raíz.

La fusión es simple: primero los resultados exactos, porque son la verdad; después los semánticos, ordenados por relevancia, para llenar los huecos que el nombre exacto no cubre.

Pilar 4: compresión contextual adaptativa

El contexto no debería ser uno solo. Entender código, refactorizarlo o generarlo desde cero son tareas distintas, y cada una necesita un contexto distinto.

code_query (entender): la cápsula principal completa más las firmas de lo que llama. Basta para explicar qué hace algo sin cargar toda su cadena de dependencias. Rango típico: 500-2000 tokens.

refactor (mejorar): el target completo, más los cuerpos de sus callees y las firmas de sus callers. Necesitas ver la cadena que se va a ver afectada, pero no el detalle de funciones lejanas en el grafo. Rango típico: 800-1500 tokens.

code_gen (crear): estructura de directorios y nombres de funciones existentes, sin lógica. No necesitas entender qué hace el código actual, solo dónde encaja el nuevo. Rango típico: 200-600 tokens.

search (explorar): un mini-índice de funciones con o sin docstring, agrupadas por archivo. Sirve para descubrir qué existe, no para entenderlo en profundidad. Rango típico: 300-800 tokens.

El beneficio es económico y directo: abrir un archivo completo sin criterio cuesta 5000 tokens de media; con compresión adaptativa, la misma tarea cuesta entre 200 y 2000 tokens según lo que realmente hace falta. Ahorro de 60-80% sin perder precisión.

Arquitectura conceptual

El flujo completo, de principio a fin:

  1. Parser AST recorre el código fuente y extrae cápsulas semánticas.
  2. El call graph builder construye el grafo de dependencias entre esas cápsulas.
  3. El indexer almacena todo en un vector store más metadata estructurada.
  4. Llega una query: un clasificador detecta el tipo de tarea (entender, refactorizar, generar, explorar).
  5. La búsqueda híbrida corre en paralelo: exacta y semántica.
  6. El resolver expande por el grafo según lo que pida el tipo de tarea.
  7. El compresor arma el contexto final, adaptado a esa tarea concreta.
  8. Ese contexto se inyecta en el prompt del LLM.

Tres cosas importan de este flujo: el indexado es determinista, no depende de un LLM ni introduce alucinaciones en la captura de datos. La búsqueda opera en dos niveles — exacta, rápida y precisa; semántica, más amplia y más ruidosa. Y la compresión es lo único que varía con la tarea, no la indexación.

Antes y después: refactorizar login()

Un agente recibe la orden de añadir soporte OAuth a una función login() existente.

Con chunking tradicional: el sistema abre auth.py completo — 2000 tokens de contenido bruto sin criterio — y busca por similitud semántica "login oauth". Recupera login() partida por el límite de tokens, más fragmentos sueltos de hashPassword y de gestión de sesión, sin relación explícita entre ellos. El LLM no ve dónde se validan las credenciales ni qué retorna hashPassword, y propone una solución que ignora esas dependencias. Coste total, incluyendo el intento fallido: unos 8000 tokens.

Con KC-RAG: el sistema clasifica la tarea como "refactor". El AST ya tiene login() completa con sus dependencias mapeadas. La búsqueda exacta la localiza en auth.py; el resolver expande por grafo hacia hashPassword, validateEmail y createSession. El compresor entrega los cuerpos completos del target y sus callees directos, y solo las firmas del resto — unos 700 tokens, todos relevantes. El LLM ve la lógica completa, entiende qué preservar, y propone un refactor que integra OAuth sin romper la validación de credenciales. Coste total: unos 2500 tokens.

Menos tokens, más precisión — no es un balance, es la misma decisión bien tomada.

Cuándo aplica KC-RAG

Tiene sentido en codebases grandes, con más de un centenar de funciones donde el coste de indexar se amortiza rápido. En refactors críticos de seguridad o performance, donde una alucinación sale cara. Cuando el presupuesto de tokens es limitado y la compresión adaptativa se nota en la factura. Y en proyectos con un grafo de dependencias denso, en lenguajes estructurados como Python, TypeScript, Java o Go.

No hace falta en scripts pequeños de menos de 50 funciones, en generación de código simple que no depende de contexto existente, ni cuando lo que buscas es documentación en lugar de lógica.

Siguiente paso

La pregunta que queda es cómo se monta esto en la práctica. El stack típico combina un parser (tree-sitter o el módulo ast nativo), una base vectorial para los embeddings, metadata estructurada aparte, y un recorrido BFS sobre el grafo para resolver dependencias en tiempo de query.

En el siguiente post, entro en el pipeline completo — cómo se ensamblan estas piezas paso a paso.

Escrito porIsmael Manzano LeónFull Stack Developer · leo/ · leosoftware.dev
Hablar con Ismael