Skip to content
View as Markdown

OpenAI SDK

Kiln serves the OpenAI Chat Completions API, so the official OpenAI clients reach it with no adapter in between. Three values change: the base URL, the key and the model ID.

Install

bash
pip install openai

Connect

python
import os
from openai import OpenAI

client = OpenAI(
    base_url="https://api.bricksum.com/v1",
    api_key=os.environ["KILN_API_KEY"],  # sk-bk-...
)

resp = client.chat.completions.create(
    model="qwen3-32b",
    messages=[{"role": "user", "content": "Write a haiku about kilns."}],
    max_tokens=800,
)
print(resp.choices[0].message.content)

Tool calling

One round trip with tool_choice "auto": the model asks for the tool, your code runs it, and the result goes back as a tool message. Every tool needs a description — the model server rejects a tool without one with 400.

python
import json, os
from openai import OpenAI

client = OpenAI(base_url="https://api.bricksum.com/v1", api_key=os.environ["KILN_API_KEY"])
tools = [{"type": "function", "function": {
    "name": "get_weather",
    "description": "Current weather for a city",  # required
    "parameters": {"type": "object", "properties": {"city": {"type": "string"}}, "required": ["city"]},
}}]
messages = [{"role": "user", "content": "What's the weather in Seoul?"}]
first = client.chat.completions.create(model="qwen3-32b", messages=messages,
                                       tools=tools, tool_choice="auto", max_tokens=800)
reply = first.choices[0].message
# The model may answer without a tool call, and one reply can carry several.
if reply.tool_calls:
    messages.append(reply)
    for call in reply.tool_calls:
        messages.append({"role": "tool", "tool_call_id": call.id, "content": json.dumps({"temp_c": 27})})
    final = client.chat.completions.create(model="qwen3-32b", messages=messages, max_tokens=800)
    print(final.choices[0].message.content)
else:
    print(reply.content)

What differs on Kiln

  • usage.cost is not in the OpenAI schema Kiln adds cost, in USD, to the usage object of every response. The OpenAI clients type usage from the OpenAI schema, which has no such field, so TypeScript rejects resp.usage.cost until the type is widened; the value is on the wire either way.
  • Reasoning tokens count against max_tokens The model reasons before it answers and those tokens are billed and counted inside max_tokens. A small budget returns finish_reason "length" with an empty message, so leave room for both — the example sends 800, a starting value not measured on this model — if content comes back empty with finish_reason "length", raise it.

The base URL, authentication, the request parameters every endpoint shares, the response envelope, streaming and the error codes are the same whichever SDK you use. They are documented once on the API Reference. The calls above land on Chat completions. The model ID has to be one the API is serving right now; how to list those, and what to do when an ID is not among them, is on Models. What this model does not support, whatever SDK sends it, is listed under Migrating from OpenAI.

Note
Snippets on this page were run against the Kiln API on 2026-09-21 with openai 3.16.2 (Python), openai 7.20.0 (Node), openai-go v3.64.0. Not verified: That run was on gpt-oss-120b; the page now names qwen3-32b, which it has not yet been re-run against. The tool-calling tabs' loop over every tool call was added after that run and has not been run against the API.