Ajuste del modelo más inteligente: un agente de IA con Langgraph + Streamlit que aumenta el rendimiento de ML

Cada día un poco más mientras trabaja con Langgraph.

Seamos realistas: dado que Langchain es uno de los primeros marcos para manejar la integración con LLMS, despegó antes y se convirtió en una especie de ir a Opción cuando se trata de construir agentes listos para la producción, les guste o no.

El hermano menor de Langchain es Langgraph. Este marco utiliza una notación gráfica con nodos y bordes para construir las aplicaciones, haciéndolas altamente personalizables y muy robustas. Eso es lo que estoy disfrutando tanto.

Al principio, algunas anotaciones me parecieron extrañas (¡tal vez solo soy yo!). Pero seguí cavando y aprendiendo más. Y creo firmemente que aprendemos mejor mientras estamos implementando cosas, porque es cuando aparecen los problemas reales. Entonces, después de algunas líneas de código y algunas horas de depuración de código, esa arquitectura gráfica comenzó a tener mucho más sentido para mí, y comencé a disfrutar creando cosas con Langgraph.

De todos modos, si no tienes ninguna introducción al marco, te recomiendo que mires esta publicación [1].

Ahora, aprendamos más sobre el proyecto de este artículo.

El proyecto

En este proyecto, vamos a construir un agente de varios pasos:

  • Se necesita en un tipo de modelo de aprendizaje automático: clasificación o regresión.
  • Y también ingresaremos las métricas de nuestro modelo, como precisión, RMSE, matriz de confusión, ROC, etc. Cuanto más proporcionamos al agente, mejor será la respuesta.

El agente, equipado con Google Gemini 2.0 Flash:

  • Lee la entrada
  • Evaluar la métrica del modelo ingresada por el usuario
  • Devuelva una lista procesable de sugerencias para ajustar el modelo y mejorar su rendimiento.

Esta es la estructura de la carpeta del proyecto:

ml-model-tuning/
├── langgraph_agent/
│ ├── graph.py #LangGraph logic
│ ├── nodes.py #LLMs and tools
├── main.py # Streamlit interface to run the agent
├── requirements.txt

El agente está en vivo y implementado en esta aplicación web.

Conjunto de datos

El conjunto de datos que se utilizará es un conjunto de datos de juguete muy simple llamado Consejos, desde el paquete Seaborn y de código abierto bajo la licencia BSD 3. Decidí usar un conjunto de datos simple como este porque tiene características categóricas y numéricas, siendo adecuada para ambos tipos de creación de modelos. Además, el comienzo del artículo es el agente, por lo que es donde queremos gastar más atención.

Para cargar los datos, use el siguiente código.

import seaborn as sns

# Data
df = sns.load_dataset('tips')

A continuación, construiremos los nodos.

Nodos

Los nodos de un objeto Langgraph son las funciones de Python. Pueden ser herramientas que el agente usará o una instancia de un LLM. Construimos cada nodo como una función separada.

Pero primero, tenemos que cargar los módulos.

import os
from textwrap import dedent
from dotenv import load_dotenv
load_dotenv()

import streamlit as st
from langchain_google_genai import ChatGoogleGenerativeAI
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder

Nuestro primer nodo es el que obtiene el tipo de modelo. Simplemente obtiene la entrada del usuario sobre si el modelo a mejorar es una regresión o una clasificación.

def get_model_type(state):
    """Check if the user is implementing a classification or regression model. """
    
    # Define the model type
    modeltype = st.text_input("Please let me know the type of model you are working on and hit Enter:", 
                              placeholder="(C)lassification or (R)egression", 
                              help="C for Classification or R for Regression")
    
    # Check if the model type is valid
    if modeltype.lower() not in ["c", "r", "classification", "regression"]:
        st.info("Please enter a valid model type: C for (C)lassification or R for (R)egression.")
        st.stop()
        
    if modeltype.lower() in ["c", "classification"]:
        modeltype = "classification"
    elif modeltype.lower() in ["r", "regression"]:
        modeltype = "regression"
    
    return {"model_type": modeltype.lower()} # "classification" or "regression"  

Los otros dos nodos de este gráfico son casi los mismos, pero difieren en la solicitud del sistema. Uno está optimizado para la evaluación de los modelos de regresión, mientras que el otro está especializado en la clasificación. Pegaré solo uno de ellos aquí. Sin embargo, el código completo está disponible en GitHub. Vea todo el código de los nodos aquí.

