¿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

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.

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

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

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

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 y description para la función en sí misma,
  • type y description 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

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!

Share:

Was this article helpful?

93 out of 132 found this helpful

Discover more

Inteligencia Artificial

Conoce DiffusionDet Un Modelo de Inteligencia Artificial (IA) Que Utiliza Difusión para la Detección de Objetos

La detección de objetos es una técnica poderosa para identificar objetos en imágenes y videos. Gracias al aprendizaje...

Inteligencia Artificial

Toma el control NVIDIA NeMo SteerLM permite a las empresas personalizar las respuestas de un modelo durante la inferencia

Los desarrolladores tienen un nuevo volante de conducción asistida por IA para ayudarles a mantenerse en la carretera...

Inteligencia Artificial

Este artículo de IA explica cómo los lenguajes de programación pueden potenciarse entre sí a través de la sintonización de instrucciones.

La introducción de los Modelos de Lenguaje Grande (LLMs) ha causado sensación en el mundo. Estos modelos son famosos ...

Inteligencia Artificial

Ingenieros del MIT desarrollan réplica robótica del ventrículo derecho del corazón

Los ingenieros de la prestigiosa Universidad Tecnológica de Massachusetts (MIT) han desarrollado una revolucionaria r...

Inteligencia Artificial

Ajuste fino de Llama 2 para generación de texto en Amazon SageMaker JumpStart

Hoy, nos complace anunciar la capacidad de ajustar finamente los modelos Llama 2 de Meta utilizando Amazon SageMaker ...

Ciencias de la Computación

Robots de entrega de comida de Uber Eats listos para ser utilizados en múltiples ciudades de EE. UU.

La compañía de robots de servicio Serve Robotics informó que Uber Eats desplegará hasta 2,000 de sus robots de entreg...