新闻详情

新闻详情

首页 / 资讯中心 / 详情

Operit 源码剖析:DeepSeek Responses 思考模式下 Commentary 思考的回归测试覆盖与回放顺序保障

发布时间:2026/9/27 21:41:08来源:尧图网络
Operit 源码剖析:DeepSeek Responses 思考模式下 Commentary 思考的回归测试覆盖与回放顺序保障
AI Agent人工智能大模型AI 应用工具调用本地部署MCP ClientsAgent 记忆【免费下载链接】OperitThe most powerful AI agent and AI chat software on Android/Operit是一款Android上能力最为强大、发展最久的AI Agent项目地址https://gitcode.com/gh_mirrors/op/Operit点击查看免费下载本指南围绕 Operit 仓库中docs/TODO/deepseek_responses_reasoning_replay_20260907/02_regression_coverage.md展开讲解 DeepSeek Responses 思考模式下message.phasecommentary这一思考表示如何被持久化为隐藏 metadata、并在下一次工具续接请求中回放为带reasoning_text的reasoningitem。读完本文你将理解该缺陷的成因HTTP 400The reasoning_text in the thinking mode must be passed back to the API、回归测试的四个断言维度以及DeepseekProvider.kt/OpenAIProvider.kt中对应的编码与回放实现细节可直接据此在仓库中定位并验证相关代码与测试。一、背景为什么需要Commentary 思考回放的回归覆盖1.1 问题根源思考模式的两种思考表示从 任务索引文档 可知DeepSeek Responses 工具续接tool continuation可能收到如下 HTTP 400 错误The reasoning_text in the thinking mode must be passed back to the API.原因是思考模式下DeepSeek 返回的思考内容存在两种表示而旧的 DeepSeek adapter 只保留了其中一种表示形式输出 item 类型说明纯文本reasoningitemoutput[].type reasoning用户可见的think思考来源旧实现已能保留message.phasecommentary消息output[].type message且phase commentary部分思考以评论性消息形态下发旧实现未持久化导致续接请求丢失这段无状态历史实现文档 进一步描述了修复前的行为流式渲染时message.phasecommentary会被当作reasoningitem 旁边第二个可见思考块输出同时后续工具续接请求中也缺少这段 commentary 表示。修复目标非常明确——commentary 只应作为隐藏 metadata 持久化绝不渲染为think且回放时必须以reasoning_text形式插入到相关function_call之前。1.2 协议层面的约定DeepSeek Responses 与 Web Search 协议 对该行为给出了权威约定思考模式下发生客户端函数调用时DeepSeek 返回的纯文本reasoningitem 与message.phasecommentary都属于后续请求必须携带的无状态历史。reasoningitem 使用 Responses reasoning 隐藏 metadata 保存并在下一轮 input 中恢复到对应的 assistant message 与function_call之前它同时是用户可见的think来源。commentarymessage 使用 Responses output item 隐藏 metadata 保存界面不渲染这段文本回放到 input 时改写成带reasoning_text的reasoningitem。同时协议明确了两条硬性约束思考模式里function_call前面必须是reasoning_text把 commentary 原样写成output_text消息会返回 400OpenAI Responses 的加密 reasoning 合约保持不变通用 adapter 只维护encrypted_content格式纯文本reasoning_text的保存与恢复只属于 DeepSeek 侧DeepseekResponsesProvider与DeepseekResponsesPayloadAdapter。二、回归覆盖的设计Previous Behavior → Change → Expected Result02_regression_coverage.md 本身即一份精炼的回归覆盖设计文档其三段式结构是本文的核心骨架。2.1 Previous Behavior既有回归的盲区The existing DeepSeek regression only verifies a standalonereasoningitem. It does not cover themessage.phasecommentaryrepresentation observed in the failing request flow.既有 DeepSeek 回归测试只验证独立的reasoningitem纯文本思考完全没有覆盖失败请求流中观察到的message.phasecommentary表示。也就是说即使reasoningitem 路径一切正常commentary 路径的损坏依然会绕过测试直接上生产环境。2.2 Change新增测试目标Add tests that derive metadata from a DeepSeek commentary message and assert that the next request places areasoningitem withreasoning_textbefore the corresponding function call and output.新增测试需要验证两条链路派生 metadata从 DeepSeek commentary message 成功派生出隐藏 metadata而非丢失或渲染回放顺序下一次请求中带reasoning_text的reasoningitem 必须出现在对应的function_call与function_call_output之前。2.3 Expected Result失败判定标准The test fails if commentary thought is rendered as visible thinking, if it is not persisted as hidden metadata, or if replay order separates a function call from its output.测试在以下三种情况下必须失败这是回归测试的护栏语义失败条件违反的协议约定commentary thought 被渲染为可见思考可见思考只应来自reasoningitemcommentary 不得成为第二个 thinking 块commentary 未被持久化为隐藏 metadatacommentary 属于后续请求必须携带的无状态历史不得丢弃回放顺序把 function call 与它的 output 分离function_call与对应function_call_output必须相邻且由call_id绑定这三条失败判定与后续的四个测试用例一一对应构成了完整的回归闭环。三、测试用例逐条解析断言维度与关键代码回归测试集中在 DeepseekResponsesPayloadAdapterTest.kt共四个用例覆盖纯文本思考与commentary 思考两大路径。3.1 纯文本 reasoning 保留并在 function call 前回放Test fun plaintext reasoning is preserved and replayed before function calls() { val reasoningItem JSONObject() .put(type, reasoning) .put(id, rs_plain_1) .put(content, reasoningContent) // typereasoning_text val metadataTag DeepseekResponsesPayloadAdapter.parseNonStreamingResponse( JSONObject({output:[$reasoningItem]}) ).reasoningMetadataTags.single() // ... 构造带 metadataTag 的续接请求后调用 toResponsesRequest assertEquals(reasoning, input.getJSONObject(0).getString(type)) assertEquals(rs_plain_1, input.getJSONObject(0).getString(id)) assertFalse(input.getJSONObject(0).has(encrypted_content)) assertFalse(input.getJSONObject(0).has(summary)) assertEquals(message, input.getJSONObject(1).getString(type)) assertEquals(function_call, input.getJSONObject(2).getString(type)) assertEquals(function_call_output, input.getJSONObject(3).getString(type)) }该用例验证纯文本reasoningitem 经解析后生成reasoningMetadataTags隐藏 metadata回放时input顺序为reasoning → message → function_call → function_call_output且原始content含reasoning_text与 item IDrs_plain_1被原样保留不携带encrypted_content与summary——即保持纯文本合约。3.2 加密 reasoning 不进入 DeepSeek metadataTest fun encrypted reasoning is not emitted as deepseek reasoning metadata() { val reasoningItem JSONObject() .put(type, reasoning) .put(id, rs_encrypted_1) .put(encrypted_content, encrypted-reasoning) .put(summary, JSONArray()) val parsed DeepseekResponsesPayloadAdapter.parseNonStreamingResponse(...) assertEquals(0, parsed.reasoningMetadataTags.size) }该用例守卫 OpenAI Responses 的加密 reasoning 合约encrypted_content形式的 reasoning item 不得被当作 DeepSeek 纯文本 metadata 处理reasoningMetadataTags必须为空确保两条协议互不串扰。3.3 commentary 思考在相关 function call 之前被保留核心回归Test fun commentary thinking is preserved before the related function call() { val commentaryItem JSONObject() .put(type, message) .put(id, msg_commentary_1) .put(role, assistant) .put(phase, commentary) .put(content, commentaryContent) // typeoutput_text val parsed DeepseekResponsesPayloadAdapter.parseNonStreamingResponse(...) assertEquals(0, parsed.reasoningChunks.size) assertEquals(0, parsed.textChunks.size) val metadataTag parsed.outputItemMetadataTags.single() // ... val input DeepseekResponsesPayloadAdapter.toResponsesRequest(chatStyleRequest) .getJSONArray(input) assertEquals(3, input.length()) assertEquals(reasoning, input.getJSONObject(0).getString(type)) assertEquals(msg_commentary_1, input.getJSONObject(0).getString(id)) val replayedContent input.getJSONObject(0).getJSONArray(content) assertEquals(reasoning_text, replayedContent.getJSONObject(0).getString(type)) assertEquals(I need to activate the package before calling its tool., replayedContent.getJSONObject(0).getString(text)) assertEquals(function_call, input.getJSONObject(1).getString(type)) assertEquals(call_commentary_1, input.getJSONObject(1).getString(call_id)) assertEquals(function_call_output, input.getJSONObject(2).getString(type)) assertEquals(call_commentary_1, input.getJSONObject(2).getString(call_id)) }这是本文档的核心回归用例完整对应三条失败判定不渲染为可见思考reasoningChunks.size 0且textChunks.size 0——commentary 文本完全不进入流式输出块持久化为隐藏 metadataoutputItemMetadataTags.single()成功生成 metadata tag回放顺序正确回放后input长度恰为 3顺序严格为reasoning携带msg_commentary_1的 ID 与reasoning_text→function_call→function_call_output且function_call与function_call_output通过相同call_idcall_commentary_1绑定绝不被其他 message 隔开。3.4 流式缓冲completed item 无内容时仍保留 commentary 文本Test fun buffered commentary thinking is preserved when the completed item has no content() { val commentaryItem JSONObject() .put(type, message) .put(role, assistant) .put(phase, commentary) // 注意无 id、无 content val commentaryText I need to wait for the command before deciding the next action. val metadataTag DeepseekResponsesPayloadAdapter.createStreamingCommentaryMetadataTag( commentaryItem, commentaryText ) ?: throw AssertionError(Expected commentary metadata) // ... assertEquals(3, input.length()) assertEquals(reasoning, input.getJSONObject(0).getString(type)) assertEquals(reasoning_text, replayedContent.getJSONObject(0).getString(type)) assertEquals(commentaryText, replayedContent.getJSONObject(0).getString(text)) assertEquals(function_call, input.getJSONObject(1).getString(type)) assertEquals(function_call_output, input.getJSONObject(2).getString(type)) }该用例覆盖流式场景当output_item.done时 commentary item 本身已不含content正文 delta 早已通过response.output_text.delta增量到达此时必须依赖createStreamingCommentaryMetadataTag用缓冲的 commentary 文本生成 metadata保证即使 completed item 为空思考文本也不丢失。3.5 测试的运行方式该测试属于 JVM 单元测试位于app/src/test/java使用 JUnit 4 org.json可在仓库根目录通过 Gradle 单元测试任务运行例如./gradlew :app:testDebugUnitTest --tests com.ai.assistance.operit.api.chat.llmprovider.DeepseekResponsesPayloadAdapterTest测试中辅助构造的singleToolContinuationRequest负责生成assistant 消息 tool_calls tool 结果的 Chat 风格请求体再交由DeepseekResponsesPayloadAdapter.toResponsesRequest转换为 Responsesinput数组进行断言完整复刻了真实工具续接的请求形态。四、源码级原理metadata 编码与回放顺序的实现4.1 解析阶段commentary 与 reasoning 的分流在 DeepseekProvider.kt 的parseNonStreamingResponse中遍历output数组时对type message的 item 先做 commentary 判定val isCommentaryMessage item.optString(phase, ).trim().equals(commentary, ignoreCase true) if (isCommentaryMessage) { // Commentary is continuation state for the next request, not a second think block. createCommentaryMetadataTag(item)?.let { metadataTag - outputItemMetadataTags.add(metadataTag) reasoningObserved true } continue // 不进入 textChunks / reasoningChunks即不产生可见输出 }而type reasoning的 item 则走createReasoningMetadataTag生成reasoningMetadataTags同时仍会提取reasoningChunks用于可见think渲染。由此可见commentary 与 reasoning 在同一轮输出中分别落盘为两类隐藏 metadata可见思考只来自 reasoning item。4.2 编码Base64 JSON payload meta协议标记两类 metadata 的编码方式高度一致定义在 ChatMarkupRegex.ktfun openAiResponsesReasoningMetaTag(payloadBase64: String): String { return meta provideropenai:responses_reasoning$payloadBase64/meta } fun openAiResponsesOutputItemMetaTag(payloadBase64: String): String { return meta provideropenai:responses_output_item$payloadBase64/meta }createReasoningMetadataTagDeepseekProvider.kt#L708-L726要求type reasoning、id非空且content含reasoning_textpayload 保存reasoning_id与原始contentcreateCommentaryMetadataTagDeepseekProvider.kt#L728-L753要求type message且phase commentarypayload 保存type、role、id可选与content并附注释DeepSeek emits some thinking as a commentary message instead of a reasoning itemcreateStreamingCommentaryMetadataTagDeepseekProvider.kt#L755-L781流式场景下基于缓冲文本重建contenttypeoutput_text同样编码为meta provideropenai:responses_output_item标记。这些meta标记属于隐藏协议标记Android 与 Web 渲染层、复制文本清理removeOpenAiResponsesReasoningMeta/removeOpenAiResponsesOutputItemMeta/removeOpenAiResponsesProtocolMeta见 ChatMarkupRegex.kt#L296-L306均不展示它们从而保证隐藏 metadata 始终不出现在用户可见请求内容中。4.3 回放阶段toResponsesRequest的 input 重建顺序在 DeepseekProvider.kt 的 input 构建逻辑 中遍历历史messages时对 assistant 消息依次执行val reasoningItemReplayed appendReasoningItemsFromAssistantMessage(message, input) val commentaryMessageReplayed appendOutputItemsFromAssistantMessage(message, input) val convertedContent convertMessageContentForResponses( content message.opt(content), removeThinkingContent reasoningItemReplayed || commentaryMessageReplayed )这段代码揭示了回放顺序的核心机制先恢复reasoningitemappendReasoningItemsFromAssistantMessage解码meta provideropenai:responses_reasoning载荷经appendReasoningItemFromMetadata原样写回input再恢复 commentary 转换的 reasoning itemappendOutputItemsFromAssistantMessage解码meta provideropenai:responses_output_item载荷正文转换时移除已回放的思考内容removeThinkingContent reasoningItemReplayed || commentaryMessageReplayed经ChatUtils.removeThinkingContent剔除think避免可见思考文本再次作为正文提交最后追加function_callitems保留call_id绑定。appendOutputItemFromMetadataDeepseekProvider.kt#L1170-L1206是 commentary 回放的关键metadatatype message且role assistant时调用convertCommentaryContentToReasoningContent把output_text/text/reasoning_text统一转换为reasoning_text再构造typereasoning的 item 插入input。源码注释直接点明了 400 的根因// Thinking-mode function calls require reasoning_text. Commentary is that thought // in a message envelope; replaying it as output_text makes DeepSeek return 400.由此input最终呈现的稳定顺序为reasoning含reasoning_text→message→function_call→function_call_output与协议思考模式里function_call前面必须是reasoning_text完全吻合。4.4 流式路径OpenAIProvider 的 provider 扩展点OpenAIProvider.kt 为 DeepSeek 提供了一组protected open扩展点其中createResponsesMessageMetadataTag默认返回null由DeepseekResponsesProvider覆写override fun createResponsesMessageMetadataTag(item: JSONObject, bufferedText: String): String? { return DeepseekResponsesPayloadAdapter.createStreamingCommentaryMetadataTag(item, bufferedText) }流式事件处理在 OpenAIProvider.kt#L2644-L2729 中response.output_item.done到达时若该 message item 是 commentary则调用扩展点生成 metadata tag 并通过emitter.emitMetadataTag落盘这正是索引文档所述在完成的 Responses message item 边界调用 provider extension hook。同时DeepseekResponsesProvider声明useResponsesApi true、bufferResponsesOutputTextUntilItemDone trueDeepseekProvider.kt#L1290-L1291isResponsesCommentaryMessage覆写为按phase commentary判定DeepseekProvider.kt#L1293-L1295emitBufferedResponsesMessageItemContentOpenAIProvider.kt#L3007-L3030在补发缓冲正文时再次检查 commentary——若是只标记reasoningObserved true而不调用processResponsesRegularContentDelta注释写明Emitting it as think would show a second thinking block next to the reasoning item。非流式路径同理parseResponsesNonStreamingResponse返回的reasoningMetadataTags与outputItemMetadataTags在 OpenAIProvider.kt#L3367-L3372 被逐一emitMetadataTag。至此流式与非流式两条路径都满足commentary 只进隐藏 metadata、绝不进入界面。五、回归护栏自查清单结合02_regression_coverage.md的 Expected Result可在代码审查或修改后快速自检可见性commentary 文本是否出现在textChunks/reasoningChunks或流式正文输出中应当为否见parseNonStreamingResponse的continue与emitBufferedResponsesMessageItemContent的 commentary 分支持久化commentary item 是否成功派生meta provideropenai:responses_output_item标记对应outputItemMetadataTags.single()断言回放顺序input中reasoning含reasoning_text是否位于对应function_call与function_call_output之前且call_id一致对应 3.3 / 3.4 用例的input.length()与顺序断言协议隔离OpenAI Responses 的encrypted_contentreasoning 是否仍保持原合约未混入 DeepSeek 的纯文本 metadata对应 3.2 用例隐藏标记清理回放后用户可见的请求文本中是否已通过removeThinkingContent与stripOpenAiResponsesProtocolMarkup移除think与meta标记六、相关文件速查文件作用02_regression_coverage.md本文主体回归覆盖设计与失败判定标准01_capture_commentary_reasoning.md实现方案commentary 捕获与回放index.md任务范围DeepseekProvider.kt、OpenAIProvider.kt、测试与协议文档deepseek_responses_web_search.md协议所有权reasoning_text保存/恢复归属与 400 约束DeepseekResponsesPayloadAdapterTest.kt四个回归测试用例DeepseekProvider.ktmetadata 编码、解码与 input 重建OpenAIProvider.kt流式事件处理与 provider 扩展点ChatMarkupRegex.ktmeta协议标记的生成、提取与清理综上02_regression_coverage.md所定义的回归覆盖通过可见性、持久化、顺序、协议隔离四个断言维度将 DeepSeek Responses 思考模式下的 commentary 回放行为固化为可执行的测试护栏而源码实现则通过meta provideropenai:responses_output_item隐藏标记与reasoning_text转换确保每一次工具续接都完整携带所有持久化的思考表示且严格保持原始顺序。赞分享AI Agent人工智能大模型AI 应用工具调用本地部署MCP ClientsAgent 记忆【免费下载链接】OperitThe most powerful AI agent and AI chat software on Android/Operit是一款Android上能力最为强大、发展最久的AI Agent项目地址https://gitcode.com/gh_mirrors/op/Operit点击查看免费下载相关推荐Operit 中 DeepSeek Responses 推理重放Commentary 思考的隐藏元数据捕获与按序回放实现Operit 中 DeepSeek Responses 推理重放Commentary 思考的隐藏元数据捕获与按序回放实现 导读 本文剖析 OperitAndAI Agent人工智能大模型AI 应用工具调用本地部署MCP ClientsAgent 记忆GUI 自动化如何用MiniCPM4混合思考模式提升3倍推理速度深思考与非思考模式切换完全指南如何用MiniCPM4混合思考模式提升3倍推理速度深思考与非思考模式切换完全指南 MiniCPM4是OpenBMB开源社区推出的超高效端侧大语言模型通过创新大模型本地部署模型量化微调LoRA工具调用openBMBAscendGLM-4.5推理模式思考与非思考双模式GLM 4.5推理模式思考与非思考双模式 概述 GLM 4.5作为智谱AI推出的新一代混合推理模型创新性地引入了 思考模式Thinking Mode 和基础模型大模型人工智能上一篇FitGirl游戏启动器完全指南3分钟打造你的专属游戏库下一篇FitGirl游戏启动器终极指南3步打造个人游戏库的完整方法创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

