Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions packages/types/src/providers/deepseek.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,24 @@ export const deepSeekModels = {
cacheReadsPrice: 0.044,
description: `DeepSeek-V4-Pro-0813 is DeepSeek's strongest V4 model for reasoning, coding, long-context, and agentic workloads. It supports thinking and non-thinking modes, JSON output, tool calls, chat prefix completion (beta), and FIM completion (beta) in non-thinking mode.`,
},
"deepseek-v4-flash-vision-exp": {
displayName: "DeepSeek V4 Flash Vision Exp",
maxTokens: 384_000,
contextWindow: 1_000_000,
supportsImages: true,
supportsPromptCache: true,
supportsReasoningEffort: ["disable", "low", "high", "max"],
preserveReasoning: true,
reasoningEffort: "high",
supportsTemperature: true,
defaultTemperature: 1.0,
inputPrice: 0,
// Static estimates use peak rates; off-peak rates are 50% lower.
outputPrice: 1.32,
cacheWritesPrice: 0.44,
cacheReadsPrice: 0.014,
description: `DeepSeek-V4-Flash-Vision-Exp is an experimental multimodal model for text and image understanding. It supports thinking and non-thinking modes, JSON output, tool calls, and image input through Chat Completions, Responses, and Anthropic-compatible APIs.`,
},
} as const satisfies Record<string, ModelInfo>

