Guides
Tool calling
The loop#
- Send
toolswith your request. - If the answer has
finish_reason: "tool_calls", run each call inmessage.tool_calls.function.argumentsis a JSON string. - Send the conversation back with the assistant's message and one
toolmessage per call, matched bytool_call_id. - Repeat until the model answers in text.
import jsonimport osfrom openai import OpenAIclient = OpenAI(base_url="https://fullsendai.com/v1", api_key=os.environ["FULLSEND_API_KEY"])tools = [{ "type": "function", "function": { "name": "get_weather", "description": "Current weather for a city", "parameters": { "type": "object", "properties": {"city": {"type": "string"}}, "required": ["city"], }, },}]def get_weather(city): # your code return {"city": city, "temp_c": 18, "sky": "clear"}messages = [{"role": "user", "content": "What's the weather in Paris?"}]while True: reply = client.chat.completions.create(model="deepseek-v4.1-flash", messages=messages, tools=tools) message = reply.choices[0].message messages.append(message) if not message.tool_calls: break for call in message.tool_calls: result = get_weather(**json.loads(call.function.arguments)) messages.append({"role": "tool", "tool_call_id": call.id, "content": json.dumps(result)})print(message.content){ "choices": [ { "message": { "role": "assistant", "content": null, "tool_calls": [ { "id": "call_34087531920d0de5cb6cc3d1", "type": "function", "function": { "name": "get_weather", "arguments": "{\"city\": \"Paris\"}" } } ] }, "finish_reason": "tool_calls" } ]}Tool call IDs are FullsendAI's own (call_…). Send them back exactly as you got them.
Controlling tool use#
| tool_choice | The model… |
|---|---|
"auto" | decides (the default when tools are sent) |
"required" | must call at least one tool |
"none" | answers in text |
{"type": "function", "function": {"name": "get_weather"}} | must call that tool |
parallel_tool_calls is passed to the model; models that support it can return several calls at once, so always loop over tool_calls.
Streaming tool calls#
With stream: true, calls arrive in pieces in delta.tool_calls. The first piece of each call has its index, id and function.name; later pieces add to function.arguments. Join them by index:
calls = {}for chunk in client.chat.completions.create(model="deepseek-v4.1-flash", messages=messages, tools=tools, stream=True): if not chunk.choices: continue for piece in chunk.choices[0].delta.tool_calls or []: call = calls.setdefault(piece.index, {"id": piece.id, "name": "", "arguments": ""}) call["name"] += piece.function.name or "" call["arguments"] += piece.function.arguments or ""Anthropic API#
Define tools with name, description and input_schema. The model answers with tool_use blocks and stop_reason: "tool_use"; send each result back as a tool_result block in the next user message. tool_choice takes auto, any, none or a named tool. A complete example is on the Anthropic API reference. Anthropic's server tools (web search, code execution) aren't run.
Tips#
- Tool definitions count as input tokens on every request, so keep descriptions short and the list to what the task needs.
- Give
max_tokensroom: a call cut off by the limit can arrive with incomplete or emptyarguments. - Validate arguments before running anything with side effects; the model can send values your schema doesn't allow.
- For one structured answer rather than an action, structured output is simpler than a tool.
Stuck? Email [email protected]System statusChangelog