feat: add DashScope (Qwen) and MiniMax provider support

- Bump esperanto dependency to >=2.20.0 for new provider profiles
- Register both providers in credentials, key provider, connection tester, model discovery, and models router
- Add frontend provider entries (display names, modalities, docs links)
- Add documentation sections for both providers in ai-providers.md, environment-reference.md, and provider comparison
This commit is contained in:
Luis Novo 2026-04-06 10:54:37 -03:00
parent c42dc10d2b
commit adc03e56bb
11 changed files with 204 additions and 6 deletions

View file

@ -57,6 +57,8 @@ PROVIDER_ENV_CONFIG: Dict[str, dict] = {
"openai_compatible": {
"required_any": ["OPENAI_COMPATIBLE_BASE_URL", "OPENAI_COMPATIBLE_API_KEY"],
},
"dashscope": {"required": ["DASHSCOPE_API_KEY"]},
"minimax": {"required": ["MINIMAX_API_KEY"]},
}
PROVIDER_MODALITIES: Dict[str, List[str]] = {
@ -74,6 +76,8 @@ PROVIDER_MODALITIES: Dict[str, List[str]] = {
"vertex": ["language", "embedding"],
"azure": ["language", "embedding", "speech_to_text", "text_to_speech"],
"openai_compatible": ["language", "embedding", "speech_to_text", "text_to_speech"],
"dashscope": ["language"],
"minimax": ["language"],
}
@ -512,6 +516,8 @@ async def discover_with_config(provider: str, config: dict) -> List[dict]:
"deepseek": "https://api.deepseek.com/models",
"xai": "https://api.x.ai/v1/models",
"openrouter": "https://openrouter.ai/api/v1/models",
"dashscope": "https://dashscope.aliyuncs.com/compatible-mode/v1/models",
"minimax": "https://api.minimax.io/v1/models",
}
if provider == "ollama":

View file

@ -96,6 +96,8 @@ PROVIDER_PRIORITY = [
"ollama",
"azure",
"openai_compatible",
"dashscope",
"minimax",
]
# Model preference patterns (preferred models within each provider)
@ -105,6 +107,8 @@ MODEL_PREFERENCES = {
"google": ["gemini-2.0", "gemini-1.5-pro", "gemini-pro"],
"mistral": ["mistral-large", "mixtral"],
"groq": ["llama-3.3", "llama-3.1", "mixtral"],
"dashscope": ["qwen-max", "qwen-plus", "qwen-turbo"],
"minimax": ["MiniMax-M2.5", "MiniMax-M2.5-highspeed"],
}
@ -378,6 +382,8 @@ async def get_provider_availability():
"voyage": "VOYAGE_API_KEY",
"elevenlabs": "ELEVENLABS_API_KEY",
"ollama": "OLLAMA_API_BASE",
"dashscope": "DASHSCOPE_API_KEY",
"minimax": "MINIMAX_API_KEY",
}
provider_status = {}

View file

