feat: answer presentation: present_results, presentation parts, default presentation skill #32

Merged
KofTwentyTwo merged 2 commits from feature/answer-presentation into develop 2026-10-14 00:59:58 +00:00
KofTwentyTwo commented 2026-10-14 00:54:26 +00:00 (Migrated from github.com)

Answer presentation (owner priority 2026-10-13)

The owner saw an answer whose table rendered on one line, ending with "You can open the lists filtered by JPEG or PNG" and no links. Two parts fix it:

  • Rendering: the root cause was the Next renderer, which lacked GFM. That is fixed in QRun-IO/qqq-frontend-next#111.
  • Content: this PR. Answers can now carry structured, clickable blocks built from metadata, and the model is told how to present.

present_results tool

  • Always offered. It reads no data itself.
  • Input:
    • summary;
    • metrics;
    • table: columns, rows, percentOfColumn (the server computes the shares), and rowFilter + rowKeys for per-row filtered-list links;
    • chart (bar, pie or line), built from the table's rows;
    • filterLinks, recordLinks and actions.
  • Checks: every table and field is validated against metadata and against what the user can see through the UI API (GET /metaData with their session). Items the user cannot see are dropped, and the tool result says so.
  • Links: built server-side only: /app/<table>?filter=<QQQ filter JSON> (Next's deserializeFilter accepts it), /app/<table>/<id>, or /app/<table>. Model-written URLs never become links.
  • Formatting: comes from the field displayFormat; percents show as 80.2%.
  • Output: a presentation message part, persisted and streamed as its own SSE event, plus a server-built multi-line GFM markdown fallback for clients without blocks (CLI/TUI). The contract is in docs/answer-presentation.md.

Prompting

The static system prompt gets a Presentation section, the built-in default skill; profile skills extend it.

  • Answer first, with no restating ("101 add up to 101").
  • A table for 3+ items, comparisons or breakdowns, and a chart when shares or trends help.
  • Metrics for a single big number.
  • Any list the answer offers to open goes in as a filterLinks item.
  • Values only from tool results, zero-count categories omitted, and never URLs.

The prefix stays cache-stable.

Golden eval: the owner's example

"How many images do I have by type?" against the fixture media table (81 image/jpeg, 20 image/png) must produce:

  • a one-line summary;
  • a 2-row table with counts and 80.2% / 19.8%;
  • a 2-point pie chart;
  • two filter links that decode to media.mimeType EQUALS image/jpeg and image/png;
  • a markdown fallback whose rows are on separate lines.

Negative cases:

  • unknown table or field;
  • a table the user cannot see;
  • a URL in a label;
  • a percent of a non-numeric column;
  • more than 12 chart points.

A live-tagged version of the eval exists for real providers.

Tests

270 in total, all green in ./mvnw verify with the JaCoCo gates (core: 194).

🤖 Generated with Claude Code

## Answer presentation (owner priority 2026-10-13) The owner saw an answer whose table rendered on one line, ending with "You can open the lists filtered by JPEG or PNG" and no links. Two parts fix it: - **Rendering:** the root cause was the Next renderer, which lacked GFM. That is fixed in QRun-IO/qqq-frontend-next#111. - **Content:** this PR. Answers can now carry structured, clickable blocks built from metadata, and the model is told how to present. ### `present_results` tool - Always offered. It reads no data itself. - **Input:** - `summary`; - `metrics`; - `table`: columns, rows, `percentOfColumn` (the server computes the shares), and `rowFilter` + `rowKeys` for per-row filtered-list links; - `chart` (bar, pie or line), built from the table's rows; - `filterLinks`, `recordLinks` and `actions`. - **Checks:** every table and field is validated against metadata **and** against what the user can see through the UI API (`GET /metaData` with their session). Items the user cannot see are dropped, and the tool result says so. - **Links:** built server-side only: `/app/<table>?filter=<QQQ filter JSON>` (Next's `deserializeFilter` accepts it), `/app/<table>/<id>`, or `/app/<table>`. Model-written URLs never become links. - **Formatting:** comes from the field `displayFormat`; percents show as `80.2%`. - **Output:** a `presentation` message part, persisted and streamed as its own SSE event, plus a server-built multi-line **GFM markdown fallback** for clients without blocks (CLI/TUI). The contract is in `docs/answer-presentation.md`. ### Prompting The static system prompt gets a **Presentation** section, the built-in default skill; profile skills extend it. - Answer first, with no restating ("101 add up to 101"). - A table for 3+ items, comparisons or breakdowns, and a chart when shares or trends help. - Metrics for a single big number. - Any list the answer offers to open goes in as a `filterLinks` item. - Values only from tool results, zero-count categories omitted, and never URLs. The prefix stays cache-stable. ### Golden eval: the owner's example "How many images do I have by type?" against the fixture media table (81 `image/jpeg`, 20 `image/png`) must produce: - a one-line summary; - a **2-row table** with counts and **80.2% / 19.8%**; - a **2-point pie** chart; - **two filter links** that decode to `media.mimeType EQUALS image/jpeg` and `image/png`; - a markdown fallback whose rows are on separate lines. **Negative cases:** - unknown table or field; - a table the user cannot see; - a URL in a label; - a percent of a non-numeric column; - more than 12 chart points. A `live`-tagged version of the eval exists for real providers. ### Tests 270 in total, all green in `./mvnw verify` with the JaCoCo gates (core: 194). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Sign in to join this conversation.