La primera herramienta de un agente de soporte con LangGraph: búsqueda por palabras clave en la documentación
Cómo un agente de soporte con LangGraph recibe una sola herramienta de búsqueda sobre documentación en Markdown: por qué prescinde de RAG, cómo se separan herramienta e implementación y cómo el estado lleva las pruebas hasta la respuesta.
El grafo del agente de soporte de LEPA ya sabe bifurcarse según lo clara que sea una pregunta:
vague question → ask for clarification
clear question → continue
Pero "continuar" no significa nada si el agente no tiene con qué continuar. La siguiente incorporación le da algo concreto que hacer: buscar en la documentación de soporte de la escuela y basar cada respuesta en lo que encuentre.
Para qué sirven las herramientas
Un modelo de lenguaje puede sonar seguro hablando de cualquier cosa, y un agente de soporte de LEPA no puede inventarse los procedimientos de la escuela. La solución son las herramientas. Una herramienta es una acción que el agente, o un nodo del grafo, puede realizar fuera de los pesos congelados del modelo. Piensa en un recepcionista que solo habla frente a otro que puede abrir el manual del personal; el útil es el segundo. En un flujo de IA, el patrón es este:
User question
↓
Decide what is needed
↓
Use a tool (search docs, call an API, …)
↓
Read the tool result
↓
Answer with evidence
El mismo patrón apareció en proyectos anteriores. TAM usaba funciones de Python normales como herramientas, y DevAssist las exponía a los clientes mediante MCP. En este proyecto la herramienta vive dentro de la aplicación de LangGraph y escribe en el estado. La misma idea con otra fontanería.
La única herramienta
Hay exactamente una:
search_knowledge(query, category?)
Busca en archivos Markdown de una carpeta knowledge/:
knowledge/
authentication.md
students.md
attendance.md
grades.md
reports.md
Cada archivo tiene contenido de soporte de LEPA realista: problemas de inicio de sesión, matrículas, hojas de asistencia, publicación de notas e informes.
Lo que deliberadamente no es
Sin base de datos vectorial, sin embeddings y sin una arquitectura RAG completa. La búsqueda se mantiene simple a propósito:
- Los documentos se dividen por los encabezados
##. - Los fragmentos se puntúan por coincidencia de palabras clave.
- Se devuelven hasta 4 fragmentos, recortados de longitud.
- Cuando la clasificación ya conoce el tema, se da preferencia al archivo
{category}.mdcorrespondiente.
La razón es que este proyecto trata de LangGraph y no de investigación en recuperación de información. El README lo dice sin rodeos: la lección es herramienta más grafo, y la calidad de la búsqueda no es lo importante.
Dos capas en el código
La lógica existe de dos formas. search_knowledge_impl(...) es Python corriente que devuelve una lista de fragmentos, mientras que @tool search_knowledge(...) envuelve esa lógica para LangChain. El nodo de conocimiento del grafo se salta el envoltorio y llama a la implementación:
def run_knowledge(state: SupportState) -> dict:
query = last_user_text(state)
category = state.get("issue_category") or "unknown"
snippets = search_knowledge_impl(query, category=category)
return {"retrieved_documents": snippets}
Esa decisión tiene un propósito. La búsqueda queda a la vista, los tests se ejecutan sin conexión y el envoltorio @tool sigue disponible para futuros experimentos con llamadas a herramientas desde el LLM. Así, el grafo usa una capacidad con forma de herramienta aunque no sea el LLM quien elige la llamada.
Cómo cambia el flujo
Antes de las herramientas, la rama de continuar era un esbozo vacío. Con la herramienta queda así:
classify → continue
↓
knowledge (search_knowledge_impl → retrieved_documents)
↓
support (turn snippets into final_answer)
↓
END
Un ejemplo de LEPA
Pensemos en un usuario que pregunta por qué no aparecen las notas:
User: "Why aren't grades showing?"
↓
classify → grades
↓
knowledge searches grades.md
↓
snippets mention Draft vs Publish
↓
support answers using those snippets
Esa es la diferencia entre adivinar por qué faltan las notas y responder a partir de una página del manual que dice que los tutores solo ven las notas publicadas.
El soporte depende de lo que devuelve la herramienta
El nodo de soporte no crea una segunda base de conocimiento. Lee retrieved_documents del estado. Si no hay documentos, pide al usuario que indique el área de LEPA. Si los hay, construye la respuesta a partir de ellos. Si hay una clave de API configurada, también puede pedir a un LLM que reescriba esos mismos fragmentos de forma más limpia, y aun así recurre a una plantilla si el LLM falla.
La frontera es sencilla: la herramienta aporta las pruebas, el paso de soporte da forma a la respuesta y el grafo decide cuándo se ejecuta la herramienta. Esa separación ayudó a depurar. Ante cualquier respuesta errónea se podía preguntar si el fallo estaba en el enrutamiento, en la búsqueda o en la redacción.
Una lección heredada del trabajo con MCP
Un proyecto anterior con MCP enseñó que una herramienta debe diseñarse pensando en el modelo que leerá su salida, y eso vale incluso para una búsqueda por palabras clave. Los fragmentos de search_knowledge llevan una etiqueta como [grades.md] seguida del texto recortado de la sección, lo que muestra al siguiente paso, o a un futuro LLM, de dónde salieron las pruebas. El tamaño de los fragmentos también tiene un límite. Inundar el siguiente paso con un manual entero reproduciría el problema de exceso de contexto que surgió al diseñar search_code en DevAssist. El proyecto es distinto, pero la presión de diseño es la misma.
Lo que enseñó una sola herramienta
- Un grafo sin herramientas puede enrutar, pero no puede fundamentar sus respuestas.
- Una herramienta aburrida basta para cambiar el producto.
- Llamar a una herramienta desde un nodo es una forma válida de aprender antes de pasar a las llamadas a herramientas completas desde el LLM.
- En un tutorial de LangGraph, "todavía sin RAG" es una virtud y no un fallo.
- El estado funciona como bus: la salida de la herramienta se convierte en
retrieved_documentsy el soporte la lee.
En resumen
Con una herramienta, el agente de LEPA deja de ser un diagrama de flujo y puede consultar procedimientos reales de la escuela. En concreto, eso significa search_knowledge y search_knowledge_impl, una búsqueda básica en Markdown que se invoca desde el nodo knowledge y pasa los resultados al nodo support a través del estado. La pregunta difícil viene después: en el siguiente mensaje, ¿recuerda el agente la conversación? Ahí entran los checkpoints, thread_id y el estado de conversación a corto plazo.