Avanzado 20 minAPI

Dominar la llamada a herramientas (Tool Calling) con Ollama y Python

La llamada a herramientas (tool calling) transforma un modelo que se limita a generar texto en un agente capaz de desencadenar la ejecución de código real: llamar a una API meteorológica, consultar una base de datos o realizar un cálculo. Esta guía muestra cómo dominar la llamada a herramientas de Ollama en Python de principio a fin: formato JSON de las herramientas, bucle de ejecución, streaming de las llamadas a herramientas (serie 0.17) y salidas estructuradas restringidas por un JSON Schema aplicado directamente durante la decodificación. Todo se ejecuta en local en http://localhost:11434, sin clave API ni fuga de datos.

Por Mohamed Meguedmi·Actualización 2026-08-27·Probado en Windows, macOS y Linux

#¿Por qué el llamado a herramientas (tool calling)?

Un LLM por sí solo no sabe nada del mundo real después de su entrenamiento: no conoce el tiempo que hace hoy, ni el saldo de una cuenta, ni el contenido de tu base de datos. La llamada a herramientas cubre ese vacío. Le describes al modelo una lista de funciones disponibles, el modelo decide cuáles invocar y con qué argumentos, tu código las ejecuta y luego devuelve el resultado al modelo para que redacte una respuesta informada.

El punto clave que hay que entender: el modelo nunca ejecuta nada por sí mismo. Solo genera una solicitud estructurada: «llama a get_meteo con ville='Lyon'». Es tu programa en Python el que ejecuta la función y mantiene el control total. Esta separación es lo que hace que las llamadas a herramientas sean seguras y predecibles.

Datos actualizados
El modelo consulta una API en tiempo real en lugar de adivinar a partir de sus recuerdos de entrenamiento.
Acciones concretas
Crear un ticket, enviar un correo, escribir en una base de datos: el LLM coordina, tu código actúa.
Fiabilidad
Los cálculos y las búsquedas exactas se delegan a código determinista, en lugar de que el modelo se los invente.
100 % local
Con Ollama, toda la cadena permanece en tu máquina: sin clave API, sin solicitudes salientes y sin facturación por token.

#Cómo funciona la llamada a herramientas en Ollama

El kit Copiloto Local

Esta guía te lleva al modelo. El kit te lleva al copiloto que programa en tu editor.

  • Espacio en línea de por vida
  • PDF + archivos
  • Actualizaciones de por vida

El ciclo completo consta de cinco pasos. Visualizarlo bien evita la confusión más común: creer que una sola llamada basta. Se necesitan al menos dos: una para obtener la solicitud de uso de una herramienta y otra para obtener la respuesta final.

  1. 01
    Envías la pregunta + las herramientas
    La solicitud de chat contiene el mensaje del usuario y la lista de herramientas disponibles (parámetro tools).
  2. 02
    El modelo devuelve una solicitud de herramienta
    En lugar de responder con texto, devuelve uno o varios tool_calls con el nombre de la función y los argumentos.
  3. 03
    Tu código ejecuta la función
    Recuperas name y arguments, llamas a la función Python real correspondiente y obtienes un resultado.
  4. 04
    Devuelves el resultado
    El resultado se añade al historial como un mensaje con el rol «tool» y luego vuelves a llamar a chat.
  5. 05
    El modelo escribe la respuesta final
    Con el resultado obtenido, esta vez produce una respuesta en lenguaje natural para el usuario.
i
Dos llamadas como mínimo
Un ciclo completo de llamada a herramientas = al menos dos pasadas por el modelo. Si el modelo encadena varias herramientas, se repite el ciclo hasta que ya no haya tool_calls en su respuesta.

#Prerrequisitos

Tres componentes: el demonio de Ollama en ejecución, un modelo que realmente admita herramientas y la biblioteca oficial de Python. Cuidado con el segundo punto: no todos los modelos pueden hacer llamadas a herramientas. Busca familias recientes diseñadas para ello.

Ollama actualizado
Serie 0.17 o posterior para aprovechar el streaming de las llamadas a herramientas. El demonio escucha en http://localhost:11434.
Un modelo compatible con herramientas
Qwen 3.5, Granite 4.2, Mistral Small, Devstral, gpt-oss. Los modelos marcados « tools » en ollama.com/library.
Suficiente VRAM
Un modelo pequeño (Qwen 3.5 4B ≈ 3,4 GB) basta para probar; un Granite 4.2 8B (≈ 5,3 GB) o un Qwen 3.5 9B (≈ 6,6 GB) sigue mejor las instrucciones que implican varias herramientas. RTX 3060 de 12 GB como opción de gama de entrada.
La biblioteca ollama
pip install -U ollama. Sabe construir el esquema de una herramienta directamente a partir de una función Python tipada.
Preparar el entorno
# Le daemon Ollama doit tourner (souvent déjà lancé en service)
ollama serve

