Skip to content

DocsUse it from

5 min read · updated

Python

Call a server from Python with the standard library, or with TypeSafe's SDK.

A server is a JSON API, so Python needs nothing but its standard library. TypeSafe’s SDK, which speaks the same wire format, works too once it is pointed at the server.

Figure · A Python client

One POST out, one JSON answer back; the rest is reading it.

The numbers are the recorded decision's; a server on another base answers with its own.

Recorded decision: Gemma 4 31B, decisio 0.9.0, RTX PRO 6000 at 600 W, 2026-10-08; record 1 of 60 of decisio-examples' support-routing run; the probabilities are rounded to two places.

With a server running (Get started starts one), this script sends the recorded support ticket and prints each question’s likeliest option:

import json
import urllib.request
body = {
"state": "I was charged twice for October. Both charges are 49.00 EUR, same day. Please refund one.",
"questions": {
"urgent": {
"type": "noul",
"instructions": "Does this ticket need a response within the hour?"
},
"queue": {
"type": "choice",
"instructions": "Which team should handle this ticket?",
"criteria": {
"billing": "Invoices, payments, refunds, prices, VAT",
"access": "Login, passwords, two-factor, SSO, permissions, invitations, account security",
"bug": "The product behaves wrongly: errors, crashes, wrong numbers, lost data",
"feature": "A request for something the product does not do yet",
"cancellation": "Cancelling, pausing or closing an account, deleting data",
"other": "Press, partners, jobs, sales pitches, wrong address, thanks, general questions"
}
},
"impact": {
"type": "score",
"instructions": "Rate the business impact using only the reported facts.",
"criteria": [
"No function impaired",
"One user impaired, with a workaround",
"Many users blocked from a core function",
"Data loss or legal exposure"
]
}
}
}
request = urllib.request.Request(
"http://127.0.0.1:8000/v1/systemone",
data=json.dumps(body).encode(),
headers={"Content-Type": "application/json"},
)
with urllib.request.urlopen(request) as response:
answers = json.load(response)["answers"]
for name, answer in answers.items():
if answer["type"] == "noul":
print(name, round(answer["noul"], 3))
else:
top = max(answer["probabilities"], key=answer["probabilities"].get)
print(name, top, round(answer["probabilities"][top], 3))
# urgent 0.159
# queue billing 0.962
# impact 1 0.648

Run against the CPU stand-in at v0.11.1 on 2026-10-14: the answers are the stand-in's, not the measured system's.

The comment lines show what the measured system answered (recorded; the stand-in prints other numbers).

Recorded decision: Gemma 4 31B, decisio 0.9.0, RTX PRO 6000 at 600 W, 2026-10-08; record 1 of 60 of decisio-examples' support-routing run; the printed probabilities are rounded to three places.

WordMeaning
noula yes/no question; its answer is the probability of yes
probabilitiesa probability for each option of a choice, or each level of a score; they add up to 1
the SDKTypeSafe’s Python SDK, published under the MIT licence; Decisio does not include it

The script posts the body with urllib.request and reads answers, which holds one answer per question under the name you gave it. A choice’s answer names its likeliest option (choice) and gives every option’s probability; read the probabilities, not only the name, to see how sure it is.

The SDK is TypeSafe’s and installs on its own (pip install typesafe-sdk); the block below was run with version 0.7.2, against a decisio server. Give it the server’s address, a key (it requires one; the server ignores it) and the served model’s name:

from typesafe_sdk import TypeSafeClient
client = TypeSafeClient(
base_url="http://127.0.0.1:8000",
# the SDK requires one; the server ignores it
api_key="unused",
# the name GET /v1/models lists
model="decisio-gemma-4-31b-it-letters",
)
body = {
"state": "I was charged twice for October. Both charges are 49.00 EUR, same day. Please refund one.",
"questions": {
"urgent": {
"type": "noul",
"instructions": "Does this ticket need a response within the hour?"
},
"queue": {
"type": "choice",
"instructions": "Which team should handle this ticket?",
"criteria": {
"billing": "Invoices, payments, refunds, prices, VAT",
"access": "Login, passwords, two-factor, SSO, permissions, invitations, account security",
"bug": "The product behaves wrongly: errors, crashes, wrong numbers, lost data",
"feature": "A request for something the product does not do yet",
"cancellation": "Cancelling, pausing or closing an account, deleting data",
"other": "Press, partners, jobs, sales pitches, wrong address, thanks, general questions"
}
},
"impact": {
"type": "score",
"instructions": "Rate the business impact using only the reported facts.",
"criteria": [
"No function impaired",
"One user impaired, with a workaround",
"Many users blocked from a core function",
"Data loss or legal exposure"
]
}
}
}
response = client.system_one(state=body["state"], questions=body["questions"])
for name, answer in response.nouls.items():
print(name, round(answer.noul, 3))
for name, answer in response.choices.items():
print(name, answer.choice, round(answer.probabilities[answer.choice], 3))
for name, answer in response.scores.items():
top = max(answer.probabilities, key=answer.probabilities.get)
print(name, top, round(answer.probabilities[top], 3))
# The server's name for the model, and the tokens it counted:
print(response.model, response.usage.input_tokens)

Run against the CPU stand-in at v0.11.1 on 2026-10-14: with TypeSafe's Python SDK 0.7.2, the version the page names. Its comments are shown above their commands.

A request that fails validation gets 422 and one the engine cannot answer gets 400, each with a detail naming the problem. With the standard library these are an HTTPError whose body holds the detail; the SDK raises its own types (TypeSafeUnprocessableEntityError, TypeSafeBadRequestError). A server sets no rate limit, so the SDK’s retries on 429 never fire; it still retries 408 and 5xx.

import json
import urllib.error
import urllib.request
request = urllib.request.Request(
"http://127.0.0.1:8000/v1/systemone",
data=json.dumps({"state": "No questions asked.", "questions": {}}).encode(),
headers={"Content-Type": "application/json"},
)
try:
urllib.request.urlopen(request)
except urllib.error.HTTPError as error:
print(error.code, "detail" in json.load(error))
# 422 True

Run against the CPU stand-in at v0.11.1 on 2026-10-14.