@ -54,6 +54,24 @@ Open Notebook supports 15+ AI providers. This guide helps you **choose the right
→ [Setup Guide](../5-CONFIGURATION/ai-providers.md#openrouter)
**DashScope (Qwen)**
- Cost: ~$0.01-0.06 per 1K tokens
- Speed: Fast
- Quality: Good
- Best for: Users in Asia, Alibaba Cloud ecosystem
- Advantage: Competitive pricing, strong multilingual support
→ [Setup Guide](../5-CONFIGURATION/ai-providers.md#dashscope-qwen)
**MiniMax**
- Cost: Varies by model
- Speed: Fast
- Quality: Good
- Best for: Long context tasks (204K tokens)
- Advantage: Very long context window
→ [Setup Guide](../5-CONFIGURATION/ai-providers.md#minimax)
### Local / Self-Hosted (Free)
**Ollama (Recommended for Local)**
@ -98,6 +116,8 @@ Open Notebook supports 15+ AI providers. This guide helps you **choose the right
| **Google** | Very Fast | $$ | Good-Excellent | Low | 5 min | 2M |
| **Groq** | Ultra Fast | $ | Good | Low | 5 min | 32K |
| **OpenRouter** | Varies | Varies | Varies | Low | 5 min | Varies |
| **DashScope** | Fast | $ | Good | Low | 5 min | Varies |
| **MiniMax** | Fast | $$ | Good | Low | 5 min | 204K |
| **Ollama** | Slow-Medium | Free | Good | Max | 10 min | Varies |
| **LM Studio** | Slow-Medium | Free | Good | Max | 15 min | Varies |
| **Azure** | Very Fast | $$ | Excellent | High | 10 min | 128K |

View file

@ -249,6 +249,76 @@ Heavy use: Depends on models chosen
---
### DashScope (Qwen)
**Cost:** ~$0.01-0.06 per 1K tokens (varies by model)
**Get Your API Key:**
1. Go to https://dashscope.console.aliyun.com/
2. Create an Alibaba Cloud account (if needed)
3. Navigate to API Keys section
4. Create a new API key
**Configure in Open Notebook:**
1. Go to **Settings** → **API Keys**
2. Click **Add Credential**
3. Select provider: **DashScope (Qwen)**
4. Give it a name, paste your API key
5. Click **Save**, then **Test Connection**
6. Click **Discover Models** → **Register Models**
**Available Models:**
- `qwen-max` — Most capable Qwen model
- `qwen-plus` — Good balance of quality and speed
- `qwen-turbo` — Fastest, cheapest
**Recommended:**
- For quality: `qwen-max` (best overall)
- For general use: `qwen-plus` (good balance)
- For speed/cost: `qwen-turbo` (cheapest)
**Troubleshooting:**
- "Invalid API key" → Check the key in the DashScope console
- "Model not available" → Re-discover models from the credential
---
### MiniMax
**Cost:** Varies by model
**Get Your API Key:**
1. Go to https://platform.minimaxi.com/
2. Create an account (if needed)
3. Navigate to API Keys section
4. Create a new API key
**Configure in Open Notebook:**
1. Go to **Settings** → **API Keys**
2. Click **Add Credential**
3. Select provider: **MiniMax**
4. Give it a name, paste your API key
5. Click **Save**, then **Test Connection**
6. Click **Discover Models** → **Register Models**
**Available Models:**
- `MiniMax-M2.5` — Most capable, 204K context
- `MiniMax-M2.5-highspeed` — Faster variant, 204K context
**Recommended:**
- For quality: `MiniMax-M2.5` (best overall)
- For speed: `MiniMax-M2.5-highspeed` (faster responses)
**Advantages:**
- Very long context (204K tokens)
- Competitive pricing
**Troubleshooting:**
- "Invalid API key" → Check the key in the MiniMax platform
- "Model not available" → Re-discover models from the credential
---
## Self-Hosted / Local
### Ollama (Recommended for Local)

View file

@ -263,6 +263,8 @@ If you have these variables configured from a previous installation, click the *
| `OPENAI_COMPATIBLE_API_KEY_STT` | OpenAI-Compatible | Configure per-service key in credential |
| `OPENAI_COMPATIBLE_BASE_URL_TTS` | OpenAI-Compatible | Configure per-service URL in credential |
| `OPENAI_COMPATIBLE_API_KEY_TTS` | OpenAI-Compatible | Configure per-service key in credential |
| `DASHSCOPE_API_KEY` | DashScope (Qwen) | Settings → API Keys → Add DashScope Credential |
| `MINIMAX_API_KEY` | MiniMax | Settings → API Keys → Add MiniMax Credential |
| `AZURE_OPENAI_API_KEY` | Azure OpenAI | Settings → API Keys → Add Azure OpenAI Credential |
| `AZURE_OPENAI_ENDPOINT` | Azure OpenAI | Configure in Azure OpenAI credential |
| `AZURE_OPENAI_API_VERSION` | Azure OpenAI | Configure in Azure OpenAI credential |

View file

@ -75,12 +75,14 @@ const PROVIDER_DISPLAY_NAMES: Record<string, string> = {
azure: 'Azure OpenAI',
vertex: 'Google Vertex AI',
openai_compatible: 'OpenAI Compatible',
dashscope: 'DashScope (Qwen)',
minimax: 'MiniMax',
}
// All providers in display order
const ALL_PROVIDERS = [
'openai', 'anthropic', 'google', 'groq', 'mistral', 'deepseek',
'xai', 'openrouter', 'voyage', 'elevenlabs', 'ollama',
'xai', 'openrouter', 'dashscope', 'minimax', 'voyage', 'elevenlabs', 'ollama',
'azure', 'vertex', 'openai_compatible',
]
@ -100,6 +102,8 @@ const PROVIDER_MODALITIES: Record<string, ModelType[]> = {
azure: ['language', 'embedding', 'text_to_speech', 'speech_to_text'],
vertex: ['language', 'embedding', 'text_to_speech'],
openai_compatible: ['language', 'embedding', 'text_to_speech', 'speech_to_text'],
dashscope: ['language'],
minimax: ['language'],
}
// Documentation links
@ -117,6 +121,8 @@ const PROVIDER_DOCS: Record<string, string> = {
azure: 'https://portal.azure.com/#view/Microsoft_Azure_ProjectOxford/CognitiveServicesHub/~/OpenAI',
vertex: 'https://cloud.google.com/vertex-ai/docs/start/cloud-environment',
openai_compatible: 'https://github.com/lfnovo/open-notebook/blob/main/docs/5-CONFIGURATION/openai-compatible.md',
dashscope: 'https://help.aliyun.com/zh/model-studio/getting-started/',
minimax: 'https://platform.minimaxi.com/document/Guides',
}
const TYPE_ICONS: Record<ModelType, React.ReactNode> = {

View file

@ -34,6 +34,8 @@ TEST_MODELS = {
"vertex": ("gemini-2.0-flash", "language"), # Uses Google Vertex AI
"azure": ("gpt-35-turbo", "language"), # Azure OpenAI deployment name
"openai_compatible": (None, "language"), # Dynamic - will use first available model
"dashscope": ("qwen-plus", "language"),
"minimax": ("MiniMax-M2.5", "language"),
}

View file

@ -62,6 +62,12 @@ PROVIDER_CONFIG = {
"ollama": {
"env_var": "OLLAMA_API_BASE",
},
"dashscope": {
"env_var": "DASHSCOPE_API_KEY",
},
"minimax": {
"env_var": "MINIMAX_API_KEY",
},
}

View file

@ -131,6 +131,14 @@ ELEVENLABS_MODEL_TYPES = {
"text_to_speech": ["eleven"],
}
DASHSCOPE_MODEL_TYPES = {
"language": ["qwen"],
}
MINIMAX_MODEL_TYPES = {
"language": ["minimax", "abab"],
}
def classify_model_type(model_name: str, provider: str) -> str:
"""
@ -150,6 +158,8 @@ def classify_model_type(model_name: str, provider: str) -> str:
"xai": XAI_MODEL_TYPES,
"voyage": VOYAGE_MODEL_TYPES,
"elevenlabs": ELEVENLABS_MODEL_TYPES,
"dashscope": DASHSCOPE_MODEL_TYPES,
"minimax": MINIMAX_MODEL_TYPES,
}
mapping = type_mappings.get(provider, {})
@ -518,6 +528,74 @@ async def discover_elevenlabs_models() -> List[DiscoveredModel]:
]
async def discover_dashscope_models() -> List[DiscoveredModel]:
"""Fetch available models from DashScope (Qwen) API."""
api_key = os.environ.get("DASHSCOPE_API_KEY")
if not api_key:
return []
models = []
try:
async with httpx.AsyncClient() as client:
response = await client.get(
"https://dashscope.aliyuncs.com/compatible-mode/v1/models",
headers={"Authorization": f"Bearer {api_key}"},
timeout=30.0,
)
response.raise_for_status()
data = response.json()
for model in data.get("data", []):
model_id = model.get("id", "")
if model_id:
model_type = classify_model_type(model_id, "dashscope")
models.append(
DiscoveredModel(
name=model_id,
provider="dashscope",
model_type=model_type,
)
)
except Exception as e:
logger.warning(f"Failed to discover DashScope models: {e}")
return models
async def discover_minimax_models() -> List[DiscoveredModel]:
"""Fetch available models from MiniMax API."""
api_key = os.environ.get("MINIMAX_API_KEY")
if not api_key:
return []
models = []
try:
async with httpx.AsyncClient() as client:
response = await client.get(
"https://api.minimax.io/v1/models",
headers={"Authorization": f"Bearer {api_key}"},
timeout=30.0,
)
response.raise_for_status()
data = response.json()
for model in data.get("data", []):
model_id = model.get("id", "")
if model_id:
model_type = classify_model_type(model_id, "minimax")
models.append(
DiscoveredModel(
name=model_id,
provider="minimax",
model_type=model_type,
)
)
except Exception as e:
logger.warning(f"Failed to discover MiniMax models: {e}")
return models
async def discover_openai_compatible_models() -> List[DiscoveredModel]:
"""
Fetch available models from an OpenAI-compatible API endpoint.
@ -600,6 +678,8 @@ PROVIDER_DISCOVERY_FUNCTIONS = {
"voyage": discover_voyage_models,
"elevenlabs": discover_elevenlabs_models,
"openai_compatible": discover_openai_compatible_models,
"dashscope": discover_dashscope_models,
"minimax": discover_minimax_models,
"azure": None, # Azure requires credential-based discovery (different auth)
"vertex": None, # Vertex requires credential-based discovery (service account)
}

View file

@ -34,7 +34,7 @@ dependencies = [
"httpx[socks]>=0.27.0",
"content-core>=1.14.1,<2",
"ai-prompter>=0.3,<1",
"esperanto>=2.19.7,<3",
"esperanto>=2.20.0,<3",
"surrealdb>=1.0.4",
"podcast-creator>=0.12.0,<1",
"surreal-commands>=1.3.1,<2",

View file

@ -648,15 +648,15 @@ wheels = [
[[package]]
name = "esperanto"
version = "2.19.7"
version = "2.20.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "httpx" },
{ name = "pydantic" },
]
sdist = { url = "https://files.pythonhosted.org/packages/da/e0/d7c7f91a7b86744ce040385a4c81f8eaa147a140f299995c1fb8a59c4238/esperanto-2.19.7.tar.gz", hash = "sha256:83b2b1683361fc019444305a34ae398da71724f76b9563c0cf5a7c4d54fb08eb", size = 833887, upload-time = "2026-03-11T20:25:41.47Z" }
sdist = { url = "https://files.pythonhosted.org/packages/bb/8c/eba93a2b7b1a4fc6812bcf0a83a69a3376fcaf5831fae04a0ad539baeff6/esperanto-2.20.0.tar.gz", hash = "sha256:53a16c539ed2f83e3400b4e56d5a20b7873df6cb926c1e6696a9611b3be928fb", size = 845084, upload-time = "2026-03-21T14:32:04.589Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/fb/ab/6d81682bfd3b2f5db283c302c4695623f282ae08c99c667609467c10bc22/esperanto-2.19.7-py3-none-any.whl", hash = "sha256:9a853a51f59495ac2487c3d7a12942c931e4daf6185e45247b715938a4597685", size = 202436, upload-time = "2026-03-11T20:25:40.089Z" },
{ url = "https://files.pythonhosted.org/packages/13/83/476dd4d7ee9f1b013ccd66e2518d150e66d3537736aed735b27230af0eb4/esperanto-2.20.0-py3-none-any.whl", hash = "sha256:767f95d08dd29e7d1950f4cc2ec908084e93ec61e945ce9446f001c54b86bb63", size = 203977, upload-time = "2026-03-21T14:32:05.676Z" },
]
[[package]]
@ -2129,7 +2129,7 @@ requires-dist = [
{ name = "ai-prompter", specifier = ">=0.3,<1" },
{ name = "babel", specifier = ">=2.18.0" },
{ name = "content-core", specifier = ">=1.14.1,<2" },
{ name = "esperanto", specifier = ">=2.19.7,<3" },
{ name = "esperanto", specifier = ">=2.20.0,<3" },
{ name = "fastapi", specifier = ">=0.104.0" },
{ name = "httpx", extras = ["socks"], specifier = ">=0.27.0" },
{ name = "ipykernel", marker = "extra == 'dev'", specifier = ">=6.29.5" },