Skip to content

State: what material to give Jev ​

A Jev request has only two things: state (the content to evaluate) + questions (typed questions). This lesson covers the first half—how to prepare Jev state: how to choose among the three forms, how to organize structured tickets, and which details you should not stuff in.

In fact, you already wrote a state once in your first request: the ticket string in route_ticket.py was exactly that. This lesson spends 8 minutes upgrading it into material that is easy to evaluate.

What you will be able to do after this lesson ​

  • Choose one of the three forms of Jev state based on the shape of your material: a string for a single piece of text, a JSON object for multiple fields, a text array for homogeneous entries
  • Organize a ticket (conversation + order + policy) into a structured state where every field has a name and the relationships are clear
  • Use dot paths (backtick-wrapped paths like `ticket.messages[0].text`) so questions point precisely to fields in the state
  • State the two context limits on state (64k per-request / 32k per-question) and the four hard rules of the request body
  • Explain why "send only the fields each question needs" both saves money and improves accuracy

Your current struggles ​

  • I pasted the entire user conversation as-is into a prompt and asked GPT which team to route it to—half the tokens were "are you there", "thanks", and signature blocks, the bill doubled, and the classification results were occasionally led astray by company names inside the signature blocks
  • The ticket clearly has several fields—title, body, customer tier—but I could only stitch them into one big paragraph in the prompt: "This is a ticket, the title is... the body is..."—once there are many fields I miss some while stitching, and when the model answers wrong I can't tell whether I gave it bad material or it failed to understand
  • Material and judgment criteria are mixed in the same prompt: to ask one more question like "is this severe?", I have to copy and resend the whole block of material; to make the model look only at the "body" field, all I can do is write "please ignore the title part"—whether it actually does so is pure luck

When to use this technique ​

When you need to:

  • Prepare material for every judgment made by Jev—state is the only thing the model can see
  • Your material has structure (multiple fields, multiple entries) and you don't want to stitch it into one big block of text
  • You want to ask several questions about the same material and precisely control which fields each question sees

And you don't want to:

  • Mix judgment instructions, reference policies, and the text to judge in the same prompt
  • Pay input tokens for irrelevant content (Jev bills by input tokens)
  • Be unable to tell which part of the material introduced the interference when the model answers wrong

🎒 Prerequisites ​

  • Completed the daily use guide: you know this section's roadmap—first learn how to feed material (this lesson), then learn the five question styles
  • Got the first request running: .venv has typesafe-sdk installed and the environment variable TYPESAFE_API_KEY set
  • Remember roughly what route_ticket.py looks like (client.system_one(state=..., questions=...)); if you've forgotten, go back and skim it

Core idea ​

The official definition in one sentence: state is the content you ask the System One model to evaluate—it may be a support message, a piece of text, or the current state of your application. It is passed in via the request's state field, together with the questions (docs.typesafe.ai/concepts/state.md).

A useful mental model, in the official words:

Think of the state as the material you present to a panel of experts before asking them to make a judgment.

How would you prepare material for a panel of experts? Write the title clearly, attach the order record as-is, put the policy clauses on a separate page—not stir all three into one paragraph. That is the entire thread of this lesson.

First, look at how it differs from the prompt context you're familiar with:

Chat LLM promptJev state
Material and instructionsmixed in one block of text; changing the question means resending everythingstate holds only material, questions are defined separately
Field referencesrely on "please look at the second paragraph", "please ignore the title"dot paths point precisely to fields, e.g. `ticket.body`
Irrelevant contentwhatever gets pasted in gets read; greetings and signature blocks all count as tokensonly fields needed for the judgment are included, trimming has a basis
Reusea different judgment means rewriting the whole blockthe same state can be reused across multiple questions

One more fact that runs through the whole lesson: Jev each request only sees the state you send it, with no implicit memory—whatever the customer told you last month, it won't know unless you put it into this request's state (this is also what the later simple memory system scenario is about).

Follow along ​

Step 1: send a request with the simplest string state ​

Why: The simplest state is just a piece of plain text—use it first to get a feel for "state = material" within 30 seconds.

The simplest example in the docs is this string:

python
state = "My card was charged twice."

Paired with the billing question from the docs, this makes a complete request:

python
# 运行前:export TYPESAFE_API_KEY="<你的API Key>"
# 安装:pip install typesafe-sdk(注意包名是连字符,import 是下划线 typesafe_sdk)
from typesafe_sdk import Noul, TypeSafeClient

state = "My card was charged twice."

