Introducción
Siempre he caminado uno al lado del otro, tomados de la mano.
Recuerdo haber escuchado “Aprenda estadísticas para saber qué hay detrás de los algoritmos” Cuando comencé a estudiar ciencia de datos. Si bien todo eso fue fascinante para mí, también fue realmente abrumador.
El hecho es que hay demasiados conceptos estadísticos, pruebas y distribuciones para realizar un seguimiento. Si no sabes de qué estoy hablando, solo visite el Scipy.stats página, y lo entenderás.
Si tiene la edad suficiente en el campo de la ciencia de datos, probablemente marcó (o incluso impreso) una de esas hojas de trucos de prueba estadística. Fueron populares por un tiempo. Pero ahora, los modelos de idiomas grandes se están convirtiendo en una especie de “segundo cerebro” para nosotros, ayudándonos a consultar rápidamente a prácticamente cualquier información que tengamos información, con el beneficio adicional de resumirlo y adaptarse a nuestras necesidades.
Con eso en mente, mi pensamiento era que elegir la prueba estadística correcta puede ser confuso porque depende de tipos de variables, supuestos, etc.
Entonces, pensé que podría obtener un asistente para ayudar con eso. Entonces, mi proyecto tomó forma.
- Usé langgraph para construir un agente de varios pasos
- El front-end fue construido con racionalización
- El agente puede consultar rápidamente la documentación de Stats Scipy y recuperar el código correcto para cada situación específica.
- Luego, nos da una muestra de código de pitón
- Se implementa en aplicaciones de transmisión, en caso de que desee probarlo.
- Enlace de la aplicación: https://ai-statistical-advisor.streamlit.app/
¡Asombroso!
Vamos a sumergirnos y aprender a construir este agente.
Langgraph
Langgraph es una biblioteca que ayuda a construir aplicaciones complejas de varios pasos con modelos de idiomas grandes (LLM) representándolos como un gráfico. Esta arquitectura gráfica permite a los desarrolladores crear condiciones, bucles, que lo hacen útil para crear agentes y chatbots sofisticados que puedan decidir qué hacer a continuación en función de los resultados de un paso anterior
Básicamente, convierte una secuencia rígida de acciones en un proceso de toma de decisiones flexible y dinámico. En Langgraph, cada nodo es una función o herramienta.
A continuación, aprendamos más sobre el agente que vamos a crear en esta publicación.
Agente de asesor estadístico
Este agente es un asesor estadístico. Entonces, la idea principal es que:
- El bot recibe una pregunta relacionada con las estadísticas, como “Cómo comparar los medios de dos grupos“.
- Verifica la pregunta y determina si necesita consultar la documentación de Scipy o simplemente dar una respuesta directa.
- Si es necesario, el agente utiliza una herramienta RAG en la documentación de SciPy integrada
- Devuelve una respuesta.
- Si corresponde, devuelve una muestra de código Python sobre cómo realizar la prueba estadística.
Veamos rápidamente el gráfico generado por Langgraph para mostrar a este agente.
Excelente. ¡Ahora, cortemos a la persecución y comencemos a codificar!
Código
Para facilitar las cosas, dividiré el desarrollo en módulos. Primero, instalemos los paquetes que necesitaremos.
pip install chromadb langchain-chroma langchain-community langchain-openai
langchain langgraph openai streamlit
Trozo e incrustación
A continuación, crearemos el script para tomar nuestra documentación y crear trozos de texto, así como incorporar esos fragmentos. Hacemos eso para que sea más fácil para las bases de datos vectoriales como ChromaDB Para buscar y recuperar información.
Entonces, creé esta función embed_docs() que puede ver en el repositorio de GitHub vinculado aquí.
- La función toma la documentación de Scipy (que es de código abierto bajo la licencia BSD)
- Lo divide en grietas de 500 tokens y se superpone a 50 tokens.
- Hace que la incrustación (transforme el texto en valores numéricos para la búsqueda de DB vectorial optimizada) utilizando
OpenAIEmbedding - Ahorra los incrustaciones en un caso de
ChromaDB
Ahora los datos están listos como una base de conocimiento para una generación de recuperación aumentada (RAG). Pero necesita un retriever que pueda buscar y encontrar los datos. Eso es lo que el retriever hace.
Perdiguero
El get_doc_answer() la función:
- Cargue la instancia de ChromAdB creada anteriormente.
- Crear una instancia de
OpenAI GPT 4o - Crear un
retrieverobjeto - Pegue todo junto en un
retrieval_chainque recibe una pregunta del usuario, la envía al LLM - El modelo usa el
retrieverPara acceder a la instancia de ChromAdB, obtenga datos relevantes sobre las pruebas estadísticas y devuelva la respuesta al usuario.
Ahora tenemos el trapo completado con los documentos incrustados y el Retriever listo. Pasemos a los nodos del agente.
Nodos de agente
Langgraph tiene esta interesante arquitectura que considera cada nodo como una función. Por lo tanto, ahora debemos crear las funciones para manejar cada parte del agente.
Seguiremos el flujo y comenzaremos con el classify_intent nodo. Dado que algunos nodos necesitan interactuar con un LLM, necesitamos generar un cliente.
from rag.retriever import get_doc_answer
from openai import OpenAI
import os
from dotenv import load_dotenv
load_dotenv()
# Instance of OpenAI
client = OpenAI()
Una vez que iniciamos el agente, recibirá una consulta del usuario. Por lo tanto, este nodo verificará la pregunta y decidirá si el próximo nodo será una respuesta simple o si necesita buscar la documentación de Scipy.
def classify_intent(state):
"""Check if the user question needs a doc search or can be answered directly."""
question = state["question"]
response = client.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "system", "content": "You are an assistant that decides if a question about statistical tests needs document lookup or not. If it is about definitions or choosing the right test, return 'search'. Otherwise return 'simple'."},
{"role": "user", "content": f"Question: {question}"}
]
)
decision = response.choices[0].message.content.strip().lower()
return {"intent": decision} # "search" or "simple"
Si se hace una pregunta sobre conceptos o pruebas estadísticas, entonces el retrieve_info() El nodo está activado. Realiza el trapo en la documentación.
def retrieve_info(state):
"""Use the RAG tool to answer from embedded docs."""
question = state["question"]
answer = get_doc_answer(question=question)
return {"rag_answer": answer}
Una vez que se recupera la parte adecuada del texto de ChromAdB, el agente va al siguiente nodo para generar una respuesta.
def respond(state):
"""Build the final answer."""
if state.get("rag_answer"):
return {"final_answer": state["rag_answer"]}
else:
return {"final_answer": "I'm not sure how to help with that yet."}
Finalmente, el último nodo es generar un código, si eso es aplicable. Es decir, si hay una respuesta donde la prueba se puede hacer usando SciPy, habrá un código de muestra.
def generate_code(state):
"""Generate Python code to perform the recommended statistical test."""
question = state["question"]
suggested_test = state.get("rag_answer") or "a statistical test"
prompt = f"""
You are a Python tutor.
Based on the following user question, generate a short Python code snippet using scipy.stats that performs the appropriate statistical test.
User question:
{question}
Answer given:
{suggested_test}
Only output code. Don't include explanations.
"""
response = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": prompt}]
)
return {"code_snippet": response.choices[0].message.content.strip()}
Observe algo importante aquí: todas las funciones en nuestros nodos siempre tienen state Como argumento porque el El estado es la única fuente de verdad para todo el flujo de trabajo. Cada función, o “nodo”, en el gráfico se lee y escribe en este objeto de estado central.
Por ejemplo:
- El
classify_intentla función lee el pregunta del estado y agrega un intención llave. - El
retrieve_infola función puede leer lo mismo pregunta y agregar un rag_swerque la función de respuesta finalmente se lee para construir el Final_answer. Este diccionario estatal compartido es cómo los diferentes pasos en el proceso de razonamiento y la toma de acciones del agente permanecen conectados.
A continuación, ¡armemos todo y construyamos nuestro gráfico!
Construyendo el gráfico
El gráfico es el agente en sí. Entonces, lo que estamos haciendo aquí es básicamente decirle a Langgraph cuáles son los nodos que tenemos y cómo se conectan entre sí, para que el marco pueda hacer que la información se ejecute de acuerdo con ese flujo.
Importemos los módulos.
from langgraph.graph import StateGraph, END
from typing_extensions import TypedDict
from langgraph_agent.nodes import classify_intent, retrieve_info, respond, generate_code
Defina nuestro esquema de estado. ¿Recuerdas ese diccionario que el agente usa para conectar los pasos del proceso? Eso es todo.
# Define the state schema (just a dictionary for now)
class TypedDictState(TypedDict):
question: str
intent: str
rag_answer: str
code_snippet: str
final_answer: str
Aquí, crearemos una función que construya el gráfico.
- Para decirle a Langgraph cuáles son los pasos (funciones) en el proceso, usamos
add_node - Una vez que hemos enumerado todas las funciones, comenzamos a crear los bordes, que son las conexiones entre los nodos.
- Comenzamos el proceso con
set_entry_point. Esta es la primera función que se utilizará. - Usamos
add_edgePara conectar un nodo a otro, utilizando el primer argumento como la función de la cual proviene la información, y el segundo argumento es a dónde va. - Si tenemos una condición a seguir, usamos
add_conditional_edges - Usamos
ENDPara terminar el gráfico ycompilepara construirlo.
def build_graph():
# Build the LangGraph flow
builder = StateGraph(TypedDictState)
# Add nodes
builder.add_node("classify_intent", classify_intent)
builder.add_node("retrieve_info", retrieve_info)
builder.add_node("respond", respond)
builder.add_node("generate_code", generate_code)
# Define flow
builder.set_entry_point("classify_intent")
builder.add_conditional_edges(
"classify_intent",
lambda state: state["intent"],
{
"search": "retrieve_info",
"simple": "respond"
}
)
builder.add_edge("retrieve_info", "respond")
builder.add_edge("respond", "generate_code")
builder.add_edge("generate_code", END)
return builder.compile()
Con nuestra función de Graph Builder lista, todo lo que tenemos que hacer ahora es crear un hermoso front-end donde podemos interactuar con este agente.
Hagamos eso ahora.
Front-end
El front-end es la pieza final del rompecabezas, donde creamos una interfaz de usuario que nos permite ingresar fácilmente una pregunta en un cuadro de texto adecuado y ver la respuesta correctamente formateada.
Elegí Streamlit porque es muy fácil de prototipos e implementados. Comencemos con las importaciones.
import os
import time
import streamlit as st
Luego, configuramos el aspecto de la página.
# Config page
st.set_page_config(page_title="Stats Advisor Agent",
page_icon='🤖',
layout="wide",
initial_sidebar_state="expanded")
Cree una barra lateral, donde el usuario pueda ingresar su tecla API de OpenAI, junto con un botón de sesión “Borrar”.
# Add a place to enter the API key
with st.sidebar:
api_key = st.text_input("OPENAI_API_KEY", type="password")
# Save the API key to the environment variable
if api_key:
os.environ["OPENAI_API_KEY"] = api_key
# Clear
if st.button('Clear'):
st.rerun()
A continuación, configuramos el título y las instrucciones de la página y agregamos un cuadro de texto para que el usuario ingrese una pregunta.
# Title and Instructions
if not api_key:
st.warning("Please enter your OpenAI API key in the sidebar.")
st.title('Statistical Advisor Agent | 🤖')
st.caption('This AI Agent is trained to answer questions about statistical tests from the [Scipy](https://docs.scipy.org/doc/scipy/reference/stats.html) package.')
st.caption('Ask questions like: "What is the best statistical test to compare two means".')
st.divider()
# User question
question = st.text_input(label="Ask me something:",
placeholder= "e.g. What is the best test to compare 3 groups means?")
Finalmente, podemos ejecutar el Graph Builder y mostrar la respuesta en la pantalla.
# Run the graph
if st.button('Search'):
# Progress bar
progress_bar = st.progress(0)
with st.spinner("Thinking..", show_time=True):
from langgraph_agent.graph import build_graph
progress_bar.progress(10)
# Build the graph
graph = build_graph()
result = graph.invoke({"question": question})
# Progress bar
progress_bar.progress(50)
# Print the result
st.subheader("📖 Answer:")
# Progress bar
progress_bar.progress(100)
st.write(result["final_answer"])
if "code_snippet" in result:
st.subheader("💻 Suggested Python Code:")
st.write(result["code_snippet"])
Veamos el resultado ahora.
¡Vaya, el resultado es impresionante!
- Yo pregunté: ¿Cuál es la mejor prueba para comparar dos grupos?
- Respuesta: Para comparar las medias de dos grupos, la prueba más apropiada es típicamente la prueba t de dos muestras independiente si los grupos son independientes y los datos normalmente se distribuyen. Si los datos no se distribuyen normalmente, una prueba no paramétrica como la prueba U de Mann-Whitney podría ser más adecuada. Si los grupos están emparejados o relacionados, una prueba t de muestra emparejada sería apropiado.
Misión cumplida para lo que propusimos crear.
Pruébalo tú mismo
¿Quieres probar a este agente?
¡Adelante y prueba la versión desplegada ahora!
https://ai-statistical-advisor.streamlit.app
Antes de que te vayas
Esta es una publicación larga, lo sé. Pero espero que valga la pena leerlo hasta el final. Aprendimos mucho sobre Langgraph. Nos hace pensar de una manera diferente para crear agentes de IA.
El marco nos obliga a pensar en cada paso de la información, desde el momento en que se solicita una pregunta al LLM hasta que se mostrará la respuesta que se mostrará. Preguntas como estas comienzan a aparecer en su mente durante el proceso de desarrollo:
- ¿Qué sucede después de que el usuario hace la pregunta?
- ¿El agente necesita verificar algo antes de continuar?
- ¿Hay condiciones a considerar durante la interacción?
Esta arquitectura se convierte en una ventaja porque hace que todo el proceso sea más limpio y escalable, ya que agregar una nueva característica puede ser tan simple como agregar una nueva función (nodo).
Por otro lado, Langgraph no es tan fácil de usar como marcos como Agno o Crewai, que encapsulan muchas de estas abstracciones en métodos más simples, lo que hace que el proceso sea mucho más fácil de aprender y desarrollar, pero también menos flexible.
Al final, todo es cuestión de qué problema se está resolviendo y cuán flexible necesita que sea.
Repositorio de Github
https://github.com/gurezende/ai-statistical-advisor
Acerca de mí
Si le gustó este contenido y desea obtener más información sobre mi trabajo, aquí está mi sitio web, donde también puede encontrar todos mis contactos.
[1. LangGraph Docs] https://langchain-ai.github.io/langgraph/concepts/why-langgraph/
[2. Scipy Stats] https://docs.scipy.org/doc/scipy/reference/stats.html
[3. Streamlit Docs] https://docs.streamlit.io/
[4. Statistical Advisor App] https://ai-statistical-advisor.streamlit.app/