Anthropic协议02、AgentForge 适配Anthropic接入核心实践

coverImg

前言

在进入正文之前,先交代一下这些文章的来龙去脉。

AgentForge 是一个面向 Java 开发者、从 LLM 最底层能力开始构建 的开源 Agent 框架。它不从高度封装的 Agent API 起步,而是先建立稳定、统一、可扩展的模型抽象,再逐层向上锻造 Tool、Memory、Middleware、Reasoning 与 Agent Runtime 等能力。

AgentForge = Agent + Forge:Agent 代表能理解目标、进行推理、调用工具并完成任务的智能体,Forge 则强调把原始智能持续加工、塑形、强化,最终锻造成真正可用的产品。

本系列《AgentForge 核心模块设计原理》沿着这条自底向上的路径,逐个模块拆解它的设计原理与实现细节。本文聚焦 AgentForge 适配 Anthropic 的接入实践(统一 ChatRequest / ChatResponse 与 Messages API 的双向映射),对应模块 agentforge-model-anthropic。

  • 开源仓库(GitHub):https://github.com/changluya/AgentForge
  • 项目文档站:https://changluya.github.io/AgentForge/
  • Gitee 镜像:https://gitee.com/changluJava/agent-forge
  • 开源协议:MIT
git clone https://github.com/changluya/AgentForge.git
cd AgentForge
mvn clean install -DskipTests

如果这套「自底向上」的设计对你有帮助,欢迎到 GitHub 给 AgentForge 点一个 Star。


一、背景与问题引入

1.1、场景驱动:一套 Core,适配 Claude 的 block 协议

AgentForge 上层只依赖 ChatMessage / ChatRequest / ChatResponse。接入 Claude 时,不能让"system 顶层字段""content block""tool_use/tool_result"这些 wire 细节泄漏到 Agent Runtime。

因此 Anthropic Provider 的职责是协议翻译层,并让 Blocking 与 Streaming 共用同一套映射逻辑。


1.2、问题引导:block 协议如何落到统一消息?

问题:SystemMessage 怎么落到顶层 system?AiMessage 的 text 与工具调用怎么落到 content[]?ToolExecutionResultMessage 怎么落到 tool_result?流式 input_json_delta 怎么聚合成完整工具参数?

本文逐项给出映射与实现位置。


1.3、Builder 参数

Builder 参数 默认值 说明
baseUrl https://api.anthropic.com API 根地址
apiKey null 必填,写入 x-api-key
anthropicVersion 2023-06-01 写入 anthropic-version Header
modelName null 请求前必须有值
temperature null 非空映射 temperature(兼容性见 5.4)
maxTokens 1024 映射必填的 max_tokens
topP null 非空映射 top_p(兼容性见 5.4)
stopSequences null 映射 stop_sequences
customParameter 空 Provider 顶层扩展字段
customHeader 空 自定义 Header
httpTransport JdkHttpTransport HTTP SPI
connectTimeoutMillis 10000 连接超时
readTimeoutMillis 60000 读取超时

重点:max_tokens 是 Anthropic 的必填字段,AgentForge 默认 1024,保证最简单调用可直接执行。


二、核心概念

ChatRequest { List<ChatMessage> messages; ChatRequestParameters parameters; }

ChatResponse { AiMessage aiMessage; TokenUsage tokenUsage; FinishReason finishReason; Map<String,Object> metadata; }

Provider 负责把 Core 的 ChatRequest 翻译为 Anthropic wire,再把响应归一化为 ChatResponse。


三、实现思路与映射

3.1、HTTP Headers

Content-Type: application/json
Accept: application/json
x-api-key: ${ANTHROPIC_API_KEY}
anthropic-version: 2023-06-01

之后追加 customHeaders。

注意:不是 Authorization: Bearer,且必须显式发送 anthropic-version。


3.2、SystemMessage 映射

Anthropic 的 system prompt 使用顶层 system 字段。AgentForge 遍历所有 SystemMessage:

SystemMessage A + SystemMessage B → "A\n\nB" → {"system":"A\n\nB"}

拼接后的 system 不再进入 messages 数组。


3.3、User / AI 消息映射

AgentForge Anthropic role content
UserMessage(单文本) user message.text()(字符串)
UserMessage(多 Content) user content[],逐条 TextContent → {"type":"text","text":...}
AiMessage(纯文本) assistant message.text()(字符串)
AiMessage(含工具调用) assistant content[]:可选 text 块 + tool_use 块
ToolExecutionResultMessage user content[]:tool_result 块
SystemMessage 不进入 messages 聚合到顶层 system