with TypeSafeClient(model="jev-latest") as client:  # 自动读 TYPESAFE_API_KEY
    response = client.system_one(
        state=state,
        questions={
            "billing": Noul(instructions="Is this message about billing?"),
        },
    )

print(response.answers["billing"].noul)

You should see: the script prints a number between 0 and 1. The official OpenAPI docs have a nearly identical example—state is "I was charged twice for my subscription this month. Please refund the extra charge.", the question is "Is this message about billing?", and it returns noul: 0.98. A clearly stated charge complaint gets a billing probability of 0.98; your actual value depends on your actual output.

  • The script above runs and prints a number between 0 and 1

Step 2: Upgrade the Jev state from a string to a structured JSON object ​

Why: The official recommendation is to use an object for most requests—"give each part of the state a descriptive name and keep relationships clear" (Use an object for most requests). A string is only appropriate when the use case is simple and there is just one piece of text.

The official comparison table for the three forms:

FormWhen to useExample
StringA single message, article, or piece of text"My card was charged twice."
JSON objectNamed fields, related records, application state{"message": "…", "order_id": "A-104"}
Array of textA series of messages or records["Hi", "My customer number is TS1337.", "My card was charged twice."]

As soon as the material to evaluate gets complex, the string's shortcomings show up:

String stateJSON object state
dot path referencescan only point to the wholeevery field can be referenced individually by a question
Basis for judgmentthe model finds it in the full text on its ownfield names carry semantics, so judgments map to fields
Best forsimple judgments on a single textcomplex multi-field judgments, multiple questions in parallel

The official structured example is a support ticket—a conversation, an order, and a refund policy packed into one state:

python
# 运行前:export TYPESAFE_API_KEY="<你的API Key>"
from typesafe_sdk import TypeSafeClient

state = {  # 官方文档例子:docs/concepts/state.md
    "ticket": {
        "subject": "Duplicate charge",
        "messages": [
            {"from": "customer", "text": "I was charged twice for order A-104. Please refund the duplicate."},
            {"from": "support", "text": "We are checking the charges."}
        ]
    },
    "order": {
        "id": "A-104",
        "charges": [
            {"amount_usd": 49, "status": "captured"},
            {"amount_usd": 49, "status": "captured"}
        ]
    },
    "refund_policy": "Duplicate charges are eligible for a refund."
}

Two things to note:

  • This is one state, even though it contains three parts—conversation, order, and policy. The official recommendation is "when a decision requires comparing these parts, keep the related information together"
  • In Python, strings, dictionaries, and lists are passed directly to client.system_one(state=...)—no need to serialize them into a JSON string

You should see: the state is just an ordinary Python dictionary—no network request has happened yet; this step only prepares the material. refund_policy is part of the material to evaluate, so it is not written as a prompt in instructions (more in the next section).

  • Can explain why the refund_policy in the state is not written as a prompt in instructions—because it is part of the material to evaluate, not a question (more in the next section)

Step 3: Use dot paths to point questions at specific fields of the Jev state ​

Why: Once the state becomes an object, you need a way to tell the model "this question only looks at this field." The official syntax: in instructions, write field names as dot/index paths wrapped in backticks.

python
# 运行前:export TYPESAFE_API_KEY="<你的API Key>"
questions = {  # 官方文档例子:docs/primitives.md「Reference specific fields」
    "refund_requested": {
        "type": "noul",
        "instructions": "Does `ticket.messages[0].text` request a refund?",
    },
    "policy_supports_refund": {
        "type": "noul",
        "instructions": (
            "Does `refund_policy` support the refund requested "
            "in `ticket.messages[0].text`, given `order.charges`?"
        ),
    },
}

with TypeSafeClient(model="jev-latest") as client:
    response = client.system_one(state=state, questions=questions)

print(response.answers["refund_requested"].noul)
print(response.answers["policy_supports_refund"].noul)

The response skeleton looks like this (three top-level fields model / answers / usage; a Noul question returns only the two fields type and noul; field names come from the official schema, and the placeholders are explained below):

json
{
  "model": "<实际回答该请求的模型 ID,别名会解析成版本号,如 jev-1.14.0>",
  "answers": {
    "refund_requested": {
      "type": "noul",
      "noul": "<0 到 1 之间的概率>"
    },
    "policy_supports_refund": {
      "type": "noul",
      "noul": "<0 到 1 之间的概率>"
    }
  },
  "usage": {"input_tokens": "<整数>", "output_tokens": "<整数>"}
}

