<a id="getting-data-back-as-json"></a>
<a id="document-template-ai-json"></a>
# Getting data back as JSON

The [{% ai %} tag](ai_tag.html.md#document-template-ai-tag) returns prose, which means the AI decides how your content is laid out. `ai_json_list` and `ai_json_object` return *data* instead, so your template decides.

Use `ai_json_list` when you want a row per thing:

```jinja
{% set forms_text %}
{% for form in draft.als_forms_set.all() %}- {{ form.DraftFormName }}: {{ form.DraftFormOID }}
{% endfor %}
{% endset %}

{% set risks = ai_json_list("For each form give a risk rating and the reason for it", forms_text,
                            keys=["name", "risk", "rationale"], effort="high") %}

{% for row in risks %}
<para><run>{{ row.name }} - {{ row.risk }}: {{ row.rationale }}</run></para>
{% endfor %}
```

and `ai_json_object` when the question has one answer:

```jinja
{% set verdict = ai_json_object("Judge whether this draft is ready for review", draft_text,
                                keys=["ready", "reason"]) %}

<para>Ready: {{ verdict.ready }} - {{ verdict.reason }}</para>
```

This gives you a real Word table or sortable Excel rows in your own house style. It also sidesteps the [output handling](ai_tag.html.md#document-template-ai-tag-output) the `{% ai %}` tag needs: each value is emitted by your template as an ordinary `{{ }}` expression, so it is escaped for you.

<a id="choosing-between-them"></a>
## Choosing between them

They are two functions rather than one function with a switch because the choice changes the whole request, not just what you get back. Each one tells the AI which shape to produce and checks the answer against that shape, so asking for a list and asking for a single answer are two different questions rather than the same question read two ways.

Pick by what your template does next. If it loops, use `ai_json_list`. If it reads values straight off the result, use `ai_json_object`. A list of one is not the same as an object: `{{ verdict.ready }}` on a list renders nothing at all rather than failing, so this is worth getting right.

<a id="building-the-content"></a>
## Building the content

Both are functions rather than block tags, so the content you want them to work on is assembled first. Jinja's `{% set %}` block does this - everything between `{% set name %}` and `{% endset %}` is rendered as normal and assigned to the variable.

<a id="arguments"></a>
## Arguments

Both functions take the same arguments.

| Argument | Meaning |
| --- | --- |
| `instruction` | Required. What the AI should produce. For `ai_json_list`, describe one item rather than the list. |
| `content` | Required. The content to work from, usually built with `{% set %}`. |
| `keys` | The keys the answer must have - every item for `ai_json_list`, the object itself for `ai_json_object`. The AI is told to use exactly these, and the generation fails if the answer comes back without one. A key can also name its type, as `"count:int"`. |
| `effort` | `low`, `medium` or `high`, as for the `{% ai %}` tag. Defaults to `medium`. |

<a id="always-pass-keys"></a>
## Always pass `keys`

`keys` is optional but worth using every time. It tells the AI exactly what shape to return, and it is what lets the most common failure - the AI calling a field `title` where your template reads `name` - be caught and corrected rather than becoming a blank cell in a finished document.

An answer missing one of your keys is handed straight back to the AI, saying which item was wrong and which keys it was asked for, and it answers again. Only if it still cannot produce them does the generation fail, naming the offending item. Without `keys` there is nothing to check against, so a renamed field is simply rendered as a blank.

Ask only for keys you are going to use. Every key you request is content the AI has to write, which costs time and counts towards your limits.

<a id="say-what-type-a-value-should-be"></a>
## Say what type a value should be

A key can name the type it must hold, by adding `:` and one of `str`, `int`, `number` or `bool`:

```jinja
keys=["oid", "severity:int", "required:bool", "ratio:number"]
```

Do this for any value you compare, sort, total or branch on. Left to itself the AI reports values the way it read them - asked for a severity from content saying "Grade 3" it answers the text `"Grade 3"`, and asked whether something is required it answers `"Yes"`.

That matters more than it sounds, because a value of the wrong type usually renders perfectly and behaves wrongly:

| What you write | What happens with text instead of a value |
| --- | --- |
| `{% if row.required %}` | **Always true.** `"Yes"` and `"No"` are both non-empty text, and any non-empty text counts as true. |
| `rows\|sort(attribute="severity")` | Sorted as text, so 12 comes before 3. |
| `row.severity > 2` | Compared as text, so this is false for `"12"`. |
| `rows\|sum(attribute="severity")` | Fails the generation. |

Only the last of those is visible. The others produce a document that looks entirely correct.

Naming the type tells the AI what to produce, and rejects the answer if it does not - so it is fixed before it reaches your document rather than after someone notices. Keys with no type are unchanged: nothing is claimed about them and nothing is checked.

<a id="put-reasoning-keys-before-conclusions"></a>
## Put reasoning keys before conclusions

The AI is asked to answer `keys` in the order you list them, and it writes them one after another - so a key is written knowing only what came before it.

That makes this:

```jinja
keys=["ready", "reason"]
```

quite different from this:

```jinja
keys=["reason", "ready"]
```

In the first, the AI commits to `ready` and then writes a `reason` justifying a decision it has already made. In the second it sets out the reasoning and then answers, so the answer follows from it. The second is usually the one you want, and it costs nothing - your template renders the keys in whatever order it likes, regardless of the order they were produced in.

The same applies to the keys of each item in an `ai_json_list` call.

<a id="laying-the-answer-out-as-a-table"></a>
## Laying the answer out as a table

Because you get data rather than prose, you can put it wherever you like. This is the same call as above, rendered as a Word table:

```jinja
{% set risks = ai_json_list("For each form give a risk rating and the reason for it", forms_text,
                            keys=["name", "risk", "rationale"]) %}

{% if risks %}
<table style="Table Grid" colwidths="5cm,2cm,9cm">
  <row heading="true">
    <cell><para><run font-bold="true">Form</run></para></cell>
    <cell><para><run font-bold="true">Risk</run></para></cell>
    <cell><para><run font-bold="true">Why</run></para></cell>
  </row>
  {% for row in risks %}
  <row keep-together="true">
    <cell{% if row.risk == "High" %} shade="ffcccc"{% endif %}><para>{{ row.name }}</para></cell>
    <cell><para>{{ row.risk }}</para></cell>
    <cell><para>{{ row.rationale }}</para></cell>
  </row>
  {% endfor %}
</table>
{% else %}
<para>No forms were rated.</para>
{% endif %}
```

`heading="true"` repeats the header row when the table breaks across pages, and `keep-together="true"` stops a row being split down the middle. Note the `{% if risks %}` - a table with only a header row looks like a mistake, so say plainly that there was nothing to report.

Values are escaped for you, so a rationale containing `&` or `<` cannot break the document.

<a id="constrain-any-value-you-branch-on"></a>
## Constrain any value you branch on

The example above shades a cell when the risk is `"High"`. That only works if the AI uses the word "High", and left to itself it may answer "high", "Severe" or "3 - major" - different words on different runs, and your condition silently never matches.

If your template compares a value, say what the permitted values are in the instruction:

```jinja
{% set risks = ai_json_list("For each form give a risk rating and the reason for it. "
                            "risk must be exactly one of: High, Medium, Low.",
                            forms_text, keys=["name", "risk", "rationale"]) %}
```

The same applies to anything you sort, group or count on. Where the constraint is a *type* rather than a set of words - a number, a true/false - declare it on the key instead, as described above: naming permitted values in the instruction relies on the AI following prose, where a declared type is checked.

<a id="checking-the-answer-with-a-second-call"></a>
## Checking the answer with a second call

For work where a wrong answer matters more than a missing one, you can pass the first answer back for checking. Give the checker the original content and the items to judge, and ask it for a verdict on each:

```jinja
{% set findings = ai_json_list("List anything wrong with this form design", form_text,
                               keys=["summary", "detail"]) %}

{% if findings %}
{% set checked %}{{ form_text }}

ITEMS TO CHECK:
{% for f in findings %}
[{{ loop.index0 }}] {{ f.summary }}: {{ f.detail }}
{% endfor %}
{% endset %}

{% set verdicts = ai_json_list("Decide whether each item is genuinely wrong. Re-read the content before "
                               "deciding. Return one item per item you were given, with its index and the "
                               "word keep or discard.",
                               checked, keys=["index:int", "verdict"], effort="high") %}

{# The numbers of the findings the checker kept, e.g. [0, 3, 4] #}
{% set keep = verdicts|selectattr("verdict", "equalto", "keep")|map(attribute="index")|list %}

{% for f in findings %}{% if loop.index0 in keep %}
<para>{{ f.summary }}</para>
{% endif %}{% endfor %}
{% endif %}
```

The line that builds `keep` does three things in a row, reading left to right:

- `verdicts` is the checker's answer, one item per finding it was given, each with an `index` and a `verdict`.
- `selectattr("verdict", "equalto", "keep")` drops the items whose `verdict` is anything other than `keep`.
- `map(attribute="index")` replaces each surviving item with just its `index` number, and `list` turns the result back into a list you can use more than once.

So `keep` ends up as a list of numbers - the positions of the findings worth showing. The loop below then prints finding *n* only when *n* appears in that list, which is what `loop.index0 in keep` tests. `index0` is Jinja's zero-based loop counter, and it matches the `[0]`, `[1]` numbering written into the content the checker was given, which is what ties the two calls together.

Note `index:int` in the `keys`. Without it the AI may answer `"3"` rather than `3`, and `loop.index0 in keep` would then be false for every finding - producing an empty section rather than an error.

A second call is a second cost and counts against the same limits, so this is worth it when a false answer in the document is expensive. Using a higher `effort` for the check than for the first pass is often a better trade than raising the effort of both.

Keep the index in the checker's answer. Asking it to return only the items worth keeping loses the link back to the original list, and you cannot then tell which of your findings survived.

<a id="expecting-a-single-answer"></a>
## Expecting a single answer

```jinja
{% set verdict = ai_json_object("Judge whether this draft is ready for review", draft_text,
                                keys=["ready", "reason"]) %}

Ready: {{ verdict.ready }} - {{ verdict.reason }}
```

`ai_json_object` gives you the answer itself, so you read values straight off it. You are asserting that there is exactly one answer, and if the AI replies with a list instead - which it will sometimes do when the instruction reads like a question about a set of things - the generation fails saying so, rather than putting an empty value in your document. If that happens repeatedly, the instruction is probably asking for a list.

<a id="limits-and-failures"></a>
## Limits and failures

Both functions count against the same per-document limits as the `{% ai %}` tag - the number of calls, the tokens spent, and your daily allowance. See [the AI tag documentation](ai_tag.html.md#document-template-ai-tag).

Unlike the `{% ai %}` tag, an answer that is cut short fails the generation rather than being reported. A truncated list of prose is visibly unfinished; a truncated list of data just looks like a shorter list, and there would be nothing in the document to tell you three rows were meant to be twelve.

If the AI returns something that is not valid JSON, or JSON that is not the shape you asked for, it is asked again with an explanation of what was wrong. If it still cannot produce a usable answer the generation fails, and the failure quotes that explanation beside the function and the instruction that asked for it - so a document with several calls in it tells you which one to change, and what the AI kept getting wrong.

A retry is a second call, so it counts towards the same token and call limits as the first. This is one reason to ask only for the keys you use: every key you request is another thing an answer can be rejected for.

<a id="non-deterministic-output"></a>
## Non-deterministic output

As with the `{% ai %}` tag, the values these functions return are not guaranteed to be identical between runs.
