OpenAI ha introducido recientemente nuevas funciones que muestran una arquitectura similar a un agente, como la API Asistente. Según OpenAI:
La API de Asistentes le permite crear asistentes de IA dentro de sus propias aplicaciones. Un asistente tiene instrucciones y puede aprovechar modelos, herramientas y archivos para responder a las consultas de los usuarios. La API de Asistentes actualmente admite tres tipos de herramientas: intérprete de código, búsqueda de archivos y llamada de funciones.
Si bien estos avances son prometedores, todavía están por detrás de LangChain. LangChain permite la creación de sistemas similares a agentes impulsados por LLM con mayor flexibilidad para procesar entradas de lenguaje natural y ejecutar acciones basadas en contexto.
Sin embargo, esto es sólo el comienzo.
En un nivel alto, la interacción con la API del Asistente se puede visualizar como un bucle:
- Dada la entrada del usuario, se llama a un LLM para determinar si se debe proporcionar una respuesta o tomar acciones específicas.
- Si la decisión del LLM es suficiente para responder la consulta, el ciclo finaliza.
- Si una acción conduce a una nueva observación, esta observación se incluye en el mensaje y se vuelve a llamar al LLM.
- Luego el bucle se reinicia.
Desafortunadamente, a pesar de las ventajas anunciadas, encontré que la documentación de la API estaba mal hecha, especialmente en lo que respecta a las interacciones con llamadas a funciones personalizadas y la creación de aplicaciones utilizando marcos como Streamlit.
En esta publicación de blog, lo guiaré en la creación de un asistente de IA utilizando la API del Asistente OpenAI con llamadas a funciones personalizadas, junto con una interfaz Streamlit, para ayudar a aquellos interesados en usar la API del Asistente de manera efectiva.
En esta publicación de blog, demostraré un ejemplo simple: un asistente de inteligencia artificial capaz de calcular impuestos en función de un ingreso determinado. Los usuarios de Langchain pueden pensar fácilmente en implementar esto creando un agente con una herramienta de “cálculo de impuestos”.
Esta herramienta incluiría los pasos de cálculo necesarios y un mensaje bien diseñado para garantizar que el LLM sepa cuándo llamar a la herramienta cada vez que una pregunta involucre ingresos o impuestos.
Sin embargo, este proceso no es exactamente el mismo con la API del Asistente OpenAI. Si bien el intérprete de código y las herramientas de búsqueda de archivos se pueden utilizar directamente de manera sencilla según Documentación de OpenAIlas herramientas personalizadas requieren un enfoque ligeramente diferente.
assistant = client.beta.assistants.create(
name="Data visualizer",
description="You are great at creating beautiful data visualizations. You analyze data present in .csv files, understand trends, and come up with data visualizations relevant to those trends. You also share a brief text summary of the trends observed.",
model="gpt-4o",
tools=[{"type": "code_interpreter"}],
)
Analicémoslo paso a paso. Apuntamos a:
- Defina una función que calcule los impuestos en función de los ingresos determinados.
- Desarrollar una herramienta utilizando esta función.
- Cree un asistente que pueda acceder a esta herramienta y llamarlo cuando sea necesario realizar el cálculo de impuestos.
Tenga en cuenta que la herramienta de cálculo de impuestos que se describe en el siguiente párrafo está diseñada como un ejemplo de juguete para demostrar cómo utilizar la API que se analiza en la publicación. No debe utilizarse para cálculos de impuestos reales.
Considere la siguiente función por partes, que devuelve el valor del impuesto para un ingreso determinado. Tenga en cuenta que la entrada se establece como una cadena para un análisis más sencillo:
def calculate_tax(revenue: str):
try:
revenue = float(revenue)
except ValueError:
raise ValueError("The revenue should be a string representation of a number.")if revenue <= 10000:
tax = 0
elif revenue <= 30000:
tax = 0.10 * (revenue - 10000)
elif revenue <= 70000:
tax = 2000 + 0.20 * (revenue - 30000)
elif revenue <= 150000:
tax = 10000 + 0.30 * (revenue - 70000)
else:
tax = 34000 + 0.40 * (revenue - 150000)
return tax
A continuación, definimos el asistente:
function_tools = [
{
"type": "function",
"function": {
"name": "calculate_tax",
"description": "Get the tax for given revenue in euro",
"parameters": {
"type": "object",
"properties": {
"revenue": {
"type": "string",
"description": "Annual revenue in euro"
}
},
"required": ["revenue"]
}
}
}
]
# Define the assistant
assistant = client.beta.assistants.create(
name="Assistant",
instructions="",
tools=function_tools,
model="gpt-4o",
)
Ahora, el punto esencial:
¿Cómo utiliza el asistente la función cuando se llama “calcular_impuesto”? Esta parte está mal documentada en el asistente OpenAI y muchos usuarios pueden confundirse la primera vez que la usan. Para manejar esto, necesitamos definir un EventHandler para gestionar diferentes eventos en el flujo de respuesta, específicamente cómo manejar el evento cuando se llama a la herramienta “calculate_tax”.
def handle_requires_action(self, data, run_id):
tool_outputs = []for tool in data.required_action.submit_tool_outputs.tool_calls:
if tool.function.name == "calculate_tax":
try:
# Extract revenue from tool parameters
revenue = ast.literal_eval(tool.function.arguments)["revenue"]
# Call your calculate_tax function to get the tax
tax_result = calculate_tax(revenue)
# Append tool output in the required format
tool_outputs.append({"tool_call_id": tool.id, "output": f"{tax_result}"})
except ValueError as e:
# Handle any errors when calculating tax
tool_outputs.append({"tool_call_id": tool.id, "error": str(e)})
# Submit all tool_outputs at the same time
self.submit_tool_outputs(tool_outputs)
El código anterior funciona de la siguiente manera: Para cada llamada de herramienta que requiere acción:
- Compruebe si el nombre de la función es “calcular_impuesto”.
- Extraiga el valor de los ingresos de los parámetros de la herramienta.
- Llama a
calculate_taxfuncionan con los ingresos para calcular el impuesto. (Aquí es donde ocurre la verdadera interacción). - Después de procesar todas las llamadas a herramientas, envíe los resultados recopilados.
Ahora puedes interactuar con el asistente siguiendo estos pasos estándar documentados por OpenAI (por ese motivo, no proporcionaré muchos detalles en esta sección):
- Crea un hilo: Esto representa una conversación entre un usuario y el asistente.
- Agregar mensajes de usuario: Estos pueden incluir tanto texto como archivos, que se agregan al hilo.
- Crea una carrera: Utilice el modelo y las herramientas asociadas con el asistente para generar una respuesta. Esta respuesta luego se agrega nuevamente al hilo.
El siguiente fragmento de código demuestra cómo ejecutar el asistente en mi caso de uso específico: El código configura una interacción de transmisión con un asistente utilizando parámetros específicos, incluido un ID de subproceso y un ID de asistente. Un EventHandler La instancia gestiona eventos durante la transmisión. El stream.until_done() El método mantiene la transmisión activa hasta que se completan todas las interacciones. El with La declaración garantiza que la transmisión se cierre correctamente después.
with client.beta.threads.runs.stream(thread_id=st.session_state.thread_id,
assistant_id=assistant.id,
event_handler=EventHandler(),
temperature=0) as stream:
stream.until_done()
Si bien mi publicación podría terminar aquí, he notado numerosas consultas en el foro Streamlit (como éste) donde los usuarios luchan para que el streaming funcione en la interfaz, aunque funcione perfectamente en el terminal. Esto me impulsó a profundizar más.
Para integrar con éxito la transmisión en su aplicación, deberá ampliar la funcionalidad de la clase EventHandler mencionada anteriormente, centrándose específicamente en el manejo de la creación de texto, deltas de texto y finalización de texto. Estos son los tres pasos clave necesarios para mostrar texto en la interfaz Streamlit mientras se administra el historial de chat:
- Manejo de la creación de texto (
on_text_created): Inicia y muestra un nuevo cuadro de texto para cada respuesta del asistente, actualizando la interfaz de usuario para reflejar el estado de las acciones anteriores. - Manejo del delta de texto (
on_text_delta): Actualiza dinámicamente el cuadro de texto actual a medida que el asistente genera texto, lo que permite cambios incrementales sin actualizar toda la interfaz de usuario. - Manejo de finalización de texto (
on_text_done): Finaliza cada segmento de interacción agregando un nuevo cuadro de texto vacío, preparándose para la siguiente interacción. Además, registra segmentos de conversación completados enchat_history.
Por ejemplo, considere el siguiente fragmento de código para administrar deltas de texto:
def on_text_delta(self, delta: TextDelta, snapshot: Text):
"""
Handler for when a text delta is created
"""
# Clear the latest text box
st.session_state.text_boxes[-1].empty()# If there is new text, append it to the latest element in the assistant text list
if delta.value:
st.session_state.assistant_text[-1] += delta.value
# Re-display the updated assistant text in the latest text box
st.session_state.text_boxes[-1].info("".join(st.session_state["assistant_text"][-1]))
Este código realiza tres tareas principales:
- Borrar el cuadro de texto más reciente: Vacía el contenido del cuadro de texto más reciente (
st.session_state.text_boxes[-1]) para prepararlo para nuevas entradas. - Agregar valor delta al texto del asistente: Si el texto nuevo (
delta.value) está presente, lo agrega al texto del asistente en curso almacenado enst.session_state.assistant_text[-1]. - Volver a mostrar el texto del asistente actualizado: Actualiza el contenido del último cuadro de texto para reflejar el contenido combinado de todo el texto del asistente acumulado hasta el momento (
st.session_state["assistant_text"][-1]).
Esta publicación de blog demostró cómo utilizar la API OpenAI Assistant y Streamlit para crear un asistente de inteligencia artificial capaz de calcular impuestos.
Hice este proyecto simple para resaltar las capacidades de la API Asistente, a pesar de su documentación poco clara. Mi objetivo era aclarar ambigüedades y brindar orientación a aquellos interesados en utilizar la API del Asistente. Espero que esta publicación haya sido útil y te anime a explorar más posibilidades con esta poderosa herramienta.
Debido a limitaciones de espacio, he intentado evitar incluir fragmentos de código innecesarios. Sin embargo, si es necesario, visite mi repositorio de github para ver la implementación completa.