Profundizando en los marcos AutoGen y Multi-Agente |  de Matthew Gunton |  junio de 2024

Para organizar los LLM, AutoGen se basa en un modelo de conversación. La idea básica es que los agentes conversen entre sí para resolver el problema. Al igual que los humanos mejoran el trabajo de los demás, los LLM escuchan lo que dicen los demás y luego brindan mejoras o nueva información.

Si bien inicialmente se podría esperar que todo el trabajo lo realice un LLM, los sistemas agentes necesitan más que solo esta funcionalidad. ALos artículos como la Voyager han demostradoSe puede obtener un rendimiento extraordinario creando habilidades y herramientas para que las utilicen los agentes. Esto significa permitir que el sistema guarde y ejecute funciones previamente codificadas por el LLM y también dejar abierta la puerta para que actores como los humanos desempeñen un papel. Por lo tanto, los autores decidieron que hay tres fuentes principales para el agente: LLM, humano y herramienta.

Parte de la figura 1 del papel

Como podemos ver arriba, tenemos una clase padre llamada ConversableAgent que permite utilizar cualquiera de las tres fuentes. A partir de este padre, nuestras clases hijas de AssistantAgent y UserProxyAgent Se derivan. Tenga en cuenta que esto muestra un patrón de elección de una de las tres fuentes para el agente a medida que creamos clases especializadas. Nos gusta separar los agentes en roles claros para poder usar la programación de conversaciones para dirigirlos.

Una vez definidos nuestros actores, podemos analizar cómo programarlos para lograr el objetivo final de nuestro agente. Los autores recomiendan pensar en la programación de conversaciones como una forma de determinar qué debe calcular un agente y cuándo debe hacerlo.

Cada agente tiene un send , receive y un generate_reply función. Yendo secuencialmente, primero el agente recibirá un mensaje, luego generate_replyy, finalmente, enviar el mensaje a otros agentes. Cuando un agente recibe el mensaje, es cómo controlamos cuándo se realizan los cálculos. Podemos hacer esto con y sin un administrador, como veremos a continuación. Si bien cada una de estas funciones se puede personalizar, la generate_reply es aquel donde los autores recomiendan que coloque su lógica de cálculo para el agente. Veamos un ejemplo de alto nivel del documento siguiente para ver cómo se hace.

Figura 2 del papel

Trabajando hacia abajo, creamos dos agentes: un AssistantAgent (que interactúa con los LLM de OpenAI) y un UserProxyAgent (que dará las instrucciones y ejecutará el código que se devuelve). Con UserProxyAgentlos autores definieron entonces reply_func_A2B donde vemos que si el agente devuelve el código, el UserProxyAgent Luego ejecutará ese código. Además, para asegurarse de que el UserProxyAgent Solo responde cuando es necesario. Tenemos una lógica en torno a esa llamada de ejecución de código. Los agentes irán y vendrán hasta que se envíe un mensaje de finalización o alcancemos la cantidad máxima de respuestas automáticas, o cuando un agente responda a sí mismo la cantidad máxima de veces.

En la siguiente visualización de esa interacción, podemos ver que los dos agentes se iteran para crear un resultado final que sea inmediatamente útil para el usuario.

Parte de la figura 1 del papel

Ahora que tenemos un conocimiento de alto nivel, profundicemos en el código con algunas aplicaciones de ejemplo.

Comencemos por pedirle al LLM que genere código que se ejecute localmente y que edite si se lanza alguna excepción. A continuación, modifico el Ejemplo “Resolución de tareas con generación de código, ejecución y depuración” del proyecto AutoGen.

