Otimizando o Contexto: Grafos de Dependência em Vez de Repositórios Completos
Para tarefas de código, a maneira mais rápida de inflar um prompt é colar um repositório inteiro quando o modelo só precisa de um punhado de arquivos.
Um grafo de dependência permite enviar estrutura em vez de volume: um mapa de o que importa o quê, mais os trechos específicos que realmente importam.
Resumo
Um grafo de dependência é uma pequena estrutura de dados que registra quais arquivos ou módulos importam, chamam ou dependem de outros.
Em vez de colar um codebase inteiro em um prompt, você percorre este grafo a partir do arquivo que está sendo alterado e puxa apenas os arquivos aos quais ele está estruturalmente conectado.
Isso mantém o prompt pequeno e focado, o que reduz o custo e evita diluir a atenção do modelo com código não relacionado.
Ele também escala melhor do que adivinhar manualmente quais arquivos são relevantes, já que o grafo é construído uma vez e reutilizado em vários prompts.
Esta página detalha a construção de um grafo de dependência mínimo em Python, seu uso para selecionar arquivos relevantes para uma alteração e o envio desse contexto otimizado para o Claude.
Receita
Cartão de receita de referência rápida - pronto para copiar e colar.
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 importsQuando usar isso:
- A tarefa é sobre um arquivo ou função, mas esse arquivo está dentro de um repositório muito maior.
- Você quer que o Claude entenda como uma alteração se propaga para os chamadores, sem enviar o código completo de todos os chamadores.
- O repositório é muito grande para caber em um único prompt, mesmo com uma janela de contexto generosa.
- Você está executando o mesmo tipo de prompt de revisão de código ou refatoração repetidamente e deseja uma etapa de seleção reutilizável.
Exemplo de Trabalho
import ast
import os
import anthropic
client = anthropic.Anthropic()
def get_imports(filepath: str) -> set[str]:
"""Retorna o conjunto de nomes de módulos que um arquivo Python importa."""
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]]:
"""Mapeia cada caminho de arquivo para os nomes de módulos que ele 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:
"""Mapeamento de melhor esforço de um nome de módulo de volta para um caminho de arquivo."""
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]:
"""Arquivos que o alvo importa, mais arquivos que importam o alvo."""
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="Se eu adicionar um parâmetro max_attempts a retry_queue.py, quais chamadores quebrarão?",
)
print(answer)O que isso demonstra:
build_dependency_graphpercorre um repositório uma vez e registra as importações de cada arquivo, o que é barato de computar e reutilizável em vários prompts.relevant_filespercorre um salto em ambas as direções: o que o arquivo de destino importa e o que importa o arquivo de destino.- Apenas os arquivos nesse vizinhança de um salto são lidos e incluídos no prompt, não o repositório inteiro.
- A etapa de construção do grafo é separada da etapa de construção do prompt, para que o mesmo grafo possa responder a muitas perguntas diferentes sobre arquivos diferentes.
Mergulho Profundo
Como Funciona
ast.parsetransforma o código-fonte Python em uma árvore de sintaxe sem executá-lo, que é a maneira segura e padrão de inspecionar importações.- Percorrer a árvore com
ast.walkencontra todos os nósImporteImportFrom, independentemente de quão aninhados estejam no arquivo. - O grafo em si é apenas um dicionário que mapeia caminhos de arquivo para os nomes de módulos que eles importam, o que é suficiente para responder "do que X depende" e, invertendo a consulta, "o que depende de X".
- Selecionar uma vizinhança de um salto (dependências diretas e dependentes diretos) geralmente é contexto suficiente para a maioria das alterações de arquivo único; a travessia mais profunda puxa mais arquivos ao custo de um prompt maior.
Escolhendo a Profundidade do Grafo
| Profundidade | O que está incluído | Melhor ajuste |
|---|---|---|
| 0 saltos | Apenas o arquivo de destino | Correções de bugs isoladas, perguntas de estilo |
| 1 salto | Importações diretas do arquivo de destino e importadores diretos | A maioria das refatorações e perguntas "o que quebra" |
| 2+ saltos | Dependências transitivas | Alterações transversais em utilitários compartilhados ou tipos principais |
Ir além de um ou dois saltos geralmente reintroduz o problema que esta técnica visa resolver: o prompt cresce de volta para "o repositório inteiro", apenas com etapas extras.
Notas de Python
# Protege contra arquivos que falham na análise (erros de sintaxe, não-UTF8, etc.)
def get_imports_safe(filepath: str) -> set[str]:
try:
return get_imports(filepath)
except (SyntaxError, UnicodeDecodeError):
return set()Repositórios reais têm arquivos gerados, código incluído ou erros de sintaxe ocasionais de edições em andamento.
Envolver a etapa de análise para que um arquivo ruim não trave toda a construção do grafo mantém isso prático para executar em um codebase real.
Armadilhas
- Importações relativas são resolvidas incorretamente.
astrelata a string de módulo literal, então uma importação relativa comofrom . import utilsprecisa de resolução ciente do pacote, não de junção simples de caminhos. Correção: resolva importações relativas contra o caminho do pacote do arquivo importador, ou restrinja o grafo a importações absolutas para uma primeira versão mais simples. - Importações dinâmicas são invisíveis para
ast. Código que importa viaimportlib.import_module(computed_name)não aparecerá como um nóImportestático. Correção: trate módulos carregados dinamicamente como uma lacuna conhecida e adicione manualmente dependências dinâmicas conhecidas ao grafo. - O grafo fica obsoleto se for construído uma vez e armazenado em cache para sempre. Um codebase que adiciona novas importações não será refletido em um grafo antigo. Correção: reconstrua o grafo a cada execução para repositórios pequenos, ou invalide o cache em alterações de arquivo para os maiores.
- Dependências circulares podem causar travessia ilimitada em contagens de saltos mais altas. Dois arquivos que se importam, encadeados através de vários outros arquivos, podem fazer com que uma travessia de "2 saltos" puxe muito mais do que o esperado. Correção: rastreie os arquivos visitados durante a travessia e pare de revisitar o mesmo arquivo.
- Uma vizinhança de um salto ainda pode perder o arquivo realmente relevante. Algumas alterações arquitetônicas afetam um arquivo sem uma relação de importação direta, como um valor de configuração lido por convenção em vez de importação. Correção: trate o grafo como um ponto de partida, não como uma garantia, e deixe um humano ou uma passagem de pontuação de relevância barata adicionar arquivos que o grafo perde.
Alternativas
| Alternativa | Use Quando | Não Use Quando |
|---|---|---|
| Seleção manual de trechos | A tarefa afeta um ou dois arquivos que você já conhece | O repositório é grande o suficiente para que a seleção manual seja lenta ou propensa a erros |
| Contexto de repositório completo | O repositório é pequeno o suficiente para caber confortavelmente e o custo não é uma preocupação | O custo é importante, ou o repositório é grande o suficiente para arriscar a deterioração do contexto |
| Busca aumentada por recuperação (baseada em embedding) | Você precisa de relevância semântica, não apenas de relações de importação (por exemplo, "encontrar código relacionado a reembolsos") | A relação que você se importa é estrutural (o que chama o quê), que um grafo de dependência responde com mais precisão |
| Uma ferramenta dedicada de análise de código (por exemplo, o grafo de chamadas de um servidor de linguagem) | Você precisa de dados de grafo de chamadas interlinguagem ou altamente precisos em escala | Um script leve e de idioma único é suficiente para a tarefa em questão |
FAQs
Qual é a diferença entre um grafo de dependência e apenas usar grep para arquivos relacionados?
Grep encontra correspondências de texto, que podem tanto perder arquivos relevantes (nenhuma palavra-chave compartilhada) quanto incluir irrelevantes (uma correspondência de string acidental).
Um grafo de dependência é construído a partir de relações de importação reais, portanto, é mais preciso para a pergunta específica de "do que este arquivo depende".
Preciso de uma biblioteca real de análise estática, ou um script simples com `ast` é suficiente?
Para um repositório de idioma único e de tamanho moderado, um script simples baseado em ast, como o acima, geralmente é suficiente.
- Para repositórios muito grandes ou multilíngues, uma ferramenta dedicada (um servidor de linguagem,
pydepsou similar) será mais robusta. - Comece de forma simples e só recorra a ferramentas mais pesadas quando a versão simples demonstrar que perde casos que você se importa.
Quantos saltos devo percorrer no grafo?
Um salto (importações diretas e importadores diretos) é um padrão razoável para a maioria das alterações de arquivo único.
- Aumente para dois saltos para alterações em código utilitário amplamente compartilhado.
- Ir muito além disso geralmente reintroduz o problema do "repositório inteiro" que esta técnica existe para evitar.
Isso substitui a sumarização ou a segmentação de modelos?
Não, eles resolvem problemas diferentes e geralmente são combinados.
- A otimização por grafo de dependência decide quais arquivos incluir.
- A sumarização condensa um arquivo que ainda é muito longo, mesmo depois de selecionado.
- A segmentação de modelos decide qual modelo lida com o prompt resultante, agora menor.
O que acontece se o grafo perder um arquivo relevante?
O prompt não terá o contexto que o modelo precisava, o que pode produzir uma resposta incompleta ou errada.
Trate o grafo como um ponto de partida: para alterações de alto risco, peça a um humano para revisar quais arquivos foram selecionados antes de enviar o prompt.
Essa abordagem pode funcionar para linguagens além de Python?
Sim, o conceito é agnóstico à linguagem: qualquer linguagem com uma instrução estática de importação ou require/use pode ser analisada de forma semelhante.
A etapa de análise específica (ast aqui) é específica do Python; outras linguagens precisam de seu próprio analisador ou de uma ferramenta de análise estática de propósito geral.
Como lidar com importações circulares ao construir o grafo?
Importações circulares são aceitáveis para a construção do grafo, pois você está apenas registrando instruções de importação, não executando-as.
Elas importam para a travessia: rastreie os arquivos visitados para que um ciclo não cause a adição do mesmo arquivo, ou pior, cause recursão ilimitada em uma implementação de travessia ingênua.
Construir o grafo em si é caro?
Construí-lo é uma operação local e offline, ele analisa arquivos de origem no disco e não custa tokens de API.
Ele só precisa ser reconstruído quando o codebase muda significativamente, então o custo é amortizado em cada prompt que o reutiliza.
Devo incluir a própria estrutura do grafo no prompt, ou apenas os arquivos selecionados?
Ambos podem ajudar, dependendo da pergunta.
- Para "o que quebra se eu mudar isso", listar quais arquivos importam o alvo (as arestas do grafo) dá ao Claude uma estrutura útil mesmo antes de ele ler o código.
- Para a maioria das tarefas, o conteúdo dos arquivos selecionados é mais importante do que a forma do grafo em si.
Por que não enviar apenas nomes de arquivos e deixar o Claude pedir os que ele precisa?
Essa é uma alternativa válida, geralmente implementada como um loop de uso de ferramentas onde o Claude solicita arquivos específicos sob demanda.
Ele troca uma viagem de ida e volta extra (e o custo de token dessa viagem) por uma relevância potencialmente melhor do que um grafo pré-computado, e se combina bem com o cache de chamadas de ferramentas descrito em outra parte desta seção.
Uma janela de contexto maior torna a otimização por grafo de dependência desnecessária?
Não, uma janela maior muda o que você pode encaixar, não o que você deve enviar.
Arquivos desnecessários ainda custam tokens, e código irrelevante ainda pode diluir o foco do modelo na parte do codebase que realmente importa, que é o problema de deterioração do contexto abordado em outra parte desta seção.
Qual é um bom sinal de que a vizinhança de um salto é muito pequena?
Se as respostas do Claude repetidamente se referirem a um arquivo ou a um conceito que não lhe foi dado, isso é um sinal de que a profundidade da travessia ou a lógica de resolução (importações relativas, importações dinâmicas) está perdendo algo.
Aumente a contagem de saltos ou corrija a lacuna de resolução específica, em vez de optar por enviar o repositório inteiro.
Relacionados
- Como a Engenharia de Contexto Reduz o Gasto de Tokens - o modelo mental mais amplo em que esta técnica se encaixa.
- Noções Básicas de Engenharia de Prompt e Contexto - exemplos práticos de otimização de prompts, incluindo uma abordagem mais simples de pontuação de relevância.
- Sumarização Antes da Chamada - o que fazer quando um arquivo selecionado ainda é muito longo.
- Evitando Chamadas de Ferramentas Redundantes em Loops de Agente de Múltiplas Voltas - uma alternativa de uso de ferramentas para pré-computar um grafo.
- Deterioração do Contexto: Por Que Mais Tokens Não Significam Melhores Respostas - por que incluir arquivos não relacionados pode prejudicar a qualidade da resposta, não apenas o custo.
Versões da Stack: Escrito contra a linha de modelos Claude atual por volta de Junho de 2026 - Claude Fable 5, Claude Opus 4.8, Claude Sonnet 5 (o padrão), e Claude Haiku 4.5 - e o SDK oficial
anthropicpara Python (última versão 0.x). Nomes de modelos, preços e versões de SDK mudam rapidamente - verifique os detalhes atuais em platform.claude.com/docs antes de confiar neles.