Skip to content

feat: structured response#54

Merged
vrtornisiello merged 12 commits into
stagingfrom
feat/structured-response
Jul 8, 2026
Merged

feat: structured response#54
vrtornisiello merged 12 commits into
stagingfrom
feat/structured-response

Conversation

@vrtornisiello

@vrtornisiello vrtornisiello commented Jul 8, 2026

Copy link
Copy Markdown
Collaborator

Adds a structured output contract for the agent's final answer — alongside the prose response, it now returns the datasets/tables used, the temporal coverage of the answer, the SQL queries run, and suggested follow-up questions. Also updates the system prompt (translated to English, gate ambiguous questions into a clarification step before querying, etc.) and adds a standalone eval harness to score output quality against a gold set.

vrtornisiello and others added 12 commits June 22, 2026 13:33
Return the agent's final answer as a structured object instead of a single markdown blob, so the frontend can build a richer UI.

- Add StructuredResponse schema (response, data_sources, temporal_coverage,
  sql_query, follow_up_questions) in app/agent/schemas.py
- Stream the structured payload via a new EventData.structured_response field
  and persist it on the message row (new nullable JSON column + migration)
- Expose dataset_id on table tools so data_sources can carry BD UUIDs
- Rework the 'Resposta Final' prompt: prose-only response, with source,
  period, SQL and follow-ups moved to dedicated structured fields

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ctured schema

When no period is given, always default to period_end and never downgrade to an earlier year on a completeness hunch: table metadata (period_start/period_end) is machine-generated and current, so it takes precedence over the hand-written usage guide, which can be stale. Also slim the StructuredResponse field descriptions so period/source policy lives in the system prompt rather than being duplicated (and contradicted) in the schema, and reframe temporal_coverage as 'the interval the SQL filtered' instead of a period_end prohibition.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Apply the Clarification Protocol as a gate ahead of the query flow: when a question is broad/exploratory or references an unspecified entity, stop and clarify instead of falling through to executing SQL. Broad questions explore datasets/tables but stop before execute_bigquery_sql and then guide the user; unnamed entities (e.g. 'meu município') are asked about rather than assumed. Also make StructuredResponse.follow_up_questions optional (None default).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Ask which entity before querying when the user references one without naming it (any type); never assume a value. Drops the 'São Paulo'/city-specific example that anchored the model into assuming a default.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…period rules

Translate SYSTEM_PROMPT to English (keeping PT identifiers, dataset keywords and example queries) for stronger instruction-following, while responses stay in the user's language. Also: scope the no-tools clarification permission to the unnamed-entity case; on out-of-range periods inform the user and ask rather than silently adjusting the query; drop the redundant geographic-level cue; rename the sql_query field reference to sql_queries; remove the search-funnel section (now in the search_datasets docstring).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-locate decision-proximate rules with the tools that read them: search_datasets gains the hierarchical search funnel (moved from the system prompt); get_table_details drops the 'Next step: execute_bigquery_sql' nudge so a query isn't primed when one isn't warranted; execute_bigquery_sql gains a precondition to not query on broad or unnamed-entity questions.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
StructuredResponse.sql_query (str) becomes sql_queries (list[str]) so a multi-query answer lists every result-backing query for reproducibility. Translate the schema field descriptions to English (values stay in the user's language).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
period_start/period_end now document the format matching `granularity` (YYYY / YYYY-MM / YYYY-MM-DD) with examples, and the granularity field restates the mapping — so the model emits e.g. 2026-05 for a monthly period, not just the year.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replays eval_gold.yaml threads turn-by-turn on the real agent and scores four deterministic dimensions (action / source / period / completed) from the structured response, writing a rich JSON for the later judge eval. Period scoring is granularity-aware (matches period_end at YYYY / YYYY-MM / YYYY-MM-DD); records the system prompt and full tracebacks per run.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…to the SQL filter

data_sources may now list the tables the answer draws on — those queried, or
specific tables recommended when clarifying/guiding — not only tables that were queried.

temporal_coverage is now explicitly the interval the SQL query filtered, left
empty when no query ran (e.g. a clarification turn) or there is no temporal
dimension.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds three transcript scorers on top of the deterministic replay harness,
all reading the transcript eval_output.py produces:
  - eval_quality: LLM judge — answer correctness/grounding vs gold
  - eval_faithfulness: gold-free structural self-consistency of the
    structured output
  - eval_queries: source/period from the executed SQL (also works on the
    free-text baseline)

Renames eval_threads.py -> eval_output.py so the script names describe what
each evaluates. Adds eval_gold.yaml (10 threads / 18 turns) and ignores the
generated transcripts/scores (eval/*.json).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Thoughts are already captured in LangSmith traces, so exposing them in
the frontend stream just adds noise; tool_call args and tool_output
already give clients enough to render good UI/UX.

Also fixes get_table_details tests, which broke after TABLE_DETAILS_QUERY
started requesting `dataset { id }` to populate the table's dataset_id.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@vrtornisiello
vrtornisiello merged commit 51ff81c into staging Jul 8, 2026
1 check passed
@vrtornisiello
vrtornisiello deleted the feat/structured-response branch July 8, 2026 20:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant