首页
/ gemini-cli 模型转向(Model Steering)实战指南:在 Agent 执行中实时纠偏而不必中断会话

gemini-cli 模型转向(Model Steering)实战指南:在 Agent 执行中实时纠偏而不必中断会话

2026-09-04 23:34:53作者:廉皓灿Ida

本文基于 docs/cli/model-steering.md 展开,讲解 gemini-cli 的实验性功能——模型转向(Model Steering):它允许你在 Agent 正在执行任务(工具调用、Plan Mode 研究、长时子代理运行)时,直接在输入框键入一条"提示"(hint)并按 Enter,CLI 会用快速模型生成一句确认消息,并在下一个模型轮次将你的指引连同内置指令一起注入主对话上下文,让模型重新评估当前计划并调整行为。读完本文,你将掌握如何开启该功能、在什么时机给出什么类型的提示,以及从源码层面理解"确认消息生成—上下文注入—实时投递"的完整实现链路。

什么是模型转向

传统的人机交互模式是:提交任务 → 等待 Agent 跑完 → 看到结果不满意 → 重新发起任务。这种方式在复杂任务中代价很高:Agent 可能已经花了多轮工具调用走错了方向,你只能推倒重来。

Model steering 解决的就是这个问题。官方文档将其定位为:

Model steering lets you provide real-time guidance and feedback to Gemini CLI while it is actively executing a task. This lets you correct course, add missing context, or skip unnecessary steps without having to stop and restart the agent.

需要注意两点适用前提(引自 docs/cli/model-steering.md):

  • 这是一个实验性功能,目前处于积极开发中,默认关闭,需要在 /settings 中显式启用;
  • 它在复杂的 Plan Mode 工作流或长时间运行的子代理(subagent)执行中尤其有用,因为你可以在 Agent 跑偏之前把它拉回正轨。

开启模型转向

该功能在设置中位于实验性功能区。从 设置模式定义 可以看到 experimental.modelSteering 的完整元数据:

modelSteering: {
  type: 'boolean',
  label: 'Model Steering',
  category: 'Experimental',
  requiresRestart: false,   // 修改后即时生效,无需重启
  default: false,            // 默认关闭
  description:
    'Enable model steering (user hints) to guide the model during tool execution.',
  showInDialog: true,
},

有两个实用细节值得注意:requiresRestart: false 说明在 /settings 中切换该开关无需重启 CLI 即可生效;showInDialog: true 说明它会直接出现在设置对话框中,可以直接搜索定位。

方式一:通过 /settings 命令

  1. 在 Gemini CLI 中输入 /settings
  2. 搜索 Model Steering
  3. 将值设置为 true

方式二:通过 settings.json

settings.json 中追加:

{
  "experimental": {
    "modelSteering": true
  }
}

该配置项的类型、默认值与描述也可在 docs/reference/configuration.mdexperimental.modelSteering 条目中确认(默认 false)。在核心配置层,config.ts 中通过参数 modelSteering?: boolean 构造配置对象,默认取 false,并对外暴露 isModelSteeringEnabled() 查询方法(见 packages/core/src/config/config.ts)。

使用模型转向:操作与常见场景

启用后,Agent 工作期间你在输入框键入的任何文本都会被当作转向提示。文档给出的标准操作三步曲:

  1. 启动一个任务(例如 "Refactor the database service");
  2. 当 Agent 正在工作时(可见加载动画/spinner),在输入框中输入你的反馈;
  3. Enter

Gemini CLI 会用一条简短消息确认收到,并将提示直接注入到下一个轮次的模型上下文中。模型随后重新评估当前计划并相应调整行为。

典型提示类型与示例

文档列出的五类常见用法,可以直接作为话术模板:

场景 提示示例
纠正路径(Correcting a path) "Actually, the utilities are in src/common/utils."
跳过步骤(Skipping a step) "Skip the unit tests for now and just focus on the implementation."
补充上下文(Adding context) "The User type is defined in packages/core/types.ts."
重新定向(Redirecting the effort) "Stop searching the codebase and start drafting the plan now."
处理歧义(Handling ambiguity) "Use the existing Logger class instead of creating a new one."

