feat(providers): add DeepSeek as built-in provider
- Add buildDeepSeekProvider() with deepseek-chat and deepseek-reasoner models - Register in resolveImplicitProviders() for auto-discovery via DEEPSEEK_API_KEY - Add DEEPSEEK_API_KEY to env var mapping in model-auth.ts - Add unit tests for provider builder and implicit resolution - Add documentation at docs/providers/deepseek.md Closes #3393
This commit is contained in:
parent
109ac1c549
commit
ffddff40ab
64
docs/providers/deepseek.md
Normal file
64
docs/providers/deepseek.md
Normal file
@ -0,0 +1,64 @@
|
||||
---
|
||||
summary: "Configure DeepSeek API (Chat + Reasoner models)"
|
||||
read_when:
|
||||
- You want to use DeepSeek models
|
||||
- You need to configure DeepSeek API key
|
||||
- You want cost-effective reasoning models
|
||||
---
|
||||
|
||||
# DeepSeek
|
||||
|
||||
DeepSeek provides cost-effective AI models with OpenAI-compatible endpoints. Moltbot supports both the Chat and Reasoner models.
|
||||
|
||||
## Models
|
||||
|
||||
| Model ID | Name | Reasoning | Context |
|
||||
|----------|------|-----------|---------|
|
||||
| `deepseek-chat` | DeepSeek Chat | No | 64K |
|
||||
| `deepseek-reasoner` | DeepSeek Reasoner | Yes | 64K |
|
||||
|
||||
## Quick start
|
||||
|
||||
```bash
|
||||
moltbot onboard --auth-choice deepseek-api-key
|
||||
```
|
||||
|
||||
Or set the environment variable:
|
||||
|
||||
```bash
|
||||
export DEEPSEEK_API_KEY="sk-..."
|
||||
moltbot models set deepseek/deepseek-chat
|
||||
```
|
||||
|
||||
## Config snippet
|
||||
|
||||
```json5
|
||||
{
|
||||
env: { DEEPSEEK_API_KEY: "sk-..." },
|
||||
agents: {
|
||||
defaults: {
|
||||
model: { primary: "deepseek/deepseek-chat" }
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Using the Reasoner model
|
||||
|
||||
For tasks requiring chain-of-thought reasoning:
|
||||
|
||||
```json5
|
||||
{
|
||||
agents: {
|
||||
defaults: {
|
||||
model: { primary: "deepseek/deepseek-reasoner" }
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- DeepSeek uses an OpenAI-compatible API at `https://api.deepseek.com`
|
||||
- Get your API key from [platform.deepseek.com](https://platform.deepseek.com/)
|
||||
- Pricing is very competitive compared to other providers
|
||||
@ -39,6 +39,7 @@ See [Venice AI](/providers/venice).
|
||||
- [OpenRouter](/providers/openrouter)
|
||||
- [Vercel AI Gateway](/providers/vercel-ai-gateway)
|
||||
- [Moonshot AI (Kimi + Kimi Code)](/providers/moonshot)
|
||||
- [DeepSeek (Chat + Reasoner)](/providers/deepseek)
|
||||
- [OpenCode Zen](/providers/opencode)
|
||||
- [Amazon Bedrock](/bedrock)
|
||||
- [Z.AI](/providers/zai)
|
||||
|
||||
@ -285,6 +285,7 @@ export function resolveEnvApiKey(provider: string): EnvApiKeyResult | null {
|
||||
venice: "VENICE_API_KEY",
|
||||
mistral: "MISTRAL_API_KEY",
|
||||
opencode: "OPENCODE_API_KEY",
|
||||
deepseek: "DEEPSEEK_API_KEY",
|
||||
};
|
||||
const envVar = envMap[normalized];
|
||||
if (!envVar) return null;
|
||||
|
||||
85
src/agents/models-config.providers.deepseek.test.ts
Normal file
85
src/agents/models-config.providers.deepseek.test.ts
Normal file
@ -0,0 +1,85 @@
|
||||
import { mkdtempSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import { tmpdir } from "node:os";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
buildDeepSeekProvider,
|
||||
DEEPSEEK_API_BASE_URL,
|
||||
DEEPSEEK_CHAT_MODEL_ID,
|
||||
DEEPSEEK_REASONER_MODEL_ID,
|
||||
resolveImplicitProviders,
|
||||
} from "./models-config.providers.js";
|
||||
|
||||
describe("DeepSeek provider", () => {
|
||||
describe("buildDeepSeekProvider", () => {
|
||||
it("returns correct provider configuration", () => {
|
||||
const provider = buildDeepSeekProvider();
|
||||
|
||||
expect(provider.baseUrl).toBe(DEEPSEEK_API_BASE_URL);
|
||||
expect(provider.api).toBe("openai-completions");
|
||||
expect(provider.models).toHaveLength(2);
|
||||
});
|
||||
|
||||
it("includes deepseek-chat model with correct config", () => {
|
||||
const provider = buildDeepSeekProvider();
|
||||
const chatModel = provider.models.find((m) => m.id === DEEPSEEK_CHAT_MODEL_ID);
|
||||
|
||||
expect(chatModel).toBeDefined();
|
||||
expect(chatModel?.name).toBe("DeepSeek Chat");
|
||||
expect(chatModel?.reasoning).toBe(false);
|
||||
expect(chatModel?.input).toEqual(["text"]);
|
||||
expect(chatModel?.contextWindow).toBe(64000);
|
||||
expect(chatModel?.maxTokens).toBe(8192);
|
||||
});
|
||||
|
||||
it("includes deepseek-reasoner model with reasoning enabled", () => {
|
||||
const provider = buildDeepSeekProvider();
|
||||
const reasonerModel = provider.models.find((m) => m.id === DEEPSEEK_REASONER_MODEL_ID);
|
||||
|
||||
expect(reasonerModel).toBeDefined();
|
||||
expect(reasonerModel?.name).toBe("DeepSeek Reasoner");
|
||||
expect(reasonerModel?.reasoning).toBe(true);
|
||||
expect(reasonerModel?.input).toEqual(["text"]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("resolveImplicitProviders", () => {
|
||||
let previousKey: string | undefined;
|
||||
|
||||
beforeEach(() => {
|
||||
previousKey = process.env.DEEPSEEK_API_KEY;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
if (previousKey === undefined) {
|
||||
delete process.env.DEEPSEEK_API_KEY;
|
||||
} else {
|
||||
process.env.DEEPSEEK_API_KEY = previousKey;
|
||||
}
|
||||
vi.resetModules();
|
||||
});
|
||||
|
||||
it("does not include deepseek when no API key is configured", async () => {
|
||||
delete process.env.DEEPSEEK_API_KEY;
|
||||
const agentDir = mkdtempSync(join(tmpdir(), "clawd-test-deepseek-"));
|
||||
const providers = await resolveImplicitProviders({ agentDir });
|
||||
|
||||
expect(providers?.deepseek).toBeUndefined();
|
||||
});
|
||||
|
||||
it("includes deepseek when DEEPSEEK_API_KEY env var is set", async () => {
|
||||
process.env.DEEPSEEK_API_KEY = "sk-test-deepseek-key";
|
||||
const agentDir = mkdtempSync(join(tmpdir(), "clawd-test-deepseek-"));
|
||||
|
||||
vi.resetModules();
|
||||
const { resolveImplicitProviders: freshResolve } =
|
||||
await import("./models-config.providers.js");
|
||||
const providers = await freshResolve({ agentDir });
|
||||
|
||||
expect(providers?.deepseek).toBeDefined();
|
||||
expect(providers?.deepseek?.baseUrl).toBe(DEEPSEEK_API_BASE_URL);
|
||||
expect(providers?.deepseek?.apiKey).toBe("DEEPSEEK_API_KEY");
|
||||
expect(providers?.deepseek?.models).toHaveLength(2);
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -75,6 +75,18 @@ const OLLAMA_DEFAULT_COST = {
|
||||
cacheWrite: 0,
|
||||
};
|
||||
|
||||
export const DEEPSEEK_API_BASE_URL = "https://api.deepseek.com";
|
||||
export const DEEPSEEK_CHAT_MODEL_ID = "deepseek-chat";
|
||||
export const DEEPSEEK_REASONER_MODEL_ID = "deepseek-reasoner";
|
||||
const DEEPSEEK_DEFAULT_CONTEXT_WINDOW = 64000;
|
||||
const DEEPSEEK_DEFAULT_MAX_TOKENS = 8192;
|
||||
const DEEPSEEK_DEFAULT_COST = {
|
||||
input: 0,
|
||||
output: 0,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
};
|
||||
|
||||
interface OllamaModel {
|
||||
name: string;
|
||||
modified_at: string;
|
||||
@ -359,6 +371,33 @@ async function buildOllamaProvider(): Promise<ProviderConfig> {
|
||||
};
|
||||
}
|
||||
|
||||
export function buildDeepSeekProvider(): ProviderConfig {
|
||||
return {
|
||||
baseUrl: DEEPSEEK_API_BASE_URL,
|
||||
api: "openai-completions",
|
||||
models: [
|
||||
{
|
||||
id: DEEPSEEK_CHAT_MODEL_ID,
|
||||
name: "DeepSeek Chat",
|
||||
reasoning: false,
|
||||
input: ["text"],
|
||||
cost: DEEPSEEK_DEFAULT_COST,
|
||||
contextWindow: DEEPSEEK_DEFAULT_CONTEXT_WINDOW,
|
||||
maxTokens: DEEPSEEK_DEFAULT_MAX_TOKENS,
|
||||
},
|
||||
{
|
||||
id: DEEPSEEK_REASONER_MODEL_ID,
|
||||
name: "DeepSeek Reasoner",
|
||||
reasoning: true,
|
||||
input: ["text"],
|
||||
cost: DEEPSEEK_DEFAULT_COST,
|
||||
contextWindow: DEEPSEEK_DEFAULT_CONTEXT_WINDOW,
|
||||
maxTokens: DEEPSEEK_DEFAULT_MAX_TOKENS,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
export async function resolveImplicitProviders(params: {
|
||||
agentDir: string;
|
||||
}): Promise<ModelsConfig["providers"]> {
|
||||
@ -418,6 +457,13 @@ export async function resolveImplicitProviders(params: {
|
||||
providers.ollama = { ...(await buildOllamaProvider()), apiKey: ollamaKey };
|
||||
}
|
||||
|
||||
const deepseekKey =
|
||||
resolveEnvApiKeyVarName("deepseek") ??
|
||||
resolveApiKeyFromProfiles({ provider: "deepseek", store: authStore });
|
||||
if (deepseekKey) {
|
||||
providers.deepseek = { ...buildDeepSeekProvider(), apiKey: deepseekKey };
|
||||
}
|
||||
|
||||
return providers;
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user