Function calling
Let a model call your own functions through the OpenAI-compatible tools API on /v1/chat/completions.
The gateway speaks the OpenAI tools contract — you describe functions with a JSON Schema,
the model decides when to call them, and you execute the call and hand the result back. The
model never runs your code; it only asks for it, by name, with arguments.
Same loop, one extra hop
A tool call is not a separate endpoint. It's still /v1/chat/completions — the difference
is the model's turn ends with a request to call a function instead of a final answer, so you
loop the same endpoint one more time with the result appended.
Define your tools
Pass a tools array on the request — each entry is { type: "function", function: { name, description, parameters } }. parameters is a JSON Schema object describing the arguments;
name is the only required field on function, but always include description so the
model knows when to reach for it.
tool_choice controls whether the model can skip tools:
Prop
Type
import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://gateway.venna.net/v1",
apiKey: process.env.VENNA_API_KEY,
});
const tools = [
{
type: "function" as const,
function: {
name: "get_weather",
description: "Get the current weather for a city",
parameters: {
type: "object",
properties: {
city: { type: "string", description: "City name, e.g. Lisbon" },
unit: { type: "string", enum: ["celsius", "fahrenheit"] },
},
required: ["city"],
},
},
},
];
const completion = await client.chat.completions.create({
model: "venna-fast",
messages: [{ role: "user", content: "What's the weather in Lisbon?" }],
tools,
tool_choice: "auto",
});import os
from openai import OpenAI
client = OpenAI(
base_url="https://gateway.venna.net/v1",
api_key=os.environ["VENNA_API_KEY"],
)
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. Lisbon"},
"unit": {"type": "string", "enum": ["celsius", "fahrenheit"]},
},
"required": ["city"],
},
},
}
]
completion = client.chat.completions.create(
model="venna-fast",
messages=[{"role": "user", "content": "What's the weather in Lisbon?"}],
tools=tools,
tool_choice="auto",
)curl https://gateway.venna.net/v1/chat/completions \
-H "Authorization: Bearer $VENNA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "venna-fast",
"messages": [
{ "role": "user", "content": "What'\''s the weather in Lisbon?" }
],
"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. Lisbon" },
"unit": { "type": "string", "enum": ["celsius", "fahrenheit"] }
},
"required": ["city"]
}
}
}
],
"tool_choice": "auto"
}'Read the tool call off the response
When the model wants to call a function, choices[0].finish_reason is "tool_calls" and
choices[0].message.tool_calls holds one entry per call — an id, type: "function", and
function: { name, arguments }. arguments is a JSON string, not a parsed object — the
model composed it to match your parameters schema, but you still parse it yourself.
const message = completion.choices[0]?.message;
for (const call of message?.tool_calls ?? []) {
console.log(call.id, call.function.name, call.function.arguments);
// e.g. "call_abc123" "get_weather" '{"city":"Lisbon","unit":"celsius"}'
}Parse defensively
arguments is model-generated JSON. Wrap JSON.parse in a try/catch (or use a schema
validator) — a malformed or partial payload should fail your function call, not your
process.
Execute the call and send the result back
Append the assistant's message (it carries the tool_calls the model made) to your
messages array, run each function locally, then append one { role: "tool", tool_call_id, content } message per call — tool_call_id must match the id from the tool call it
answers. Re-request /v1/chat/completions with the extended history and the model finishes
with a normal text answer.
import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://gateway.venna.net/v1",
apiKey: process.env.VENNA_API_KEY,
});
function getWeather(city: string, unit = "celsius") {
// Your own implementation — an API call, a DB lookup, whatever backs the function.
return { city, unit, temperature: 21, conditions: "clear" };
}
const tools = [
{
type: "function" as const,
function: {
name: "get_weather",
description: "Get the current weather for a city",
parameters: {
type: "object",
properties: {
city: { type: "string" },
unit: { type: "string", enum: ["celsius", "fahrenheit"] },
},
required: ["city"],
},
},
},
];
const messages: OpenAI.ChatCompletionMessageParam[] = [
{ role: "user", content: "What's the weather in Lisbon?" },
];
const first = await client.chat.completions.create({
model: "venna-fast",
messages,
tools,
tool_choice: "auto",
});
const assistantMessage = first.choices[0]?.message;
messages.push(assistantMessage);
for (const call of assistantMessage?.tool_calls ?? []) {
const args = JSON.parse(call.function.arguments) as { city: string; unit?: string };
const result = getWeather(args.city, args.unit);
messages.push({
role: "tool",
tool_call_id: call.id,
content: JSON.stringify(result),
});
}
const final = await client.chat.completions.create({
model: "venna-fast",
messages,
tools,
});
console.log(final.choices[0]?.message.content);
// "It's clear and 21°C in Lisbon right now."parallel_tool_calls (default on) lets the model request several calls in one turn — loop
over every entry in tool_calls and append a tool message for each before re-requesting;
the second request fails if any call is left unanswered.
Builtin tools are opt-in
The gateway also ships server-executed builtin tools — { type: "web_search" } and
{ type: "web_fetch" } — that run on the gateway itself instead of round-tripping to your
code. They share the same tools array and tool_calls shape, but you must list them
explicitly; the gateway never adds them on its own. This guide covers user-defined
function tools, the common case.
Next steps
- Text generation — chat completions and the Responses API in depth.
- Quickstart — get a
vk_key and make your first call. - API reference: Chat — full
tools/tool_choice/tool_callsrequest and response schemas.