def llm_node_regression(state):
    """
    Processes the user query and search results using the LLM and returns an answer.
    """
    llm = ChatGoogleGenerativeAI(
        model="gemini-2.5-flash",
        api_key=os.environ.get("GEMINI_API_KEY"),
        temperature=0.5,
        max_tokens=None,
        timeout=None,
        max_retries=2
    )

    # Create a prompt
    messages = ChatPromptTemplate.from_messages([
        ("system", dedent("""\
                          You are a seasoned data scientist, specialized in regression models. 
                          You have a deep understanding of regression models and their applications.
                          You will get the user's result for a regression model and your task is to build a summary of how to improve the model.
                          Use the context to answer the question.
                          Give me actionable suggestions in the form of bullet points.
                          Be concise and avoid unnecessary details. 
                          If the question is not about regression, say 'Please input regression model metrics.'.
                          \
                          """)),
        MessagesPlaceholder(variable_name="messages"),
        ("user", state["metrics_to_tune"])
    ])
    
    # Create a chain
    chain = messages | llm
    response = chain.invoke(state)
    return {"final_answer": [response]}

Excelente. Ahora es el momento de unir estos nodos construyendo los bordes para conectarlos. En otras palabras, construyendo el flujo de la información de la entrada del usuario a la salida final.

Gráfico

El archivo graph.py se utilizará para generar el objeto Langgraph. Primero, necesitamos importar los módulos.

from langgraph.graph import StateGraph, END
from typing_extensions import TypedDict
from langgraph.graph.message import add_messages
from langchain_core.messages import AnyMessage
from langgraph.graph import StateGraph, END
from typing import TypedDict, Annotated
from langgraph_agent.nodes import llm_node_classification, llm_node_regression, get_model_type

El siguiente paso es crear el estado del gráfico. Género estatal administra el estado del agente durante todo el flujo de trabajo. Él Realiza un seguimiento de la información El agente se ha reunido y procesado. No es más que una clase con los nombres de las variables y su tipo escrito en el estilo del diccionario.

# Create a state graph
class AgentState(TypedDict):
    """
    Represents the state of our graph.

    Attributes:
        messages: A list of messages in the conversation, including user input and agent outputs.
        model_type: The type of model being used, either "classification" or "regression".
        question: The initial question from the user.
        final_answer: The final answer provided by the agent.
    """
    messages: Annotated[AnyMessage, add_messages] # accumulate messages
    model_type: str
    metrics_to_tune: str
    final_answer: str

Para construir el gráfico, utilizaremos una función que:

  • Agrega cada nodo con una tupla ("name", node_function_name)
  • Define el punto de partida en el get_model_type nodo. .set_entry_point("get_model_type")
  • Luego, hay un borde condicional, que decide ir al nodo apropiado dependiendo de la respuesta de la get_model_type nodo.
  • Finalmente, conecte los nodos LLM al END estado.
  • Compile el gráfico para prepararlo para su uso.
def build_graph():
    # Build the LangGraph flow
    builder = StateGraph(AgentState)

    # Add nodes
    builder.add_node("get_model_type", get_model_type)
    builder.add_node("classification", llm_node_classification)
    builder.add_node("regression", llm_node_regression)

    # Define edges and flow
    builder.set_entry_point("get_model_type")

    builder.add_conditional_edges(
        "get_model_type",
        lambda state: state["model_type"],
        {
            "classification": "classification",
            "regression": "regression"
        }
    )

    builder.add_edge("classification", END)
    builder.add_edge("regression", END)

    # Compile the graph
    return builder.compile()

Si desea ver el gráfico, puede usar este pequeño fragmento.

# Create the graph image and save png
from IPython.display import display, Image
graph = build_graph()
display(Image(graph.get_graph().draw_mermaid_png(output_file_path="graph.png")))
Imagen del gráfico creado. Imagen del autor.

Es un agente simple, pero funciona muy bien. Llegaremos a eso pronto. Pero primero debemos construir la pieza frontal.

Construyendo la interfaz de usuario

La interfaz de usuario es una aplicación de transmisión. He elegido esta opción debido a las características fáciles de prototipos y implementación.

Cargamos las bibliotecas necesarias una vez más.

import os
from langgraph_agent.graph import AgentState, build_graph
from textwrap import dedent
import streamlit as st

Configuración del diseño de la página (título, icono, barra lateral, etc.).

## Config page
st.set_page_config(page_title="ML Model Tuning Assistant",
                   page_icon='🤖',
                   layout="wide",
                   initial_sidebar_state="expanded")

Creación de la barra lateral que contiene el campo para agregar una tecla API de Google Gemini y la reinicio botón.

## SIDEBAR | Add a place to enter the API key
with st.sidebar:
    api_key = st.text_input("GOOGLE_API_KEY", type="password")

    # Save the API key to the environment variable
    if api_key:
        os.environ["GEMINI_API_KEY"] = api_key

    # Clear
    if st.button('Clear'):
        st.rerun()