from IPython.display import Image, display
import autogen
from autogen.coding import LocalCommandLineCodeExecutor
import os
config_list = [{
"model": "llama3-70b-8192",
"api_key": os.environ.get('GROQ_API_KEY'),
"base_url":"https://api.groq.com/openai/v1"
}]
assistant = autogen.AssistantAgent(
name="assistant",
llm_config={
"cache_seed": 41, # seed for caching and reproducibility
"config_list": config_list, # a list of OpenAI API configurations
"temperature": 0, # temperature for sampling
}, # configuration for autogen's enhanced inference API which is compatible with OpenAI API
)
# create a UserProxyAgent instance named "user_proxy"
user_proxy = autogen.UserProxyAgent(
name="user_proxy",
human_input_mode="NEVER",
max_consecutive_auto_reply=10,
is_termination_msg=lambda x: x.get("content", "").rstrip().endswith("TERMINATE"),
code_execution_config={
# the executor to run the generated code
"executor": LocalCommandLineCodeExecutor(work_dir="coding"),
},
)
# the assistant receives a message from the user_proxy, which contains the task description
chat_res = user_proxy.initiate_chat(
assistant,
message="""What date is today? Compare the year-to-date gain for META and TESLA.""",
summary_method="reflection_with_llm",
)

Para entrar en detalles, comenzamos instanciando dos agentes: nuestro agente proxy de usuario (AssistantAgent) y nuestro agente LLM (UserProxyAgent). El agente LLM recibe una clave API para que pueda llamar al LLM externo y luego un mensaje del sistema junto con un cache_seed para reducir la aleatoriedad entre ejecuciones. Tenga en cuenta que AutoGen no lo limita a usar únicamente puntos finales de OpenAI: puede conectarse a cualquier proveedor externo que siga el formato API de OpenAI (en este ejemplo, muestro Groq).

El agente proxy de usuario tiene una configuración más compleja. Repasando algunos de los más interesantes, comencemos desde arriba. El human_input_mode le permite determinar qué tan involucrado debe estar el ser humano en el proceso. Los ejemplos aquí y a continuación eligen “NEVER”ya que quieren que esto sea lo más fluido posible y que el ser humano nunca reciba indicaciones. Si eliges algo como “ALWAYS”el humano recibe un aviso cada vez que el agente recibe un mensaje. El término medio es “TERMINATE”que le avisará al usuario solo cuando presionemos el botón max_consecutive_auto_reply o cuando se recibe un mensaje de terminación de uno de los otros agentes.

También podemos configurar cómo se ve el mensaje de terminación. En este caso, miramos para ver si TERMINATE aparece al final del mensaje recibido por el agente. Aunque el código anterior no lo muestra, Las indicaciones dadas a los agentes dentro de la biblioteca de AutoGen son las que le indican al LLM que responda de esta manera.Para cambiar esto, deberá modificar el mensaje y la configuración.

Captura de pantalla de “Ejecutor de código de línea de comando local”

Por último, y quizás lo más importante, está la code_execution_config. En este ejemplo, queremos que la computadora del usuario ejecute el código generado por el LLM. Para ello pasamos por este LocalCommandLineCodeExecutor que se encargará del procesamiento. El código aquí determina el shell local del sistema y luego guarda el programa en un archivo local. Luego usará Python subprocess para ejecutar esto localmente y devolver tanto stdout como stderr, junto con el código de salida del subproceso.

Pasando a otro ejemplo, veamos cómo configurar un agente de Generación Aumentada de Recuperación (RAG) utilizando el “Uso de RetrieveChat para la generación de código aumentado y la respuesta a preguntas” ejemplo. En resumen, este código permite a un usuario hacer una pregunta a un LLM sobre una fuente de datos específica y obtener una respuesta de alta precisión a esa pregunta.

import json
import os

import chromadb

import autogen
from autogen.agentchat.contrib.retrieve_assistant_agent import RetrieveAssistantAgent
from autogen.agentchat.contrib.retrieve_user_proxy_agent import RetrieveUserProxyAgent

# Accepted file formats for that can be stored in
# a vector database instance
from autogen.retrieve_utils import TEXT_FORMATS

config_list = [
{"model": "gpt-3.5-turbo-0125", "api_key": "<YOUR_API_KEY>", "api_type": "openai"},
]

assistant = RetrieveAssistantAgent(
name="assistant",
system_message="You are a helpful assistant.",
llm_config={
"timeout": 600,
"cache_seed": 42,
"config_list": config_list,
},
)

