# Tool calling y salida JSON con modelos open-weight

> Function calling con modelos open-weight en el formato de OpenAI: tools, tool_choice, el bucle de llamada y response_format para obtener una salida JSON válida.

- URL: https://llmwithcrypto.com/es/docs/tool-calling/
- Última actualización: 2026-10-23
- Editor: LLMwithCrypto (https://llmwithcrypto.com)

El modelo puede pedir a tu código que ejecute una función y luego usar el resultado en su respuesta; o puede responder con un objeto JSON que tu programa lee directamente. Ambos usan el formato de petición de OpenAI, y ambos dependen del modelo que elijas.

**Respuesta rápida — ¿Cómo funciona el tool calling en LLMwithCrypto?**

Envía tus funciones en `tools`, en el formato de OpenAI. Cuando el modelo quiera una, **LLMwithCrypto** devuelve `finish_reason: "tool_calls"` con el nombre de la función y los argumentos en JSON; tu código la ejecuta y envía el resultado de vuelta como un mensaje `tool`. Para respuestas estructuradas, `response_format: {"type": "json_object"}` pide JSON válido. Ambos funcionan en los modelos que los admiten, según la documentación del 23 de octubre de 2026.

## ¿Qué modelos admiten tools y JSON?

El streaming funciona en todos los modelos; el tool calling y la salida JSON dependen de lo que admita cada modelo.

Prueba tu propio esquema en el modelo que piensas usar antes de depender de él. Un modelo que no admite tools responde en texto plano en lugar de llamar a la función; un modelo que no admite el modo JSON devuelve un error `400` para `response_format`.

## ¿Qué es el bucle?

1. **Describe las funciones** — Cada tool tiene un name, un description que el modelo lee para decidir cuándo usarla, y parameters como JSON Schema.
2. **Envía la conversación con tools** — El modelo o bien responde en texto, o bien se detiene con finish_reason: "tool_calls" y una o más llamadas en message.tool_calls.
3. **Ejecuta cada llamada** — Analiza function.arguments (una cadena JSON), ejecuta tu función y nunca confíes ciegamente en los argumentos: valídalos como cualquier entrada de usuario.
4. **Envía los resultados de vuelta** — Añade el mensaje del asistente y luego un mensaje tool por cada llamada con su tool_call_id y el resultado como texto. Vuelve a llamar a la API; repite hasta que el modelo responda en texto.

*Python: un bucle de tools completo con gpt-oss-120b*

```python
import json
import os

from openai import OpenAI

client = OpenAI(
    base_url="https://llmwithcrypto.com/api/v1",
    api_key=os.environ["LLMWITHCRYPTO_API_KEY"],
)

tools = [
    {
        "type": "function",
        "function": {
            "name": "get_order_status",
            "description": "Return the delivery status of an order.",
            "parameters": {
                "type": "object",
                "properties": {"order_id": {"type": "string"}},
                "required": ["order_id"],
            },
        },
    }
]


def get_order_status(order_id):
    return {"order_id": order_id, "status": "shipped", "eta": "2026-10-25"}


messages = [{"role": "user", "content": "Where is my order A-1042?"}]
while True:
    resp = client.chat.completions.create(model="gpt-oss-120b", messages=messages, tools=tools, max_tokens=300)
    msg = resp.choices[0].message
    if not msg.tool_calls:
        print(msg.content)
        break
    messages.append(msg)
    for call in msg.tool_calls:
        args = json.loads(call.function.arguments)
        result = get_order_status(**args)
        messages.append({"role": "tool", "tool_call_id": call.id, "content": json.dumps(result)})

```

## ¿Cómo se ve una llamada a una tool en la respuesta?

El mensaje del asistente no tiene texto, tiene un array `tool_calls`, y la elección termina con `finish_reason: "tool_calls"`. Los argumentos llegan como una cadena JSON, no como un objeto. Los ids y recuentos de abajo son ilustrativos.

*Respuesta que pide una llamada a función (ilustrativa)*

```json
{
  "id": "chatcmpl-3c9d27e1",
  "object": "chat.completion",
  "created": 1792742400,
  "model": "gpt-oss-120b",
  "choices": [
    {
      "index": 0,
      "message": {
        "role": "assistant",
        "content": null,
        "tool_calls": [
          {
            "id": "call_7f31",
            "type": "function",
            "function": {
              "name": "get_order_status",
              "arguments": "{\"order_id\": \"A-1042\"}"
            }
          }
        ]
      },
      "finish_reason": "tool_calls"
    }
  ],
  "usage": {
    "prompt_tokens": 118,
    "completion_tokens": 22,
    "total_tokens": 140
  }
}

```

## ¿Cómo controlo cuándo se usan las tools?

- **`"tool_choice": "auto"`** (el valor por defecto cuando `tools` está definido): el modelo decide.
- **`"tool_choice": "none"`**: el modelo responde en texto aunque haya tools listadas.
- **`"tool_choice": "required"`**: el modelo debe llamar al menos a una tool.
- **`{"type": "function", "function": {"name": "get_order_status"}}`**: el modelo debe llamar a esa.

Cada definición de tool se envía como entrada en cada llamada, así que las descripciones largas y muchas tools aumentan los tokens de entrada. Mantén las descripciones cortas y envía solo las tools que necesita un paso.

## ¿Cómo obtengo una salida JSON válida?

Define `response_format` como `{"type": "json_object"}` y describe las claves esperadas en el mensaje de sistema: el modo JSON garantiza que la respuesta se pueda parsear, no que tenga las claves que quieres. Mantén `temperature` bajo para una salida estable y comprueba el objeto parseado en tu código antes de usarlo.

*Node.js: una respuesta JSON con response_format*

```javascript
import OpenAI from 'openai';

const client = new OpenAI({
  baseURL: 'https://llmwithcrypto.com/api/v1',
  apiKey: process.env.LLMWITHCRYPTO_API_KEY,
});

const resp = await client.chat.completions.create({
  model: 'gpt-oss-120b',
  messages: [
    { role: 'system', content: 'Reply with a JSON object {"sentiment": "positive" | "negative" | "neutral", "reason": string}.' },
    { role: 'user', content: 'The delivery was late but support fixed it in an hour.' },
  ],
  response_format: { type: 'json_object' },
  temperature: 0.2,
  max_tokens: 150,
});
const result = JSON.parse(resp.choices[0].message.content);
console.log(result.sentiment, '-', result.reason);

```

## Tools y JSON: preguntas frecuentes

### ¿Las llamadas a tools tienen coste extra?

No tienen una tarifa propia. Las definiciones de tools, la llamada y los resultados que envías de vuelta son tokens, facturados como cualquier otro. Pagas por token desde tu saldo: los tokens de entrada y de salida se cuentan con el tokenizador propio del modelo y se cobran al precio de ese modelo por 1M de tokens. Una llamada que termina en error no cuesta nada.

### ¿Puede el modelo llamar a varias tools a la vez?

Algunos modelos devuelven más de una llamada en `tool_calls`. Ejecútalas todas y envía un mensaje `tool` por cada llamada antes de la siguiente petición.

### ¿La API ejecuta mis funciones?

No. El modelo solo indica qué función llamar y con qué argumentos; tu código la ejecuta. No se ejecuta nada por nuestra parte y no se almacena nada del intercambio: No guardamos tus prompts ni las respuestas del modelo: una vez enviada una respuesta, su texto desaparece. Cada llamada deja una línea de log con la hora, la cuenta, el modelo y el recuento de tokens, que se borra a los 30 días.

---

LLMwithCrypto: LLMwithCrypto es una API de LLM compatible con OpenAI para modelos de pesos abiertos — DeepSeek V4 Pro, DeepSeek V4.1 Flash, DeepSeek R1, Qwen3.5 397B A17B, Qwen3 235B A22B Instruct, gpt-oss-120b, gpt-oss-20b, GLM-4.7, Llama 3.3 70B Instruct, Gemma 4 31B, Mistral Large 3, Mistral Small 4 — con pago por token desde un saldo de criptomonedas prepago, a un 5% por debajo del precio medio de los hosts públicos. Sin KYC: sin identificación, sin selfie, sin documentos. Los prompts y las salidas no se almacenan.