Ahora, agregamos el título y las instrucciones de la página para usar el agente. Todo esto es un código simple que usa principalmente la función st.write().

## Title and Instructions
if not api_key:
    st.warning("Please enter your OpenAI API key in the sidebar.")
    
st.title('ML Model Tuning Assistant | 🤖')
st.caption('This AI Agent is will help you tuning your machine learning model.')
st.write(':red[**1**] | 👨‍💻 Add the metrics of your ML model to be tuned in the text box. The more metrics you add, the better.')
st.write(':red[**2**] | ℹ️ Inform the AI Agent what type of model you are working on.')
st.write(':red[**3**] | 🤖 The AI Agent will respond with suggestions on how to improve your model.')
st.divider()

# Get the user input
text = st.text_area('**👨‍💻 Add here the metrics of your ML model to be tuned:**')

st.divider()

Y, por último, el código a:

  • Ejecutar el build_graph() función y crear el agente.
  • Crear el estado inicial del agente, con un vacío messages.
  • Invoca al agente.
  • Imprima los resultados en la pantalla.
## Run the graph

# Spinner
with st.spinner("Gathering Tuning Suggestions...", show_time=True):
    from langgraph_agent.graph import build_graph
    agent = build_graph()

    # Create the initial state for the agent, with blank messages and the user input
    prompt = {
        "messages": [],
        "metrics_to_tune": text
    }

    # Invoke the agent
    result = agent.invoke(prompt)

    # Print the agent's response
    st.write('**🤖 Agent Response:**')
    st.write(result['final_answer'][0].content)

Todo creado. ¡Es hora de poner a este agente de IA a trabajar!

Por lo tanto, construiremos algunos modelos y le pediremos al agente sugerencias de ajuste.

Ejecutando el agente

Bueno, como este es un agente que nos ayuda con las sugerencias de ajuste del modelo, debemos tener un modelo que sintonizar.

Modelo de regresión

Probaremos primero el modelo de regresión. Podemos construir rápidamente un modelo simple.

# Imports
import pandas as pd
import numpy as np
import seaborn as sns
from sklearn.pipeline import Pipeline
from feature_engine.encoding import OneHotEncoder
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression
from sklearn.metrics import root_mean_squared_error

## Baseline Model
# Data
df = sns.load_dataset('tips')

# Train Test Split
X = df.drop('tip', axis=1)
y = df['tip']
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# Categorical
cat_vars = df.select_dtypes(include=['object']).columns

# Pipeline
pipe = Pipeline([
    ('encoder', OneHotEncoder(variables=['sex', 'smoker', 'day', 'time'],
                              drop_last=True)),
    ('model', LinearRegression())
])

# Fit
pipe.fit(X_train, y_train)

Ahora, tenemos que recopilar datos de métricas para presentar a nuestro agente de IA para obtener sugerencias de ajuste. Cuantos más datos, mejor. Mientras estoy trabajando con un modelo de regresión, elegí presentar la siguiente información:

  • Nombres de características
  • Descripción estadística del conjunto de datos
  • Error cuadrado medio de raíz (RMSE)
  • Intercepción de regresión y coeficientes
  • Vif
        total_bill         tip   sex smoker  day    time        size
count   244.000000  244.000000   244    244  244     244  244.000000
unique         NaN         NaN     2      2    4       2         NaN
top            NaN         NaN  Male     No  Sat  Dinner         NaN
freq           NaN         NaN   157    151   87     176         NaN
mean     19.785943    2.998279   NaN    NaN  NaN     NaN    2.569672
std       8.902412    1.383638   NaN    NaN  NaN     NaN    0.951100
min       3.070000    1.000000   NaN    NaN  NaN     NaN    1.000000
25%      13.347500    2.000000   NaN    NaN  NaN     NaN    2.000000
50%      17.795000    2.900000   NaN    NaN  NaN     NaN    2.000000
75%      24.127500    3.562500   NaN    NaN  NaN     NaN    3.000000
max      50.810000   10.000000   NaN    NaN  NaN     NaN    6.000000

---
Model: Linear Regression
Score: 0.44
RMSE: 0.84
Intercept: 0.45

Coefficients:
       feature  coefficient
0   total_bill     0.094700
1         size     0.233484
2     sex_Male     0.028819
3    smoker_No     0.192353
4      day_Sat    -0.006064
5      day_Fri     0.179721
6      day_Sun     0.128928
7  time_Dinner    -0.094957

VIF:
total_bill    2.226294
tip           1.879238
size          1.590524

