Skip to content
Heimdallm
Marketplace/Documentation
Documentation · Guides

Tool calling

Models that support tools can answer a request by calling a function you define instead of writing prose. On chat completions, Heimdallm uses the OpenAI shapes end to end: you send tools, the model answers with tool_calls, you run the function and send the result back as a tool message. This page walks the whole loop.

Coding harnesses that speak Messages or Responses use different tool-call and result shapes. Those references describe each dialect's supported function tools and limits.

The tools a request declares run in your client, never in the marketplace. A web search is such a function, so the number of searches is the number of calls your client ran. Anthropic's hosted web search is refused on Messages and a built-in web_search is dropped on Responses, because neither would run.

Defining tools

Each entry in tools is a function with a name, an optional description and a JSON Schema for its arguments:

{
  "type": "function",
  "function": {
    "name": "get_weather",
    "description": "Current weather for a city.",
    "parameters": {
      "type": "object",
      "properties": {
        "city": { "type": "string", "description": "City name" },
        "unit": { "type": "string", "enum": ["celsius", "fahrenheit"] }
      },
      "required": ["city"]
    },
    "strict": true
  }
}

strict asks the endpoint to constrain generation to the schema, where it supports doing so.

tool_choice

ValueMeaning
"auto"The model decides. Default when tools are present.
"none"Never call a tool.
"required"Must call some tool.
{"type":"function","function":{"name":"get_weather"}}Must call this tool.

parallel_tool_calls: false limits the model to at most one call per turn, where the endpoint supports the flag. The Responses API accepts the same member and forwards it the same way. Response objects, including those in stream events, report parallel_tool_calls: true as a placeholder rather than a record of the request.

The loop, end to end

1. Send the conversation with your tools.

{
  "model": "deepseek/deepseek-v4-flash-0731-abliterated",
  "messages": [{ "role": "user", "content": "What's the weather in Lyon, in celsius?" }],
  "tools": [
    {
      "type": "function",
      "function": {
        "name": "get_weather",
        "description": "Current weather for a city.",
        "parameters": {
          "type": "object",
          "properties": {
            "city": { "type": "string" },
            "unit": { "type": "string", "enum": ["celsius", "fahrenheit"] }
          },
          "required": ["city"]
        }
      }
    }
  ]
}

2. The model answers with a call. finish_reason is tool_calls; arguments is a JSON string to parse:

{
  "choices": [
    {
      "index": 0,
      "message": {
        "role": "assistant",
        "content": null,
        "tool_calls": [
          {
            "id": "call_a1b2c3",
            "type": "function",
            "function": { "name": "get_weather", "arguments": "{\"city\":\"Lyon\",\"unit\":\"celsius\"}" }
          }
        ]
      },
      "finish_reason": "tool_calls"
    }
  ]
}

3. Run the function and send the result back. Append the assistant message exactly as received, then one tool message per call, with tool_call_id echoing the call's id:

{
  "model": "deepseek/deepseek-v4-flash-0731-abliterated",
  "messages": [
    { "role": "user", "content": "What's the weather in Lyon, in celsius?" },
    {
      "role": "assistant",
      "content": null,
      "tool_calls": [
        {
          "id": "call_a1b2c3",
          "type": "function",
          "function": { "name": "get_weather", "arguments": "{\"city\":\"Lyon\",\"unit\":\"celsius\"}" }
        }
      ]
    },
    { "role": "tool", "tool_call_id": "call_a1b2c3", "content": "{\"temp_c\": 24, \"sky\": \"clear\"}" }
  ],
  "tools": [{ "type": "function", "function": { "name": "get_weather", "parameters": { "type": "object" } } }]
}

4. The model answers in prose (finish_reason: "stop"), or with another call — loop until it stops. Keep sending tools on every turn: the model needs the definitions to keep using them.

Streaming tool calls

With "stream": true, calls arrive incrementally in choices[0].delta.tool_calls. Each entry carries an index; the id and function name arrive on the first fragment for that index, and function.arguments arrives as string fragments to concatenate in order:

data: {"...":"...","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"id":"call_a1b2c3","type":"function","function":{"name":"get_weather","arguments":""}}]},"finish_reason":null}]}

data: {"...":"...","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"{\"city\":\"Ly"}}]},"finish_reason":null}]}

data: {"...":"...","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"on\"}"}}]},"finish_reason":"tool_calls"}]}

Buffer per index, and treat the chunk with finish_reason: "tool_calls" as the end of the calls. Full frame mechanics: Streaming.

Routing to endpoints that can do this

Not every endpoint supports tools, and you do not have to arrange this yourself. An endpoint declares what it honours in its supported_parameters (visible in the endpoints listing), and a request carrying tools is only ever routed to an endpoint that declares them. An endpoint that never declared tools is not a candidate for your request, however cheap it is — the Tools column on a model page shows which endpoints those are.

That is on by default because ignoring tools is not a degraded answer but the wrong kind of one: you would get prose where you needed a tool call, and discover it after paying for the answer.

A demanding tool_choice — "none", "required" or a named function — is routed only to an endpoint declaring tool_choice. "auto" restates what an endpoint declaring tools does unasked, and parallel_tool_calls and the sampling controls are advisory: an endpoint declaring them is preferred, and where none does the request is served without them and the reply reports them unhonoured. See advisory settings.

Heimdallm also measures tool calling per endpoint: tool_call_success_1d in the endpoints listing is the share of the last day's tool requests that actually produced a tool call. A day rather than an hour, because the figure swings hour to hour and only a whole day of asks separates an endpoint that honours tools from one that drops them. It appears once the day holds 20 counted tool requests; below that it is null, which means too few tool requests to measure and never that none were answered. Requests you cancelled, requests whose stream broke, and requests whose outcome was never recorded count neither way.

A declaration is held to that measurement. An endpoint measuring below 85% has its tools promise withheld and stops receiving tool-calling traffic, the same way a failed capability check withholds it — your request routes to an endpoint that does honour tools, with nothing to change on your side. It takes the traffic back on its own once it measures above the floor again.

DiscoverFind your next modelModel libraryAll models, versions, prices and capabilitiesBenchmarksMeasured quality, cost and time by serving configurationShowcaseImages, video and 3D made on the marketplaceProvidersCompare independent inference providersActivityMarketplace traffic and usageNetwork statusAvailability, latency and endpoint healthChatConversations, model settings and filesImage, video & 3DGenerate images, video and 3D assetsScene studioStage a shot in 3D, direct it and render it as a videoCoding playgroundBuild software with an AI agent on a machine of your ownOverview & routingIdentity, workspaces and routing preferencesCredits & billingBalance, deposits, ledger and reserved fundsAPI keysKeys scoped to workspaces, limits and expirationOrganizationsTeams, members and shared workspacesRequest historyRequests, receipts, sandbox sessions and account historyReferralsInvitations and referral earningsPrivacyData settings, exports and deletionDocumentationQuickstart, SDKs, pricing and guidesAPI referenceInteractive endpoint reference