ragproxyagent = RetrieveUserProxyAgent(
name="ragproxyagent",
human_input_mode="NEVER",
max_consecutive_auto_reply=3,
retrieve_config={
"task": "qa",
"docs_path": [
"https://raw.githubusercontent.com/microsoft/FLAML/main/website/docs/Examples/Integrate%20-%20Spark.md",
"https://raw.githubusercontent.com/microsoft/FLAML/main/website/docs/Research.md",
os.path.join(os.path.abspath(""), "..", "website", "docs"),
],
"custom_text_types": ["non-existent-type"],
"chunk_token_size": 2000,
"model": config_list[0]["model"],
"vector_db": "chroma", # conversely pass your client here
"overwrite": False, # set to True if you want to overwrite an existing collection
},
code_execution_config=False, # set to False if you don't want to execute the code
)

assistant.reset()
code_problem = "How can I use FLAML to perform a classification task and use spark to do parallel training. Train 30 seconds and force cancel jobs if time limit is reached."
chat_result = ragproxyagent.initiate_chat(
assistant, message=ragproxyagent.message_generator, problem=code_problem, search_string="spark"
)

El agente LLM se configura de manera similar aquí, solo que con el clase RetrieveAssistantAgent en cambioque parece muy similar a la clase típica de AssistantAgent.

Para RetrieveUserProxyAgent, tenemos varias configuraciones. Desde arriba, tenemos un valor de “tarea” que le dice al Agente qué hacer. Puede ser cualquiera “qa” (pregunta y respuesta), “code” o “default”dónde “default” significa hacer código y control de calidad. Estos determinan el aviso dado al agente..

La mayor parte del resto de la configuración de recuperación aquí se utiliza para pasar información para nuestro RAG. RAG se construye típicamente sobre la base de una búsqueda de similitud a través de incrustaciones de vectores, por lo que esta configuración nos permite especificar cómo se crean las incrustaciones de vectores a partir de los documentos de origen. En el ejemplo anterior, estamos pasando por el modelo que crea estas incrustaciones, el tamaño del fragmento en el que dividiremos los documentos de origen para cada incrustación y la base de datos de vectores elegida.

Hay dos cosas que tener en cuenta con respecto al ejemplo anterior. En primer lugar, se supone que estás creando tu base de datos vectorial sobre la marcha. Si quieres conectarte a una base de datos vectorial ya instanciada, AutoGen puede manejar esto; solo tendrás que pasar tu cliente. En segundo lugar, debes tener en cuenta que esta API ha cambiado recientemente y parece que todavía se está trabajando en ella, por lo que las configuraciones pueden ser ligeramente diferentes cuando la ejecutes localmente, aunque los conceptos de alto nivel probablemente seguirán siendo los mismos.

Finalmente, veamos cómo podemos usar AutoGen para tener tres o más agentes. A continuación tenemos el “Chat grupal con codificador y crítico de visualización” Por ejemplo, con tres agentes: un codificador, un crítico y un proxy de usuario.

Al igual que en una red, a medida que agregamos más agentes, la cantidad de conexiones aumenta a un ritmo cuadrático. Con los ejemplos anteriores, solo teníamos dos agentes, por lo que no teníamos demasiados mensajes para enviar. Con tres, necesitamos asistencia; necesitamos un administrador.

import matplotlib.pyplot as plt
import pandas as pd
import seaborn as sns
from IPython.display import Image

import autogen

config_list_gpt4 = autogen.config_list_from_json(
"OAI_CONFIG_LIST",
filter_dict={
"model": ["gpt-4", "gpt-4-0314", "gpt4", "gpt-4-32k", "gpt-4-32k-0314", "gpt-4-32k-v0314"],
},
)

