Skip to content

feat(websearch): add toggle to disable prefilling search keywords in …#14135

Open
karuboniru wants to merge 5 commits intoCherryHQ:v2from
karuboniru:feat/web-search-disable-prefill-keywords
Open

feat(websearch): add toggle to disable prefilling search keywords in …#14135
karuboniru wants to merge 5 commits intoCherryHQ:v2from
karuboniru:feat/web-search-disable-prefill-keywords

Conversation

@karuboniru
Copy link
Copy Markdown

@karuboniru karuboniru commented Apr 8, 2026

…tool description

When an agent uses web search, Cherry Studio pre-extracts keywords and injects them into the builtin_web_search tool description. This commit adds a setting in Settings -> Web Search -> General to toggle this behavior.

When disabled, the model receives a generic tool description and must independently decide what search query to use, which can be useful for models that perform better without pre-suggested queries.


What this PR does

Before this PR: When an agent with web search enabled processes a user query, Cherry Studio pre-analyzes the message, extracts search keywords, and injects them directly into the builtin_web_search tool description — e.g.:

- Prepared queries: "what is X", "X latest news"

The model sees these prefilled queries and tends to use them as-is.

After this PR: A new toggle — Settings → Web Search → General → "Prefill search keywords" — lets users disable this behavior. When turned off:

  • The preliminary LLM call that pre-extracts keywords is skipped entirely (saving tokens and latency).
  • The builtin_web_search tool is exposed unconditionally so the model can always reach for it.
  • The model independently decides whether to search and what to search for.

Fixes #

Why we need it and why it was done in this way

Modern LLMs are generally capable enough to decide on their own when to search and what to search for. Pre-extracting keywords with a separate LLM call adds extra token usage and latency before the main request even starts. Users who want to control searching behavior (e.g. force or suppress searches, steer the query style) can already do so more naturally through the system prompt. The prefill behavior remains the default for users who prefer it, but this toggle lets others opt out.

The following tradeoffs were made:

  • The setting defaults to true (existing behavior preserved — no regression for current users).
  • When disabled, if the model calls the tool without providing additionalContext, the search is treated as not needed (no silent fallback to pre-extracted queries). The tool description instructs the model to always supply its own query.
  • The schema keeps additionalContext optional in both modes; the description text guides the model's behavior rather than enforcing it at the schema level.
  • Knowledge base intent analysis is unaffected — it still runs its own pre-extraction pass when knowledge search is enabled.

The following alternatives were considered:

  • Per-assistant toggle (more granular, but adds UI complexity and this is more of a global preference).
  • Removing pre-extraction entirely — rejected because it provides real value for users on weaker models.

Breaking changes

None. The default value is true, preserving existing behavior for all users.

Special notes for your reviewer

Unlike the other settings in the websearch Redux slice (which have not yet been migrated), this setting is stored directly in PreferenceService — the v2 SQLite-backed preference system — rather than Redux. The preference key chat.web_search.prefill_keywords (default true) is declared in packages/shared/data/preference/preferenceSchemas.ts.

In the renderer the toggle reads and writes via the usePreference('chat.web_search.prefill_keywords') hook (no Redux dispatch). In the plugin layer, searchOrchestrationPlugin.ts reads it asynchronously via preferenceService.get('chat.web_search.prefill_keywords'). The Redux websearch slice (src/renderer/src/store/websearch.ts) is untouched.

This means no data migration will be needed when the websearch slice is eventually migrated — the preference is already in SQLite.

Checklist

  • PR: The PR description is expressive enough and will help future contributors
  • Code: Write code that humans can understand and Keep it simple
  • Refactor: You have left the code cleaner than you found it (Boy Scout Rule)
  • Upgrade: Impact of this change on upgrade flows was considered and addressed if required
  • Documentation: A user-guide update was considered — not required for a toggle with a tooltip
  • Self-review: I have reviewed my own code before requesting review from others

Release note

Add a "Prefill search keywords" toggle in Settings → Web Search → General.
When disabled, the model independently decides what to search for instead of
using pre-extracted keywords injected into the tool description.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds a user-facing setting to control whether Cherry Studio injects pre-extracted web-search queries into the builtin_web_search tool description, allowing models to decide queries independently when desired.

Changes:

  • Added prefillKeywords to the persisted websearch Redux slice (default true) and exposed an action to update it.
  • Added a new “Prefill search keywords” toggle in Settings → Web Search → General with localized label/tooltip.
  • Updated builtin_web_search tool description/schema text and the search orchestration plugin to respect the toggle.

