Guide · Tool calling and streaming
Tool calling and streaming on Synap
-
Get a free key
Sign in with Google at synap.linkrra.com — no card. Your key starts with
sk-pool-. -
Send the tools array
Identical to OpenAI. The model answers with
finish_reason: "tool_calls"and the arguments as a JSON string.tools = [{"type": "function", "function": { "name": "get_weather", "description": "Current weather for a city", "parameters": {"type": "object", "properties": {"city": {"type": "string"}}, "required": ["city"]}, }}] r = client.chat.completions.create( model="qwen3-coder-30b", messages=messages, tools=tools, ) call = r.choices[0].message.tool_calls[0] -
Feed the result back
Append the assistant message, then a
toolmessage carrying the sametool_call_id, and ask again.messages += [ r.choices[0].message, {"role": "tool", "tool_call_id": call.id, "content": json.dumps({"city": "Tel Aviv", "c": 24, "sky": "clear"})}, ] r2 = client.chat.completions.create( model="qwen3-coder-30b", messages=messages, tools=tools, ) print(r2.choices[0].message.content) -
Stream, and guard the last chunk
The final chunk carries
usageand an emptychoicesarray. Skip it, or you will index an empty list.stream = client.chat.completions.create( model="qwen3-coder-30b", messages=messages, stream=True, ) for chunk in stream: if not chunk.choices: # last chunk carries usage, not text continue print(chunk.choices[0].delta.content or "", end="")const stream = await client.chat.completions.create({ model: "qwen3-coder-30b", messages, stream: true, }); for await (const chunk of stream) { const delta = chunk.choices?.[0]?.delta?.content; // last chunk has no choices if (delta) process.stdout.write(delta); }
Notes, prices and limits
The standard OpenAI tools and stream fields. Recommended model: qwen3-coder-30b —
$0.0675 in and $0.27 out
per 1M tokens, 98,304-token context.
Run live against this API with openai 2.48.0 and openai 7.47.0: the full round trip (tools → finish_reason: "tool_calls" → a role: "tool" message → the final answer), then a streamed completion.
The last chunk is the one thing that differs from a naive loop: it carries usage and an empty choices array, on every stream, with or without stream_options. The unguarded Python loop raises IndexError: list index out of range — measured, not assumed.
arguments arrives as a JSON string, not an object. Parse it, and expect the model to get a field wrong occasionally — validate before you act on it.
JSON mode is declared on three of the six models we run. Check /v1/models before relying on response_format.
On the free tier: every request counts input and output against 100,000 free tokens a day, and answers stop at 4,096 output tokens. Coding agents send long prompts, so the free tier covers a short session; credits cover the rest.
Field names from the OpenAI function-calling guide, run against this API, checked 2026-11-03 20:20 UTC. Prices refresh from /v1/models when this page loads.