, una de las primeras decisiones de diseño que tenemos que tomar es:
¿Flujo de trabajo o agente?
El paradigma del flujo de trabajo sigue una secuencia que definimos de antemano. Esto hace que la aplicación sea muy fácil de entender y nos brinda un control claro sobre cómo pasa la información de una etapa a la siguiente. Funciona bien cuando sabemos qué operación debe realizarse en cada etapa. Sin embargo, en el caso de preguntas más abiertas, la siguiente acción útil puede depender de lo que el sistema descubra en el camino.
El paradigma del agente, por otro lado, comienza con un objetivo y decide qué acciones o herramientas utilizar a lo largo del camino. Esto lo hace más flexible cuando el camino de la solución es incierto. Sin embargo, esa flexibilidad también significa que renunciamos a cierto control sobre cómo se desarrolla la solución.
Pero ¿por qué hacer esta elección para toda la aplicación?
Por lo que veo, en la práctica muchas aplicaciones contienen ambos tipos de trabajo. Algunas etapas pueden realizar una transformación conocida, entonces el paradigma de flujo de trabajo es una buena opción. Es posible que otras etapas deban adaptarse en función de resultados intermedios, lo que naturalmente requiere un enfoque agente.
Para estas aplicaciones, un patrón híbrido de agente de flujo de trabajo es más adecuado.
En esta publicación, exploraremos este patrón híbrido a través de un estudio de caso concreto. La ruta general de la solución permanecerá fija, mientras que un agente se coloca dentro del escenario donde la ruta no se puede determinar de antemano.
En esta publicación, configuramos un agente simple que solo tiene acceso a herramientas predefinidas. Si desea actualizar su agente con capacidades de ejecución de código y navegación web, consulte mis publicaciones aquí: Crear un agente LLM que pueda escribir y ejecutar código y Cómo proporcionar un navegador a un agente LLM.
1. Estudio de caso: ajuste de hiperparámetros asistido por LLM
Para el estudio de caso, creemos una aplicación LLM que ayude a seleccionar algoritmos y ajustar hiperparámetros para problemas de clasificación.
Esta aplicación funciona así: primero recibe un conjunto de datos etiquetados y una solicitud de modelado en lenguaje sencillo. Luego debe ejecutar experimentos modelo y recomendar una de las configuraciones probadas. Finalmente, resume el resultado.
Naturalmente, podemos dividir este trabajo en tres etapas.
1.1 Preparar el experimento
El propósito de esta etapa es traducir la solicitud de modelado en un resumen que contenga el objetivo, la métrica de evaluación y la configuración de validación cruzada.
Como ya conocemos la entrada, la operación que desea realizar y el resultado esperado, una sola llamada LLM es suficiente para esta etapa.
1.2 Ajuste de hiperparámetros
En esta etapa, necesitamos ejecutar los experimentos. Aquí conocemos el objetivo, es decir, encontrar el mejor modelo y los hiperparámetros asociados. Pero no sabemos la secuencia exacta de acciones necesarias para alcanzarlo. El próximo experimento útil debería depender de los resultados observados hasta ahora.
Como resultado, esta etapa la maneja mejor un agente, que puede elegir dinámicamente las configuraciones del clasificador, evaluarlas y continuar explorando.
1.3 Resumen e informes
Finalmente, necesitamos resumir la ejecución completa. En esta etapa, el resumen del experimento, el historial de pruebas y la recomendación ya están disponibles. Convertirlos en un informe estructurado es una operación conocida, por lo que una sola llamada de LLM es suficiente.
Como puede ver, en nuestra aplicación LLM prevista, la secuencia general de resolución de problemas es fija, con preparación, exploración e informes. Dentro de este flujo de trabajo predefinido, introducimos la autonomía solo en la etapa intermedia para permitir la experimentación con modelos adaptativos.
De esta manera, combinamos la claridad de un flujo de trabajo con la flexibilidad de la experimentación agente.
A continuación, construyamos esas tres etapas.
2. Creación del flujo de trabajo de tres etapas
La solicitud completa podrá expresarse en tres convocatorias:
experiment_spec = prepare_experiment( modeling_request, dataset_summary, ) recomendación, try_history = await explore_configurations( experiment_spec, dataset_summary, ) report = resume_run( experiment_spec, testing_history, recomendación, )
Ahora, analicemos las tres funciones una por una.
2.1 Preparación del experimento con resultados estructurados
La primera etapa convierte la solicitud de modelado y el resumen del conjunto de datos en un resumen de experimento compacto. Usamos una única convocatoria LLM estructurada para hacer eso.
Necesitamos definir el esquema de salida, la instrucción LLM y el generador de mensajes para esta etapa. Comenzamos con el esquema de salida, que es un modelo Pydantic:
clase ExperimentSpec(BaseModel): objetivo: str Primary_metric: str = Field( descripción="Un nombre válido de puntuación de scikit-learn" ) cv_folds: int
Tiene tres campos, que especifican la información que necesita el agente de experimentación, es decir, qué debe lograr, cómo se deben evaluar las configuraciones y cuántos pliegues de validación cruzada usar.
Esta es la característica de salida estructurada del LLM: garantiza que la salida del LLM siga esta estructura predefinida, simplificando así en gran medida el consumo posterior de los resultados.
A continuación, definimos la instrucción:
PREPARER_INSTRUCTION = """ Cree un resumen del experimento a partir de la solicitud de modelado y el resumen del conjunto de datos. """
Luego, definimos la función de construcción para componer el mensaje:
def build_preparer_prompt(solicitud: str, dataset_summary: dict) -> str: return f"""Solicitud de modelado: {solicitud} Resumen del conjunto de datos: {json.dumps(dataset_summary, indent=2)}"""
Finalmente, juntamos todo con una llamada sincrónica a la API de Respuestas:
de openai import AzureOpenAI llm_client = AzureOpenAI( api_key=os.environ["OPENAI_API_KEY"], azure_endpoint=os.environ["OPENAI_API_BASE"], api_version=os.environ["OPENAI_API_VERSION"], ) def prepare_experiment( request: str, dataset_summary: dict, ) -> ExperimentSpec: respuesta = llm_client.responses.parse( model="gpt-5.4", razonamiento={"effort": "medium"}, instrucciones=PREPARER_INSTRUCTION, input=build_preparer_prompt(solicitud, datos), text_format=ExperimentSpec,) devuelve respuesta.output_parsed
Esto le da a la siguiente etapa un contrato experimental explícito.
2.2 Construyendo el agente de experimentación
El propósito de esta etapa es encontrar una configuración de clasificador sólida mediante la ejecución de experimentos.
Una sola llamada de LLM no será suficiente, ya que la siguiente configuración útil depende de las puntuaciones observadas hasta el momento. Por lo tanto, utilizamos un agente para maximizar la adaptabilidad.
Primero configuramos un cliente asíncrono para alimentar el agente:
desde openai import AsyncAzureOpenAI desde agentes import OpenAIResponsesModel agent_client = AsyncAzureOpenAI( api_key=os.environ["OPENAI_API_KEY"], azure_endpoint=os.environ["OPENAI_API_BASE"], api_version=os.environ["OPENAI_API_VERSION"],) agent_model = OpenAIResponsesModel(model="gpt-5.4", openai_client=agent_client,)
Luego definimos el esquema de salida, la instrucción y el mensaje para el agente:
importar json de pydantic importar BaseModel clase AgentRecommendation(BaseModel): model_name: str hyperparameters_json: str racionalidad: str AGENT_INSTRUCTION = """ Encuentre un clasificador fuerte para el problema proporcionado. """ def build_agent_prompt( experiment_spec: ExperimentSpec, dataset_summary: dict,) -> str: return f"""Resumen del experimento: {experiment_spec.model_dump_json(indent=2)} Resumen del conjunto de datos: {json.dumps(dataset_summary, indent=2)}"""
A continuación, definimos la herramienta que el agente puede utilizar para ejecutar un experimento:
importar json de agentes importar function_tool de sklearn.model_selection importar cross_val_score de sklearn.utils importar all_estimators def build_experiment_tool( X, y, experiment_spec, trial_history, ): clasificadores = dict( all_estimators(type_filter="classifier") ) @function_tool def run_experiment( model_name: str, hyperparameters_json: str, ) -> str: """Evaluar una configuración de clasificador scikit-learn.""" parámetros = json.loads(hyperparameters_json) clasificador = clasificadores[nombre_modelo](**parámetros) puntuaciones = cross_val_score( clasificador, X, y, cv=experiment_spec.cv_folds, puntuación=experiment_spec.primary_metric,) resultado = { "model_name": model_name, "hyperparameters": parámetros, "mean_score": round(float(scores.mean()), 4), } trial_history.append(resultado) devuelve json.dumps(resultado) devuelve run_experiment
Esta herramienta es intencionalmente pequeña y simplemente evalúa la configuración del clasificador proporcionada por el agente. Además, el agente puede elegir clasificadores del registro de clasificadores de scikit-learn y proporcionar argumentos de constructor como JSON.
Ahora podemos desarrollar la etapa agente en su totalidad:
# pip install openai-agents desde agentes import Agent, ModelSettings, Runner async def explore_configurations( X, y, experiment_spec, dataset_summary, ): prueba_historia =[]run_experiment = build_experiment_tool( X, y, experiment_spec, trial_history, ) agente = Agente( nombre="Agente de selección de modelo", instrucciones=AGENT_INSTRUCTION, modelo=agente_modelo, model_settings=ModelSettings( razonamiento={"esfuerzo": "medio"}, paralelo_tool_calls=True, ), herramientas=[ejecutar_experimento], tipo_salida=Recomendación del agente, ) resultado = await Runner.run( agente, build_agent_prompt( experiment_spec, dataset_summary, ), max_turns=10, ) devuelve resultado.final_output, try_history
Tenga en cuenta que configuramos paralelo_tool_calls=True. Esto permite al agente solicitar varias configuraciones al mismo tiempo.
Esta es la única parte autónoma del flujo de trabajo.
2.3 Resumen de la ejecución
La etapa final debe convertir la ejecución completa en un informe conciso. Esta es una tarea cerrada, por lo que una única convocatoria de LLM es suficiente.
Como es habitual, comenzamos definiendo el esquema de salida:
de la clase BaseModel de importación pydantic ModelSelectionReport (BaseModel): modelo_seleccionado: cadena hiperparámetros_seleccionados: cadena puntuación_mean_cv: resumen flotante: cadena
Luego, definimos el generador de instrucciones y avisos:
REPORTER_INSTRUCTION = """ Resume la ejecución de selección de modelo completada. """ def build_reporter_prompt( experiment_spec: ExperimentSpec, trial_history: list[dict], agent_recommendation: AgentRecommendation, ) -> str: return f"""Resumen del experimento: {experiment_spec.model_dump_json(indent=2)} Pruebas completadas: {json.dumps(trial_history, indent=2)} Recomendación del agente: {agent_recommendation.model_dump_json(indent=2)}"""
Finalmente, encapsulamos la llamada LLM en una función:
def resume_run( experiment_spec: ExperimentSpec, trial_history: list[dict], agent_recommendation: AgentRecommendation, ) -> ModelSelectionReport: respuesta = llm_client.responses.parse( model="gpt-5.4", razonamiento={"effort": "medium"}, instrucciones=REPORTER_INSTRUCTION, input=build_reporter_prompt( experiment_spec, historial_prueba, recomendación_agente, ), formato_texto = Informe de selección de modelo, ) respuesta de retorno.salida_parsed
3. Prueba del flujo de trabajo en la clasificación de dígitos escritos a mano
Para probar lo que hemos creado, utilizamos el conjunto de datos de dígitos escritos a mano integrado de scikit-learn (CC BY 4.0):
de sklearn.datasets importar load_digits dígitos = load_digits() X = dígitos.data y = dígitos.target dataset_summary = { "n_samples": X.shape[0], "n_features": forma de X[1], "n_clases": len(conjunto(y)), }
Aquí está nuestra solicitud de modelado:
modeling_request = """ Cree un clasificador para imágenes de dígitos escritos a mano. Utilice validación cruzada para comparar modelos candidatos. Recomiende una configuración sólida. """
Ahora podemos ejecutar el flujo de trabajo de tres etapas:
experiment_spec = prepare_experiment( modeling_spec, dataset_summary, ) recomendación, try_history = await explore_configurations( X, y, experiment_spec, dataset_summary, ) report = resume_run( experiment_spec, testing_history, recomendación, )
Esto es lo que produjo la primera llamada LLM en experiment_spec:
{ "objective": "Crear y evaluar un clasificador para imágenes de dígitos escritos a mano.", "primary_metric": "accuracy", "cv_folds": 5, }
Luego, el agente utiliza la herramienta experimental para explorar las configuraciones del modelo. En mi ejecución, la duración total de trial_history es 19, aquí están las primeras 5 pruebas:
[ { "model_name": "LogisticRegression", "hyperparameters": { "max_iter": 1000 }, "mean_score": 0.9132 }, { "model_name": "RandomForestClassifier", "hyperparameters": { "n_estimators": 100, "max_ Depth": null }, "mean_score": 0.9382 }, { "model_name": "SVC", "hyperparameters": { "C": 1, "kernel": "rbf", "gamma": "scale" }, "mean_score": 0.9638 }, { "model_name": "KNeighborsClassifier", "hyperparameters": { "n_neighbors": 3 }, "mean_score": 0.9661 }, { "model_name": "SVC", "hiperparámetros": { "C": 10, "kernel": "rbf", "gamma": "escala" }, "mean_score": 0,975 } ]
Finalmente, la última convocatoria de LLM resume el informe de ejecución completo:
{ "selected_model": "SVC", "selected_hyperparameters": '{"C": 10, "kernel": "rbf", "gamma": "scale"}', "mean_cv_score": 0.975, "summary": "La configuración SVC logró la puntuación de validación cruzada más sólida entre los candidatos evaluados y se recomienda como clasificador final.", }
4. Cuándo utilizar este patrón
Al crear su próxima aplicación LLM, no debe preguntarse si toda la aplicación debe ser un flujo de trabajo o un agente.
Una mejor pregunta es: ¿dónde necesita autonomía la aplicación?
Si una etapa tiene una operación conocida, utilice una llamada LLM estructurada.
Si una etapa tiene un objetivo claro, pero la siguiente acción depende de lo que observe el sistema, acude con un agente.
Descomponga el camino de la solución en etapas y coloque la autonomía sólo donde sea necesario descubrir el camino. Así es como se mantiene la claridad y el control de un flujo de trabajo mientras se utiliza la flexibilidad de la agencia para una resolución de problemas más eficaz.