Uso de herramientas


El uso de herramientas (a menudo llamado llamada de funciones) es cómo un modelo de lenguaje sale de su propia caja de texto: le das una lista de funciones que puede pedirte que ejecutes, y decide cuándo usarlas. El modelo nunca ejecuta nada por sí mismo, solo solicita una llamada, y tu código realiza el trabajo.
Un modelo por sí solo está encerrado en una caja. No puede verificar el clima de hoy, buscar un usuario en tu base de datos o enviar un correo electrónico. Solo predice texto a partir de lo que aprendió, congelado en el momento del entrenamiento, la misma imagen de cómo funcionan los LLMs. El uso de herramientas es cómo dejas que salga de la caja, y es la base sobre la que se construyen los agentes.
La palabra clave es "pedir". 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 ciclo
El uso de herramientas es un ir y venir, no una única 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 quiere.
- Si pidió 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 el patrón completo. El modelo pide, 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á pasando
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 añadiendo al contexto del que el modelo predice. El modelo fue entrenado en ejemplos donde, dadas herramientas como estas, la continuación correcta a veces es un mensaje especialmente formateado que significa "llama a get_weather con la 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 a herramienta estructurado, y la API lo expone a ti como tool_calls.
El modelo no ha llegado 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 cómo leer.
Luego tú, no el modelo, ejecutas la función. Tomas el resultado real y lo pones de nuevo en los mensajes como más contexto, y el modelo predice su respuesta final desde ese contexto ampliado. Toda la característica es dos predicciones ordinarias con tu código haciendo el trabajo real en el medio.
Eres el puente entre la caja de texto del modelo y el mundo real. Mantén esa imagen y el resto del capítulo, incluidos los agentes, dejan de parecer 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 al modelo sobre cuándo usar la herramienta, así que escríbela como el prompt que es.
tools = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get the current weather for a city. Use when the user asks about weather.",
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "The city name, e.g. '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 el clima") lleva a que el modelo use la herramienta en los momentos incorrectos. Una clara ("Usa cuando el usuario pregunta sobre el clima") la guía bien.
parameters para sus argumentos. La descripción es realmente un prompt: le dice al modelo cuándo usar la herramienta, así que escríbela cuidadosamente. El esquema de parámetros es el mismo mecanismo que la salida estructurada, restringiendo 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 los argumentos, ejecutas la función real, y devuelves el resultado como un mensaje tool. Luego llamas al modelo de nuevo para que pueda terminar.
import json
# implementación real de la herramienta
def get_weather(city):
# en una aplicación 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. devuelve la solicitud del modelo y tu resultado
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 haces json.loads para convertirlos 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 climáticos crudos en una oración natural.
tool_calls en lugar de texto. Analizas los argumentos de una cadena JSON, ejecutas la función real, y empujas dos mensajes de vuelta: la solicitud del modelo y tu resultado, vinculados por tool_call_id. Una llamada final del modelo convierte tu resultado crudo en una respuesta natural. En la práctica
El mismo flujo que una función reutilizable. Fíjate en que la única cosa que se interpone 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 de herramientas paralelas, que manejarias haciendo un bucle sobre cada entrada en tool_calls. Y si dejas que el modelo siga llamando herramientas en un ciclo hasta que termine, decidiendo su propio siguiente paso cada vez, obtienes un agente, que es exactamente donde va el siguiente capítulo.

