Browse Source

Use routable Doubao LLM model ids

The LLM selector exposed display-style Doubao model names that the NewApiWG test gateway cannot route. The UI now stores the gateway model ids returned by /models, while the API route maps legacy saved selector values to those ids so existing workflows do not keep failing.

Constraint: Preserve GPT-4.1 Mini as the NewApiWG default and keep old workflow JSON compatible.

Rejected: Remove Doubao from the LLM selector | the gateway exposes working Doubao chat model ids, so the issue is stale ids rather than product support.

Confidence: high

Scope-risk: narrow

Directive: LLM selector values must be callable provider ids; use labels only for display text.

Tested: Live NewApiWG chat completion with doubao-seed-2-0-lite-260428 and doubao-seed-2-0-pro-260215.

Tested: npm run test:run -- src/lib/__tests__/llmModels.test.ts src/app/api/llm/__tests__/route.test.ts src/components/__tests__/LLMGenerateNode.test.tsx src/store/utils/__tests__/nodeDefaults.test.ts

Tested: npm run build

Tested: git diff --check
feature/canvas-chatbot-copilot
jiajia 2 months ago
parent
commit
c2bf435d19
  1. 82
      src/app/api/llm/__tests__/route.test.ts
  2. 8
      src/app/api/llm/route.ts
  3. 2
      src/components/__tests__/LLMGenerateNode.test.tsx
  4. 8
      src/lib/__tests__/llmModels.test.ts
  5. 2
      src/lib/chat/tools.ts
  6. 4
      src/lib/llmModels.ts
  7. 2
      src/types/providers.ts

82
src/app/api/llm/__tests__/route.test.ts

