The orchestrator is the central nervous system of Mesa de Ayuda IA, implemented inDocumentation Index
Fetch the complete documentation index at: https://mintlify.com/geremyjampiersalasgarcia-eng/Caso_Practico_Semillero_IA/llms.txt
Use this file to discover all available pages before exploring further.
backend/app/core/orchestrator.py as a LangGraph StateGraph. LangGraph was chosen over LangChain’s classic AgentExecutor for a concrete set of architectural reasons: the execution path is modeled as an explicit directed graph with named nodes and edges, making the flow fully auditable and testable; routing decisions are deterministic (the classifier runs at temperature 0 and the conditional edge function is pure Python); parallel fan-out for mixed-intent queries is a native feature, not a workaround; and the graph topology itself prevents infinite loops since there are no back-edges — every path leads to consolidate → END. The result is a system that is predictable enough to confidently deploy in a corporate sales context where wrong answers have real commercial consequences.
GraphState
The entire execution context of a single user request is stored in aTypedDict called GraphState. Every node in the graph reads from and writes to this shared state object — nodes never call each other directly.
| Field | Type | Set By | Purpose |
|---|---|---|---|
question | str | API request | The user’s raw question text |
category | str | classify node | One of the 6 intent categories |
history | List[Dict] | API request | Previous conversation turns for context |
image_data | Optional[str] | API request | Base64-encoded image for multimodal queries |
confirmation | Optional[bool] | API request | Explicit frontend confirmation flag for CRM registration |
agent_results | Annotated[List, operator.add] | Agent nodes | Accumulated AgentResult objects from all invoked agents |
final_answer | str | consolidate node | The synthesized answer returned to the user |
sources | List[Dict] | consolidate node | Merged source list from all agents |
agents_invoked | Annotated[List, operator.add] | Agent nodes | Names of every agent that participated |
warnings | List[str] | classify / consolidate | Non-fatal advisory messages |
start_time | float | classify node | time.time() at classification start, used for latency calculation |
tokens_input | Annotated[int, operator.add] | Agent nodes + consolidate | Accumulated input token count across all Gemini calls |
tokens_output | Annotated[int, operator.add] | Agent nodes + consolidate | Accumulated output token count across all Gemini calls |
The Annotated[List, operator.add] pattern
Four fields — agent_results, agents_invoked, tokens_input, and tokens_output — use Annotated[..., operator.add] as their type. This is LangGraph’s reducer mechanism: when multiple parallel branches write to the same field, LangGraph uses the reducer function (operator.add) to merge the writes rather than letting one overwrite the other. For lists this means concatenation; for integers it means addition. Without this annotation, a mixta query with three parallel agent nodes would produce a race condition where the last-finishing agent’s result would silently discard the other two.
Graph Nodes
The graph contains seven named nodes, each corresponding to a Python function:classify
Entry point. Calls
classify_intent(), detects image data, checks the confirmation flag, and inspects conversation history to detect in-progress registration flows. Sets state["category"] and state["start_time"].catalogo
Calls
agent_registry.get_agent("agente_catalogo").process_query(state["question"]) and writes the AgentResult plus token counts into state.politicas
Calls
agent_registry.get_agent("agente_politicas").process_query(state["question"]) and writes the AgentResult into state.proceso_ventas
Calls
agent_registry.get_agent("agente_proceso_ventas").process_query(state["question"]) and writes the AgentResult into state.multimodal
Retrieves
agente_multimodal from registry (asserts it is a MultimodalAgent instance) and calls process_query(question, image_data=state.get("image_data")).accion
Retrieves
agente_accion from registry (asserts it is an AccionAgent instance) and calls process_query(question, confirmation=..., history=...), passing both the confirmation flag and the full conversation history.consolidate — the final convergence node:
- If exactly one
AgentResultis present, itsanswerfield becomesfinal_answerdirectly. No additional LLM call is made. - If two or more
AgentResultobjects are present (possible formixta), the node loadsprompts/orchestrator_prompt.md, builds a consolidation prompt with all partial answers labeled by agent name, and makes one final Gemini call to synthesize a single coherent response. If Gemini fails, a safe fallback concatenates the individual answers separated by---dividers.
Conditional Routing
Theroute_agents() function is registered as the decision function for the conditional edge that follows the classify node. It reads state["category"] and returns a list of node names to execute next:
["catalogo", "politicas", "proceso_ventas"] for mixta triggers LangGraph’s parallel fan-out — all three nodes start executing concurrently, writing their results into the shared GraphState via operator.add accumulation.
Graph Construction
The complete graph topology is declared using LangGraph’sStateGraph builder API:
add_conditional_edges() call takes three arguments: the source node ("classify"), the decision function (route_agents), and a mapping dictionary that translates the string values returned by the decision function into actual node names. Every agent node is then wired to consolidate with add_edge(), and consolidate leads unconditionally to END. This topology guarantees that:
- Every execution path passes through
classifyexactly once. - Every execution path terminates at
consolidate → END— there are no cycles. - Parallel branches from
mixtaall converge atconsolidatebefore the final answer is produced.
Confirmation Flow
Theaccion_registro category is special because the CRM registration agent uses a multi-step confirmation flow. The classify node detects confirmation in three distinct ways before the LLM classifier even runs:
- Explicit
confirmationflag — the frontend sends"confirmation": truein the JSON body when the user clicks a confirmation button in the UI. This is the most reliable signal and short-circuits everything else. - Short confirmation text — if the user types any of the recognized short phrases (
"sí","confirmar","confirmo", etc.), it is treated as a confirmation even without the explicit flag. - History inspection — if neither of the above applies, the node walks backwards through the conversation history to find the last assistant message. If that message contains the phrases
"registrar la oportunidad"or"para continuar", the system infers that the previous turn was anagente_accionsummary pending confirmation, and routes the new message toaccionto complete the registration.
The compiled
orchestrator_app object is a LangGraph CompiledStateGraph. It is invoked by ChatService (in backend/app/services/chat_service.py) via orchestrator_app.invoke(initial_state), where initial_state is a dictionary populated from the incoming API request. The return value is the final GraphState dictionary, from which ChatService extracts final_answer, sources, agents_invoked, tokens_input, tokens_output, and warnings to build the HTTP response. The compiled graph is module-level and stateless between requests — each invoke() call creates a fresh, isolated GraphState from the provided initial values.