10. Prompt Suggestions(智能补全)——开发者参考

April 7, 2026 · View on GitHub

预测用户下一步操作并提前生成建议,Tab 接受后零延迟执行(Speculation 推测执行)。Qwen Code 已实现但默认关闭(PR#2525 ✓ 已合并)。

Qwen Code 对标:suggestion 生成流程、12 条过滤规则(避免低质量建议)、Speculation 推测执行(预测 Tab 接受并提前执行 API 调用)

内部代号tengu_chomp_inflection(GrowthBook feature flag)

为什么需要 Prompt Suggestions

问题定义

Code Agent 的典型交互模式是:用户输入 → Agent 执行 → 用户输入下一步。在"Agent 执行完 → 用户开始输入"之间有一个决策空白期——用户需要思考"接下来让 Agent 做什么"。

对于 80%+ 的场景,下一步操作是可预测的:

Agent 刚做完用户通常会说
修改了代码"运行测试"
测试失败了"修复这个失败"
创建了文件"在这里添加更多功能"
审查了 PR"提交这些修改"

设计理念:Speculation(推测执行)

Claude Code 的 Suggestion 系统不只是"显示建议文本"——它在生成建议的同时,假设用户会接受,提前向 API 发起推理请求。当用户按 Tab 时,结果已经在路上甚至已经返回,实现零感知延迟

传统流程(无 Speculation):
  Agent 完成 → 显示建议 → 用户按 Tab → 发送 API 请求 → 等待 2-5s → 开始执行
                                      ↑ 延迟在这里

Speculation 流程:
  Agent 完成 → 显示建议 → 同时发送 API 请求 → 用户按 Tab → 结果已就绪 → 立即执行
                           ↑ 延迟被隐藏

竞品对比

Agent智能补全推测执行过滤机制
Claude Code✓ 每轮结束后生成✓ Speculation + Prompt Cache 共享12 条规则
Gemini CLI✓ Follow-up suggestions基础过滤
Qwen Code✓ 已实现但默认关闭✓ 已实现(PR#2525)需完善
Copilot CLI
Cursor✓ Tab 补全✓ Speculative edits编辑器级过滤

关键风险:低质量建议的代价

如果建议质量差(如反复建议"继续"、建议已完成的操作),用户会停止阅读建议——这比没有建议更糟,因为偶尔的好建议也会被忽略。Claude Code 用 12 条过滤规则解决这个问题。

功能概述

Claude Code 在每轮 assistant 回复完成后,自动预测用户下一步可能输入的内容,以蓝紫色提示文本显示在输入框中。用户可通过 Tab/Enter 接受,或直接输入覆盖。

架构概览

┌─────────────────────────────────────────────────────────────────┐
│  stopHooks.ts                                                   │
│  query/stopHooks.ts#L139                                        │
│  每轮 assistant 回复完成后触发                                   │
│  void executePromptSuggestion(stopHookContext)                  │
└────────────────────┬────────────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────────┐
│  executePromptSuggestion()                                      │
│  services/PromptSuggestion/promptSuggestion.ts#L184             │
│  仅处理 querySource === 'repl_main_thread' 的主线程请求          │
│                                                                 │
│  1. tryGenerateSuggestion() — 守卫检查 + 生成 + 过滤             │
│  2. 写入 AppState.promptSuggestion                              │
│  3. 如果 Speculation 启用 → startSpeculation()                  │
└────────────────────┬────────────────────────────────────────────┘

              ┌──────┴──────┐
              ▼             ▼
┌──────────────────────┐  ┌──────────────────────────────────────┐
│ usePromptSuggestion  │  │ startSpeculation()                   │
│ hooks/               │  │ services/PromptSuggestion/           │
│ usePromptSuggestion  │  │ speculation.ts                       │
│ .ts                  │  │ 以 suggestion 为假设输入预执行 agent  │
│                      │  │ (仅限 Anthropic 内部用户启用)         │
│ 管理 UI 显示         │  └──────────────────────────────────────┘
│ Tab/Enter 接受       │
│ 遥测日志             │
└──────────────────────┘

生成流程

触发入口

每轮 assistant 回复完成后,在 stop hooks 阶段以 fire-and-forget 方式异步发起:

// 源码: query/stopHooks.ts#L138-139
if (!isEnvDefinedFalsy(process.env.CLAUDE_CODE_ENABLE_PROMPT_SUGGESTION)) {
  void executePromptSuggestion(stopHookContext)
}

--bare 模式(最小化模式,跳过 hooks、LSP、插件同步等)和 -p(非交互管道模式)均跳过 suggestion 生成。

API 调用方式

源码: services/PromptSuggestion/promptSuggestion.ts#L294-352

调用 runForkedAgent() 发起独立的 API 请求:

  • 缓存复用:复用主对话的 cacheSafeParams,刻意不覆盖任何 API 参数(不设 effortValuemaxOutputTokens 等),以确保命中主对话的 prompt cache
  • 独立标记querySource: "prompt_suggestion"forkLabel: "prompt_suggestion"
  • 不写 transcriptskipTranscript: true, skipCacheWrite: true
  • 禁止工具:所有工具调用通过 canUseTool 回调拒绝(behavior: "deny"),模型只能返回纯文本

历史教训:据源码注释(promptSuggestion.ts#L308-318),Anthropic 内部曾尝试设置 effort:'low' 降低 suggestion 成本,结果导致 cache 命中率从 92.7% 暴跌至 61%(45x cache write spike)。billing cache key 包含的参数比文档描述的更多,任何差异都会 bust cache。

Suggestion Prompt

源码: services/PromptSuggestion/promptSuggestion.ts#L258-287,常量 SUGGESTION_PROMPT

[SUGGESTION MODE: Suggest what the user might naturally type next into Claude Code.]

FIRST: Look at the user's recent messages and original request.

Your job is to predict what THEY would type - not what you think they should do.

THE TEST: Would they think "I was just about to type that"?

EXAMPLES:
User asked "fix the bug and run tests", bug is fixed → "run the tests"
After code written → "try it out"
Claude offers options → suggest the one the user would likely pick, based on conversation
Claude asks to continue → "yes" or "go ahead"
Task complete, obvious follow-up → "commit this" or "push it"
After error or misunderstanding → silence (let them assess/correct)

Be specific: "run the tests" beats "continue".

NEVER SUGGEST:
- Evaluative ("looks good", "thanks")
- Questions ("what about...?")
- Claude-voice ("Let me...", "I'll...", "Here's...")
- New ideas they didn't ask about
- Multiple sentences

Stay silent if the next step isn't obvious from what the user said.

Format: 2-12 words, match the user's style. Or nothing.

Reply with ONLY the suggestion, no quotes or explanation.

Prompt 通过 PromptVariant 类型索引(源码: promptSuggestion.ts#L31-35),定义了 'user_intent''stated_intent' 两个变体,均映射到同一模板。getPromptVariant() 当前始终硬编码返回 'user_intent''stated_intent' 为预留变体,未被使用。

过滤机制

源码: services/PromptSuggestion/promptSuggestion.ts#L354-456,函数 shouldFilterSuggestion

生成的 suggestion 经过 12 条过滤规则严格筛选,不满足条件的被静默丢弃:

过滤规则说明匹配示例
done内容恰好为 "done"done
meta_text模型输出元描述而非真实预测"nothing to suggest"、"silence"、"nothing found"
meta_wrapped被括号包裹的元推理(silence — ...)[no suggestion]
error_messageAPI 错误信息泄漏"api error: ..."、"prompt is too long"、"image was too large"
prefixed_labelword: 标签前缀"Next step: run tests"
too_few_words少于 2 个单词(允许斜杠命令和特定单词)单个普通单词(非白名单词)
too_many_words超过 12 个单词过长的句子
too_long≥100 个字符
multiple_sentences包含多个句子(/[.!?]\s+[A-Z]/"Do this. Then that."
has_formatting包含换行符或 Markdown 格式\n***
evaluative评价性/感谢语句"looks good"、"thanks"、"perfect"、"awesome"
claude_voice模型自身语气开头"Let me..."、"I'll..."、"Here's..."、"You should..."

单词白名单(源码: promptSuggestion.ts#L403-424,即使只有 1 个单词也不过滤):

类别单词
肯定词yes, yeah, yep, yea, yup, sure, ok, okay
动作词push, commit, deploy, stop, continue, check, exit, quit
否定词no

交互方式

操作效果遥测 acceptMethod
Tab接受 suggestion 填入输入框(可继续编辑后再提交)tab
Enter(输入框为空时)接受 suggestion 并直接提交enter
(右箭头)接受 suggestion 填入输入框
开始输入其他内容suggestion 自动消失,Speculation 被中止
忽略(直接输入新内容提交)suggestion 在下一轮对话后被新预测替换ignored

接受判定逻辑(源码: hooks/usePromptSuggestion.ts#L116-117):

  • Tab 按下:acceptedAt > shownAt
  • 或:用户最终提交内容 === suggestion 文本(空 Enter 场景)

状态数据结构

源码: state/AppStateStore.ts#L385-393

promptSuggestion: {
  text: string | null           // suggestion 文本内容,无 suggestion 时为 null
  promptId: 'user_intent' | 'stated_intent' | null  // prompt 变体标识,无 suggestion 时为 null
  shownAt: number               // 首次渲染时间戳(Date.now() ms),未显示时为 0
  acceptedAt: number            // Tab 接受时间戳(Date.now() ms),未接受时为 0
  generationRequestId: string | null  // 关联的 API 请求 ID(用于 RL 数据集关联)
}

默认值语义shownAtacceptedAt 均为 number 类型,以 0 表示「未触发」。时间戳单位为 Date.now() 返回的毫秒(ms since Unix epoch)。接受判定使用 acceptedAt > shownAt(源码: usePromptSuggestion.ts#L116),因此 0 > 0 为 false 即表示未接受。每次新 suggestion 写入时,两个字段均重置为 0

抑制条件(三层守卫)

初始化守卫

源码: promptSuggestion.ts#L37-94,函数 shouldEnablePromptSuggestion

检查顺序条件结果
1环境变量显式为 falsy(0/false/no/off强制禁用
2环境变量显式为 truthy(1/true/yes/on强制启用
3环境变量未设置或空字符串 → 进入后续判定
4GrowthBook flag tengu_chomp_inflection 为 false禁用
5非交互模式(-p、管道输入、SDK)禁用
6Swarm teammate(非 leader)禁用
7settings.promptSuggestionEnabled !== false按设置值

环境变量解析(源码: utils/envUtils.ts#L32-47):isEnvDefinedFalsy() 仅在变量已设置且值为 0/false/no/off(不区分大小写)时返回 true;isEnvTruthy() 仅在值为 1/true/yes/on 时返回 true。变量未设置(undefined)或空字符串时两者均返回 false,继续进入 GrowthBook 等后续判定。

运行时守卫

源码: promptSuggestion.ts#L107-119,函数 getSuggestionSuppressReason

条件抑制原因
promptSuggestionEnabled === falsedisabled
存在待审批的 Worker/Sandbox 权限请求pending_permission
MCP elicitation 队列非空elicitation_active
Plan mode 激活plan_mode
外部用户且速率限制触发rate_limit

生成前守卫

源码: promptSuggestion.ts#L125-182,函数 tryGenerateSuggestion

条件抑制原因
AbortController 已中止aborted
assistant 回复不足 2 轮early_conversation
上一条回复是 API 错误last_response_error
父消息总 token 数(input_tokens + cache_creation_input_tokens + output_tokens)> 10,000cache_cold

配置方式

方式说明
/config → "Prompt suggestions"交互式配置菜单中切换开关
settings.json 中设置 "promptSuggestionEnabled": false持久化关闭
环境变量 CLAUDE_CODE_ENABLE_PROMPT_SUGGESTION=0强制关闭(优先级最高)
环境变量 CLAUDE_CODE_ENABLE_PROMPT_SUGGESTION=1强制开启(优先级最高)
GrowthBook feature flag tengu_chomp_inflection服务端灰度发布控制

UI 样式

suggestion 文本使用主题中的 suggestion 颜色渲染:

主题颜色
Lightrgb(87, 105, 247)(蓝紫色)
Darkrgb(177, 185, 249)(浅蓝紫色)
High Contrast Lightrgb(51, 102, 255)
High Contrast Darkrgb(153, 204, 255)
ANSI Lightansi:blue
ANSI Darkansi:blueBright

遥测事件

初始化事件

事件名: tengu_prompt_suggestion_init(源码: promptSuggestion.ts#L41-92

字段说明
enabled是否启用
source决策来源:env / growthbook / non_interactive / swarm_teammate / setting

结果事件

事件名: tengu_prompt_suggestion(源码: hooks/usePromptSuggestion.ts#L120-157promptSuggestion.ts#L462-523

字段说明
sourcecli(TUI)或 sdk(API 消费方)
outcomeaccepted / ignored / suppressed
prompt_iduser_intent / stated_intent
reason抑制原因(仅 suppressed 时)
acceptMethodtab / enter(仅 CLI 且 accepted 时)
timeToAcceptMs从显示到接受的毫秒数
timeToIgnoreMs从显示到忽略的毫秒数
timeToFirstKeystrokeMs从显示到首次按键的毫秒数
wasFocusedWhenShownsuggestion 出现时终端是否有焦点
similarityfinalInput.length / suggestion.length(相似度)

Anthropic 内部用户(USER_TYPE === 'ant')额外记录 suggestionuserInput 原文,用于 RL 数据集训练。

Speculation(推测执行)

Prompt Suggestions 是更深层 Speculation 系统的触发器。当 suggestion 生成后,系统立即使用该 suggestion 作为假设的用户输入,预执行一轮 agent 响应。

启用条件

源码: speculation.ts#L337-343

export function isSpeculationEnabled(): boolean {
  const enabled =
    process.env.USER_TYPE === 'ant' &&
    (getGlobalConfig().speculationEnabled ?? true)
  return enabled
}

注意:Speculation 仅对 Anthropic 内部用户启用(USER_TYPE === 'ant'),外部用户仅使用 Prompt Suggestions 文本预测功能。

核心参数

源码: services/PromptSuggestion/speculation.ts#L58-70

const MAX_SPECULATION_TURNS = 20    // 最大推测轮数
const MAX_SPECULATION_MESSAGES = 100 // 最大消息数

// 允许在推测中执行的工具
const WRITE_TOOLS = new Set(['Edit', 'Write', 'NotebookEdit'])
const SAFE_READ_ONLY_TOOLS = new Set([
  'Read', 'Glob', 'Grep', 'ToolSearch', 'LSP', 'TaskGet', 'TaskList'
])

文件隔离机制(Copy-on-Write Overlay)

源码: speculation.ts#L80-81, #L402-715

  • 推测执行在独立目录中进行:$CLAUDE_TEMP_DIR/speculation/{pid}/{id}/
  • 写操作使用 Copy-on-Write:首次写入时将原文件复制到 overlay 目录,后续读写均重定向到 overlay
  • CWD 外的写操作被拒绝
  • 接受时:overlay 文件复制回主目录(copyOverlayToMain);中止时:overlay 直接删除(safeRemoveOverlay

边界检测

CompletionBoundary 类型(源码: state/AppStateStore.ts#L41-50):

边界类型触发条件行为
completeagent 自然完成记录 outputTokens
bash非只读 Bash 命令(只读命令如 ls/grep/cat 允许执行)中止推测
edit文件编辑但权限不足(非 acceptEdits/bypassPermissions 模式)中止推测
denied_tool不在允许列表中的工具(记录 detail: URL/路径/命令,截取前 200 字符)中止推测

Speculation 通过 onMessage 回调实时追踪消息数量,达到 MAX_SPECULATION_MESSAGES(100)时自动中止,防止 speculation 无限运行(源码: speculation.ts#L637-641)。

Pipeline 机制

源码: speculation.ts#L345-400,函数 generatePipelinedSuggestion

推测执行完成后,如果用户尚未做出响应,会立即生成下一轮 suggestion。当用户接受当前 suggestion 时,仅在 speculation 自然完成(boundary.type === 'complete')时,pipelined suggestion 才会被提升为新的 suggestion 显示并启动新一轮 speculation;若 speculation 因 bash/edit/denied_tool 边界中止,pipelined suggestion 会被丢弃(源码: speculation.ts#L928-929)。

用户发送消息 → Claude 回复
  → 生成 suggestion A → 开始 speculation A
    → speculation A 完成 → 生成 pipelined suggestion B
      → 用户接受 A → 提升 B 为当前 suggestion → 开始 speculation B
        → ...

Speculation 使用独立的 fork 标签:querySource: 'speculation'forkLabel: 'speculation'(源码: speculation.ts#L633-634),与 suggestion 生成的 querySource: 'prompt_suggestion' 区分。

接受后处理

用户接受 suggestion 后,接受流程由两个函数协作完成:

  • handleSpeculationAccept()(源码: speculation.ts#L835-991):React 层,负责状态更新、消息注入、pipeline promotion
  • acceptSpeculation()(源码: speculation.ts#L717-800):底层,负责 overlay 回写、transcript 记录、timeSavedMs 计算

执行步骤:

  1. 用户消息优先注入:立即将用户输入显示在 UI 中,确保即时视觉反馈(源码: speculation.ts#L875-876

  2. 消息清洗prepareMessagesForInjection,源码: speculation.ts#L203-271):

    • 过滤 thinkingredacted_thinking
    • 移除未成功完成的 tool_use/tool_result
    • 移除中断消息(INTERRUPT_MESSAGE
    • 过滤全空白文本消息(避免 API 400 错误)
    • 若 speculation 未完成,丢弃尾部 assistant 消息(不支持 prefill 的模型拒绝以 assistant turn 结尾)
  3. 文件状态合并:将 speculation 读取的文件状态缓存合并到主对话,避免重复读取(源码: speculation.ts#L910-917

  4. Overlay 回写:将 overlay 目录中修改的文件复制回主目录

  5. 反馈消息注入(仅 USER_TYPE === 'ant',源码: speculation.ts#L273-308):

    [ANT-ONLY] Speculated 3 tool uses · 1,234 tokens · +2.1s saved (5.3s this session)
    
  6. Transcript 记录:写入 speculation-accept 条目到 JSONL transcript(源码: speculation.ts#L784-794),用于统计会话累计节省时间

Speculation 遥测

事件名: tengu_speculation(源码: speculation.ts#L124-153

字段说明
speculation_id推测会话 UUID(前 8 位)
outcomeaccepted / aborted / error
duration_ms推测执行耗时
suggestion_lengthsuggestion 文本长度
tools_executed成功返回结果的工具调用数(计数 tool_result && !is_error
completed是否到达边界(boundary !== null
boundary_typecomplete / bash / edit / denied_tool
boundary_tool触发边界的工具名
boundary_detail触发边界的命令/路径(截取前 200 字符)
message_count推测消息总数(仅 accepted 时)
time_saved_ms从 speculation 开始到 min(接受时间, 边界完成时间) 的毫秒数(仅 accepted 时)
is_pipelined是否为 pipeline 产生的推测

源码文件索引

文件LOC职责
services/PromptSuggestion/promptSuggestion.ts524核心服务:启用检查、生成、过滤、遥测
services/PromptSuggestion/speculation.ts992推测执行:overlay 隔离、边界检测、pipeline
hooks/usePromptSuggestion.ts178React Hook:UI 状态管理、接受/显示/遥测
components/PromptInput/PromptInput.tsx输入框组件:集成 suggestion 显示与 Enter 接受
components/PromptInput/useTypeahead.tsxTab/→ 键接受与 ghost text 渲染
state/AppStateStore.ts状态定义:promptSuggestion + speculation + speculationSessionTimeSavedMs
query/stopHooks.ts入口:在 stop hooks 中 fire-and-forget 调用
components/Settings/Config.tsx/config 菜单中的开关切换