# Un modèle qui supporte les outils
ollama pull qwen3.5:4b

# La librairie Python officielle
pip install -U ollama
!
Modelo sin soporte de herramientas
Pasar un parámetro tools a un modelo que no lo soporta no siempre genera un error claro: el modelo ignora las herramientas y responde en texto, o devuelve JSON falso en el contenido. Revisa siempre la etiqueta «Tools» del modelo antes de programar.

#El formato JSON de las herramientas

Una herramienta se describe con un esquema JSON estrictamente alineado con el de OpenAI: un objeto type: "function" que contiene un nombre, una descripción y un objeto parameters en formato JSON Schema. La descripción es sumamente importante: es lo que lee el modelo para decidir cuándo y cómo llamar a la herramienta. Sé explícito.

Definición de una herramienta (formato OpenAI)
{
  "type": "function",
  "function": {
    "name": "get_meteo",
    "description": "Renvoie la météo actuelle pour une ville donnée",
    "parameters": {
      "type": "object",
      "properties": {
        "ville": {
          "type": "string",
          "description": "Nom de la ville, ex : Lyon"
        },
        "unite": {
          "type": "string",
          "enum": ["celsius", "fahrenheit"],
          "description": "Unité de température souhaitée"
        }
      },
      "required": ["ville"]
    }
  }
}
→
Deja que la biblioteca escriba el esquema
En Python, no tienes que escribir este JSON a mano. Si pasas directamente una función tipada con una docstring, la biblioteca ollama deduce automáticamente el esquema a partir de ella (nombres, tipos, descripción). Es la forma más segura de evitar errores tipográficos en el JSON.

#Primera llamada a una herramienta en Python

Empecemos por el caso más sencillo: una función, una pregunta, y observamos lo que el modelo decide. Las anotaciones de tipo y la docstring sirven para generar el esquema enviado al modelo.

Una primera llamada a una herramienta
import ollama

def get_meteo(ville: str, unite: str = "celsius") -> str:
    """Renvoie la météo actuelle pour une ville donnée.

    Args:
        ville: Nom de la ville (ex : Lyon).
        unite: Unité de température, celsius ou fahrenheit.
    """
    # Ici, un vrai appel à une API météo. On simule le retour.
    return f"21 degrés, ciel dégagé à {ville} ({unite})."

reponse = ollama.chat(
    model="qwen3.5:4b",
    messages=[{"role": "user", "content": "Quel temps fait-il à Lyon ?"}],
    tools=[get_meteo],  # la lib introspecte signature + docstring
)

# Le modèle n'a pas répondu en texte : il demande un outil
for appel in reponse.message.tool_calls or []:
    print(appel.function.name)       # -> get_meteo
    print(appel.function.arguments)  # -> {'ville': 'Lyon'}

En este punto, message.content suele estar vacío: el modelo ha devuelto su solicitud en message.tool_calls. Cada tool_call expone function.name (una cadena) y function.arguments (ya deserializado por la biblioteca como un diccionario de Python). Solo queda ejecutar la llamada y devolver el resultado.

#El bucle de ejecución completo

Aquí tienes la estructura reutilizable de un agente con llamadas a herramientas: un diccionario que asocia cada nombre de herramienta con su función, la ejecución de las llamadas solicitadas, la incorporación de los resultados al historial y, después, una segunda llamada para obtener la respuesta final. Lo envolvemos todo en un bucle para gestionar el caso en que el modelo encadene varias herramientas.

Bucle completo del agente
import ollama

def get_meteo(ville: str, unite: str = "celsius") -> str:
    """Météo actuelle d'une ville."""
    return f"21 degrés, ciel dégagé à {ville}."

# Registre nom -> fonction réelle
OUTILS = {"get_meteo": get_meteo}

messages = [{"role": "user", "content": "Météo à Lyon puis à Marseille ?"}]