C++模板元编程深入:编译期计算的终极武器,面试必考全解析 2026/9/27 22:29:17

C++模板元编程深入:编译期计算的终极武器,面试必考全解析

C++模板元编程深入:编译期计算的终极武器,面试必考全解析 引言 模板元编程(Template Metaprogramming)是C++中最强大也最神秘的技术之一。它允许我们在编译期执行计算、生成代码,甚至实现图灵完备的计算。大厂面试中,模板元编程是区分初级和高级程序员的分水岭。本文将…

阅读更多 →
宝安小学网站建设避坑指南:搞定备案与性能优化只需3步 2026/9/27 22:29:11

宝安小学网站建设避坑指南:搞定备案与性能优化只需3步

宝安小学网站建设避坑指南:搞定备案与性能优化只需3步 备案号卡在教育局审批三天没动?备案流程一头雾水,看着工信部系统里的状态提示心里直打鼓,生怕耽误了开学前的系统上线。别急,这种焦虑我太熟悉了。很多做教育信息化项目的同行,技术底子不差,但一…

阅读更多 →
RL-赵-(七)-不基于模型2-计算Q/ActionValue-TD算法01:Sarsa04【例子:只关注从一个特定的状态到达目标的一个策略或者路径,而不是要求每一个状态都达到最优策略】 2026/9/27 22:29:10

