Skip to content

fix(chat): embed curated-view schema statically to stop first-attempt table-name guessing - #29

Merged
Badri-Rajendran merged 1 commit into
mainfrom
fix_chat_static_schema_and_layout_and_cleanup
Sep 7, 2026
Merged

Badri-Rajendran merged 1 commit into
mainfrom
fix_chat_static_schema_and_layout_and_cleanup

Conversation

@Badri-Rajendran

Copy link
Copy Markdown
Owner

Problem

Live-tested after PR #26/#27 landed: every one of 4 real chat questions still got
validator_rejected on the model's first execute_read_only_sql attempt.

Diagnosis (verified live)

The model guessed account_balance, transactions, positions, tax_lots instead of the real
view names v_customer_balance, v_transaction_history, v_holdings, v_tax_lots. PR #27's
retry-on-rejection fix recovered 3 of 4 on a second attempt — but "balance" failed twice in a
row
: it found the right table on retry, then guessed a column (balance) that doesn't exist on
v_customer_balance (raw signed postings needing sum(amount_money) for a total). That column
only exists on v_published_snapshot — the as-published figure for a period, a different thing
entirely. Root cause: get_database_schema is a tool the model can choose to skip, and its own
tool-call order for the balance question showed both SQL attempts happening before it ever
called that tool.

Fix

Embed the exact 8 curated view/column list directly in _SYSTEM_PROMPT_TEMPLATE — always in
context from turn one, no longer dependent on the model choosing to call a tool before it guesses
— with a one-line note resolving the balance/v_published_snapshot ambiguity. Kept
get_database_schema as a tool (harmless, still useful if the schema ever drifts from the
hardcoded prompt) and rule 8's retry-on-rejection guidance as a fallback safety net.

Validation evidence

  • New tests/unit/test_chat_orchestration_service.py: a tripwire asserting every curated view
    name appears in the prompt, and a regression guard that the view/column listing itself never
    lists customer_id as a column (the exact confusion PR fix(chat): stop rejecting compound WHERE clauses; stop inviting a fabricated customer_id #26 already removed once from
    describe_schema()'s dynamic output). Not a content-wording pin — matches this codebase's
    existing "script-driven, not content-driven" convention (FakeAgentAdapter deletes
    system_prompt).
  • uv run pytest tests/unit/test_chat_orchestration_service.py tests/unit/test_sql_tool_validator.py tests/integration/test_chat_security_perimeter.py tests/api/test_chat.py — 62 passed.
  • uv run ruff check, uv run mypy --strict app, uv run lint-imports — clean, zero new baseline noise.
  • Live end-to-end re-verification: restarted the backend on this branch, registered a fresh
    funded/approved customer, and re-asked the exact 4 previously-failing questions. All 4 now
    succeed on the first execute_read_only_sql call with zero rejections, including "balance"
    (previously the one that failed twice) — correctly answers "$1,000.00 (LIVE figure)".

Security / DB / API impact

None. Prompt content only — no new endpoint, no wire-shape change, no change to the ADR 19 safety
perimeter (views, grants, validator allow-list all unchanged).

…s guessing table names

Live-tested after PR #26/#27: every one of 4 real questions still got
validator_rejected on its first execute_read_only_sql attempt (guessed
account_balance/transactions/positions/tax_lots instead of the real
v_customer_balance/v_transaction_history/v_holdings/v_tax_lots). PR #27's
retry-on-rejection fix recovered 3 of 4 on a second attempt, but "balance"
failed twice in a row: it found the right table on retry, then guessed a
column ("balance") that doesn't exist on v_customer_balance (raw signed
postings needing sum(amount_money)) -- that column only exists on
v_published_snapshot, the as-published figure for a period, not the live
one. Root cause: get_database_schema is a tool the model can skip, and
its own tool-call order for the balance question showed both SQL
attempts happening before it ever called that tool.

Fixed by embedding the exact 8 view/column list directly in
_SYSTEM_PROMPT_TEMPLATE -- always in context from turn one, no longer
dependent on the model choosing to call a tool before it guesses -- with
a one-line note resolving the balance/v_published_snapshot ambiguity.
get_database_schema stays available as a tool (harmless, still useful if
the schema ever drifts from the prompt) and rule 8's retry guidance stays
as a fallback.

Verified live: all 4 previously-failing questions, including balance,
now succeed on their first execute_read_only_sql call with zero
rejections.

New tests/unit/test_chat_orchestration_service.py: a tripwire (every
curated view name appears in the prompt) plus a regression guard (the
view/column listing itself never lists customer_id as a column, the
exact confusion PR #26 already fixed once in describe_schema()'s dynamic
output) -- not a content-wording pin, matching this codebase's existing
"script-driven, not content-driven" test convention for the fake agent
adapter.
Copilot AI lite review requested due to automatic review settings September 7, 2026 07:26

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@Badri-Rajendran
Badri-Rajendran merged commit c8bc4b9 into main Sep 7, 2026
5 of 6 checks passed
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.

2 participants