Tool Calling

Define your own tools in OpenAI wire form and get real tool calls back, streaming or not.

The provider accepts a standard tools array. When you define tools, the model can call them: the response carries finish_reason: "tool_calls" and the call appears in tool_calls with a name and JSON arguments. This works on both the streaming and non-streaming paths.

json
{
  "model": "zyraxon-codex",
  "messages": [{ "role": "user", "content": "Save a hello world file." }],
  "tools": [
    {
      "type": "function",
      "function": {
        "name": "write_file",
        "description": "Write text to a file on disk.",
        "parameters": {
          "type": "object",
          "properties": {
            "path": { "type": "string" },
            "content": { "type": "string" }
          },
          "required": ["path", "content"]
        }
      }
    }
  ]
}

The reply ends the turn with the call rather than a prose answer:

json
{
  "choices": [
    {
      "finish_reason": "tool_calls",
      "message": {
        "role": "assistant",
        "content": null,
        "tool_calls": [
          {
            "id": "call_abc123",
            "type": "function",
            "function": {
              "name": "write_file",
              "arguments": "{\"path\":\"hello.txt\",\"content\":\"Hello, world\"}"
            }
          }
        ]
      }
    }
  ]
}

The loop

  1. 01Send your tools array with the user message.
  2. 02The model replies with finish_reason: "tool_calls" and one or more calls.
  3. 03Run each call yourself, and append a message with role: "tool", the matching tool_call_id, and the result.
  4. 04Call the endpoint again with the extended message list. Repeat until finish_reason is stop.
⚠
Send the assistant's tool call back, not only the result
The message list you send on the second call must include the assistant message that asked for the tools — with its tool_calls array and their ids — followed by the role: "tool" result that names the same tool_call_id. Sending only the tool result leaves the result unmatched, and a model that cannot match a result to its call will ask for the same tool again instead of answering.
⚠
You execute the tools
The provider does not run your tools. It returns the call; your code performs the action and sends the result back. That is what keeps the model from touching anything you did not authorise.
ℹ
Arguments are a JSON string
As in the OpenAI schema, function.arguments is a string containing JSON, not an object. Parse it before use. Streamed calls arrive as fragments — accumulate them by index, then parse.

The second request, in full

This is the request that turns a tool call into an answer. The assistant message that requested the tools is echoed back verbatim, then the result is appended as a role: "tool" message carrying the same tool_call_id.

json
{
  "model": "zyraxon-codex",
  "messages": [
    { "role": "user", "content": "Save a hello world file." },
    {
      "role": "assistant",
      "content": null,
      "tool_calls": [
        {
          "id": "call_abc123",
          "type": "function",
          "function": {
            "name": "write_file",
            "arguments": "{\"path\":\"hello.txt\",\"content\":\"Hello, world\"}"
          }
        }
      ]
    },
    { "role": "tool", "tool_call_id": "call_abc123", "content": "wrote hello.txt" }
  ],
  "tools": [ /* the same tools array as before */ ]
}
✓
How you know it worked
The reply now has finish_reason: "stop" and a normal content answer. If you get finish_reason: "tool_calls" asking for the same tool again, the assistant's tool_calls block is missing from what you sent — the model never saw its own call, so it cannot see the result either.