Skip to content
Patterns

Fan-out

Ask many questions in one request. The state is read once and every question is answered from that reading, so a list of ten checks costs about the same as one.

When to use it

  • You need several facts about the same document: a checklist, a form to fill, a set of policy rules.
  • Later steps depend on earlier answers, and you would rather ask everything up front than make a second call. Ask the questions you may not need too; they are cheap next to the state.
  • The questions come from data: one per rule in a policy, one per item in a list, one per candidate.

How it works

Most of the work in a request is reading the state. Once it is read, each question adds only its own instructions and options. So ten questions over one document cost far less than ten requests, and the answers all come from the same reading. Keep each question about one thing; a request may hold as many as fit in the token limits.

  1. State

    Read once

    • an invoice, a contract, a set of applications
  2. Questions

    Many, any type

    • has_totalnoul
    • has_due_datenoul
    • productchoice
    • severityscore
  3. Answers

    One per question

    • all in one response
Every question is answered from one reading of the state.

Build the questions from data and send them together, like these four checks on an invoice:

from typesafe_sdk import Noul, TypeSafeClient # Reads TYPESAFE_BASE_URL (https://api.vakyn.com) and TYPESAFE_API_KEY from the environment.client = TypeSafeClient()CHECKS = {    "has_total": "Does the invoice state a total amount?",    "has_due_date": "Does the invoice state a payment due date?",    "has_vat_number": "Does the invoice show the supplier's VAT number?",    "mentions_late_fee": "Does the invoice mention a fee for late payment?",} invoice = {    "supplier": "Northwind Paper Co.",    "lines": [{"item": "A4 paper, 40 boxes", "amount": "1,180.00 EUR"}],    "text": "Total due: 1,180.00 EUR. Payment within 30 days of the invoice date.",} # One request, one reading of the invoice, every check answered.result = client.system_one(    state=invoice,    questions={name: Noul(instructions=q) for name, q in CHECKS.items()},)missing = [name for name, a in result.nouls.items() if a.noul < 0.5]print("missing:", missing)

The same idea works across types: a noul for "is this a complaint", a choice for the product and a score for severity, all in one call. Each answer comes back under its own name with its own confidence, so your code can act on some and send others for review.

Example: eight job applications in one request

Eight applications for one role in one state, and ten questions built from the data: a priority score for each application, who to interview first and whether any application shows a red flag. Step by step: How VAKYN works.

