ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

OpenAI 接口协议是普通话,其他大模型是方言,Java 视角拆字段与流式调用

OpenAI 接口协议是普通话,其他大模型是方言,Java 视角拆字段与流式调用 大家好我是晚安code。一、为什么说它是普通话OpenAI 接口协议不是某家厂商写进标准文档的规范它是被生态投出来的事实标准。OpenAI 接口协议OpenAI API Protocol以POST /v1/chat/completions为核心的一组请求/响应约定。请求体按它的字段名发服务端就按它的结构回。你可以理解成跟模型说话用的普通话——不是官方钦定的是大家都这么说说的人多了就成了标准。最有意思的地方在于国内几乎每家都开了兼容模式。厂商兼容端点 base_url认证方式OpenAIhttps://api.openai.com/v1Authorization: BearerDeepSeekhttps://api.deepseek.com/v1同上智谱 GLMhttps://open.bigmodel.cn/api/paas/v4同上Kimihttps://api.moonshot.cn/v1同上通义千问https://dashscope.aliyuncs.com/compatible-mode/v1同上2026 年 10 月核对各家地址可能调整用之前建议再扫一眼官方文档。注意通义千问那行的路径里带着compatible-mode四个字——这就是明牌告诉你我们说的是方言但给你配了个翻译。对 Java 开发者来说这件事的现实收益很直接你不用为每家模型写一套 SDK。一个 OpenAI 风格的客户端改base_url和model就能横着走。二、一条请求里到底装了什么真正需要背下来的字段不超过八个剩下的都是调优。先看最小可用的一条请求别看文档看这个就够了{model:deepseek-chat,messages:[{role:system,content:你是严谨的 Java 助手},{role:user,content:HashMap 扩容为什么是 2 倍}],temperature:0.3,stream:false}Java 侧用 OpenAI 官方出的openai-java写法是把这些字段装进一个 builder// 非流式一次拿全量ChatCompletionCreateParamsparamsChatCompletionCreateParams.builder().model(deepseek-chat).addSystemMessage(你是严谨的 Java 助手).addUserMessage(HashMap 扩容为什么是 2 倍).temperature(0.3).build();ChatCompletionresclient.chat().completions().create(params);Stringtextres.choices().get(0).message().content().orElse();params那个对象就是你跟协议打交道的地方。字段看着几十个日常真正常用的就这几个字段作用我的建议model指定用哪个模型必填别名写错直接 404messages对话历史必填接口无状态每次带全temperature02 的随机性写代码给 0.20.3别用默认max_tokens输出长度上限别抠给够不够会被截断stream流式开关见下一节这是重点tools工具/函数定义Agent 场景必用response_format强制 JSON 输出要解析结构化结果时开seed复现种子只是尽力别指望完全一致往下再挖一层messages里每条消息都带一个role四种system人设和规则放数组第一条user用户说的话assistant模型上一轮的回答多轮对话时回填tool工具执行的结果配合tools用。有个坑要先说这个接口是无状态的。它不记得你上一句说了啥所谓多轮对话是你自己把历史消息一条条塞回messages数组。上下文越长token 越贵——这不是模型贵是你每次都在重发。可能有人会问max_tokens到底填多少才合适看你要它输出多长。写一段代码注释512 够用让它生成一个完整类给 4096做长文总结直接拉满。填小了最典型的症状是回答说到一半硬生生断掉finish_reason会告诉你是length而不是stop——看到这个值就该调大了。三、流式和非流式Java 里差在哪流式调用不是让总耗时变短而是让用户更早看到第一个字。SSEServer-Sent Events服务端事件推送一种基于 HTTP 的单向流式传输格式服务端可以持续往同一个响应里推文本行。大模型的逐字输出用的就是它。非流式的逻辑特别简单请求发出去服务端在那边憋着把整段话生成完一次性把 JSON 甩给你。中间那几秒到几十秒你的 Java 线程就是在等。流式就热闹了。stream: true之后服务端回的不是一个大 JSON而是一串以data:开头的行data: {object:chat.completion.chunk,choices:[{index:0,delta:{role:assistant},finish_reason:null}]} data: {object:chat.completion.chunk,choices:[{index:0,delta:{content:因为},finish_reason:null}]} data: {object:chat.completion.chunk,choices:[{index:0,delta:{},finish_reason:stop}]} data: [DONE]关键差别只有一个非流式读choices[0].message流式读choices[0].delta。最后收到一个带finish_reason的块再收到data: [DONE]流就结束了。Java 侧接流式官方 SDK 已经把 SSE 解析包好了// 流式逐块吐字StreamResponseChatCompletionChunkstreamclient.chat().completions().createStreaming(params);stream.stream().flatMap(chunk-chunk.choices().stream()).map(choice-choice.delta().content()).flatMap(Optional::stream).forEach(System.out::print);如果你还想在流结束之后拿到那个完整的ChatCompletion对象比如要读最终的usageSDK 提供累加器ChatCompletionAccumulatoraccChatCompletionAccumulator.create();stream.stream().forEach(acc::accumulate);ChatCompletionfullacc.chatCompletion();顺手提一句流式模式下usage字段默认可能不返回。要统计 token 消耗请求里得显式加stream_options: {include_usage: true}服务端会在[DONE]之前额外补一个只带用量的块。这个细节我第一次踩的时候排查了挺久。下面这张图把两种模式的往返过程放在一起看那到底该选哪个我的判断标准是用户能不能等。场景选哪个理由后台批处理、定时任务非流式代码简单异常处理干净网页/App 对话流式首字 1 秒内出现体感完全不同长文生成、报告流式顺便规避 HTTP 超时要严格 JSON 结构非流式整段好校验增量拼 JSON 很痛苦说真的第 2 行的差别大到什么程度同一个模型、同样的回复非流式转 8 秒圈圈用户会以为卡了流式一秒开始吐字用户觉得挺快。总耗时可能一样甚至流式更长一点点但没人会去掐表。四、方言登场Anthropic Messages API所谓兼容大多数只兼容到你的 demo 跑通为止。Anthropic Messages APIClaude 的原生接口协议端点是POST /v1/messages。它和 OpenAI 的字段语义高度接近但位置、必填性、读取路径都不一样可以理解成同一门语言的另一套方言——能听懂但你不能按普通话的语法说。一条最简请求长这样{model:claude-sonnet-5,max_tokens:1024,system:你是严谨的 Java 助手,messages:[{role:user,content:HashMap 扩容为什么是 2 倍}],stream:true}看出来了吗system不在messages里它是顶层一个独立字段。这是最容易踩的一个差异。其余的差异我列成一张表接 Claude 之前扫一眼维度OpenAI 接口协议Anthropic Messages API端点/v1/chat/completions/v1/messages认证Authorization: Bearerx-api-keyanthropic-versionsystem 提示messages里的一条顶层独立system字段max_tokens可选必填读取结果choices[0].message.contentcontent[]数组按type挑块结束原因finish_reasonstop_reason用量字段usage.prompt_tokens/completion_tokensusage.input_tokens/output_tokens头部的两个差异得单独拎出来说。第一认证头不叫Authorization叫x-api-key第二它有个必须带的anthropic-version头当前值是2023-06-01少一个直接 400。写过 HTTP 客户端的人可能觉得这没什么但你如果用的是OpenAI 兼容的通用客户端它压根不会帮你加这个头。流式返回的结构也完全不同。它不是统一的delta.content而是一串有名字的事件顺序大致是message_start→ 若干组content_block_start→content_block_delta→content_block_stop→message_delta→message_stop。中间那个content_block_delta里还有细分文本是text_delta工具参数是input_json_delta思考过程是thinking_delta。你不能粗暴地按第 0 个块就是文本来取值得先看type。下面这张图把两套协议里最容易混淆的四组字段一一对上接新模型前扫一眼能少查半天文档可能有人会问既然都说兼容 OpenAI为什么换个模型还是报错因为兼容的通常只是最基础的文本对话部分。一旦用到工具调用、结构化输出、多模态、stream_options这些进阶字段各家实现就开始分叉了。典型的一个DeepSeek、Qwen 在流式工具调用里每块都会重发完整的工具调用 ID而 LangChain4j 默认是按块累积 ID 的——这时候得把accumulateToolCallId显式设成false不设的话你会拿到一串被拼起来的、根本对不上的 ID。五、Java 落地时的三个真坑Java 侧对接大模型九成的报错不是模型的问题是 base_url 的问题。第一/v1到底加不加。有些客户端要求你填的 base_url 以/v1结尾有些会自动帮你拼/chat/completions。如果你填了完整路径又碰上会自动拼的客户端最后发出去的就是.../v1/chat/completions/chat/completions返回 404 还不告诉你为什么。我的习惯是只填到版本号那一层https://api.deepseek.com/v1后面交给客户端。第二max_tokens和max_completion_tokens。新版 OpenAI 模型在推max_completion_tokens老参数在部分模型上已经不认了而国内兼容端大多还在用max_tokens。这两个不是完全等价——切模型的时候如果报未知参数先想想是不是这里。第三流式接口在 Spring 里要单独配。如果你用 Spring AI 的ChatClient返回类型得是Flux而且响应体的 content type 要声明成事件流不然前端接不到GetMapping(value/chat,producesMediaType.TEXT_EVENT_STREAM_VALUE)publicFluxStringchat(RequestParamStringq){returnchatClient.prompt().user(q).stream().content();}少写produces那行浏览器侧就是一直 pending 到超时日志里干干净净什么都不报。六、收个尾学一套协议对付十家模型这是 OpenAI 接口协议带来的最大红利但它给你的只是能通不是全通。以 2026 年 10 月这个时间点看我的实际做法是主链路统一走 OpenAI 风格的接口用base_urlmodel两个变量切换只有确实需要 Claude 那些原生能力——比如思考块、服务端工具——才单独走 Messages API并且老老实实按它的规矩写一套客户端。别指望兼容两个字能替你做所有事。真正的兼容层是你自己写的那层适配代码不是厂商文档里的一句承诺。参考链接OpenAI Chat Completions API 参考搜OpenAI Chat Completions API referenceAnthropic Messages API 文档搜Claude Messages API docsAnthropic 流式事件说明搜Claude streaming messagesopenai-java 官方仓库搜openai-java GitHubLangChain4j OpenAI 兼容模型文档搜LangChain4j OpenAI compatible我是晚安code持续分享编程干货。觉得有用的话记得点赞收藏和关注~也欢迎在评论区聊聊你项目里是直接照着 OpenAI 接口协议写还是被哪家方言坑过
返回列表