Chapter 3: Getting Started: Your First System One Request
Learning Objectives
Obtain a TypeSafe API key from the console and store it in the TYPESAFE_API_KEY environment variable
Install the typesafe-sdk package with pip or uv on Python 3.10+ and instantiate a TypeSafeClient
Construct a system_one request with a state argument and a questions dictionary containing named Choice, Score, and Noul questions
Read answers, probabilities, confidence, and usage from a SystemOneResponse and translate them into NOC triage decisions
Pre-Quiz: Environment Setup
A Python script that classifies syslog lines runs perfectly when you execute it by hand, but the same script fails immediately when triggered by a systemd timer. What is the most likely cause?
TYPESAFE_API_KEY is exported in your interactive shell but is not inherited by the systemd service's environment
The TypeSafe API key has expired and must be reissued from the console
systemd services cannot make outbound HTTPS requests
The typesafe-sdk package was installed with uv instead of pip
Before writing any triage logic, you want the cheapest possible way to confirm your API key is accepted and the TypeSafe API is reachable. Which check should you run first?
Send a full system_one request with three Choice, Score, and Noul questions
Call GET /v1/models with your API key
Run pip install typesafe-sdk again to force a reinstall
Print the raw value of TYPESAFE_API_KEY to the terminal
A triage script raises TypeSafeAPIConnectionError. Based on the SDK's exception types, what does that tell you about where the problem lies?
The request was malformed, such as an empty questions mapping
The server received the request but returned an unsuccessful response, such as a 401 or 429
The request never successfully reached and returned from the server — likely a proxy, firewall, or DNS issue
The API key has exceeded its published rate limit of 1,200 requests per minute
Environment Setup
Key Points
The SDK reads your key automatically from the TYPESAFE_API_KEY environment variable — you normally never pass it into code.
An interactive shell export is not automatically visible to a systemd service, cron job, or container; check the scheduler's environment first when a working script fails unattended.
typesafe-sdk requires Python 3.10+ and installs with either pip install typesafe-sdk or uv add typesafe-sdk; use a virtual environment.
Prove access in layers: package imports, key is readable, then GET /v1/models (free) before spending tokens on a real request.
The SDK raises three distinct exceptions — TypeSafeError (validation), TypeSafeAPIError (bad server response), TypeSafeAPIConnectionError (connection/timeout) — and each points to a different place to look.
Every example in this book runs through one Python package and one environment variable. The SDK "automatically reads your TYPESAFE_API_KEY environment variable and uses the jev-latest model by default." Treat the key like an SNMPv3 credential or a TACACS+ key: export it for interactive work, keep it out of Git via .gitignore, and store it in a real secret manager (Ansible Vault, Kubernetes Secret, CI encrypted variables) for anything unattended.
export TYPESAFE_API_KEY="ts_live_your_key_here"
echo "${TYPESAFE_API_KEY:0:8}..." # print only a prefix, never the whole key
A detail that bites automation engineers constantly: a variable exported in your interactive shell is not automatically visible to a systemd service, a cron job, or a Docker container. When a script works by hand but fails under the scheduler, check the environment first — the same way you'd check whether a route exists in the VRF the traffic actually lands in.
The SDK requires Python 3.10 or higher and installs with either of the two common package managers. Use a per-project virtual environment — the equivalent of putting each service in its own VRF instead of letting dependencies leak into the global routing table.
Before writing triage logic, prove three things independently: the package imports, the key is readable, and the API answers. The cheapest live check is GET /v1/models — it consumes no input tokens, so it's the network engineer's equivalent of a ping before a traceroute.
The most reliable end-to-end smoke test is the smallest possible real request: one short state, one Noul question. If it returns, your key, network path, Python version, and billing account are all confirmed live in one round trip. Log the returned request_id from this first call on — it is the only identifier that ties your side of a conversation to TypeSafe's.
The SDK raises three distinct exception types: TypeSafeError for validation issues such as empty questions or empty criteria, TypeSafeAPIError for unsuccessful server responses, and TypeSafeAPIConnectionError for connection or timeout failures. Knowing which one you got tells you whether the problem is in your request, on the server, or in the path between them.
Symptom
Likely cause
What to check
KeyError at client construction
TYPESAFE_API_KEY unset in the running process, even if set in your shell
Re-export it; confirm the scheduler, container, or service unit inherits it
Install or import fails
Python older than 3.10
Check python3 --version; rebuild the venv on a supported interpreter
HTTP 401 as TypeSafeAPIError
Key wrong, revoked, or truncated by copy/paste
Re-issue the key; confirm no trailing whitespace or shell quoting mangled it
HTTP 429 as TypeSafeAPIError
Exceeded 250,000 tokens/sec or 1,200 requests/min
Batch more questions per request, back off and retry, or raise your plan's limits
TypeSafeAPIConnectionError
Connection or timeout failure
Proxy, egress firewall, or DNS — then consider raising timeout
TypeSafeError before any network traffic
Empty questions mapping or empty criteria
Every Choice needs at least one label; questions must be non-empty
Key Takeaway: The entire setup is one package and one environment variable: install typesafe-sdk on Python 3.10+, export TYPESAFE_API_KEY, and let TypeSafeClient() pick the key up on its own. Prove access with the smallest possible real request rather than assuming it works, and log the returned request_id from day one.
Figure 3.1: Environment setup sequence, from interpreter check to a verified live request
flowchart TD
A["Check Python version is 3.10 or higher"] --> B["Create a virtual environment"]
B --> C["Install typesafe-sdk with pip or uv"]
C --> D["Set TYPESAFE_API_KEY environment variable"]
D --> E["Verify with GET /v1/models"]
E --> F["Run smallest real request as smoke test"]
Visual animation — coming soon
Post-Quiz: Environment Setup
A Python script that classifies syslog lines runs perfectly when you execute it by hand, but the same script fails immediately when triggered by a systemd timer. What is the most likely cause?
TYPESAFE_API_KEY is exported in your interactive shell but is not inherited by the systemd service's environment
The TypeSafe API key has expired and must be reissued from the console
systemd services cannot make outbound HTTPS requests
The typesafe-sdk package was installed with uv instead of pip
Before writing any triage logic, you want the cheapest possible way to confirm your API key is accepted and the TypeSafe API is reachable. Which check should you run first?
Send a full system_one request with three Choice, Score, and Noul questions
Call GET /v1/models with your API key
Run pip install typesafe-sdk again to force a reinstall
Print the raw value of TYPESAFE_API_KEY to the terminal
A triage script raises TypeSafeAPIConnectionError. Based on the SDK's exception types, what does that tell you about where the problem lies?
The request was malformed, such as an empty questions mapping
The server received the request but returned an unsuccessful response, such as a 401 or 429
The request never successfully reached and returned from the server — likely a proxy, firewall, or DNS issue
The API key has exceeded its published rate limit of 1,200 requests per minute
Pre-Quiz: Anatomy of a Request
Your NOC triage service needs to answer "is this the fourth interface flap on this port in the last ten minutes?" about an event. Why can't a single raw syslog line, by itself, answer that question reliably as the state argument?
state only accepts plain text, never structured data, so flap history cannot be represented
Jev can only answer from what is present in state, and a single line contains no record of prior flaps
Noul questions cannot be asked about anything except a single log line
The model field must be set to jev-preview to reason about historical context
The SDK usage documentation shows Choice(instructions="What is the tone?", criteria={"calm": None, "angry": None}) as a valid form. Why does the chapter recommend writing full descriptions for each label instead, in a production triage pipeline?
Choice questions fail validation and raise TypeSafeError if any criteria value is None
A bare label name like 'routing' means one thing to the engineer and may be ambiguous to the model without a description, the same way an ACL remark clarifies intent that a rule number alone doesn't
Descriptions are required to compute the confidence field on a ChoiceAnswer
Without descriptions, the response will omit the probabilities dictionary for that question
A NOC triage service writes severity and team assignments directly into ServiceNow. Which model-selection approach best matches the change-control discipline the chapter recommends for this production path?
Always use jev-latest everywhere so the service automatically benefits from every model update
Pin the production path to jev-1.13.0 and use jev-latest or jev-preview only in staging or lab environments
Set model to None in every call so the client decides which version to use at random
Use jev-preview in production because it is the newest and most capable build
Anatomy of a Request
Key Points
A System One request has exactly two mandatory parts: state (the evidence) and questions (a non-empty mapping of names to typed question objects).
state accepts text, a JSON object, or an array — feed it in whatever shape your pipeline already produces.
Jev can only answer from what's actually in state; if triage depends on context like flap history, that context must be structured into the state itself.
Choice criteria are a dict (label to description); Score criteria are an ordered list (rubric, low to high).
model is optional and defaults to jev-latest (currently jev-1.13.0); pin a specific version in production for change control.
You give Jev something to look at (state) and a set of named, typed questions about it (questions). Everything else — model selection, timeouts, retries, extra headers — is optional tuning.
Figure 3.2: Anatomy of a System One request and response
state is "text, a JSON object, or an array to evaluate." The best mental model for network engineers is packet classification: state is the packet header plus payload; the questions are the ACL entries and QoS classifiers matched against it. A classifier can only act on fields present in the packet — Jev can only answer from what you put in state. For a single syslog event the raw line is usually enough; when triage depends on context ("is this the fourth flap in ten minutes?"), that context has to be structured into the state as an object, because Jev evaluates what you give it and nothing else. Keep the state tight — you're billed on input tokens at $42 per billion, with output free.
questions is "a non-empty mapping of question names to question objects." The keys are names you invent; the values are Noul (yes/no), Choice (multiple options), or Score (rating on a scale).
Question type
Constructor pattern
Criteria shape
Returns
Noul
Noul(instructions="...")
none
Probability of yes, 0–1
Choice
Choice(instructions="...", criteria={...})
dict: label → description
Selected label, confidence, probabilities per label
Score
Score(instructions="...", criteria=[...])
ordered list of rubric levels
Expected score, confidence, legend, probabilities per integer score
Choice criteria are a dict; Score criteria are an ordered list where position 0 is the bottom of the scale. Descriptions on Choice labels are technically optional (the SDK accepts None per label), but write them anyway for production triage — a label like routing means one thing to you and may be ambiguous to a model inferring intent from six characters. Descriptions are the equivalent of an ACL remark line: optional, but operationally essential. The question-name keys you choose become the keys of the response's answers dictionary, so pick names that will survive contact with downstream systems like ServiceNow.
model is optional and overrides the client default; when None, the call inherits the client's configured model, which defaults to jev-latest.
Name
What it points to
When to use it
jev-latest
jev-1.13.0 — most recent stable release; the SDK default
Default choice for most users
jev-1.13.0
A specific version ID
Pinning, so an update can't silently shift classifications
jev-preview
Currently identical to latest; used for future preview builds
Testing upcoming builds before they become default
For a NOC service writing severity and team assignments into ServiceNow, pin jev-1.13.0 in production while running jev-latest in staging — the same change-control discipline that stops you from pushing a new vendor release to sixty access switches without a deliberate promotion.
Other optional per-call parameters: retry (retry policy), timeout (override in seconds), extra_headers, and extra_body (shallow-merged extra fields). In a syslog pipeline, timeout and retry matter most — a triage service that blocks indefinitely on one call is worse than one that drops the event and logs it.
Key Takeaway: A request is state plus questions. state accepts text, a JSON object, or an array, and questions is a non-empty mapping whose keys become the keys of the response. Everything else, including model, is optional — but pinning jev-1.13.0 in production instead of riding jev-latest is the same change-control instinct you already apply to device firmware.
Post-Quiz: Anatomy of a Request
Your NOC triage service needs to answer "is this the fourth interface flap on this port in the last ten minutes?" about an event. Why can't a single raw syslog line, by itself, answer that question reliably as the state argument?
state only accepts plain text, never structured data, so flap history cannot be represented
Jev can only answer from what is present in state, and a single line contains no record of prior flaps
Noul questions cannot be asked about anything except a single log line
The model field must be set to jev-preview to reason about historical context
The SDK usage documentation shows Choice(instructions="What is the tone?", criteria={"calm": None, "angry": None}) as a valid form. Why does the chapter recommend writing full descriptions for each label instead, in a production triage pipeline?
Choice questions fail validation and raise TypeSafeError if any criteria value is None
A bare label name like 'routing' means one thing to the engineer and may be ambiguous to the model without a description, the same way an ACL remark clarifies intent that a rule number alone doesn't
Descriptions are required to compute the confidence field on a ChoiceAnswer
Without descriptions, the response will omit the probabilities dictionary for that question
A NOC triage service writes severity and team assignments directly into ServiceNow. Which model-selection approach best matches the change-control discipline the chapter recommends for this production path?
Always use jev-latest everywhere so the service automatically benefits from every model update
Pin the production path to jev-1.13.0 and use jev-latest or jev-preview only in staging or lab environments
Set model to None in every call so the client decides which version to use at random
Use jev-preview in production because it is the newest and most capable build
Pre-Quiz: Worked Example: Classifying a Cisco Syslog Line
The example syslog line is stamped %LINEPROTO-5-UPDOWN, and Cisco's own severity digit is 5 (Notice) regardless of whether the affected port is an unused desk drop or a voice uplink. Why does the worked example ask a Score question about operational_severity instead of just reading the device's severity digit?
Score questions are the only question type that can process syslog-formatted text
The device's severity digit is fixed per mnemonic and can't distinguish scope of impact, while a Score question can weigh context like which port and what it carries
Cisco severity digits are unreliable and are frequently transmitted incorrectly by IOS-XE
operational_severity must be a Score because Choice questions cannot accept numeric criteria
In the worked example, subsystem is modeled as a Choice rather than a Noul question. Why is Choice the right fit here?
The answer must be one of a fixed set of NOC teams (interface, routing, system, wireless), which is a labeled-category decision, not a yes/no one
Choice questions are cheaper to run than Noul questions because they return fewer output tokens
Noul questions cannot be combined with Score questions in the same request
subsystem needs a numeric rubric like operational_severity, so it must use the same question type
A Splunk forwarder starts handing your triage service hundreds of syslog events per minute, and single-threaded synchronous calls are now the throughput bottleneck. What does the chapter recommend?
Switch every call to AsyncTypeSafeClient so many requests can be in flight at once, rather than waiting on each round trip before starting the next
Keep using TypeSafeClient but reduce the number of questions per call to speed up each request
Switch to jev-preview, which processes requests faster than jev-latest
Increase the timeout parameter so each synchronous call completes further apart
Worked Example: Classifying a Cisco Syslog Line
Key Points
Cisco IOS-XE syslog follows %FACILITY-SEVERITY-MNEMONIC: Message-text; the severity digit (0–7, lower = worse) is fixed per mnemonic and can't express scope of impact.
Arista, Junos, and Aruba share the same eight-level severity model, so one set of typed questions covers a multi-vendor NOC.
The worked example bundles three questions — a Choice for owning team, a Score for operational severity, and a Noul for whether to page tonight — into a single call.
Write Choice descriptions and Score rubric levels in operational language, and explicitly instruct the model to judge impact rather than parrot the device's own severity digit.
Start with the synchronous TypeSafeClient; move to AsyncTypeSafeClient only when event volume, not curiosity, demands concurrency.
Cisco IOS-XE syslog follows %FACILITY-SEVERITY-MNEMONIC: Message-text. FACILITY names the subsystem (LINEPROTO for line protocol, LINK for physical interface, SYS, OSPF, BGP); the severity digit runs 0–7 (lower = more severe); MNEMONIC is a short uppercase code like UPDOWN.
*Feb 14 09:40:12.418: %LINEPROTO-5-UPDOWN: Line protocol on Interface GigabitEthernet1/0/24, changed state to down
LINEPROTO means a Layer 2 event, not Layer 1 (LINK-3-UPDOWN is the physical-layer counterpart). The 5 is Cisco's own severity, Notice — "normal but significant," typically used for interface state changes. This is exactly where the device's built-in severity stops being enough: Cisco stamps every LINEPROTO-5-UPDOWN as a 5 whether the port is an unused desk drop or a voice uplink. GigabitEthernet1/0/24 is an access-stack port, a strong hint this is an edge port — but the severity digit can't express that. A Score question about operational severity produces a judgment the facility code structurally cannot. The same pattern scales across the NOC: Arista EOS, Junos, and Aruba AOS-CX all implement essentially the same eight severity levels, so one set of typed questions covers the whole multi-vendor estate.
Three questions, one call: subsystem is a Choice (the answer is one of a fixed set of teams), operational_severity is a Score (a graded judgment on an ordered scale), and actionable_tonight is a Noul (paging is yes/no).
triage_questions = {
"subsystem": Choice(
instructions="Which NOC team owns the subsystem this message came from",
criteria={
"interface": "Physical port, line protocol, or link state events",
"routing": "OSPF, BGP, EIGRP adjacency and convergence events",
"system": "Config changes, reloads, process crashes, hw faults",
"wireless": "Access point, WLAN, or controller events",
},
),
"operational_severity": Score(
instructions=(
"How severe this event is for network operations, considering "
"scope of impact rather than the severity digit in the message"
),
criteria=[
"Routine state change on a single edge port, no user impact",
"Localized issue affecting one access port or one user",
"Service-affecting issue on an uplink, trunk, or shared segment",
"Widespread outage affecting a site or many users",
],
),
"actionable_tonight": Noul(
instructions="Needs a human to act during the current shift",
),
}
response = client.system_one(state=syslog_line, questions=triage_questions)
Every Choice label carries a description in the vocabulary of a network engineer, the same signal a runbook gives a new NOC hire. The Score criteria are ordered least to most severe, and the instruction explicitly tells the model to reason about scope of impact rather than copy the 5 out of the message — without that instruction, "severity 5" is a reasonable literal answer, and you'd get what you asked for rather than what you meant. One request, three answers, is also the cost argument: you're billed on input tokens, so bundling questions avoids re-sending the same syslog line three times.
Figure 3.3: The syslog triage call as a request/response sequence
sequenceDiagram
participant Client as Python Client
participant API as TypeSafe API
Client->>API: state plus subsystem, operational_severity, actionable_tonight
API-->>Client: typed answers with confidence and probabilities
Client->>Client: Check confidence threshold
Client->>Client: Route to team or escalate to human
The SDK offers both a synchronous TypeSafeClient and an asynchronous AsyncTypeSafeClient, used with await. Synchronous blocking is right for a cron job, a CLI tool, or any script where simplicity beats throughput. Async lets many requests be in flight at once — what you want when a Splunk forwarder hands you hundreds of events a minute.
The published rate limits are 250,000 tokens/second and 1,200 requests/minute. A synchronous single-threaded loop hits wall-clock latency as its ceiling long before the API's rate limit — that's usually the trigger to move to async. Note: the documentation confirms the class name and await usage but doesn't publish full async lifecycle details (context manager vs. explicit close); if unsure, start synchronous.
Visual animation — coming soon
Key Takeaway: A complete triage call is one raw syslog line as state and three typed questions — a Choice for owning team, a Score for operational severity, a Noul for whether to page — sent in a single request. Write descriptions and rubric levels in operational language, and state explicitly when you want judgment about impact rather than the severity digit already in the message. Start synchronous; move to async only when volume demands it.
Post-Quiz: Worked Example: Classifying a Cisco Syslog Line
The example syslog line is stamped %LINEPROTO-5-UPDOWN, and Cisco's own severity digit is 5 (Notice) regardless of whether the affected port is an unused desk drop or a voice uplink. Why does the worked example ask a Score question about operational_severity instead of just reading the device's severity digit?
Score questions are the only question type that can process syslog-formatted text
The device's severity digit is fixed per mnemonic and can't distinguish scope of impact, while a Score question can weigh context like which port and what it carries
Cisco severity digits are unreliable and are frequently transmitted incorrectly by IOS-XE
operational_severity must be a Score because Choice questions cannot accept numeric criteria
In the worked example, subsystem is modeled as a Choice rather than a Noul question. Why is Choice the right fit here?
The answer must be one of a fixed set of NOC teams (interface, routing, system, wireless), which is a labeled-category decision, not a yes/no one
Choice questions are cheaper to run than Noul questions because they return fewer output tokens
Noul questions cannot be combined with Score questions in the same request
subsystem needs a numeric rubric like operational_severity, so it must use the same question type
A Splunk forwarder starts handing your triage service hundreds of syslog events per minute, and single-threaded synchronous calls are now the throughput bottleneck. What does the chapter recommend?
Switch every call to AsyncTypeSafeClient so many requests can be in flight at once, rather than waiting on each round trip before starting the next
Keep using TypeSafeClient but reduce the number of questions per call to speed up each request
Switch to jev-preview, which processes requests faster than jev-latest
Increase the timeout parameter so each synchronous call completes further apart
Pre-Quiz: Reading the Response
Your code only needs to check whether an event is actionable tonight, and you want to iterate over just the yes/no answers without touching Choice or Score results. Which response attribute fits best?
response.answers, since it contains every answer type together
response.nouls, the typed collection holding only yes/no answers keyed by question name
response.usage, which reports token counts, not answer values
response.raw_http_response, since it exposes every field the API returned
In the worked example, actionable_tonight.noul returns 0.22 and operational_severity.score returns 1.12. What do these values actually represent?
noul is a boolean True/False, and score is always a whole rubric level such as 0, 1, 2, or 3
noul is the probability of a yes answer on a 0-1 scale, and score is an expected value that can land between rubric levels rather than exactly on one
noul and score are both raw token counts consumed by that specific question
noul is the model's confidence in its Choice answer, and score is the rubric legend text
Your NOC triage service currently sends three separate system_one calls, each resending the full syslog line to ask one question at a time. Why does the chapter say this is an expensive mistake, and what should you do instead?
Output tokens are billed per call, so three calls generate three times the output cost; switch to Noul-only questions to reduce output
Only input_tokens are billed, and resending the same state three times triples the only number that drives cost; bundle all three questions into a single call instead
Each separate call resets the request_id counter, corrupting log correlation; switch to the async client to fix it
Score and Choice questions cannot be billed accurately when issued in separate calls, only when combined
Reading the Response
Key Points
answers is keyed by the exact question names you supplied; the typed collections nouls, choices, and scores narrow to one answer kind at a time.
A Noul answer is a probability (0–1), not a boolean; a Score answer is an expected value that can be fractional, not necessarily a whole rubric level.
confidence and probabilities accompany Choice and Score answers — gate automation on confidence rather than acting on every answer identically.
usage.input_tokens is the only number billed, at $42 per billion, with output free; bundling questions into one call is both faster and cheaper than separate calls.
Log input_tokens and request_id together on every call so cost and correlation data exist from day one.
The call returns a SystemOneResponse. answers is "a dictionary of all answer objects keyed by question name" — ask about subsystem, read back answers["subsystem"]. Alongside it, three type-specific collections narrow the view: nouls (yes/no answers), choices, and scores, each keyed by question name. Use answers to iterate uniformly; use the typed collections when code only cares about one kind of answer, such as a paging rule that reads only nouls.
Figure 3.4: Structure of a SystemOneResponse for the syslog triage example
The response also carries metadata: request_id (the x-typesafe-request-id header), model (which model answered), and raw_http_response (an escape hatch for anything the typed fields don't surface).
Field
Present on
What it holds
noul
NoulAnswer
Probability of a yes answer, 0–1
choice
ChoiceAnswer
Selected label, one of your criteria keys
score
ScoreAnswer
Expected score — not necessarily an integer
confidence
ChoiceAnswer, ScoreAnswer
How concentrated the model's belief is
probabilities
ChoiceAnswer, ScoreAnswer
Per-label or per-integer-score distribution
legend
ScoreAnswer
The rubric scale the score is measured against
Two behaviors surprise people coming from traditional APIs. First, a Noul returns a probability, not a boolean — a number you threshold, not a yes you act on. Second, a Score returns an expected score that can be fractional: in the syslog example, 1.12 means the model is mostly on "localized issue affecting one access port" with meaningful weight still on the routine level below it — exactly the right read for a single edge port going down.
subsystem = response.answers["subsystem"]
severity = response.answers["operational_severity"]
actionable = response.answers["actionable_tonight"]
print(f"team: {subsystem.choice} severity: {severity.score:.2f} page: {actionable.noul:.3f}")
# Route to a human when the model is not sure enough to act alone
if subsystem.confidence < 0.80:
print(f"LOW CONFIDENCE — queue for review (request_id={response.request_id})")
else:
print(f"Assigning to team: {subsystem.choice}")
That confidence gate is the single most important habit to form early. An automation that assigns tickets at 94% confidence and escalates at 60% is trustworthy; one that assigns every ticket with equal conviction is not — the first time it silently routes a core routing failure to the wireless queue, your team stops trusting the whole pipeline.
Visual animation — coming soon
usage reports input_tokens and output_tokens, each an integer or None — handle the None case so a missing field doesn't crash a pipeline. Only input_tokens is billed, at $42 per billion, with output free.
usage = response.usage
input_tokens = usage.input_tokens if usage and usage.input_tokens is not None else 0
cost_usd = input_tokens * 42 / 1_000_000_000
print(f"input_tokens={input_tokens} cost=${cost_usd:.8f} request_id={response.request_id}")
A NOC processing a million syslog events a month at roughly 200 input tokens each lands around 200 million input tokens — a little over eight dollars. The expensive mistake isn't per-event cost; it's re-sending the same state three times because you asked three questions in three separate calls, which triples the only number you're billed on. Bundle your questions, and log input_tokens with request_id together on every call.
One honest limitation: Jev is built for fast, typed judgment, not calculation. Don't ask it to total your token spend or do arithmetic on values inside the state — do the math in Python, and let Jev classify, score, and tell you how sure it is.
Key Takeaway:answers is keyed by your question names and yields noul as a probability, choice as a label, and score as an expected value that may be fractional, each accompanied by confidence and probabilities. Gate automation on confidence rather than acting on every answer identically, and log usage.input_tokens with request_id on every call — input tokens are the only thing billed, at $42 per billion, with output free.
Post-Quiz: Reading the Response
Your code only needs to check whether an event is actionable tonight, and you want to iterate over just the yes/no answers without touching Choice or Score results. Which response attribute fits best?
response.answers, since it contains every answer type together
response.nouls, the typed collection holding only yes/no answers keyed by question name
response.usage, which reports token counts, not answer values
response.raw_http_response, since it exposes every field the API returned
In the worked example, actionable_tonight.noul returns 0.22 and operational_severity.score returns 1.12. What do these values actually represent?
noul is a boolean True/False, and score is always a whole rubric level such as 0, 1, 2, or 3
noul is the probability of a yes answer on a 0-1 scale, and score is an expected value that can land between rubric levels rather than exactly on one
noul and score are both raw token counts consumed by that specific question
noul is the model's confidence in its Choice answer, and score is the rubric legend text
Your NOC triage service currently sends three separate system_one calls, each resending the full syslog line to ask one question at a time. Why does the chapter say this is an expensive mistake, and what should you do instead?
Output tokens are billed per call, so three calls generate three times the output cost; switch to Noul-only questions to reduce output
Only input_tokens are billed, and resending the same state three times triples the only number that drives cost; bundle all three questions into a single call instead
Each separate call resets the request_id counter, corrupting log correlation; switch to the async client to fix it
Score and Choice questions cannot be billed accurately when issued in separate calls, only when combined
Key Terms
Term
Definition
TYPESAFE_API_KEY
The environment variable holding your TypeSafe API key. TypeSafeClient reads it automatically at construction.
TypeSafeClient
The synchronous Python client for the TypeSafe API. Reads TYPESAFE_API_KEY from the environment and defaults to jev-latest.
AsyncTypeSafeClient
The asynchronous counterpart to TypeSafeClient, used with await for concurrent request handling.
system_one
The client method that sends a System One request. Takes state and questions plus optional model, retry, timeout, extra_headers, extra_body; returns a SystemOneResponse.
synchronous client
A client whose calls block until the response arrives. Appropriate for scripts, cron jobs, and CLI tools.
state
The content Jev evaluates. Accepts text, a JSON object, or an array.
questions dictionary
A non-empty mapping of question names you choose to Choice, Score, or Noul objects. Keys become the keys of answers.
Choice
Multiple labeled options, constructed with instructions and a criteria dict mapping label to description. Returns a selected label, confidence, and per-label probabilities.
Score
A rating along an ordered scale, constructed with instructions and a criteria list from lowest to highest. Returns an expected score, confidence, legend, and per-integer-score probabilities.
Noul
A yes/no question type, constructed with instructions alone. Returns the probability of yes on a 0–1 scale, not a boolean.
jev-latest
The alias pointing to jev-1.13.0, the default in client SDKs. Alternatives: pinned jev-1.13.0 and forward-looking jev-preview.
SystemOneResponse
The object returned by system_one: answers, typed collections nouls/choices/scores, usage, request_id, model, raw_http_response.
answers
A dictionary of all answer objects keyed by question name.
probabilities
Per-label (Choice) or per-integer-score (Score) probability distribution behind an answer.
confidence
How concentrated the model's belief is in its selected answer; present on ChoiceAnswer and ScoreAnswer.
legend
The rubric legend returned with a ScoreAnswer.
usage
Reports input_tokens and output_tokens, each an integer or None. Only input_tokens is billed.
request_id
The value of the x-typesafe-request-id response header, carried on every response.
POST /v1/systemone
The single API endpoint used by all TypeSafe models, including every Jev version.
%FACILITY-SEVERITY-MNEMONIC
The Cisco IOS-XE syslog message format. FACILITY names the subsystem, SEVERITY is a digit 0–7 (lower = more severe), MNEMONIC identifies the message type.