Recorte de contexto: Grafos de dependencias en lugar de repositorios completos
Para las tareas de código, la forma más rápida de inflar un prompt es pegar un repositorio completo cuando el modelo solo necesita un puñado de archivos.
Un grafo de dependencias te permite enviar estructura en lugar de volumen: un mapa de qué importa qué, además de los fragmentos específicos que realmente importan.
Resumen
Un grafo de dependencias es una pequeña estructura de datos que registra qué archivos o módulos importan, llaman o dependen de otros.
En lugar de pegar un código base completo en un prompt, recorres este grafo desde el archivo que se está modificando e incluyes solo los archivos a los que está estructuralmente conectado.
Esto mantiene el prompt pequeño y enfocado, lo que reduce el costo y evita diluir la atención del modelo con código no relacionado.
También escala mejor que adivinar manualmente qué archivos son relevantes, ya que el grafo se construye una vez y se reutiliza en muchos prompts.
Esta página explica cómo construir un grafo de dependencias mínimo en Python, usarlo para seleccionar archivos relevantes para un cambio y enviar ese contexto recortado a Claude.
Receta
Tarjeta de receta de referencia rápida: lista para copiar y pegar.
import ast
import os
def get_imports(filepath: str) -> set[str]:
with open(filepath) as f:
tree = ast.parse(f.read(), filename=filepath)
imports = set()
for node in ast.walk(tree):
if isinstance(node, ast.Import):
imports.update(alias.name for alias in node.names)
elif isinstance(node, ast.ImportFrom) and node.module:
imports.add(node.module)
return importsCuándo usarlo:
- La tarea trata sobre un archivo o función, pero ese archivo reside dentro de un repositorio mucho más grande.
- Quieres que Claude entienda cómo un cambio se propaga a los llamadores, sin enviar el código fuente completo de cada llamador.
- El repositorio es demasiado grande para caber en un solo prompt, incluso con una ventana de contexto generosa.
- Estás ejecutando el mismo tipo de prompt de revisión de código o refactorización repetidamente y quieres un paso de selección reutilizable.
Ejemplo de trabajo
import ast
import os
import anthropic
client = anthropic.Anthropic()
def get_imports(filepath: str) -> set[str]:
"""Devuelve el conjunto de nombres de módulos que importa un archivo Python."""
with open(filepath) as f:
tree = ast.parse(f.read(), filename=filepath)
imports = set()
for node in ast.walk(tree):
if isinstance(node, ast.Import):
imports.update(alias.name for alias in node.names)
elif isinstance(node, ast.ImportFrom) and node.module:
imports.add(node.module)
return imports
def build_dependency_graph(repo_root: str) -> dict[str, set[str]]:
"""Mapea cada ruta de archivo a los nombres de módulos que importa."""
graph = {}
for dirpath, _, filenames in os.walk(repo_root):
for name in filenames:
if name.endswith(".py"):
path = os.path.join(dirpath, name)
graph[path] = get_imports(path)
return graph
def module_name_to_path(repo_root: str, module: str) -> str | None:
"""Mapeo de mejor esfuerzo de un nombre de módulo a una ruta de archivo."""
candidate = os.path.join(repo_root, *module.split(".")) + ".py"
return candidate if os.path.exists(candidate) else None
def relevant_files(repo_root: str, target_file: str, graph: dict[str, set[str]]) -> set[str]:
"""Archivos que el objetivo importa, más archivos que importan el objetivo."""
related = {target_file}
for module in graph.get(target_file, set()):
path = module_name_to_path(repo_root, module)
if path:
related.add(path)
for path, imports in graph.items():
target_module = os.path.splitext(os.path.relpath(target_file, repo_root))[0].replace(os.sep, ".")
if target_module in imports:
related.add(path)
return related
def prompt_with_trimmed_context(repo_root: str, target_file: str, question: str) -> str:
graph = build_dependency_graph(repo_root)
files = relevant_files(repo_root, target_file, graph)
context_blocks = []
for path in sorted(files):
with open(path) as f:
context_blocks.append(f"# {os.path.relpath(path, repo_root)}\n{f.read()}")
prompt = f"{question}\n\n" + "\n\n".join(context_blocks)
response = client.messages.create(
model="claude-sonnet-5",
max_tokens=500,
messages=[{"role": "user", "content": prompt}],
)
return response.content[0].text
answer = prompt_with_trimmed_context(
repo_root="./billing_service",
target_file="./billing_service/retry_queue.py",
question="If I add a max_attempts parameter to retry_queue.py, what callers break?",
)
print(answer)Lo que esto demuestra:
build_dependency_graphrecorre un repositorio una vez y registra las importaciones de cada archivo, lo cual es económico de calcular y reutilizable en muchos prompts.relevant_filesrecorre un salto en ambas direcciones: lo que importa el archivo objetivo y lo que importa el archivo objetivo.- Solo los archivos en ese vecindario de un salto se leen e incluyen en el prompt, no todo el repositorio.
- El paso de construcción del grafo está separado del paso de construcción del prompt, por lo que el mismo grafo puede responder a muchas preguntas diferentes sobre diferentes archivos.
Análisis profundo
Cómo funciona
ast.parseconvierte el código fuente de Python en un árbol de sintaxis sin ejecutarlo, que es la forma segura y estándar de inspeccionar las importaciones.- Recorrer el árbol con
ast.walkencuentra cada nodoImporteImportFromindependientemente de cuán profundamente esté anidado en el archivo. - El grafo en sí es solo un diccionario que mapea las rutas de los archivos a los nombres de los módulos que importan, lo cual es suficiente para responder "de qué depende X" y, al invertir la búsqueda, "qué depende de X".
- Seleccionar un vecindario de un salto (dependencias directas y dependientes directos) suele ser suficiente contexto para la mayoría de los cambios de un solo archivo; una transversal más profunda incluye más archivos a costa de un prompt más grande.
Elección de la profundidad del grafo
| Profundidad | Qué se incluye | Mejor ajuste |
|---|---|---|
| 0 saltos | Solo el archivo objetivo | Correcciones de errores aisladas, preguntas de estilo |
| 1 salto | Importaciones directas del archivo objetivo e importadores directos | La mayoría de las refactorizaciones y preguntas sobre "qué se rompe" |
| 2+ saltos | Dependencias transitivas | Cambios transversales en utilidades compartidas o tipos centrales |
Ir más allá de uno o dos saltos generalmente reintroduce el problema que esta técnica pretende resolver: el prompt vuelve a crecer hacia "todo el repositorio", solo que con pasos adicionales.
Notas de Python
# Protege contra archivos que no se pueden analizar (errores de sintaxis, no UTF8, etc.)
def get_imports_safe(filepath: str) -> set[str]:
try:
return get_imports(filepath)
except (SyntaxError, UnicodeDecodeError):
return set()Los repositorios reales tienen archivos generados, código de terceros o errores de sintaxis ocasionales de ediciones en curso.
Envolver el paso de análisis para que un archivo defectuoso no bloquee toda la construcción del grafo lo hace práctico para ejecutarlo en un código base real.
Errores comunes
- Las importaciones relativas se resuelven incorrectamente.
astinforma la cadena literal del módulo, por lo que una importación relativa comofrom . import utilsnecesita una resolución consciente del paquete, no una simple unión de rutas. Solución: resuelve las importaciones relativas con respecto a la ruta del paquete del archivo importador, o restringe el grafo a importaciones absolutas para una primera versión más simple. - Las importaciones dinámicas son invisibles para
ast. El código que importa a través deimportlib.import_module(computed_name)no aparecerá como un nodoImportestático. Solución: trata los módulos cargados dinámicamente como una brecha conocida y agrega manualmente las dependencias dinámicas conocidas al grafo. - El grafo se vuelve obsoleto si se construye una vez y se almacena en caché para siempre. Un código base que agrega nuevas importaciones no se reflejará en un grafo antiguo. Solución: reconstruye el grafo en cada ejecución para repositorios pequeños, o invalida la caché en los cambios de archivo para repositorios más grandes.
- Las dependencias circulares pueden causar una transversal ilimitada en recuentos de saltos más altos. Dos archivos que se importan entre sí, encadenados a través de varios archivos más, pueden hacer que una transversal de "2 saltos" incluya mucho más de lo esperado. Solución: rastrea los archivos visitados durante la transversal y deja de volver a visitar el mismo archivo.
- Un vecindario de un salto aún puede omitir el archivo realmente relevante. Algunos cambios arquitectónicos afectan un archivo sin una relación de importación directa, como un valor de configuración leído por convención en lugar de por importación. Solución: trata el grafo como un punto de partida, no como una garantía, y permite que un humano o un pase de puntuación de relevancia económico agregue los archivos que el grafo omite.
Alternativas
| Alternativa | Cuándo usar | Cuándo no usar |
|---|---|---|
| Selección manual de fragmentos | La tarea toca uno o dos archivos que ya conoces | El repositorio es lo suficientemente grande como para que la selección manual sea lenta o propensa a errores |
| Contexto de repositorio completo | El repositorio es lo suficientemente pequeño como para caber cómodamente y el costo no es una preocupación | El costo importa, o el repositorio es lo suficientemente grande como para arriesgar la corrupción del contexto |
| Búsqueda aumentada por recuperación (basada en incrustaciones) | Necesitas relevancia semántica, no solo relaciones de importación (por ejemplo, "encontrar código relacionado con reembolsos") | La relación que te interesa es estructural (qué llama a qué), lo que un grafo de dependencias responde con mayor precisión |
| Una herramienta de análisis de código dedicada (por ejemplo, el grafo de llamadas de un servidor de lenguaje) | Necesitas datos de grafo de llamadas entre lenguajes o altamente precisos a escala | Un script ligero de un solo lenguaje es suficiente para la tarea en cuestión |
Preguntas frecuentes
¿Cuál es la diferencia entre un grafo de dependencias y simplemente buscar archivos relacionados con grep?
Grep encuentra coincidencias de texto, lo que puede omitir archivos relevantes (sin palabra clave compartida) e incluir archivos irrelevantes (una coincidencia de cadena casual).
Un grafo de dependencias se construye a partir de relaciones de importación reales, por lo que es más preciso para la pregunta específica de "de qué depende este archivo".
¿Necesito una biblioteca de análisis estático real, o un script `ast` simple es suficiente?
Para un repositorio de un solo lenguaje y tamaño moderado, un script simple basado en ast como el anterior suele ser suficiente.
- Para repositorios muy grandes o multilenguaje, una herramienta dedicada (un servidor de lenguaje,
pydepso similar) será más robusta. - Comienza de forma sencilla y solo recurre a herramientas más pesadas una vez que la versión simple demuestre que omite casos que te interesan.
¿Cuántos saltos debo recorrer en el grafo?
Un salto (importaciones directas e importadores directos) es un valor predeterminado razonable para la mayoría de los cambios de un solo archivo.
- Aumenta a dos saltos para cambios en código de utilidad ampliamente compartido.
- Ir mucho más allá de eso generalmente reintroduce el problema de "todo el repositorio" que esta técnica existe para evitar.
¿Esto reemplaza la sumarización o la estratificación del modelo?
No, resuelven problemas diferentes y normalmente se combinan.
- El recorte del grafo de dependencias decide qué archivos incluir.
- La sumarización condensa un archivo que sigue siendo demasiado largo incluso después de haber sido seleccionado.
- La estratificación del modelo decide qué modelo maneja el prompt resultante, ahora más pequeño.
¿Qué sucede si el grafo omite un archivo relevante?
El prompt carecerá del contexto que el modelo necesitaba, lo que puede producir una respuesta incompleta o incorrecta.
Trata el grafo como un punto de partida: para cambios de alto riesgo, haz que un humano revise qué archivos se seleccionaron antes de enviar el prompt.
¿Puede este enfoque funcionar para otros lenguajes además de Python?
Sí, el concepto es agnóstico al lenguaje: cualquier lenguaje con una importación estática o una declaración require/use puede analizarse de manera similar.
El paso de análisis específico (ast aquí) es específico de Python; otros lenguajes necesitan su propio analizador o una herramienta de análisis estático de propósito general.
¿Cómo manejo las importaciones circulares al construir el grafo?
Las importaciones circulares están bien para la construcción del grafo, ya que solo estás registrando las declaraciones de importación, no ejecutándolas.
Importan para la transversal: rastrea los archivos visitados para que un ciclo no haga que se agregue el mismo archivo, o peor aún, cause una recursión ilimitada en una implementación de transversal ingenua.
¿Es costoso construir el grafo en sí?
Construirlo es una operación local y fuera de línea, analiza archivos fuente en el disco y no cuesta tokens de API.
Solo necesita reconstruirse cuando el código base cambia significativamente, por lo que el costo se amortiza en cada prompt que lo reutiliza.
¿Debo incluir la estructura del grafo en el prompt, o solo los archivos seleccionados?
Ambos pueden ayudar, dependiendo de la pregunta.
- Para "qué se rompe si cambio esto", enumerar qué archivos importan el objetivo (los bordes del grafo) le da a Claude una estructura útil incluso antes de que lea el código.
- Para la mayoría de las tareas, el contenido del archivo seleccionado importa más que la forma del grafo en sí.
¿Por qué no simplemente enviar los nombres de los archivos y dejar que Claude pida los que necesita?
Esa es una alternativa válida, típicamente implementada como un bucle de uso de herramientas donde Claude solicita archivos específicos bajo demanda.
Intercambia un viaje de ida y vuelta adicional (y el costo de tokens de ese viaje de ida y vuelta) por una relevancia potencialmente mejor que un grafo precalculado, y se combina bien con el almacenamiento en caché de llamadas a herramientas descrito en otra parte de esta sección.
¿Una ventana de contexto más grande hace que el recorte del grafo de dependencias sea innecesario?
No, una ventana más grande cambia lo que puedes encajar, no lo que deberías enviar.
Los archivos innecesarios siguen costando tokens, y el código irrelevante aún puede diluir el enfoque del modelo en la parte del código base que realmente importa, que es el problema de la corrupción del contexto cubierto en otra parte de esta sección.
¿Cuál es una buena señal de que el vecindario de un salto es demasiado pequeño?
Si las respuestas de Claude hacen referencia repetidamente a un archivo o un concepto que no se le dio, eso es una señal de que la profundidad de la transversal o la lógica de resolución (importaciones relativas, importaciones dinámicas) está omitiendo algo.
Amplía el recuento de saltos o corrige la brecha de resolución específica, en lugar de recurrir a enviar todo el repositorio.
Relacionado
- Cómo la ingeniería de contexto reduce el gasto de tokens - el modelo mental más amplio en el que encaja esta técnica.
- Conceptos básicos de ingeniería de prompts y contexto - ejemplos prácticos de recorte de prompts, incluido un enfoque de puntuación de relevancia más simple.
- Sumarización antes de la llamada - qué hacer cuando un archivo seleccionado sigue siendo demasiado largo.
- Evitar llamadas a herramientas redundantes en bucles de agente de múltiples turnos - una alternativa de uso de herramientas para precalcular un grafo.
- Corrupción del contexto: por qué más tokens no significan mejores respuestas - por qué incluir archivos no relacionados puede perjudicar la calidad de la respuesta, no solo el costo.
Versiones de pila: Escrito para la línea de modelos Claude actual a partir de junio de 2026: Claude Fable 5, Claude Opus 4.8, Claude Sonnet 5 (el predeterminado) y Claude Haiku 4.5, y el SDK oficial de Python de
anthropic(última versión 0.x). Los nombres de los modelos, los precios y las versiones del SDK cambian rápidamente; verifica los detalles actuales en platform.claude.com/docs antes de confiar en ellos.