Skip to content

fix(chat): rejection messages name the available views/functions so the model self-corrects - #27

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

Badri-Rajendran merged 1 commit into
mainfrom
bugfix_chat_relation_rejection_hint

Conversation

@Badri-Rajendran

Copy link
Copy Markdown
Owner

Problem

Live testing after PR #26 (which fixed the compound-WHERE and customer_id-hallucination bugs)
found chat still failing a real question: "What are the transactions available in this
account?"
answered with "I cannot access transaction details at the moment."

Diagnosis (verified live, not guessed)

Ruled out permission/grant issues first, since that was the reporter's own hypothesis:
queried v_transaction_history directly as the trueup_chat_readonly role, with
app.role/app.customer_id set exactly as a real session does, against a real customer with real
transaction data — returns rows correctly, no permission error. information_schema.role_table_grants
confirms SELECT on all 8 curated views. No permission change was needed.

Root cause, from the actual chat_tool_call row of the live (post-restart) session:

tool_name              sql_text                     status
execute_read_only_sql  SELECT * FROM transactions    validator_rejected

The model guessed the plain English word "transactions" (matching the system prompt's own topic
list) as a table name instead of the real view v_transaction_history. The validator correctly
rejected it — traced the rejection message through openai_agent_adapter.py and confirmed it
really does reach the model as a tool result — but the message named only what was rejected, never
what was actually available, and the model gave up rather than retrying.

A second question in the same report ("what model portfolio is chosen") is not a bug — no
curated view covers model-portfolio assignment at all (S8/S9-domain data, never in S11's 8-view
allow-list), so declining is FR-54's mandated behavior. Confirmed still declining correctly (with
clearer wording) after this fix.

Fix

sql_tool_validator.py: both rejection paths (off-allow-list relation, disallowed function) now
name the actual allow-list contents in the same tool result, so the very next thing the model sees
already contains the correction. chat_orchestration_service.py: one new system-prompt rule
instructing the model to retry once with a corrected name from the rejection message before
declining.

Validation evidence

  • New unit tests asserting both rejection reasons now name the real options
    (test_off_allow_list_relation_reason_names_the_available_views,
    test_disallowed_function_reason_names_the_available_functions).
  • uv run pytest tests/unit/test_sql_tool_validator.py tests/integration/test_chat_security_perimeter.py tests/api/test_chat.py — 60 passed.
  • uv run ruff check, uv run mypy --strict app, uv run lint-imports — clean, zero new baseline noise.
  • Live end-to-end verification: restarted the backend on this branch, registered a fresh test
    customer, and re-asked the exact failing question through the real chat API. The chat_tool_call
    row now shows SELECT * FROM v_transaction_history succeeding on the first attempt, and the
    assistant correctly answers "There are currently no transactions available in this account"
    (an honest answer for a test account with no transactions, not a false "I cannot access").

Security / DB / API impact

No migration, no grant/permission change, no new curated view, no new endpoint. Strictly improves
error-message content inside the existing ADR 19 safety perimeter.

…model self-corrects

Live QA after PR #26 found a real, separate bug: asked "What are the
transactions available in this account?", the model queried
`SELECT * FROM transactions` (a plausible-sounding guess matching the
system prompt's own topic word "transactions") instead of the real view
`v_transaction_history`, got validator_rejected, and gave up rather than
retrying -- answering "I cannot access transaction details" even though
the correct view was available and readable the whole time.

Confirmed via information_schema.role_table_grants and a live query
through trueup_chat_readonly against real data that this was never a
permission/grant issue -- the DB layer works correctly. Confirmed from
the actual chat_tool_call row for the live session that the rejection
message did reach the model as a tool result, but named only what was
rejected, never what was available, giving it nothing to correct toward.

Fixed by having both sql_tool_validator rejection paths (off-allow-list
relation, disallowed function) name the actual allow-list contents in the
same tool result, plus a new system-prompt rule instructing the model to
retry once with a corrected name before declining. Verified live against
the restarted backend: the same question now correctly resolves to
`SELECT * FROM v_transaction_history` on the first or corrected attempt.

Also confirms a second finding from the same report: "what model
portfolio is chosen" is not a bug -- no curated view covers model
assignment, so declining is FR-54's mandated behavior, not a defect.
Copilot AI lite review requested due to automatic review settings September 7, 2026 06:34

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 07a0e8d 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