bots-garden/mini-mepublic Fork
⑂ feature/structured-output
Commits
⬇ Clone ▾
git clone https://git.rickub.com/bots-garden/mini-me.git
git clone ssh://git@rickub.com/bots-garden/mini-me.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

Download ZIP
🛟 Updated. ffb1541Unverified · on feature/structured-output · k33g · 14d ago
get-json-answers.md · 74 lines · 4.5 KBmarkdown
Blame HistoryOpen raw

How to get answers as JSON (structuredOutput)

This guide shows how to make an agent answer only with JSON of a shape you declare, so that a script, jq or another agent can use the answer. It assumes you already run mm with a configuration file, on a server that supports response_format: json_schema (llama.cpp, so dmr, llamacpp, llmman; Ollama; the OpenAI API). The demo-structured-output/ directory holds the files used below.

Steps

  1. Write a configuration file for a JSON-only agent, with every tool off — an answer constrained to a schema leaves no room for a tool call, and mm refuses to start otherwise:

    bashTool: false
    editTools: false
    planTool: false
    skillsDir: no-skills      # a directory that does not exist: no read_skill tool
    

    Leave out mmConfigs, lsp and mcp.

  2. Declare the shape of the answer under structuredOutput, as Go types and one var naming the root:

    structuredOutput: |
      type Language struct {
          Name     string `json:"name"`     // the language's name, in English
          Official bool   `json:"official"` // true when it has an official status at the national level
      }
    
      type Country struct {
          Name      string     `json:"name"`      // the country's common name, in English
          Capital   *string    `json:"capital"`   // the capital CITY (not the country), in English; null when unknown
          Languages []Language `json:"languages"` // the main languages only, at most 4, most spoken first, each once
      }
    
      // One entry per country named in the question, in the order asked.
      var Countries = []Country{}
    

    The json tags give the keys. The comments are what the model reads to fill each field: write them as instructions.

  3. Ask a question with -p and pipe the answer:

    mm agent.countries.yaml -p "languages spoken in France, Canada, Vietnam and the US" | jq -c '.[] | {name, capital}'
    
    {"name":"France","capital":"Paris"}
    {"name":"Canada","capital":"Ottawa"}
    {"name":"Vietnam","capital":"Hanoi"}
    {"name":"United States","capital":"Washington, D.C."}
    

    stdout carries the JSON alone; the exit status is 1 when the answer is missing or invalid.

Variants

  • Let another agent use the JSON. Give a coordinator — an agent with tools — the JSON agent in its mmConfigs; it calls it with ask_agent and gets the JSON as the tool's result. See How to let your agent call other agents and demo-structured-output/agent.coordinator.yaml. The coordinator itself cannot have a structuredOutput: it has tools.
  • Ask the JSON agent through the coordinator's config, from a shell: mm agent.coordinator.yaml --ask countries -p "capital of Vietnam" | jq . (see How to ask another agent from a skill or a script).
  • In the terminal or the window, the agent answers each question with JSON, shown like any answer.
  • For the OpenAI hosted API, make the root a struct (var Answer = Triage{}): its strict mode demands an object at the top. llama.cpp and Ollama accept an array.
  • Optional fields: a pointer (*string) or json:",omitempty" lets the value be null; the key is always present.

