From b4ced02d3e127066cb0aa2371ea12ec3c3749958 Mon Sep 17 00:00:00 2001 From: JEECG <445654970@qq.com> Date: Wed, 29 Apr 2026 23:24:19 +0800 Subject: [PATCH] =?UTF-8?q?DeepSeek=E5=A4=A7=E6=A8=A1=E5=9E=8B=E5=88=87?= =?UTF-8?q?=E6=8D=A2=E4=B8=BA=E6=96=B0=E5=8F=91=E5=B8=83deepseek-v4-flash?= =?UTF-8?q?=20=EF=BC=8C=E6=B5=81=E7=A8=8B=E4=B8=AD=E8=B0=83=E7=94=A8?= =?UTF-8?q?=E5=87=BA=E7=8E=B0=E5=BC=82=E5=B8=B8=20#9585?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../modules/airag/llm/consts/LLMConsts.java | 36 ++++++++ .../airag/llm/handler/AIChatHandler.java | 82 ++++++++++++++++++- jeecg-boot/pom.xml | 2 +- .../super/airag/aimodel/components/model.json | 8 +- 4 files changed, 123 insertions(+), 5 deletions(-) diff --git a/jeecg-boot/jeecg-boot-module/jeecg-boot-module-airag/src/main/java/org/jeecg/modules/airag/llm/consts/LLMConsts.java b/jeecg-boot/jeecg-boot-module/jeecg-boot-module-airag/src/main/java/org/jeecg/modules/airag/llm/consts/LLMConsts.java index 8a1c42786..bcd507968 100644 --- a/jeecg-boot/jeecg-boot-module/jeecg-boot-module-airag/src/main/java/org/jeecg/modules/airag/llm/consts/LLMConsts.java +++ b/jeecg-boot/jeecg-boot-module/jeecg-boot-module-airag/src/main/java/org/jeecg/modules/airag/llm/consts/LLMConsts.java @@ -98,6 +98,42 @@ public class LLMConsts { */ public static final String DEEPSEEK_REASONER = "deepseek-reasoner"; + //update-begin---author:scott ---date:20260429 for:[issues/9585]DeepSeek大模型切换为新发布deepseek-v4-flash,流程中调用出现异常------------ + /** + * DEEPSEEK 推理模型(返回 reasoning_content 字段、在多轮工具调用中要求把 reasoning_content 回传)集合, + * 后续 DeepSeek 新增推理模型时在此追加;非推理模型(如 deepseek-chat)不要加入。 + * 触发场景:仅当对话存在工具调用导致的多轮请求时才会出现 "reasoning_content must be passed back" 错误, + * 单轮 Q&A(如 AI 应用聊天无工具)不会触发,但开启 sendThinking 也无副作用。 + */ + public static final Set DEEPSEEK_THINKING_MODELS = new HashSet<>(Arrays.asList( + "deepseek-reasoner", + "deepseek-v4-flash", + "deepseek-v4-pro" + )); + + /** + * 判断指定模型名是否为 DeepSeek 推理模型(返回 reasoning_content 字段) + * 匹配规则:先做大小写不敏感的精确匹配,再做关键字包含匹配(reasoner/v4-flash/v4-pro) + * 以兼容带版本后缀的变体(如 deepseek-v4-flash-0428) + * + * @param modelName 模型名(大小写不敏感、首尾空白容错) + * @return true=推理模型;false=非推理模型或空 + */ + public static boolean isDeepSeekThinkingModel(String modelName) { + if (modelName == null || modelName.trim().isEmpty()) { + return false; + } + String name = modelName.trim().toLowerCase(); + if (DEEPSEEK_THINKING_MODELS.contains(name)) { + return true; + } + // 兼容带版本后缀或厂商前缀的变体 + return name.contains("reasoner") + || name.contains("v4-flash") + || name.contains("v4-pro"); + } + //update-end---author:scott ---date:20260429 for:[issues/9585]DeepSeek大模型切换为新发布deepseek-v4-flash,流程中调用出现异常------------ + /** * 知识库类型:知识库 */ diff --git a/jeecg-boot/jeecg-boot-module/jeecg-boot-module-airag/src/main/java/org/jeecg/modules/airag/llm/handler/AIChatHandler.java b/jeecg-boot/jeecg-boot-module/jeecg-boot-module-airag/src/main/java/org/jeecg/modules/airag/llm/handler/AIChatHandler.java index a3e839415..d0d89fa60 100644 --- a/jeecg-boot/jeecg-boot-module/jeecg-boot-module-airag/src/main/java/org/jeecg/modules/airag/llm/handler/AIChatHandler.java +++ b/jeecg-boot/jeecg-boot-module/jeecg-boot-module-airag/src/main/java/org/jeecg/modules/airag/llm/handler/AIChatHandler.java @@ -16,6 +16,7 @@ import org.jeecg.common.util.AssertUtils; import org.jeecg.common.util.filter.SsrfFileTypeFilter; import org.jeecg.common.util.oConvertUtils; import org.jeecg.config.AiChatConfig; +import org.jeecg.config.AiRagConfigBean; import org.jeecg.modules.airag.common.consts.AiragConsts; import org.jeecg.modules.airag.common.handler.AIChatParams; import org.jeecg.modules.airag.common.handler.IAIChatHandler; @@ -25,7 +26,6 @@ import org.jeecg.modules.airag.llm.entity.AiragMcp; import org.jeecg.modules.airag.llm.entity.AiragModel; import org.jeecg.modules.airag.llm.mapper.AiragMcpMapper; import org.jeecg.modules.airag.llm.mapper.AiragModelMapper; -import org.jeecg.config.AiRagConfigBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; @@ -119,6 +119,9 @@ public class AIChatHandler implements IAIChatHandler { */ public String completions(AiragModel airagModel, List messages, AIChatParams params) { params = mergeParams(airagModel, params); + //update-begin---author:scott ---date:20260429 for:[issues/9585]DeepSeek大模型切换为新发布deepseek-v4-flash,流程中调用出现异常------------ + messages = injectThinkingPlaceholderIfNeeded(messages, airagModel.getModelName()); + //update-end---author:scott ---date:20260429 for:[issues/9585]DeepSeek大模型切换为新发布deepseek-v4-flash,流程中调用出现异常------------ String resp = null; try { resp = llmHandler.completions(messages, params); @@ -205,9 +208,64 @@ public class AIChatHandler implements IAIChatHandler { */ private TokenStream chat(AiragModel airagModel, List messages, AIChatParams params) { params = mergeParams(airagModel, params); + //update-begin---author:scott ---date:20260429 for:[issues/9585]DeepSeek大模型切换为新发布deepseek-v4-flash,流程中调用出现异常------------ + messages = injectThinkingPlaceholderIfNeeded(messages, airagModel.getModelName()); + //update-end---author:scott ---date:20260429 for:[issues/9585]DeepSeek大模型切换为新发布deepseek-v4-flash,流程中调用出现异常------------ return llmHandler.chat(messages, params); } + //update-begin---author:scott ---date:20260429 for:[issues/9585]DeepSeek大模型切换为新发布deepseek-v4-flash,流程中调用出现异常------------ + /** + * 当目标模型是 DeepSeek 推理模型(deepseek-v4-flash 等)时, + * 为历史 AI 消息(从 MessageHistory 重建出来的、thinking 字段为空的 AiMessage)注入占位 thinking。 + * + * 原因:DeepSeek 推理模型校验请求时要求每条 assistant 历史消息携带 reasoning_content 字段; + * langchain4j 的 sendThinking 仅在 AiMessage.thinking() 非空时才会注入 reasoning_content; + * 而历史持久化层(MessageHistory)目前没有保存 reasoning_content,重建出的 AiMessage thinking 始终为 null, + * 导致 DeepSeek 返回 "The reasoning_content in the thinking mode must be passed back to the API." + * + * 临时方案:注入占位字符串让 langchain4j 通过 isNullOrEmpty 校验、把 reasoning_content 字段带上, + * 后续若 MessageHistory 升级支持持久化 reasoning_content 可去掉该兜底。 + * + * @param messages 原始消息列表(可能为 null/空) + * @param modelName 目标模型名(用于判定是否需要注入) + * @return 处理后的新消息列表(若无需处理则原样返回) + * @author scott + * @date 2026-04-29 + */ + private static List injectThinkingPlaceholderIfNeeded(List messages, String modelName) { + if (messages == null || messages.isEmpty() + || !LLMConsts.isDeepSeekThinkingModel(modelName)) { + return messages; + } + List result = new ArrayList<>(messages.size()); + int injected = 0; + for (ChatMessage msg : messages) { + if (msg instanceof AiMessage) { + AiMessage aiMsg = (AiMessage) msg; + if (oConvertUtils.isEmpty(aiMsg.thinking())) { + AiMessage rebuilt = AiMessage.builder() + .text(aiMsg.text()) + // 占位:让 langchain4j 把 reasoning_content 字段带上以满足 DeepSeek 校验 + .thinking("...") + .toolExecutionRequests(aiMsg.toolExecutionRequests()) + .attributes(aiMsg.attributes()) + .build(); + result.add(rebuilt); + injected++; + continue; + } + } + result.add(msg); + } + if (injected > 0) { + log.info("[AI-CHAT][issues/9585] 为 DeepSeek 推理模型[{}]的 {} 条历史 AI 消息注入了占位 thinking", + modelName, injected); + } + return result; + } + //update-end---author:scott ---date:20260429 for:[issues/9585]DeepSeek大模型切换为新发布deepseek-v4-flash,流程中调用出现异常------------ + /** * 使用默认模型聊天 * @@ -306,6 +364,28 @@ public class AIChatHandler implements IAIChatHandler { buildPlugins(params); } + //update-begin---author:scott ---date:20260429 for:[issues/9585]DeepSeek大模型切换为新发布deepseek-v4-flash,流程中调用出现异常------------ + // 仅对 DeepSeek 推理模型(deepseek-reasoner/deepseek-v4-flash 等)开启思考过程的捕获与回传, + // 否则 deepseek-v4-flash 在工具调用多轮对话中会返回: + // "The reasoning_content in the thinking mode must be passed back to the API." + // 注意:不要对 deepseek-chat 等非推理模型开启,避免无意义的请求字段污染。 + boolean isDsThinking = LLMConsts.isDeepSeekThinkingModel(modelName); + log.info("[AI-CHAT][issues/9585] mergeParams provider={}, modelName={}, isDeepSeekThinkingModel={}, params.returnThinking={}, params.sendThinking={}", + airagModel.getProvider(), modelName, isDsThinking, params.getReturnThinking(), params.getSendThinking()); + if (isDsThinking) { + // returnThinking: 把响应中的 reasoning_content 解析到 AiMessage.thinking + if (null == params.getReturnThinking()) { + params.setReturnThinking(true); + } + // sendThinking: 把 AiMessage.thinking 以 reasoning_content 字段回传到下次请求 + if (null == params.getSendThinking()) { + params.setSendThinking(true); + } + log.info("[AI-CHAT][issues/9585] mergeParams after-fix returnThinking={}, sendThinking={}", + params.getReturnThinking(), params.getSendThinking()); + } + //update-end---author:scott ---date:20260429 for:[issues/9585]DeepSeek大模型切换为新发布deepseek-v4-flash,流程中调用出现异常------------ + return params; } diff --git a/jeecg-boot/pom.xml b/jeecg-boot/pom.xml index cba9cf605..1c417b26b 100644 --- a/jeecg-boot/pom.xml +++ b/jeecg-boot/pom.xml @@ -546,7 +546,7 @@ org.jeecgframework.boot3 jeecg-boot-starter-ai - 3.9.2 + 3.9.2.1 diff --git a/jeecgboot-vue3/src/views/super/airag/aimodel/components/model.json b/jeecgboot-vue3/src/views/super/airag/aimodel/components/model.json index 8afea82cd..b2bfae90c 100644 --- a/jeecgboot-vue3/src/views/super/airag/aimodel/components/model.json +++ b/jeecgboot-vue3/src/views/super/airag/aimodel/components/model.json @@ -21,12 +21,14 @@ "title": "DeepSeek", "value": "DEEPSEEK", "LLM": [ - {"label": "deepseek-reasoner", "value": "deepseek-reasoner","descr": "【官方模型】深度求索 新推出的推理模型R1满血版\n火便全球。\n支持64k上下文,其中支持8k最大回复。","type": "text"}, - {"label":"deepseek-chat", "value": "deepseek-chat","descr": "最强开源 MoE 模型 DeepSeek-V3,全球首个在代码、数学能力上与GPT-4-Turbo争锋的模型,在代码、数学的多个榜单上位居全球第二;","type": "text"} + {"label": "deepseek-v4-pro", "value": "deepseek-v4-pro","descr": "【官方模型】深度求索 新推出的推理模型R1满血版\n火便全球。\n支持64k上下文,其中支持8k最大回复。","type": "text"}, + {"label": "deepseek-v4-flash", "value": "deepseek-v4-flash","descr": "【官方模型】深度求索 新推出的推理模型R1满血版\n火便全球。\n支持64k上下文,其中支持8k最大回复。","type": "text"}, + {"label": "deepseek-reasoner", "value": "deepseek-reasoner","descr": " 2026/07/24下线,【官方模型】深度求索 新推出的推理模型R1满血版\n火便全球。\n支持64k上下文,其中支持8k最大回复。","type": "text"}, + {"label":"deepseek-chat", "value": "deepseek-chat","descr": "2026/07/24下线,最强开源 MoE 模型 DeepSeek-V3,全球首个在代码、数学能力上与GPT-4-Turbo争锋的模型,在代码、数学的多个榜单上位居全球第二;","type": "text"} ], "type": ["LLM"], "baseUrl": "https://api.deepseek.com/v1", - "LLMDefaultValue": "deepseek-chat" + "LLMDefaultValue": "deepseek-v4-pro" }, { "title": "Ollama",