Questionspriority_1scorepriority_2scorepriority_3scorepriority_4scorepriority_5scorepriority_6scorepriority_7scorepriority_8scorebestchoicered_flagsnoul
  • Clear case

    Acts automatically
    { "role": { "title": "Night shift lead, distribution center in Rotterdam", "must_have": [ "Two years or more leading a warehouse team", "A valid forklift certificate", "Can work nights, Sunday to Thursday" ], "nice_to_have": [ "Dutch and English", "Has used a warehouse management system" ] }, "applications": [ { "application": 1, "name": "Joost Verhagen", "summary": "Four years as night shift lead at a grocery distribution center, team of 14. Forklift certificate valid to 2028. Wants to stay on nights. Dutch and English. Daily work in a warehouse management system." }, { "application": 2, "name": "Mila Petrova", "summary": "Recent graduate in logistics, no work experience in a warehouse yet. No forklift certificate. Prefers day shifts." }, { "application": 3, "name": "Sam O'Brien", "summary": "Barista for three years. No warehouse or forklift experience. Available weekends only." }, { "application": 4, "name": "Fatima Zahra El Idrissi", "summary": "Office manager for six years. No warehouse experience, no forklift certificate. Looking for a day job close to home." }, { "application": 5, "name": "Kees de Wit", "summary": "Retired in 2025 after thirty years as a truck driver. No team lead experience. Wants two days a week." }, { "application": 6, "name": "Lucas Moreau", "summary": "Software developer looking for a career change into tech sales. Never worked in a warehouse." }, { "application": 7, "name": "Hanna Kowalski", "summary": "Student, wants a summer job. No experience and no forklift certificate. Available in July and August only." }, { "application": 8, "name": "Ravi Menon", "summary": "Chef for ten years in restaurants. No warehouse experience. Cannot work nights." } ]}
    QuestionAnswerConfidence
    priority_11.98 of 20.97
    priority_20.00 of 21.00
    priority_30.00 of 21.00
    priority_40.00 of 21.00
    priority_50.01 of 20.99
    priority_60.00 of 21.00
    priority_70.00 of 21.00
    priority_80.00 of 21.00
    bestapplication_10.96
    red_flagsyes 0.090.91

    Every answer is at least 0.70 sure (lowest: red_flags 0.91), so your code acts on it.

  • Borderline case

    Goes to a person
    { "role": { "title": "Night shift lead, distribution center in Rotterdam", "must_have": [ "Two years or more leading a warehouse team", "A valid forklift certificate", "Can work nights, Sunday to Thursday" ], "nice_to_have": [ "Dutch and English", "Has used a warehouse management system" ] }, "applications": [ { "application": 1, "name": "Anouk Jansen", "summary": "Three years as shift lead in a parcel hub, team of 9, mostly evenings. Forklift certificate valid to 2027. Open to nights. Dutch and English." }, { "application": 2, "name": "Daniel Mensah", "summary": "Five years as warehouse team lead on day shifts, team of 20. Forklift certificate expired in 2025, says he will renew it. Can work nights. English only." }, { "application": 3, "name": "Eva Lindgren", "summary": "Two years leading a night team of 6 in a cold store. Forklift certificate valid. Uses a warehouse management system daily. English and some Dutch." }, { "application": 4, "name": "Tom Bakker", "summary": "Forklift driver for seven years, no team lead role. Certificate valid. Night shifts now. Dutch." }, { "application": 5, "name": "Yusuf Demir", "summary": "Says he led a team of 30 for eight years; his dates show four years in total at two companies. Forklift certificate attached." }, { "application": 6, "name": "Sofia Russo", "summary": "Two years as assistant shift lead at a fashion warehouse. Forklift certificate valid. Prefers nights. English and Italian." }, { "application": 7, "name": "Bram Visser", "summary": "Nine years as a warehouse supervisor, then three years out of work for family reasons. Certificate valid to 2026. Can work nights." }, { "application": 8, "name": "Lena Hofmann", "summary": "Store manager in retail for five years, team of 12. No forklift certificate. Can work nights." } ]}
    QuestionAnswerConfidence
    priority_11.85 of 20.77
    priority_20.29 of 20.56
    priority_31.81 of 20.71
    priority_40.05 of 20.93
    priority_50.77 of 20.27
    priority_60.12 of 20.82
    priority_71.00 of 20.49
    priority_80.01 of 20.99
    bestapplication_30.49
    red_flagsyes 0.930.93

    priority_5 is only 0.27 sure, under 0.70, so a person decides.

