Skip to content

Upgrade query-generator SQL step to Claude Sonnet 5 - #49187

Merged
allenhouchins merged 3 commits into
mainfrom
query-generator-sonnet-5-migration
Aug 4, 2026
Merged

allenhouchins merged 3 commits into
mainfrom
query-generator-sonnet-5-migration

Conversation

@allenhouchins

@allenhouchins allenhouchins commented Jul 13, 2026 •

Copy link
Copy Markdown
Member

Related issue: N/A

What this does

The /query-generator page's osquery-SQL-generation step (get-llm-generated-sql.js) was on claude-sonnet-4-6, which is now one generation behind. This PR:

  • Bumps that call to claude-sonnet-5. The schema-filtration step stays on claude-haiku-4-5, which is already the latest Haiku release, so no change needed there.
  • Adds effort support to the shared ai.prompt helper, forwarded as output_config.effort on Anthropic requests, and sets it to "low" for the SQL-generation call. Effort controls how much the model deliberates (and how many tokens/how much latency that costs). "low" was chosen because the Haiku pre-filtering step already narrows the osquery schema down to relevant tables, so the Sonnet step isn't starting from scratch and doesn't need to spend much effort re-deriving that context.
  • Bumps max_tokens in the Anthropic branch of the helper from 4096 to 8192. Claude Sonnet 5 turns on adaptive thinking by default when the thinking param is omitted (which this helper does), and max_tokens is a hard cap on total output including thinking tokens — at 4096 there was a real risk of thinking tokens eating into the budget and truncating the JSON response the SQL step needs to return.
  • Fixes a pre-existing bug found while making the above changes: the sqlReport call passed the system prompt as a bare object-shorthand key named systemPromptForQueryGeneration, but the ai.prompt helper's declared input is systemPrompt. Sails silently drops unrecognized keys passed to .with(...), so the "Return ONLY a raw JSON object..." system prompt was never actually reaching the model for this call. This has been broken since the query generator was switched to Anthropic (f7c20c4731); the sibling filteredTables call above it was unaffected since it passes systemPrompt positionally. Now fixed to systemPrompt: systemPromptForQueryGeneration.

Why

