Skip to content

23 · Computations

What is the average total of orders 4411 and 4412? No tool of the shop answers that: get_order returns one order. The router reads it as two reads and a calculation over their results, and the calculation runs on your machine, written by your own model. This chapter is how, and what each part may see.

Language support

The selector reads turns written in English and in Brazilian Portuguese (pt-BR). The language is chosen, never guessed from the turn: the console's Settings page (Language the selector reads), language on a plan request, or the settings.language of your vocabulary file; with none of them, English. A Portuguese turn may be typed with its accents or without them. European Portuguese is not a grammar the selector has, and a turn in any other language is unread.

The plan

with Client() as client:
    plan = client.plan("what is the average total of orders 4411 and 4412", agent="desk", catalogue=catalogue)
    print(plan.outcome, "| compute:", plan.compute)
    for step in plan.steps:
        what = (f"over steps {list(step.compute['sources'])}" if step.kind == "compute"
                else f"{step.selection.tool} {dict(step.selection.arguments)}")
        print(f"  {step.index} {step.kind} | {step.decided_by} | {what}")

The plan reads each order the turn names (each order_id) and then a compute step that depends on both:

run | compute: run
  0 tool | R2 source: get_order, each order_id | get_order {'order_id': '4411'}
  1 tool | R2 source: get_order, each order_id | get_order {'order_id': '4412'}
  2 compute | R1+R2: average, total over get_order | over steps [0, 1]
succeeded 26.25 number
the prompt names the fields, never a value: True True
not_admitted | the function was not admitted: import: socket (line 1)

The compute step carries its calculation: the instruction in the person's words, the result type it must return -- number, number[], table or text -- and the steps whose results it reads (sources).

Running it: client.compute

Your code runs the read steps as it runs any tool, then hands the step and their results to client.compute, with your model:

def write(prompt):
    """Your model. It is given the prompt and returns the function's source; here, a reply it might give."""
    return ("```python\n"
            "def compute(data):\n"
            "    totals = [float(order['total']) for order in data.values()]\n"
            "    return sum(totals) / len(totals)\n"
            "```")


with Client() as client:
    step = next(step for step in plan.steps if step.kind == "compute")
    sources = [{"tool": "get_order", "result": get_order(plan.steps[i].selection.arguments["order_id"])}
               for i in step.compute["sources"]]
    done = client.compute(step, sources, model=write)
    print(done.state, done.result, done.result_type)
    print("the prompt names the fields, never a value:", "40.00" not in str(done.prompt), "total" in str(done.prompt))

It runs in four parts, all on your machine:

  1. The prompt. The licensed runtime builds it from the prompt pack its lease holds: the instruction, the result type, and the shape of the sources -- their field names and JSON types, never a value of theirs. The last line of the output above says so of this run.
  2. The function. Your model writes it. model is any function from the prompt -- a ComputePrompt, a str of the system part then the user part, with .system, .user and .schema for a model that takes roles -- to the code: your provider's SDK, your own endpoint, a local model. The stalwart-agents package holds no model credential and calls no provider.
  3. Admission. The runtime reads the function before anything runs it. A function that opens a socket, reads a file, or imports anything outside the pack's modules is refused, and nothing runs.
  4. The run. One fresh container from the sandbox image: no network, a read-only file system, no file and no variable of your machine's. The result is checked against the type asked for, and the checked result is what you are given.

client.compute returns a Computation: state is succeeded (result holds the checked value), failed (the sandbox ran it, or could not start, and failure says which) or not_admitted. result_type is the type the plan asked for -- here a number, the average.

A function that is refused

def careless(prompt):
    return "import socket\n\ndef compute(data):\n    return socket.gethostname()\n"


with Client() as client:
    refused = client.compute(step, sources, model=careless)
    print(refused.state, "|", refused.failure["message"])

The output's last line: admission names the rule and the line, the state is not_admitted, and nothing ran. A refused function counts nothing.

Where a calculation runs

A plan asks for a compute step only where your machine can run one: a container engine answers, the sandbox image is on it, and your licence grants it. Anywhere else, the plan declines the turn, naming the calculation (compute off: ...), and no step of it runs (chapter 22). python -m stalwart_agents.sandbox fetches the image once and prints its reference.

Nothing of a calculation leaves your machine: not the sources, the prompt, the function or the result. What does is two counts in your usage report: compute_runs, a run that reached the sandbox, and compute_seconds, its wall time rounded up. A month's quota spent raises QuotaExceededError before your model is asked anything.

The limits

Chapter 20's limits hold here, and these besides:

  • The function is your model's. A checked result has the type asked for; whether it is the calculation the person meant is your model's work, and nothing here proves it.
  • Admission refuses what reaches outside the sandbox. It does not read the function for mistakes.