fix(llm): 修复 thinking mode 的 reasoning_content 协议 400,默认关闭深度思考
build / build-push (push) Successful in 1m31s
build / build-push (push) Successful in 1m31s
线上现象:用户发消息后助手回 「大模型错误:The `reasoning_content` in the thinking mode must be passed back to the API.」 根因:DeepSeek V4 系列(含 deepseek-flash)默认开启 thinking mode。官方协议规定, 只要请求带 tools 参数,历史里所有 assistant 消息的 reasoning_content 都必须原样回传, 否则接口直接回 HTTP 400。而 ChatMessage.toJson() 只输出 role/content/tool_calls/tool_call_id, 把该字段整个丢掉 —— 于是「第一轮模型调工具成功、第二轮请求必炸」。 改动: - ChatMessage 增加 reasoningContent 字段并在 toJson 里输出(非空才输出);ChatResult 同步解析响应字段。 - AgentOrchestrator 新增 assistantOf(),把模型每次返回的思维链写回对应的 assistant 消息 (带 tool_calls 的那条与最终回复那条都要带)。 - DeepSeekClient: * 新增 agent.llm.thinking(auto/enabled/disabled),按配置显式发送 thinking 字段; * 命中 reasoning_content 协议错误时自动关闭 thinking 重试一次(自愈,线上无需等改配置即可恢复); * humanizeApiError 增加该错误的中文话术,避免原样抛出英文协议报错。 - AgentSettings.Llm.thinking 由 boolean 改为三态字符串,默认 disabled: 本工程属工具密集编排,几乎每轮都带 tools,CoT 收益有限却显著增加延迟与 token, 且 thinking 模式下 temperature 会被忽略。 - application.yml 补 agent.llm.thinking: disabled 与说明。 新增 ThinkingModeTest(7 例):字段序列化/省略、disabled 与 auto 的请求体、响应解析、 命中协议错误时的自愈重试、以及非协议错误不被误重试。
This commit is contained in:
@@ -166,14 +166,16 @@ public class AgentOrchestrator {
|
||||
String reply = StringUtils.isNotBlank(result.getContent())
|
||||
? result.getContent()
|
||||
: "(模型没有返回内容,请换个说法再试一次)";
|
||||
messages.add(new ChatMessage(ChatMessage.ROLE_ASSISTANT, reply));
|
||||
messages.add(assistantOf(reply, result.getReasoningContent()));
|
||||
resp.setReply(reply);
|
||||
finish(resp, session, totalPrompt, totalCompletion, out);
|
||||
return resp;
|
||||
}
|
||||
|
||||
// 记录 assistant 的 tool_calls(后续必须以 role=tool 逐条应答,否则上下文非法)
|
||||
ChatMessage assistantMsg = new ChatMessage(ChatMessage.ROLE_ASSISTANT, result.getContent());
|
||||
// ★ 同时带上 reasoning_content:thinking mode 下带 tools 的请求要求历史 assistant 原样回传该字段,
|
||||
// 否则下一轮请求会被接口判 400(The reasoning_content ... must be passed back to the API)。
|
||||
ChatMessage assistantMsg = assistantOf(result.getContent(), result.getReasoningContent());
|
||||
assistantMsg.setToolCalls(result.getToolCalls());
|
||||
messages.add(assistantMsg);
|
||||
|
||||
@@ -324,6 +326,8 @@ public class AgentOrchestrator {
|
||||
}
|
||||
|
||||
String idPrefix = "br_" + System.currentTimeMillis() + "_";
|
||||
// 注:这条 assistant 是「服务端补造」的(模型当轮的消息在挂起时已撤销),没有真实 reasoning_content。
|
||||
// thinking mode 关闭时(agent.llm.thinking=disabled,默认)不需要该字段;若改为开启,此处可能被接口判 400。
|
||||
ChatMessage assistant = new ChatMessage(ChatMessage.ROLE_ASSISTANT, null);
|
||||
List<ToolCall> calls = new ArrayList<>();
|
||||
List<String> contents = new ArrayList<>();
|
||||
@@ -512,6 +516,7 @@ public class AgentOrchestrator {
|
||||
fn.setName(def.getName());
|
||||
fn.setArguments(JSON.toJSONString(args));
|
||||
tc.setFunction(fn);
|
||||
// 同上:服务端补造的 assistant(无真实 reasoning_content),thinking 关闭时无需该字段
|
||||
ChatMessage assistant = new ChatMessage(ChatMessage.ROLE_ASSISTANT, null);
|
||||
assistant.setToolCalls(Collections.singletonList(tc));
|
||||
messages.add(assistant);
|
||||
@@ -797,6 +802,19 @@ public class AgentOrchestrator {
|
||||
return "「" + def.getTitle() + "」执行失败:" + (tr.getError() == null ? "未知原因" : tr.getError());
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造 assistant 消息,并统一挂上 thinking mode 的思维链。
|
||||
*
|
||||
* <p>★ DeepSeek V4 系列默认开启 thinking mode,官方协议要求:只要请求带 {@code tools},
|
||||
* 历史里所有 assistant 消息的 {@code reasoning_content} 必须原样回传,否则 HTTP 400。
|
||||
* 因此模型每一次返回的思维链都要落进对应的 assistant 消息里。
|
||||
*/
|
||||
private ChatMessage assistantOf(String content, String reasoningContent) {
|
||||
ChatMessage m = new ChatMessage(ChatMessage.ROLE_ASSISTANT, content);
|
||||
m.setReasoningContent(reasoningContent);
|
||||
return m;
|
||||
}
|
||||
|
||||
private void finish(ChatResponse resp, SessionStore.SessionState session,
|
||||
int promptTokens, int completionTokens, EventSink out) {
|
||||
resp.setPromptTokens(promptTokens);
|
||||
|
||||
Reference in New Issue
Block a user