From 377aad48db33ecdaa74c78695e844e3c193f4c5f Mon Sep 17 00:00:00 2001 From: wanghanlin <1533525126@qq.com> Date: Wed, 26 Aug 2026 13:36:24 +0800 Subject: [PATCH] =?UTF-8?q?fix(app):=20=E4=BF=AE=E5=A4=8D=E5=88=87?= =?UTF-8?q?=E6=8D=A2=E6=8E=A8=E7=90=86=E6=A8=A1=E5=9E=8B=E5=90=8E=E5=AF=B9?= =?UTF-8?q?=E8=AF=9D=E6=B5=81=E5=BC=8F=E8=B6=85=E6=97=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit chatStreamOpenAi 流开始时立即发出首片 chunk(role=assistant),确保 SSE 响应头及时 flush,避免推理模型思考阶段无输出导致前端等待首字节触发 60s 超时;AI_READ_TIMEOUT 由 120s 放宽到 300s,给推理模型思考+生成留足总时长 --- .../java/com/wok/supportbot/app/AssistantApp.java | 13 +++++++------ .../com/wok/supportbot/config/ChatModelFactory.java | 4 ++-- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/wok/supportbot/app/AssistantApp.java b/src/main/java/com/wok/supportbot/app/AssistantApp.java index faa297c..a3a4010 100644 --- a/src/main/java/com/wok/supportbot/app/AssistantApp.java +++ b/src/main/java/com/wok/supportbot/app/AssistantApp.java @@ -44,7 +44,6 @@ import java.util.List; import java.util.Map; import java.util.UUID; import java.util.concurrent.CopyOnWriteArrayList; -import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; @@ -519,11 +518,9 @@ public class AssistantApp { }); // 聚合所有分片用于埋点(在 doFinally 时取完整回复文本) StringBuilder aggregated = new StringBuilder(); - // 首片标记:第一片 delta 需带 role=assistant,后续片仅含 content - AtomicBoolean first = new AtomicBoolean(true); return preserveTrailingWhitespace(rawStream) .doOnNext(aggregated::append) - .map(chunk -> buildOpenAiChunk(completionId, model, created, chunk, first.getAndSet(false), null)) + .map(chunk -> buildOpenAiChunk(completionId, model, created, chunk, false, null)) .doOnComplete(() -> aiCircuitBreaker.recordSuccess(AI_CIRCUIT_KEY)) .doOnError(e -> { aiCircuitBreaker.recordFailure(AI_CIRCUIT_KEY); @@ -543,13 +540,17 @@ public class AssistantApp { usage != null ? usage.getTotalTokens() : null, events)); }) + // 首片(仅 role=assistant、无 content)在流订阅时立即发出,确保 SSE 响应头/首字节及时 flush。 + // 推理模型(如 doubao-seed)思考阶段 delta.content 为空、被 preserveTrailingWhitespace 吞掉, + // 若不提前发首片,思考阶段将无任何字节输出,前端等待首字节会触发 60s 超时。 + .startWith(buildOpenAiChunk(completionId, model, created, "", true, null)) // 流正常结束时追加 finish_reason=stop 的 chunk 与 [DONE] .concatWith(Flux.just( buildOpenAiChunk(completionId, model, created, "", false, "stop"), "[DONE]")) - // 错误兜底:脱敏错误信息,避免泄露内部细节;role 是否出现取决于此前是否已发出过内容片 + // 错误兜底:脱敏错误信息,避免泄露内部细节(首片 role 已提前发出,此处不再带 role) .onErrorResume(e -> openAiFallbackStream(completionId, model, created, - "抱歉,AI 服务调用失败:" + maskError(e.getMessage()), first.get())); + "抱歉,AI 服务调用失败:" + maskError(e.getMessage()), false)); } /** diff --git a/src/main/java/com/wok/supportbot/config/ChatModelFactory.java b/src/main/java/com/wok/supportbot/config/ChatModelFactory.java index 124c6de..15438cc 100644 --- a/src/main/java/com/wok/supportbot/config/ChatModelFactory.java +++ b/src/main/java/com/wok/supportbot/config/ChatModelFactory.java @@ -66,8 +66,8 @@ public class ChatModelFactory { /** AI 模型调用连接超时 */ private static final Duration AI_CONNECT_TIMEOUT = Duration.ofSeconds(10); - /** AI 模型调用读取超时,含流式生成时间 */ - private static final Duration AI_READ_TIMEOUT = Duration.ofSeconds(120); + /** AI 模型调用读取超时,含流式生成时间(推理模型思考+生成耗时较长,放宽到 300s) */ + private static final Duration AI_READ_TIMEOUT = Duration.ofSeconds(300); /** * 构建带超时配置的 RestClient.Builder,供 DashScope 和 OpenAI 兼容路径共用。