The pattern here:

  • `ticket.messages[0].text` = dot path + array index, pointing to the customer's first message
  • One question can reference multiple fields: policy_supports_refund uses refund_policy, ticket.messages[0].text, and order.charges together
  • This is separating content from questions: the state holds content and supporting facts (ticket, order, policy), while questions define the judgments. Changing the questions doesn't require touching the state, and adding one more question doesn't require resending the material

You should see: two lines of output, each a probability between 0 and 1; the response structure is in the skeleton above (the official docs don't give specific values, so go by your actual output).

The same state can carry any number of questions, and you can mix question types—all questions are evaluated in parallel and independently against the same state, and adding questions adds almost no response time (this is covered in the lesson on parallel questions).

  • Can write the dot path pointing to the second charge amount in order.charges: `order.charges[1].amount_usd`

Step 4: Trim your state within the context limits ​

Why: More state isn't better—it's constrained by hard limits, and it directly affects accuracy and your bill.

Two context limits (identical for Jev 1.14 / 1.13):

LimitMeaningDefault
per-request contextstate + all questions combined64k tokens
per-question contextstate + any single question32k tokens

Key points:

  • There is no cap on the number of questions—the limits only look at size; as long as both contexts fit, you're fine
  • The state is usually the bulk (questions are often short), and both limits include the state itself—what bursts the limit first is usually not the questions, it's the state
  • Do the accounting on the state item by item: the input tokens for the whole request (state + all questions) are billed only once, but every irrelevant field you stuff in costs real money
  • It really exceeds the limit: adding the request header TypeSafe-Danger-Zone-Long-Context: true raises both limits to 262k, but the official docs warn that it is "slower and less accurate — enable it only for the requests that need it" — the correct approach is to trim first; long context is a last resort (see Models and Pricing)

Beyond context, the request body has four hard rules (docs.typesafe.ai/api/errors):

RuleLimit
Request body size≤ 32 MiB (headers excluded)
EncodingUTF-8 JSON, no BOM
Nestingstate / instructions / criteria each allow at most 64 levels; anything deeper must be flattened
Numbersonly finite numbers are accepted — NaN, Infinity, 1e400 are all rejected; pass IDs and large numbers as strings

The last one bites people most often: order numbers and user IDs from tickets should be written as strings like "A-104" when placed into state, not as bare numbers.

How to trim — the official design principle is one sentence: "include only the context relevant to the current question, helping the model avoid interference and context rot". Three concrete techniques:

  1. Filter before sending: in your code, select the fields this question needs first — don't dump an entire database row in as-is
  2. Don't concatenate JSON into strings: schemas, category trees, and database rows are already JSON — pass the whole thing or only the relevant subfields; don't serialize them into string templates
  3. When you can't filter in code, use one Noul pass to filter first: first ask "is this passage relevant to the judgment", then send only the relevant ones into the next batch of questions (that's exactly what RAG passage vetting does)

What you save isn't just tokens. Jev bills by input tokens with free output, and the official pitfall handbook says outright: the more irrelevant content in state, the lower the accuracy — irrelevant details are distractors and also make it impossible for you to pinpoint where the error is (expanded in the next section).

  • Before writing state you can answer: "which fields does this question actually need?" If you can't answer, don't send yet

Checkpoint ✅ ​

  • You can name the three forms of state and when each applies (string = a piece of text, object = multiple fields, array = multiple homogeneous entries)
  • You can write a dot path pointing to any field in structured state (including array indices)
  • You know the default values of the two context limits: 64k per-request / 32k per-question, both of which include state itself
  • You know that IDs like order numbers must go into state as strings, not bare numbers
  • State contains no pleasantries, signature blocks, or entire unfiltered raw logs irrelevant to the judgment

Pitfall reminders ​

❌ Pitfall 1: treating large state as "the more complete the context, the better" ​

Symptom: thinking "a bit more background can't hurt", you stuff the customer's entire ticket history, chat pleasantries, and signature blocks into state.

Why it's wrong: the official pitfall handbook (applicable to jev-1.13) lists it as a known failure mode — "the more decision-irrelevant content in state, the lower the accuracy; irrelevant details are distractors, and large state also makes it harder to pinpoint which part of the input caused the wrong answer". The extra input tokens you spend buy you lower accuracy.

Correct approach: filter in code before sending and send only the fields this question needs; when you can't filter in code, first ask about relevance with one Noul pass and then screen.

❌ Pitfall 2: referring to fields in roundabout ways in questions ​

Symptom: not wanting to learn dot paths, you write in instructions "please look at the text field of the first item of the messages array nested under ticket in state".