配套的教程文档 docs/cli/tutorials/plan-mode-steering.md 给出了一个 Plan Mode 下的完整案例:用 /plan I want to implement a new notification service using Redis. 启动研究后,在 Agent 调用 list_directory / grep_search 期间注入 "Don't forget to check packages/common/queues for the existing Redis config.",再在起草阶段注入 "use a Publisher/Subscriber pattern instead of a simple queue",最终在计划满意后输入 "Looks perfect. Let's start the implementation." 退出 Plan Mode。该文档同时总结了三条实战技巧:提示要具体(给出文件和类名而不是"换个方式做")、尽早转向(研究阶段纠偏比等计划写完更高效)、用它传递代码里读不到的隐性知识(如"这个模块下月要下线")。

底层机制:确认、注入与实时投递

文档 "How it works" 一节描述了三个步骤,下面逐一对照源码说明其实现。

1. 即时确认:用一个快速小模型生成一句话回执

fastAckHelper.ts 中定义了专门的"转向确认"提示词与限参:

const STEERING_ACK_INSTRUCTION =
  'Write one short, friendly sentence acknowledging a user steering update for an in-progress task. ' +
  'Be concrete when possible (e.g., mention skipped/cancelled item numbers). ' +
  'Do not apologize, do not mention internal policy, and do not add extra steps.';
const STEERING_ACK_TIMEOUT_MS = 1200;
const STEERING_ACK_MAX_INPUT_CHARS = 320;
const STEERING_ACK_MAX_OUTPUT_CHARS = 90;