Real answers from VAKYN-4B Q8_0. The rule: act when every answer is at least 0.70 sure; a noul's confidence is how far it leans either way.
curl -s "https://api.vakyn.com/v1/systemone" \  -H "Authorization: Bearer $TYPESAFE_API_KEY" \  -H "Content-Type: application/json" \  -o answer.json \  -d @- <<'JSON'{  "model": "jev-latest",  "state": {    "role": {      "title": "Night shift lead, distribution center in Rotterdam",      "must_have": [        "Two years or more leading a warehouse team",        "A valid forklift certificate",        "Can work nights, Sunday to Thursday"      ],      "nice_to_have": [        "Dutch and English",        "Has used a warehouse management system"      ]    },    "applications": [      {        "application": 1,        "name": "Joost Verhagen",        "summary": "Four years as night shift lead at a grocery distribution center, team of 14. Forklift certificate valid to 2028. Wants to stay on nights. Dutch and English. Daily work in a warehouse management system."      },      {        "application": 2,        "name": "Mila Petrova",        "summary": "Recent graduate in logistics, no work experience in a warehouse yet. No forklift certificate. Prefers day shifts."      },      {        "application": 3,        "name": "Sam O'Brien",        "summary": "Barista for three years. No warehouse or forklift experience. Available weekends only."      },      {        "application": 4,        "name": "Fatima Zahra El Idrissi",        "summary": "Office manager for six years. No warehouse experience, no forklift certificate. Looking for a day job close to home."      },      {        "application": 5,        "name": "Kees de Wit",        "summary": "Retired in 2025 after thirty years as a truck driver. No team lead experience. Wants two days a week."      },      {        "application": 6,        "name": "Lucas Moreau",        "summary": "Software developer looking for a career change into tech sales. Never worked in a warehouse."      },      {        "application": 7,        "name": "Hanna Kowalski",        "summary": "Student, wants a summer job. No experience and no forklift certificate. Available in July and August only."      },      {        "application": 8,        "name": "Ravi Menon",        "summary": "Chef for ten years in restaurants. No warehouse experience. Cannot work nights."      }    ]  },  "questions": {    "priority_1": {      "type": "score",      "instructions": "How high should application 1 go on the interview list?",      "criteria": [        "Do not interview",        "Interview if needed",        "Interview first"      ]    },    "priority_2": {      "type": "score",      "instructions": "How high should application 2 go on the interview list?",      "criteria": [        "Do not interview",        "Interview if needed",        "Interview first"      ]    },    "priority_3": {      "type": "score",      "instructions": "How high should application 3 go on the interview list?",      "criteria": [        "Do not interview",        "Interview if needed",        "Interview first"      ]    },    "priority_4": {      "type": "score",      "instructions": "How high should application 4 go on the interview list?",      "criteria": [        "Do not interview",        "Interview if needed",        "Interview first"      ]    },    "priority_5": {      "type": "score",      "instructions": "How high should application 5 go on the interview list?",      "criteria": [        "Do not interview",        "Interview if needed",        "Interview first"      ]    },    "priority_6": {      "type": "score",      "instructions": "How high should application 6 go on the interview list?",      "criteria": [        "Do not interview",        "Interview if needed",        "Interview first"      ]    },    "priority_7": {      "type": "score",      "instructions": "How high should application 7 go on the interview list?",      "criteria": [        "Do not interview",        "Interview if needed",        "Interview first"      ]    },    "priority_8": {      "type": "score",      "instructions": "How high should application 8 go on the interview list?",      "criteria": [        "Do not interview",        "Interview if needed",        "Interview first"      ]    },    "best": {      "type": "choice",      "instructions": "Which application should we interview first?",      "criteria": {        "application_1": null,        "application_2": null,        "application_3": null,        "application_4": null,        "application_5": null,        "application_6": null,        "application_7": null,        "application_8": null      }    },    "red_flags": {      "type": "noul",      "instructions": "Does any application show a red flag, such as claims that contradict each other or a certificate that looks made up?"    }  }}JSON # Act when every answer is at least 0.70 sure; otherwise a person decides.jq -r '.answers as $a  | $a | map_values(if .type == "noul" then [.noul, 1 - .noul] | max else .confidence end)  | to_entries | min_by(.value) as $weakest  | if $weakest.value >= 0.70    then "act: interview \($a.best.choice) first, red flags \(if $a.red_flags.noul >= 0.5 then "yes" else "no" end), "      + "priorities \([range(1; 9) as $i | $a["priority_\($i)"].score | round] | map(tostring) | join(" "))"    else "human: \($weakest.key) is only \($weakest.value * 100 | round / 100) sure" end' answer.json

The borderline case runs the same code with its own state. priority_5 is only 0.27 sure, under 0.70, so it prints a human line and a person decides. Change the threshold to match what a wrong answer costs you.

Run it in the playground, with a threshold slider

Tips

  • Give each question a stable name. Your code reads the answers by name, and logs stay comparable over time.
  • To combine several answers into one number, see composite scores.
  • If the state itself is too long for one request, see long documents.

Loading the docs…