From f395309207726283c576aa6bfe5c01abc801ccfe Mon Sep 17 00:00:00 2001 From: Kunthawat Greethong Date: Wed, 17 Jun 2026 11:13:34 +0700 Subject: [PATCH] feat: add DeepSeek and Xiaomi MiMo LLM provider presets - Add providers.py with 5 provider presets (OpenAI, DeepSeek, Xiaomi MiMo, Alibaba DashScope, MiniMax) - Add LLM_PROVIDER env var for one-line provider switching - Improve tag stripping for reasoning models - Add .env.example with documented configuration - Update README with provider configuration section --- .env.example | 106 ++++++++++++++++++++++--- README-ZH.md | 39 +++++++++- README.md | 39 +++++++++- backend/app/config.py | 62 ++++++++++++++- backend/app/providers.py | 133 ++++++++++++++++++++++++++++++++ backend/app/utils/llm_client.py | 54 +++++++++++-- 6 files changed, 406 insertions(+), 27 deletions(-) create mode 100644 backend/app/providers.py diff --git a/.env.example b/.env.example index 78a3b72..568adc9 100644 --- a/.env.example +++ b/.env.example @@ -1,16 +1,104 @@ -# LLM API配置(支持 OpenAI SDK 格式的任意 LLM API) -# 推荐使用阿里百炼平台qwen-plus模型:https://bailian.console.aliyun.com/ +# ================================================================ +# MiroFish 环境变量配置 +# ================================================================ +# 复制此文件为 .env 并填入你的 API 密钥: +# cp .env.example .env +# +# LLM 配置支持两种方式: +# 方式1(推荐):设置 LLM_PROVIDER,只需提供 API Key +# 方式2(灵活):手动指定 LLM_BASE_URL 和 LLM_MODEL_NAME +# ================================================================ + + +# ================================================================ +# 方式1:使用提供商预设(推荐) +# ================================================================ +# 取消注释你要使用的提供商,然后填入对应的 API Key。 +# 设置 LLM_PROVIDER 后,LLM_BASE_URL 和 LLM_MODEL_NAME 会自动填充默认值。 +# +# 可用的提供商: +# - openai : OpenAI GPT 系列 +# - deepseek : DeepSeek (深度求索) +# - xiaomi_mimo : Xiaomi MiMo (小米 MiMo) +# - alibaba_dashscope : 阿里百炼 (通义千问) +# - minimax : MiniMax (海螺 AI) + +# --- DeepSeek --- +# API Key 获取: https://platform.deepseek.com +# LLM_PROVIDER=deepseek +# LLM_API_KEY=sk-your-deepseek-key-here + +# --- Xiaomi MiMo --- +# API Key 获取: https://platform.xiaomimimo.com +# LLM_PROVIDER=xiaomi_mimo +# LLM_API_KEY=your-mimo-api-key-here + +# --- OpenAI --- +# API Key 获取: https://platform.openai.com/api-keys +# LLM_PROVIDER=openai +# LLM_API_KEY=sk-your-openai-key-here + +# --- 阿里百炼 (通义千问) --- +# API Key 获取: https://bailian.console.aliyun.com/ # 注意消耗较大,可先进行小于40轮的模拟尝试 +# LLM_PROVIDER=alibaba_dashscope +# LLM_API_KEY=sk-your-dashscope-key-here + +# --- MiniMax (海螺 AI) --- +# API Key 获取: https://platform.minimaxi.com/ +# LLM_PROVIDER=minimax +# LLM_API_KEY=your-minimax-key-here + + +# ================================================================ +# 方式2:手动指定配置(兼容原有方式) +# ================================================================ +# 如果不使用 LLM_PROVIDER,需要手动指定以下三个变量。 +# 适用于任何兼容 OpenAI SDK 格式的 LLM API。 + LLM_API_KEY=your_api_key_here LLM_BASE_URL=https://dashscope.aliyuncs.com/compatible-mode/v1 LLM_MODEL_NAME=qwen-plus -# ===== ZEP记忆图谱配置 ===== -# 每月免费额度即可支撑简单使用:https://app.getzep.com/ + +# ================================================================ +# Zep 记忆图谱配置(必需) +# ================================================================ +# 每月免费额度即可支撑简单使用 +# 获取地址: https://app.getzep.com/ ZEP_API_KEY=your_zep_api_key_here -# ===== 加速 LLM 配置(可选)===== -# 注意如果不使用加速配置,env文件中就不要出现下面的配置项 -LLM_BOOST_API_KEY=your_api_key_here -LLM_BOOST_BASE_URL=your_base_url_here -LLM_BOOST_MODEL_NAME=your_model_name_here \ No newline at end of file + +# ================================================================ +# 加速 LLM 配置(可选) +# ================================================================ +# 注意:如果不使用加速配置,env文件中就不要出现下面的配置项 +# LLM_BOOST_API_KEY=your_api_key_here +# LLM_BOOST_BASE_URL=your_base_url_here +# LLM_BOOST_MODEL_NAME=your_model_name_here + + +# ================================================================ +# 提供商配置示例(完整示例,取消注释即可使用) +# ================================================================ + +# ---- DeepSeek 完整示例 ---- +# LLM_PROVIDER=deepseek +# LLM_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +# # 以下可省略(使用默认值): +# # LLM_BASE_URL=https://api.deepseek.com/v1 +# # LLM_MODEL_NAME=deepseek-chat + +# ---- Xiaomi MiMo 完整示例 ---- +# LLM_PROVIDER=xiaomi_mimo +# LLM_API_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +# # 以下可省略(使用默认值): +# # LLM_BASE_URL=https://api.xiaomimimo.com/v1 +# # LLM_MODEL_NAME=mimo-v2.5-pro + +# ---- 阿里百炼 完整示例 ---- +# LLM_PROVIDER=alibaba_dashscope +# LLM_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +# # 以下可省略(使用默认值): +# # LLM_BASE_URL=https://dashscope.aliyuncs.com/compatible-mode/v1 +# # LLM_MODEL_NAME=qwen-plus diff --git a/README-ZH.md b/README-ZH.md index 13fbcb4..c774259 100644 --- a/README-ZH.md +++ b/README-ZH.md @@ -116,17 +116,48 @@ cp .env.example .env ```env # LLM API配置(支持 OpenAI SDK 格式的任意 LLM API) -# 推荐使用阿里百炼平台qwen-plus模型:https://bailian.console.aliyun.com/ -# 注意消耗较大,可先进行小于40轮的模拟尝试 +# 方式1(推荐):使用提供商预设,只需设置提供商名称和 API Key +LLM_PROVIDER=deepseek LLM_API_KEY=your_api_key -LLM_BASE_URL=https://dashscope.aliyuncs.com/compatible-mode/v1 -LLM_MODEL_NAME=qwen-plus + +# 方式2:手动指定配置(兼容原有方式) +# LLM_BASE_URL=https://api.deepseek.com/v1 +# LLM_MODEL_NAME=deepseek-chat # Zep Cloud 配置 # 每月免费额度即可支撑简单使用:https://app.getzep.com/ ZEP_API_KEY=your_zep_api_key ``` +**支持的LLM提供商:** + +| 提供商 | `LLM_PROVIDER` | 默认模型 | 说明 | +|--------|-----------------|----------|------| +| **DeepSeek (深度求索)** | `deepseek` | `deepseek-chat` | 性价比高,有推理模型 (`deepseek-reasoner`) | +| **Xiaomi MiMo (小米)** | `xiaomi_mimo` | `mimo-v2.5-pro` | 推理速度快,性能优秀 | +| **OpenAI** | `openai` | `gpt-4o-mini` | 行业标准 | +| **阿里百炼 (通义千问)** | `alibaba_dashscope` | `qwen-plus` | 消耗较大,先试<40轮 | +| **MiniMax (海螺AI)** | `minimax` | `MiniMax-M2.5` | 中文表现好 | + +**快速示例:** + +```bash +# DeepSeek(推荐,性价比高) +LLM_PROVIDER=deepseek +LLM_API_KEY=your_api_key + +# Xiaomi MiMo(小米,推理快) +LLM_PROVIDER=xiaomi_mimo +LLM_API_KEY=your_api_key +``` + +> **提示**: 可以通过设置 `LLM_MODEL_NAME` 来覆盖默认模型: +> ```env +> LLM_PROVIDER=deepseek +> LLM_API_KEY=your_api_key +> LLM_MODEL_NAME=deepseek-reasoner # 使用推理模型 +> ``` + #### 2. 安装依赖 ```bash diff --git a/README.md b/README.md index de08293..fe4929c 100644 --- a/README.md +++ b/README.md @@ -116,17 +116,48 @@ cp .env.example .env ```env # LLM API Configuration (supports any LLM API with OpenAI SDK format) -# Recommended: Alibaba Qwen-plus model via Bailian Platform: https://bailian.console.aliyun.com/ -# High consumption, try simulations with fewer than 40 rounds first +# Option 1 (Recommended): Use provider preset - just set provider name and API key +LLM_PROVIDER=deepseek LLM_API_KEY=your_api_key -LLM_BASE_URL=https://dashscope.aliyuncs.com/compatible-mode/v1 -LLM_MODEL_NAME=qwen-plus + +# Option 2: Manual configuration (compatible with original method) +# LLM_BASE_URL=https://api.deepseek.com/v1 +# LLM_MODEL_NAME=deepseek-chat # Zep Cloud Configuration # Free monthly quota is sufficient for simple usage: https://app.getzep.com/ ZEP_API_KEY=your_zep_api_key ``` +**Supported LLM Providers:** + +| Provider | `LLM_PROVIDER` | Default Model | Notes | +|----------|-----------------|---------------|-------| +| **DeepSeek** | `deepseek` | `deepseek-chat` | Cost-effective, reasoning model available (`deepseek-reasoner`) | +| **Xiaomi MiMo** | `xiaomi_mimo` | `mimo-v2.5-pro` | Fast inference, competitive performance | +| **OpenAI** | `openai` | `gpt-4o-mini` | Industry standard | +| **Alibaba DashScope** | `alibaba_dashscope` | `qwen-plus` | High consumption, try <40 rounds first | +| **MiniMax** | `minimax` | `MiniMax-M2.5` | Good for Chinese content | + +**Quick Examples:** + +```bash +# DeepSeek (Recommended for cost-effectiveness) +LLM_PROVIDER=deepseek +LLM_API_KEY=sk-you...n + +# Xiaomi MiMo (Fast inference) +LLM_PROVIDER=xiaomi_mimo +LLM_API_KEY=your-m...n +``` + +> **Note**: You can override the default model by also setting `LLM_MODEL_NAME`: +> ```env +> LLM_PROVIDER=deepseek +> LLM_API_KEY=sk-you...n +> LLM_MODEL_NAME=deepseek-reasoner # Use reasoning model +> ``` + #### 2. Install Dependencies ```bash diff --git a/backend/app/config.py b/backend/app/config.py index de63e2b..bb3cdee 100644 --- a/backend/app/config.py +++ b/backend/app/config.py @@ -17,6 +17,47 @@ else: load_dotenv(override=True) +def _resolve_llm_config() -> tuple[str, str, str | None]: + """ + 解析LLM配置。 + + 优先级: + 1. 如果设置了 LLM_PROVIDER,使用提供商预设填充 base_url 和 model(但可被显式值覆盖) + 2. 否则使用 LLM_BASE_URL / LLM_MODEL_NAME(兼容原有行为) + + Returns: + (base_url, model_name, provider_name_or_none) + """ + from .providers import get_provider + + provider_name = os.environ.get('LLM_PROVIDER', '').strip() + explicit_base_url = os.environ.get('LLM_BASE_URL', '').strip() + explicit_model = os.environ.get('LLM_MODEL_NAME', '').strip() + + if provider_name: + preset = get_provider(provider_name) + if preset is None: + provider_names = ["openai", "deepseek", "xiaomi_mimo", "alibaba_dashscope", "minimax"] + available = ", ".join(provider_names) + raise ValueError( + f"未知的 LLM_PROVIDER: '{provider_name}'. " + f"可用值: {available}" + ) + # 显式值优先于预设默认值 + base_url = explicit_base_url or preset.base_url + model = explicit_model or preset.default_model + return base_url, model, provider_name + else: + # 兼容原有行为:无 LLM_PROVIDER 时直接使用显式值 + base_url = explicit_base_url or 'https://api.openai.com/v1' + model = explicit_model or 'gpt-4o-mini' + return base_url, model, None + + +# 在模块加载时解析配置(避免重复计算) +_llm_base_url, _llm_model_name, _llm_provider = _resolve_llm_config() + + class Config: """Flask配置类""" @@ -29,8 +70,9 @@ class Config: # LLM配置(统一使用OpenAI格式) LLM_API_KEY = os.environ.get('LLM_API_KEY') - LLM_BASE_URL = os.environ.get('LLM_BASE_URL', 'https://api.openai.com/v1') - LLM_MODEL_NAME = os.environ.get('LLM_MODEL_NAME', 'gpt-4o-mini') + LLM_PROVIDER = _llm_provider # e.g. "deepseek" or None + LLM_BASE_URL = _llm_base_url + LLM_MODEL_NAME = _llm_model_name # Zep配置 ZEP_API_KEY = os.environ.get('ZEP_API_KEY') @@ -73,3 +115,19 @@ class Config: errors.append("ZEP_API_KEY 未配置") return errors + @classmethod + def get_active_provider_info(cls) -> dict: + """返回当前活跃的LLM提供商信息(用于日志/API展示)""" + from .providers import get_provider + + info = { + "provider": cls.LLM_PROVIDER or "custom", + "base_url": cls.LLM_BASE_URL, + "model": cls.LLM_MODEL_NAME, + } + if cls.LLM_PROVIDER: + preset = get_provider(cls.LLM_PROVIDER) + if preset: + info["display_name"] = preset.display_name + info["notes"] = preset.notes + return info diff --git a/backend/app/providers.py b/backend/app/providers.py new file mode 100644 index 0000000..d8b18c6 --- /dev/null +++ b/backend/app/providers.py @@ -0,0 +1,133 @@ +""" +LLM Provider Presets +==================== +预设的LLM提供商配置,简化环境变量设置。 + +使用方式: + 方式1(推荐):设置 LLM_PROVIDER 环境变量为提供商名称,自动填充 base_url 和 model + LLM_PROVIDER=deepseek + LLM_API_KEY=sk-xxx + + 方式2:手动指定所有配置(兼容原有方式) + LLM_API_KEY=sk-xxx + LLM_BASE_URL=https://api.deepseek.com/v1 + LLM_MODEL_NAME=deepseek-chat + +支持的提供商: + - openai : OpenAI GPT系列 + - deepseek : DeepSeek (深度求索) + - xiaomi_mimo : Xiaomi MiMo (小米MiMo) + - alibaba_dashscope : 阿里百炼 (通义千问) + - minimax : MiniMax (海螺AI) +""" + +from dataclasses import dataclass +from typing import Optional + + +@dataclass(frozen=True) +class ProviderPreset: + """LLM提供商预设配置""" + name: str + display_name: str + base_url: str + default_model: str + api_key_url: str + notes: str = "" + # 某些提供商的响应可能包含标签(如DeepSeek推理模型) + may_include_think_tags: bool = False + + +# ============================================================ +# Provider Presets +# ============================================================ + +PROVIDERS: dict[str, ProviderPreset] = { + "openai": ProviderPreset( + name="openai", + display_name="OpenAI", + base_url="https://api.openai.com/v1", + default_model="gpt-4o-mini", + api_key_url="https://platform.openai.com/api-keys", + notes="GPT-4o-mini recommended for cost efficiency.", + ), + "deepseek": ProviderPreset( + name="deepseek", + display_name="DeepSeek (深度求索)", + base_url="https://api.deepseek.com/v1", + default_model="deepseek-chat", + api_key_url="https://platform.deepseek.com", + notes=( + "deepseek-chat: general purpose; " + "deepseek-reasoner: reasoning model with tags in output. " + "Pricing: https://api-docs.deepseek.com/quick_start/pricing" + ), + may_include_think_tags=True, + ), + "xiaomi_mimo": ProviderPreset( + name="xiaomi_mimo", + display_name="Xiaomi MiMo (小米MiMo)", + base_url="https://api.xiaomimimo.com/v1", + default_model="mimo-v2.5-pro", + api_key_url="https://platform.xiaomimimo.com", + notes=( + "mimo-v2.5-pro: flagship model; " + "mimo-v2-flash: fast & economical. " + "OpenAI SDK compatible. May include tags for reasoning." + ), + may_include_think_tags=True, + ), + "alibaba_dashscope": ProviderPreset( + name="alibaba_dashscope", + display_name="Alibaba DashScope (阿里百炼)", + base_url="https://dashscope.aliyuncs.com/compatible-mode/v1", + default_model="qwen-plus", + api_key_url="https://bailian.console.aliyun.com/", + notes=( + "qwen-plus: recommended balance of quality & cost. " + "High token consumption — try <40 round simulations first." + ), + ), + "minimax": ProviderPreset( + name="minimax", + display_name="MiniMax (海螺AI)", + base_url="https://api.minimax.chat/v1", + default_model="MiniMax-M2.5", + api_key_url="https://platform.minimaxi.com/", + notes="MiniMax-M2.5 may include tags.", + may_include_think_tags=True, + ), +} + + +def get_provider(name: str) -> Optional[ProviderPreset]: + """ + 获取提供商预设配置。 + + Args: + name: 提供商名称(不区分大小写) + + Returns: + ProviderPreset 或 None(如果未找到) + """ + return PROVIDERS.get(name.lower().strip()) + + +def list_providers() -> list[dict]: + """ + 列出所有可用的提供商预设。 + + Returns: + 提供商信息列表 + """ + return [ + { + "name": p.name, + "display_name": p.display_name, + "base_url": p.base_url, + "default_model": p.default_model, + "api_key_url": p.api_key_url, + "notes": p.notes, + } + for p in PROVIDERS.values() + ] diff --git a/backend/app/utils/llm_client.py b/backend/app/utils/llm_client.py index 6c1a81f..260bb57 100644 --- a/backend/app/utils/llm_client.py +++ b/backend/app/utils/llm_client.py @@ -1,6 +1,6 @@ """ LLM客户端封装 -统一使用OpenAI格式调用 +统一使用OpenAI格式调用,支持提供商预设(DeepSeek、Xiaomi MiMo等) """ import json @@ -11,27 +11,56 @@ from openai import OpenAI from ..config import Config +# 标签的正则表达式 +# 匹配 ... 标签及其内容(支持多行,非贪婪匹配) +# 也处理 ... 变体(某些模型可能使用略微不同的格式) +THINK_TAG_PATTERN = re.compile(r'[\s\S]*?\s*', re.IGNORECASE) + +# 某些提供商的推理模型会在响应中包含标签 +PROVIDERS_WITH_THINK_TAGS = {"deepseek", "xiaomi_mimo", "minimax"} + + class LLMClient: - """LLM客户端""" + """LLM客户端,支持提供商预设配置""" def __init__( self, api_key: Optional[str] = None, base_url: Optional[str] = None, - model: Optional[str] = None + model: Optional[str] = None, + provider: Optional[str] = None, ): self.api_key = api_key or Config.LLM_API_KEY self.base_url = base_url or Config.LLM_BASE_URL self.model = model or Config.LLM_MODEL_NAME - + self.provider = provider or Config.LLM_PROVIDER + if not self.api_key: raise ValueError("LLM_API_KEY 未配置") - + self.client = OpenAI( api_key=self.api_key, base_url=self.base_url ) + + # 判断是否需要强制清理标签 + # 如果是已知的推理模型提供商,总是清理;否则也清理(安全兜底) + self._should_strip_think = ( + self.provider in PROVIDERS_WITH_THINK_TAGS + or True # 总是清理,因为不影响正常输出 + ) + def _strip_think_tags(self, content: str) -> str: + """ + 移除响应中的思考内容标签。 + + 某些模型(如DeepSeek Reasoner、Xiaomi MiMo、MiniMax M2.5) + 会在响应中包含...标签,需要移除以获得纯净输出。 + """ + if not content: + return content + return THINK_TAG_PATTERN.sub('', content).strip() + def chat( self, messages: List[Dict[str, str]], @@ -62,9 +91,11 @@ class LLMClient: kwargs["response_format"] = response_format response = self.client.chat.completions.create(**kwargs) - content = response.choices[0].message.content - # 部分模型(如MiniMax M2.5)会在content中包含思考内容,需要移除 - content = re.sub(r'[\s\S]*?', '', content).strip() + content = response.choices[0].message.content or "" + + # 移除思考内容标签 + content = self._strip_think_tags(content) + return content def chat_json( @@ -101,3 +132,10 @@ class LLMClient: except json.JSONDecodeError: raise ValueError(f"LLM返回的JSON格式无效: {cleaned_response}") + def get_info(self) -> Dict[str, Any]: + """返回当前客户端配置信息(用于日志/调试)""" + return { + "provider": self.provider or "custom", + "base_url": self.base_url, + "model": self.model, + }