@ -313,6 +313,88 @@ describe("/api/llm route", () => {
});
});
describe("NewApiWG provider", () => {
beforeEach(() => {
global.fetch = mockFetch;
});
it("should use current Doubao Seed model ids for NewApiWG", async () => {
mockFetch.mockResolvedValueOnce({
ok: true,
json: () =>
Promise.resolve({
choices: [{ message: { content: "Doubao response text" } }],
}),
});
const request = createMockPostRequest(
{
prompt: "Test prompt",
provider: "newapiwg",
model: "doubao-seed-2-0-lite-260428",
temperature: 0.7,
maxTokens: 1024,
},
{
"X-NewApiWG-Key": "test-newapiwg-key",
"X-NewApiWG-Base-URL": "https://newapi.example/v1",
}
);
const response = await POST(request);
const data = await response.json();
expect(response.status).toBe(200);
expect(data.success).toBe(true);
expect(data.text).toBe("Doubao response text");
expect(mockFetch).toHaveBeenCalledWith(
"https://newapi.example/v1/chat/completions",
expect.objectContaining({
body: JSON.stringify({
model: "doubao-seed-2-0-lite-260428",
messages: [{ role: "user", content: "Test prompt" }],
temperature: 0.7,
max_tokens: 1024,
}),
})
);
});
it("should map legacy Doubao selector values to available gateway model ids", async () => {
mockFetch.mockResolvedValueOnce({
ok: true,
json: () =>
Promise.resolve({
choices: [{ message: { content: "Legacy Doubao response text" } }],
}),
});
const request = createMockPostRequest(
{
prompt: "Test prompt",
provider: "newapiwg",
model: "Doubao-Seed-2.0-lite",
},
{
"X-NewApiWG-Key": "test-newapiwg-key",
"X-NewApiWG-Base-URL": "https://newapi.example/v1",
}
);
const response = await POST(request);
const data = await response.json();
expect(response.status).toBe(200);
expect(data.success).toBe(true);
expect(mockFetch).toHaveBeenCalledWith(
"https://newapi.example/v1/chat/completions",
expect.objectContaining({
body: expect.stringContaining('"model":"doubao-seed-2-0-lite-260428"'),
})
);
});
});
describe("OpenAI provider", () => {
beforeEach(() => {
global.fetch = mockFetch;

8
src/app/api/llm/route.ts

@ -30,6 +30,11 @@ const ANTHROPIC_MODEL_MAP: Record<string, string> = {
"claude-opus-4.6": "claude-opus-4-6",
};
const NEWAPIWG_LLM_MODEL_ALIASES: Record<string, string> = {
"Doubao-Seed-2.0-lite": "doubao-seed-2-0-lite-260428",
"Doubao-Seed-2.0-pro": "doubao-seed-2-0-pro-260215",
};
type GoogleContentPart =
| { inlineData: { mimeType: string; data: string } }
| { fileData: { mimeType: string; fileUri: string } }
@ -246,6 +251,7 @@ async function generateWithNewApiWG(
}
const content = buildChatContent(prompt, images, videos);
const modelId = NEWAPIWG_LLM_MODEL_ALIASES[model] ?? model;
const startTime = Date.now();
const response = await fetch(`${normalizeNewApiWGBaseUrl(userBaseUrl)}/chat/completions`, {
@ -255,7 +261,7 @@ async function generateWithNewApiWG(
"Authorization": `Bearer ${apiKey}`,
},
body: JSON.stringify({
model,
model: modelId,
messages: [{ role: "user", content }],
temperature,
max_tokens: maxTokens,

2
src/components/__tests__/LLMGenerateNode.test.tsx

@ -54,7 +54,7 @@ describe("LLMGenerateNode", () => {
inputVideos: [],
outputText: null,
provider: "newapiwg",
model: "Doubao-Seed-2.0-lite",
model: "doubao-seed-2-0-lite-260428",
temperature: 1.0,
maxTokens: 2048,
status: "idle",

8
src/lib/__tests__/llmModels.test.ts

@ -12,12 +12,12 @@ describe("LLM model options", () => {
label: "GPT-4.1 Mini",
});
expect(LLM_MODELS.newapiwg).toContainEqual({
value: "Doubao-Seed-2.0-lite",
label: "Doubao-Seed-2.0-lite",
value: "doubao-seed-2-0-lite-260428",
label: "Doubao Seed 2.0 Lite",
});
expect(LLM_MODELS.newapiwg).toContainEqual({
value: "Doubao-Seed-2.0-pro",
label: "Doubao-Seed-2.0-pro",
value: "doubao-seed-2-0-pro-260215",
label: "Doubao Seed 2.0 Pro",
});
});
});

2
src/lib/chat/tools.ts

@ -59,7 +59,7 @@ AI video generation. Takes image + text inputs, outputs video. Only available wi
### LLM Text Generation
AI text generation for expanding prompts or analyzing images.
- **Provider dropdown**: NewApiWG, Google, OpenAI, or Anthropic
- **Model dropdown**: GPT-4.1 Mini (NewApiWG default), Doubao-Seed-2.0-lite/pro (NewApiWG options) / Gemini 3 Flash, Gemini 2.5 Flash, Gemini 3.0 Pro (Google) / GPT-4.1 Mini, GPT-4.1 Nano (OpenAI)
- **Model dropdown**: GPT-4.1 Mini (NewApiWG default), Doubao Seed 2.0 Lite/Pro (NewApiWG options) / Gemini 3 Flash, Gemini 2.5 Flash, Gemini 3.0 Pro (Google) / GPT-4.1 Mini, GPT-4.1 Nano (OpenAI)
- **Parameters** (collapsible): Temperature slider (0-2), Max Tokens slider (256-16384)
- Takes **text** input (required), optional **image** input

4
src/lib/llmModels.ts

@ -17,8 +17,8 @@ export const LLM_PROVIDERS: { value: LLMProvider; label: string }[] = [
export const LLM_MODELS: Record<LLMProvider, LLMModelOption[]> = {
newapiwg: [
{ value: "gpt-4.1-mini", label: "GPT-4.1 Mini" },
{ value: "Doubao-Seed-2.0-lite", label: "Doubao-Seed-2.0-lite" },
{ value: "Doubao-Seed-2.0-pro", label: "Doubao-Seed-2.0-pro" },
{ value: "doubao-seed-2-0-lite-260428", label: "Doubao Seed 2.0 Lite" },
{ value: "doubao-seed-2-0-pro-260215", label: "Doubao Seed 2.0 Pro" },
],
google: [
{ value: "gemini-3-flash-preview", label: "Gemini 3 Flash" },

2
src/types/providers.ts

@ -47,6 +47,8 @@ export type LLMModelType =
| "gemini-3-flash-preview"
| "gemini-3-pro-preview"
| "gemini-3.1-pro-preview"
| "doubao-seed-2-0-lite-260428"
| "doubao-seed-2-0-pro-260215"
| "Doubao-Seed-2.0-lite"
| "Doubao-Seed-2.0-pro"
| "gpt-4.1-mini"

Loading…
Cancel
Save