DeepSeek

In one line
Dispatches unified requests to DeepSeek over the OpenAI-compatible route or the Anthropic Messages bridge, chosen by the base URL.
You need
The provider-deepseek feature and an API key. The bridge route requires the key.
Read this if
You route requests through ProviderEndpoint::DeepSeek on either route, or need the bridge's model id and prompt cache rules.

The adapter reuses two other adapters instead of translating on its own. The native route runs the code on the OpenAI page, and the bridge runs the code on the Anthropic page. Its own differences:

  • A base URL containing api.deepseek.com/anthropic, or ending in /anthropic, selects the bridge. Every other base URL selects the native route.
  • Native thinking is a thinking mode object with no effort level.
  • The bridge translates Claude-style model ids and always disables prompt caching.

Endpoint

FactNative routeBridge route
Feature flagprovider-deepseekprovider-deepseek
ProviderEndpoint variantDeepSeekDeepSeek
Default base URLhttps://api.deepseek.com/v1, used when the client's base URL is emptynone; set https://api.deepseek.com/anthropic or another URL ending in /anthropic
RoutePOST {base_url}/chat/completionsPOST {base_url}/messages
Wire protocolOpenAI-compatibleAnthropic Messages

The native route trims a trailing / and appends /chat/completions. The bridge uses the base URL as given, with no /v1 handling.

Authentication

Native routeBridge route
HeaderAuthorization: Bearer <api_key>x-api-key: <api_key>
Without a keyno header is sentCucaError::Config("deepseek anthropic bridge requires an api key")
Other headersnoneanthropic-version: 2023-06-01

The bridge ignores CucaClientBuilder::with_bearer_token. It sends no anthropic-beta header.

Request translation

Native route: the body is the one described under OpenAI request translation, with the request's model unchanged.

Bridge route: the body is the one described under Anthropic request translation, after two changes to the request:

  • The model id is mapped: claude-opus becomes deepseek-v4-pro, and claude-sonnet and claude-haiku become deepseek-v4-flash. Any other id passes through unchanged.
  • PromptCacheDirective is set to Disabled, whatever the caller requested. No bridge request carries a prompt cache beta header, a cache_control block or a breakpoint check.

Response stream

Token usage

  • Native route: DeepSeek sends the usage object on the final finish_reason frame, and the shared translator reads it. The rules are on the OpenAI page.
  • Bridge route: no token counts are read, as on the Anthropic page. usage is None, prompt_tokens is 0, and completion_tokens counts one per Text, Thinking and ToolCall block.
  • The bridge still reads prompt cache counters from message_start when the server sends them.

Thinking

  • Native route: req.thinking becomes a top-level thinking object. An enabled config sends {"type": "enabled"} and a disabled one sends {"type": "disabled"}. With no thinking on the request, the key is omitted. No reasoning_effort is sent, and the effort level and ThinkingParams are ignored.
  • Bridge route: req.thinking follows the budget and adaptive rules in the Anthropic thinking table.

Errors

ConditionError
Feature not compiledCucaError::ProviderNotEnabled("provider-deepseek")
Bridge route with no API keyCucaError::Config
Non-2xx responseCucaError::Http { status, body } with the captured body
Connection or body-read failureCucaError::Transport { message }
Native frame is not valid JSONCucaError::Json
Native frame carries {"error": {"message": ...}}CucaError::Provider with provider: OpenAi
Bridge error eventCucaError::Provider with provider: Anthropic
Frame bytes are not valid UTF-8CucaError::SseParse

The provider name in a stream error comes from the shared translator: OpenAi on the native route and Anthropic on the bridge, never DeepSeek.

Limits

  • No cap on request or response size, and the client sets no request timeout.
  • The SSE parser keeps one reusable buffer that starts at 8192 bytes. Its high-water mark is the largest single frame plus one partial line.
  • Native route: in-flight tool calls hold their argument fragments until each call completes. Bridge route: one thinking text, signature or tool call input is buffered at a time.
  • The bridge sends max_tokens 1024 when the request leaves it unset. The native route sends none.

Example

The smallest streaming turn: the DeepSeek variant and an API key. The default base URL selects the native route.

cargo add cuca --path /path/to/cuca --features provider-deepseek
cargo add tokio --features rt,macros
cargo add tokio-stream
export DEEPSEEK_API_KEY="sk-..."
$env:DEEPSEEK_API_KEY = "sk-..."
use std::io::{Write, stdout};

use cuca::types::{MessageContentBlock, ProviderEndpoint};
use cuca::{CucaClient, UnifiedRequest};
use tokio_stream::StreamExt;

#[tokio::main(flavor = "current_thread")]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = CucaClient::builder()
        .with_provider(ProviderEndpoint::DeepSeek)
        .with_api_key(std::env::var("DEEPSEEK_API_KEY")?)
        .build()?;

    let request = UnifiedRequest::new("deepseek-v4-flash")
        .add_system_message("You are concise.")
        .add_user_message("Say hello.")
        .set_max_tokens(128);
    let mut stream = client.generate_stream(request).await?;

    let mut text_blocks = 0usize;
    let mut thinking_blocks = 0usize;
    while let Some(block) = stream.next().await {
        match block? {
            MessageContentBlock::Text(text) => {
                print!("{text}");
                stdout().flush()?;
                text_blocks += 1;
            }
            MessageContentBlock::Thinking { .. } => thinking_blocks += 1,
            _ => {}
        }
    }
    println!("\nblocks: {text_blocks} text, {thinking_blocks} thinking");
    Ok(())
}
cargo run

The provider-deepseek feature comes from the cargo add line above.

To use the bridge, add .with_base_url("https://api.deepseek.com/anthropic") to the builder.

  • Prompt cache is the client-owned local response cache, a separate feature from the provider-side prompt caching the bridge disables.
↑↓ to navigate ↵ to open esc to close