[{"data":1,"prerenderedAt":719},["ShallowReactive",2],{"docs-\u002Fdocs\u002Fv2\u002Fconcepts\u002Fstructured-output":3},{"id":4,"title":5,"body":6,"description":17,"extension":713,"meta":714,"navigation":380,"path":715,"seo":716,"stem":717,"__hash__":718},"docs\u002Fdocs\u002Fv2\u002Fconcepts\u002Fstructured-output.md","Structured Output",{"type":7,"value":8,"toc":704},"minimark",[9,14,18,93,99,104,111,114,118,126,160,170,198,201,205,208,252,258,261,280,284,291,419,426,429,433,436,444,447,506,517,520,526,535,539,545,570,603,606,662,668,677,681,690,700],[10,11,13],"h1",{"id":12},"structured-output","Structured output",[15,16,17],"p",{},"Sometimes you don't want prose back. You want an object, with the fields you asked for,\nthat your code can use without parsing anything.",[19,20,25],"pre",{"className":21,"code":22,"language":23,"meta":24,"style":24},"language-ts shiki shiki-themes dark-plus","const result = await axon.request({\n    prompt: \"audit src\u002F and report every issue you find\",\n    output: \"{ files: number, issues: { file: string, line: number, message: string }[] }\",\n})\n","ts","",[26,27,28,63,76,87],"code",{"__ignoreMap":24},[29,30,33,37,41,45,49,53,56,60],"span",{"class":31,"line":32},"line",1,[29,34,36],{"class":35},"scz_3","const",[29,38,40],{"class":39},"s9McN"," result",[29,42,44],{"class":43},"sTNBD"," = ",[29,46,48],{"class":47},"sYbnZ","await",[29,50,52],{"class":51},"s3F5K"," axon",[29,54,55],{"class":43},".",[29,57,59],{"class":58},"sCudf","request",[29,61,62],{"class":43},"({\n",[29,64,66,69,73],{"class":31,"line":65},2,[29,67,68],{"class":51},"    prompt:",[29,70,72],{"class":71},"sKc5r"," \"audit src\u002F and report every issue you find\"",[29,74,75],{"class":43},",\n",[29,77,79,82,85],{"class":31,"line":78},3,[29,80,81],{"class":51},"    output:",[29,83,84],{"class":71}," \"{ files: number, issues: { file: string, line: number, message: string }[] }\"",[29,86,75],{"class":43},[29,88,90],{"class":31,"line":89},4,[29,91,92],{"class":43},"})\n",[15,94,95,98],{},[26,96,97],{},"output"," is a TypeScript type, written as a string. That single string does three jobs:\nit is checked before the model is called, shown to the model as its target, and enforced\nagainst what the model produces.",[100,101,103],"h2",{"id":102},"why-typescript-and-not-a-schema-library","Why TypeScript and not a schema library",[15,105,106,107,110],{},"The agent's whole world is already TypeScript. Every tool it can call arrives as a\n",[26,108,109],{},"declare"," block in its context window. Adding zod or JSON Schema would mean a second type\nlanguage in a system that already renders one — something new for you to learn, a\ndependency shipped into every agent, and a translation layer between the shape you wrote\nand the shape the model reads.",[15,112,113],{},"So there is no schema library. You write the type. The model sees exactly that type,\nin the same language as its tools. And if you already use zod or arktype, they can both\nemit a TypeScript type — so you are not locked out, you just don't have to be locked in.",[100,115,117],{"id":116},"the-two-forms","The two forms",[15,119,120,121,125],{},"A ",[122,123,124],"strong",{},"type expression"," covers most cases:",[19,127,129],{"className":21,"code":128,"language":23,"meta":24,"style":24},"output: \"number\"\noutput: \"string[]\"\noutput: \"{ ok: boolean, count: number }\"\n",[26,130,131,142,151],{"__ignoreMap":24},[29,132,133,136,139],{"class":31,"line":32},[29,134,97],{"class":135},"sqDPy",[29,137,138],{"class":43},": ",[29,140,141],{"class":71},"\"number\"\n",[29,143,144,146,148],{"class":31,"line":65},[29,145,97],{"class":135},[29,147,138],{"class":43},[29,149,150],{"class":71},"\"string[]\"\n",[29,152,153,155,157],{"class":31,"line":78},[29,154,97],{"class":135},[29,156,138],{"class":43},[29,158,159],{"class":71},"\"{ ok: boolean, count: number }\"\n",[15,161,162,165,166,169],{},[122,163,164],{},"Declarations"," are how you express a shape that repeats or nests. Name the target\n",[26,167,168],{},"Output",":",[19,171,173],{"className":21,"code":172,"language":23,"meta":24,"style":24},"output: `\n    type Issue = { file: string, line: number, message: string }\n    type Output = { issues: Issue[], summary: string }\n`\n",[26,174,175,184,189,194],{"__ignoreMap":24},[29,176,177,179,181],{"class":31,"line":32},[29,178,97],{"class":135},[29,180,138],{"class":43},[29,182,183],{"class":71},"`\n",[29,185,186],{"class":31,"line":65},[29,187,188],{"class":71},"    type Issue = { file: string, line: number, message: string }\n",[29,190,191],{"class":31,"line":78},[29,192,193],{"class":71},"    type Output = { issues: Issue[], summary: string }\n",[29,195,196],{"class":31,"line":89},[29,197,183],{"class":71},[15,199,200],{},"Your own type names are what the model sees, and what appears in any error — so name them\nthe way you would in real code.",[100,202,204],{"id":203},"checked-before-the-model-runs","Checked before the model runs",[15,206,207],{},"An invalid type throws immediately, at your call site, before any inference is spent:",[19,209,211],{"className":21,"code":210,"language":23,"meta":24,"style":24},"await axon.request({\n    prompt: \"...\",\n    output: \"{ files: nubmer }\",   \u002F\u002F throws: Cannot find name 'nubmer'\n})\n",[26,212,213,225,234,248],{"__ignoreMap":24},[29,214,215,217,219,221,223],{"class":31,"line":32},[29,216,48],{"class":47},[29,218,52],{"class":51},[29,220,55],{"class":43},[29,222,59],{"class":58},[29,224,62],{"class":43},[29,226,227,229,232],{"class":31,"line":65},[29,228,68],{"class":51},[29,230,231],{"class":71}," \"...\"",[29,233,75],{"class":43},[29,235,236,238,241,244],{"class":31,"line":78},[29,237,81],{"class":51},[29,239,240],{"class":71}," \"{ files: nubmer }\"",[29,242,243],{"class":43},",   ",[29,245,247],{"class":246},"sOLPB","\u002F\u002F throws: Cannot find name 'nubmer'\n",[29,249,250],{"class":31,"line":89},[29,251,92],{"class":43},[15,253,254,255,257],{},"This is what makes ",[26,256,97],{}," a guarantee rather than a hint. A typo costs you a stack trace,\nnot three model calls and a confusing retry loop.",[15,259,260],{},"Because the check runs against the agent's live scope, an output type can also reference\ntypes the agent's own tools declare:",[19,262,264],{"className":21,"code":263,"language":23,"meta":24,"style":24},"\u002F\u002F src\u002Ftools\u002Ffiles.ts declares FileEntry\noutput: \"{ entries: FileEntry[] }\"\n",[26,265,266,271],{"__ignoreMap":24},[29,267,268],{"class":31,"line":32},[29,269,270],{"class":246},"\u002F\u002F src\u002Ftools\u002Ffiles.ts declares FileEntry\n",[29,272,273,275,277],{"class":31,"line":65},[29,274,97],{"class":135},[29,276,138],{"class":43},[29,278,279],{"class":71},"\"{ entries: FileEntry[] }\"\n",[100,281,283],{"id":282},"how-the-agent-produces-it","How the agent produces it",[15,285,286,287,290],{},"The agent builds the object in its ",[26,288,289],{},"\u003Cscript>"," block as ordinary TypeScript, then hands it\nover whole:",[19,292,296],{"className":293,"code":294,"language":295,"meta":24,"style":24},"language-vue shiki shiki-themes dark-plus","\u003Cscript>\nconst entries = await files.list(\"src\")\nconst result = { files: entries.length, issues: [] }\n\u003C\u002Fscript>\n\n\u003Ctemplate lang=\"json\">{{ result }}\u003C\u002Ftemplate>\n","vue",[26,297,298,310,338,366,375,382],{"__ignoreMap":24},[29,299,300,304,307],{"class":31,"line":32},[29,301,303],{"class":302},"srRes","\u003C",[29,305,306],{"class":35},"script",[29,308,309],{"class":302},">\n",[29,311,312,314,317,319,321,324,326,329,332,335],{"class":31,"line":65},[29,313,36],{"class":35},[29,315,316],{"class":39}," entries",[29,318,44],{"class":43},[29,320,48],{"class":47},[29,322,323],{"class":51}," files",[29,325,55],{"class":43},[29,327,328],{"class":58},"list",[29,330,331],{"class":43},"(",[29,333,334],{"class":71},"\"src\"",[29,336,337],{"class":43},")\n",[29,339,340,342,344,347,350,352,354,357,360,363],{"class":31,"line":78},[29,341,36],{"class":35},[29,343,40],{"class":39},[29,345,346],{"class":43}," = { ",[29,348,349],{"class":51},"files:",[29,351,316],{"class":51},[29,353,55],{"class":43},[29,355,356],{"class":51},"length",[29,358,359],{"class":43},", ",[29,361,362],{"class":51},"issues:",[29,364,365],{"class":43}," [] }\n",[29,367,368,371,373],{"class":31,"line":89},[29,369,370],{"class":302},"\u003C\u002F",[29,372,306],{"class":35},[29,374,309],{"class":302},[29,376,378],{"class":31,"line":377},5,[29,379,381],{"emptyLinePlaceholder":380},true,"\n",[29,383,385,387,390,393,396,399,402,405,408,410,413,415,417],{"class":31,"line":384},6,[29,386,303],{"class":302},[29,388,389],{"class":35},"template",[29,391,392],{"class":51}," lang",[29,394,395],{"class":43},"=",[29,397,398],{"class":71},"\"json\"",[29,400,401],{"class":302},">",[29,403,404],{"class":43},"{",[29,406,404],{"class":407},"sHkYM",[29,409,40],{"class":407},[29,411,412],{"class":43}," }}",[29,414,370],{"class":302},[29,416,389],{"class":35},[29,418,309],{"class":302},[15,420,421,422,425],{},"The value is ",[122,423,424],{},"serialised, never typed",". The model never writes JSON syntax by hand — no\nbraces, no commas, no quoting — so the result is valid however large or deeply nested it\ngets. A ten-thousand-item array is one interpolation.",[15,427,428],{},"This is why a JSON template must contain exactly one interpolation and nothing else.\nHand-written syntax around a value would forfeit the guarantee, so the runtime rejects it\nas a format error rather than emitting something that might not parse.",[100,430,432],{"id":431},"enforcement-and-retries","Enforcement and retries",[15,434,435],{},"After the model writes its script, the runtime typechecks it against your type — before\nrunning it. A mismatch comes back to the model as a real TypeScript diagnostic:",[19,437,442],{"className":438,"code":440,"language":441},[439],"language-text","line 3: Type 'string' is not assignable to type 'number'.\n","text",[26,443,440],{"__ignoreMap":24},[15,445,446],{},"Models correct that reliably, far more so than schema-validator prose. The model rewrites\nits script and tries again.",[19,448,450],{"className":21,"code":449,"language":23,"meta":24,"style":24},"const result = await axon.request({\n    prompt: \"audit src\u002F\",\n    output: \"{ files: number }\",\n    retries: 3,   \u002F\u002F default 2 — at most 3 model calls\n})\n",[26,451,452,470,479,488,502],{"__ignoreMap":24},[29,453,454,456,458,460,462,464,466,468],{"class":31,"line":32},[29,455,36],{"class":35},[29,457,40],{"class":39},[29,459,44],{"class":43},[29,461,48],{"class":47},[29,463,52],{"class":51},[29,465,55],{"class":43},[29,467,59],{"class":58},[29,469,62],{"class":43},[29,471,472,474,477],{"class":31,"line":65},[29,473,68],{"class":51},[29,475,476],{"class":71}," \"audit src\u002F\"",[29,478,75],{"class":43},[29,480,481,483,486],{"class":31,"line":78},[29,482,81],{"class":51},[29,484,485],{"class":71}," \"{ files: number }\"",[29,487,75],{"class":43},[29,489,490,493,497,499],{"class":31,"line":89},[29,491,492],{"class":51},"    retries:",[29,494,496],{"class":495},"sDGX8"," 3",[29,498,243],{"class":43},[29,500,501],{"class":246},"\u002F\u002F default 2 — at most 3 model calls\n",[29,503,504],{"class":31,"line":377},[29,505,92],{"class":43},[15,507,508,511,512,516],{},[26,509,510],{},"retries"," counts attempts ",[513,514,515],"em",{},"after"," the first. When the budget runs out the request throws\nwith the accumulated diagnostics. It never returns a value that failed its check — a\ncaller that asked for a shape gets that shape or an error, never an unvalidated object it\nwould treat as validated.",[15,518,519],{},"Two details that matter:",[15,521,522,525],{},[122,523,524],{},"The script is checked before it runs."," The script is real code that touches the world,\nso one that cannot satisfy the contract never executes. Otherwise every retry would\ndouble its side effects.",[15,527,528,534],{},[122,529,530,531,55],{},"A failed check overrides ",[26,532,533],{},"\u003Cdone\u002F>"," A model that declares itself finished while\nproducing the wrong shape has not finished.",[100,536,538],{"id":537},"what-this-does-and-does-not-guarantee","What this does and does not guarantee",[15,540,541,542,544],{},"TypeScript proves a program is well-typed. It does not, by itself, prove a runtime value\nhas a shape — the language has two deliberate ways for well-typed code to lie about a\ntype. Both are rejected while an ",[26,543,97],{}," type is in force:",[15,546,547,550,551,359,554,557,558,561,562,565,566,569],{},[122,548,549],{},"Assertions."," ",[26,552,553],{},"as T",[26,555,556],{},"\u003CT>x"," and ",[26,559,560],{},"satisfies T"," all tell the checker to believe a claim\nit never verified — ",[26,563,564],{},"JSON.parse(x) as Output"," is perfectly well-typed and can produce\nanything. ",[26,567,568],{},"as const"," is still fine: it narrows rather than widens.",[15,571,572,577,578,581,582,585,586,589,590,593,594,596,597,599,600,602],{},[122,573,574,55],{},[26,575,576],{},"any"," The wider hole in practice, because nobody writes it — it arrives from a\nloosely typed tool. If ",[26,579,580],{},"db.query()"," returns ",[26,583,584],{},"Promise\u003Cany>",", that value flows into\n",[26,587,588],{},"result"," and every check succeeds ",[513,591,592],{},"vacuously",", since ",[26,595,576],{}," is assignable to everything.\nSo the runtime asks the checker what ",[26,598,588],{}," actually resolved to, and rejects it if\n",[26,601,576],{}," is anywhere in the shape.",[15,604,605],{},"The fix the model is told to make is to narrow:",[19,607,609],{"className":21,"code":608,"language":23,"meta":24,"style":24},"const rows = await db.query(\"select ...\")\nconst result = { rows: Number(rows) }   \u002F\u002F now genuinely checked\n",[26,610,611,637],{"__ignoreMap":24},[29,612,613,615,618,620,622,625,627,630,632,635],{"class":31,"line":32},[29,614,36],{"class":35},[29,616,617],{"class":39}," rows",[29,619,44],{"class":43},[29,621,48],{"class":47},[29,623,624],{"class":51}," db",[29,626,55],{"class":43},[29,628,629],{"class":58},"query",[29,631,331],{"class":43},[29,633,634],{"class":71},"\"select ...\"",[29,636,337],{"class":43},[29,638,639,641,643,645,648,651,653,656,659],{"class":31,"line":65},[29,640,36],{"class":35},[29,642,40],{"class":39},[29,644,346],{"class":43},[29,646,647],{"class":51},"rows:",[29,649,650],{"class":58}," Number",[29,652,331],{"class":43},[29,654,655],{"class":51},"rows",[29,657,658],{"class":43},") }   ",[29,660,661],{"class":246},"\u002F\u002F now genuinely checked\n",[15,663,664,667],{},[26,665,666],{},"unknown"," is not affected — it is assignable to nothing without narrowing, so it already\nfails with an ordinary type error.",[15,669,670,671,673,674,55],{},"With those closed, a value reaching ",[26,672,588],{}," has a shape the checker genuinely verified.\nWhat remains outside its reach is anything TypeScript cannot see: this is output parsing,\nnot a sandbox. The capsule and OS permissions are what contain what an agent can ",[513,675,676],{},"do",[100,678,680],{"id":679},"streaming","Streaming",[15,682,683,685,686,689],{},[26,684,97],{}," is for ",[26,687,688],{},"axon.request",". Enforcement can only run once the script closes, so a\nstream would emit output before it has been verified — and with a JSON template there is\nnothing to read incrementally anyway: it is a single value.",[15,691,692,693,695,696,699],{},"Use ",[26,694,688],{}," when you want a shape. Use ",[26,697,698],{},"axon.stream"," when you want to watch the\nagent work.",[701,702,703],"style",{},"html pre.shiki code .scz_3, html code.shiki .scz_3{--shiki-default:#569CD6}html pre.shiki code .s9McN, html code.shiki .s9McN{--shiki-default:#4FC1FF}html pre.shiki code .sTNBD, html code.shiki .sTNBD{--shiki-default:#D4D4D4}html pre.shiki code .sYbnZ, html code.shiki .sYbnZ{--shiki-default:#C586C0}html pre.shiki code .s3F5K, html code.shiki .s3F5K{--shiki-default:#9CDCFE}html pre.shiki code .sCudf, html code.shiki .sCudf{--shiki-default:#DCDCAA}html pre.shiki code .sKc5r, html code.shiki .sKc5r{--shiki-default:#CE9178}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sqDPy, html code.shiki .sqDPy{--shiki-default:#C8C8C8}html pre.shiki code .sOLPB, html code.shiki .sOLPB{--shiki-default:#6A9955}html pre.shiki code .srRes, html code.shiki .srRes{--shiki-default:#808080}html pre.shiki code .sHkYM, html code.shiki .sHkYM{--shiki-default:#F44747}html pre.shiki code .sDGX8, html code.shiki .sDGX8{--shiki-default:#B5CEA8}",{"title":24,"searchDepth":65,"depth":65,"links":705},[706,707,708,709,710,711,712],{"id":102,"depth":65,"text":103},{"id":116,"depth":65,"text":117},{"id":203,"depth":65,"text":204},{"id":282,"depth":65,"text":283},{"id":431,"depth":65,"text":432},{"id":537,"depth":65,"text":538},{"id":679,"depth":65,"text":680},"md",{},"\u002Fdocs\u002Fv2\u002Fconcepts\u002Fstructured-output",{"title":5,"description":17},"docs\u002Fv2\u002Fconcepts\u002Fstructured-output","KRaI88_s-mmoyjLwaDpQsIXSwdw2HVaLQV4FEVXD2BU",1786356956927]