3.4、工具调用映射

AiMessage.hasToolExecutionRequests() 为真时,映射为 assistant 的 content[]:

{ "role": "assistant", "content": [
  { "type": "text", "text": "I will check." },
  { "type": "tool_use", "id": "toolu_1", "name": "get_weather", "input": { "city": "hangzhou" } }
]}

ToolExecutionResultMessage 映射为 user 的 content[]:

{ "role": "user", "content": [
  { "type": "tool_result", "tool_use_id": "toolu_1", "content": "{\"temperature\":22}" }
]}
Core 字段 Anthropic 字段
ToolExecutionRequest.id tool_use.id(回填对应 tool_result.tool_use_id)
ToolExecutionRequest.name tool_use.name
ToolExecutionRequest.arguments(JSON 字符串) tool_use.input(解析为对象后下发)
AiMessage.text 可选的前置 text 块
ToolExecutionResultMessage.isError == true tool_result.is_error = true

注意:CustomMessage 没有 text(),当前仍会抛 UnsupportedOperationException。


3.5、参数映射

AgentForge 参数 Anthropic 字段 当前行为
modelName model 必填;为空本地失败
maxTokens max_tokens 必填;默认 1024
temperature temperature 非空发送;新模型兼容性需注意
topP top_p 非空发送;新模型兼容性需注意
stopSequences stop_sequences 非空发送
tools tools[] {"name","description","input_schema"};无参数时下发空 object schema
toolChoice tool_choice AUTO→{"type":"auto"}、NONE→{"type":"none"}、REQUIRED→{"type":"any"}、SPECIFIC→{"type":"tool","name":X}
SystemMessage system 多条以双换行拼接
customParameters 顶层透传 标准字段最终覆盖同名 custom 值

3.6、响应提取规则

遍历 content 数组:

  • 只提取 type == "text" 的块,按顺序拼接为 AiMessage.text();
  • 提取 type == "tool_use" 的块,映射为 ToolExecutionRequest:
Anthropic tool_use AgentForge
id id
name name
input(对象) arguments(Json.stringify(input))
content = [text("I will check."), tool_use(...)]
        ↓
AiMessage.text()                     = "I will check."
AiMessage.hasToolExecutionRequests() = true
AiMessage.toolExecutionRequests()    = [ToolExecutionRequest(toolu_1, get_weather, {"city":"hangzhou"})]

注意:只有 tool_use 没有 text 块时,AiMessage.text() 为 null;thinking / image / document / server tool block 当前不进入 AiMessage。


3.7、ChatResponse 映射

Anthropic 字段 AgentForge 字段
content[*].text ChatResponse.aiMessage().text()
content[*].tool_use ChatResponse.aiMessage().toolExecutionRequests()
usage.input_tokens TokenUsage.inputTokens()
usage.output_tokens TokenUsage.outputTokens()
input + output TokenUsage.totalTokens()
stop_reason ChatResponse.finishReason()
id / model / type metadata["id"] / ["model"] / ["type"]

3.8、stop_reason 映射

Anthropic AgentForge FinishReason
end_turn STOP
stop_sequence STOP
max_tokens LENGTH
tool_use TOOL_EXECUTION
其他非空 OTHER
null null

3.9、流式实现

AnthropicStreamingChatModel 使用同一 Endpoint,强制 stream=true,Accept: text/event-stream;请求体映射与 Blocking 完全一致,统一由 AnthropicProtocol 生成。

JdkHttpTransport 只按行回调,不解析 SSE 语义;AnthropicStreamState 自行按 event: / data: 累积一帧,遇空行(或单行 data: 帧)时 dispatch:

事件 AgentForge 行为
message_start 记录 metadata["id"]/["model"],读 usage.input_tokens
content_block_start tool_use 时按 index 建累加器,记 id / name / 初始 input
content_block_delta text_delta 立即 onPartialResponse(...);input_json_delta 追加到同 index 工具参数
content_block_stop 不处理,等整块结束
message_delta 读 delta.stop_reason 与 usage.output_tokens
message_stop 由 EOF / 完成回调触发最终响应
error 包装为 ModelException 并 onError(...)

工具调用聚合规则:

  • 文本块与工具块可交错出现,互不影响;
  • arguments 按到达顺序拼接原始 JSON 文本,不重新格式化;
  • 若只有 content_block_start 没有 input_json_delta(input 直接给对象),使用 start 帧的 input;
  • 无任何 partial_json 时,arguments 归一化为 {};
  • 与 LangChain4j 一致:流式中不暴露半成品工具调用,只在最终 ChatResponse 给出完整结果。