while True:
    reponse = ollama.chat(model="qwen3.5:4b", messages=messages, tools=[get_meteo])
    messages.append(reponse.message)  # on garde la demande dans l'historique

    if not reponse.message.tool_calls:
        # Plus d'outil demandé : c'est la réponse finale
        print(reponse.message.content)
        break

    for appel in reponse.message.tool_calls:
        fonction = OUTILS.get(appel.function.name)
        if fonction is None:
            resultat = f"Erreur : outil inconnu '{appel.function.name}'"
        else:
            resultat = fonction(**appel.function.arguments)
        messages.append({
            "role": "tool",
            "tool_name": appel.function.name,
            "content": str(resultat),
        })
!
No confiar nunca en los argumentos
Los argumentos provienen del modelo: pueden ser incompletos, mal tipados o fuera de rango. Valídalos antes de ejecutar la función, especialmente si afecta a un sistema de archivos, una base de datos o una orden de shell. Un llamado a herramientas no validado abre la puerta a inyecciones.

El mensaje de resultado tiene el rol tool y un campo tool_name que indica a qué llamada responde. El campo content debe ser una cadena: serializa tus objetos (json.dumps) antes de devolverlos. El modelo vuelve a leer este contenido como si fuera una observación del mundo.

#Paridad con OpenAI: el mismo código con el cliente openai

Ollama expone un endpoint compatible con OpenAI en /v1. Si tu código ya utiliza el cliente openai, apenas tienes que cambiar nada: apunta base_url a Ollama y usa una clave API ficticia. El formato de las herramientas y de tool_calls es idéntico: esa «paridad con OpenAI» hace que la migración sea trivial.

Llamada a herramientas mediante el cliente openai
from openai import OpenAI

client = OpenAI(base_url="http://localhost:11434/v1", api_key="ollama")

reponse = client.chat.completions.create(
    model="qwen3.5:4b",
    messages=[{"role": "user", "content": "Météo à Lyon ?"}],
    tools=[{
        "type": "function",
        "function": {
            "name": "get_meteo",
            "description": "Météo actuelle d'une ville",
            "parameters": {
                "type": "object",
                "properties": {"ville": {"type": "string"}},
                "required": ["ville"],
            },
        },
    }],
)

print(reponse.choices[0].message.tool_calls)
i
Una diferencia que debes conocer
A través del cliente openai, function.arguments llega en forma de cadena JSON (que debe parsearse con json.loads), mientras que la biblioteca ollama nativa ya te entrega un diccionario. Tenlo en cuenta al migrar de un cliente a otro.

#Streaming de llamadas a herramientas (serie 0.17)

Históricamente, activar el streaming desactivaba las llamadas a herramientas: había que elegir. Desde la serie 0.17, Ollama puede transmitir las llamadas a herramientas en streaming a medida que avanza la generación. En concreto, recibes las tool_calls en los fragmentos (chunks) del flujo, junto con el texto que pueda generarse, lo que permite mostrar una respuesta fluida mientras se activan herramientas.

Llamadas a herramientas en streaming
import ollama

flux = ollama.chat(
    model="qwen3.5:4b",
    messages=[{"role": "user", "content": "Météo à Nice ?"}],
    tools=[get_meteo],
    stream=True,
)

for morceau in flux:
    # Le texte arrive token par token
    if morceau.message.content:
        print(morceau.message.content, end="", flush=True)
    # Les appels d'outils arrivent aussi dans le flux
    for appel in morceau.message.tool_calls or []:
        print("\n[outil]", appel.function.name, appel.function.arguments)
→
Cuándo usar streaming
El streaming destaca en las interfaces conversacionales donde el usuario ve cómo se construye la respuesta. Para un procesamiento por lotes o una extracción de datos, mantén el modo sin streaming: es más sencillo de gestionar y recibes la respuesta completa de una sola vez.

#Salidas estructuradas: forzar un esquema JSON

La llamada a herramientas sirve para actuar; las salidas estructuradas sirven para garantizar la forma de la respuesta. Con el parámetro format, proporcionas un esquema JSON que Ollama aplica durante la decodificación: el modelo está obligado, token a token, a producir únicamente una salida válida según el esquema. Se acabó el análisis frágil de un JSON aproximado: la estructura queda garantizada por diseño.

Lo más práctico en Python es describir la estructura con un modelo Pydantic, luego obtener el esquema mediante model_json_schema(). Luego obtienes un objeto tipado y validado.

Salida estructurada validada por Pydantic
from pydantic import BaseModel
import ollama

class Facture(BaseModel):
    numero: str
    montant_ttc: float
    devise: str
    lignes: list[str]

