cCarebun Reading‹ All reading
Carebun Interactive/Decision models Decision labCheat sheet Study view

Part II · Apprentice

Chapter 7

The paper on the desk: state

The state is everything the clerk may read. We shape it, count it in tokens, find its limit, and measure what 150 unneeded messages do to speed, accuracy and the bill.

11 min read · interactive

On this page
  1. What the state is
  2. Three shapes, text only
  3. Tokens: how the paper is measured
  4. The context limit
  5. What belongs on the desk
  6. A cluttered desk, measured
  7. Try it yourself
  8. Common questions
  9. Carry this
  10. Check yourself

In this chapter, we will learn how to prepare the state, which is the text a decision model reads before it answers. We will see the three shapes a state can take, count one call in tokens, find the size limit, and then bury a tenant's message under 150 old ones to measure what that costs.

What the state is

The state is the content we ask the model to judge, sent in the state field of the request. In Chapter 2 a request had three fields: model, state and questions.

The documentation defines it like this: "State is the content you ask a System One model to evaluate. It could be a support message, a passage of text, or the current state of your application." Two rules follow.

  • One state per request. The documentation says: "All questions see the same state and are evaluated independently."
  • Nothing else is sent. The request has no other field for background, and the documentation warns: "Do not rely on knowledge stored in model weights when current information can come from your own knowledge base." If the clerk should know that flat 14B has a boiler contract, that fact must be on the desk.

Three shapes, text only

A state is a string, an object or an array. A string is one piece of text. An object is a set of named fields, written in JSON with curly brackets. An array is a list, written with square brackets.

ShapeThe documentation says it is useful forAt Harbor Court
String"A message, article, or passage"one tenant message, as in experiment 0002
Object"Named fields, related records, or application state"the message plus facts the back office looked up
Array"A sequence of messages or records"the messages of one conversation, in order

Most of our experiments use the simplest shape. This function from experiments/desk.py builds the state for one row of the dataset:

def state_of(row):
    return "Tenant message:\n" + row["text"]

For real work the documentation recommends the second shape: "Use an object for most requests so each part of the state has a descriptive name and its relationships remain clear." Here is an object state for the desk. It is an invented example, not one we measured.

{
  "new_request": "My bedroom radiator is stone cold and it is freezing outside. Flat 14B. Let yourselves in if I am out.",
  "flat": {"number": "14B", "open_jobs": 1},
  "heating_contract": "Boiler and radiators are covered. Portable heaters are not."
}