// https://api-docs.deepseek.com/quick_start/parameter_settings
Expand Down
14 changes: 14 additions & 0 deletions packages/types/src/providers/fireworks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export type FireworksModelId =
| "accounts/fireworks/models/deepseek-v3p2"
| "accounts/fireworks/models/deepseek-v4-pro"
| "accounts/fireworks/models/deepseek-v4-pro-0813"
| "accounts/fireworks/models/deepseek-v4-flash-vision-exp"
| "accounts/fireworks/models/glm-4p5"
| "accounts/fireworks/models/glm-4p5-air"
| "accounts/fireworks/models/glm-4p6"
Expand Down Expand Up @@ -280,6 +281,19 @@ export const fireworksModels = {
description:
"DeepSeek V4 Pro 0813 is DeepSeek's production checkpoint for advanced reasoning, coding, and long-context agentic workloads.",
},
"accounts/fireworks/models/deepseek-v4-flash-vision-exp": {
displayName: "DeepSeek V4 Flash Vision Exp",
maxTokens: 384_000,
contextWindow: 1_048_576,
supportsImages: true,
supportsPromptCache: true,
supportsMaxTokens: true,
inputPrice: 0.22,
outputPrice: 0.66,
cacheReadsPrice: 0.007,
description:
"DeepSeek V4 Flash Vision Exp is an experimental multimodal model with text and image input, function calling, and long-context support.",
},
"accounts/fireworks/models/kimi-k2p7-code": {
maxTokens: 16384,
contextWindow: 262144,
Expand Down
60 changes: 60 additions & 0 deletions src/api/providers/__tests__/deepseek.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,24 @@ describe("DeepSeekHandler", () => {
expect((model.info as ModelInfo).reasoningEffort).toBe("high")
})

it("should return vision metadata for deepseek-v4-flash-vision-exp", () => {
const visionHandler = new DeepSeekHandler({
...mockOptions,
apiModelId: "deepseek-v4-flash-vision-exp",
})
const model = visionHandler.getModel()

expect(model.info).toMatchObject({
maxTokens: 384_000,
contextWindow: 1_000_000,
supportsImages: true,
supportsPromptCache: true,
preserveReasoning: true,
reasoningEffort: "high",
defaultTemperature: 1.0,
})
})

it("should return provided model ID with default model info if model does not exist", () => {
const handlerWithInvalidModel = new DeepSeekHandler({
...mockOptions,
Expand Down Expand Up @@ -324,6 +342,43 @@ describe("DeepSeekHandler", () => {
expect(textChunks[0].text).toBe("Test response")
})

it("should send images and V4 thinking controls to deepseek-v4-flash-vision-exp", async () => {
const visionHandler = new DeepSeekHandler({
...mockOptions,
apiModelId: "deepseek-v4-flash-vision-exp",
})
const visionMessages: Anthropic.Messages.MessageParam[] = [
{
role: "user",
content: [
{ type: "text", text: "Describe this image." },
{
type: "image",
source: { type: "base64", media_type: "image/png", data: "image-data" },
},
],
},
]

await collectStream(visionHandler.createMessage(systemPrompt, visionMessages))

const callArgs = mockCreate.mock.calls[0][0]
expect(callArgs).toMatchObject({
model: "deepseek-v4-flash-vision-exp",
thinking: { type: "enabled" },
reasoning_effort: "high",
max_completion_tokens: 200_000,
})
expect(callArgs.temperature).toBeUndefined()
expect(callArgs.messages).toContainEqual({
role: "user",
content: expect.arrayContaining([
{ type: "text", text: expect.stringContaining("Describe this image.") },
{ type: "image_url", image_url: { url: "data:image/png;base64,image-data" } },
]),
})
})

it("should include usage information", async () => {
const chunks: any[] = await collectStream(handler.createMessage(systemPrompt, messages))

Expand Down Expand Up @@ -684,6 +739,11 @@ describe("DeepSeekHandler", () => {
rawReasoningEffort: "max",
mappedReasoningEffort: "max",
},
{
modelId: "deepseek-v4-flash-vision-exp",
rawReasoningEffort: "medium",
mappedReasoningEffort: "high",
},
]

for (const { modelId, rawReasoningEffort, mappedReasoningEffort } of mappings) {
Expand Down
17 changes: 17 additions & 0 deletions src/api/providers/__tests__/fireworks.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,13 @@ describe("FireworksHandler", () => {
outputPrice: 3.96,
cacheReadsPrice: 0.044,
},
{
modelId: "accounts/fireworks/models/deepseek-v4-flash-vision-exp" as const,
contextWindow: 1_048_576,
inputPrice: 0.22,
outputPrice: 0.66,
cacheReadsPrice: 0.007,
},
])(
"should expose newly added model $modelId",
({ modelId, contextWindow, inputPrice, outputPrice, cacheReadsPrice }) => {
Expand All @@ -147,6 +154,16 @@ describe("FireworksHandler", () => {
},
)

it("should expose vision support for DeepSeek V4 Flash Vision Exp", () => {
const model = fireworksModels["accounts/fireworks/models/deepseek-v4-flash-vision-exp"]

expect(model).toMatchObject({
supportsImages: true,
supportsPromptCache: true,
supportsMaxTokens: true,
})
})

it("should return Kimi K2 Instruct model with correct configuration", () => {
const testModelId: FireworksModelId = "accounts/fireworks/models/kimi-k2-instruct"
const handlerWithModel = new FireworksHandler({
Expand Down
3 changes: 2 additions & 1 deletion src/api/providers/deepseek.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ type DeepSeekChatCompletionParams = Omit<OpenAI.Chat.ChatCompletionCreateParamsS
reasoning_effort?: "low" | "high" | "max"
}

const deepSeekV4ThinkingModels = new Set(["deepseek-v4-flash", "deepseek-v4-pro"])
const deepSeekV4ThinkingModels = new Set(["deepseek-v4-flash", "deepseek-v4-pro", "deepseek-v4-flash-vision-exp"])
const supportsDeepSeekThinkingToggle = (modelId: string) => deepSeekV4ThinkingModels.has(modelId)

// Only known V4 models and the legacy reasoner alias support DeepSeek's
Expand All @@ -51,6 +51,7 @@ export const normalizeDeepSeekReasoningEffort = (
switch (modelId) {
case "deepseek-v4-flash":
case "deepseek-v4-pro":
case "deepseek-v4-flash-vision-exp":
switch (reasoningEffort) {
case "low":
return "low"
Expand Down
1 change: 1 addition & 0 deletions src/api/providers/fetchers/__tests__/deepseek.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ describe("getDeepSeekModels", () => {
expect(globalThis.fetch).toHaveBeenCalledWith("http://127.0.0.1:43123/models", expect.any(Object))
expect(models["deepseek-v4-flash"]).toEqual(deepSeekModels["deepseek-v4-flash"])
expect(models["deepseek-v4-pro"]).toEqual(deepSeekModels["deepseek-v4-pro"])
expect(models["deepseek-v4-flash-vision-exp"]).toEqual(deepSeekModels["deepseek-v4-flash-vision-exp"])
})

it("throws for 404 responses when fallback flag is not enabled", async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,21 @@ describe("useSelectedModel", () => {
},
)

it("selects static vision metadata when the DeepSeek catalog is unavailable", () => {
const modelId = "deepseek-v4-flash-vision-exp"
mockUseRouterModels.mockReturnValue(createRouterModelsResult({ [providerIdentifiers.deepseek]: null }))
mockUseOpenRouterModelProviders.mockReturnValue(createOpenRouterModelProvidersResult({}))

const { result } = renderHook(
() => useSelectedModel({ apiProvider: providerIdentifiers.deepseek, apiModelId: modelId }),
{ wrapper: createWrapper() },
)

expect(result.current.id).toBe(modelId)
expect(result.current.info).toEqual(deepSeekModels[modelId])
expect(result.current.info?.supportsImages).toBe(true)
})

it.each([providerIdentifiers.deepseek, providerIdentifiers.moonshot])(
"falls back to static data when the %s router catalog is null",
(provider) => {
Expand Down
Loading