diff --git a/core/llm/autodetect.ts b/core/llm/autodetect.ts index 736caebcc6e..adc2bc57b23 100644 --- a/core/llm/autodetect.ts +++ b/core/llm/autodetect.ts @@ -67,6 +67,7 @@ const PROVIDER_HANDLES_TEMPLATING: string[] = [ "openrouter", "clawrouter", "deepseek", + "doubao", "xAI", "minimax", "groq", diff --git a/core/llm/llms/Doubao.ts b/core/llm/llms/Doubao.ts new file mode 100644 index 00000000000..3cd460f71c1 --- /dev/null +++ b/core/llm/llms/Doubao.ts @@ -0,0 +1,33 @@ +import { LLMOptions } from "../../index.js"; + +import OpenAI from "./OpenAI.js"; + +/** + * Doubao (豆包) is ByteDance's large-language-model family, served through + * Volcengine Ark (火山方舟). + * + * API surface: OpenAI-compatible `/chat/completions` at + * https://ark.cn-beijing.volces.com/api/v3/, Bearer-token auth. Unlike most + * OpenAI-compatible providers, Doubao requires users to deploy a model as an + * "endpoint" and use the endpoint ID (e.g. `ep-20240xxx-xxxxx`) as the model + * identifier — though shared/public endpoints also expose model-name aliases + * such as `doubao-1-5-pro-32k` and `doubao-seed-1-6`. + * + * Docs: https://www.volcengine.com/docs/82379 + */ +class Doubao extends OpenAI { + static providerName = "doubao"; + static defaultOptions: Partial = { + apiBase: "https://ark.cn-beijing.volces.com/api/v3/", + // Ark model IDs are date-stamped (e.g. `doubao-seed-1-6-251015`) or are + // Ark endpoint IDs (`ep-20240xxx-xxxxx`). We intentionally do NOT set a + // default `model` here: picking a specific dated ID would go stale, and + // users must in practice verify model availability against their own + // Ark deployment. Requiring an explicit `model` forces a conscious + // decision and avoids silent 404s from Ark. + useLegacyCompletionsEndpoint: false, + }; + maxStopWords: number | undefined = 4; +} + +export default Doubao; diff --git a/core/llm/llms/OpenAI-compatible.vitest.ts b/core/llm/llms/OpenAI-compatible.vitest.ts index 402fb7e7585..3eb04e29fc7 100644 --- a/core/llm/llms/OpenAI-compatible.vitest.ts +++ b/core/llm/llms/OpenAI-compatible.vitest.ts @@ -6,6 +6,7 @@ import Groq from "./Groq.js"; import Fireworks from "./Fireworks.js"; import Together from "./Together.js"; import Deepseek from "./Deepseek.js"; +import Doubao from "./Doubao.js"; import OpenRouter from "./OpenRouter.js"; import xAI from "./xAI.js"; import Mistral from "./Mistral.js"; @@ -370,6 +371,11 @@ createOpenAISubclassTests(Moonshot, { defaultApiBase: "https://api.moonshot.cn/v1/", }); +createOpenAISubclassTests(Doubao, { + providerName: "doubao", + defaultApiBase: "https://ark.cn-beijing.volces.com/api/v3/", +}); + createOpenAISubclassTests(Novita, { providerName: "novita", defaultApiBase: "https://api.novita.ai/v3/openai/", diff --git a/core/llm/llms/index.ts b/core/llm/llms/index.ts index 453b2d90cd8..1e29710f243 100644 --- a/core/llm/llms/index.ts +++ b/core/llm/llms/index.ts @@ -21,6 +21,7 @@ import CometAPI from "./CometAPI"; import DeepInfra from "./DeepInfra"; import Deepseek from "./Deepseek"; import Docker from "./Docker"; +import Doubao from "./Doubao"; import Fireworks from "./Fireworks"; import Flowise from "./Flowise"; import FunctionNetwork from "./FunctionNetwork"; @@ -108,6 +109,7 @@ export const LLMClasses = [ Cloudflare, Deepseek, Docker, + Doubao, Msty, Azure, WatsonX, diff --git a/docs/customize/model-providers/more/doubao.mdx b/docs/customize/model-providers/more/doubao.mdx new file mode 100644 index 00000000000..abb867b260d --- /dev/null +++ b/docs/customize/model-providers/more/doubao.mdx @@ -0,0 +1,111 @@ +--- +title: "Doubao (豆包)" +description: "Configure ByteDance Doubao models served via Volcengine Ark in Continue, including the doubao-seed and doubao-1.5-pro model families." +--- + +[Doubao (豆包)](https://www.volcengine.com/product/ark) is ByteDance's large-language-model family served through Volcengine Ark (火山方舟). The service exposes an OpenAI-compatible `/chat/completions` API and is widely used in the China region for both chat and code-assistance workloads. + +Continue supports Doubao as a first-class provider. Ark addresses models either by a **date-stamped model ID** (e.g. `doubao-seed-1-6-251015`, `doubao-1-5-pro-32k-250115`) or by an **endpoint ID** you provision in the Ark console (`ep-20240xxx-xxxxx`). Always verify the current model ID in the [Ark model list](https://www.volcengine.com/docs/82379/1330310) before configuring Continue — undated aliases are not guaranteed to resolve. + +## Configuration + +To use Doubao models: + +1. Create an API key on the [Volcengine Ark console](https://console.volcengine.com/ark/). +2. Either deploy the model you want as an endpoint and copy its endpoint ID, or use a public model alias. +3. Add the following configuration: + + + + ```yaml title="config.yaml" + name: My Config + version: 0.0.1 + schema: v1 + + models: + - name: Doubao Seed 1.6 + provider: doubao + model: doubao-seed-1-6-251015 + apiKey: + ``` + + + ```json title="config.json" + { + "models": [ + { + "title": "Doubao Seed 1.6", + "provider": "doubao", + "model": "doubao-seed-1-6-251015", + "apiKey": "" + } + ] + } + ``` + + + +## Using an endpoint ID + +For custom or newly released models, deploy the model as an Ark endpoint and pass the endpoint ID as the `model`: + +```yaml title="config.yaml" +models: + - name: Doubao (custom endpoint) + provider: doubao + model: ep-20240xxx-xxxxx + apiKey: +``` + +Endpoint IDs bypass model-name routing on the Ark gateway and give you full control over which deployment handles the request, including quota and region. + +## Configuration Options + +| Option | Description | Default | +| --------- | ---------------------------------------------------- | ------------------------------------------------- | +| `apiKey` | Volcengine Ark API key | Required | +| `apiBase` | Ark API base URL | `https://ark.cn-beijing.volces.com/api/v3/` | +| `model` | Ark model ID (date-stamped) or endpoint ID | Required | + +## Example + +Complete configuration with tuned completion options: + + + + ```yaml title="config.yaml" + name: My Config + version: 0.0.1 + schema: v1 + + models: + - name: Doubao Seed 1.6 + provider: doubao + model: doubao-seed-1-6-251015 + apiKey: + defaultCompletionOptions: + temperature: 0.7 + topP: 0.95 + maxTokens: 2048 + ``` + + + ```json title="config.json" + { + "models": [ + { + "title": "Doubao Seed 1.6", + "provider": "doubao", + "model": "doubao-seed-1-6-251015", + "apiKey": "", + "completionOptions": { + "temperature": 0.7, + "topP": 0.95, + "maxTokens": 2048 + } + } + ] + } + ``` + + diff --git a/packages/openai-adapters/src/apis/Doubao.test.ts b/packages/openai-adapters/src/apis/Doubao.test.ts new file mode 100644 index 00000000000..08f5b762e9e --- /dev/null +++ b/packages/openai-adapters/src/apis/Doubao.test.ts @@ -0,0 +1,34 @@ +import { describe, expect, it } from "vitest"; +import { constructLlmApi } from "../index.js"; +import { DoubaoApi } from "./Doubao.js"; + +describe("Doubao (ByteDance / Volcengine Ark) adapter", () => { + it("registers under the 'doubao' provider and returns a DoubaoApi", () => { + const api = constructLlmApi({ + provider: "doubao", + apiKey: "test-key", + }); + expect(api).toBeInstanceOf(DoubaoApi); + }); + + it("points at the Ark cn-beijing base URL by default", () => { + const api = new DoubaoApi({ + provider: "doubao", + apiKey: "test-key", + }); + // apiBase is a public field on the adapter; sanity-check it directly so + // a future refactor can't silently swap the default to, say, api.openai.com. + expect(api.apiBase).toBe("https://ark.cn-beijing.volces.com/api/v3/"); + }); + + it("inherits from OpenAIApi (OpenAI-compatible Ark /chat/completions)", () => { + const api = new DoubaoApi({ + provider: "doubao", + apiKey: "test-key", + }); + // Ark is OpenAI-compatible for /chat/completions; the adapter relies on + // the base class, so it must not accidentally drop that relationship. + expect(typeof api.chatCompletionStream).toBe("function"); + expect(typeof api.chatCompletionNonStream).toBe("function"); + }); +}); diff --git a/packages/openai-adapters/src/apis/Doubao.ts b/packages/openai-adapters/src/apis/Doubao.ts new file mode 100644 index 00000000000..17176ba894a --- /dev/null +++ b/packages/openai-adapters/src/apis/Doubao.ts @@ -0,0 +1,26 @@ +import { DoubaoConfig } from "../types.js"; +import { OpenAIApi } from "./OpenAI.js"; + +/** + * Doubao (豆包) served via Volcengine Ark. + * + * Uses the OpenAI-compatible `/chat/completions` endpoint. Unlike most + * OpenAI-compatible services, Doubao recommends addressing models through + * a deployed "endpoint ID" (e.g. `ep-20240xxx-xxxxx`), though shared model + * aliases such as `doubao-1-5-pro-32k` also resolve on the public tenancy. + * + * No custom FIM: Ark does not expose a public `beta/completions` or + * `[fill]`-prompt protocol today. If that changes we can override + * `fimStream` the way Moonshot and Deepseek do. + * + * Reference: https://www.volcengine.com/docs/82379 + */ +export class DoubaoApi extends OpenAIApi { + apiBase: string = "https://ark.cn-beijing.volces.com/api/v3/"; + constructor(config: DoubaoConfig) { + super({ + ...config, + provider: "openai", + }); + } +} diff --git a/packages/openai-adapters/src/index.ts b/packages/openai-adapters/src/index.ts index c9eb4da00fa..d88ffdad1bc 100644 --- a/packages/openai-adapters/src/index.ts +++ b/packages/openai-adapters/src/index.ts @@ -9,6 +9,7 @@ import { CohereApi } from "./apis/Cohere.js"; import { CometAPIApi } from "./apis/CometAPI.js"; import { ContinueProxyApi } from "./apis/ContinueProxy.js"; import { DeepSeekApi } from "./apis/DeepSeek.js"; +import { DoubaoApi } from "./apis/Doubao.js"; import { GeminiApi } from "./apis/Gemini.js"; import { InceptionApi } from "./apis/Inception.js"; import { JinaApi } from "./apis/Jina.js"; @@ -115,6 +116,8 @@ export function constructLlmApi(config: LLMConfig): BaseLlmApi | undefined { return new JinaApi(config); case "deepseek": return new DeepSeekApi(config); + case "doubao": + return new DoubaoApi(config); case "moonshot": return new MoonshotApi(config); case "relace": diff --git a/packages/openai-adapters/src/types.ts b/packages/openai-adapters/src/types.ts index 3b324b0ac6b..f3aee80a566 100644 --- a/packages/openai-adapters/src/types.ts +++ b/packages/openai-adapters/src/types.ts @@ -77,6 +77,11 @@ export const DeepseekConfigSchema = OpenAIConfigSchema.extend({ }); export type DeepseekConfig = z.infer; +export const DoubaoConfigSchema = OpenAIConfigSchema.extend({ + provider: z.literal("doubao"), +}); +export type DoubaoConfig = z.infer; + export const MiniMaxConfigSchema = OpenAIConfigSchema.extend({ provider: z.literal("minimax"), }); @@ -271,6 +276,7 @@ export const LLMConfigSchema = z.discriminatedUnion("provider", [ BedrockConfigSchema, MoonshotConfigSchema, DeepseekConfigSchema, + DoubaoConfigSchema, MiniMaxConfigSchema, CohereConfigSchema, AzureConfigSchema,