https://apis.iflow.cn/v1接口返回不符合openAI的协议要求

LangChain4j NullPointerException 问题报告

问题概述

在使用 LangChain4j 处理大模型 API 的流式响应时,当 API 返回包含 tool_calls 的 SSE 事件且 id 字段为空字符串时,会触发 NullPointerException,导致流式响应中断。

问题详情

错误信息

java.lang.NullPointerException: null
    at java.base/java.util.concurrent.ConcurrentHashMap.computeIfAbsent(ConcurrentHashMap.java:1693)
    at dev.langchain4j.model.openai.OpenAiStreamingResponseBuilder.append(OpenAiStreamingResponseBuilder.java:160)
    at dev.langchain4j.model.openai.OpenAiStreamingResponseBuilder.append(OpenAiStreamingResponseBuilder.java:85)
    at dev.langchain4j.model.openai.OpenAiStreamingChatModel.lambda$doChat$0(OpenAiStreamingChatModel.java:150)
    at dev.langchain4j.model.openai.internal.StreamingRequestExecutor$2.onEvent(StreamingRequestExecutor.java:140)
    at dev.langchain4j.http.client.log.LoggingHttpClient$1.onEvent(LoggingHttpClient.java:88)
    at dev.langchain4j.http.client.sse.DefaultServerSentEventParser.lambda$parse$0(DefaultServerSentEventParser.java:28)

环境信息

  • LangChain4j 版本: 0.34.0 (推测,基于代码结构)
  • Java 版本: 17.0.14
  • Spring Boot 版本: 3.2.3
  • API 提供商: https://apis.iflow.cn/v1
  • 模型: qwen3-max

根本原因分析

问题触发场景

当大模型 API 返回包含 tool_calls 的流式响应时,tool_callsid 字段在流式传输过程中被分片。某些 SSE 事件中的 id 字段为空字符串 "",导致 LangChain4j 的 OpenAiStreamingResponseBuilder.append 方法在处理时出现异常。

日志证据

1. 第一个 tool_call 事件(正常)

{
  "id":"6b3b4f4b-0278-4d67-ac50-10b4169b253c",
  "object":"chat.completion.chunk",
  "choices":[{
    "index":0,
    "delta":{
      "role":"assistant",
      "content":"",
      "tool_calls":[{
        "id":"call_dfe56453fbb941e7adfebd27",
        "type":"function",
        "function":{"arguments":"","name":"maps_geo"}
      }]
    }
  }]
}

关键点: id 字段有完整值: "call_dfe56453fbb941e7adfebd27"

2. 后续 tool_call 事件(异常)

{
  "id":"6b3b4f4b-0278-4d67-ac50-10b4169b253c",
  "object":"chat.completion.chunk",
  "choices":[{
    "index":0,
    "delta":{
      "role":"assistant",
      "content":"",
      "tool_calls":[{
        "id":"",
        "type":"function",
        "function":{"arguments":"{\"address\": \"杭州"}}
      }]
    }
  }]
}

关键点: id 字段为空字符串: ""

3. 错误发生时间点

从日志可以看到:

  • 第 604 行:第一个 tool_call 事件被处理
  • 第 605 行:立即触发 NullPointerException
  • 第 650-654 行:后续的 tool_call 事件继续到达,但 id 字段为空字符串

代码位置

错误发生在 dev.langchain4j.model.openai.OpenAiStreamingResponseBuilder.append 方法的第 160 行:

// 推测的代码逻辑(基于错误堆栈)
Map<String, ...> map = ...;
String key = toolCall.getId(); // 可能返回空字符串 ""
map.computeIfAbsent(key, ...); // 当 key 为空字符串或 null 时触发异常

复现步骤

  1. 配置环境

    • 使用 LangChain4j 的 OpenAiStreamingChatModel
    • 配置支持工具调用的 Agent(如 TravelAgent
    • 启用日志记录以查看原始 SSE 数据
  2. 触发条件

    • 发送一个会触发工具调用的消息(如:“规划一下杭州青山湖骑行路线”)
    • 确保 Agent 会调用工具(如高德地图 MCP 工具)
  3. 观察结果

    • 流式响应开始正常返回
    • 当第一个 tool_call 事件到达时,响应继续
    • 当后续 tool_call 事件的 id 字段为空字符串时,触发 NullPointerException
    • 流式响应中断,前端显示不完整

完整请求示例

// 用户消息
String userMessage = "规划一下杭州青山湖骑行路线,要求:1.起点为青山湖圣鹤码头停车场;2.骑行里程15~20公里;3.骑行到一半后原路返回;";

// 通过 RouterService 路由到 TravelAgent
Flux<String> response = routerService.routeAndStreamChat(userMessage);

影响范围

  • 功能影响: 流式响应中断,用户无法看到完整的 AI 回复
  • 用户体验: 前端显示不完整,可能显示错误信息
  • 系统稳定性: 虽然应用不会崩溃(已有错误处理),但功能不完整

解决方案建议

API 提供商修复(推荐)

问题: API 返回的 tool_calls 的 id 字段在流式传输中被分片,导致某些事件中 id 为空字符串。

建议: 联系 API 提供商(apis.iflow.cn),要求:

  1. 确保 tool_calls 的 id 字段在第一个包含该 tool_call 的事件中完整提供
  2. 后续事件中,如果 id 已存在,应保持原值,而不是返回空字符串
  3. 遵循 OpenAI 的流式响应格式规范

参考: OpenAI 的流式响应格式要求 tool_calls 的 id 应该在第一个包含该 tool_call 的事件中完整提供。

从日志中提取的关键 SSE 事件:

  1. 正常文本响应事件:

    data: {"id":"...","choices":[{"delta":{"content":"您好"}}]}
    
  2. 第一个 tool_call 事件(正常):

    data: {"id":"...","choices":[{"delta":{"tool_calls":[{"id":"call_dfe56453fbb941e7adfebd27","type":"function","function":{"name":"maps_geo"}}]}}]}
    
  3. 后续 tool_call 事件(异常,id 为空):

    data: {"id":"...","choices":[{"delta":{"tool_calls":[{"id":"","type":"function","function":{"arguments":"{\"address\": \"杭州"}}]}}]}
    
1 个赞

有人看吗,这个问题之前没有,好像最近两周开始出现

感谢反馈,我们这边排查下~

目前已排查到qwen3-max模型部署厂商针对流式输出格式有问题,不是标准openAI协议。所以建议换其他模型即可适配langchain4j框架

今天的qwen3-coder-plus好像也有这个问题,直接在返回的文本中返回了工具调用。我也是一样的环境 langchain4j + springboot3