Ola, nelson!
Sim, essa abordagem híbrida que você descreveu não só faz sentido como é, na prática, uma arquitetura bastante utilizada em cenários mais avançados.
Usar o LangGraph como orquestrador e, dentro dele, ter nós que encapsulam agentes criados com create_agent do LangChain é uma forma bem equilibrada de unir simplicidade com controle. O LangGraph cuida do fluxo (roteamento, estados, decisões), enquanto cada agente fica responsável por resolver um tipo específico de tarefa com suas próprias ferramentas e contexto.
Isso é especialmente útil quando você tem:
- múltiplos domínios (ex: financeiro, suporte, CRM),
- ferramentas diferentes por contexto,
- necessidade de roteamento inteligente antes da execução.
Nesse cenário, o seu “roteador” no grafo decide qual agente chamar, e cada agente atua como um nó especializado. Ou seja: você não está “misturando errado”, está compondo responsabilidades — o que é uma boa prática.
Sobre a segunda parte (estado e memória), aqui está o ponto mais importante: no LangGraph, o estado pertence ao grafo, não ao agente isolado. Então você precisa pensar em dois níveis de memória:
- Estado global do grafo (AgentState)
Esse estado é compartilhado entre os nós e geralmente contém coisas como:
- histórico de mensagens
- intenção identificada
- dados intermediários
Exemplo conceitual:
from typing import TypedDict, List
from langchain.schema import BaseMessage
class AgentState(TypedDict):
messages: List[BaseMessage]
user_name: str
intent: str
- Memória por agente (opcional)
Se você usar create_agent com MemorySaver, cada agente pode manter sua própria memória, mas aí entra uma decisão de arquitetura:
- Se você quer memória centralizada → use só o estado do grafo
- Se você quer memória especializada por agente → cada nó pode ter seu próprio
checkpointer
Exemplo simplificado de arquitetura híbrida
from langgraph.graph import StateGraph
from langgraph.checkpoint.memory import MemorySaver
from langchain.agents import create_agent
memory = MemorySaver()
# agentes especializados
agent_financeiro = create_agent(model=llm, tools=tools_fin, checkpointer=memory)
agent_suporte = create_agent(model=llm, tools=tools_sup, checkpointer=memory)
def roteador(state: AgentState):
ultima_msg = state["messages"][-1].content
if "pagamento" in ultima_msg:
return "financeiro"
return "suporte"
def no_financeiro(state: AgentState):
resposta = agent_financeiro.invoke(
{"messages": state["messages"]},
config={"configurable": {"thread_id": "user1"}}
)
return {"messages": resposta["messages"]}
def no_suporte(state: AgentState):
resposta = agent_suporte.invoke(
{"messages": state["messages"]},
config={"configurable": {"thread_id": "user1"}}
)
return {"messages": resposta["messages"]}
graph = StateGraph(AgentState)
graph.add_node("roteador", roteador)
graph.add_node("financeiro", no_financeiro)
graph.add_node("suporte", no_suporte)
graph.set_entry_point("roteador")
graph.add_conditional_edges(
"roteador",
lambda state: roteador(state),
{
"financeiro": "financeiro",
"suporte": "suporte"
}
)
Ponto de atenção importante
Se você usar o mesmo thread_id para todos os agentes, a memória pode “vazar” entre contextos. Algumas estratégias:
- usar
thread_id por usuário (ex: "user_123") - ou por agente + usuário (
"user_123_financeiro")
Abçs.