You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
wanghanlin
5c75cb3f71
fix(ai): 流式回答零内容时不再返回空回答,并标记 EMPTY_COMPLETION
问题(与上一个提交同一个现场):/ai/chat/stream 只发出「开场 role 帧 + finish_reason=stop 帧
+ [DONE]」。这两个帧都是本地生成的、与模型无关(startWith / concatWith),所以这就是一个
**空回答**:客户端表现为一直停在加载态,而 llm_call_trace 把它记为 status=COMPLETE 且
error_type 为空,服务端全程没有告警痕迹 —— 线上持续 10 天、7/204 次,直到客户端报障才发现。
根因(数据库证据,非推断):llm_call_trace 全库 204 条 COMPLETE 记录中,ai_response 为空的有
7 条,且这 7 条 **100% 都 completion_tokens >= max_tokens**(精确等于配置的 2000)。CHAT 活跃
模型 deepseek-v4-flash 会把推理 token 计入 completion_tokens;开启 RAG 后 prompt_tokens 涨到
1035~3557,推理阶段把 max_tokens=2000 的预算吃干,可见答案零 token 产出。Spring AI 只取
delta.content(推理内容进 metadata 的 reasoningContent,不拼进正文),于是上游分片全为空串,
被 preserveTrailingWhitespace 的 current.isEmpty() 分支静默吞掉(该分支无日志、无计数),最终
只剩本地生成的首尾帧。同一问题的同步兜底请求因 URL 不带 enableRag、prompt 仅 334 token,
预算有富余而返回了正常答案 —— 这也解释了「流式空、同步正常」的观感。
- chatStreamOpenAi 增加零内容兜底,帧序仍为 role → 内容… → 兜底内容 → stop → [DONE]
(客户端解析器无需改动):统计非空白内容分片数与上游原始分片数,内容分片为 0 时补一帧内容
① 先复用同一 spec 做一次非流式调用(subscribeOn(boundedElastic)),成功则作为内容帧发出,
用户完全无感 —— 覆盖「provider 流式分片不带内容、但非流式正常」这类成因;
② 非流式也为空则发一条明确提示 —— 覆盖「预算被推理吃光」这类成因:非流式走的是同一套预算与
同样的 prompt,同样会返回空,所以两级都不可省。
- 可观测性:兜底发生时打 WARN(含上游分片数、内容分片数、兜底来源),并在 llm_call_trace 记
error_type=EMPTY_COMPLETION,后台「提示词追踪」可直接筛出这类记录。
- MyLoggerAdvisor:修 after() 的真实 NPE —— getResult() 在 generations 为空时返回 null
(provider 在流末下发 "choices":[] 的用量分片即命中),相邻的 ContentSafetyAdvisor 判了空、
它没判,会抛 NPE 打断整条 advisor 链;同时补注释说明「流式下 AI Response: 为空是必然现象」
(BaseAdvisor.adviseStream 只在携带 finish_reason 的收尾分片回调 after,那片 delta 内容天然
为空),避免下次排查被这行日志误导。
- README:修正流格式契约 —— data: 后**无空格**(原文档写作 data: {...},按文档实现的自研解析器
会一个字节都取不到),并补充首帧只含 delta.role、末帧 delta 为空对象 + finish_reason=stop、
全链路不发任何 event: 具名事件这三个特征。
注意:本提交不改变「推理吃光 max_tokens 预算」这一根因,只把「静默的空回答」变成「可见、不破坏
体验的失败」。真正恢复答案需要把 CHAT 的 max_tokens 从 2000 调大(配置层,另行处理)。
验证:mvn clean package -P prod 通过;编译产物已确认含 EMPTY_COMPLETION 与提示文案;用真实 SDK
回归新帧序 —— 兜底内容被正常渲染、无 stream_empty、客户端不再触发自身兜底(syncFallbackCalls=0)。
|
2 weeks ago |
| .. |
|
wrapper
|
初始化spring boot项目,测试spring ai框架是否能正常调用ai大模型
|
1 year ago |