
前言
在进入正文之前,先交代一下这些文章的来龙去脉。
AgentForge 是一个面向 Java 开发者、从 LLM 最底层能力开始构建 的开源 Agent 框架。它不从高度封装的 Agent API 起步,而是先建立稳定、统一、可扩展的模型抽象,再逐层向上锻造 Tool、Memory、Middleware、Reasoning 与 Agent Runtime 等能力。
AgentForge = Agent + Forge:Agent 代表能理解目标、进行推理、调用工具并完成任务的智能体,Forge 则强调把原始智能持续加工、塑形、强化,最终锻造成真正可用的产品。
本系列《AgentForge 核心模块设计原理》沿着这条自底向上的路径,逐个模块拆解它的设计原理与实现细节。本文聚焦 AgentForge 适配 OpenAI 的接入实践(统一类型与 Chat Completions 的双向映射),对应模块 agentforge-model-openai。
- 开源仓库(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,适配多家模型
AgentForge 的 Agent Runtime 只依赖
ChatModel/StreamingChatModel接口。我们希望"换模型"只换一个 Provider 依赖,而不是改 Agent 代码。
于是 OpenAI Provider 的职责被限定为协议翻译层:
Agent Runtime
│ ChatRequest / ChatResponse(Provider 无关)
▼
OpenAiChatModel / OpenAiStreamingChatModel
│ Chat Completions wire
▼
OpenAI / OpenAI-compatible 服务
1.2、问题引导:统一类型如何落到 OpenAI wire?
问题:AgentForge 的
ChatMessage、ChatRequestParameters、ChatResponse分别怎么落到 OpenAI 的messages/ 请求字段 / 响应字段?工具调用如何双向映射?流式如何聚合?
本文逐项给出映射表与实现位置。
1.3、实现边界
- Wire API:
POST {baseUrl}/chat/completions(默认https://api.openai.com/v1); - release_1.x 以 Chat Completions 为第一版统一协议,Responses API 采用"并存 Adapter"策略(见第六章)。
1.4、Builder 参数
| Builder 参数 | 默认值 | 说明 |
|---|---|---|
baseUrl |
https://api.openai.com/v1 |
也支持 OpenAI-compatible 服务 |
apiKey |
null |
非空时发送 Authorization: Bearer ... |
modelName |
null |
请求前必须有值 |
temperature |
null |
非空才发送 |
maxTokens |
null |
映射 max_tokens |
topP |
null |
映射 top_p |
stopSequences |
null |
映射 stop |
customParameter |
空 | 透传 Provider 扩展顶层字段 |
customHeader |
空 | 追加自定义 HTTP Header |
httpTransport |
JdkHttpTransport |
HTTP SPI |
connectTimeoutMillis |
10000 |
连接超时 |
readTimeoutMillis |
60000 |
读取超时 |
重点:
OpenAiStreamingChatModel复用同一套配置,并强制写入stream=true与stream_options.include_usage=true。
二、核心概念
2.1、统一入参
ChatRequest {
List<ChatMessage> messages;
ChatRequestParameters parameters;
}
ChatRequestParameters {
String modelName();
Double temperature();
Integer maxTokens();
Double topP();
List<String> stopSequences();
Map<String, Object> customParameters();
}
2.2、统一出参
ChatResponse {
AiMessage aiMessage; // text + toolExecutionRequests
TokenUsage tokenUsage;
FinishReason finishReason;
Map<String, Object> metadata;
}
重点:Provider 的职责就是把 wire 字段无损地落到这两个统一对象上。
三、实现思路与映射
3.1、HTTP Headers
Content-Type: application/json
Accept: application/json
Authorization: Bearer ${apiKey} # apiKey 非空时
之后追加 customHeaders;企业内部网关可用 customHeader(...) 增加租户、路由、trace 等。
3.2、消息映射
| AgentForge | OpenAI role | content / 关键字段 |
|---|---|---|
SystemMessage |
system |
message.text() |
UserMessage(单文本) |
user |
message.text() |
UserMessage(多 Content) |
user |
content[],逐条 TextContent → {"type":"text","text":...} |
AiMessage(纯文本) |
assistant |
message.text() |
AiMessage(含工具调用) |
assistant |
content(可为 null)+ tool_calls[] |
ToolExecutionResultMessage |
tool |
tool_call_id = message.id(),content = message.text() |
工具调用 + 结果回填的 wire 形态:
{
"messages": [
{
"role": "assistant",
"content": null,
"tool_calls": [
{ "id": "call_1", "type": "function",
"function": { "name": "getWeather", "arguments": "{\"city\":\"hangzhou\"}" } }
]
},
{ "role": "tool", "tool_call_id": "call_1", "content": "{\"temperature\":22}" }
]
}
注意:
CustomMessage当前未实现 wire mapping,遇到会抛IllegalArgumentException。
3.3、参数映射
| AgentForge 参数 | Chat Completions 字段 | 当前行为 |
|---|---|---|
modelName |
model |
必填;为空本地失败 |
temperature |
temperature |
非空才发送 |
maxTokens |
max_tokens |
非空才发送 |
topP |
top_p |
非空才发送 |
stopSequences |
stop |
非空才发送 |
tools |
tools[] |
{"type":"function","function":{name,description,parameters,strict}} |
toolChoice |
tool_choice |
AUTO→"auto"、NONE→"none"、REQUIRED→"required"、SPECIFIC→{"type":"function","function":{"name":X}} |
customParameters |
顶层原样写入 | 通用字段写入后覆盖同名 custom 字段 |
构建顺序(标准字段拥有最终优先级):
1. payload.putAll(customParameters)
2. 写入 model / messages
3. 写入 temperature / max_tokens / top_p / stop
4. 写入 tools / tool_choice
重点:即使
customParameters写了另一个model,最终仍会被modelName覆盖。
3.4、响应映射
| OpenAI 字段 | AgentForge 字段 |
|---|---|
choices[0].message.content |
ChatResponse.aiMessage().text() |
choices[0].message.tool_calls[] |
ChatResponse.aiMessage().toolExecutionRequests() |
usage.prompt_tokens |
TokenUsage.inputTokens() |
usage.completion_tokens |
TokenUsage.outputTokens() |
usage.total_tokens |
TokenUsage.totalTokens() |
choices[0].finish_reason |
ChatResponse.finishReason() |
id / model / created |
metadata["id"] / ["model"] / ["created"] |
tool_calls[] 映射:id → ToolExecutionRequest.id、function.name → name、function.arguments → arguments(保留原始 JSON 字符串,不重新格式化)。
注意:当
tool_calls非空且content为空时,AiMessage.text()返回null;两者同时存在时,两者都会保留。
3.5、finish_reason 映射
| OpenAI | AgentForge FinishReason |
|---|---|
stop |
STOP |
length |
LENGTH |
tool_calls |
TOOL_EXECUTION |
function_call |
TOOL_EXECUTION |
content_filter |
CONTENT_FILTER |
| 其他非空 | OTHER |
null |
null |
3.6、响应结构异常
以下情况直接抛 ModelException(不静默返回空):
choices 不存在 / 为空 / choices[0].message 不存在
3.7、流式实现
请求:同 Endpoint,强制 stream=true + stream_options.include_usage=true。
SSE 解析:忽略空行、: 注释、非 data: 行;每个 data: chunk 读取 choices[0].delta.content、delta.tool_calls[]、finish_reason。
delta.content→ 立即handler.onPartialResponse(...),同时本地StringBuilder聚合;delta.tool_calls[]→ 按index分桶累加(规则见协议篇 4.1),不回调半成品;- 结束 →
handler.onCompleteResponse(response)。
最终响应:
ChatResponse.builder()
.aiMessage(AiMessage.from(fullText, toolExecutionRequests))
.finishReason(finishReason)
.tokenUsage(tokenUsage)
.metadata(metadata)
.build();
重点:最终 usage chunk(
choices=[])由"先读 usage、再判断 choices 是否为空"处理;流中断时最终 usage 可能缺失,ChatResponse.tokenUsage()不保证一定存在。
四、实战代码
4.1、非流式
OpenAiChatModel model =
OpenAiChatModel.builder()
.baseUrl("https://api.openai.com/v1")
.apiKey(System.getenv("OPENAI_API_KEY"))
.modelName("your-model")
.temperature(0.2)
.maxTokens(1024)
.build();
ChatRequest request =
ChatRequest.builder()
.message(SystemMessage.from("You are a concise Java assistant."))
.message(UserMessage.from("What is CAS?"))
.build();
ChatResponse response = model.chat(request);
System.out.println(response.aiMessage().text());
运行输出(模拟终端)
$ curl -s https://api.openai.com/v1/chat/completions -H "Authorization: Bearer $OPENAI_API_KEY" \
-d '{"model":"your-model","messages":[{"role":"system","content":"..."},{"role":"user","content":"What is CAS?"}]}'
{"choices":[{"index":0,"message":{"role":"assistant","content":"CAS means Compare-And-Swap."},"finish_reason":"stop"}],
"usage":{"prompt_tokens":20,"completion_tokens":10,"total_tokens":30}}
4.2、流式
OpenAiStreamingChatModel streaming =
OpenAiStreamingChatModel.builder()
.baseUrl("https://api.openai.com/v1")
.apiKey(System.getenv("OPENAI_API_KEY"))
.modelName("your-model")
.build();
streaming.chat(request, new StreamingChatResponseHandler() {
@Override public void onPartialResponse(String partial) { System.out.print(partial); }
@Override public void onCompleteResponse(ChatResponse response) { System.out.println(); }
@Override public void onError(Throwable error) { error.printStackTrace(); }
});
运行输出(模拟终端)
CAS means Compare-And-Swap.
4.3、OpenAI-compatible 服务
OpenAiChatModel model =
OpenAiChatModel.builder()
.baseUrl("https://example.com/v1")
.apiKey("...")
.modelName("provider-model")
.build();
五、兼容性、错误与边界
5.1、HTTP 错误
非 2xx → new ModelException("OpenAI request failed with HTTP " + statusCode, statusCode, responseBody);网络层 IOException → ModelException("OpenAI request failed", cause)。流式非 2xx 会累积 body 并通过 handler.onError(...) 返回。
5.2、协议差距(release_1.x 未映射)
developer role、图片 / 音频多模态 content、流式 onPartialToolCall、refusal、logprobs、structured outputs、audio、provider reasoning(AiMessage.thinking() 字段位已留未接线)、多 choices。
customParameters可临时透传请求字段;但若返回结构需要框架理解,仍须正式扩展 Core / Provider 类型。
六、总结与演进
已落地:tool role 的 tool_call_id 请求映射、tools/tool_choice(含 SPECIFIC)、tool_calls 非流式解析与流式按 index 聚合、UserMessage 多 Content → content[]。
OpenAI 官方建议新应用优先 Responses API,因此后续采用并存 Adapter:OpenAiChatModel → /chat/completions、OpenAiStreamingChatModel → /chat/completions + SSE,未来新增 OpenAiResponsesModel → /responses。上层仍只依赖 Core 接口,协议迁移不侵入 Agent Runtime。
参考资料
[1]. OpenAI Chat Completions API(官方参考)
[2]. OpenAI 文本生成指南
整理者:长路 创建时间:2026.10.5 更新时间:2026.10.5
评论区请在客户端页面查看