Tool Calling
Let a model call your functions on Chat Completions, Responses or Anthropic Messages.
You describe your functions in the request. When the model wants one, it returns the function name and JSON arguments instead of a final answer. Your code runs the function, sends the result back, and the model continues. The model never runs anything itself.
Write your tools once, in the shape of the endpoint you already use: Chat Completions, Responses or Anthropic Messages. The same definitions then work with every model on that endpoint, so moving a tool loop from GPT to Claude or GLM is a one-line model change. You can test the whole loop for free with ohmygpt/mock.
Chat Completions
1. Send the tools
curl https://api.ohmygpt.com/v1/chat/completions \
-H "Authorization: Bearer $OHMYGPT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "openai/gpt-5.4-nano",
"messages": [{"role": "user", "content": "What is the weather in Paris?"}],
"tools": [{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get the current weather for a city.",
"parameters": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"]
}
}
}]
}'The model answers with finish_reason: "tool_calls" and the call it wants (trimmed):
{
"choices": [
{
"index": 0,
"finish_reason": "tool_calls",
"message": {
"role": "assistant",
"content": null,
"tool_calls": [
{
"id": "call_wwN71lk5akb6aFGYQv9f234b",
"type": "function",
"function": { "name": "get_weather", "arguments": "{\"city\":\"Paris\"}" }
}
]
}
}
]
}arguments is a JSON string. Parse it before you use it.
2. Return the result
Append the assistant message unchanged, then one tool message per call with the matching tool_call_id, and send the conversation again with the same tools. The full loop in Python:
import json
import os
from openai import OpenAI
client = OpenAI(api_key=os.environ["OHMYGPT_API_KEY"], base_url="https://api.ohmygpt.com/v1")
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. Paris"}},
"required": ["city"],
},
},
}
]
def get_weather(city: str) -> dict:
return {"city": city, "temperature_c": 18, "condition": "cloudy"}
messages = [{"role": "user", "content": "What's the weather in Paris?"}]
response = client.chat.completions.create(model="openai/gpt-5.4-nano", messages=messages, tools=tools)
message = response.choices[0].message
messages.append(message)
for call in message.tool_calls or []:
args = json.loads(call.function.arguments)
messages.append({"role": "tool", "tool_call_id": call.id, "content": json.dumps(get_weather(**args))})
final = client.chat.completions.create(model="openai/gpt-5.4-nano", messages=messages, tools=tools)
print(final.choices[0].message.content)In **Paris**, it’s currently **cloudy** with a temperature of **18°C**.The same program with model="anthropic/claude-haiku-4-5" prints The weather in Paris is currently **cloudy** with a temperature of **18°C** (about 64°F).
Control tool use
tool_choice | Effect |
|---|---|
"auto" (default) | The model decides whether to call a tool |
"none" | The model answers in text |
"required" | The model must call at least one tool |
{"type": "function", "function": {"name": "get_time"}} | The model must call get_time |
Test the loop for free
ohmygpt/mock answers deterministically. When the request has tools, it calls the first one, or the one tool_choice names, with every required argument set to its zero value. After a tool result, it replies with text. The first request above with "model": "ohmygpt/mock" returns:
{
"id": "chatcmpl-ohmygpt-mock",
"object": "chat.completion",
"model": "mock",
"choices": [
{
"index": 0,
"finish_reason": "tool_calls",
"message": {
"role": "assistant",
"content": null,
"tool_calls": [
{
"id": "call_mock_1",
"type": "function",
"function": { "name": "get_weather", "arguments": "{\"city\":\"\"}" }
}
]
}
}
],
"usage": { "prompt_tokens": 73, "completion_tokens": 8, "total_tokens": 81 }
}The mock is available once your account has been topped up.
Responses
Tools are flat (name and parameters sit next to type). The model's call arrives as a function_call item in output; send the result back as a function_call_output item with the same call_id:
curl https://api.ohmygpt.com/v1/responses \
-H "Authorization: Bearer $OHMYGPT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "ohmygpt/mock",
"input": [
{"role": "user", "content": "What is the weather in Paris?"},
{"type": "function_call", "call_id": "call_mock_1", "name": "get_weather", "arguments": "{\"city\":\"Paris\"}"},
{"type": "function_call_output", "call_id": "call_mock_1", "output": "{\"temperature_c\":18,\"condition\":\"cloudy\"}"}
],
"tools": [{
"type": "function",
"name": "get_weather",
"description": "Get the current weather for a city.",
"parameters": {"type": "object", "properties": {"city": {"type": "string"}}, "required": ["city"]}
}]
}'The first turn's output item looks like this:
{ "type": "function_call", "id": "fc_0", "call_id": "call_mock_1", "name": "get_weather", "arguments": "{\"city\":\"\"}", "status": "completed" }Anthropic Messages
Tools use input_schema. The model's call is a tool_use content block with stop_reason: "tool_use"; send the result in the next user message as a tool_result block with the same ID:
curl https://api.ohmygpt.com/v1/messages \
-H "x-api-key: $OHMYGPT_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "Content-Type: application/json" \
-d '{
"model": "ohmygpt/mock",
"max_tokens": 1024,
"messages": [
{"role": "user", "content": "What is the weather in Paris?"},
{"role": "assistant", "content": [{"type": "tool_use", "id": "call_mock_1", "name": "get_weather", "input": {"city": "Paris"}}]},
{"role": "user", "content": [{"type": "tool_result", "tool_use_id": "call_mock_1", "content": "{\"temperature_c\":18,\"condition\":\"cloudy\"}"}]}
],
"tools": [{
"name": "get_weather",
"description": "Get the current weather for a city.",
"input_schema": {"type": "object", "properties": {"city": {"type": "string"}}, "required": ["city"]}
}]
}'The first turn's content block looks like this:
{ "type": "tool_use", "id": "call_mock_1", "name": "get_weather", "input": { "city": "" } }Streaming
With "stream": true, the call arrives in pieces: the name first, then the arguments as string fragments (tool_calls[].function.arguments deltas on Chat Completions, response.function_call_arguments.delta events on Responses, input_json_delta events on Messages). Concatenate the fragments and parse the JSON once the stream ends.