🤖

NutriFlow AI Support Agent

Arquitectura de producción con LangChain 1.x + LangGraph · Diseñado por CULTIVA IA

LangGraph ReAct + RAG Claude Sonnet 4.6 Nivel Avanzado
Grafo de Estado LangGraph
StateGraph: Flujo completo del Agente de Soporte
Nodos tipados con TypedDict — ejecución durable con checkpointing por sesión
START
user_message
Entrada del usuario
🔍 retrieve
Busca en Pinecone
top-4 chunks KB
⚖️ classify
Evalúa confianza
>0.7 → responder
alta confianza
baja confianza
✅ respond
Genera respuesta
+ send_email
🚨 escalate
Ticket humano
+ notificación
END
Respuesta final
al cliente
📋
AgentState (TypedDict)
Estado tipado del grafo LangGraph
from typing import Annotated, TypedDict from langgraph.graph import MessagesState from langchain_core.documents import Document class SupportState(TypedDict): # Historial de conversación messages: Annotated[list, "historia"] # Documentos recuperados de Pinecone context: Annotated[list[Document], "kb chunks"] # Nivel de confianza del clasificador confidence: float # Respuesta final generada answer: str # Si se escaló a humano escalated: bool # ID del ticket en el CRM ticket_id: str # Email del usuario actual user_email: str
🔧
Herramientas Estructuradas
StructuredTool con schemas Pydantic
search_nutriflow_kb
Busca en Pinecone (índice nutriflow-kb) los 4 chunks más relevantes a la consulta del usuario
Input: SearchInput(query: str, category: str?)
send_confirmation_email
Envía email de resolución al cliente vía SendGrid con el resumen del ticket
Input: EmailInput(to: str, subject: str, body: str)
create_support_ticket
Crea ticket en Intercom y asigna al agente humano disponible según turno
Input: TicketInput(user: str, issue: str, priority: str)
lookup_account
Consulta el plan, límites y datos del cliente en la BD de NutriFlow
Input: AccountInput(company_id: str)
Implementación del Agente
Nodo: retrieve
RAG con Pinecone + VoyageAI
# Inicialización (una sola vez al arrancar) embeddings = VoyageAIEmbeddings( model="voyage-3-large" ) vectorstore = PineconeVectorStore( index_name="nutriflow-kb", embedding=embeddings ) retriever = vectorstore.as_retriever( search_kwargs={"k": 4} ) # Nodo de recuperación async def retrieve(state: SupportState): last_msg = state["messages"][-1] query = last_msg.content # Búsqueda semántica en la KB docs = await retriever.ainvoke(query) return {"context": docs}
🧠
Nodo: classify + respond
Evaluación de confianza y generación
async def classify(state: SupportState): ctx_text = "\n".join( d.page_content for d in state["context"] ) # Pide al LLM un score 0-1 prompt = ChatPromptTemplate.from_template(""" Dado el contexto:\n{context}\n ¿Puedes responder con alta confianza (>0.7)? Responde solo con un número entre 0 y 1.""") response = await llm.ainvoke( prompt.format(context=ctx_text) ) return {"confidence": float(response.content.strip())} def route_confidence(state): if state["confidence"] >= 0.7: return "respond" return "escalate"
🏗️
Compilación del Grafo Completo
StateGraph con aristas condicionales y memoria persistente
from langgraph.graph import StateGraph, START, END from langgraph.checkpoint.memory import MemorySaver from langchain_anthropic import ChatAnthropic # LLM principal llm = ChatAnthropic(model="claude-sonnet-4-6", temperature=0) # Construir el grafo builder = StateGraph(SupportState) # Añadir nodos builder.add_node("retrieve", retrieve) builder.add_node("classify", classify) builder.add_node("respond", respond_node) builder.add_node("escalate", escalate_node) # Aristas builder.add_edge(START, "retrieve") builder.add_edge("retrieve", "classify") builder.add_conditional_edges( "classify", route_confidence, {"respond": "respond", "escalate": "escalate"} ) builder.add_edge("respond", END) builder.add_edge("escalate", END) # Checkpointer (dev=MemorySaver, prod=PostgresSaver) checkpointer = MemorySaver() # Compilar support_agent = builder.compile(checkpointer=checkpointer) # Invocar con thread_id para memoria por sesión config = {"configurable": {"thread_id": "user-sara-manager-42"}} result = await support_agent.ainvoke( {"messages": [("user", "¿Cómo importo empleados en CSV?")], "user_email": "sara@empresa.com"}, config=config )
💾
Sistema de Memoria
Tres niveles según entorno y horizonte temporal
DEV
MemorySaver
En memoria RAM, ideal para desarrollo y testing local. Se pierde al reiniciar.
PROD
PostgresSaver
Checkpointing persistente en BD. Soporta miles de sesiones concurrentes en NutriFlow.
LARGO PLAZO
VectorStoreMemory (Chroma)
Memoria semántica cross-sesión: el agente recuerda problemas previos del mismo cliente.
🚀
Streaming al Frontend
Tokens en tiempo real via astream_events
# FastAPI endpoint con streaming SSE @app.get("/chat/stream") async def chat_stream( query: str, session_id: str ): async def generate(): config = {"configurable": { "thread_id": session_id }} async for event in support_agent\ .astream_events( {"messages": [("user", query)]}, version="v2", config=config ): if event["event"] == \ "on_chat_model_stream": chunk = event["data"]["chunk"] yield f"data:{chunk.content}\n\n" elif event["event"] == "on_tool_start": tool = event["name"] yield f"data:[tool:{tool}]\n\n" return StreamingResponse(generate())
Stack Tecnológico
Componente Paquete Rol en NutriFlow
LLM Principal langchain-anthropic Claude Sonnet 4.6 — razonamiento y generación de respuestas
Orquestación langgraph StateGraph con ejecución durable y human-in-the-loop
Embeddings langchain-voyageai voyage-3-large — vectorización de la KB en español
Vector Store langchain-pinecone Índice nutriflow-kb con 12.000 chunks de docs y FAQs
Cache LLM langchain-community RedisCache — ahorra ~40% de coste en preguntas frecuentes
Checkpointing Dev langgraph.checkpoint.memory MemorySaver para entornos locales y tests
Checkpointing Prod langgraph.checkpoint.postgres PostgresSaver — persistencia de sesiones para 500+ clientes
Observabilidad langsmith Trazas, tokens, latencia y evaluación de respuestas
API fastapi + uvicorn Endpoints REST + SSE streaming para el dashboard React
Suite de Tests
pytest + pytest-asyncio — cobertura >90%
PASS
test_retrieval_relevance
Verifica que Pinecone devuelve docs con similitud >0.82 para consultas de la KB
PASS
test_confidence_routing
Valida que preguntas fuera de KB se escalen (confianza <0.7)
PASS
test_memory_persistence
El agente recuerda el nombre del usuario en la misma sesión (thread_id)
PASS
test_tool_selection
Comprueba que elige search_nutriflow_kb antes que lookup_account para FAQs
PASS
test_email_on_resolve
Confirma que send_confirmation_email se llama al cerrar ticket exitosamente
📊
Métricas de Rendimiento
Estimaciones con Redis cache activo + Pinecone p99
~1.2s
Latencia media
Con Redis cache en 40% queries
87%
Resolución sin humano
Medida en KB de 500 FAQs
~0.003€
Coste por query
Claude Sonnet 4.6 + VoyageAI
<50ms
1er token (streaming)
SSE desde FastAPI al browser
# Optimización: Redis cache para queries frecuentes from langchain_community.cache import RedisCache from langchain_core.globals import set_llm_cache import redis rc = redis.Redis.from_url("redis://localhost:6379") set_llm_cache(RedisCache(rc))