Ahora ejecutaré el agente.

Agente de IA en acción. Gif por el autor.

Aquí está la respuesta del agente:

🤖 Respuesta del agente:

Aquí hay sugerencias procesables para mejorar su modelo de regresión:

  • Explore las relaciones no lineales: Considere agregar características polinomiales para Total_Bill y tamaño, o términos de interacción entre características (por ejemplo, tamaño total_bill *), ya que el modelo lineal actual podría ser demasiado simplista para los patrones de datos subyacentes.
  • Evaluar modelos de regresión alternativa: Dado el cuadrado R de 0.44, pruebe otros modelos como regresor bosque aleatorio, regresor de impulso de gradiente o regresor de vectores de soporte, que puede capturar relaciones más complejas y no lineales.
  • Dirección de distribución de datos y valores atípicos: Investigue y maneje los valores atípicos en Total_Bill y la punta de la variable objetivo. Considere aplicar transformaciones (por ejemplo, transformación de registro) a características sesgadas para cumplir mejor los supuestos de linealidad y mejorar el rendimiento del modelo.
  • Analizar la importancia estadística de la función: Obtenga valores p para cada coeficiente para identificar características que pueden no ser estadísticamente significativas. Eliminar o reevaluar tales características puede simplificar el modelo y potencialmente mejorar la generalización.

Hay un par de sugerencias aquí. Ahora podemos elegir lo que aceptaremos o no. Esto es lo que probé (código en GitHub):

  • Entrené a un regresor de bosque aleatorio, pero el resultado no fue bueno con el modelo fuera de la caja, dejando caer el R² a 0.25 y el RMSE a 0.97. Así que descarté esa opción.
  • Entonces, si mantengo la regresión lineal, otra sugerencia es usar transformaciones de registro y tratar los valores atípicos. Lo intenté y el resultado es mejor. El modelo va a una R² de 0.55 y RMSE de 0.23. Una mejora significativa.

Modelo de clasificación

Seguí el mismo ejercicio aquí, pero ahora trabajando en un modelo de clasificación, usando el mismo conjunto de datos e intentando predecir si el cliente del restaurante es un fumador o no.

  1. Entrenado un modelo de clasificación
  2. Obtuve las métricas iniciales: Score = 0.69; RMSE = 0.55
  3. Corrió el agente de IA para sugerencias
  4. Aplicó algunas sugerencias de ajuste: class_weight='balanced' y BayesSearchCV.
  5. Tengo las métricas sintonizadas: Score = 0.71; RMSE = 0.52
Sugerencias del agente de IA. Imagen del autor.

Observe cómo la precisión versus el recuerdo también está más equilibrada.

Puntuación antes vs. después de sintonizar. Imagen del autor.

Nuestro trabajo está completo. El agente está funcionando según lo diseñado.

Antes de que te vayas

Hemos llegado al final de este proyecto. En general, estoy satisfecho con el resultado. Este proyecto es bastante simple y rápido de construir, ¡y sin embargo ofrece mucho valor!

Tuning Models no es un de talla única acción. Hay muchas opciones para probar. Por lo tanto, tener la ayuda de un agente de IA para darnos algunas ideas para probar es muy valioso y facilita nuestro trabajo sin reemplazarnos.

¡Prueba la aplicación por ti mismo y avísame si te ayudó a obtener una métrica de rendimiento mejorada!

https://ml tuning-assistant.streamlit.app

Repositorio de Github

https://github.com/gurezende/ml-tuning-assistant

Encuéntrame en línea

https://gustavorsantos.me

Referencias

[1. Building Your First AI Agent with LangGraph] https://medium.com/code-applied/building-your-first-ai-agent-with-langgraph-599a7bcf01cd?sk=a22e309c1e6e3602ae37ef28835ee8433

[2. Using Gemini with LangGraph] https://python.langchain.com/docs/integrations/chat/google_generative_ai/

[3. LangGraph Docs] https://langchain-ai.github.io/langgraph/tutorials/get-started/1-build-basic-chatbot/

[4. Streamlit Docs] https://docs.streamlit.io/

[5. Get a Gemini API Key] https://tinyurl.com/gemini-api- key

[6. GitHub Repository ML Tuning Agent] https://github.com/gurezende/ml-tuning-assistant

[7. Guide to Hyperparameter Tuning with Bayesian Search] https://medium.com/code-applied/dont-guess-get-the-best-a-smart-guide-to-hyperparameter-tuning-with-bayesian-search-123e4e98e845?sk=ff4c378d816bca0c82988f0e8e1d2cdff

[8. Deployed App] https://ml-tuning-assistant.streamlit.app/