Named fields give us something to point at. To aim a question at one part of the state, the documentation says to "name it in the instructions with a dot-and-index path to its key, including the backticks". A backtick is the ` character. The documentation's example is "Does `ticket.messages[0].text` request a refund?". For our object, a question could ask "Is the problem in `new_request` covered by `heating_contract`?".

Tokens: how the paper is measured

A token is a small piece of text, such as a short word, a part of a long word or a punctuation mark, and it is the unit in which a model's input is counted and billed. Before a model reads text, software called a tokenizer cuts the text into tokens.

The documentation names no tokenizer for Jev and no tool to count tokens in advance. So we do not count them ourselves. We read the count that every response reports in usage.input_tokens. The state and the questions are both input, so both are counted.

In experiment 0002, a call with one tenant message and the desk's three questions used 694 input tokens on average. The published price is $0.042 per million input tokens, and the answers are free.

tokens in one call (measured, mean of 215 calls)      694
price per token (published)                           $0.042 / 1,000,000
cost of one call      694 x 0.042 / 1,000,000     =   $0.000029
cost of 1,000 calls   0.000029 x 1,000            =   $0.029

How is that total divided between the state and the questions? A response reports one number, so experiment 0027 sent seven versions of one request, built so that two rows differ in one part only. The message is the one from Chapter 2: 173 characters about water dripping through a ceiling light. A bare Noul is one yes/no statement with no criteria.

the message is 173 characters, 33 words

state              questions                         input tokens
one word ('x')     one bare Noul, no criteria             272
the message        one bare Noul, no criteria             313
one word ('x')     trade (Choice, 6 options)              419
the message        trade                                  460
the message        trade + severity                       562
the message        trade + severity + may_enter           625
the message twice  trade + severity + may_enter           667
the call of Chapter 2, part by part
  the smallest call: a one-word state and one bare Noul           272
  the message                                 313 - 272     =      41
  the trade Choice in place of the bare Noul  419 - 272     =     147
  the severity Score                          562 - 460     =     102
  the may_enter Noul                          625 - 562     =      63
  total               272 + 41 + 147 + 102 + 63             =     625

characters per token          173 / 41        =  about 4.2
the message's share           41 / 625        =  6.6%

The 272 tokens are spent before the message and the real questions arrive. They cover the one-letter state, a four-word statement, and whatever the service adds to every request. TypeSafe does not itemise that part. The lesson is plain all the same: on a short message, the questions and the fixed part weigh far more than the state.

The 625 belongs to one short message. The average call in experiment 0002 was 694 tokens, with the same three questions, so the difference is the messages.

longer messages, same questions      694 - 625    =  69 more tokens
the average message of our dataset   41 + 69      =  about 110 tokens
at 4.2 characters per token          110 x 4.2    =  about 460 characters

So two sizes are in play, and we will say which one we mean. The Chapter 2 message is 41 tokens. An average message of our dataset is about 110, nearly three times as long. Both are small next to the questions, and that changes as soon as the state grows.

The context limit

The context limit is the largest number of tokens one request may contain. It decides how much paper fits on the desk.

Two published numbers exist, and we give both. TypeSafe's documentation says: "64k tokens per request; 32k tokens for state plus the longest question", where k means thousand. The first budget covers the state plus all questions. The second covers the state plus the single longest question. OpenRouter, the service through which our experiments call Jev, lists a context length of 32,000 tokens and describes it as "the state you send plus the questions". That may be TypeSafe's second budget in looser words, but neither page says so.

Our desk calls through OpenRouter, so the safe reading is the stricter one: the state and all the questions together stay under 32,000 tokens. How many old messages from our dataset is that?

budget (published)                                       32,000 tokens
one message and three questions (measured, 0008)            712 tokens
left for more state                 32,000 - 712      =  31,288 tokens
one old message (derived, 0008)     (17,137 - 712) / 150  =  about 110 tokens
old messages that fit               31,288 / 110      =  about 284

The 110 comes from experiment 0008, later in this chapter, where 150 old messages of average length raised a call from 712 to 17,137 tokens. It matches the average message we derived above. Messages as short as the one in Chapter 2 would fit in larger numbers.

The largest calls we sent averaged 17,137 tokens, a little over half of that budget. We have not tested what happens near the limit or above it.

What belongs on the desk

A good state holds everything the questions need and nothing they do not. The documentation gives three pieces of advice, and we adopt them.

Advice from the documentationWhat Rosa does
Prefer an object with descriptive field namescalls the fields new_request and heating_contract, not a and b
Point at fields with backticked pathswrites "Judge only `new_request`." in the question
"Include only the context relevant to the current questions"leaves the tenant's older messages in the filing cabinet

The last piece comes with a reason. The documentation says: "Accuracy falls as the state grows with content unrelated to the decision. Unrelated detail acts as a distractor." It calls the effect context rot: answers getting worse as unneeded material piles up.

One more kind of content belongs on the desk: facts the back office has already worked out. The clerk does not do sums. If a question depends on a total or on a number of days, code should compute it and write it into the state as a named field. In experiment 0009 we asked whether a repair quote was over an approval limit, on 100 invented quotes. The model was right 92% of the time when it had to add up the raw line items, and 100% of the time when the code had put the total in the state (the 100 quotes have 92 different totals, so that row is 92 different requests). Chapter 10 is about this split.

A cluttered desk, measured

Experiment 0008 adds unrelated old messages to the state and records tokens, latency, cost and accuracy. Latency is the waiting time for one answer. As in Chapter 2, p50 is the middle call and p95 is the time that 95 calls in 100 beat.

We took 60 of our labelled requests (from the half of the dataset called the test split). Each state was an object. The field new_request held the message to judge, and the field older_requests_from_other_flats held 0, 5, 20, 60 or 150 other messages. Every question ended with "Judge only `new_request`." This is what the program printed. Tokens and cost are means over the 60 calls.

old messages added   input tokens   p50 ms   p95 ms   $ per call   trade   severity   may_enter
         0                 712      278      342   $0.000030   100.0%    96.7%   100.0%
         5                1270      249      341   $0.000053    98.3%    96.7%   100.0%
        20                2929      260      331   $0.000123    98.3%    96.7%   100.0%
        60                7351      270      377   $0.000309    98.3%   100.0%   100.0%
       150               17137      333      416   $0.000720    98.3%    98.3%   100.0%
  • Tokens grew in a straight line, about 110 for every message added.
  • Latency hardly moved. The median with 5 old messages was lower than with none, so differences of 30 ms are noise between runs. Only at 150 messages is there a clear rise.
  • Accuracy moved by a request or two. With any old messages present, 59 of the 60 trades were right (98.3%) against 60 with none, and no more were lost as the pile grew thirty times larger. Severity gained one or two requests, which is as likely to be chance as the loss was.
  • Cost followed the tokens.
tokens     17,137 / 712              =  24.1 times
cost       0.000720 / 0.000030       =  24 times
latency    333 - 278 = 55 ms,   55 / 278   =  20% slower

per 1,000 calls     0.000030 x 1,000  =  $0.030      bare message
                    0.000720 x 1,000  =  $0.720      with 150 old messages

The clutter cost 24 times the money and a fifth more time, and bought nothing.

Before you touch the slider below, predict one number: with 40 old messages in the state, what do 1,000 calls cost? Then move the slider. Positions between the five measured points are read from a straight line between their neighbours, and the interactive says so when it does that.

Try it yourself

Take one decision from your own work and write its state as a JSON object. Then go through the fields and ask of each: which question reads this? Cross out every field that no question reads. For every number a question needs, ask whether code could compute it first.

With the book's repository and an OpenRouter key you can repeat the measurement. Our run cost $0.07.

python3 experiments/0008-state-size/main.py

Common questions

Does the model remember the state from my last call?

Nothing documented lets it. The request has three fields, and none of them refers to an earlier call.

Why did the bare call in experiment 0008 use 712 tokens when experiment 0002 used 694?

Three things differ. Experiment 0008 used 60 test messages, not all 215. The state was an object with a field name, not a plain string. And each question had the extra words "Judge only `new_request`." The response reports one total, so we cannot say how the 18 extra tokens divide among the three.

If a bigger state did not hurt accuracy, why not send everything just in case?

Because the test was an easy one, and because the bill is certain while the benefit is not. The cost rose 24 times. The documentation says accuracy falls with unrelated content, and we have not tested the hard cases where that would show.

The tenant's message is text that a stranger wrote. Is that a risk?

Yes. The documentation says the state is data and that the model "does not treat it as hostile by default". A message can contain text that tries to steer the answer. Chapter 14 measures this and shows a defence.

Carry this

  • The state is everything the clerk may read: a string, an object or an array, and text only.
  • Prefer an object with descriptive field names, and point questions at fields with backticked paths.
  • Input is counted in tokens. One desk call averaged 694 tokens: $0.000029, or $0.029 per 1,000 calls. In the Chapter 2 call the message was 41 tokens of 625.
  • Published limits: 64k tokens per request, 32k for the state plus the longest question. OpenRouter lists 32,000 for the state plus the questions.
  • 150 unneeded messages: 24 times the cost, 55 ms slower, accuracy within a request or two of 60 in our easy test. Send what the questions read, and send facts that code has computed.

Check yourself

1. Rosa's state holds one new message and 40 old ones. Using 712 tokens for the bare call and about 110 tokens per old message, estimate the tokens per call and the cost of 1,000 calls.

Answer
tokens      712 + 40 x 110              =  5,112
one call    5,112 x 0.042 / 1,000,000   =  $0.000215
1,000 calls 0.000215 x 1,000            =  $0.215

About seven times the bare call. The interactive shows 5,140 tokens here, from its straight line between 20 and 60 messages.

2. A question needs to know whether a repair is still under a 12-month guarantee. The state has the date of the repair and today's date. What would you change?

Answer

Let code compare the dates and write the result into the state, for example a field under_guarantee. The documentation lists date comparison among the things the model does unreliably. Our easy test (experiment 0009) did not show it, but a harder one did. Experiment 0041 asked whether a request was opened more than 14 days ago, on 100 different pairs of dates. The model was right on 93 from the raw dates and on 100 of 100 when the code sent the number of days. All seven misses were requests opened exactly 14 days ago. The fix is one line of code.

3. Name the two published context limits and say what each covers.

Answer

64k tokens covers the whole request: the state plus all questions. 32k tokens covers the state plus the single longest question. OpenRouter lists one number, 32,000, for the state plus the questions, so through OpenRouter we plan with that one.