Skip to content
Resources

Chat completions

client.chat.completions.create — every option, and everything the completion carries.

client.chat.completions.create() sends a conversation to a model and returns its reply. It is OpenAI's method, with the same arguments and the same response shape, and it is what almost everything else here is built around.

from trace_sdk import Trace

client = Trace()

completion = client.chat.completions.create(
    messages=[
        {"role": "system", "content": "You answer in one sentence."},
        {"role": "user", "content": "Why is the sky blue?"},
    ],
    temperature=0.2,
    max_tokens=200,
)

print(completion.choices[0].message.content)

Parameters#

ParameterTypeDefaultMeaning
messageslist[dict]requiredThe conversation so far, oldest first.
modelstrthe roster defaultA model id from the roster.
toolslistNonePython functions or OpenAI tool dicts. See Tool calling.
tool_choicestr or dict"auto" with tools"auto", "none", "required", or a named function.
temperaturefloatprovider default0 to 2. Lower is more predictable.
top_pfloatprovider defaultNucleus sampling. Prefer changing one of temperature or top_p, not both.
max_tokensint512Cap on the reply length. Trace caps this at 4000.
streamboolFalseReturn an iterator of chunks instead. See Streaming.
seedintNoneAsk the provider for a repeatable sample.
stopstr or list[str]NoneStrings that end the reply when generated.

Only the parameters listed above are accepted. Any other keyword returns a 400 invalid_request_error, so upgrade this package to use a parameter Trace adds later.

Parameters left at None are not sent at all — the provider's own default applies, and the request body stays small.

model is optional#

The one place Trace loosens OpenAI's signature. Three ways to choose, most specific first:

client.chat.completions.create(messages=[...], model="openai/gpt-5.6-luna")  # this request
client = Trace(model="openai/gpt-5.6-luna")                                  # every request
client.chat.completions.create(messages=[...])                               # the roster default

The roster default is published, not hidden: client.models.list() marks it with is_default, trace models shows the same list, and every completion reports the model that actually answered on completion.model. It is the cheapest chat model on the roster.

Messages#

Each message is a dict with a role and content.

RoleUse
systemStanding instructions. Put it first.
userWhat the person said.
assistantWhat the model said previously.
toolThe result of a tool call, keyed by tool_call_id. See Tool calling.

The gateway is stateless: it remembers nothing between requests. To hold a conversation, append the assistant's message to your own list and send the whole list again.

messages = [{"role": "user", "content": "My name is Ada."}]

first = client.chat.completions.create(messages=messages)
messages.append(first.choices[0].message.to_dict())

messages.append({"role": "user", "content": "What is my name?"})
second = client.chat.completions.create(messages=messages)
print(second.text)

Append message.to_dict() rather than a message you rebuilt — with tools, the ids that match a call to its result only live in there.

Determinism with seed#

seed is passed to the provider. With the same model, the same messages, the same temperature, and the same seed, providers try to return the same sample. Treat it as a strong hint rather than a guarantee: providers change hardware and kernels, and identical output across weeks is not promised by anyone.

completion = client.chat.completions.create(
    messages=[{"role": "user", "content": "Name one primary colour."}],
    temperature=0,
    seed=7,
)

The completion#

The mirrored surface, identical to OpenAI's:

completion.id
completion.model                              # the model that answered
completion.created
completion.choices[0].message.role            # "assistant"
completion.choices[0].message.content         # the text, or None on a tool turn
completion.choices[0].message.tool_calls      # list[ToolCall]
completion.choices[0].finish_reason           # "stop", "length", "tool_calls", …
completion.usage.prompt_tokens
completion.usage.completion_tokens
completion.usage.total_tokens

finish_reason is worth checking. "length" means the reply was cut off by max_tokens, not that the model finished.

Trace's additions, which sit beside those rather than replacing them:

completion.usage.credits_spent   # added to OpenAI's usage object
completion.text                  # choices[0].message.content, "" instead of None
completion.credits_spent         # usage.credits_spent
completion.request_id            # matches request_id on the ledger rows
completion.run_tools(messages, confirm=approve)   # see Tool calling

completion.text is the same string as the mirrored path, with one difference: a tool-only turn has no text, and .text is "" there where .content is None. That makes printing and concatenating safe without a guard.

Anything the gateway returns that this SDK does not model is still reachable:

completion["object"]      # "chat.completion"
completion.to_dict()      # the whole body as a dict

credits_spent#

The number on the completion is the settled cost, not an estimate. Trace reserves before the call and settles against provider-reported cost before you can read the response. It is the number the curriculum's checkpoints refer to. See Credits and limits.

One choice per request#

Trace returns exactly one choice. Passing n greater than 1 is a 400 with code: "unsupported_parameter". Call twice if you want two samples — you will be charged for two, which is the honest accounting.

Safety#

Message text is redacted server-side before it reaches any provider, and only models on the reviewed child-safe roster can be called. See Authentication.

Errors#

Every failure raises a typed exception. The ones this method produces most often:

  • ModelNotFoundError — the model id is not on the roster; the exception lists the ids that are.
  • InsufficientCreditsError — the balance cannot cover the request.
  • BadRequestErrormax_tokens over the cap, a malformed message, n > 1.
  • RateLimitError — more than 30 requests in a minute from one key.

Full list, with the recovery for each: Errors.