Rank, route, and verify in a Flow
Build three Flows that take run-time input, ask a published question set, and turn the answers into a decision in code. The Flows rank candidates, route a support ticket with a review outcome, and hold a draft that its source doesn’t support.
Each Flow has the same three parts: a transform-data step that builds the state, a Tool Call step that asks the questions, and a transform-data step that applies your policy. For why each design works, see Designing judgments.
Before you begin
Use a Runtype API key with an organization context and the QUESTION_SETS:WRITE, FLOWS:WRITE, and FLOWS:EXECUTE scopes. Install curl, jq, and Bash.
Each run calls a model and can incur usage charges. Each recipe creates question sets and a Flow. A failed later step doesn’t delete resources that an earlier step created.
Set the API key and URL for the examples:
Replace YOUR_API_KEY with your Runtype API key.
Save these functions in your Bash session. They create and publish a question set, create a Flow, and run a Flow with inputs:
create_published_set prints the question set ID. run_flow prints the output variable of the last step.
Pass state to the Tool Call step as one template string, such as "{{rankInput.state}}". A Tool Call step substitutes variables only in top-level string parameters. If you write "state": { "message": "{{message}}" }, the Flow validates and runs, and the model judges the literal text {{message}}.
Rank candidates in one call
Put every candidate in one state and ask about each candidate slot. A candidate that the model judges alone has nothing to be compared with, so its score carries little ranking information.
This recipe asks two question sets about the same state so that you can compare them. The first has one score question per slot. The second has one choice question per ordered pair of slots.
To build and run the ranking Flow, follow these steps:
-
Generate both question sets. Each has eight slots,
c1toc8:Create rank-scores.json and rank-pairs.jsonThe pair set holds 56 questions: each pair in both orders.
-
Create and publish both question sets:
Publish the ranking question sets -
Save the script that builds the state. It fills unused slots with a marker that the ranking step ignores:
Create rank-assemble.js -
Save the script that ranks. It sorts by expected score, and separately by the summed probability that each candidate wins its pairs:
Create rank-in-code.jsA refused or missing score answer puts the candidate in
unscored. The script never treats it as a zero. -
Create the Flow:
Create the ranking Flow -
Save six candidates for one question, then run the Flow:
Run the ranking Flow
A run of this Flow with typesafe-ai/jev returned the following output. Your numbers can differ:
Both orderings agree here. Keep the score step when one call and the lowest cost matter most. Keep the pair step when candidates are close and a tie would change what you show. Remove the step that you don’t use, and its half of the ranking script.
A ranking always has a first place. To decide whether the first candidate is good enough to show, apply a minimum score that you measured, or ask a separate boolean question about the winner.
Route a ticket with a review outcome
Ask one choice question with an option for no match, and one boolean question for each queue that can apply together with another. The policy step routes a ticket only when one queue clearly leads.
To build and run the routing Flow, follow these steps:
-
Save the question set:
Create route-support.json -
Save the policy script. The value
0.8is a starting point for the example, not a measured threshold:Create route-policy.jsThe script checks for a failed call and a refusal before it reads a probability. It sends the ticket to review unless all three queue checks returned an answer. It also sends the ticket to review when the queue checks don’t name the chosen queue and only that queue, or when the choice is
otherand a queue check is confident. It writes each comparison as!(value >= threshold)so that a missing probability goes to review. -
Create the Flow. The Tool Call step sets
onErrortocontinueso that a failed judgment reaches the policy step as{ "error": "...", "result": null }:Create the routing Flow -
Run the Flow on a ticket that names two queues:
Run the routing Flow
A run with typesafe-ai/jev sent this ticket to review:
In the same test, “Where can I download last month’s invoice?” returned billing with a top probability of 1. “I’d like to apply for the support engineer job posting.” returned none.
Replace 0.8 with a threshold that you fit on labeled tickets before you automate routing. See Calibrate a question set version.
Hold a draft that its source doesn’t support
Split the draft into statements in code and ask one choice question about each statement. The policy step holds the draft unless every statement is supported or makes no claim.
To build and run the verification Flow, follow these steps:
-
Generate and publish the question set. It has eight statement slots,
k1tok8:Create and publish verify-claims.json -
Save the script that splits the draft into sentences:
Create verify-split.js -
Save the policy script. The value
0.9is a starting point for the example, not a measured threshold:Create verify-decide.jsEvery path that isn’t a clear pass returns
hold: a failed call, an empty draft, more than eight statements, a refusal, a low probability, and a draft with no statement to check. Theno_claimoption lets a greeting or a sign-off pass, so a draft that opens with “Thanks for reaching out!” isn’t held for it. -
Create the Flow:
Create the verification Flow -
Run the Flow on a draft with one invented sentence among four supported sentences:
Run the verification Flow
A run with typesafe-ai/jev held the draft and named the invented sentence:
In the same test, a draft of three supported sentences with a greeting and a sign-off returned { "action": "send", "checked": 3 }. A draft that said “Refunds take up to 30 business days.” returned hold with the finding contradicted.
A sentence split by punctuation is approximate. An abbreviation or a list can split in the wrong place, which produces a hold, not a wrong send. This check covers only what the excerpt says. It doesn’t establish that the excerpt is the right source or the current version.
Scripts that match these recipes
The repository file docs/examples/decision-models/patterns.mjs builds the same question sets, Flow steps, and inputs with Node.js 22 or later. Run node docs/examples/decision-models/patterns.mjs rank to print the requests without sending them.
Next steps
- Designing judgments: choose question types, batch sizes, and thresholds.
- Run judgment recipes: create and test three smaller question sets over REST.
- Using tool-call steps: set parameters, timeouts, and error handling for the judgment step.
- Using transform-data steps: write and test the state and policy scripts.