【Claude Code-System Prompt实测】Anthropic / OpenAI system 结构差异:不要把请求前缀当成普通消息搬运
非线智能API经验 [Claude Code-System Prompt实测] 第9篇
摘要
Claude Code 接入 DeepSeek、Qwen、vLLM、Bedrock 或 OpenAI-compatible gateway 时,最早暴露的问题往往不是模型能力,而是请求结构。Anthropic Messages API 的起始系统指令使用顶层 system 字段;OpenAI-compatible 生态长期把系统级指令放在 messages 序列里的 role: "system",或在新接口中使用 instructions / developer 指令。两者都能表达“系统指令”,但它们在字段位置、合法 role、content block、cache control 和上下文生命周期上并不等价。
如果兼容层把 Claude Code 的 system prompt 直接塞进 Anthropic-compatible endpoint 的 messages 数组,就可能触发严格校验错误。vLLM 和 Claude Code 社区 issue 中都出现过类似案例:messages[].role 被校验为只能是 user 或 assistant,而请求里出现了 system / ctx / msg 等 role,最终返回 400。
因此,system prompt 跨模型处理的第一步不是字符串拼接,而是建立一个请求前缀 IR:把系统指令、项目上下文、工具定义、MCP 描述、动态 metadata 和对话消息拆开,再按目标 provider profile 渲染。
数据来源:非线智能Nonlinear 官网
两种结构的根本差异
Anthropic Messages API 的系统指令是请求级字段:
{
"model": "claude-sonnet-4-5",
"system": "You are a coding agent.",
"messages": [
{
"role": "user",
"content": "Fix this bug."
}
]
}
OpenAI-compatible Chat Completions 生态常见形态是:
{
"model": "gpt-like-model",
"messages": [
{
"role": "system",
"content": "You are a coding agent."
},
{
"role": "user",
"content": "Fix this bug."
}
]
}
两者看起来只是字段位置不同,但对网关来说差异更深:
| 维度 | Anthropic Messages API | OpenAI-compatible 生态 | 兼容层风险 |
|---|---|---|---|
| 起始系统指令 | 顶层 system |
messages 中 system role,或 instructions |
不能把 system role 原样传给 Anthropic endpoint |
| 对话合法 role | 主要是 user / assistant |
常见为 system / developer / user / assistant / tool |
role 集合不同 |
| system content | 字符串或 content block 数组 | 字符串、多模态 content、instructions | content block 能力不同 |
| cache control | 可在 system / block 上表达 cache breakpoint | 不同 provider 支持不一 | cache hint 不能机械复制 |
| 工具结果位置 | tool_result 在 user content block 中 |
常见为 tool role message |
工具状态机不同 |
这意味着“把 Anthropic 顶层 system 转成 OpenAI 的 system message”只是一个渲染选择,不是通用事实;反方向更不能把 OpenAI messages[0].role=system 原样发给 Anthropic-compatible endpoint。
vLLM role 校验案例说明了什么
来源文件列出的 vLLM issue、NVIDIA forum 和 Claude Code issue 报告称,某些 Claude Code 版本在 Anthropic-compatible endpoint 场景中出现了 endpoint 不接受的 role,服务端按 Anthropic Messages 格式校验后返回 400,错误类似 Input should be 'user' or 'assistant'。这些是工程案例,不应反推为 Claude Code 或 Anthropic Messages API 的长期官方规格。
这个案例不应被解读为“Claude Code 官方协议允许 messages 中出现任意 role”。更稳妥的结论是:
1、Claude Code 可以通过 ANTHROPIC_BASE_URL 或 gateway 指向 Anthropic-compatible endpoint。
2、Anthropic-compatible 不等于 OpenAI-compatible;它通常仍会按 Anthropic Messages role 规则校验。
3、跨模型兼容层必须显式处理 role 映射,而不是把上游消息数组原封不动透传。
正确做法:先拆 IR,再渲染
推荐定义一个 PromptEnvelope:
type PromptEnvelope = {
systemBlocks: PromptBlock[];
developerBlocks: PromptBlock[];
projectContextBlocks: PromptBlock[];
toolSpecs: ToolSpecIR[];
mcpSpecs: McpSpecIR[];
cacheHints: CacheHintIR[];
dynamicMetadata: MetadataBlock[];
conversation: ConversationMessage[];
};
然后按 provider profile 渲染:
type ProviderProfile = {
name: string;
systemPlacement: "top_level_system" | "system_message" | "instructions" | "developer_message";
allowedConversationRoles: Array<"user" | "assistant" | "tool" | "system" | "developer">;
supportsSystemBlocks: boolean;
supportsCacheControl: boolean;
supportsDeveloperRole: boolean;
};
Anthropic 渲染器:
systemBlocks -> top-level system
projectContextBlocks -> system 后的 project context / user-side context
conversation -> 只保留 user / assistant
cacheHints -> Anthropic cache_control
OpenAI-compatible 渲染器:
systemBlocks -> system message 或 instructions
developerBlocks -> developer message 或合并到 system
projectContextBlocks -> user-side context 或单独上下文消息
conversation -> provider 支持的 messages role
cacheHints -> provider 原生能力;不支持时转内部 metadata
vLLM Anthropic-compatible 渲染器:
systemBlocks -> top-level system
conversation -> user / assistant only
unsupported roles -> fail closed 或转换为普通文本上下文
cache_control 也不能随便搬运
Anthropic prompt caching 文档支持在顶层 system 或 content block 上设置 cache breakpoint。这个能力依赖 Anthropic 的请求结构和缓存语义。OpenAI-compatible gateway、本地 vLLM server、Bedrock 代理链路不一定支持同样的字段。
兼容层应该把 cache hint 抽象成内部结构:
type CacheHintIR = {
segmentId: string;
ttl?: "5m" | "1h";
stability: "static" | "semi_static" | "dynamic";
providerPolicy: "anthropic_cache_control" | "gateway_prefix_hash" | "none";
};
渲染到 Anthropic 时,它可以变成 cache_control。渲染到不支持显式 cache control 的 provider 时,它只能用于内部 prefix hash、观测指标或本地 KV reuse 策略。不要把 cache_control JSON 当作 prompt 文本塞进 system prompt。
失败模式
| 失败模式 | 触发原因 | 修复策略 |
|---|---|---|
| Anthropic-compatible endpoint 返回 role 400 | OpenAI system message 被透传到 Anthropic messages |
上提到顶层 system |
| developer 指令丢失 | OpenAI developer role 被简单丢弃 | IR 中保留优先级,按 provider profile 合并 |
| cache_control 无效 | Anthropic cache hint 被发给不支持的 gateway | 降级为内部 prefix hash |
| project context 被误当 system | 兼容层误把 CLAUDE.md project context 渲染为 Anthropic 顶层 system |
按官方语义作为 project context / user-side context |
| 工具状态机断裂 | OpenAI tool role 与 Anthropic tool_result 混用 | 使用独立 tool IR 和协议渲染器 |
验证清单
• 给 Anthropic-compatible renderer 输入 OpenAI-style system message,确认输出中 messages 不含 system role。
• 给 OpenAI-compatible renderer 输入 Anthropic 顶层 system,确认落到目标接口支持的 system / developer / instructions。
• 构造 developer role,确认不会静默丢失。
• 构造 cache breakpoint,确认 Anthropic 输出保留 cache_control,不支持的 provider 输出内部 metadata。
• 构造 vLLM profile,确认非法 role 会被转换或 fail closed。
• 构造 CLAUDE.md project context,确认不会被描述为 Anthropic 顶层 system 本体。
参考链接
• Anthropic Using the Messages API
• Anthropic Mid-conversation system messages
• Anthropic Prompt Caching
• OpenAI Text Generation Docs
• AWS Bedrock Anthropic Claude Messages API
• vLLM Claude Code Integration
• vllm-project/vllm #44000
• NVIDIA Developer Forum: Claude Code 2.1.154 breaks with vLLM Anthropic-compatible endpoint
• anthropics/claude-code #63469
本文由非线智能API Claude Code 行业专家整理编写