Skip to Content

Function calling

The model proposes a tool name and arguments; your application decides whether and how to execute it. The API does not run your local functions. Choose a tool-capable model and complete the quickstart first.

A complete round trip

This Python example uses a harmless local calculator. Install openai and set FENNO_API_KEY and FENNO_MODEL.

import os from openai import OpenAI client = OpenAI( base_url="https://api.fenno.ai/v1", api_key=os.environ["FENNO_API_KEY"], timeout=60.0, max_retries=0, ) import json tools = [{ "type": "function", "function": { "name": "add", "description": "Add two numbers.", "parameters": { "type": "object", "properties": {"a": {"type": "number"}, "b": {"type": "number"}}, "required": ["a", "b"], "additionalProperties": False, }, }, }] messages = [{"role": "user", "content": "Use add to calculate 12 + 30."}] first = client.chat.completions.create( model=os.environ["FENNO_MODEL"], messages=messages, tools=tools, ) message = first.choices[0].message if message.tool_calls: messages.append(message.model_dump(exclude_none=True)) for call in message.tool_calls: if call.function.name != "add": raise ValueError("Tool not allowed") args = json.loads(call.function.arguments) if set(args) != {"a", "b"} or any(type(v) not in (int, float) for v in args.values()): raise ValueError("Invalid arguments") result = {"sum": args["a"] + args["b"]} messages.append({ "role": "tool", "tool_call_id": call.id, "content": json.dumps(result), }) final = client.chat.completions.create( model=os.environ["FENNO_MODEL"], messages=messages, ) print(final.choices[0].message.content) else: print(message.content)

The final request intentionally omits tools so this example ends after one tool round. A production agent can repeat the cycle, but must cap tool rounds, time and cost.

Match the protocol

ProtocolTool definitionModel’s requestApplication’s result
Chat Completionstools[].functionmessage.tool_calls[]A tool message with matching tool_call_id
ResponsesFlat function fields in tools[]output[] item with type: function_callfunction_call_output linked by call_id
Anthropic Messagesname, description, input_schematool_use content blockA user tool_result block with matching tool_use_id

Keep the assistant’s entire tool-call message in history before adding results. Return one result for each call; do not confuse a response ID with a tool-call ID. For Responses, also preserve relevant non-text output items when manually carrying history.

Selection and parallel calls

tool_choice can allow the model to choose, disable tools, or request a particular tool where supported. Its shape differs by protocol. An instruction to call a tool does not grant that tool permission to perform an operation.

Models can request multiple calls. Execute independent read-only calls concurrently only when safe, and associate each result with its own ID. In a stream, collect all argument fragments before validating them.

Execution safeguards

  • Validate JSON and business rules on the server. A schema is not an authorization check.
  • Use an explicit allowlist; never evaluate model-generated code or resolve arbitrary function names.
  • Require confirmation for payments, deletion, deployment or other side effects.
  • Apply timeouts, output-size limits and application-level deduplication to side-effecting operations.
  • Treat tool output as untrusted input too. Do not expose secrets in results sent back to the model.

See streaming, Chat Completions, Responses and Messages.