llm_config = {"config_list": config_list_gpt4, "cache_seed": 42}
user_proxy = autogen.UserProxyAgent(
name="User_proxy",
system_message="A human admin.",
code_execution_config={
"last_n_messages": 3,
"work_dir": "groupchat",
"use_docker": False,
}, # Please set use_docker=True if docker is available to run the generated code. Using docker is safer than running the generated code directly.
human_input_mode="NEVER",
)
coder = autogen.AssistantAgent(
name="Coder", # the default assistant agent is capable of solving problems with code
llm_config=llm_config,
)
critic = autogen.AssistantAgent(
name="Critic",
system_message="""Critic. You are a helpful assistant highly skilled in evaluating the quality of a given visualization code by providing a score from 1 (bad) - 10 (good) while providing clear rationale. YOU MUST CONSIDER VISUALIZATION BEST PRACTICES for each evaluation. Specifically, you can carefully evaluate the code across the following dimensions
- bugs (bugs): are there bugs, logic errors, syntax error or typos? Are there any reasons why the code may fail to compile? How should it be fixed? If ANY bug exists, the bug score MUST be less than 5.
- Data transformation (transformation): Is the data transformed appropriately for the visualization type? E.g., is the dataset appropriated filtered, aggregated, or grouped if needed? If a date field is used, is the date field first converted to a date object etc?
- Goal compliance (compliance): how well the code meets the specified visualization goals?
- Visualization type (type): CONSIDERING BEST PRACTICES, is the visualization type appropriate for the data and intent? Is there a visualization type that would be more effective in conveying insights? If a different visualization type is more appropriate, the score MUST BE LESS THAN 5.
- Data encoding (encoding): Is the data encoded appropriately for the visualization type?
- aesthetics (aesthetics): Are the aesthetics of the visualization appropriate for the visualization type and the data?

YOU MUST PROVIDE A SCORE for each of the above dimensions.
{bugs: 0, transformation: 0, compliance: 0, type: 0, encoding: 0, aesthetics: 0}
Do not suggest code.
Finally, based on the critique above, suggest a concrete list of actions that the coder should take to improve the code.
""",
llm_config=llm_config,
)

groupchat = autogen.GroupChat(agents=[user_proxy, coder, critic], messages=[], max_round=20)
manager = autogen.GroupChatManager(groupchat=groupchat, llm_config=llm_config)

user_proxy.initiate_chat(
manager,
message="download data from https://raw.githubusercontent.com/uwdata/draco/master/data/cars.csv and plot a visualization that tells us about the relationship between weight and horsepower. Save the plot to a file. Print the fields in a dataset before visualizing it.",
)

Para comenzar, de manera similar a los ejemplos anteriores, creamos un agente para el usuario (UserProxyAgent), pero esta vez creamos 2 agentes LLM distintos: un codificador y un crítico. El codificador no recibe instrucciones especiales, pero sí el crítico. El agente codificador le da al crítico un código y le dice que lo critique según un determinado paradigma.

Después de crear los agentes, tomamos los tres y los pasamos a un objeto GroupChat. El chat grupal es un objeto de datos que lleva la cuenta de todo lo que ha sucedido. Almacena los mensajes, las indicaciones para ayudar a seleccionar el siguiente agente y la lista de agentes involucrados.

El Administrador de chat grupal Luego, se le proporciona este objeto de datos como una forma de ayudarlo a tomar sus decisiones. Puede configurarlo para que elija al próximo orador de diversas maneras, incluidas ronda tras ronda, de manera aleatoria y proporcionando una función. De manera predeterminada, utiliza el siguiente mensaje: “Read the above conversation. Then select the next role from {agentlist} to play. Only return the role.” Naturalmente, puedes ajustar esto a tu gusto.

Una vez que la conversación continúa durante el número máximo de rondas (en round robin) o una vez que obtiene un “TERMINATE” cadena, el administrador finalizará el chat grupal. De esta manera, podemos coordinar varios agentes a la vez.

Si bien la investigación y el desarrollo de LLM continúan generando un rendimiento increíble, parece probable que haya casos extremos tanto en lo que respecta a los datos como al rendimiento que los desarrolladores de LLM simplemente no habrán incorporado a sus modelos. Por lo tanto, los sistemas que puedan proporcionar a estos modelos las herramientas, la retroalimentación y los datos que necesitan para crear un rendimiento de alta calidad de manera constante serán enormemente valiosos.

Desde ese punto de vista, AutoGen es un proyecto divertido de observar. Es de código abierto y se puede utilizar de inmediato, lo que permite vislumbrar cómo piensan las personas en resolver algunos de los desafíos técnicos relacionados con los agentes.

Es un momento emocionante para construir