# Tool calling and JSON output with open-weight models

> Function calling with open-weight models in the OpenAI format: tools, tool_choice, the call-and-answer loop, and response_format for valid JSON output.

- URL: https://llmwithcrypto.com/docs/tool-calling/
- Last updated: 2026-10-23
- Publisher: LLMwithCrypto (https://llmwithcrypto.com)

The model can ask your code to run a function, then use the result in its answer; or it can reply with a JSON object your program reads directly. Both use the OpenAI request format, and both depend on the model you choose.

**Quick answer — How does tool calling work on LLMwithCrypto?**

Send your functions in `tools`, in the OpenAI format. When the model wants one, **LLMwithCrypto** returns `finish_reason: "tool_calls"` with the function name and JSON arguments; your code runs it and sends the result back as a `tool` message. For structured replies, `response_format: {"type": "json_object"}` asks for valid JSON. Both work on the models that support them, as documented on 23 October 2026.

## Which models support tools and JSON?

Streaming works on every model; tool calling and JSON output follow what each model itself supports.

Test your own schema on the model you plan to use before you depend on it. A model that does not support tools answers in plain text instead of calling the function; a model that does not support JSON mode returns a `400` error for `response_format`.

## What is the loop?

1. **Describe the functions** — Each tool has a name, a description the model reads to decide when to use it, and parameters as a JSON Schema.
2. **Send the conversation with tools** — The model either answers in text, or stops with finish_reason: "tool_calls" and one or more calls in message.tool_calls.
3. **Run each call** — Parse function.arguments (a JSON string), run your function, and never trust the arguments blindly: validate them like any user input.
4. **Send the results back** — Append the assistant message, then one tool message per call with its tool_call_id and the result as text. Call the API again; repeat until the model answers in text.

*Python: a complete tool loop with 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)})

```

## What does a tool call look like in the response?

The assistant message has no text, a `tool_calls` array, and the choice ends with `finish_reason: "tool_calls"`. The arguments come as a JSON string, not an object. The ids and counts below are illustrative.

*Response asking for a function call (illustrative)*

```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
  }
}

```

## How do I control when tools are used?

- **`"tool_choice": "auto"`** (the default when `tools` is set): the model decides.
- **`"tool_choice": "none"`**: the model answers in text even though tools are listed.
- **`"tool_choice": "required"`**: the model must call at least one tool.
- **`{"type": "function", "function": {"name": "get_order_status"}}`**: the model must call that one.

Every tool definition is sent as input on each call, so long descriptions and many tools raise the input tokens. Keep descriptions short and send only the tools a step needs.

## How do I get valid JSON back?

Set `response_format` to `{"type": "json_object"}` and describe the expected keys in the system message: JSON mode guarantees that the reply parses, not that it has the keys you want. Keep `temperature` low for stable output and check the parsed object in your code before using it.

*Node.js: a JSON reply with 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 and JSON: common questions

### Do tool calls cost extra?

No fee of their own. The tool definitions, the call and the results you send back are all tokens, billed like any other. You pay per token from your balance: input and output tokens are counted by the model’s own tokenizer and charged at that model’s price per 1M tokens. A call that ends in an error costs nothing.

### Can the model call several tools at once?

Some models return more than one call in `tool_calls`. Run them all and send one `tool` message per call before the next request.

### Does the API run my functions?

No. The model only says which function to call and with which arguments; your code runs it. Nothing is executed on our side, and nothing of the exchange is stored: We do not store your prompts or the model’s answers: once a response is sent, its text is gone. Each call leaves one log line with the time, the account, the model and the token counts, deleted after 30 days.

---

LLMwithCrypto: LLMwithCrypto is an OpenAI-compatible LLM API for open-weight models — 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 — paid per token from a prepaid crypto balance, at 5% below the median price of the public hosts. No KYC: no ID, no selfie, no documents. Prompts and outputs are not stored.
