diff --git a/docker-compose.yml b/docker-compose.yml index 05fea015..ff2618ab 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -77,7 +77,10 @@ services: DB_NAME: ${DB_NAME:-mateclaw} DB_USERNAME: ${DB_USERNAME:-mateclaw} DB_PASSWORD: ${DB_PASSWORD:?DB_PASSWORD is required in .env} - DASHSCOPE_API_KEY: ${DASHSCOPE_API_KEY:-} + # LLM provider keys (DashScope / OpenAI / Anthropic / DeepSeek / Kimi / …) are + # NOT configured via env vars. After startup, add providers in the admin UI: + # Settings → Models → Add Provider + # Keys are stored in mate_model_provider and hot-reloaded. SERPER_API_KEY: ${SERPER_API_KEY:-} JWT_SECRET: ${JWT_SECRET:-} MATECLAW_CORS_ALLOWED_ORIGINS: ${MATECLAW_CORS_ALLOWED_ORIGINS:-} diff --git a/mateclaw-server/src/main/java/vip/mate/MateClawApplication.java b/mateclaw-server/src/main/java/vip/mate/MateClawApplication.java index 0d699cbb..c0eaf1c5 100644 --- a/mateclaw-server/src/main/java/vip/mate/MateClawApplication.java +++ b/mateclaw-server/src/main/java/vip/mate/MateClawApplication.java @@ -15,13 +15,19 @@ import org.springframework.scheduling.annotation.EnableScheduling; * @author MateClaw Team */ @SpringBootApplication(exclude = { - // 禁用 Spring AI MCP Client 自动配置(由 McpClientManager 自行管理生命周期) + // Disable Spring AI MCP Client auto-configuration (lifecycle owned by McpClientManager). org.springframework.ai.mcp.client.common.autoconfigure.McpClientAutoConfiguration.class, org.springframework.ai.mcp.client.common.autoconfigure.McpToolCallbackAutoConfiguration.class, org.springframework.ai.mcp.client.common.autoconfigure.StdioTransportAutoConfiguration.class, org.springframework.ai.mcp.client.common.autoconfigure.annotations.McpClientAnnotationScannerAutoConfiguration.class, org.springframework.ai.mcp.client.httpclient.autoconfigure.SseHttpClientTransportAutoConfiguration.class, org.springframework.ai.mcp.client.httpclient.autoconfigure.StreamableHttpHttpClientTransportAutoConfiguration.class, + // DashScopeAgent is the Bailian "Application Agent" (Bailian-hosted prompt+tool app), + // not the chat model. We don't use it — model configuration is admin-UI driven and + // built by AgentDashScopeChatModelBuilder. Its auto-config strictly requires + // spring.ai.dashscope.api-key to be non-empty at startup, which makes the whole + // ApplicationContext fail when users deploy via Docker without setting the key. + com.alibaba.cloud.ai.autoconfigure.dashscope.DashScopeAgentAutoConfiguration.class, }) @EnableScheduling @MapperScan("vip.mate.**.repository") diff --git a/mateclaw-server/src/main/java/vip/mate/agent/chatmodel/AgentDashScopeChatModelBuilder.java b/mateclaw-server/src/main/java/vip/mate/agent/chatmodel/AgentDashScopeChatModelBuilder.java index 4ec000e6..e512681a 100644 --- a/mateclaw-server/src/main/java/vip/mate/agent/chatmodel/AgentDashScopeChatModelBuilder.java +++ b/mateclaw-server/src/main/java/vip/mate/agent/chatmodel/AgentDashScopeChatModelBuilder.java @@ -172,7 +172,7 @@ public class AgentDashScopeChatModelBuilder implements ChatModelBuilder { } if (!modelProviderService.hasUsableApiKey(apiKey)) { throw new MateClawException("err.agent.dashscope_key_missing", - "DashScope API Key 未配置,请在模型设置中填写 dashscope 的 API Key,或设置 DASHSCOPE_API_KEY 环境变量"); + "DashScope API Key 未配置,请在「设置 → 模型 → 添加供应商」中为 dashscope 填写 API Key"); } builder.apiKey(apiKey.trim()); diff --git a/mateclaw-server/src/main/java/vip/mate/llm/service/ModelProviderService.java b/mateclaw-server/src/main/java/vip/mate/llm/service/ModelProviderService.java index b51b6ac1..be00295b 100644 --- a/mateclaw-server/src/main/java/vip/mate/llm/service/ModelProviderService.java +++ b/mateclaw-server/src/main/java/vip/mate/llm/service/ModelProviderService.java @@ -551,7 +551,16 @@ public class ModelProviderService { return false; } String normalized = apiKey.trim(); + // Reject masked display values (the UI sends "********" when the user + // didn't re-type the key) and known placeholder sentinels — without this + // check, the chat / embedding fallback chain happily forwards the + // placeholder to the LLM endpoint, which then returns a 401 at request + // time. "configure-in-admin-ui" is the application.yml default that + // keeps DashScopeChatAutoConfiguration happy at startup when no env var + // is set; "your-*-api-key-here" are legacy sentinels from earlier + // .env.example / application.yml versions. return !normalized.contains("*") + && !"configure-in-admin-ui".equalsIgnoreCase(normalized) && !"your-dashscope-api-key-here".equalsIgnoreCase(normalized) && !"your-api-key-here".equalsIgnoreCase(normalized); } diff --git a/mateclaw-server/src/main/java/vip/mate/tool/image/vision/provider/DashScopeVisionProvider.java b/mateclaw-server/src/main/java/vip/mate/tool/image/vision/provider/DashScopeVisionProvider.java index 3650918d..fff04065 100644 --- a/mateclaw-server/src/main/java/vip/mate/tool/image/vision/provider/DashScopeVisionProvider.java +++ b/mateclaw-server/src/main/java/vip/mate/tool/image/vision/provider/DashScopeVisionProvider.java @@ -8,10 +8,11 @@ import vip.mate.llm.service.ModelProviderService; * DashScope vision provider — uses {@code qwen-vl-max} via the * OpenAI-compatible endpoint at {@code /compatible-mode/v1/chat/completions}. * - *
Default for the Chinese cloud rollout: API keys are typically - * available (DASHSCOPE_API_KEY is mandatory for the rest of the - * platform) and per-image cost is the lowest of the supported vendors, - * so this provider sits at the front of the auto-detect chain. + *
Sits at the front of the auto-detect chain when a DashScope provider row + * is configured in the admin UI: per-image cost is the lowest of the supported + * vendors, and DashScope is the most common first provider added on the + * Chinese cloud rollout. Falls back to the next provider in the chain when no + * DashScope API key is available. */ @Component public class DashScopeVisionProvider extends OpenAiCompatibleVisionProvider { diff --git a/mateclaw-server/src/main/resources/application.yml b/mateclaw-server/src/main/resources/application.yml index e8a015ef..1778126c 100644 --- a/mateclaw-server/src/main/resources/application.yml +++ b/mateclaw-server/src/main/resources/application.yml @@ -67,10 +67,19 @@ spring: enabled: ${H2_CONSOLE_ENABLED:false} path: /h2-console - # Spring AI Alibaba (DashScope) - Spring AI Alibaba 1.1.x 配置路径 + # Spring AI Alibaba (DashScope) — Spring AI Alibaba 1.1.x configuration path. + # + # The api-key here is only consumed by Spring AI Alibaba's auto-configured beans + # as a *fallback*. The real source of truth for every provider/key/model is the + # admin UI ("Settings → Models", persisted in mate_model_provider / + # mate_model_config); AgentDashScopeChatModelBuilder resolves the key per-request + # from the provider row first, only falling back to this property when the row + # is incomplete. The placeholder default keeps DashScopeChatAutoConfiguration + # happy at startup when no env var is set (Docker / fresh install) — leave it + # alone unless you know what you're doing. ai: dashscope: - api-key: ${DASHSCOPE_API_KEY:your-dashscope-api-key-here} + api-key: ${DASHSCOPE_API_KEY:configure-in-admin-ui} chat: options: model: qwen-max diff --git a/mateclaw-ui/TEST_CASES.md b/mateclaw-ui/TEST_CASES.md index 3151ac21..aff1082a 100644 --- a/mateclaw-ui/TEST_CASES.md +++ b/mateclaw-ui/TEST_CASES.md @@ -7,7 +7,7 @@ ## 前置条件 -1. 后端启动:`cd mateclaw-server && mvn spring-boot:run`(需设置 `DASHSCOPE_API_KEY`) +1. 后端启动:`cd mateclaw-server && mvn spring-boot:run`(零环境变量,启动后到「设置 → 模型」加 LLM 供应商) 2. 前端启动:`cd mateclaw-ui && pnpm dev` 3. 访问 http://localhost:5173,登录