Claude CLI 与 Agent SDK 在默认情况下会发送完全不同的系统提示,因此针对相同输入(如“挪威的首都是哪?”),两者无法产生完全相同的输出。CLI采用模块化的系统提示架构(约269基础token加上110多个条件加载片段),而SDK默认使用最小化提示。即使通过claude_code预设和settingSources配置来匹配CLI环境,并设置temperature=0,也无法保证输出的确定性,因为Claude API没有提供seed参数。
Claude Agent SDK vs CLI 系统提示对比:输出一致性与确定性分析
在开发基于Claude的应用时,理解Claude Agent SDK和CLI(即Claude Code)在底层行为上的区别至关重要。本文将深入对比两者系统提示的架构差异,分析输出不一致的根本原因,并提供配置指南,帮助你在SDK中尽可能模拟CLI的行为。
1. 核心架构差异:模块化提示 vs 最小化提示
理解两者输出不同的第一步,是明白它们发送给模型的系统提示(System Prompt)内容天差地别。
Claude CLI 的模块化系统提示
CLI拥有一个经过精心设计的、高度结构化的提示系统。其核心是一个约269个token的基础提示,随后会根据启用的功能,条件性地加载超过110个不同的提示片段。
这些组件共同构成了CLI的完整能力:
- 基础提示:定义核心行为与指令。
- 工具指令:详细说明18个以上内置工具(如
Write,Read,Edit,Bash)的用法。 - 编码规范与安全规则:代码风格、安全实践和防注入指令。
- 环境上下文:自动包含当前工作目录、Git状态等信息。
- 项目上下文:自动加载工作区下的
CLAUDE.md文件内容。 - 条件模块:例如当进入“计划模式”或使用
Explore子代理时,会加载对应的专用提示。
这意味着,CLI在每次交互中都自动为模型装备了强大的“全副武装”,使其具备完整的软件工程能力。
Agent SDK 的最小默认提示
相比之下,Agent SDK的默认行为要精简得多。它只提供最基础的工具指令(如果确实提供了工具的话)和简短的安全规则。
SDK默认提示不包含:
- 编码指南与风格偏好
- CLI的完整安全规则集
- 项目上下文(除非显式配置)
- 详细的工具使用说明
这种设计赋予了开发者更高的灵活性,但也意味着SDK开箱即用的能力远不如CLI全面。
2. 实现最大输出一致性的配置方法
如果你在SDK应用中希望获得接近CLI的体验和行为,需要通过以下配置进行对齐。
步骤一:使用 claude_code 预设
这是匹配CLI提示架构最关键的一步。通过将systemPrompt的类型设置为preset,可以让SDK加载与CLI相同的核心模块化提示。
import { query } from "@anthropic-ai/agent-sdk";
const response = query({
prompt: "分析当前目录的Git状态并提出建议",
options: {
systemPrompt: {
type: "preset",
preset: "claude_code" // 使用CLI的系统提示架构
}
}
});
步骤二:加载项目与用户设置
CLI会自动加载CLAUDE.md和用户级设置,SDK需要显式配置才能实现。
const response = query({
prompt: "编写一个单元测试",
options: {
systemPrompt: {
type: "preset",
preset: "claude_code"
},
settingSources: ["project", "user"] // 加载项目级和用户级设置
}
});
settingSources: ["project"]:会尝试加载项目根目录下的CLAUDE.md文件。settingSources: ["user"]:会加载用户级的配置(如输出样式)。
步骤三:匹配模型与温度参数
确保使用相同的模型,并设置temperature: 0以追求最大确定性(虽然无法保证)。
const response = query({
prompt: "什么是闭包?",
options: {
systemPrompt: {
type: "preset",
preset: "claude_code",
append: "请用中文简洁解释。" // 在CLI提示基础上追加指令
},
settingSources: ["project"],
model: "claude-sonnet-4-20250514",
temperature: 0 // 使用贪心解码以追求一致性
}
});
3. 输出一致性无法保证:根本原因分析
即使你完美复制了CLI的所有配置,SDK和CLI的输出也可能不同。更甚者,同一个接口在相同输入下多次运行的结果也可能存在差异。这是由多个不可控因素决定的。
无法实现完全确定性的技术因素
| 因素类别 | 具体原因 | 可否通过配置控制 |
|---|---|---|
| API 架构 | Claude Messages API 不提供 seed 参数,这是无法实现种子级复现的根本原因。 |
❌ 不可控 |
| 硬件与算力 | 并行计算中的浮点运算细微差异、云基础设施的调度差异。 | ❌ 不可控 |
| 模型架构 | 模型内部的专家混合(Mixture-of-Experts, MoE)路由可能因输入微小扰动而变化。 | ❌ 不可控 |
| 提示差异 | 即使配置相同,加载的上下文(如动态变化的Git状态)也可能略有不同。 | ✅ 可控制 |
重要结论:temperature=0 不等于确定性
将temperature设置为0(贪心解码)会大幅降低随机性,但不能保证输出完全一致。已知的Claude CLI问题(如GitHub Issue #3370)也证实了这一点。系统内部的非确定性因素仍然会导致微小差异。
4. 实用建议与最佳实践
基于以上分析,在设计和构建应用时应遵循以下原则。
选择正确的接口
- 选择CLI:当你需要进行交互式开发、调试,或希望模型拥有完整的本地项目上下文和工具链时。
- 选择Agent SDK:当你需要将Claude能力集成到自定义应用、自动化管道或需要精细控制提示和输入输出格式时。
设计健壮的系统
- 不要依赖比特级一致:将模型输出视为概率性的。在应用逻辑层做好容错和校验。
- 拥抱结构化输出:使用JSON模式(JSON Mode)或请求结构化数据,这比依赖自然语言输出的字面一致性更可靠。
const response = await client.messages.create({ model: "claude-sonnet-4-20250514", max_tokens: 1024, system: "你是一个JSON输出助手。只返回JSON。", messages: [{role: "user", content: "列出北京的3个地标"}] }); - 缓存与重试:对于相同输入期望得到稳定结果的场景,考虑缓存首次成功的结果。对于关键任务,可实施重试机制。
- 明确你的目标:你需要的是“行为相似”还是“输出逐字相同”?前者通过
claude_code预设已高度可实现,后者在当前技术架构下无法保证。
通过理解系统提示的底层差异并合理配置SDK,你可以在自定义应用中获得非常接近CLI的强大且一致的体验。关键在于为输出差异性设计系统,而非试图完全消除它。
FAQ
Q: 为什么我设置了temperature: 0,但CLI和SDK的输出还是不一样?
A: temperature: 0只能消除采样随机性,无法消除模型固有的非确定性。两者系统提示的细微差异、加载的环境上下文不同,以及模型内部(如MoE路由)的计算差异都会导致输出不同。
Q: 如何让SDK的行为尽可能像CLI?
A: 关键配置是:1) 使用systemPrompt: { type: "preset", preset: "claude_code" };2) 添加settingSources: ["project", "user"]以加载CLAUDE.md等上下文;3) 使用相同的模型和temperature: 0。但请注意,这仍不能保证输出完全相同。
Q: SDK的“最小默认提示”有什么优势? A: 优势在于轻量和灵活。它不会强加CLI的编码风格、安全规则等,为你留出更多的token预算来构建自己的系统提示,特别适合需要完全定制模型行为的特定领域应用。