RL-赵-(七)-不基于模型2-计算Q/ActionValue-TD算法01:Sarsa04【例子:只关注从一个特定的状态到达目标的一个策略或者路径,而不是要求每一个状态都达到最优策略】

2、Sarsa案例举个例子: 任务的目标是找到一条较好的路径,从一个特定的starting state到target state。 这个任务和之前的任务不同,之前的任务是需要对每个state找到最优的策略,但在这个例子其实我们不关注每一个状态,我…

阅读更多 →
太好了---拼多多每个账号注册会送2块钱 2026/9/27 22:29:10

太好了---拼多多每个账号注册会送2块钱

这样以后买2块钱一根的数据线就不用花钱了至于美团和京东10块钱话费我就不要了,太多了。。。。。。。。。

阅读更多 →
VScode 插件 package.json 中 Contribution 字段配置详解:从 settings.json 骨架到 TaoToken 统一 Key 接入 2026/9/27 22:29:10

VScode 插件 package.json 中 Contribution 字段配置详解:从 settings.json 骨架到 TaoToken 统一 Key 接入

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
别再说不懂AI了!一文看懂AI应用分类与TaoToken统一接入配置 2026/9/27 22:29:10

别再说不懂AI了!一文看懂AI应用分类与TaoToken统一接入配置

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