Otimização
Otimize aplicações LangGraph, reduza chamadas de nós, estratégias de cache, lotação de requisições LLM, gerenciamento de tokens e ajuste de desempenho do grafo.
À medida que as aplicações LangGraph crescem, a otimização de desempenho e de custo torna-se crítica. Esta lição cobre estratégias para reduzir latência, minimizar o consumo de tokens, armazenar resultados em cache, agrupar operações e ajustar a execução do grafo.
Onde a Otimização é Importante
Área | Impacto | Otimização |
|---|---|---|
Chamadas de LLM | Custo + latência | Cache, agrupamento e seleção de modelo |
Número de nós | Latência | Mescle nós triviais, reduza nós desnecessários |
Tamanho do estado | Memória + serialização | Reduza o estado, use estruturas de dados eficientes |
Checkpointing | Latência | Reduza a frequência de checkpointing |
Paralelismo | Throughput | Fan-out inteligente, evite contenção |
Cache de Chamadas LLM
Armazene em cache as respostas de LLM para entradas idênticas:
from langchain.globals import set_llm_cache
from langchain.cache import InMemoryCache
import hashlib
# Enable caching
set_llm_cache(InMemoryCache())
# Cache responses based on prompt content
@tool
def cached_search(query: str) -> str:
"""Search with caching for identical queries."""
return perform_search(query)
# Or use a persistent cache
from langchain.cache import SQLiteCache
set_llm_cache(SQLiteCache(database_path=".llm_cache.db"))O cache de LLM é mais eficaz quando os usuários fazem perguntas semelhantes repetidamente. A invalidação de cache deve ser baseada em tempo ou em gatilhos de atualização manual.
Cache Personalizado em Nós
import hashlib
import json
from functools import lru_cache
# Simple in-memory cache with TTL
class TTLCache:
def __init__(self, ttl_seconds: int = 3600):
self.cache = {}
self.ttl = ttl_seconds
def get(self, key: str):
if key in self.cache:
value, timestamp = self.cache[key]
if time.time() - timestamp < self.ttl:
return value
del self.cache[key]
return None
def set(self, key: str, value):
self.cache[key] = (value, time.time())
cache = TTLCache(ttl_seconds=300)
def cached_llm_node(state: State) -> dict:
# Generate cache key from input
prompt = state["messages"][-1].content
cache_key = hashlib.md5(prompt.encode()).hexdigest()
# Check cache
cached = cache.get(cache_key)
if cached:
print("Cache hit!")
return {"output": cached}
# Compute and cache
result = llm.invoke(prompt).content
cache.set(cache_key, result)
return {"output": result}Gerenciamento de Tokens
Orçamento de Tokens
import tiktoken
class TokenBudget:
def __init__(self, model: str, max_tokens: int = 8000):
self.encoder = tiktoken.encoding_for_model(model)
self.max_tokens = max_tokens
self.used_tokens = 0
def count_tokens(self, text: str) -> int:
return len(self.encoder.encode(text))
def can_add(self, text: str) -> bool:
return self.used_tokens + self.count_tokens(text) <= self.max_tokens
def add(self, text: str) -> bool:
tokens = self.count_tokens(text)
if self.used_tokens + tokens <= self.max_tokens:
self.used_tokens += tokens
return True
return False
def budget_aware_node(state: State) -> dict:
budget = TokenBudget("gpt-4o", max_tokens=4000)
trimmed_messages = []
for msg in reversed(state["messages"]):
if budget.add(msg.content):
trimmed_messages.insert(0, msg)
response = llm.invoke(trimmed_messages)
return {"response": response.content}Contagem de Tokens em Streaming
class TokenCounter:
def __init__(self):
self.prompt_tokens = 0
self.completion_tokens = 0
def count_prompt(self, messages: list) -> int:
enc = tiktoken.encoding_for_model("gpt-4o")
total = 0
for msg in messages:
total += len(enc.encode(msg.content))
self.prompt_tokens = total
return total
def count_completion(self, text: str) -> int:
enc = tiktoken.encoding_for_model("gpt-4o")
self.completion_tokens = len(enc.encode(text))
return self.completion_tokensAcompanhe o consumo de tokens por invocação de grafo. Isso ajuda a identificar quais nós são mais caros e onde concentrar os esforços de otimização.
Agrupamento de Chamadas LLM
Quando forem necessárias múltiplas chamadas independentes de LLM, agrupe-as:
from langchain_core.runnables import RunnableParallel
# Define multiple chains
chain_a = prompt_a | llm | StrOutputParser()
chain_b = prompt_b | llm | StrOutputParser()
chain_c = prompt_c | llm | StrOutputParser()
# Run them in parallel (batched)
combined = RunnableParallel(
result_a=chain_a,
result_b=chain_b,
result_c=chain_c
)
def batched_node(state: State) -> dict:
results = combined.invoke({
"topic": state["topic"],
"question": state["question"]
})
return {
"analysis_a": results["result_a"],
"analysis_b": results["result_b"],
"analysis_c": results["result_c"]
}Reduzindo Nós Desnecessários
Mesclar Nós de Transformação Triviais
# Instead of three nodes for simple transformations:
def clean(state): return {"text": state["text"].strip()}
def lower(state): return {"text": state["text"].lower()}
def truncate(state): return {"text": state["text"][:100]}
# Merge into one:
def process_text(state: State) -> dict:
return {"text": state["text"].strip().lower()[:100]}Pular Nós Sem Operação
def conditional_node(state: State) -> dict:
if state.get("skip_processing"):
return {} # Return empty — no state changes, minimal overhead
return {"result": expensive_operation(state)}Otimização de Checkpointing
# Reduce checkpoint frequency
# Instead of checkpointing after every node, batch updates
# Option: Use a single node for sequential operations
def batched_operations(state: State) -> dict:
# Multiple operations in one node = one checkpoint
cleaned = state["input"].strip()
processed = clean(cleaned)
validated = validate(processed)
return {"output": validated}
# Option: Disable checkpointing for intermediate nodes
app = builder.compile(
checkpointer=checkpointer,
interrupt_before_nodes=[], # Only checkpoint at specific points
interrupt_after_nodes=[]
)Estratégia de Seleção de Modelo
Use o modelo certo para cada nó:
# Expensive model for complex reasoning
reasoning_llm = ChatOpenAI(model="gpt-4o", temperature=0.3)
# Cheap model for simple tasks
fast_llm = ChatOpenAI(model="gpt-4o-mini", temperature=0.0)
# Fastest model for classification/routing
router_llm = ChatOpenAI(model="gpt-4o-mini", temperature=0.0)
def classify_node(state: State) -> dict:
# Router nodes can use the cheapest model
category = router_llm.invoke(f"Classify: {state['query']}")
return {"category": category.content.strip()}
def analyze_node(state: State) -> dict:
# Complex nodes use the expensive model
analysis = reasoning_llm.invoke(f"Deep analysis: {state['query']}")
return {"analysis": analysis.content}
def format_node(state: State) -> dict:
# Simple formatting uses cheap model
formatted = fast_llm.invoke(f"Format: {state['analysis']}")
return {"formatted": formatted.content}Usar modelos diferentes para nós diferentes pode reduzir os custos em 5-10x mantendo a qualidade: use modelos baratos para roteamento/tarefas simples e modelos caros apenas para raciocínio complexo.
Lista de Verificação de Otimização
Estratégia | Esforço | Impacto |
|---|---|---|
Cache de respostas de LLM | Baixo | Alto (consultas repetidas) |
Seleção de modelo por nó | Baixo | Alto (custo) |
Truncamento de tokens | Médio | Alto (gerenciamento de contexto) |
Mesclar nós triviais | Baixo | Médio (latência) |
Reduzir frequência de checkpointing | Baixo | Médio (latência) |
Trabalho independente em paralelo | Médio | Alto (latência) |
Agrupar múltiplas chamadas de LLM | Médio | Médio (throughput) |
Streaming em vez de espera | Alto | Alto (experiência do usuário) |
Perguntas de Prática (Practice Questions)
Como responder
Questão de múltipla escolha com 4 opções. Selecione uma opção e escolha Verificar resposta.
What is the most impactful optimization for LLM costs?
Como responder
Questão de múltipla escolha com 4 opções. Selecione uma opção e escolha Verificar resposta.
Which model strategy optimizes cost in a multi-node graph?
Como responder
Questão de múltipla escolha com 4 opções. Selecione uma opção e escolha Verificar resposta.
What is a good cache TTL for LLM responses?
Como responder
Questão de múltipla escolha com 4 opções. Selecione uma opção e escolha Verificar resposta.
When should you merge multiple nodes into one?
Como responder
Questão de múltipla escolha com 4 opções. Selecione uma opção e escolha Verificar resposta.
What is the benefit of streaming LLM tokens?
Como responder
Questão de múltipla escolha com 4 opções. Selecione uma opção e escolha Verificar resposta.
How does token budgeting help with LLM optimization?
Como responder
Questão de múltipla escolha com 4 opções. Selecione uma opção e escolha Verificar resposta.
What is a disadvantage of excessive checkpointing?
Como responder
Questão de múltipla escolha com 4 opções. Selecione uma opção e escolha Verificar resposta.
What does RunnableParallel enable?
Como responder
Questão de múltipla escolha com 4 opções. Selecione uma opção e escolha Verificar resposta.
What tool can count tokens for OpenAI models?
Como responder
Questão de múltipla escolha com 4 opções. Selecione uma opção e escolha Verificar resposta.
Which optimizer strategy reduces latency the most?
Principais Conclusões
- Cacheie respostas de LLM para consultas repetidas e reduza custo e latência
- Use o modelo apropriado por nó: barato para o simples, caro para o complexo
- Acompanhe o consumo de tokens com tiktoken para gerenciar janelas de contexto e orçamentos
- Mescle nós triviais para reduzir a sobrecarga, mas mantenha nós significativos separados
- Reduza a frequência de checkpointing para grafos de alto throughput
- Paralelize trabalho independente com fan-out para melhorar a latência
- Agrupe múltiplas chamadas independentes de LLM com RunnableParallel
- O streaming melhora o desempenho percebido mesmo quando a latência total é a mesma