reponse = ollama.chat(
    model="qwen3.5:4b",
    messages=[{
        "role": "user",
        "content": "Extrais numéro, montant TTC, devise et lignes de : "
                   "Facture F-2026-0042, total 149,90 EUR, "
                   "prestations : audit, rédaction.",
    }],
    # Le schéma est appliqué au décodage : sortie garantie conforme
    format=Facture.model_json_schema(),
)

facture = Facture.model_validate_json(reponse.message.content)
print(facture.montant_ttc)  # -> 149.9
i
format="json" vs esquema completo
format="json" solo obliga a generar JSON válido, sin imponer una estructura. Pasar un esquema JSON completo va mucho más allá: restringe los campos, los tipos y los valores permitidos durante la decodificación. Prefiere siempre el esquema explícito cuando conozcas la estructura esperada.
→
El combo ganador
Llamada a herramientas para obtener los datos, salida estructurada para devolverlos correctamente. Un agente que llama a una API y devuelve un objeto Pydantic validado es mucho más robusto que un modelo al que se le pide «responde en JSON» esperando que funcione.

#Gestión de errores y escollos habituales

La llamada a herramientas rara vez falla de forma evidente: la mayoría de las veces, el modelo «se desvía» silenciosamente. A continuación, los fallos frecuentes y cómo solucionarlos.

Ningún tool_call devuelto
El modelo respondió con texto cuando era necesario usar una herramienta. Mejora la descripción de la herramienta o cambia de modelo: los modelos pequeños suelen fallar al decidir cuándo llamar a una herramienta.
Argumentos ausentes o incorrectos
function.arguments puede omitir un campo required o asignarle un tipo incorrecto. Valida con Pydantic o un try/except antes de llamar a la función real y devuelve el error al modelo como resultado de la herramienta.
Herramienta inventada
El modelo inventa un nombre de función inexistente. Por eso se usa OUTILS.get(name) que devuelve un mensaje de error en lugar de fallar — así el modelo puede corregirse en el siguiente turno.
Bucle infinito de llamadas a herramientas
Un modelo puede volver a solicitar la misma herramienta indefinidamente. Añade un contador de iteraciones con un máximo (por ejemplo, 5) para interrumpir el bucle y evitar que siga ejecutándose sin avanzar.
Contexto truncado
Ollama limita a veces el contexto a 2048 tokens por defecto, lo que borra el historial de herramientas en sesiones largas. Aumenta num_ctx mediante las opciones del modelo.
Resultado no serializado
Devolver un objeto Python sin serializar como content hace que falle la solicitud. Serialízalo siempre como una cadena (json.dumps o str) antes de añadirlo a los mensajes.
Ejecución defensiva de una herramienta
import json

def executer_outil(appel, registre, garde_fou=5):
    nom = appel.function.name
    fonction = registre.get(nom)
    if fonction is None:
        return f"Erreur : outil inconnu '{nom}'."
    try:
        resultat = fonction(**appel.function.arguments)
    except TypeError as e:
        return f"Erreur d'arguments pour {nom} : {e}"
    except Exception as e:
        return f"Échec de {nom} : {e}"
    return json.dumps(resultat, ensure_ascii=False, default=str)

La idea clave: nunca dejar que un error de una herramienta provoque un fallo que detenga al agente. Devuelve el mensaje de error al modelo como si fuera un resultado. Un buen modelo lee «herramienta desconocida» o «falta un argumento» y ajusta su siguiente llamada por sí mismo.


#Para ir más allá

Ya sabes cómo hacer llamadas a herramientas con Ollama en Python: formato JSON de las herramientas, bucle de ejecución, paridad con OpenAI, streaming y salidas estructuradas restringidas por un esquema. Estas guías profundizan de forma natural en el tema.

La API REST de Ollama
«Integrar Ollama en una aplicación Python a través de la API REST» — los fundamentos del endpoint :11434, del streaming y del modo JSON, base de toda esta guía.
Agentes con LangChain
«Crear un agente de IA local en Python con LangChain y Ollama» — orquestar varias herramientas y memoria sobre una capa básica de llamadas a herramientas.
Elegir tu cuantización
«Elegir la cuantización (Q4, Q5, Q8, FP16)» — para equilibrar la VRAM y la calidad del modelo que controlará tus herramientas.
¿Esta guía te ha ayudado?

¿Un comentario, un error, una precisión? Avísanos, eso mejora la guía para todos.