Function calling

Send OpenAI tools and get one tool call back: classification picks the tool, extraction fills the arguments.

Send tools on POST /v1/chat/completions and decision-machine-1 returns one tool call. No API key is required during the launch period.

The model does not reason about your tools. It runs two typed decisions:

1

Pick the tool

Each tool becomes one label: its function.name with its function.description. Classification over those labels picks the winning name.

2

Fill the arguments

Extraction then runs the chosen tool’s function.parameters schema over the same text. Each property becomes a field to find.

Both steps read the user text only. The API joins user messages and discards system, developer, assistant and tool roles.

What comes back

The response is an OpenAI chat completion with finish_reason: "tool_calls" and content: null. The API returns exactly one tool call, ever. Ids are call_ plus 24 hex characters.

curl -s https://api.milliseconds.ai/v1/chat/completions \
-H 'content-type: application/json' \
-d '{
"model": "decision-machine-1",
"messages": [
{"role": "user", "content": "Hi, I was charged twice for my May invoice. Order #A-4471. Please refund the duplicate."}
],
"tools": [
{"type": "function", "function": {
"name": "issue_refund",
"description": "The customer asks for money back for a duplicate or wrong charge.",
"parameters": {"type": "object", "properties": {
"order_id": {"type": "string", "description": "The order or invoice reference"},
"reason": {"type": "string", "description": "Why the customer wants a refund"}
}}
}},
{"type": "function", "function": {
"name": "track_shipment",
"description": "The customer asks where a package is or when it arrives.",
"parameters": {"type": "object", "properties": {"order_id": {"type": "string"}}}
}},
{"type": "function", "function": {
"name": "reset_password",
"description": "The customer cannot sign in and needs a password reset.",
"parameters": {"type": "object", "properties": {"email": {"type": "string"}}}
}}
]
}'

arguments is a JSON string, as in the OpenAI API. Parse it before you use it. A property with nothing to find in the text comes back null, like reason above. Handle nulls in your tool handler.

The OpenAI SDKs

Point the official OpenAI client at https://api.milliseconds.ai/v1. Any api_key string works.

import json
from openai import OpenAI
client = OpenAI(base_url="https://api.milliseconds.ai/v1", api_key="not-needed")
tools = [
{"type": "function", "function": {
"name": "issue_refund",
"description": "The customer asks for money back for a duplicate or wrong charge.",
"parameters": {"type": "object", "properties": {
"order_id": {"type": "string", "description": "The order or invoice reference"},
"reason": {"type": "string", "description": "Why the customer wants a refund"},
}},
}},
{"type": "function", "function": {
"name": "track_shipment",
"description": "The customer asks where a package is or when it arrives.",
"parameters": {"type": "object", "properties": {"order_id": {"type": "string"}}},
}},
]
resp = client.chat.completions.create(
model="decision-machine-1",
messages=[{"role": "user", "content": "Charged twice for order A-4471, refund please."}],
tools=tools,
)
call = resp.choices[0].message.tool_calls[0]
print(call.function.name, json.loads(call.function.arguments))

tool_choice rules

tool_choiceBehaviour
absent, "auto", "required"The same path. With one tool, the API uses it and runs no classification. With two or more, classification over the tool labels picks the name.
{"type":"function","function":{"name":"…"}}The API forces that tool and runs no classification. An unknown name returns 400 invalid_request.
"none"The API skips the tools path. The request falls through to response_format, or returns 400 unsupported_request.

auto and required behave the same way, because the model always returns a tool call on this path. A classification result that names no known tool falls back to tools[0].

tool_choice: "none" without a response_format.json_schema returns 400:

{"error":{"code":"unsupported_request","message":"decision-machine-1 decides, it does not chat. Send response_format.json_schema for structured extraction, tools for function calling, or use the capability endpoints under /v1/decision-machine-1/."}}

Forcing one tool

A named tool_choice skips the pick and runs extraction only. It overrides what the text is about.

curl -s https://api.milliseconds.ai/v1/chat/completions \
-H 'content-type: application/json' \
-d '{
"model": "decision-machine-1",
"messages": [{"role": "user", "content": "Weather in Paris"}],
"tools": [
{"type": "function", "function": {"name": "get_weather",
"parameters": {"type": "object", "properties": {"city": {"type": "string"}}}}},
{"type": "function", "function": {"name": "book_hotel",
"parameters": {"type": "object", "properties": {"city": {"type": "string"}}}}}
],
"tool_choice": {"type": "function", "function": {"name": "book_hotel"}}
}'

Tools with no parameters

A tool without parameters, or with no properties, makes no extraction call. arguments is the literal "{}". This call took 0.07 s, because no model ran.

Response
{"id":"chatcmpl-1083cb565f6345dd98ade359","object":"chat.completion","created":1789606543,"model":"decision-machine-1","choices":[{"index":0,"message":{"role":"assistant","content":null,"tool_calls":[{"id":"call_69971c1a13854e7aaeedb156","type":"function","function":{"name":"do_it","arguments":"{}"}}]},"finish_reason":"tool_calls"}],"usage":{"prompt_tokens":1,"completion_tokens":1,"total_tokens":2}}

Write descriptions that describe the case

The tool pick is a classification over your descriptions. A description of the tool’s mechanics classifies badly. Describe the user situation that should trigger the tool.

WeakStrong
"Refund tool""The customer asks for money back for a duplicate or wrong charge."
"Shipping API wrapper""The customer asks where a package is or when it arrives."
"Auth helper""The customer cannot sign in and needs a password reset."

The same rule applies to parameters: give each property a description that says what to look for in the text. The writing rules for statements and labels apply here without change.

Limits of this surface

tools wins over response_format when you send both. The tools path runs first.

  • One tool call per response. Parallel tool calls do not exist.
  • No probability and no confidence. The classification scores stay inside the facade.
  • No conversation. The API discards a follow-up tool message, like every non-user role.
  • The parameters schema goes through the same extraction engine, so the schema support matrix applies.
  • usage.prompt_tokens reports the request’s input tokens. See Pricing.

Measured latency: 1.16 s for two tools (one classification call plus one extraction call), 0.43 s for a forced tool. Fewer tools and a forced tool_choice both cut a call.

Call classify and extract directly when you want the label probabilities, the scores for every tool, or a batch of texts in one request. The migration table maps each chat usage to a capability.

Next