vennaVenna

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

On this page