Reviewed changes

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

Show a summary per file
File Description
src/renderer/src/store/websearch.ts Introduces persisted prefillKeywords state and setPrefillKeywords action.
src/renderer/src/pages/settings/WebSearchSettings/BasicSettings.tsx Adds the new toggle UI and wires it to Redux.
src/renderer/src/i18n/locales/en-us.json Adds English strings for the toggle label/tooltip.
src/renderer/src/i18n/locales/zh-cn.json Adds Simplified Chinese strings for the toggle label/tooltip.
src/renderer/src/i18n/locales/zh-tw.json Adds Traditional Chinese strings for the toggle label/tooltip.
src/renderer/src/aiCore/tools/WebSearchTool.ts Makes the tool description/schema conditional on prefillKeywords.
src/renderer/src/aiCore/plugins/searchOrchestrationPlugin.ts Passes prefillKeywords from Redux into the web search tool factory.

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

@karuboniru karuboniru force-pushed the feat/web-search-disable-prefill-keywords branch from d5ce867 to 0309b82 Compare April 8, 2026 21:42
…tool description

When an agent uses web search, Cherry Studio pre-extracts keywords and
injects them into the builtin_web_search tool description. This commit
adds a setting in Settings -> Web Search -> General to toggle this behavior.

When disabled, the model receives a generic tool description and must
independently decide what search query to use, which can be useful for
models that perform better without pre-suggested queries.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Karuboniru <yanqiyu01@gmail.com>
@karuboniru karuboniru force-pushed the feat/web-search-disable-prefill-keywords branch from 0309b82 to 5b3bdcf Compare April 9, 2026 06:32
@karuboniru karuboniru requested a review from Copilot April 9, 2026 19:09
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

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 7 out of 7 changed files in this pull request and generated 3 comments.


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

…refill is disabled

When the "Prefill search keywords" toggle is off the goal is to let the
model decide on its own whether to search and what to search for.  The
previous implementation still ran the intent-analysis LLM call and
silently fell back to the pre-extracted keywords inside execute() if the
model supplied no additionalContext -- partially defeating the purpose.

Changes:
- onRequestStart: skip the web-search extraction call entirely when
  prefillKeywords is false (knowledge-base extraction is unaffected).
- transformParams: when prefillKeywords is false, always register the
  builtin_web_search tool unconditionally so the model can reach for it;
  when true, keep the existing intent-gated behaviour.
- WebSearchTool execute: when prefillKeywords is false and the model
  provides no additionalContext, treat the call as not_needed instead of
  falling back to the pre-extracted queries.
- BasicSettings: prefix void on the setPrefillKeywords call to satisfy
  the no-floating-promises lint rule.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Karuboniru <yanqiyu01@gmail.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

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 7 out of 7 changed files in this pull request and generated 4 comments.


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

- Move preference key into generator sources: add chat.web_search.prefill_keywords
  to target-key-definitions.json and classification.json, then regenerate
  preferenceSchemas.ts and migration mappings so the key is durable.

- Cache prefillKeywords per-request: read the preference once in onRequestStart,
  store in prefillKeywordsPerRequest map, reuse in transformParams and clean up
  in onRequestEnd -- both phases are now guaranteed to see the same value.

- Validate non-empty queries before adding the web search tool in prefill mode:
  check that at least one query trims to a non-empty string in addition to the
  existing not_needed guard.

- Clearer signal for missing query in model-driven mode: throw a descriptive
  Error when the model calls the tool without additionalContext so the AI SDK
  surfaces it as a tool error and the model can retry with an explicit query,
  instead of silently returning an empty no-search-needed result.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Karuboniru <yanqiyu01@gmail.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

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 12 out of 12 changed files in this pull request and generated 3 comments.


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

karuboniru and others added 2 commits April 10, 2026 08:10
…cache leak

- WebSearchTool: throw a clear error when getWebSearchProvider() returns
  undefined instead of using a non-null assertion, so a mis-configured or
  not-yet-loaded provider id surfaces with an actionable message rather
  than a cryptic crash.

- WebSearchTool (model-driven mode): additionalContext is declared optional
  in the schema; calling the tool without a query now returns empty results
  (no search) consistent with the schema contract and PR description, instead
  of throwing. The provider guard above still throws for a genuinely broken
  provider, keeping the two failure modes distinct.

