Uso de herramientas

El uso de herramientas (frecuentemente llamado invocación de funciones) es cómo un modelo de lenguaje se extiende más allá de su propia caja de texto: le entregas una lista de funciones que puede solicitar que ejecutes, y decide cuándo usarlas. El modelo nunca ejecuta nada por sí mismo, solo solicita una llamada, y tu código hace el trabajo.
Un modelo por sí solo está encerrado en una caja. No puede verificar el clima actual, buscar un usuario en tu base de datos o enviar un correo electrónico. Solo predice texto basado en lo que aprendió, congelado en el momento del entrenamiento, la misma imagen de cómo funcionan los LLM. El uso de herramientas es cómo le permites llegar más allá de la caja, y es la base sobre la cual se construyen los agentes.
La palabra clave es "solicitar". El modelo te dice qué función quiere y con qué argumentos. Tu código la ejecuta y devuelve el resultado. Mantienes el control todo el tiempo.
El bucle
El uso de herramientas es un ir y venir, no una sola llamada:
- Envías el mensaje del usuario más una lista de herramientas que el modelo puede usar.
- El modelo responde de una de dos formas: una respuesta normal, o una solicitud para llamar a una herramienta, con los argumentos que desea.
- Si solicitó una herramienta, tu código ejecuta esa función y devuelve el resultado al modelo.
- El modelo usa el resultado para escribir su respuesta final.
Ese es todo el patrón. El modelo solicita, tu código ejecuta. Los pasos 2 y 3 pueden repetirse si el modelo necesita varias herramientas, que es la semilla de los agentes un par de capítulos adelante.
Qué realmente está sucediendo
El uso de herramientas puede parecer que el modelo ganó nuevos poderes, y no fue así. Bajo el capó, el modelo está haciendo la única cosa que siempre hace: predecir texto. Nada ha cambiado sobre la caja en la que vive.
Cuando incluyes definiciones de herramientas en la solicitud, las estás agregando al contexto desde el cual el modelo predice. El modelo fue entrenado en ejemplos donde, dadas herramientas como estas, la continuación correcta es a veces un mensaje especialmente formateado que significa "llama a get_weather con ciudad Ciudad de México" en lugar de una oración normal. Así que cuando tu pregunta hace que una herramienta se vea útil, la salida más probable es ese mensaje de llamada de herramienta estructurado, y la API lo te lo presenta como tool_calls.
El modelo no ha extendido su brazo y ejecutado nada. Ha predicho que una llamada a herramienta es el movimiento correcto y ha escrito una solicitud para una, en un formato que tu código sabe leer.
Luego tú, no el modelo, ejecutas la función. Tomas el resultado real y lo pones de vuelta en los mensajes como más contexto, y el modelo predice su respuesta final desde ese contexto ampliado. Toda la característica son dos predicciones ordinarias con tu código haciendo el trabajo real en medio.
Eres el puente entre la caja de texto del modelo y el mundo real. Sostén esa imagen y el resto del capítulo, agentes incluidos, deja de sentirse misterioso: cada acción que toma una IA es una solicitud predicha que tu código elige ejecutar.
tool_calls. El modelo nunca ejecuta nada, predice una solicitud. Tu código ejecuta la función y devuelve el resultado como más contexto para la siguiente predicción, así que eres el puente entre el modelo y el mundo real. Definir una herramienta
Describes cada herramienta al modelo: su nombre, qué hace, y los argumentos que toma. La descripción no es documentación para ti, es una instrucción para el modelo sobre cuándo usar la herramienta, así que escríbela como la solicitud que es.
tools = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Obtén el clima actual de una ciudad. Úsalo cuando el usuario pregunta sobre el clima.",
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "El nombre de la ciudad, p. ej. 'Ciudad de México'"},
},
"required": ["city"],
},
},
},
]El bloque parameters es un esquema, la misma idea que salida estructurada: define los argumentos que el modelo debe producir. Cuando el modelo decide llamar a get_weather, devuelve un argumento city que coincide con esta forma. Una descripción vaga ("obtiene clima") lleva al modelo a usar la herramienta en los momentos equivocados. Una clara ("Úsalo cuando el usuario pregunta sobre el clima") la guía bien.
parameters para sus argumentos. La descripción es realmente una solicitud: le dice al modelo cuándo usar la herramienta, así que escríbela cuidadosamente. El esquema de parámetros es el mismo mecanismo que salida estructurada, limitando los argumentos que el modelo envía. Manejar la llamada
Cuando el modelo quiere una herramienta, la respuesta contiene tool_calls en lugar de una respuesta final. Lees la función solicitada y argumentos, ejecutas la función real, y devuelves el resultado como un mensaje tool. Luego llamas al modelo nuevamente para que pueda terminar.
import json
# implementación real de la herramienta
def get_weather(city):
# en una app real esto llama a una API de clima; aquí lo fingimos
return {"city": city, "tempC": 18, "condition": "cloudy"}
messages = [{"role": "user", "content": "¿Cuál es el clima en Ciudad de México?"}]
# 1. primera llamada: ofrece las herramientas
response = client.chat.completions.create(model=MODEL, messages=messages, tools=tools)
tool_calls = response.choices[0].message.tool_calls
tool_call = tool_calls[0] if tool_calls else None
if tool_call:
# 2. ejecuta la función solicitada con los argumentos del modelo
args = json.loads(tool_call.function.arguments)
result = get_weather(args["city"])
# 3. envía la solicitud del modelo y tu resultado de vuelta
messages.append(response.choices[0].message) # la solicitud de herramienta del asistente
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": json.dumps(result),
})
# 4. llama de nuevo para que el modelo pueda responder usando el resultado
response = client.chat.completions.create(model=MODEL, messages=messages)
print(response.choices[0].message.content)
# "Actualmente hay 18 grados y está nublado en Ciudad de México."Los argumentos llegan como una cadena JSON, así que json.loads los convierte en un objeto real, luego los verificas antes de usar. Empujas dos mensajes al historial: la solicitud de herramienta del modelo y tu resultado tool, vinculados por tool_call_id. La llamada final convierte los datos de clima crudos en una oración natural.
tool_calls en lugar de texto. Parseas los argumentos de una cadena JSON, ejecutas la función real, y empujas dos mensajes atrás: la solicitud del modelo y tu resultado, vinculados por tool_call_id. Una llamada al modelo final convierte tu resultado crudo en una respuesta natural. En la práctica
El mismo flujo que una función reutilizable. Observa que la única cosa entre el modelo y tus sistemas es código que escribiste y controlas:
import json
tool_impls = {"get_weather": lambda args: get_weather(args["city"])}
def answer_with_tools(user_text):
messages = [{"role": "user", "content": user_text}]
response = client.chat.completions.create(model=MODEL, messages=messages, tools=tools)
calls = response.choices[0].message.tool_calls
if not calls:
return response.choices[0].message.content # el modelo respondió directamente
call = calls[0]
args = json.loads(call.function.arguments)
result = tool_impls[call.function.name](args)
messages.append(response.choices[0].message)
messages.append({"role": "tool", "tool_call_id": call.id, "content": json.dumps(result)})
response = client.chat.completions.create(model=MODEL, messages=messages)
return response.choices[0].message.contentEsto maneja una llamada a herramienta. Un modelo también puede solicitar varias a la vez, llamadas paralelas de herramientas, que manejarías haciendo bucle sobre cada entrada en tool_calls. Y si dejas que el modelo siga llamando a herramientas en un bucle hasta que termina, decidiendo su propio siguiente paso cada vez, obtienes un agente, que es exactamente adónde va el próximo capítulo.

