¿Pueden los LLM reemplazar a los analistas de datos? Construyendo un analista potenciado por LLM
¿Pueden los LLM reemplazar a los analistas de datos? Construyendo un analista fortalecido con LLM
Parte 1: dotando de herramientas a ChatGPT
![Imagen de DALL-E 3](https://ai.miximages.com/miro.medium.com/v2/resize:fit:640/format:webp/1*cUH3JCAISUwvit33qk6D7g.jpeg)
Creo que cada uno de nosotros se ha preguntado al menos una vez durante el último año si (o más bien cuándo) ChatGPT podrá reemplazar tu rol. Yo no soy una excepción aquí.
<p+tenemos afectarán="" avances="" cambiarán="" clara="" con="" consenso="" considerablemente="" cómo="" de="" el="" embargo,="" en="" generativa="" hay="" ia="" la="" los="" no="" nuestras="" nuestro="" nuestros="" p="" personales="" que="" recientes="" roles="" sin="" tiempo.
Pasar mucho tiempo pensando en diferentes escenarios futuros posibles y sus probabilidades puede ser cautivador, pero sugiero un enfoque totalmente diferente: intentar construir tu propio prototipo. Primero, es bastante desafiante y divertido. Segundo, nos ayudará a ver nuestro trabajo de manera más estructurada. Tercero, nos dará la oportunidad de probar en la práctica uno de los enfoques más punteros: agentes LLM.
En este artículo, comenzaremos de forma sencilla y aprenderemos cómo los LLM pueden aprovechar las herramientas y realizar tareas sencillas. Pero en los siguientes artículos, profundizaremos en diferentes enfoques y mejores prácticas para los agentes LLM.
- Valentía para aprender ML Desmitificando la regularización L1 y L2 (parte 4)
- Mejorando la documentación de Python Una guía paso a paso para enlazar código fuente
- Revelando la Esencia de lo Estocástico en el Aprendizaje Automático
Así que, comencemos el viaje.
¿Qué es el análisis de datos?
Antes de pasar a los LLM, intentemos definir qué es la analítica y qué tareas realizamos como analistas.
Mi lema es que el objetivo del equipo analítico es ayudar a los equipos de productos a tomar las decisiones correctas basadas en datos en el tiempo disponible. Es una buena misión, pero para definir el alcance del analista potenciado por LLM, debemos descomponer aún más el trabajo analítico.
Me gusta el marco de trabajo propuesto por Gartner. Identifica cuatro técnicas diferentes de Datos y Analítica:
- Análisis descriptivo responde preguntas como “¿Qué sucedió?”. Por ejemplo, ¿cuál fue el ingreso en diciembre? Este enfoque incluye tareas de informes y trabajar con herramientas de BI.
- Análisis diagnóstico va un poco más allá y hace preguntas como “¿Por qué sucedió algo?”. Por ejemplo, ¿por qué los ingresos disminuyeron un 10% en comparación con el año anterior? Esta técnica requiere un análisis más detallado y segmentación de tus datos.
- Análisis predictivo nos permite obtener respuestas a preguntas como “¿Qué sucederá?”. Los dos pilares de este enfoque son la previsión (predecir el futuro en situaciones habituales) y la simulación (modelar diferentes resultados posibles).
- Análisis prescriptivo impacta las decisiones finales. Las preguntas comunes son “¿En qué deberíamos enfocarnos?” o “¿Cómo podríamos aumentar el volumen en un 10%?”.
Por lo general, las empresas pasan por todas estas etapas paso a paso. Es casi imposible comenzar a analizar pronósticos y diferentes escenarios si tu empresa aún no ha dominado el análisis descriptivo (no tienes un almacén de datos, herramientas de BI o definiciones de métricas). Por lo tanto, este marco de trabajo también puede mostrar la madurez de los datos de la empresa.
De manera similar, cuando un analista pasa de ser junior a senior, es probable que pase por todas estas etapas, comenzando desde tareas de informes bien definidas y progresando hacia preguntas estratégicas vagas. Por lo tanto, este marco de trabajo también es relevante a nivel individual.
Si volvemos a nuestro analista potenciado por LLM, debemos centrarnos en el análisis descriptivo y las tareas de informes. Es mejor comenzar desde lo básico. Por lo tanto, nos centraremos en aprender LLM para comprender las preguntas básicas sobre los datos.
Hemos definido nuestro enfoque para el primer prototipo. Por lo tanto, estamos listos para pasar a las preguntas técnicas y discutir el concepto de agentes LLM y las herramientas.
Agentes LLM y herramientas
Cuando usábamos LLMs anteriormente (por ejemplo, para realizar modelado de temas aquí), describíamos los pasos exactos nosotros mismos en el código. Por ejemplo, veamos la cadena a continuación. En primer lugar, le pedimos al modelo que determinara el sentimiento de una reseña de un cliente. Luego, dependiendo del sentimiento, extraemos de la reseña tanto las ventajas como las desventajas mencionadas en el texto.
![Ilustración del autor](https://ai.miximages.com/miro.medium.com/v2/resize:fit:640/format:webp/1*D4ZM46STZ-S_yf39-q4y_g.png)
En este ejemplo, hemos definido claramente el comportamiento del LLM y el LLM resolvió esta tarea bastante bien. Sin embargo, este enfoque no funcionará si construimos algo más complejo y vago, como un analista impulsado por LLM.
Si alguna vez has trabajado como analista, o con un analista, durante al menos un día, sabrías que los analistas reciben una amplia gama de diferentes preguntas y solicitudes, desde preguntas básicas (como “¿Cuántos clientes tuvimos en nuestro sitio ayer?” o “¿Podrías hacer un gráfico para nuestra reunión de la junta mañana?”) hasta preguntas de alto nivel (por ejemplo, “¿Cuáles son los principales puntos problemáticos de los clientes?” o “¿En qué mercado deberíamos lanzar nuestro próximo producto?”). Como es obvio, no es factible describir todos los escenarios posibles.
Sin embargo, hay un enfoque que podría ayudarnos: los agentes. La idea principal de los agentes es utilizar LLMs como motor de razonamiento que pueda elegir qué hacer a continuación y cuándo es el momento de devolver la respuesta final al cliente. Suena bastante cercano a nuestro comportamiento: recibimos una tarea, definimos las herramientas necesarias, las utilizamos y luego regresamos con la respuesta final cuando estemos listos.
El concepto esencial relacionado con los agentes (que ya he mencionado anteriormente) son las herramientas. Las herramientas son funciones que el LLM podría invocar para obtener información que falta (por ejemplo, ejecutar SQL, usar una calculadora o llamar a un motor de búsqueda). Las herramientas son cruciales porque te permiten llevar los LLM a un nivel superior e interactuar con el mundo. En este artículo, nos centraremos principalmente en las funciones de OpenAI como herramientas.
OpenAI ha ajustado sus modelos para poder trabajar con funciones de manera que:
- Puedas pasarle al modelo una lista de funciones con sus descripciones;
- Si es relevante para tu consulta, el modelo te devolverá una llamada a función, es decir, el nombre de la función y los parámetros de entrada para llamarla.
Puedes encontrar más información y una lista actualizada de modelos que admiten funciones en la documentación.
Existen dos casos de uso destacados para utilizar funciones con LLMs:
- Etiquetado y extracción: en estos casos, las funciones se utilizan para asegurar el formato de salida del modelo. En lugar de la salida habitual con contenido, obtendrás una llamada a función estructurada.
- Herramientas y enrutamiento: este es un caso de uso más emocionante que te permite crear un agente.
Comencemos con el caso de uso más sencillo de extracción para aprender cómo utilizar las funciones de OpenAI.
Caso de uso #1: Etiquetado y extracción
Tal vez te preguntes cuál es la diferencia entre el etiquetado y la extracción. Estos términos son bastante similares. La única diferencia es si el modelo extrae información presentada en el texto o etiqueta el texto proporcionando nueva información (por ejemplo, define el lenguaje o el sentimiento).
![Ilustración del autor](https://ai.miximages.com/miro.medium.com/v2/resize:fit:640/format:webp/1*3UhwvtqKSdcPk0n3qpf4HQ.png)
Dado que hemos decidido centrarnos en tareas de analítica descriptiva y generación de informes, utilizaremos este enfoque para estructurar las solicitudes de datos entrantes y extraer los siguientes componentes: métricas, dimensiones, filtros, período y salida deseada.
![Ilustración del autor](https://ai.miximages.com/miro.medium.com/v2/resize:fit:640/format:webp/1*6NaTx38FMVvUBPUP4Vx0sw.png)
Este será un ejemplo de extracción, ya que solo necesitamos la información presente en el texto.
Ejemplo básico de API de Completado de OpenAI
Primero, debemos definir la función. OpenAI espera una descripción de la función en formato JSON. Este JSON se pasará al LLM, por lo que necesitamos proporcionarle todo el contexto: qué hace esta función y cómo se utiliza.
Aquí tienes un ejemplo de un JSON de función. Hemos especificado:
name
ydescription
para la función en sí misma,type
ydescription
para cada argumento,- la lista de los parámetros de entrada requeridos para la función.
extraction_functions = [ { "name": "extract_information", "description": "extrae información", "parameters": { "type": "object", "properties": { "metric": { "type": "string", "description": "métrica principal que necesitamos calcular, por ejemplo, 'número de usuarios' o 'número de sesiones'", }, "filters": { "type": "string", "description": "filtros a aplicar al cálculo (no incluir filtros por fechas aquí)", }, "dimensions": { "type": "string", "description": "parámetros para dividir tu métrica", }, "period_start": { "type": "string", "description": "día de inicio del período para un informe", }, "period_end": { "type": "string", "description": "día de finalización del período para un informe", }, "output_type": { "type": "string", "description": "salida deseada", "enum": ["número", "visualización"] } }, "required": ["metric"], }, }]
No es necesario implementar la función en este caso de uso porque no la utilizaremos. Solo recibimos respuestas de LLM de manera estructurada como llamadas a funciones.
Ahora, podríamos utilizar la API estándar de Completado de Chat de OpenAI para llamar a la función. Pasamos a la llamada a la API:
- modelo: he utilizado el último ChatGPT 3.5 Turbo que puede trabajar con funciones,
- lista de mensajes: un mensaje del sistema para configurar el contexto y una solicitud del usuario,
- lista de funciones que hemos definido anteriormente.
import openaimessages = [ { "role": "system", "content": "Extrae la información relevante de la solicitud proporcionada." }, { "role": "user", "content": "¿Cómo ha cambiado el número de usuarios de iOS a lo largo del tiempo?" }]response = openai.ChatCompletion.create( model = "gpt-3.5-turbo-1106", messages = messages, functions = extraction_functions)print(response)
Como resultado, obtenemos el siguiente JSON.
{ "id": "chatcmpl-8TqGWvGAXZ7L43gYjPyxsWdOTD2n2", "object": "chat.completion", "created": 1702123112, "model": "gpt-3.5-turbo-1106", "choices": [ { "index": 0, "message": { "role": "assistant", "content": null, "function_call": { "name": "extraer_informacion", "arguments": "{\"metric\":\"número de usuarios\",\"filtros\":\"plataforma='iOS'\",\"dimensiones\":\"fecha\",\"periodo_inicio\":\"2021-01-01\",\"periodo_fin\":\"2021-12-31\",\"tipo_salida\":\"visualización\"}" } }, "finish_reason": "function_call" } ], "usage": { "prompt_tokens": 159, "completion_tokens": 53, "total_tokens": 212 }, "system_fingerprint": "fp_eeff13170a"}
Recuerda que las funciones y las llamadas a funciones se contarán en los límites de tokens y se facturarán.
El modelo devuelve una llamada a función en lugar de una respuesta común: podemos ver que el content
está vacío y finish_reason
es igual a function_call
. En la respuesta, también se incluyen los parámetros de entrada para la llamada a función:
metrica = "número de usuarios"
,filtros = "plataforma = 'iOS'"
,dimensiones = "fecha"
,periodo_inicio = "2021-01-01"
,periodo_fin = "2021-12-31"
,tipo_salida = "visualización"
.
El modelo hizo un trabajo bastante bueno. El único problema es que supuso el período de la nada. Podemos solucionarlo añadiendo una guía más explícita al mensaje del sistema, por ejemplo, "Extrae la información relevante de la solicitud proporcionada. Extrae ÚNICAMENTE la información presentada en la solicitud inicial; no añadas nada más. Devuelve información parcial si falta algo."
De forma predeterminada, los modelos deciden si utilizar las funciones de forma independiente (function_call = 'auto'
). Podemos exigir que devuelva una llamada a función específica en cada ocasión o no utilizar funciones en absoluto.
# llamando siempre a la función extraer_informacionresponse = openai.ChatCompletion.create( model = "gpt-3.5-turbo-1106", messages = messages, functions = extraction_functions, function_call = {"name": "extraer_informacion"})# no hay llamadas a funcionesresponse = openai.ChatCompletion.create( model = "gpt-3.5-turbo-1106", messages = messages, functions = extraction_functions, function_call = "none")
Hemos obtenido el primer programa funcional que utiliza las funciones de LLM. Eso es genial. Sin embargo, no es muy conveniente describir las funciones en un JSON. Veamos cómo hacerlo más fácil.
Usar Pydantic para definir funciones
Para definir las funciones de forma más conveniente, podemos aprovechar Pydantic. Pydantic es la biblioteca de Python más popular para la validación de datos.
Ya hemos utilizado Pydantic para definir el Analizador de Salida de LangChain.
Primero, necesitamos crear una clase que herede de la clase BaseModel
y definir todos los campos (argumentos de nuestra función).
from pydantic import BaseModel, Fieldfrom typing import Optionalclass RequestStructure(BaseModel): """extrae información""" metric: str = Field(description = "métrica principal que debemos calcular, por ejemplo, 'número de usuarios' o 'número de sesiones'") filters: Optional[str] = Field(description = "filtros a aplicar al cálculo (no incluir filtros de fechas aquí)") dimensions: Optional[str] = Field(description = "parámetros para dividir su métrica") period_start: Optional[str] = Field(description = "el día de inicio del período para un informe") period_end: Optional[str] = Field(description = "el día de finalización del período para un informe") output_type: Optional[str] = Field(description = "la salida deseada", enum = ["número", "visualización"])
Luego, podemos usar LangChain para convertir la clase Pydantic en la función de OpenAI.
from langchain.utils.openai_functions import convert_pydantic_to_openai_functionextract_info_function = convert_pydantic_to_openai_function(RequestStructure, name = 'extraer_informacion')
LangChain valida la clase que proporcionamos. Por ejemplo, se asegura de que se especifique la descripción de la función, ya que LLM la necesita para poder utilizar esta herramienta.
Como resultado, obtuvimos el mismo JSON para pasar a LLM, pero ahora lo expresamos como una clase Pydantic.
{'nombre': 'extraer_informacion', 'descripción': 'extrae información', 'parámetros': {'título': 'RequestStructure', 'descripción': 'extrae información', 'tipo': 'objeto', 'propiedades': {'metric': {'título': 'Métrica', 'descripción': "métrica principal que debemos calcular, por ejemplo, 'número de usuarios' o 'número de sesiones'", 'tipo': 'cadena'}, 'filters': {'título': 'Filtros', 'descripción': 'filtros a aplicar al cálculo (no incluir filtros de fechas aquí)', 'tipo': 'cadena'}, 'dimensions': {'título': 'Dimensiones', 'descripción': 'parámetros para dividir su métrica', 'tipo': 'cadena'}, 'period_start': {'título': 'Inicio del período', 'descripción': 'el día de inicio del período para un informe', 'tipo': 'cadena'}, 'period_end': {'título': 'Fin del período', 'descripción': 'el día de finalización del período para un informe', 'tipo': 'cadena'}, 'output_type': {'título': 'Tipo de salida', 'descripción': 'la salida deseada', 'enum': ['número', 'visualización'], 'tipo': 'cadena'}}, 'requerido': ['metric']}}
Ahora, podríamos usarlo en nuestra llamada a OpenAI. Cambiemos de la API de OpenAI a LangChain para hacer nuestras llamadas a la API más modulares.
Definiendo la cadena de LangChain
Definamos una cadena para extraer la información necesaria de las solicitudes. Usaremos LangChain ya que es el framework más popular para LLMs. Si no has trabajado con él antes, te recomiendo que aprendas algunos conceptos básicos en uno de mis artículos anteriores.
Nuestra cadena es simple. Consiste en un modelo de Open AI y una indicación con una variable request
(un mensaje del usuario).
También hemos utilizado la función bind
para pasar el argumento functions
al modelo. La función bind
nos permite especificar argumentos constantes para nuestros modelos que no forman parte de la entrada (por ejemplo, functions
o temperature
).
from langchain.prompts import ChatPromptTemplatefrom langchain.chat_models import ChatOpenAImodel = ChatOpenAI(temperature=0.1, model = 'gpt-3.5-turbo-1106')\ .bind(functions = [extract_info_function])prompt = ChatPromptTemplate.from_messages([ ("sistema", "Extrae la información relevante de la solicitud proporcionada. \ Extrae SOLO la información presentada en la solicitud inicial. \ No agregues nada más. \ Devuelve información parcial si algo falta."), ("humano", "{request}")])extraction_chain = prompt | model
Ahora es el momento de probar nuestra función. Necesitamos usar el método invoke y pasar una request
.
extraction_chain.invoke({'request': "¿Cuántos clientes visitaron nuestro sitio en iOS en abril de 2023 desde diferentes países?"})
En la salida, obtenemos AIMessage
sin contenido pero con una llamada a función.
AIMessage( content='', additional_kwargs={ 'function_call': { 'name': 'extract_information', 'arguments': '''{ "metric":"número de clientes", "filters":"device = 'iOS'", "dimensions":"país", "period_start":"2023-04-01", "period_end":"2023-04-30", "output_type":"número"} '''} })
Así que hemos aprendido cómo usar las funciones de OpenAI en LangChain para obtener una salida estructurada. Ahora, pasemos al caso de uso más interesante, herramientas y enrutamiento.
Caso de uso #2: Herramientas y Enrutamiento
Es hora de usar herramientas y potenciar nuestro modelo con capacidades externas. Los modelos en este enfoque son motores de razonamiento, y pueden decidir qué herramientas usar y cuándo (se llama enrutamiento).
LangChain tiene un concepto de herramientas – interfaces que los agentes pueden usar para interactuar con el mundo. Las herramientas pueden ser funciones, cadenas de LangChain e incluso otros agentes.
Podemos convertir fácilmente las herramientas en funciones de OpenAI utilizando format_tool_to_openai_function
y seguir pasando el argumento functions
a los LLMs.
Definir una herramienta personalizada
Enseñemos a nuestro analista impulsado por LLM a calcular la diferencia entre dos métricas. Sabemos que los LLMs pueden cometer errores en matemáticas, por lo que nos gustaría pedirle a un modelo que use una calculadora en lugar de confiar en él mismo.
Para definir una herramienta, necesitamos crear una función y usar un decorador @tool
.
from langchain.agents import tool@tooldef percentage_difference(metric1: float, metric2: float) -> float: """Calcula la diferencia porcentual entre las métricas""" return (metric2 - metric1)/metric1*100
Ahora, esta función tiene parámetros name
y description
que se pasarán a los LLMs.
print(percentage_difference.name)# percentage_difference.nameprint(percentage_difference.args)# {'metric1': {'title': 'Métrica1', 'type': 'número'},# 'metric2': {'title': 'Métrica2', 'type': 'número'}}print(percentage_difference.description)# 'percentage_difference(metric1: float, metric2: float) -> float - Calcula la diferencia porcentual entre las métricas'
Estos parámetros se utilizarán para crear una especificación de función de OpenAI. Conviertamos nuestra herramienta en una función de OpenAI.
from langchain.tools.render import format_tool_to_openai_functionprint(format_tool_to_openai_function(percentage_difference))
Obtenemos el siguiente JSON como resultado. Esquematiza la estructura, pero faltan las descripciones de los campos.
{'name': 'percentage_difference', 'description': 'percentage_difference(metric1: float, metric2: float) -> float - Calcula la diferencia porcentual entre las métricas', 'parameters': {'title': 'percentage_differenceSchemaSchema', 'type': 'object', 'properties': {'metric1': {'title': 'Métrica1', 'type': 'número'}, 'metric2': {'title': 'Métrica2', 'type': 'número'}}, 'required': ['metric1', 'metric2']}}
Podemos usar Pydantic para especificar un esquema para los argumentos.
class Metrics(BaseModel): metric1: float = Field(description="Valor de la métrica base para calcular la diferencia") metric2: float = Field(description="Nuevo valor de la métrica que comparamos con la línea de base")@tool(args_schema=Metrics)def percentage_difference(metric1: float, metric2: float) -> float: """Calcula la diferencia porcentual entre las métricas""" return (metric2 - metric1)/metric1*100
Ahora, si convertimos una nueva versión en la especificación de función de OpenAI, incluirá descripciones de argumentos. Es mucho mejor, ya que podríamos compartir todo el contexto necesario con el modelo.
{'name': 'percentage_difference', 'description': 'percentage_difference(metric1: float, metric2: float) -> float - Calcula la diferencia porcentual entre las métricas', 'parameters': {'title': 'Métricas', 'type': 'object', 'properties': {'metric1': {'title': 'Métrica 1', 'description': 'Valor base de la métrica para calcular la diferencia', 'type': 'number'}, 'metric2': {'title': 'Métrica 2', 'description': 'Nuevo valor de la métrica que vamos a comparar con la línea base', 'type': 'number'}}, 'required': ['metric1', 'metric2']}}
Así que hemos definido la herramienta que LLM podrá utilizar. Vamos a probarla en la práctica.
Usando una herramienta en la práctica
Definamos una cadena y pasemos nuestra herramienta a la función. Luego, podremos probarla con una solicitud de usuario.
modelo = ChatOpenAI(temperature=0.1, modelo = 'gpt-3.5-turbo-1106')\ .bind(functions = [format_tool_to_openai_function(percentage_difference)])prompt = ChatPromptTemplate.from_messages([ ("sistema", "Eres un analista de productos dispuesto a ayudar a tu equipo de productos. Eres muy estricto y preciso. Solo usas hechos, sin inventar información."), ("usuario", "{solicitud}")])cadena_analista = prompt | modelocadena_analista.invoke({'solicitud': "En abril teníamos 100 usuarios y en mayo solo 95. ¿Cuál es la diferencia en porcentaje?"})
Obtuvimos una llamada de función con los argumentos correctos, así que está funcionando.
AIMessage(content='', additional_kwargs={ 'function_call': { 'name': 'percentage_difference', 'arguments': '{"metric1":100,"metric2":95}'} })
Para tener una forma más conveniente de trabajar con el resultado, podemos usar OpenAIFunctionsAgentOutputParser
. Agreguémoslo a nuestra cadena.
from langchain.agents.output_parsers import OpenAIFunctionsAgentOutputParsercadena_analista = prompt | modelo | OpenAIFunctionsAgentOutputParser()resultado = cadena_analista.invoke({'solicitud': "En abril había 100 usuarios y en mayo 110 usuarios. ¿Cómo cambió el número de usuarios?"})
Ahora obtuvimos la salida de una forma más estructurada, y podemos recuperar fácilmente los argumentos para nuestra herramienta como resultado.tool_input
.
AgentActionMessageLog( tool='percentage_difference', tool_input={'metric1': 100, 'metric2': 110}, log="\nInvoking: `percentage_difference` with `{'metric1': 100, 'metric2': 110}`\n\n\n", message_log=[AIMessage(content='', additional_kwargs={'function_call': {'name': 'percentage_difference', 'arguments': '{"metric1":100,"metric2":110}'}})])
Entonces, podemos ejecutar la función como lo solicitó LLM de esta manera.
observacion = percentage_difference(resultado.tool_input)print(observacion)# 10
Si queremos obtener la respuesta final del modelo, debemos devolver el resultado de la ejecución de la función. Para hacerlo, necesitamos definir una lista de mensajes para pasar a las observaciones del modelo.
from langchain.prompts import MessagesPlaceholdermodelo = ChatOpenAI(temperature=0.1, modelo = 'gpt-3.5-turbo-1106')\ .bind(functions = [format_tool_to_openai_function(percentage_difference)])prompt = ChatPromptTemplate.from_messages([ ("sistema", "Eres un analista de productos dispuesto a ayudar a tu equipo de productos. Eres muy estricto y preciso. Solo usas hechos, sin inventar información."), ("usuario", "{solicitud}"), MessagesPlaceholder(variable_name="observaciones")])cadena_analista = prompt | modelo | OpenAIFunctionsAgentOutputParser()resultado1 = cadena_analista.invoke({ 'solicitud': "En abril había 100 usuarios y en mayo 110 usuarios. ¿Cómo cambió el número de usuarios?", "observaciones": []})observacion = percentage_difference(resultado1.tool_input)print(observacion)# 10
Luego, necesitamos agregar la observación a nuestra variable observaciones
. Podemos usar la función format_to_openai_functions
para formatear nuestros resultados de la manera esperada por el modelo.
from langchain.agents.format_scratchpad import format_to_openai_functionsformat_to_openai_functions([(result1, observation), ])
Como resultado, obtuvimos un mensaje que la LLM puede entender.
[AIMessage(content='', additional_kwargs={'function_call': {'name': 'percentage_difference', 'arguments': '{"metric1":100,"metric2":110}'}}), FunctionMessage(content='10.0', name='percentage_difference')]
Vamos a invocar nuestra cadena una vez más, pasando el resultado de la ejecución de la función como una observación.
result2 = analyst_chain.invoke({ 'request': "En abril había 100 usuarios y en mayo había 110 usuarios. ¿Cómo cambió el número de usuarios?", "observations": format_to_openai_functions([(result1, observation)])})
Ahora, obtuvimos el resultado final del modelo, que suena razonable.
AgentFinish( return_values={'output': 'El número de usuarios aumentó un 10%.'}, log='El número de usuarios aumentó un 10%.')
Si estuviéramos trabajando con la API de completación de chat de OpenAI, podríamos agregar otro mensaje con el rol de herramienta. Puede encontrar un ejemplo detallado aquí.
Si activamos el modo de depuración, podemos ver la frase exacta que se pasó a la API de OpenAI.
Sistema: Eres un analista de productos dispuesto a ayudar a tu equipo de productos. Eres muy estricto y preciso. Solo usas hechos, sin inventar información.Humano: En abril había 100 usuarios y en mayo había 110 usuarios. ¿Cómo cambió el número de usuarios?IA: {'name': 'percentage_difference', 'arguments': '{"metric1":100,"metric2":110}'}Función: 10.0
Para activar la depuración de LangChain, ejecuta el siguiente código e invoca tu cadena para ver qué está pasando bajo el capó.
import langchainlangchain.debug = True
Hemos intentado trabajar con una herramienta, pero ampliemos nuestro conjunto de herramientas y veamos cómo LLM podría manejarlo.
Enrutamiento: utilizando varias herramientas
Agreguemos un par de herramientas más al conjunto de herramientas de nuestro analista:
- obtener usuarios activos mensuales
- usando Wikipedia.
Primero, definamos una función ficticia para calcular la audiencia con filtros por mes y ciudad. Usaremos Pydantic nuevamente para especificar los argumentos de entrada para nuestra función.
import datetimeimport randomclass Filters(BaseModel): month: str = Field(description="Mes de la actividad del cliente en formato %Y-%m-%d") city: Optional[str] = Field(description="Ciudad de residencia de los clientes (por defecto no hay filtro)", enum = ["Londres", "Berlín", "Ámsterdam", "París"])@tool(args_schema=Filters)def get_monthly_active_users(month: str, city: str = None) -> int: """Devuelve el número de clientes activos para el mes especificado""" dt = datetime.datetime.strptime(month, '%Y-%m-%d') total = dt.year + 10*dt.month if city is None: return total else: return int(total*random.random())
Luego, usemos el paquete de Python Wikipedia para permitir que el modelo consulte Wikipedia.
import wikipediaclass Wikipedia(BaseModel): term: str = Field(description="Término a buscar")@tool(args_schema=Wikipedia)def get_summary(term: str) -> str: """Devuelve conocimientos básicos sobre el término dado proporcionados por Wikipedia""" return wikipedia.summary(term)
Definamos un diccionario con todas las funciones que nuestro modelo conoce ahora. Este diccionario nos ayudará a enrutar más tarde.
toolkit = { 'percentage_difference': percentage_difference, 'get_monthly_active_users': get_monthly_active_users, 'get_summary': get_summary}analyst_functions = [format_tool_to_openai_function(f) for f in toolkit.values()]
Hice algunos cambios en nuestra configuración anterior:
- Ajusté un poco la indicación del sistema para obligar a LLM a consultar Wikipedia si necesita algunos conocimientos básicos.
- Cambié el modelo a GPT 4 porque es mejor para manejar tareas que requieren razonamiento.
from langchain.prompts import MessagesPlaceholder
model = ChatOpenAI(temperature=0.1, model='gpt-4-1106-preview').bind(functions=analyst_functions)
prompt = ChatPromptTemplate.from_messages([
("system", "Eres un analista de productos dispuesto a ayudar a tu equipo de productos. Eres muy estricto y preciso. "
"Solo utilizas la información proporcionada en la solicitud inicial. "
"Si necesitas determinar alguna información, como cuál es el nombre de la capital, puedes utilizar Wikipedia."),
("user", "{solicitud}"),
MessagesPlaceholder(variable_name="observaciones")])
analyst_chain = prompt | model | OpenAIFunctionsAgentOutputParser()
Podemos invocar nuestra cadena con todas las funciones. Empecemos con una consulta bastante sencilla.
resultado1 = analyst_chain.invoke({
'solicitud': "¿Cuántos usuarios había en abril de 2023 en Berlín?",
"observaciones": []
})
print(resultado1)
Obtuvimos en la llamada a la función del resultado get_monthly_active_users
con los parámetros de entrada: {'mes': '2023-04-01', 'ciudad': 'Berlín'}
, lo cual parece correcto. El modelo pudo encontrar la herramienta adecuada y resolver la tarea.
Intentemos hacer la tarea un poco más compleja.
resultado1 = analyst_chain.invoke({
'solicitud': "¿Cómo cambió la cantidad de usuarios de la capital de Alemania entre abril y mayo de 2023?",
"observaciones": []
})
Detengámonos un minuto y pensemos cómo nos gustaría que el modelo razonara. Es evidente que no hay suficiente información para que el modelo responda de inmediato, por lo que necesita realizar una serie de llamadas a funciones:
- Llamar a Wikipedia para obtener la capital de Alemania
- Llamar a la función
get_monthly_active_users
dos veces para obtener los MAU de abril y mayo - Llamar a
percentage_difference
para calcular la diferencia entre las métricas.
Se ve bastante complejo. Veamos si ChatGPT será capaz de manejar esta pregunta.
Para la primera llamada, LLM devolvió una llamada a la función a Wikipedia con los siguientes parámetros: {'término': 'capital de Alemania'}
. Hasta ahora, sigue nuestro plan.
Proporcionemos la observación y veamos cuáles serán los siguientes pasos.
observacion1 = toolkit[result1.herramienta](result1.entrada_herramienta)
print(observacion1)
La capital de Alemania es la ciudad estado de Berlín. Es la sede del presidente de Alemania, cuya residencia oficial es el Schloss Bellevue. El Bundesrat (“consejo federal”) es la representación de los estados federales (Bundesländer) de Alemania y tiene su sede en el antiguo Herrenhaus prusiano (Cámara de los Lores). Aunque la mayoría de los ministerios tienen su sede en Berlín, algunos de ellos, así como algunos departamentos menores, tienen su sede en Bonn, la antigua capital de Alemania Occidental. Aunque Berlín es oficialmente la capital de la República Federal de Alemania, todavía trabajan en Bonn 8.000 de los 18.000 funcionarios empleados en la burocracia federal, a una distancia de unos 600 km de Berlín. Fuente: https://en.wikipedia.org/wiki/Capital_of_Germany
resultado2 = analyst_chain.invoke({
‘solicitud’: “¿Cómo cambió la cantidad de usuarios de la capital de Alemania entre abril y mayo de 2023?”,
“observaciones”: format_to_openai_functions([(resultado1, observacion1)])
})
El modelo quiere ejecutar get_monthly_active_users
con los argumentos {'mes': '2023-04-01', 'ciudad': 'Berlín'}
. Hagámoslo y devolvamos la información al modelo una vez más.
observacion2 = toolkit[result2.herramienta](result2.entrada_herramienta)
print(observacion2)
168
resultado3 = analyst_chain.invoke({
'solicitud': "¿Cómo cambió la cantidad de usuarios de la capital de Alemania entre abril y mayo de 2023?",
"observaciones": format_to_openai_functions([(resultado1, observacion1), (resultado2, observacion2)])
})
Luego, el modelo solicita llamar nuevamente a get_monthly_active_users
con los argumentos {'mes': '2023-05-01', 'ciudad': 'Berlín'}
. Hasta ahora, está haciendo un excelente trabajo. Sigamos su lógica.
observation3 = toolkit[result3.tool](result3.tool_input)
print(observation3) # 1046
result4 = analyst_chain.invoke({
'request': "¿Cómo cambió el número de usuarios de la capital de Alemania entre abril y mayo de 2023?",
"observations": format_to_openai_functions(
[(result1, observation1), (result2, observation2),
(result3, observation3)])
})
El siguiente resultado es una llamada de función para percentage_difference con los siguientes argumentos {'metric1': 168, 'metric2': 1046}
. Vamos a calcular la observación e invocar nuestra cadena una vez más. Con suerte, será el último paso.
observation4 = toolkit[result4.tool](result4.tool_input)
print(observation4) # 523.27
result5 = analyst_chain.invoke({
'request': "¿Cómo cambió el número de usuarios de la capital de Alemania entre abril y mayo de 2023?",
"observations": format_to_openai_functions(
[(result1, observation1), (result2, observation2),
(result3, observation3), (result4, observation4)])
})
Al final, obtuvimos la siguiente respuesta del modelo: El número de usuarios de Berlín, la capital de Alemania, aumentó aproximadamente un 523.27% entre abril y mayo de 2023.
Aquí está el esquema completo de las llamadas de LLM para esta pregunta.
![Ilustración del autor](https://ai.miximages.com/miro.medium.com/v2/resize:fit:640/format:webp/1*XG5gLTDxO9eHsZ2mnf80_w.jpeg)
En el ejemplo anterior, activamos las llamadas subsiguientes una por una manualmente, pero se puede automatizar fácilmente.
Es un resultado fantástico y pudimos ver cómo los LLM pueden razonar y utilizar múltiples herramientas. El modelo necesitó 5 pasos para lograr el resultado, pero siguió el plan que delineamos inicialmente, así que fue un camino bastante lógico. Sin embargo, si planeas usar LLM en producción, ten en cuenta que puede cometer errores y debes introducir procesos de evaluación y garantía de calidad.
Puedes encontrar el código completo en GitHub.
Resumen
Este artículo nos enseñó cómo potenciar los LLM con herramientas externas utilizando funciones de OpenAI. Hemos examinado dos casos de uso: extracción para obtener resultados estructurados y enrutado para utilizar información externa en preguntas. El resultado final me inspira, ya que los LLM pueden responder preguntas bastante complejas utilizando tres herramientas diferentes.
Volviendo a la pregunta inicial de si los LLM pueden reemplazar a los analistas de datos. Nuestro prototipo actual es básico y está lejos de las capacidades de los analistas juniors, pero sólo es el comienzo. ¡Manténganse al tanto! Profundizaremos en los diferentes enfoques de los agentes LLM. La próxima vez, intentaremos crear un agente que pueda acceder a la base de datos y responder preguntas básicas.
Referencia
Este artículo está inspirado en el curso “Funciones, herramientas y agentes con LangChain” de DeepLearning.AI
We will continue to update Zepes; if you have any questions or suggestions, please contact us!
Was this article helpful?
93 out of 132 found this helpful
Related articles
- El Manual para Construir Aplicaciones de AI Generativas
- Volver a lo básico semana de bonificación Implementación en la nube
- 7 Funciones de Trama de Pandas para una Visualización Rápida de Datos
- En el año 2024, se espera que la industria de la moda y belleza experimente grandes avances en términos de infraestructura. Aquí están algunas predicciones sobre lo que podemos esperar 1. Tiendas de belleza y moda inteligentes Con los avances en la
- ¿Cómo utilizar AutoGen sin depender de OpenAI o LM Studio?
- Slawa Madelska, emprendedora de tecnología sanitaria IA en atención médica, tecnología para el manejo del dolor, cuidado preventivo, innovaciones en el tratamiento del dolor de espalda y tendencias en la atención médica.
- Cómo filtrar listas en Python?