This commit is contained in:
jyek 2026-04-16 11:39:16 +08:00 committed by GitHub
commit e0945f5414
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 47 additions and 2 deletions

View File

@ -7,3 +7,4 @@ DEEPSEEK_API_KEY=
DASHSCOPE_API_KEY=
ZHIPU_API_KEY=
OPENROUTER_API_KEY=
MINIMAX_API_KEY=

View File

@ -239,6 +239,7 @@ def select_llm_provider() -> tuple[str, str | None]:
("DeepSeek", "deepseek", "https://api.deepseek.com"),
("Qwen", "qwen", "https://dashscope.aliyuncs.com/compatible-mode/v1"),
("GLM", "glm", "https://open.bigmodel.cn/api/paas/v4/"),
("MiniMax", "minimax", "https://api.minimaxi.chat/v1"),
("OpenRouter", "openrouter", "https://openrouter.ai/api/v1"),
("Azure OpenAI", "azure", None),
("Ollama", "ollama", "http://localhost:11434/v1"),

View File

@ -8,7 +8,7 @@ from .azure_client import AzureOpenAIClient
# Providers that use the OpenAI-compatible chat completions API
_OPENAI_COMPATIBLE = (
"openai", "xai", "deepseek", "qwen", "glm", "ollama", "openrouter",
"openai", "xai", "deepseek", "qwen", "glm", "ollama", "openrouter", "minimax",
)

View File

@ -99,6 +99,20 @@ MODEL_OPTIONS: ProviderModeOptions = {
("Custom model ID", "custom"),
],
},
"minimax": {
"quick": [
("MiniMax-M2.7", "MiniMax-M2.7"),
("MiniMax-M2.7-highspeed", "MiniMax-M2.7-highspeed"),
("MiniMax-M2.5", "MiniMax-M2.5"),
("MiniMax-M2.5-highspeed", "MiniMax-M2.5-highspeed"),
],
"deep": [
("MiniMax-M2.7", "MiniMax-M2.7"),
("MiniMax-M2.7-highspeed", "MiniMax-M2.7-highspeed"),
("MiniMax-M2.5", "MiniMax-M2.5"),
("MiniMax-M2.5-highspeed", "MiniMax-M2.5-highspeed"),
],
},
# OpenRouter: fetched dynamically. Azure: any deployed model name.
"ollama": {
"quick": [

View File

@ -1,4 +1,6 @@
import os
import time
import logging
from typing import Any, Optional
from langchain_openai import ChatOpenAI
@ -6,6 +8,11 @@ from langchain_openai import ChatOpenAI
from .base_client import BaseLLMClient, normalize_content
from .validators import validate_model
logger = logging.getLogger(__name__)
_NULL_CHOICES_RETRIES = 3
_NULL_CHOICES_DELAY = 2 # seconds
class NormalizedChatOpenAI(ChatOpenAI):
"""ChatOpenAI with normalized content output.
@ -16,7 +23,28 @@ class NormalizedChatOpenAI(ChatOpenAI):
"""
def invoke(self, input, config=None, **kwargs):
return normalize_content(super().invoke(input, config, **kwargs))
for attempt in range(1, _NULL_CHOICES_RETRIES + 1):
try:
return normalize_content(super().invoke(input, config, **kwargs))
except TypeError as e:
if "null value for 'choices'" in str(e):
if attempt < _NULL_CHOICES_RETRIES:
logger.warning(
"Received null choices from API (content filter or transient error). "
"Retrying in %ds (attempt %d/%d)...",
_NULL_CHOICES_DELAY,
attempt,
_NULL_CHOICES_RETRIES,
)
time.sleep(_NULL_CHOICES_DELAY)
else:
raise RuntimeError(
"API returned null choices after retries. "
"The request may have been blocked by content moderation. "
"Try rephrasing the prompt or check the provider's content policy."
) from e
else:
raise
# Kwargs forwarded from user config to ChatOpenAI
_PASSTHROUGH_KWARGS = (
@ -32,6 +60,7 @@ _PROVIDER_CONFIG = {
"glm": ("https://api.z.ai/api/paas/v4/", "ZHIPU_API_KEY"),
"openrouter": ("https://openrouter.ai/api/v1", "OPENROUTER_API_KEY"),
"ollama": ("http://localhost:11434/v1", None),
"minimax": ("https://api.minimaxi.chat/v1", "MINIMAX_API_KEY"),
}