fix(ai-chat): 思考型模型的 reasoning 增量实时转发 + 断连提示条(dev-board#364) - #702
Merged
Conversation
病灶:选 Kimi K3 让 AI 修订文档,「思考了 281 秒才开始工作」期间前端零提示。
真机探测(OpenRouter kimi-k3 流式):第 4 秒起每个 chunk 都是 content:"" +
reasoning:"…",前面夹 ": OPENROUTER PROCESSING" 保活注释。openai4j 0.23 的 Delta
没有 reasoning 字段、okhttp-sse 吞掉注释行、langchain4j 只对非 null content 调
onNext——于是几百秒思考期间编排器收到的全是 onNext(""):看门狗被空 token 喂活、
前端一个字节都收不到。281 秒是模型真实思考时长,不是首 token 传输延迟。
后端
- 新增 OpenRouterStreamingChatModel(自有 HTTP + SSE 行协议;复用 langchain4j 的
消息/工具编组、openai4j 的请求序列化、OpenAiStreamingResponseBuilder 的片段组装),
ChatModelFactory.streamingModel 成为两个流式通道的唯一构建口径;Ollama 不变。
HTTP 200 里 data 事件送来的 {"error":…} 也当错误,不再按空回复静默收尾。
- 新增 ReasoningStreamingHandler(onReasoning / onKeepAlive,default 空实现)。
AgentStreamHandler 实现它:reasoning → SSE reasoning_delta(不进正文、不落库、
不回喂模型),两者都刷新看门狗;streamedAnyReasoning 与 streamedAnyToken 分开,
看门狗时限选择认前者、可重放判定只认后者。首字节 60s 保持不变。
- SseEmitterService 心跳扫描提成 heartbeatSweep() 供测试直接调用。
前端
- useAgentStream 认 reasoning_delta → appendReasoning(顶层思考卡 / 最后一个过程卡的
思考条目,与 <thinking> 标签同落点,不过标签解析器);新增 linkStatus
(live / reconnecting + 次数),ChatInterface 输入区在重连期间渲染
chat.linkReconnecting 提示条(zh-CN / en-US)。等待首 token 的读秒本来就从发送起算。
测试(先红后绿,红用还原病灶实测)
- OpenRouterStreamingChatModelTest(假服务端回放真机片段形状:reasoning/正文/保活三通道、
tool_calls 拼装、429 状态码、流内 error 对象、旧 handler 兼容)
- AgentStreamHandlerReasoningTest(reasoning_delta 事件、思考期间看门狗不按零字节掐、
保活刷新看门狗)
- SseEmitterServiceTest.heartbeatSweepReachesEveryLiveConnection
- StreamingTransportFailureTest 改走 streamingModel;ChatModelFactoryTest 的流式类型断言改成 OpenRouterStreamingChatModel
- frontend/tests/project-home/reasoning-stream.test.mjs(ThinkingCard 真实响应式读秒、
reasoning_delta 分派、linkStatus 与提示条、前端判死阈值 = 后端心跳 × 3)
- 领域文档 .claude/agents/ai-chat.md 同步
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
dev-board#364:K3 思考 281 秒才动,前端零提示分不清死机还是思考。
诊断:
修复:新增 OpenRouterStreamingChatModel(自有 HTTP+SSE 行协议,复用 langchain4j 消息/工具编组与 OpenAiStreamingResponseBuilder)作为 OpenRouter 流式唯一通道;ReasoningStreamingHandler 增 onReasoning/onKeepAlive;AgentStreamHandler 转发 SSE reasoning_delta(不进正文不落库不回喂),思考增量与保活注释刷新看门狗,streamedAnyReasoning 与 streamedAnyToken 分开。前端 reasoning_delta 实时渲染进思考卡;新增 linkStatus,重连期间输入区渲染提示条(两套 locales)。
验证:后端 OpenRouterStreamingChatModelTest / AgentStreamHandlerReasoningTest 等 6 组定向用例 + 全量 2766 例;前端 reasoning-stream.test.mjs 9 例、test:project-home 305/305。主会话病灶还原:onReasoning 置空→2 红、前端去掉 reasoning_delta 分支→1 红。主会话另用真实 OpenRouter 跑新通道:普通文本流(usage/finish 正确)与工具调用流(参数正确拼出)通过;K3 真流未跑成——开发 key 余额仅够约 1500 token,K3 预留 131072 被 402 拒绝,错误路径正确抛出。reasoning 解析靠 mock 用例 + 子 agent 对 K3 的真机探测(80 个 reasoning chunk)。
已知边界:reasoning 不持久化、刷新后不回放;未主动请求 reasoning 参数;Office 插件窗格未接该事件(未知事件被忽略)。
🤖 Generated with Claude Code