Tool calling
Define a function, validate arguments, and return its result.
A tool-capable model can request that your application run a function. SimpleLLM returns the model's request; your application validates it, executes the permitted action, and returns the result.
The request/result loop
- Describe the available functions in
toolsand send the user's message. - Inspect the assistant's
tool_calls. - Validate the function name and arguments against your allowlist and schema.
- Append the assistant message and one
toolresult for each call. - Send the updated messages so the model can answer using the result.
Complete Python example
This example uses a read-only local demo function, with a maximum of four model rounds. Install openai, set SIMPLELLM_API_KEY, and set SIMPLELLM_MODEL to your validated tool-capable model.
import json
import os
from openai import OpenAI
client = OpenAI(
base_url="https://api.simplellm.eu/v1",
api_key=os.environ["SIMPLELLM_API_KEY"],
max_retries=0,
)
tools = [{
"type": "function",
"function": {
"name": "lookup_stock",
"description": "Read demo stock for one product SKU.",
"parameters": {
"type": "object",
"properties": {"sku": {"type": "string"}},
"required": ["sku"],
"additionalProperties": False,
},
},
}]
messages = [{"role": "user", "content": "How many DEMO-1 items are in stock?"}]
for _ in range(4):
response = client.chat.completions.create(
model=os.environ["SIMPLELLM_MODEL"],
messages=messages,
tools=tools,
tool_choice="auto",
max_tokens=2048,
)
answer = response.choices[0].message
if not answer.tool_calls:
print(answer.content or "")
break
messages.append(answer.model_dump(exclude_none=True))
for call in answer.tool_calls:
try:
if call.function.name != "lookup_stock":
raise ValueError("Unknown function")
args = json.loads(call.function.arguments)
if not isinstance(args, dict) or set(args) != {"sku"}:
raise ValueError("Expected only sku")
if not isinstance(args["sku"], str) or len(args["sku"]) > 64:
raise ValueError("Invalid sku")
result = {"sku": args["sku"], "stock": {"DEMO-1": 12}.get(args["sku"], 0)}
except (ValueError, TypeError) as error:
result = {"error": str(error)}
messages.append({
"role": "tool",
"tool_call_id": call.id,
"content": json.dumps(result),
})
else:
raise RuntimeError("Tool round limit reached")Streamed tool calls
With streaming, the tool name and JSON arguments may arrive in separate deltas. Accumulate calls by their choice and tool-call index; concatenate argument fragments and parse the JSON only once the call is complete.
Do not execute an incomplete argument fragment or assume each event contains a complete function call. Multiple tool calls can occur in one assistant response.
Safety and reliability
Treat generated tool arguments as untrusted input. Never dispatch a model-supplied name directly to arbitrary code, SQL, or a shell.
Require user approval for destructive or externally visible operations where appropriate. Use your own action IDs and idempotency rules so retries cannot repeat a payment, email, or deletion.
Limit tool rounds, execution time, and response size. Tool results become new model input and can contribute to both context size and cost.
Reference
See Chat completions API for fields and streaming for event handling.