feat(provider): add Baidu Qianfan chat provider (#1582)

Integrated into release/v3.7.0
This commit is contained in:
jimmyzhuu
2026-04-25 18:47:42 +08:00
committed by GitHub
parent e64dc3b431
commit 9eac8c2efe
9 changed files with 130 additions and 0 deletions

View File

@@ -1770,6 +1770,16 @@ Models:
**Dashboard behavior:** OpenRouter models are managed from **Available Models**. Manual add, import, and auto-sync all update the same list.
### Baidu Qianfan / ERNIE
1. Sign up: [Baidu AI Cloud Qianfan](https://cloud.baidu.com/product/wenxinworkshop)
2. Create a Qianfan API key
3. Dashboard → Add Provider → Baidu Qianfan
**Use:** `qianfan/ernie-4.5-turbo-128k`, `qianfan/ernie-x1-turbo-32k`, or any Qianfan OpenAI-compatible model ID.
**Dashboard behavior:** Qianfan is registered as an OpenAI-compatible API key provider. Built-in ERNIE models are available immediately, and passthrough model IDs are accepted for newer Qianfan deployments.
</details>
<details>

View File

@@ -404,6 +404,7 @@ Recognized pattern: `{PROVIDER_ID}_API_KEY`
| `COHERE_API_KEY` | Cohere |
| `NVIDIA_API_KEY` | NVIDIA NIM |
| `NEBIUS_API_KEY` | Nebius (embeddings) |
| `QIANFAN_API_KEY` | Baidu Qianfan |
> [!TIP]
> Keys set via the Dashboard are stored encrypted in SQLite and take precedence over environment variables.

View File

@@ -36,6 +36,7 @@ Complete guide for configuring providers, creating combos, integrating CLI tools
| | Cerebras | Pay per use | None | Wafer-scale speed |
| | Cohere | Pay per use | None | Command R+ RAG |
| | NVIDIA NIM | Pay per use | None | Enterprise models |
| | Baidu Qianfan | Pay per use | None | ERNIE models |
| **💰 CHEAP** | GLM-4.7 | $0.6/1M | Daily 10AM | Budget backup |
| | MiniMax M2.1 | $0.2/1M | 5-hour rolling | Cheapest option |
| | Kimi K2 | $9/mo flat | 10M tokens/mo | Predictable cost |
@@ -191,6 +192,13 @@ Models:
**Use:** `kimi/kimi-latest`**Pro Tip:** Fixed $9/month for 10M tokens = $0.90/1M effective cost!
#### Baidu Qianfan / ERNIE
1. Sign up: [Baidu AI Cloud Qianfan](https://cloud.baidu.com/product/wenxinworkshop)
2. Create a Qianfan API key → Dashboard → Add API Key: Provider: `qianfan`
**Use:** `qianfan/ernie-4.5-turbo-128k`, `qianfan/ernie-x1-turbo-32k`, or another Qianfan OpenAI-compatible model ID.
### 🆓 FREE Providers
#### Qoder (8 FREE models)
@@ -590,6 +598,8 @@ For the full environment variable reference, see the [README](../README.md).
**NVIDIA NIM (`nvidia/`)**: `nvidia/nvidia/llama-3.3-70b-instruct`
**Baidu Qianfan (`qianfan/`)**: `qianfan/ernie-4.5-turbo-128k`, `qianfan/ernie-x1-turbo-32k`
</details>
---

View File

@@ -864,6 +864,27 @@ export const REGISTRY: Record<string, RegistryEntry> = {
models: [{ id: "auto", name: "Auto (Best Available)" }],
},
qianfan: {
id: "qianfan",
alias: "qianfan",
format: "openai",
executor: "default",
baseUrl: "https://qianfan.baidubce.com/v2/chat/completions",
modelsUrl: "https://qianfan.baidubce.com/v2/models",
authType: "apikey",
authHeader: "bearer",
defaultContextLength: 128000,
models: [
{ id: "ernie-4.5-turbo-128k", name: "ERNIE 4.5 Turbo 128K" },
{ id: "ernie-4.5-turbo-32k", name: "ERNIE 4.5 Turbo 32K", contextLength: 32000 },
{ id: "ernie-4.5-turbo-8k", name: "ERNIE 4.5 Turbo 8K", contextLength: 8000 },
{ id: "ernie-x1-turbo-32k", name: "ERNIE X1 Turbo 32K", contextLength: 32000 },
{ id: "ernie-x1-turbo-8k", name: "ERNIE X1 Turbo 8K", contextLength: 8000 },
{ id: "ernie-4.5-8k-preview", name: "ERNIE 4.5 8K Preview", contextLength: 8000 },
],
passthroughModels: true,
},
glm: {
id: "glm",
alias: "glm",

View File

@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128" fill="none">
<g transform="translate(12 4) scale(0.10815)">
<path fill="#3245DF" d="M184.682 538.759c111.177-23.874 96.03-156.737 92.702-185.776-5.445-44.768-58.102-123.02-129.606-116.831-89.98 8.074-103.126 138.052-103.126 138.052-12.17 60.08 29.132 188.452 140.03 164.555zM302.746 769.86c-3.257 9.331-10.517 33.228-4.234 54.03 12.402 46.677 52.912 48.77 52.912 48.77h58.218V730.35h-62.336c-28.016 8.354-41.535 30.157-44.56 39.51zm88.281-453.898c61.406 0 111.037-70.667 111.037-158.04C502.064 70.643 452.433 0 391.027 0c-61.312 0-111.06 70.643-111.06 157.923 0 87.373 49.77 158.04 111.06 158.04zm264.47 10.447c82.068 10.657 134.84-76.925 145.335-143.31 10.703-66.292-42.256-143.288-100.357-156.527-58.218-13.356-130.909 79.904-137.54 140.704-7.912 74.32 10.633 148.593 92.562 159.133zm201.086 390.213s-126.976-98.24-201.11-204.414C555 355.66 412.272 419.37 364.525 498.993 316.987 578.594 242.9 628.947 232.382 642.28c-10.68 13.124-153.385 90.166-121.694 230.87 31.669 140.612 142.939 137.936 142.939 137.936s81.998 8.074 177.12-13.217c95.168-21.104 177.096 5.26 177.096 5.26s222.284 74.435 283.108-68.852c60.754-143.334-34.368-217.654-34.368-217.654zM476.26 929.88H331.739c-62.406-12.449-87.257-55.03-90.398-62.29-3.072-7.376-20.802-41.604-11.425-99.845 26.968-87.257 103.87-93.516 103.87-93.516h76.926v-94.563l65.524 1V929.88zm269.146-1h-166.3c-64.453-16.614-67.455-62.407-67.455-62.407v-183.89l67.455-1.094v165.276c4.119 17.637 26.015 20.825 26.015 20.825h68.525V682.581h71.76v246.297zm235.408-490.99c0-31.76-26.387-127.394-124.23-127.394-98.008 0-111.108 90.258-111.108 154.06 0 60.894 5.142 145.894 126.883 143.195 121.788-2.7 108.455-137.936 108.455-169.86zm0 0"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -150,6 +150,7 @@ const KNOWN_SVGS = new Set([
"opencode",
"playht",
"puter",
"qianfan",
"scaleway",
"sdwebui",
"synthetic",

View File

@@ -151,6 +151,18 @@ export const APIKEY_PROVIDERS = {
passthroughModels: true,
website: "https://openrouter.ai",
},
qianfan: {
id: "qianfan",
alias: "qianfan",
name: "Baidu Qianfan",
icon: "cloud",
color: "#2468F2",
textIcon: "BD",
website: "https://cloud.baidu.com/product/wenxinworkshop",
passthroughModels: true,
apiHint:
"Use a Qianfan API key from Baidu AI Cloud. The default endpoint is OpenAI-compatible v2.",
},
glm: {
id: "glm",
alias: "glm",

View File

@@ -8,6 +8,7 @@ import { APIKEY_PROVIDERS } from "../../src/shared/constants/providers.ts";
const CHAT_OPENAI_COMPAT_PROVIDER_IDS = [
"deepinfra",
"vercel-ai-gateway",
"qianfan",
"lambda-ai",
"sambanova",
"nscale",

View File

@@ -0,0 +1,69 @@
import test from "node:test";
import assert from "node:assert/strict";
import { existsSync } from "node:fs";
import { join } from "node:path";
import { REGISTRY, getRegistryEntry } from "../../open-sse/config/providerRegistry.ts";
import { PROVIDERS } from "../../open-sse/config/constants.ts";
import { getModelsByProviderId, isValidModel } from "../../src/shared/constants/models.ts";
import { APIKEY_PROVIDERS } from "../../src/shared/constants/providers.ts";
import { validateBody, createProviderSchema } from "../../src/shared/validation/schemas.ts";
test("qianfan registers Baidu ERNIE as an OpenAI-compatible API key provider", () => {
const registryEntry = getRegistryEntry("qianfan");
assert.ok(registryEntry, "qianfan should be in the provider registry");
assert.equal(registryEntry, REGISTRY.qianfan);
assert.equal(registryEntry.id, "qianfan");
assert.equal(registryEntry.alias, "qianfan");
assert.equal(registryEntry.format, "openai");
assert.equal(registryEntry.executor, "default");
assert.equal(registryEntry.authType, "apikey");
assert.equal(registryEntry.authHeader, "bearer");
assert.equal(registryEntry.baseUrl, "https://qianfan.baidubce.com/v2/chat/completions");
assert.equal(registryEntry.defaultContextLength, 128000);
assert.equal(registryEntry.passthroughModels, true);
assert.ok(APIKEY_PROVIDERS.qianfan, "qianfan should be visible in API key providers");
assert.equal(APIKEY_PROVIDERS.qianfan.name, "Baidu Qianfan");
assert.equal(APIKEY_PROVIDERS.qianfan.website, "https://cloud.baidu.com/product/wenxinworkshop");
assert.equal(APIKEY_PROVIDERS.qianfan.passthroughModels, true);
assert.equal(PROVIDERS.qianfan.baseUrl, registryEntry.baseUrl);
assert.equal(PROVIDERS.qianfan.format, "openai");
assert.equal("modelsUrl" in PROVIDERS.qianfan, false);
assert.equal("passthroughModels" in PROVIDERS.qianfan, false);
});
test("qianfan exposes ERNIE chat models in the local model catalog", () => {
const models = getModelsByProviderId("qianfan");
const modelIds = models.map((model) => model.id);
assert.ok(modelIds.includes("ernie-4.5-turbo-128k"));
assert.ok(modelIds.includes("ernie-x1-turbo-32k"));
assert.ok(modelIds.includes("ernie-4.5-8k-preview"));
assert.ok(models.every((model) => typeof model.name === "string" && model.name.length > 0));
});
test("qianfan accepts known and passthrough model IDs", () => {
assert.equal(isValidModel("qianfan", "ernie-4.5-turbo-128k"), true);
assert.equal(isValidModel("qianfan", "future-qianfan-openai-compatible-model"), true);
});
test("qianfan provider creation schema accepts API-key connections", () => {
const validation = validateBody(createProviderSchema, {
provider: "qianfan",
apiKey: "bce-v3/test-key",
name: "Baidu Qianfan",
});
assert.equal(validation.success, true);
if (validation.success) {
assert.equal(validation.data.provider, "qianfan");
assert.equal(validation.data.apiKey, "bce-v3/test-key");
}
});
test("qianfan has a static provider icon asset", () => {
assert.equal(existsSync(join(process.cwd(), "public/providers/qianfan.svg")), true);
});