Why it's wrong: the Indirection entry in the official pitfall handbook: instructions requiring multi-hop reasoning or double negatives produce less reliable answers.

Correct approach: use the backtick path `ticket.messages[0].text` directly to point at the field in one step.

❌ Pitfall 3: writing judgment criteria into state ​

Symptom: state = {"text": "判断这条工单是不是退款诉求:我订的东西三天没到"}.

Why it's wrong: judgment instructions mixed into the material mean state must be redone whenever the question changes; this is also not what state is for — state holds content and supporting facts, while judgments are written in the instructions and criteria of questions.

Correct approach: state = {"subject": "物流延迟", "body": "我订的东西三天没到"}, write the question in questions and point back with a dot path to body. The same goes for domain rules: encode them into instructions and criteria, not lay them at the bottom of state.

❌ Pitfall 4: passing IDs as bare numbers ​

Symptom: {"order_id": 1000042} — casually putting a database integer ID directly into state.

Why it's wrong: the API's number rules only accept finite numbers — NaN, Infinity, and numbers too large to represent (such as 1e400) are rejected outright (400); meanwhile the official docs explicitly advise "pass identifiers and exact large numbers as strings". An ID is an identifier, not a quantity — a bare number both risks tripping the number rules and lacks the readability of something like "A-104".

Correct approach: always use strings for IDs and exact large numbers: {"order_id": "1000042"}.

❌ Pitfall 5: can't install typesafe-sdk / import errors ​

Symptom: pip install typesafe installs fine, but from typesafe_sdk import ... throws ModuleNotFoundError.

Why it's wrong: that typesafe on PyPI is an unrelated package with the same name. The pip package name is hyphenated typesafe-sdk, while the import is underscored typesafe_sdk.

Correct approach: pip install typesafe-sdk, then from typesafe_sdk import Noul, TypeSafeClient.

Advanced techniques (optional) ​

🔧 Images can go into state too. Images can appear anywhere text can — state, instructions, criteria all work; a single state can mix text and images, hold multiple images, and carry labels or nest just like other data. It's off by default: every image-bearing request needs the TypeSafe-Danger-Zone-Images: true header, and as of Python SDK v0.7.5 the Image object can be placed directly into state. Note the official docs mark it as still "dreamer-ready" (experimental; format and accuracy may change) — see Box and images.

🔧 Audio and video don't work, but can be converted. Jev does not accept audio or video; the official recommendation is to convert them into text or structured fields before putting them into state.

🔧 Answers can go into the next leg's state. Jev's answers are themselves typed values—your code can compare them, check them against thresholds, sort them, or put them into the state of subsequent requests. That's exactly how the official memory system cookbook does it: one state per ticket, and anything worth remembering, along with Jev's answers about it, gets added to the state before the next call (see simple memory system).

🔧 Chinese state works, but keep a close eye on confidence. English is Jev's primary training language and has the best accuracy; CJK Chinese is supported but with lower accuracy—the official recommendation is to test non-English content with your own data before going live, paying special attention to confidence. This is even more true when making routing decisions.

Lesson Summary ​

✅ Look back at "What you'll be able to do"—the three forms, structured organization, dot paths, the two context limits, and filtering before sending: you should now be able to do each one.

One-sentence wrap-up: Jev state is the only material the model can see—choose the form based on the shape of the material, write judgments in questions referencing fields back via dot paths, and don't stuff in any irrelevant details within the context limits.

Key takeaways recap:

TakeawayRecommended practiceExplanation
state formUse a JSON object for most field scenariosFields have names and clear relationships; use a string only for plain text
Field referencesBacktick dot paths: `ticket.messages[0].text`Write them in the question's instructions
Content boundaryMaterials and supporting facts go in state, judgments go in questionsChange questions without touching state
Context64k per-request / 32k per-questionThe long-context header is a last resort that's "slower and less accurate"
Identifying fieldsUse strings for IDs and large numbersNumbers only accept limited values
PruningSend only the fields needed for the question at handFilter in code first—saves tokens and protects accuracy

Next Lesson Preview ​

With the materials ready, the next lesson covers the first way of asking questions: Choice questions—picking one from fixed options, returning the chosen option, the probability of each option, and a confidence score.

You will learn:

  • Using criteria to define the options map, so answers always land within the options you provide
  • Reading the three return fields choice / probabilities / confidence, and using them to directly drive code branching
  • Why you should use Choice instead of letting the model improvise when "the answer is a set of known options"
  • Option order can also affect the answer—how to self-check with "reorder the options to verify consistency"