【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 被校验为只能是 userassistant,而请求里出现了 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 行业专家整理编写