重点:temperature / top_p 已被 Anthropic 官方标记为对较新模型 deprecated/受限;推荐默认不显式设置,按目标模型能力决定是否传入。Provider 后续可加入 model capability 校验,提前在客户端阻止无效参数。


四、实战代码

4.1、非流式

AnthropicChatModel model =
        AnthropicChatModel.builder()
                .baseUrl("https://api.anthropic.com")
                .apiKey(System.getenv("ANTHROPIC_API_KEY"))
                .modelName("your-claude-model")
                .maxTokens(1024)
                .build();

ChatRequest request =
        ChatRequest.builder()
                .message(SystemMessage.from("You are a concise Java assistant."))
                .message(UserMessage.from("Explain volatile."))
                .build();

ChatResponse response = model.chat(request);
System.out.println(response.aiMessage().text());

运行输出(模拟终端)

$ curl -s https://api.anthropic.com/v1/messages -H "x-api-key: $ANTHROPIC_API_KEY" \
    -H "anthropic-version: 2023-06-01" \
    -d '{"model":"your-claude-model","max_tokens":1024,"system":"You are a concise Java assistant.","messages":[{"role":"user","content":"Explain volatile."}]}'

{"id":"msg_xxx","type":"message","role":"assistant",
 "content":[{"type":"text","text":"volatile provides visibility guarantees..."}],
 "stop_reason":"end_turn","usage":{"input_tokens":24,"output_tokens":40}}

4.2、工具调用闭环(wire 示例)

// assistant 发起
{"role":"assistant","content":[
  {"type":"text","text":"I will check."},
  {"type":"tool_use","id":"toolu_1","name":"get_weather","input":{"city":"hangzhou"}}]}

// 工具结果回填
{"role":"user","content":[
  {"type":"tool_result","tool_use_id":"toolu_1","content":"{\"temperature\":22}"}]}

五、错误处理、限制与演进

5.1、本地参数校验

modelName 必须非空
apiKey 必须非空
messages 中至少有一条非 system 的 user/assistant 类消息

只有 SystemMessage 时抛:

IllegalArgumentException: Anthropic request requires at least one user/assistant message

5.2、HTTP 错误

非 2xx → new ModelException("Anthropic request failed with HTTP " + statusCode, statusCode, responseBody);IOException → ModelException("Anthropic request failed", cause)。上层可统一读取 exception.statusCode() / exception.responseBody()。


5.3、当前未实现能力

已落地:Streaming/SSE、tool_use 强类型响应(含流式 input_json_delta 聚合)、tool_result 强类型请求、tools/tool_choice 请求映射、UserMessage 多 Content → content[]。

尚未完整支持:thinking / redacted thinking、image / document content blocks、Prompt Caching 强类型配置与 usage、Structured Output、server tools、extended usage 字段(cache token 等)、stop_sequence 写入 metadata、provider refusal / stop details 标准化。


5.4、类结构与演进

Blocking 与 Streaming 共用同一套 wire mapping:

AnthropicProtocol(package-private)
    ├── collectSystemMessages / serializeMessages
    ├── serializeTools / toolChoice
    ├── extractText / extractToolUses
        │
        ├── AnthropicChatModel          -> blocking Messages API
        └── AnthropicStreamingChatModel -> Messages API SSE

后续引入 Thinking / Multimodal 时,建议在 Core 扩展统一 Content 层级(TextContent 已存在):ImageContent / AudioContent / VideoContent / PdfFileContent,使 Anthropic block protocol 与 OpenAI 多模态汇聚到同一上层模型。


六、总结

  1. 两大差异:system 顶层字段、content block 数组——都被 Provider 层吸收,Core 不变。
  2. 工具调用:tool_use(input 对象)→ tool_result(user 角色回填),强类型双向映射。
  3. 流式:AnthropicStreamState 自行解析带事件名的 SSE,input_json_delta 按 block index 聚合。
  4. 必填约束:max_tokens 默认 1024,保证直接可调用。
  5. 风险提示:新 Claude 模型对 temperature / top_p 收紧,默认不设置更安全。

参考资料

[1]. Anthropic Messages API(英文)

[2]. Anthropic Messages API(中文)


整理者:长路 创建时间:2026.10.5 更新时间:2026.10.5

评论区请在客户端页面查看