Function calling (also called tool calling or tool use) is how you give a model access to your code. You describe functions such as "look up an order" or "quote shipping". The model decides which one to call and with what arguments; your code runs it and sends back the result. Every agent, from a store support bot to Claude Code, is built on this loop.
On GetVibe API function calling works through the standard /v1/chat/completions endpoint in the OpenAI format and through /v1/messages in the Anthropic format. Below is a complete loop that we ran on three models from three different makers.
How the loop works
- You send messages plus a list of
tools. Each tool has a name, a description and a JSON Schema for its parameters. - Instead of text, the model returns
tool_calls: which function to call and with which arguments.finish_reasonistool_calls. - Your code runs the functions and appends one message with the role
toolper call, carrying the sametool_call_id. - You send the history again. The model either asks for more calls or answers the user in text.
The model never runs anything itself. It only asks for a call. What actually runs, and with which permissions, is decided by your code.
A complete example: a store support assistant
Two functions: order status and a shipping quote. The user asks about both at once, and the model calls both in a single turn.
import json
import os
from openai import OpenAI
client = OpenAI(base_url="https://getvibeapi.com/v1", api_key=os.environ["GETVIBE_API_KEY"])
MODEL = "gpt-6-luna"
def get_order_status(order_id: str) -> dict:
# your database or CRM lookup goes here
return {"order_id": order_id, "status": "out for delivery", "eta": "2026-10-24"}
def get_shipping_quote(zip_code: str) -> dict:
return {"zip_code": zip_code, "price_usd": 7.5, "days": 2}
TOOLS = [
{"type": "function", "function": {
"name": "get_order_status",
"description": "Look up an order by its ID",
"parameters": {"type": "object",
"properties": {"order_id": {"type": "string", "description": "Order ID, e.g. A-1042"}},
"required": ["order_id"]}}},
{"type": "function", "function": {
"name": "get_shipping_quote",
"description": "Courier shipping price and delivery time to a US ZIP code",
"parameters": {"type": "object",
"properties": {"zip_code": {"type": "string"}},
"required": ["zip_code"]}}},
]
FUNCTIONS = {"get_order_status": get_order_status, "get_shipping_quote": get_shipping_quote}
messages = [
{"role": "system", "content": "You are a store support assistant. Be brief."},
{"role": "user", "content": "Where is my order A-1042? And how much is shipping to 94110?"},
]
for _ in range(5): # cap the number of rounds
resp = client.chat.completions.create(model=MODEL, messages=messages, tools=TOOLS)
msg = resp.choices[0].message
if not msg.tool_calls:
print(msg.content)
break
messages.append(msg.model_dump(exclude_none=True))
for call in msg.tool_calls:
args = json.loads(call.function.arguments)
result = FUNCTIONS[call.function.name](**args)
messages.append({"role": "tool", "tool_call_id": call.id, "content": json.dumps(result)})
We ran this code unchanged on gpt-6-luna, deepseek-v4.1-flash and claude-haiku-5.5, changing only MODEL. In the first round all three called both functions, get_order_status with A-1042 and get_shipping_quote with 94110. In the second round all three answered in text with the delivery status, the ETA, the 7.50 price and the two-day delivery time.
What matters in this code
- Append the model's tool-call message to the history in full. Without it, your
toolmessage answers a call the model cannot see, and many models simply call the same function again, so the loop goes in circles. - One
toolmessage per call, with the matchingtool_call_id. The model may ask for several calls at once, as it did here. - Arguments arrive as a string.
argumentsis JSON inside a string. Parse it and validate it, because the model can get a value wrong. - Cap the rounds. Without a limit, a bug in a function can turn the loop into an endless one, and into an endless token bill.
tool_choice: when a call is mandatory
By default the model decides whether to call a tool or answer in text ("auto"). tool_choice changes that:
"none"— never call tools;"required"— call at least one tool;{"type": "function", "function": {"name": "..."}}— call this specific function.
The last form is handy for data extraction. The function's parameter schema becomes the shape of the answer:
curl https://getvibeapi.com/v1/chat/completions \
-H "Authorization: Bearer $GETVIBE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "deepseek-v4.1-flash",
"messages": [{"role": "user", "content": "Jane Doe, [email protected], wants a demo on Thursday"}],
"tools": [{
"type": "function",
"function": {
"name": "save_lead",
"description": "Save a sales lead to the CRM",
"parameters": {
"type": "object",
"properties": {"name": {"type": "string"}, "email": {"type": "string"}, "request": {"type": "string"}},
"required": ["name", "email", "request"]
}
}
}],
"tool_choice": {"type": "function", "function": {"name": "save_lead"}}
}'
The response was a save_lead call with name "Jane Doe", email "[email protected]" and request "Wants a demo on Thursday".
One caveat: some Claude models, including Claude Sonnet 5.5 and Claude Opus 5.5, do not accept a forced tool choice. GetVibe turns such a request into auto with the same tools, so you get an answer instead of an error, but the model then decides on its own whether to call. For strict JSON from those models, use structured outputs.
The Anthropic format: /v1/messages
With the Anthropic SDK, tools look slightly different: the parameter schema goes in input_schema, and the call comes back as a tool_use content block.
curl https://getvibeapi.com/v1/messages \
-H "x-api-key: $GETVIBE_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "Content-Type: application/json" \
-d '{
"model": "claude-haiku-5.5",
"max_tokens": 512,
"tools": [{
"name": "get_order_status",
"description": "Look up an order by its ID",
"input_schema": {"type": "object", "properties": {"order_id": {"type": "string"}}, "required": ["order_id"]}
}],
"messages": [{"role": "user", "content": "Where is order A-1042?"}]
}'
The response had stop_reason tool_use and a tool_use block calling get_order_status with order_id A-1042. You send the result back as a tool_result block in the next user message, with the same tool_use_id.
Which model to use for tools
- High-volume bots and simple tools — DeepSeek V4.1 Flash ($0.0112 input · $0.0445 output per 1M tokens) or Claude Haiku 5.5 ($0.0267 input · $0.1334 output per 1M tokens).
- Agents with many tools and long plans — GPT-6 Luna ($0.0055 input · $0.0274 output per 1M tokens) or Claude Sonnet 5.5.
Agent loops resend the same long prefix every round, so prompt caching matters a lot here. More detail on the request fields is in the tool calling docs.