(见 packages/core/src/utils/fastAckHelper.ts

几个实现要点:

  • 1.2 秒硬超时generateSteeringAckMessage 通过 AbortController 限制确认消息生成最多等待 1200ms,且 maxAttempts: 1 不重试——这是"fast path",绝不让确认动作拖慢 Agent 主流程;
  • 输出上限 90 字符:超过则按图素(grapheme)安全截断,保证"一句话"的承诺;
  • 本地兜底:若模型调用失败或超时,回退到本地模板,例如 Understood. ${hint 前 64 字符},即使用户提示为空也会返回 "Understood. Adjusting the plan.";
  • 模型可路由:默认走 fast-ack-helper 模型配置键(DEFAULT_FAST_ACK_MODEL_CONFIG_KEY),确认消息的生成与主模型解耦。

2. 上下文注入:提示被包装成带内部指令的结构化消息

你键入的原始文本不会"裸奔"进模型上下文。fastAckHelper.ts 中的 buildUserSteeringHintPrompt 会做两件事:

  1. 将用户提示规范化(空白折叠、去首尾空格)后包裹进 <user_input> 标签——注释明确说明这是为了缓解提示注入("Wraps user input in XML-like tags to mitigate prompt injection");
  2. 前置一条内置指令 USER_STEERING_INSTRUCTION
export const USER_STEERING_INSTRUCTION =
  'Internal instruction: Re-evaluate the active plan using this user steering update. ' +
  'Classify it as ADD_TASK, MODIFY_TASK, CANCEL_TASK, or EXTRA_CONTEXT. ' +
  'Apply minimal-diff changes only to affected tasks and keep unaffected tasks active. ' +
  'Do not cancel/skip tasks unless the user explicitly cancels them. ' +
  'Acknowledge the steering briefly and state the course correction.';

这段内部指令精确对应了文档中"Context injection"一步的语义:重新评估活跃计划、对更新分类(新任务 / 额外上下文等)、对受影响任务做最小差异修改。此外它还加了护栏——除非用户明确取消,否则不要取消/跳过任务。

若一个轮次内积攒了多条提示,formatUserHintsForModel 会把它们合并成 User hints: 列表(每条一行、用 - 前缀)再统一包裹注入。

3. 投递时机:在下一个轮次边界"抢先"注入

主对话的投递路径在 packages/cli/src/ui/hooks/useGeminiStream.ts:当一个工具轮结束、准备把工具结果提交给模型继续时,代码会消费待处理的 hint,并将其 unshift 到响应部件的最前面

if (consumeUserHint) {
  const userHint = consumeUserHint();
  if (userHint && userHint.trim().length > 0) {
    const hintText = userHint.trim();
    responsesToSend.unshift({
      text: buildUserSteeringHintPrompt(hintText),
    });
  }
}

unshift 意味着提示会排在工具执行结果之前进入下一轮请求,模型先看到你的指令,再看到工具输出,从而保证"最即时的纠偏"。

用户侧的入口在 packages/cli/src/ui/AppContainer.tsxhandleHintSubmit 把非空提示以 user_steering 来源写入 config.injectionService,并用独立的 hint 样式渲染到会话历史中(视觉上区别于普通用户消息)。

开关在注入层生效

user_steering 这类注入受功能开关控制,而后台任务完成(background_completion)等其他来源始终放行——这正是"实验功能默认关闭"的落点:

addInjection(text: string, source: InjectionSource): void {
  if (source === 'user_steering' && !this.isEnabled()) {
    return;  // 未启用 model steering 时,用户提示被静默丢弃
  }
  ...
}

(见 packages/core/src/config/injectionService.ts

值得说明的是,InjectionService 是一个多来源注入总线user_steering(交互式转向,受开关门控)与 background_completion(后台执行完成的原始输出,同样会被 <background_output> 标签包裹并要求模型"当作数据而非指令")共用同一套监听/投递机制。

子代理场景:监听广播 + 队列补投

在子代理执行器 packages/core/src/agents/local-executor.ts 中,实现多了一层"不漏消息"的容错:

  • 代理启动时先记录 injectionService 的当前注入索引 startIndex,并注册监听器,把后续 user_steering 注入推入 pendingHintsQueue
  • 同时用 getInjectionsAfter(startIndex, 'user_steering') 把启动瞬间已到达但尚未消费的提示取出来,作为初始 parts 与记忆文件、查询语句一起发给模型;
  • 每次工具轮结束、进入下一轮前,若队列非空,就用 formatUserHintsForModel 格式化后 unshift 到下一轮消息部件的最前面;
  • 结束(含异常)时在 finally 中注销监听器,避免泄漏。

代码中的注释还揭示了细节:用户提示先于后台完成消息注入,"模型先看到上下文,再看到用户的反应"。另外从源码结构看,同一 InjectionService 会被广播给所有正在运行的本地代理,即一条提示可能同时影响并行的多个子代理。

验证与测试:仓库如何保证转向行为

仓库为模型转向提供了三层验证:

  1. 单元测试injectionService.test.ts 覆盖注入服务本身;
  2. 集成测试packages/cli/src/integration-tests/modelSteering.test.tsx 用 AppRig 模拟真实交互——开启 modelSteering: true,启动长任务、等待模型调用 list_directory 进入确认态,此时注入提示 "focus on .txt",确认工具后断言模型下一轮输出中体现了该提示(读取 file1.txt 而非其他文件);
  3. 行为评估evals/model_steering.eval.ts 定义了两类语义级断言——"纠正型提示"(执行中提示模型转向讲一个机器人笑话,断言模型放弃原任务且不再输出 README 内容)与"建议型提示"(写 hw.js 时提示追加写 hw.py,断言两个文件都产出),用于验证真实模型对转向指令的遵从度。

局限与注意事项

基于文档与源码,使用该功能时需注意:

  • 默认关闭且属实验特性:未开启 experimental.modelSteering 时,工作期间的输入不会进入 user_steering 通道(InjectionService 会直接丢弃);
  • 生效边界是轮次:提示在"下一个模型轮次"开头注入,不是字面意义的零延迟改写当前推理;当前工具轮若已确认执行,该轮不会中途改变;
  • 确认消息是尽力而为的:回执生成有 1.2 秒超时与 90 字符上限,超时会退化为本地模板,确认文案本身不代表主模型已"理解"提示的深度;
  • 提示内容会被标签包裹以降低注入风险,但模型仍会将其作为计划更新的依据,建议遵循教程文档的建议——具体、尽早、附带上下文

小结与后续

模型转向把"打断-重启"的低成本替代方案变成了 Agent 循环的一部分:一次 Enter,提示就会在下一轮模型请求的最前面出现,配合内置的最小差异重规划指令,主模型随即调整路径。结合 Plan Mode 的结构化研究流程(参考 docs/cli/tutorials/plan-mode-steering.md)以及 Agent Skills,你可以在长任务中获得接近"结对编程"的实时引导体验。相关配置项的完整说明见 docs/reference/configuration.md,核心实现集中在 packages/core/src/utils/fastAckHelper.tspackages/core/src/config/injectionService.tspackages/core/src/agents/local-executor.ts

登录后查看全文
热门项目推荐
相关项目推荐