From 0197a9de05f108881582d7d92a183d8ca93e2a43 Mon Sep 17 00:00:00 2001 From: lijing Date: Mon, 12 Oct 2026 11:57:49 +0800 Subject: [PATCH] =?UTF-8?q?fix(llm):=20=E5=A4=A7=E6=A8=A1=E5=9E=8B=20api-k?= =?UTF-8?q?ey=20=E5=90=AF=E5=8A=A8=E8=87=AA=E6=A3=80=EF=BC=8C=E7=BC=BA?= =?UTF-8?q?=E9=85=8D/=E5=8D=A0=E4=BD=8D=E7=AC=A6=E7=9B=B4=E6=8E=A5=20ERROR?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 线上容器漏注入 DEEPSEEK_API_KEY 时,api-key 会落到 application.yml 里的中文 占位符,直到某个用户发起一次对话才以 HTTP 401 暴露: ResourceAccessException: I/O error on POST ... java.net.HttpRetryException: cannot retry due to server authentication, in streaming mode 排查成本很高。 DeepSeekClient 增加 @PostConstruct 自检(可 grep [大模型]): - 为空 / 仍含"请替换"占位符 -> ERROR,并写明修法 - 不以 sk- 开头 -> WARN 提示格式可疑 - 正常 -> INFO 打长度 + host + model(不打印 key 本体) 修法提示:在 projman 的 /app/config/application-prod.yml 填真实 key, 或给容器注入环境变量 DEEPSEEK_API_KEY(勿写入 public 仓库中的 application.yml)。 --- .../com/yunhai/agent/llm/DeepSeekClient.java | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/main/java/com/yunhai/agent/llm/DeepSeekClient.java b/src/main/java/com/yunhai/agent/llm/DeepSeekClient.java index 0122ba6..b186b85 100644 --- a/src/main/java/com/yunhai/agent/llm/DeepSeekClient.java +++ b/src/main/java/com/yunhai/agent/llm/DeepSeekClient.java @@ -19,6 +19,7 @@ import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Component; import org.springframework.web.client.RestTemplate; +import javax.annotation.PostConstruct; import java.util.ArrayList; import java.util.List; @@ -51,6 +52,34 @@ public class DeepSeekClient implements LlmClient { this.settings = settings; } + /** + * 启动自检:把「大模型到底配好没有」这件事在启动日志里说清楚(可 grep {@code [大模型]})。 + * + *

线上最容易被忽略的一类故障:容器没有注入 {@code DEEPSEEK_API_KEY}, + * 于是 api-key 落到 application.yml 里的中文占位符,直到某个用户发起一次对话才以 + * HTTP 401(表现为 {@code HttpRetryException: cannot retry due to server authentication, + * in streaming mode})暴露出来 —— 排查成本很高。这里改成启动时就 ERROR 出来。 + */ + @PostConstruct + void checkLlmConfig() { + AgentSettings.Llm llm = settings.getLlm(); + String key = llm.getApiKey(); + if (StringUtils.isBlank(key)) { + log.error("[大模型] agent.llm.api-key 为空 —— 任何对话都会鉴权失败。" + + "请在 application-prod.yml 填写真实 key,或给容器注入环境变量 DEEPSEEK_API_KEY。"); + } else if (key.contains("请替换")) { + log.error("[大模型] agent.llm.api-key 仍是占位符(长度 {})—— 说明容器没有注入环境变量 " + + "DEEPSEEK_API_KEY。任何对话都会 401:请在 application-prod.yml 写真实 key," + + "或给容器注入该环境变量后重启。", key.length()); + } else if (!key.startsWith("sk-")) { + log.warn("[大模型] agent.llm.api-key 已配置,但格式可疑(不以 sk- 开头,长度 {}),请确认是否填错。", + key.length()); + } else { + log.info("[大模型] api-key 已配置(长度 {}),host={} model={}", + key.length(), llm.getHost(), llm.getModel()); + } + } + @Override public ChatResult chat(List messages, JSONArray tools) { AgentSettings.Llm llm = settings.getLlm();