Repository navigation
fix(ai): use the first text block of an Anthropic response - #1332
Conversation
Models that return a thinking block before the answer made the provider fail with "Invalid response format from Anthropic", because the reply was read from content[0]. Pick the first block whose type is text instead, and cover it with fixture tests for a thinking-first reply and a reply with no text block. Closes TabularisDB#1330
Code Review SummaryStatus: No Issues Found | Recommendation: Merge Files Reviewed (2 files)
Notes: Previous Review Summary (commit a89d196)Current summary above is authoritative. Previous snapshots are kept for context only. Previous review (commit a89d196)Status: No Issues Found | Recommendation: Merge Files Reviewed (2 files)
Notes: Reviewed by claude-sonnet-5.5 · Input: 246 · Output: 1.2K · Cached: 163.1K Review guidance: REVIEW.md from base branch |
HongTin2104
left a comment
There was a problem hiding this comment.
The fix looks right to me. With a leading thinking block, json["content"][0]["text"] is Null, so the old as_str() returned None and produced exactly the error in #1330. The thinking-first test covers that case, and filtering on type == "text" also skips redacted_thinking blocks. The two tests follow the existing Fixture pattern.
Two non-blocking notes:
-
A thinking-only reply will still show the generic error.
anthropic_request_bodysends"max_tokens": 1024, and thinking tokens count toward that limit. A longer Explain can run out of budget during thinking and come back withstop_reason: "max_tokens"and notextblock. The user would then see "Invalid response format from Anthropic" again, which points at the wrong cause. It could be worth checkingjson["stop_reason"]when no text block is found and returning something like "Anthropic stopped at the max_tokens limit before producing an answer". Raisingmax_tokensfor the explain paths could also be a follow-up. Either is fine outside this PR. -
Only the first
textblock is used. For this request shape (no tools, no documents), the answer comes back as a single text block, so this is fine today. If the API ever splits the answer into several text blocks, joining them would be a little more robust and costs nothing:let text: String = blocks .iter() .filter(|block| block["type"].as_str() == Some("text")) .filter_map(|block| block["text"].as_str()) .collect();
This is optional, not a request.
I don't have a Rust toolchain on this machine, so I reviewed by reading only and haven't run cargo test. The CI run linked in the description covers the new tests.
|
Thanks for the careful review, @HongTin2104. I took note 1: when there is no text block and |
There was a problem hiding this comment.
Thanks for fixing Anthropic responses that put a thinking block before the text answer, and for adding regression tests for missing text and max_tokens exhaustion. This is a useful fix for the existing provider.
We are evaluating moving AI assistant integrations into plugins in #1337, but that does not need to hold up this bug fix. I will verify it on this branch and merge it if the checks pass.
|
I tested Anthropic SQL generation in the real macOS app with claude-haiku-4-5 against a SQLite fixture, then ran the generated query: 255 orders and a total of 34972.5. The three regression tests and existing provider fixtures pass. Full checks reproduce only the same two Rust theme-path failures and one frontend workspace-layout failure seen on main; scoped ESLint, formatting, theme checks and typecheck pass. No console errors appeared during generation. This is ready to merge. |
What
The Anthropic provider failed with "Invalid response format from Anthropic" whenever the model put a
thinkingblock ahead of its answer, even though the API call returned 200.generate_anthropicnow uses the firstcontentblock whosetypeistext. Other block types are skipped, and a reply with no text block still returns the same error.Cause
generate_anthropicread the reply fromcontent[0].text. With extended thinking,content[0]is athinkingblock that has notextfield, so the parse failed.Changes
src-tauri/src/ai.rs: pick the firsttextblock incontentinstead of indexingcontent[0].src-tauri/src/ai_provider_tests.rs: two tests on the existing local fixture. One returns athinkingblock followed by atextblock and expects the parsed SQL. The other returns only athinkingblock and expects the invalid-format error.Only the built-in Anthropic provider is touched. Other providers and plugin drivers do not go through this code.
Verification
test(cargo test, which runs the two new tests),web-e2e, Postgres and both MySQL jobs: https://github.com/TabularisDB/tabularis/actions/runs/40770839575content[0]read; I did not run it against the old code.Closes #1330