Skip to main content

deepseek_sdk/
lib.rs

1//! DeepSeek API client for Rust.
2//!
3//! This crate provides:
4//! - Chat completions (`/chat/completions`)
5//! - FIM completions (beta, `/beta/completions`)
6//! - Model listing (`/models`)
7//! - Account balance (`/user/balance`)
8//!
9//! Streaming is supported in both async and blocking forms. The async API returns
10//! a `tokio::mpsc::Receiver`, while the blocking API returns an iterator that
11//! yields stream items.
12//!
13//! ```no_run
14//! use deepseek_sdk::chat::request::{ChatMessage, ChatRequestBuilder, Thinking};
15//! use deepseek_sdk::{DeepSeekClient, DeepSeekRequest, DEFAULT_BASE_URL};
16//!
17//! # async fn run() -> Result<(), Box<dyn std::error::Error>> {
18//! let req = ChatRequestBuilder::default()
19//!     .client(DeepSeekClient::new("sk-...", DEFAULT_BASE_URL.clone()))
20//!     .model("deepseek-v4-flash")
21//!     .message(ChatMessage::User { content: "Hi".into(), name: None })
22//!     .thinking(Thinking::disabled())
23//!     .build()?;
24//! let _resp = req.send().await?;
25//! # Ok(()) }
26//! ```
27pub mod balance;
28pub mod chat;
29pub mod completion;
30pub mod error;
31pub mod models;
32
33use crate::error::{ApiErrorEnvelope, DeepSeekError};
34
35use reqwest::{Client, Method, RequestBuilder, Response, header::AUTHORIZATION};
36use reqwest_eventsource::{EventSource, RequestBuilderExt};
37use serde::{Serialize, de::DeserializeOwned};
38use std::future::Future;
39use std::sync::LazyLock;
40use tokio::sync::mpsc;
41
42/// Default base URL for stable API endpoints.
43pub static DEFAULT_BASE_URL: LazyLock<String> =
44    LazyLock::new(|| String::from("https://api.deepseek.com"));
45/// Default base URL for beta endpoints (e.g. FIM completion).
46pub static DEFAULT_BETA_BASE_URL: LazyLock<String> =
47    LazyLock::new(|| String::from("https://api.deepseek.com/beta"));
48
49/// API credentials for a DeepSeek endpoint.
50#[derive(Clone, Debug, Eq, PartialEq)]
51struct Credentials {
52    pub(crate) api_key: String,
53    pub(crate) base_url: String,
54}
55
56#[derive(Clone, Debug)]
57pub struct DeepSeekClient {
58    pub(crate) credentials: Credentials,
59    pub client: Client,
60}
61
62impl DeepSeekClient {
63    pub fn new(api_key: impl Into<String>, base_url: impl Into<String>) -> Self {
64        DeepSeekClient {
65            credentials: Credentials::new(api_key, base_url),
66            client: Client::new(),
67        }
68    }
69
70    pub fn with_client(mut self, client: Client) -> Self {
71        self.client = client;
72        self
73    }
74
75    pub fn with_credentials(
76        mut self,
77        api_key: impl Into<String>,
78        base_url: impl Into<String>,
79    ) -> Self {
80        self.credentials = Credentials::new(api_key, base_url);
81        self
82    }
83}
84
85impl Credentials {
86    /// Create credentials with an API key and base URL.
87    pub(crate) fn new(api_key: impl Into<String>, base_url: impl Into<String>) -> Self {
88        Credentials {
89            api_key: api_key.into(),
90            base_url: base_url.into(),
91        }
92    }
93}
94
95/// Unified request interface for DeepSeek endpoints.
96///
97/// Requests that support streaming should return stream items through `stream`
98/// and a blocking iterator through `stream_blocking`.
99pub trait DeepSeekRequest: Sized {
100    /// Full response type for non-streaming calls.
101    type Response;
102
103    /// Item type emitted by streaming calls.
104    type StreamItem;
105    /// Blocking stream iterator type.
106    type BlockingStream: Iterator<Item = Self::StreamItem>;
107
108    /// Send a non-streaming request.
109    fn send(self) -> impl Future<Output = Result<Self::Response, DeepSeekError>> + Send;
110    /// Send a streaming request (SSE), returning a receiver of stream items.
111    fn stream(
112        self,
113    ) -> impl Future<Output = Result<mpsc::Receiver<Self::StreamItem>, DeepSeekError>> + Send;
114    /// Send a streaming request but consume results via a blocking iterator.
115    fn stream_blocking(self) -> Result<Self::BlockingStream, DeepSeekError>;
116}
117
118async fn api_request_json<F, T>(
119    method: Method,
120    route: &str,
121    builder: F,
122    deepseek_client: DeepSeekClient,
123) -> Result<T, DeepSeekError>
124where
125    F: FnOnce(RequestBuilder) -> RequestBuilder,
126    T: DeserializeOwned,
127{
128    let response = api_request(method, route, builder, deepseek_client).await?;
129    let status = response.status();
130
131    let text = response.text().await?;
132
133    if !status.is_success() {
134        if let Ok(envelope) = serde_json::from_str::<ApiErrorEnvelope>(&text) {
135            return Err(DeepSeekError::api(
136                envelope.error,
137                Some(status.as_u16()),
138                Some(text),
139            ));
140        }
141
142        return Err(DeepSeekError::http(status.as_u16(), text));
143    }
144
145    serde_json::from_str::<T>(&text).map_err(|err| DeepSeekError::decode(err.to_string(), text))
146}
147
148async fn api_request<F>(
149    method: Method,
150    route: &str,
151    builder: F,
152    deepseek_client: DeepSeekClient,
153) -> Result<Response, DeepSeekError>
154where
155    F: FnOnce(RequestBuilder) -> RequestBuilder,
156{
157    let client = deepseek_client.client;
158    let mut request = client.request(
159        method,
160        format!("{}{route}", deepseek_client.credentials.base_url),
161    );
162    request = builder(request);
163    let response = request
164        .header(
165            AUTHORIZATION,
166            format!("Bearer {}", deepseek_client.credentials.api_key),
167        )
168        .send()
169        .await?;
170    Ok(response)
171}
172
173async fn api_request_stream<F>(
174    method: Method,
175    route: &str,
176    builder: F,
177    deepseek_client: DeepSeekClient,
178) -> Result<EventSource, DeepSeekError>
179where
180    F: FnOnce(RequestBuilder) -> RequestBuilder,
181{
182    let mut request = deepseek_client.client.request(
183        method,
184        format!("{}{route}", deepseek_client.credentials.base_url),
185    );
186    request = builder(request);
187    let stream = request
188        .header(
189            AUTHORIZATION,
190            format!("Bearer {}", deepseek_client.credentials.api_key),
191        )
192        .eventsource()
193        .map_err(|err| DeepSeekError::decode(err.to_string(), String::new()))?;
194    Ok(stream)
195}
196
197async fn api_get<T>(route: &str, client: DeepSeekClient) -> Result<T, DeepSeekError>
198where
199    T: DeserializeOwned,
200{
201    api_request_json(Method::GET, route, |request| request, client).await
202}
203
204async fn api_post<J, T>(route: &str, json: &J, client: DeepSeekClient) -> Result<T, DeepSeekError>
205where
206    J: Serialize + ?Sized,
207    T: DeserializeOwned,
208{
209    api_request_json(Method::POST, route, |request| request.json(json), client).await
210}