Skip to navigation

Function Calling

View as Markdown

Function calling lets a model decide when to call tools that your application exposes. OpenModels supports the OpenAI-compatible tools format on models that support tool calling.

Not every model supports tools. Before using function calling in production, verify tool support for the selected model and provider route, because parameter support can vary by upstream provider.

The examples below use <tool-capable-model> as a placeholder. Replace it with a model and route that you have verified supports tool calling.

How it works

  1. Define one or more tools with JSON schemas.
  2. Send the user message and tools to POST /chat/completions.
  3. Check whether the assistant returned tool_calls.
  4. Execute the matching function in your application.
  5. Send the tool result back as a role: "tool" message.
  6. Ask the model to produce the final answer.

Request

{
"model": "<tool-capable-model>",
"route": { "provider": "<provider-for-this-model>" },
"route_mode": "balanced",
"messages": [
{
"role": "user",
"content": "What is the weather in San Francisco?"
}
],
"tools": [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get current weather for a city",
"parameters": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "City name, for example San Francisco"
}
},
"required": ["city"],
"additionalProperties": false
}
}
}
],
"tool_choice": "auto"
}

If the model chooses a tool, the assistant message includes tool_calls:

{
"role": "assistant",
"content": null,
"tool_calls": [
{
"id": "call_123",
"type": "function",
"function": {
"name": "get_weather",
"arguments": "{\"city\":\"San Francisco\"}"
}
}
]
}

Your application executes get_weather, then sends the result back:

{
"role": "tool",
"tool_call_id": "call_123",
"content": "{\"city\":\"San Francisco\",\"temperature\":\"18C\",\"condition\":\"clear\"}"
}

Python

import json
import os
from openai import OpenAI
client = OpenAI(
base_url="https://api.getopenmodels.com/v1",
api_key=os.environ["OM_API_KEY"],
)
def get_weather(city: str) -> dict:
return {
"city": city,
"temperature": "18C",
"condition": "clear",
}
tools = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get current weather for a city",
"parameters": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "City name, for example San Francisco",
}
},
"required": ["city"],
"additionalProperties": False,
},
},
}
]
messages = [
{
"role": "user",
"content": "What is the weather in San Francisco?",
}
]
response = client.chat.completions.create(
model="<tool-capable-model>",
messages=messages,
tools=tools,
tool_choice="auto",
extra_body={
"route": {"provider": "<provider-for-this-model>"},
"route_mode": "balanced",
},
)
assistant_message = response.choices[0].message
messages.append(assistant_message.model_dump(exclude_none=True))
for tool_call in assistant_message.tool_calls or []:
if tool_call.function.name != "get_weather":
continue
arguments = json.loads(tool_call.function.arguments)
result = get_weather(city=arguments["city"])
messages.append(
{
"role": "tool",
"tool_call_id": tool_call.id,
"content": json.dumps(result),
}
)
final_response = client.chat.completions.create(
model="<tool-capable-model>",
messages=messages,
extra_body={
"route": {"provider": "<provider-for-this-model>"},
"route_mode": "balanced",
},
)
print(final_response.choices[0].message.content)

Node.js

import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://api.getopenmodels.com/v1",
apiKey: process.env.OM_API_KEY,
});
function getWeather({ city }) {
return {
city,
temperature: "18C",
condition: "clear",
};
}
const tools = [
{
type: "function",
function: {
name: "get_weather",
description: "Get current weather for a city",
parameters: {
type: "object",
properties: {
city: {
type: "string",
description: "City name, for example San Francisco",
},
},
required: ["city"],
additionalProperties: false,
},
},
},
];
const messages = [
{
role: "user",
content: "What is the weather in San Francisco?",
},
];
const response = await client.chat.completions.create({
model: "<tool-capable-model>",
route: { provider: "<provider-for-this-model>" },
route_mode: "balanced",
messages,
tools,
tool_choice: "auto",
});
const assistantMessage = response.choices[0].message;
messages.push(assistantMessage);
for (const toolCall of assistantMessage.tool_calls ?? []) {
if (toolCall.function.name !== "get_weather") {
continue;
}
const args = JSON.parse(toolCall.function.arguments);
const result = getWeather(args);
messages.push({
role: "tool",
tool_call_id: toolCall.id,
content: JSON.stringify(result),
});
}
const finalResponse = await client.chat.completions.create({
model: "<tool-capable-model>",
route: { provider: "<provider-for-this-model>" },
route_mode: "balanced",
messages,
});
console.log(finalResponse.choices[0].message.content);

Tool choice

Use tool_choice to control whether the model can call tools:

ValueBehavior
autoThe model decides whether to call a tool
noneThe model should answer without calling tools
{"type":"function","function":{"name":"get_weather"}}Force a specific function when supported by the model

Production guidance

  • Only execute tools that your application explicitly allowlists.
  • Validate and parse function.arguments before calling any internal system.
  • Keep tool schemas small, typed, and specific.
  • Treat tool output as untrusted input when sending it back to the model.
  • If the model returns no tool_calls, handle the assistant message as a normal text response.
  • Prefer non-streaming function calling first. Streaming tool calls require accumulating partial delta.tool_calls arguments before execution.

Billing

Function calling uses the same credit-based billing as normal chat completions. Credits are consumed based on the input tokens, generated output tokens, tool call messages, and tool results included in the conversation.