ReAct 的 Reason→Act→Observe 循环,本质上已经把"思考"拆成了多轮显式的 LLM 调用。每一轮 call 中,模型看到完整的 history(包括之前的推理轨迹、工具返回结果),然后决定下一步。这意味着模型不需要在单次调用内部做太深的多步推理——框架替它做了"分步思考"的调度。 如果你同时开启 thinking mode,等于在每一轮 ReAct iteration 内部又嵌套了一层隐式 CoT,形成"思考中套思考"的结构。这带来几个实际问题: 延迟叠加。一个垂直 agent 完成一个用户请求通常需要 3-8 轮 tool call。thinking mode 每轮额外增加 2-10 秒(取决于 thinking budget),总延迟可能从 10 秒膨胀到 40-60 秒,对交互体验是致命的。 成本倍增。thinking tokens 虽然通常半价计费,但量大。每轮多 1000-3000 tokens 的 thinking,8 轮下来就是上万额外 tokens。 收益递减。ReAct 的每轮决策通常是"看当前状态→选一个 tool→填参数",这是一个相对浅的单步决策,thinking mode 擅长的深度多步推理在这里用不上。 但也不是完全不能开。关键判断标准是:这一轮调用中,模型是否需要做"不可分解的复合推理"。 典型该开的场景: Planning 阶段。如果你的 agent 有一个独立的 planner 节点(比如先让用户描述需求,一次性生成完整的多步计划),这个调用内部确实需要深度推理——分析约束、处理冲突、排列步骤。这里开 thinking 收益明显。 复杂结果解读。工具返回了一大段非结构化数据(比如一份合同文本、一段报错日志),需要模型做深度理解和判断后再决定下一步。这种"观察→深度理解→决策"在单轮内完成,thinking 有帮助。 最终回答生成。所有工具调用结束后,最后一轮需要综合所有观察结果给用户一个高质量回答。这里开 thinking 可以提升回答质量,且只发生一次,延迟可接受。 典型不该开的场景: 纯路由/分发。意图识别、选择调哪个 tool、填参数——这些是浅层决策,thinking 纯粹浪费。 简单工具链。如果 tool 的 schema 清晰、参数映射直接,模型不需要"想"就知道该调什么。 高频循环。比如 agent 在一个 loop 里反复调同一个 API 翻页取数据,每轮都开 thinking 是灾难。
gogo做法
百炼上的 qwen3.7-max 等模型支持 enableThinking,默认是开启的,开启时模型会在正式输出前,先生成一段内部推理链(thinking tokens)。这段推理不返回给用户,但它帮助模型「想清楚再说」,类似人类做题前在草稿纸上列算式。 在 gogo-agent 里的我们定义了两个model:
// ModelConfig.java — 同一个模型,分两个 Bean 注册,唯一区别是 thinking 开关
@Bean("strongModel")
public Model strongModel() { ... .enableThinking(false).build(); }
@Bean("strongModelWithThinking")
public Model strongModelWithThinking() { ... .enableThinking(true).build(); }
各个Agent针对thinking的开关情况 | Agent | 模型 | enableThinking | thinkingBudget | 关/开理由 | | --- | --- | --- | --- | --- | | MasterAgent | strongModel | 关 | — | 纯路由分发,不需要推理 | | ItineraryManageAgent | strongModel | 关 | — | 结构化 CRUD(差旅单申请/查询/修改),流程明确无歧义 | | InfoAgent | stableModel | 关 | — | RAG 检索 + 格式化,简单问答 | | ItineraryReviewAgent | stableModel | 关 | — | 调审核工具 → 格式化输出,机械性 | | ItineraryPlanAgent | strongModelWithThinking | 开 | 2048 | 多方案比价、排序、审核循环,需要全局权衡 | | BookingAgent | strongModelWithThinking | 开 | 2048 | 多步下单编排 + 审批门禁 + 错误恢复,决策链复杂 |
我们只有针对「需要在多个候选里做权衡」或「多步有状态编排、错一步不可逆」的 Agent 才开思考,比如我们的形成规划和预定两个智能体。 路由型、检索型、CRUD 型全部关掉。 但是,就算是开启了thinking,我们也不是说就无限用的,我们做了两个限制。
thinkingBudget
thinkingBudget是百炼上支持的一个参数。这告诉模型「你最多想 2048 个 token 就必须开始输出」,避免模型陷入冗长的自我论证循环。
// BaseSubAgent.java
public GenerateOptions getEnableThinkingGenerateOptions(Integer thinkingBudget) {
return GenerateOptions.builder()
.cacheControl(true)
// 深度思考模型有时会生成冗长的推理过程,增加等待时间并消耗更多 Token。
// 通过 thinking_budget 参数可设置推理过程的最大 Token 数。
.thinkingBudget(thinkingBudget)
.build();
}
为什么是 2048?这是一个经验值权衡: - 太小(如 512):模型可能还没想清楚就被截断,反而产出更差结果。 - 太大(如 8192):每轮多 8K token 的延迟和费用,对一个 Agent 任务不划算。 - 2048 token 大约等于 1000~1500 汉字,够把一个中等复杂决策的推理链走完,同时把单轮额外开销控制在约 0.024 元以内。
规划前思考,规划后不思考
即使是开了思考的 ItineraryPlanAgent,也不是每一轮都在烧 thinking tokens。我们给PlanAgent提供了一个PlanAwareThinkingHook 实现了阶段感知的动态切换:
public class PlanAwareThinkingHook implements Hook {
private final PlanNotebook planNotebook;
private final int thinkingBudget; // 2048
private void handleReasoning(PreReasoningEvent event) {
if (isPlanning()) {
// Planning 阶段(还没创建计划):保持思考模式。
// 模型需要从零开始设计方案,这是最需要深度推理的时刻。
} else {
// Execute 阶段(计划已创建,正在逐步执行):关闭思考,快速执行。
disableThinking(event);
}
}
private boolean isPlanning() {
return planNotebook.getCurrentPlan() == null; // 没有 plan = 还在规划
}
private void disableThinking(PreReasoningEvent event) {
event.setGenerateOptions(GenerateOptions.builder()
.cacheControl(true) // 保住百炼隐式缓存
.additionalBodyParam("enable_thinking", false) // 动态关闭思考
.build());
}
private void enableThinking(PreSummaryEvent event) {
// 最终总结阶段:重新开启思考,让模型综合全部结果做一次深度整理
event.setGenerateOptions(GenerateOptions.builder()
.cacheControl(true)
.thinkingBudget(thinkingBudget)
.build());
}
}
逻辑分三阶段: | 阶段 | 判定条件 | 思考 | 理由 | | --- | --- | --- | --- | | Planning | planNotebook.getCurrentPlan() == null | 开 | 从零设计方案,需全局权衡 | | Execute | plan 已存在,逐步执行子任务 | 关 | 按计划调工具即可,不需要再重新想 | | Summary | PreSummaryEvent | 开 | 综合所有结果做一次最终整理 |
这意味着一个 15 轮的规划任务里,只有开头 2~3 轮(制定计划)和最后 1 轮(总结)在思考,中间 10+ 轮的工具执行全部关闭思考。省了 60~70% 的 thinking tokens,而推理质量不降——因为执行阶段只是按既定计划照做,不需要全局推理。