Skip to main content
Una sola llamada a función es fácil. Lo interesante es el bucle a su alrededor, porque un modelo rara vez consigue lo que necesita en la primera llamada. Consulta algo, ve el resultado y decide qué pedir a continuación. Este tutorial construye un agente de línea de comandos que responde preguntas sobre una base de datos SQLite que nunca ha visto. No tiene el esquema en su prompt. Recibe tres herramientas de solo lectura y averigua el resto por sí mismo:
Por el camino haremos lo siguiente:
  1. Dar al modelo una base de datos y tres herramientas que la leen
  2. Describir esas herramientas para que el modelo sepa cuándo recurrir a cada una
  3. Ejecutar el bucle que convierte las llamadas a herramientas en resultados de herramientas
  4. Verlo solicitar varias herramientas a la vez
  5. Devolver los errores al modelo en lugar de lanzarlos
  6. Trazar la línea entre lo que el modelo no hará y lo que no puede hacer
La guía de Llamada a funciones cubre la forma de la petición por sí sola. Esta página trata de lo que ocurre después de que llega la primera respuesta.

Configuración

Necesitas Python 3.9 o más reciente, el paquete requests y una clave de API de Venice. Consulta Generar una clave de API si no tienes una. Todo lo demás está en la biblioteca estándar.
Crea agent.py con los imports y el bloque de cabecera que reutiliza cada llamada:
No todos los modelos pueden llamar herramientas, y los IDs de los modelos cambian, así que pregunta a la API cuál usar en lugar de fijar un nombre que envejecerá:
GET /models/traits asigna nombres estables de rasgos al modelo que actualmente cumple ese rol. Leer function_calling_default al iniciar significa que tu agente sigue funcionando cuando se reemplaza el modelo subyacente. Consulta Modelos para ver la lista completa de rasgos.

1. Una base de datos sobre la que valga la pena preguntar

Cualquier archivo SQLite sirve. Este es una pequeña tienda con clientes, productos y los pedidos que los unen, lo suficiente para que una pregunta real necesite un join y una agregación:

2. Tres herramientas a las que puede recurrir el modelo

Las herramientas reflejan la forma en la que una persona se enfrenta a una base de datos desconocida: averiguar qué contiene, mirar una tabla de cerca y luego consultarla.
Todas ellas devuelven una cadena JSON, incluyendo los fallos. Es deliberado, y la sección 5 explica por qué. Ahora descríbelas para el modelo. El campo description no es un comentario. Es lo único que lee el modelo al decidir qué herramienta llamar y qué poner en ella:

3. El bucle

La llamada a funciones es una conversación, no una petición. El modelo responde con llamadas a herramientas, tú las ejecutas, añades los resultados y vuelves a preguntar. Termina cuando el modelo responde con contenido en lugar de llamadas.
Tres detalles de ese bucle son más importantes de lo que parecen. El mensaje del asistente sin modificar vuelve a messages antes que los resultados. Lleva los tool_calls a los que los resultados están respondiendo, y en un modelo con razonamiento también lleva un campo reasoning_content. Reconstruir el mensaje a mano y descartar campos que no esperabas es la forma más común de romper la segunda ronda. Cada resultado se empareja con su llamada mediante tool_call_id. Nada más lo identifica. max_rounds es un límite real, no una formalidad. Un modelo que sigue consultando sin llegar a una conclusión terminará bucleando hasta que se te acabe la paciencia o el saldo.
Las llamadas a herramientas también llevan un campo index, y resulta tentador usarlo para alinear los resultados con las llamadas. No lo hagas. Cuando el modelo pide tres herramientas a la vez, las tres pueden llegar con el mismo index, porque numera el turno del asistente y no la llamada dentro de él. Solo id es único.

4. Qué hace en realidad

Conecta un bloque principal y ejecútalo:
Las llamadas a herramientas se imprimen en stderr a medida que ocurren, así que puedes verlo trabajar:
Eso tomó cinco rondas. La forma que tienen merece leerse con atención, porque es todo el argumento a favor del bucle: La ronda 4 es la parte que una sola llamada a función no puede hacer. El modelo no podía escribir esa consulta hasta haber visto la respuesta a la anterior. Tu ejecución no coincidirá llamada por llamada con esta. A veces el modelo describe las tres tablas a la vez y a veces una por una, y de vez en cuando se salta list_tables y adivina un nombre. Las cifras son estables porque salen de la base de datos; el camino hasta ellas no lo es.
La ronda 2 devolvió tres llamadas a herramientas en una sola respuesta, y el bucle de arriba las ejecuta una detrás de otra. Son independientes, así que un ThreadPoolExecutor merece la pena en cuanto tus herramientas hagan E/S real. Mantén los mensajes tool en el mismo orden que las llamadas que los produjeron.
Cada ronda vuelve a enviar toda la conversación, así que el prompt crece a medida que el agente trabaja. Venice cachea el prefijo estable automáticamente, y el bloque usage muestra cómo compensa:
En la última ronda, 960 de 1.020 tokens del prompt se sirvieron desde caché. Prompt caching explica cómo mantener estable ese prefijo.

