Guide · Tool calling and streaming

Tool calling and streaming on Synap

4 steps · about 2 minutes

  1. Get a free key

    Sign in with Google at synap.linkrra.com — no card. Your key starts with sk-pool-.

  2. 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]
  3. Feed the result back

    Append the assistant message, then a tool message carrying the same tool_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)
  4. Stream, and guard the last chunk

    The final chunk carries usage and an empty choices array. 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.