fix(web): narrow default search review scope

This commit is contained in:
kingwl
2026-07-31 12:46:18 +08:00
parent 2b2f109dcb
commit 5759c287a2
23 changed files with 340 additions and 59 deletions

View File

@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write packages/web/tool-web/README.md
README.md: 7bee0d2d30fbbcf582fd7b60eb5d9130b6bdf888
README.zh.md: 3d708839c9ffbdd89df08678fd6997fc6c45ee07
README.md: 12f5c806db66b2109888c1ec642d117f3432d0df
README.zh.md: cfbf47219f160af706912ca53f85cff535c381ec

View File

@@ -4,7 +4,7 @@ English | [中文](README.zh.md)
The model-facing web tool suite — `web_search` and `web_fetch` — over the [web capability seam](../web/README.md) (`ctx.web`). It owns model-facing concerns only: tool names, JSON schemas, snake_case argument names, prompt sections, the result-count bound, result formatting, HTML→markdown presentation, and the UI presentation projection — `presentCall`, `presentResult` (a `card: 'web'` result card discriminated by `kind: 'search' | 'fetch'`), and the `output.presentationMeta` that carries the structured search sources or the fetch summary the lossy render text cannot (see the [web-result-card Agent Note](../../../.agents/notes/implemented/feature/2026-07-30-web-result-card.md)). All web access goes through `ctx.web`; this package never imports a concrete provider. Neither tool exposes a model-facing timeout — each tool's cooperative tool-call budget is declared here via config (`fetchTimeoutMs`/`searchTimeoutMs`, attached as `ToolDefinition.timeoutMs`) and enforced by [`@deepseek-ai/dsh-timeout-policy`](../../timeout/timeout-policy/README.md) (a `tools/execute` wrapper); each tool just forwards `exec.signal` to the seam.
Each tool is registered independently; a product that wants only one disables the other via config (`{ search: false }` / `{ fetch: false }`).
Each tool is registered independently; a product that wants only one disables the other via config (`{ search: false }` / `{ fetch: false }`). Search guidance mentions `web_fetch` only when fetch is also config-enabled; a search-only composition instead tells the model to use returned snippets and cite their URLs.
## Tools
@@ -47,14 +47,20 @@ The tool never calls a provider's `available()` and never enumerates providers
#### What the model sees
Search and fetch contribute the web-search and web-fetch guidance below. A scoped tool restriction does not remove these independently registered sections.
Search and fetch contribute the web-search and web-fetch guidance below. Search chooses its fetch-enabled or search-only text from config at registration time. A scoped tool restriction does not remove these independently registered sections.
##### Web search guidance
##### Web search guidance with fetch enabled
```markdown
Use the web_search tool to discover current information on the web. It returns an optional answer plus a list of source URLs. Follow up with web_fetch when you need the full content of a specific result, and cite the relevant URLs as markdown links.
```
##### Web search-only guidance
```markdown
Use the web_search tool to discover current information on the web. It returns an optional answer plus a list of source URLs. Use the returned source snippets when available, and cite the relevant URLs as markdown links.
```
##### Web fetch guidance
```markdown
@@ -63,11 +69,11 @@ Use the web_fetch tool to retrieve the content of a specific HTTP(S) URL (for ex
#### Token effect
Fixed guidance cost per request for each config-enabled tool, even when a restriction hides its schema.
Fixed guidance cost per request for each config-enabled tool, even when a restriction hides its schema. Toggling fetch changes the search guidance as well as registering or removing the fetch section.
#### KV Cache effect
Prefix-stable while enabled tools, scope, and guidance text are unchanged. Config enablement or plugin lifecycle may invalidate reuse from the first changed prompt section; scoped schema restrictions do not remove it.
Prefix-stable while enabled tools, scope, and guidance text are unchanged. Config enablement—including toggling fetch's search-guidance branch—or plugin lifecycle may invalidate reuse from the first changed prompt section; scoped schema restrictions do not remove it.
### Tool schemas

View File

@@ -4,7 +4,7 @@
面向模型的 web 工具套件 `web_search``web_fetch`,构建于 [web 能力 seam](../web/README.md)`ctx.web`之上。它只负责面向模型的事项工具名称、JSON Schema、snake_case 参数名称、提示词区段、结果数量上限、结果格式、HTML→markdown 呈现,以及 UI 呈现投影——`presentCall``presentResult`(以 `kind: 'search' | 'fetch'` 区分的 `card: 'web'` 结果卡片),以及承载有损渲染文本无法携带的结构化搜索来源或抓取摘要的 `output.presentationMeta`(见 [web-result-card Agent Note](../../../.agents/notes/implemented/feature/2026-07-30-web-result-card.md))。所有 web 访问都通过 `ctx.web`该包package绝不导入具体提供方。两个工具都不公开面向模型的超时每个工具的协作式工具调用超时预算通过配置在此声明`fetchTimeoutMs``searchTimeoutMs`,附加为 `ToolDefinition.timeoutMs`),由 [`@deepseek-ai/dsh-timeout-policy`](../../timeout/timeout-policy/README.md)`tools/execute` 包装层)强制执行;每个工具只把 `exec.signal` 转发给 seam。
每个工具独立注册;只需要其中一个工具的产品可以通过配置禁用另一个(`{ search: false }``{ fetch: false }`)。
每个工具独立注册;只需要其中一个工具的产品可以通过配置禁用另一个(`{ search: false }``{ fetch: false }`)。仅当抓取也通过配置启用时,搜索指引才会提及 `web_fetch`;仅启用搜索的组合则会要求模型使用返回的 snippet 并引用其 URL。
## 工具
@@ -47,14 +47,20 @@
#### 模型看到的内容
搜索与抓取分别贡献以下 web-search 和 web-fetch 指引。scope 工具限制不会移除这些独立注册的区段。
搜索与抓取分别贡献以下 web-search 和 web-fetch 指引。搜索会在注册时根据配置选用启用抓取或仅搜索的文本。scope 工具限制不会移除这些独立注册的区段。
##### Web 搜索指引
##### 启用抓取时的 Web 搜索指引
```markdown
Use the web_search tool to discover current information on the web. It returns an optional answer plus a list of source URLs. Follow up with web_fetch when you need the full content of a specific result, and cite the relevant URLs as markdown links.
```
##### 仅搜索时的 Web 搜索指引
```markdown
Use the web_search tool to discover current information on the web. It returns an optional answer plus a list of source URLs. Use the returned source snippets when available, and cite the relevant URLs as markdown links.
```
##### Web 抓取指引
```markdown
@@ -63,11 +69,11 @@ Use the web_fetch tool to retrieve the content of a specific HTTP(S) URL (for ex
#### Token 影响
每个通过配置启用的工具都会为每次请求增加固定的指引 token 开销,即使限制隐藏了其 schema。
每个通过配置启用的工具都会为每次请求增加固定的指引 token 开销,即使限制隐藏了其 schema。切换抓取状态不仅会注册或移除抓取区段,也会更改搜索指引。
#### KV Cache 影响
只要启用工具、scope 与指引文本不变前缀就保持稳定。配置启用状态或插件生命周期可能使从第一个变化的提示词区段起的复用失效scope schema 限制不会移除该区段。
只要启用工具、scope 与指引文本不变,前缀就保持稳定。配置启用状态(包括因切换抓取状态而改变搜索指引分支)或插件生命周期可能使从第一个变化的提示词区段起的复用失效scope schema 限制不会移除该区段。
### 工具 schema

View File

@@ -84,6 +84,8 @@ export function apply(ctx: Context, config: Config): void {
assertPositiveInteger('fetchTimeoutMs', resolved.fetchTimeoutMs)
assertPositiveInteger('searchTimeoutMs', resolved.searchTimeoutMs)
assertPositiveInteger('fetchMaxOutputChars', resolved.fetchMaxOutputChars)
if (resolved.search) applyWebSearchTool(ctx, resolved.searchMaxResults, resolved.searchTimeoutMs)
if (resolved.search) {
applyWebSearchTool(ctx, resolved.searchMaxResults, resolved.searchTimeoutMs, resolved.fetch)
}
if (resolved.fetch) applyWebFetchTool(ctx, resolved.fetchTimeoutMs, resolved.fetchMaxOutputChars)
}

View File

@@ -204,12 +204,21 @@ export function presentSearchResult(args: { query: string }, result: ToolResult)
* request's `maxResults`.
* @param timeoutMs - the cooperative tool-call budget (ms) attached as the tool's
* `ToolDefinition.timeoutMs` for `@deepseek-ai/dsh-timeout-policy` to enforce.
* @param fetchEnabled - whether the same composition exposes `web_fetch`, which
* controls whether search guidance may recommend that follow-up tool.
*/
export function applyWebSearchTool(ctx: Context, maxResults: number, timeoutMs: number): void {
export function applyWebSearchTool(
ctx: Context,
maxResults: number,
timeoutMs: number,
fetchEnabled: boolean,
): void {
ctx.systemPrompt.section({
name: 'tool:web_search',
order: 110,
text: 'Use the web_search tool to discover current information on the web. It returns an optional answer plus a list of source URLs. Follow up with web_fetch when you need the full content of a specific result, and cite the relevant URLs as markdown links.',
text: fetchEnabled
? 'Use the web_search tool to discover current information on the web. It returns an optional answer plus a list of source URLs. Follow up with web_fetch when you need the full content of a specific result, and cite the relevant URLs as markdown links.'
: 'Use the web_search tool to discover current information on the web. It returns an optional answer plus a list of source URLs. Use the returned source snippets when available, and cite the relevant URLs as markdown links.',
})
ctx.tools.register(defineTool({

View File

@@ -483,8 +483,17 @@ describe('tool-web registration', () => {
const { fiber, ctx } = await mountTools()
const prompt = await ctx.systemPrompt.assemble()
const text = prompt.sections.map(s => s.text).join('\n')
expect(text).toContain('web_search')
expect(text).toContain('web_fetch')
expect(text).toContain('Use the web_search tool to discover current information on the web. It returns an optional answer plus a list of source URLs. Follow up with web_fetch when you need the full content of a specific result, and cite the relevant URLs as markdown links.')
expect(text).toContain('Use the web_fetch tool to retrieve the content of a specific HTTP(S) URL')
await fiber.dispose()
})
it('does not advertise web_fetch in search-only prompt guidance', async () => {
const { fiber, ctx } = await mountTools({ config: { search: true, fetch: false } })
const prompt = await ctx.systemPrompt.assemble()
const text = prompt.sections.map(s => s.text).join('\n')
expect(text).toContain('Use the returned source snippets when available')
expect(text).not.toContain('web_fetch')
await fiber.dispose()
})
})