If it goes wrong

  • the answer was cut off by max_tokens before its JSON was complete — a small model can repeat list items until the limit. Bound the lists in their comments (at most 4, each once), or raise sampling.max_tokens.
  • A field holds the wrong thing (a country's name in capital) — sharpen its comment. With llama.cpp the model writes the keys in alphabetical order whatever the declaration order, so a field may be filled before the one it depends on: make each comment self-sufficient.
  • structuredOutput cannot be used with tools — this agent declares: … — turn off the tools listed.
  • structuredOutput:<line>:<col>: … — the declaration at that line of the block is not supported; see the structuredOutput reference.
  • A thinking model is slow to answer: turn its reasoning off for a lookup agent (How to turn a model's reasoning off).

See also

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# How to get answers as JSON (`structuredOutput`)

This guide shows how to make an agent answer only with JSON of a shape you declare, so that a script, `jq` or another agent can use the answer. It assumes you already run `mm` with a configuration file, on a server that supports `response_format: json_schema` (llama.cpp, so `dmr`, `llamacpp`, `llmman`; Ollama; the OpenAI API). The `demo-structured-output/` directory holds the files used below.

## Steps

1. Write a configuration file for a JSON-only agent, with every tool off — an answer constrained to a schema leaves no room for a tool call, and mm refuses to start otherwise:

   ```yaml
   bashTool: false
   editTools: false
   planTool: false
   skillsDir: no-skills      # a directory that does not exist: no read_skill tool
   ```

   Leave out `mmConfigs`, `lsp` and `mcp`.

2. Declare the shape of the answer under `structuredOutput`, as Go types and one `var` naming the root:

   ```yaml
   structuredOutput: |
     type Language struct {
         Name     string `json:"name"`     // the language's name, in English
         Official bool   `json:"official"` // true when it has an official status at the national level
     }

     type Country struct {
         Name      string     `json:"name"`      // the country's common name, in English
         Capital   *string    `json:"capital"`   // the capital CITY (not the country), in English; null when unknown
         Languages []Language `json:"languages"` // the main languages only, at most 4, most spoken first, each once
     }

     // One entry per country named in the question, in the order asked.
     var Countries = []Country{}
   ```

   The `json` tags give the keys. The comments are what the model reads to fill each field: write them as instructions.

3. Ask a question with `-p` and pipe the answer:

   ```bash
   mm agent.countries.yaml -p "languages spoken in France, Canada, Vietnam and the US" | jq -c '.[] | {name, capital}'
   ```

   ```
   {"name":"France","capital":"Paris"}
   {"name":"Canada","capital":"Ottawa"}
   {"name":"Vietnam","capital":"Hanoi"}
   {"name":"United States","capital":"Washington, D.C."}
   ```

   stdout carries the JSON alone; the exit status is 1 when the answer is missing or invalid.

## Variants

- **Let another agent use the JSON.** Give a coordinator — an agent with tools — the JSON agent in its `mmConfigs`; it calls it with `ask_agent` and gets the JSON as the tool's result. See [How to let your agent call other agents](let-the-agent-call-other-agents.md) and `demo-structured-output/agent.coordinator.yaml`. The coordinator itself cannot have a `structuredOutput`: it has tools.
- **Ask the JSON agent through the coordinator's config**, from a shell: `mm agent.coordinator.yaml --ask countries -p "capital of Vietnam" | jq .` (see [How to ask another agent from a skill or a script](ask-another-agent-from-a-skill.md)).
- **In the terminal or the window**, the agent answers each question with JSON, shown like any answer.
- **For the OpenAI hosted API**, make the root a struct (`var Answer = Triage{}`): its strict mode demands an object at the top. llama.cpp and Ollama accept an array.
- **Optional fields**: a pointer (`*string`) or `json:",omitempty"` lets the value be `null`; the key is always present.

## If it goes wrong

- `the answer was cut off by max_tokens before its JSON was complete` — a small model can repeat list items until the limit. Bound the lists in their comments (`at most 4`, `each once`), or raise `sampling.max_tokens`.
- A field holds the wrong thing (a country's name in `capital`) — sharpen its comment. With llama.cpp the model writes the keys in alphabetical order whatever the declaration order, so a field may be filled before the one it depends on: make each comment self-sufficient.
- `structuredOutput cannot be used with tools — this agent declares: …` — turn off the tools listed.
- `structuredOutput:<line>:<col>: …` — the declaration at that line of the block is not supported; see the [`structuredOutput` reference](../reference/configuration.md#structuredoutput).
- A thinking model is slow to answer: turn its reasoning off for a lookup agent ([How to turn a model's reasoning off](turn-reasoning-off.md)).

## See also

- [`structuredOutput` reference](../reference/configuration.md#structuredoutput) — the type mapping, the errors
- [Prompt mode specifics](../reference/cli.md#prompt-mode-specifics) — what `-p` prints
- [Structured output — explanation](../explanation/structured-output.md) — why Go declarations, why no tools