Tool Calling (Function Calling)
Tool calling (also known as function calling) lets you give the model the ability to make structured requests to functions you define, instead of guessing on its own. Based on the user’s request, the model decides which tool makes sense and returns the arguments as well-formed JSON. Your code executes the tool and passes the result back to the model, which then formulates the final answer.
Typical use cases include calculators, database lookups, weather APIs, internal microservices, or search systems. The model “knows” nothing about the concrete implementation. It only knows the name, description, and parameter schema.
Defining a Tool Schema
Section titled “Defining a Tool Schema”Tools are passed as a list in the tools field of the request. Each tool has the type "function" and contains a nested function object with the following fields:
| Field | Meaning |
|---|---|
name | Function name the model uses when calling it. |
description | Plain-text description of when and why the model should use this tool. |
parameters | JSON Schema (Draft 07) of the expected arguments: types, properties, required list. |
The more precisely description and parameters are formulated, the less often the model picks the wrong tool or passes invalid arguments.
Example: Calculator
Section titled “Example: Calculator”We define a single tool, calculate, that supports the four basic arithmetic operations. The schema describes an operator parameter with enum values plus two numeric operands.
curl -X POST https://ai.noris.de/v1/chat/completions \ -H "Content-Type: application/json" \ -H "Authorization: Bearer YOUR_API_KEY" \ -d '{ "model": "vllm/release/gpt-oss-120b", "messages": [ {"role": "user", "content": "What is 17 times 24?"} ], "tools": [ { "type": "function", "function": { "name": "calculate", "description": "Performs a basic arithmetic operation.", "parameters": { "type": "object", "properties": { "operator": { "type": "string", "enum": ["add", "subtract", "multiply", "divide"], "description": "The operation to perform." }, "operand_a": {"type": "number", "description": "First operand."}, "operand_b": {"type": "number", "description": "Second operand."} }, "required": ["operator", "operand_a", "operand_b"] } } } ] }'If the model determines that a tool call makes sense, it responds not with text but with tool_calls. The response then looks roughly like this:
{ "choices": [ { "index": 0, "message": { "role": "assistant", "content": null, "tool_calls": [ { "id": "call_abc123", "type": "function", "function": { "name": "calculate", "arguments": "{\"operator\":\"multiply\",\"operand_a\":17,\"operand_b\":24}" } } ] }, "finish_reason": "tool_calls" } ]}Watch for finish_reason: "tool_calls". This signals that the model is waiting for the tool to be executed. The arguments are a JSON-encoded string in function.arguments and must be parsed before execution.
Executing the Tool and Returning the Result
Section titled “Executing the Tool and Returning the Result”Now your code comes into play: parse the arguments, execute the function locally, and send the result back as a new message with the role "tool". Also append the original assistant message including tool_calls to the history, so the model knows what it previously requested.
curl -X POST https://ai.noris.de/v1/chat/completions \ -H "Content-Type: application/json" \ -H "Authorization: Bearer YOUR_API_KEY" \ -d '{ "model": "vllm/release/gpt-oss-120b", "messages": [ {"role": "user", "content": "What is 17 times 24?"}, {"role": "assistant", "content": null, "tool_calls": [ {"id": "call_abc123", "type": "function", "function": {"name": "calculate", "arguments": "{\"operator\":\"multiply\",\"operand_a\":17,\"operand_b\":24}"}} ]}, {"role": "tool", "tool_call_id": "call_abc123", "content": "408"} ], "tools": [ { "type": "function", "function": { "name": "calculate", "description": "Performs a basic arithmetic operation.", "parameters": { "type": "object", "properties": { "operator": {"type": "string", "enum": ["add","subtract","multiply","divide"]}, "operand_a": {"type": "number"}, "operand_b": {"type": "number"} }, "required": ["operator","operand_a","operand_b"] } } } ] }'The model uses the result to formulate the final natural-language answer, for example: “17 times 24 is 408.”
Python
Section titled “Python”The entire cycle can be expressed compactly with the openai SDK:
import jsonfrom openai import OpenAI
client = OpenAI(base_url="https://ai.noris.de/v1", api_key="YOUR_API_KEY")
tools = [{ "type": "function", "function": { "name": "calculate", "description": "Performs a basic arithmetic operation.", "parameters": { "type": "object", "properties": { "operator": {"type": "string", "enum": ["add", "subtract", "multiply", "divide"]}, "operand_a": {"type": "number"}, "operand_b": {"type": "number"} }, "required": ["operator", "operand_a", "operand_b"] } }}]
messages = [{"role": "user", "content": "What is 17 times 24?"}]
response = client.chat.completions.create( model="vllm/release/gpt-oss-120b", messages=messages, tools=tools)
assistant_msg = response.choices[0].messagemessages.append(assistant_msg)
for tool_call in assistant_msg.tool_calls: args = json.loads(tool_call.function.arguments) ops = {"add": lambda a, b: a + b, "subtract": lambda a, b: a - b, "multiply": lambda a, b: a * b, "divide": lambda a, b: a / b} result = ops[args["operator"]](args["operand_a"], args["operand_b"]) messages.append({ "role": "tool", "tool_call_id": tool_call.id, "content": str(result) })
final = client.chat.completions.create( model="vllm/release/gpt-oss-120b", messages=messages, tools=tools)print(final.choices[0].message.content)Advanced Integrations (MCP)
Section titled “Advanced Integrations (MCP)”For anything beyond individual functions, the Model Context Protocol (MCP) is the way to go. A separate server provides a collection of tools that the LLM client can discover and use dynamically. MCP is particularly well suited to complex agent setups with many tools, since it eliminates the need to manually maintain long tools lists.