5. Deja que los errores lleguen al modelo

El instinto es lanzar una excepción ante una consulta incorrecta. Resístelo. Un error es información, y el modelo puede actuar en consecuencia. Pide una tabla que no existe:
La primera consulta falló. Como run_query devolvió {"error": "OperationalError: no such table: purchases"} como un resultado de herramienta normal en lugar de lanzar, el modelo lo leyó, llamó a list_tables para averiguar qué sí existía y se corrigió. Si la excepción se hubiera propagado, el script habría muerto por un error tipográfico. Por eso cada herramienta devuelve JSON también en la ruta de error. La regla es sencilla: si a un humano que depura tu herramienta le interesaría ver el mensaje, al modelo también.

6. Lo que no hará y lo que no puede hacer

Pídele al agente que destruya algo:
Ejecútalo dos veces y puedes obtener dos comportamientos distintos. En una ocasión, se negó antes de tocar una herramienta:
En otra ocasión fue a buscar primero, ejecutó un SELECT para los clientes españoles, no encontró ninguno porque la columna guarda ES en lugar de Spain, e informó de eso en su lugar:
Ambas respuestas son razonables. Ninguna es un control de seguridad. El modelo leyó la palabra “read-only” en la descripción de una herramienta y decidió respetarla, y un modelo distinto, una conversación más larga o un usuario más insistente pueden llevar a una decisión diferente. La guarda dentro de run_query es la parte que no depende de una decisión:
Escribe la descripción para que el modelo rara vez lo intente. Escribe la guarda para que no importe cuando lo intente.
Esa segunda línea es la razón por la que run_query captura sqlite3.Warning junto con sqlite3.Error. El driver de Python rechaza sentencias apiladas, pero lanza Warning para ellas, y Warning no es una subclase de Error. Capturar solo sqlite3.Error deja que una sentencia apilada escape del handler y mate el bucle en lugar de devolver un mensaje que el modelo pueda leer.
Una comprobación de prefijo detiene las escrituras, pero no dice nada sobre las lecturas. Cualquier SELECT que escriba el modelo puede alcanzar cualquier tabla del archivo, incluidas algunas que nunca quisiste exponer. Merece la pena hacer dos cambios antes de que esto toque datos reales: abre la base de datos en solo lectura con sqlite3.connect("file:shop.db?mode=ro", uri=True), que falla las escrituras con attempt to write a readonly database sin importar lo que la comprobación de cadena se le pase, y apunta al agente a una base de datos o a un conjunto de vistas que contengan únicamente las columnas que se le permite ver.

Controlar cuándo se usan las herramientas

tool_choice decide cuánta voz tiene el modelo: "required" es más contundente de lo que parece. Preguntar a este agente What is 2 + 2? con tool_choice en "required" hace que llame a list_tables, mire una base de datos que no le sirve de nada y luego responda 4 en la siguiente ronda. Con "auto" responde 4 de inmediato y no llama a nada. Recurre a "required" cuando una herramienta realmente deba ejecutarse, por ejemplo para registrar una petición, y déjalo en paz en el resto de casos.

Ajustar el agente

Próximos pasos

El bucle que ahora tienes es el mismo que hay detrás de la mayoría de agentes. Solo cambian las herramientas.
  • Cambia las herramientas SQL por llamadas HTTP y se convierte en un agente de API.
  • Añade Web Search y scraping como herramienta y podrá consultar la web en vivo a mitad de la respuesta.
  • Pide un resultado tipado en lugar de prosa con Respuestas estructuradas.
  • Consulta una versión más grande de este patrón en el Private Research Agent.

Llamada a funciones

Referencia para el array de tools y tool_choice.

Respuestas estructuradas

Restringe la respuesta final a un esquema JSON.

Prompt caching

Mantén barata la conversación creciente.

Private Research Agent

El mismo bucle con herramientas web y un planificador.