- searchOrchestrationPlugin: move per-request cache cleanup
  (intentAnalysisResults, userMessages, prefillKeywordsPerRequest) from the
  try block into a finally block so the caches are always cleared even when
  storeConversationMemory throws.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Karuboniru <yanqiyu01@gmail.com>
In model-driven mode (prefillKeywords=false) the tool has no pre-extracted
queries to fall back on, so a missing additionalContext is always an error.

- Split inputSchema into two separate Zod objects: optional additionalContext
  in prefill mode (existing behaviour), required additionalContext in
  model-driven mode. The AI SDK now validates the presence of the query
  before execute is called, rather than relying solely on a runtime guard.

- Keep a defensive throw inside execute for the model-driven branch so
  any blank/whitespace-only string (which passes Zod .string()) is still
  rejected with a clear message asking the model to retry.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Karuboniru <yanqiyu01@gmail.com>
@karuboniru karuboniru force-pushed the feat/web-search-disable-prefill-keywords branch from d691fcb to 39f5b8d Compare April 10, 2026 08:18
@karuboniru karuboniru requested a review from Copilot April 10, 2026 08:18
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

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 12 out of 12 changed files in this pull request and generated 4 comments.


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

Comment on lines +32 to +44
// In model-driven mode additionalContext is the sole source of the query — make it required
// so the AI SDK validates it before execute is called. In prefill mode it remains optional
// because the tool can fall back to the pre-extracted keywords.
inputSchema: prefillKeywords
? z.object({
additionalContext: z
.string()
.optional()
.describe('Optional additional context, keywords, or specific focus to enhance the search')
})
: z.object({
additionalContext: z.string().describe('Required: the search query or keywords to search the web')
}),
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

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

PR description says the schema keeps additionalContext optional in both modes and that when prefill is disabled a missing query is treated as “not needed”. In this implementation, additionalContext becomes required when prefillKeywords is false and an empty/blank value throws an error. Please either update the PR description to match, or adjust the schema/execute behavior to keep additionalContext optional and return an empty result when it’s absent (as described).

Copilot uses AI. Check for mistakes.
Comment on lines +55 to 63
if (!prefillKeywords) {
// Schema requires additionalContext in this mode; this guard is purely defensive.
const query = additionalContext?.trim()
if (!query) {
throw new Error('No search query provided. Please retry with a non-empty search query in additionalContext.')
}
const extractResults: ExtractResults = { websearch: { question: [query] } }
return webSearchService.processWebsearch(webSearchProvider, extractResults, requestId)
}
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

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

In model-driven mode (prefillKeywords === false), if the model passes the sentinel value "not_needed" as additionalContext, this will currently run a real web search for the literal string not_needed. Consider handling this the same way as prefill mode (returning an empty result) to avoid unnecessary requests and confusing outputs.

Copilot uses AI. Check for mistakes.
Comment on lines +55 to +62
if (!prefillKeywords) {
// Schema requires additionalContext in this mode; this guard is purely defensive.
const query = additionalContext?.trim()
if (!query) {
throw new Error('No search query provided. Please retry with a non-empty search query in additionalContext.')
}
const extractResults: ExtractResults = { websearch: { question: [query] } }
return webSearchService.processWebsearch(webSearchProvider, extractResults, requestId)
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

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

With prefillKeywords disabled, web intent analysis (including URL extraction) is skipped and the tool input only accepts a single additionalContext string. That makes the processWebsearch summarize-path (question[0] === 'summarize' + links[]) effectively unreachable in this mode, even if the user asks to summarize specific URLs. Consider extending the tool input to accept optional links (or parsing URLs from additionalContext) so summarization still works without the pre-extraction phase.

Copilot uses AI. Check for mistakes.
Comment on lines +287 to +293
// When prefillKeywords is disabled the model decides on its own when and what to search,
// so there is no point running the pre-extraction LLM call for web search.
// Read once and cache so transformParams sees the same value for this request.
const prefillKeywords = await preferenceService.get('chat.web_search.prefill_keywords')
prefillKeywordsPerRequest[context.requestId] = prefillKeywords
const shouldWebExtract = shouldWebSearch && prefillKeywords

Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

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

prefillKeywords is fetched and cached even when shouldWebSearch is false (e.g., assistants with only knowledge-base search enabled). Since preferenceService.get can trigger an IPC call on a cold cache, consider only reading this preference when shouldWebSearch is true, and defaulting prefillKeywordsPerRequest[requestId] to true otherwise.

Copilot uses AI. Check for mistakes.
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