Claude Sonnet 5 follows structured/constrained instructions (don't alias tables, use LIKE with wildcards, only reference documented columns, etc.) more literally than 4.6, which should make the generated SQL more reliable. It's priced the same or cheaper than 4.6 during the current introductory period.

Trade-offs called out for review

  • Thinking being on by default adds some latency versus the old (thinking-off) behavior on 4.6. This call is not currently streamed (sails.helpers.http.post, single blocking call over a socket), so any added thinking time is invisible wait time for the user rather than a visible "thinking" indicator. effort: "low" should keep this modest, but worth confirming with a manual QA pass on a few representative questions before merging.
  • Only the SQL-generation call was migrated. The schema-filtration call also runs on an Anthropic model, but Haiku 4.5 doesn't support output_config.effort (added effort is a no-op if passed to it), so it was left as-is.

Checklist for submitter

If some of the following don't apply, delete the relevant line.

  • Changes file added for user-visible changes in changes/, orbit/changes/ or ee/fleetd-chrome/changes.
    See Changes files for more information.

  • Input data is properly validated, SELECT * is avoided, SQL injection is prevented (using placeholders for values in statements), JS inline code is prevented especially for url redirects, and untrusted data interpolated into shell scripts/commands is validated against shell metacharacters.

  • Timeouts are implemented and retries are limited to avoid infinite loops

  • If paths of existing endpoints are modified without backwards compatibility, checked the frontend/CLI for any necessary changes

Testing

Summary by CodeRabbit

  • Improvements
    • Improved AI-generated SQL responses with an updated language model.
    • Added adaptive effort controls for supported AI requests.
    • Increased response capacity to support more detailed generated results.
    • Improved handling of AI responses to provide more reliable results when content includes different response formats.

The osquery-SQL-generation call in the /query-generator page was still on
claude-sonnet-4-6. Move it to claude-sonnet-5, which follows the platform/
schema constraints in the prompt more literally and should produce more
reliable SQL, since Claude Sonnet 5 now defaults to adaptive thinking when
`thinking` is omitted (which this helper does), add `output_config.effort`
support to the shared ai.prompt helper and set it to "low" here -- the
Haiku pre-filtering step already narrows the schema, so the SQL-generation
step doesn't need much additional deliberation. Also bump max_tokens from
4096 to 8192 so thinking tokens don't eat into the JSON response budget and
cause truncation.

The schema-filtration step stays on claude-haiku-4-5, which is already the
latest Haiku release.
The sqlReport call passed the system prompt as a bare object-shorthand key
named systemPromptForQueryGeneration, but the ai.prompt helper's declared
input is systemPrompt -- Sails silently drops unrecognized keys passed to
.with(), so the "Return ONLY a raw JSON object..." system prompt was never
actually reaching the model for this call. This has been broken since the
query generator was switched to Anthropic (f7c20c4); the sibling
filteredTables call above was unaffected since it passes systemPrompt
positionally.
@allenhouchins
allenhouchins marked this pull request as ready for review July 13, 2026 14:26
Copilot AI review requested due to automatic review settings July 13, 2026 14:26
@fleet-release
fleet-release requested a review from eashaw July 13, 2026 14:26
@coderabbitai

coderabbitai Bot commented Jul 13, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

The SQL generation controller now uses Claude Sonnet 5 with low effort. The AI prompt helper accepts an optional effort input, forwards it through Anthropic requests as output_config.effort when supported, and increases Anthropic max_tokens to 8192. Anthropic response handling now selects the first text content block and errors when no text block exists. The OpenAI request path and existing SQL-generation control flow remain unchanged.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the primary change: upgrading the query-generator SQL step to Claude Sonnet 5.
Description check ✅ Passed The description explains the changes, rationale, trade-offs, and validation requirements, although some checklist items remain unchecked.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch query-generator-sonnet-5-migration

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@website/api/helpers/ai/prompt.js`:
- Around line 87-99: Update the Anthropic response handling that derives
rawPromptResponse from anthropicResponse.content to scan for the first block
whose type is "text" rather than assuming content[0] is text. Throw an explicit
error when no text block exists, preserving the existing downstream parsing flow
for valid text responses.
- Around line 97-99: Update the effort handling in the prompt helper so
output_config.effort is attached only when baseModel identifies a supported
Anthropic model; ensure claude-haiku-* requests omit it or reject the
unsupported baseModel/effort combination before sending the request.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 65ea3077-960f-44c3-9ca4-084cbb45ef4d

📥 Commits

Reviewing files that changed from the base of the PR and between 2299f02 and f404a7b.

📒 Files selected for processing (2)
  • website/api/controllers/query-generator/get-llm-generated-sql.js
  • website/api/helpers/ai/prompt.js

Comment thread website/api/helpers/ai/prompt.js
Comment thread website/api/helpers/ai/prompt.js

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR updates the website query generator’s LLM SQL-generation step to use a newer Anthropic model and adds support for configuring “effort” (adaptive thinking depth) in the shared ai.prompt helper.

Changes:

  • Add an effort input to sails.helpers.ai.prompt and forward it to Anthropic requests as output_config.effort.
  • Increase Anthropic max_tokens from 4096 → 8192 in the shared helper.
  • Update /query-generator SQL generation to use claude-sonnet-5, pass the correct systemPrompt key, and set effort: "low".

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
website/api/helpers/ai/prompt.js Adds optional effort support for Anthropic calls and increases max_tokens headroom.
website/api/controllers/query-generator/get-llm-generated-sql.js Switches the SQL-generation call to Sonnet 5, fixes system prompt forwarding, and sets low effort.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread website/api/controllers/query-generator/get-llm-generated-sql.js
Copilot AI review requested due to automatic review settings July 31, 2026 23:19
@fleet-release
fleet-release requested a review from eashaw July 31, 2026 23:20

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.

Suppressed comments (2)

website/api/helpers/ai/prompt.js:42

  • effort is passed through to Anthropic as output_config.effort, but the helper input accepts any string. A typo/unsupported value would result in a 4xx from Anthropic and fail the helper. Since this helper already validates other enum-like inputs elsewhere in the codebase (via isIn), it would be safer to constrain effort to the supported set.
    effort: {
      type: 'string',
      description: 'Optional effort level for adaptive thinking (controls thinking depth vs. token/latency cost).  Only supported on Anthropic models with output_config.effort support (e.g. Claude Sonnet 5, Claude Opus 4.6+).  Ignored for models that don\'t support it (e.g. Claude Haiku 4.5, OpenAI models).',
      example: 'low'
    },

website/api/helpers/ai/prompt.js:127

  • Anthropic responses can include multiple text content blocks. Taking only the first text block can truncate the answer and cause downstream JSON parsing failures. Joining all text blocks is more robust while still skipping thinking/redacted_thinking blocks.
      let textBlock = _.find(anthropicResponse.content, { type: 'text' });
      if (!textBlock) {
        throw new Error('The LLM responded, but its response did not contain a text block.  Full response content: '+require('util').inspect(anthropicResponse.content, {depth: 3}));
      }
      rawPromptResponse = textBlock.text;

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@website/api/helpers/ai/prompt.js`:
- Around line 124-125: Update the `!textBlock` error path in `prompt.js` to omit
the full `anthropicResponse.content` and include only bounded metadata, such as
content-block types and `stop_reason`; preserve the existing error behavior
while ensuring generated thinking or other response content cannot reach logs
through `get-llm-generated-sql.js`. Add a test that verifies sensitive response
blocks are absent from the emitted error.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 87912b90-6a6c-4449-b0de-310168fe492b

📥 Commits

Reviewing files that changed from the base of the PR and between f404a7b and 44fa651.

📒 Files selected for processing (1)
  • website/api/helpers/ai/prompt.js

Comment on lines +124 to +125
if (!textBlock) {
throw new Error('The LLM responded, but its response did not contain a text block. Full response content: '+require('util').inspect(anthropicResponse.content, {depth: 3}));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail
rg -n -C 6 \
  'Full response content|did not contain a text block|helpers\.ai\.prompt|sails\.log\.(warn|error).*err' \
  website

Repository: fleetdm/fleet

Length of output: 50372


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "## prompt.js relevant section"
sed -n '90,145p' website/api/helpers/ai/prompt.js | cat -n

echo
echo "## get-llm-generated-sql.js relevant section"
sed -n '55,95p' website/api/controllers/query-generator/get-llm-generated-sql.js | cat -n

echo
echo "## references to anthropic usage in website/api helpers/controllers"
rg -n -C 4 "anthropic|baseModel|think|thinking|contentBlock|Claude|claude|messages" website/api website/scripts --glob '*.js' || true

Repository: fleetdm/fleet

Length of output: 50371


🌐 Web query:

Anthropic messages API content blocks thinking type documentation

💡 Result:

In the Anthropic Messages API, thinking is a feature that allows models to output their step-by-step reasoning process before providing a final answer [1][2]. This reasoning is encapsulated in dedicated thinking content blocks [1][3]. Thinking Configuration You control thinking through the thinking parameter in the request body [4][2]. There are two primary modes: - Adaptive Thinking: Enabled by setting thinking: {"type": "adaptive"}. This is the recommended mode for models that support it; it allows you to control reasoning depth using an effort parameter (e.g., output_config: {"effort": "medium"}) rather than a fixed token budget [4][5][2]. - Manual Extended Thinking: Enabled by setting thinking: {"type": "enabled", "budget_tokens": N}. This requires a specific token budget [4][5]. This mode is deprecated on newer models (Claude 4.7+) but remains the only option for certain older models (e.g., Claude 3.5, 4.5) [5]. Content Block Structure When thinking is enabled, the API returns content blocks of type thinking [1][6]. - Block Contents: These blocks contain the raw reasoning text and a cryptographic signature [6][7]. - Preserving History: If you are engaging in multi-turn conversations or tool use, you must preserve these thinking blocks in your conversation history and send them back to the API exactly as received [8][9]. Modifying them will cause the API to reject the request [8]. - Redacted Thinking: In some cases, safety systems may redact parts of the reasoning; these are returned as redacted_thinking blocks, which must also be preserved and returned in subsequent turns to maintain conversation context [3][8]. Display Settings You can control whether the thinking output is returned to you by using the display field within the thinking configuration (e.g., thinking: {"type": "adaptive", "display": "visible"}) [2]. Streaming When using streaming, thinking output is delivered via thinking_delta events within content_block_delta events, followed by a signature_delta event before the content_block_stop event [2]. Text blocks are streamed afterward [2].

Citations:


Do not include the full Anthropic response in the exception.

website/api/helpers/ai/prompt.js:124-125 formats every Anthropic response block into the error. website/api/controllers/query-generator/get-llm-generated-sql.js:74-75 and 179-183 then inspect/log that error, which can expose generated thinking or other generated content to application logs. Log only bounded metadata such as content-block types and stop_reason, and add a test asserting sensitive response blocks are absent from the emitted error.

Proposed fix
       let textBlock = _.find(anthropicResponse.content, { type: 'text' });
       if (!textBlock) {
-        throw new Error('The LLM responded, but its response did not contain a text block.  Full response content: '+require('util').inspect(anthropicResponse.content, {depth: 3}));
+        let contentBlockTypes = Array.isArray(anthropicResponse.content)
+          ? anthropicResponse.content.map((block) => block && block.type).join(', ')
+          : 'unknown';
+        throw new Error('The LLM responded, but its response did not contain a text block. Content block types: '+contentBlockTypes);
       }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (!textBlock) {
throw new Error('The LLM responded, but its response did not contain a text block. Full response content: '+require('util').inspect(anthropicResponse.content, {depth: 3}));
if (!textBlock) {
let contentBlockTypes = Array.isArray(anthropicResponse.content)
? anthropicResponse.content.map((block) => block && block.type).join(', ')
: 'unknown';
throw new Error('The LLM responded, but its response did not contain a text block. Content block types: '+contentBlockTypes);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@website/api/helpers/ai/prompt.js` around lines 124 - 125, Update the
`!textBlock` error path in `prompt.js` to omit the full
`anthropicResponse.content` and include only bounded metadata, such as
content-block types and `stop_reason`; preserve the existing error behavior
while ensuring generated thinking or other response content cannot reach logs
through `get-llm-generated-sql.js`. Add a test that verifies sensitive response
blocks are absent from the emitted error.

Source: MCP tools

@allenhouchins
allenhouchins merged commit 19af21d into main Aug 4, 2026
13 checks passed
@allenhouchins
allenhouchins deleted the query-generator-sonnet-5-migration branch August 4, 2026 02:39
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.

4 participants