Bu sayfa henüz Türkçe diline çevrilmedi. İngilizce özgün metin gösteriliyor.

Tool calling

Letting the model call your functions

Tool calls (also called function calls) let a model ask for information or actions it does not have on its own. The model never calls anything itself — it replies with a tool_calls array naming a function and its arguments, you run that function locally, and you send the result back in a follow-up request. The model then folds the result into its final answer.

The request and response shapes are the OpenAI Chat Completions tool-calling format, unchanged. If your code already calls OpenAI or an OpenAI-compatible gateway this way, pointing it at https://openrouter.myip.co.kr/api/v1 and one of our model ids is the only change required.

Checking support before you call

supported_parameters in GET /models is the source of truth, and it also works as a query filter:

bash
curl "https://openrouter.myip.co.kr/api/v1/models?supported_parameters=tools"

To force the gateway to reject a request rather than silently drop to a model that cannot use tools, add require_parameters: true to provider{} — see Provider routing.

The three-step exchange

Step 1 — send the tools with the request

json
{
  "model": "google/gemma-4-26b-a4b",
  "messages": [
    { "role": "user", "content": "What's the weather in Busan right now?" }
  ],
  "tools": [
    {
      "type": "function",
      "function": {
        "name": "get_weather",
        "description": "Get the current weather for a city",
        "parameters": {
          "type": "object",
          "properties": {
            "city": { "type": "string", "description": "City name, e.g. Busan" }
          },
          "required": ["city"]
        }
      }
    }
  ]
}

If the model decides it needs the tool, it replies with finish_reason: "tool_calls" and a tool_calls array instead of message content:

json
{
  "choices": [
    {
      "index": 0,
      "message": {
        "role": "assistant",
        "content": null,
        "tool_calls": [
          {
            "id": "call_8f2a",
            "type": "function",
            "function": { "name": "get_weather", "arguments": "{\"city\":\"Busan\"}" }
          }
        ]
      },
      "finish_reason": "tool_calls",
      "native_finish_reason": "tool_calls"
    }
  ]
}

Step 2 — run the tool yourself

We never execute anything. Parse function.arguments (it is a JSON string, not an object) and call your own code:

typescript
const args = JSON.parse(toolCall.function.arguments) as { city: string };
const result = await getWeather(args.city);

Step 3 — send the result back

Append the assistant's tool-call message and a role: "tool" message carrying the result, then call the API again with the same tools array:

json
{
  "model": "google/gemma-4-26b-a4b",
  "messages": [
    { "role": "user", "content": "What's the weather in Busan right now?" },
    {
      "role": "assistant",
      "content": null,
      "tool_calls": [
        { "id": "call_8f2a", "type": "function", "function": { "name": "get_weather", "arguments": "{\"city\":\"Busan\"}" } }
      ]
    },
    { "role": "tool", "tool_call_id": "call_8f2a", "content": "{\"tempC\":21,\"condition\":\"clear\"}" }
  ],
  "tools": [ /* same tool definitions as step 1 */ ]
}

The model's second response is a normal text message, with finish_reason: "stop".

Full example

import json
import os
from openai import OpenAI

client = OpenAI(base_url="https://openrouter.myip.co.kr/api/v1", api_key=os.environ["MYIP_API_KEY"])
MODEL = "google/gemma-4-26b-a4b"

def get_weather(city: str) -> dict:
    return {"tempC": 21, "condition": "clear"}  # replace with a real call

tools = [{
    "type": "function",
    "function": {
        "name": "get_weather",
        "description": "Get the current weather for a city",
        "parameters": {
            "type": "object",
            "properties": {"city": {"type": "string"}},
            "required": ["city"],
        },
    },
}]

messages = [{"role": "user", "content": "What's the weather in Busan right now?"}]

response = client.chat.completions.create(model=MODEL, messages=messages, tools=tools)
message = response.choices[0].message
messages.append(message.model_dump())

for call in message.tool_calls or []:
    args = json.loads(call.function.arguments)
    result = get_weather(**args)
    messages.append({"role": "tool", "tool_call_id": call.id, "content": json.dumps(result)})

final = client.chat.completions.create(model=MODEL, messages=messages, tools=tools)
print(final.choices[0].message.content)

tool_choice

json
{ "tool_choice": "auto" }      // default: model decides
{ "tool_choice": "none" }      // never call a tool
{ "tool_choice": "required" }  // must call some tool
{ "tool_choice": { "type": "function", "function": { "name": "get_weather" } } }  // force this one

tool_choice is not in our routing-key list, so it is forwarded upstream exactly as sent; whether an engine honours every variant is a property of that engine, not of us.

parallel_tool_calls

Some engines can request several tools in the same turn. Set parallel_tool_calls: false to force one call at a time. Like every non-routing parameter, it is passed straight through — it has an effect only if the serving engine implements it.

Streaming with tools

Tool calls arrive incrementally across delta.tool_calls chunks, keyed by array index; you accumulate function.arguments as a string until the chunk with finish_reason: "tool_calls".

typescript
const toolCallsByIndex: Record<number, { id: string; name: string; args: string }> = {};

for await (const chunk of stream) {
  for (const delta of chunk.choices[0]?.delta.tool_calls ?? []) {
    const slot = (toolCallsByIndex[delta.index] ??= { id: '', name: '', args: '' });
    if (delta.id) slot.id = delta.id;
    if (delta.function?.name) slot.name = delta.function.name;
    if (delta.function?.arguments) slot.args += delta.function.arguments;
  }
  if (chunk.choices[0]?.finish_reason === 'tool_calls') break;
}

See Streaming for the general SSE shape, including the final usage chunk and how mid-stream errors are reported.

A minimal agent loop

Chaining tool calls until the model stops asking for one is the same loop regardless of how many tools you define:

python
def run(messages, tools, max_turns=10):
    for _ in range(max_turns):
        response = client.chat.completions.create(model=MODEL, messages=messages, tools=tools)
        message = response.choices[0].message
        messages.append(message.model_dump())
        if not message.tool_calls:
            return message.content
        for call in message.tool_calls:
            result = TOOL_MAP[call.function.name](**json.loads(call.function.arguments))
            messages.append({"role": "tool", "tool_call_id": call.id, "content": json.dumps(result)})
    raise RuntimeError("max_turns exceeded")

Always cap the number of turns. A model that keeps asking for tools is not a hypothetical — it is the most common way an integration runs up an unexpected bill.

Good tool definitions

  • Name tools for what they do, not vaguely: get_weather_forecast, not weather.
  • Describe them like documentation. The model only knows what description tells it — mention units, formats, and edge cases ("City name, zip code, or 'lat,lng'").
  • Keep schemas strict. "additionalProperties": false and a required list reduce malformed arguments.
  • Design tools to compose. A search_productsget_product_detailscheck_inventory chain reads naturally to a model that already understands each tool's purpose.

Errors specific to tool calling

SituationWhat you get
tools sent to a model/candidate that does not support itDepends on the engine: usually the field is silently ignored and the model answers as if it were not sent
Malformed arguments JSON from the modelNot caught by us — validate before you JSON.parse it
You forget to send tools again on the follow-up callThe model may ask for the same tool again, or answer without using the earlier result

See Errors and debugging for the general error envelope.

Son güncelleme 5 Eyl 2026