Skip to content

Commit

Permalink
DeepSeek (#61)
Browse files Browse the repository at this point in the history
  • Loading branch information
kamillitman authored Jan 28, 2025
1 parent afa5cc7 commit dbef82a
Show file tree
Hide file tree
Showing 7 changed files with 239 additions and 4 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "allms"
version = "0.11.0"
version = "0.12.0"
edition = "2021"
authors = [
"Kamil Litman <[email protected]>",
Expand Down
11 changes: 10 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ This Rust library is specialized in providing type-safe interactions with APIs o

## Features

- Support for various foundational LLM providers including Anthropic, AWS Bedrock, Azure, Google Gemini, OpenAI, Mistral, and Perplexity.
- Support for various foundational LLM providers including Anthropic, AWS Bedrock, Azure, DeepSeek, Google Gemini, OpenAI, Mistral, and Perplexity.
- Easy-to-use functions for chat/text completions and assistants. Use the same struct and methods regardless of which model you choose.
- Automated response deserialization to custom types.
- Standardized approach to providing context with support of function calling, tools, and file uploads.
Expand All @@ -29,6 +29,10 @@ AWS Bedrock:
- APIs: Converse
- Models: Nova Micro, Nova Lite, Nova Pro (additional models to be added)

DeepSeek:
- APIs: Chat Completion
- Models: DeepSeek-V3, DeepSeek-R1

Google Vertex AI / AI Studio:
- APIs: Chat Completions (including streaming)
- Models: Gemini 1.5 Pro, Gemini 1.5 Flash, Gemini 1.0 Pro
Expand All @@ -49,6 +53,7 @@ Perplexity:
- Anthropic: API key (passed in model constructor)
- Azure OpenAI: environment variable `OPENAI_API_URL` set to your Azure OpenAI resource endpoint. Endpoint key passed in constructor
- AWS Bedrock: environment variables `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY` and `AWS_REGION` set as per AWS settings.
- DeepSeek: API key (passed in model constructor)
- Google AI Studio: API key (passed in model constructor)
- Google Vertex AI: GCP service account key (used to obtain access token) + GCP project ID (set as environment variable)
- Mistral: API key (passed in model constructor)
Expand All @@ -68,6 +73,10 @@ let aws_bedrock_answer = Completions::new(AwsBedrockModels::NovaLite, "", None,
.get_answer::<T>(instructions)
.await?
let deepseek_answer = Completions::new(DeepSeekModels::DeepSeekReasoner, &API_KEY, None, None)
.get_answer::<T>(instructions)
.await?
let google_answer = Completions::new(GoogleModels::GeminiPro, &API_KEY, None, None)
.get_answer::<T>(instructions)
.await?
Expand Down
22 changes: 20 additions & 2 deletions examples/use_completions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ use serde::Serialize;

use allms::{
llm::{
AnthropicModels, AwsBedrockModels, GoogleModels, LLMModel, MistralModels, OpenAIModels,
PerplexityModels,
AnthropicModels, AwsBedrockModels, DeepSeekModels, GoogleModels, LLMModel, MistralModels,
OpenAIModels, PerplexityModels,
},
Completions,
};
Expand Down Expand Up @@ -126,4 +126,22 @@ async fn main() {
Ok(response) => println!("Perplexity response: {:#?}", response),
Err(e) => eprintln!("Error: {:?}", e),
}

// Get answer using DeepSeek
let model =
DeepSeekModels::try_from_str("deepseek-chat").unwrap_or(DeepSeekModels::DeepSeekChat); // Choose the model
println!("DeepSeek model: {:#?}", model.as_str());

let deepseek_token_str: String =
std::env::var("DEEPSEEK_API_KEY").expect("DEEPSEEK_API_KEY not set");

let deepseek_completion = Completions::new(model, &deepseek_token_str, None, None);

match deepseek_completion
.get_answer::<TranslationResponse>(instructions)
.await
{
Ok(response) => println!("DeepSeek response: {:#?}", response),
Err(e) => eprintln!("Error: {:?}", e),
}
}
5 changes: 5 additions & 0 deletions src/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ lazy_static! {
std::env::var("AWS_SECRET_ACCESS_KEY").expect("AWS_SECRET_ACCESS_KEY not set");
}

lazy_static! {
pub(crate) static ref DEEPSEEK_API_URL: String = std::env::var("DEEPSEEK_API_URL")
.unwrap_or("https://api.deepseek.com/chat/completions".to_string());
}

//Generic OpenAI instructions
pub(crate) const OPENAI_BASE_INSTRUCTIONS: &str = r#"You are a computer function. You are expected to perform the following tasks:
Step 1: Review and understand the 'instructions' from the *Instructions* section.
Expand Down
40 changes: 40 additions & 0 deletions src/domain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -335,3 +335,43 @@ pub struct PerplexityAPICompletionsUsage {
pub completion_tokens: usize,
pub total_tokens: usize,
}

// DeepSeek API response type format for Chat Completions API
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct DeepSeekAPICompletionsResponse {
pub id: Option<String>,
pub choices: Vec<DeepSeekAPICompletionsChoices>,
pub created: Option<usize>,
pub model: Option<String>,
pub system_fingerprint: Option<String>,
pub object: Option<String>,
pub usage: Option<DeepSeekAPICompletionsUsage>,
}

#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct DeepSeekAPICompletionsChoices {
pub index: usize,
pub finish_reason: String,
pub message: Option<DeepSeekAPICompletionsMessage>,
}

#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct DeepSeekAPICompletionsMessage {
pub role: Option<String>,
pub content: Option<String>,
}

#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct DeepSeekAPICompletionsUsage {
pub completion_tokens: usize,
pub prompt_tokens: usize,
pub prompt_cache_hit_tokens: usize,
pub prompt_cache_miss_tokens: usize,
pub total_tokens: usize,
pub completion_tokens_details: Option<DeepSeekAPICompletionsReasoningUsage>,
}

#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct DeepSeekAPICompletionsReasoningUsage {
pub reasoning_tokens: usize,
}
161 changes: 161 additions & 0 deletions src/llm_models/deepseek.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
use anyhow::{anyhow, Result};
use async_trait::async_trait;
use log::info;
use reqwest::{header, Client};
use serde::{Deserialize, Serialize};
use serde_json::{json, Value};

use crate::constants::DEEPSEEK_API_URL;
use crate::domain::{DeepSeekAPICompletionsResponse, RateLimit};
use crate::llm_models::LLMModel;
use crate::utils::{map_to_range_f32, sanitize_json_response};

#[derive(Deserialize, Serialize, Debug, Clone, Eq, PartialEq)]
//DeepSeek docs: https://api-docs.deepseek.com/quick_start/pricing
pub enum DeepSeekModels {
DeepSeekChat,
DeepSeekReasoner,
}

#[async_trait(?Send)]
impl LLMModel for DeepSeekModels {
fn as_str(&self) -> &str {
match self {
DeepSeekModels::DeepSeekChat => "deepseek-chat",
DeepSeekModels::DeepSeekReasoner => "deepseek-reasoner",
}
}

fn try_from_str(name: &str) -> Option<Self> {
match name.to_lowercase().as_str() {
"deepseek-chat" => Some(DeepSeekModels::DeepSeekChat),
"deepseek-reasoner" => Some(DeepSeekModels::DeepSeekReasoner),
_ => None,
}
}

fn default_max_tokens(&self) -> usize {
match self {
DeepSeekModels::DeepSeekChat => 8_192,
DeepSeekModels::DeepSeekReasoner => 8_192,
}
}

fn get_endpoint(&self) -> String {
DEEPSEEK_API_URL.to_string()
}

/// This method prepares the body of the API call for different models
fn get_body(
&self,
instructions: &str,
json_schema: &Value,
function_call: bool,
max_tokens: &usize,
temperature: &f32,
) -> serde_json::Value {
//Prepare the 'messages' part of the body
let base_instructions = self.get_base_instructions(Some(function_call));
let system_message = json!({
"role": "system",
"content": base_instructions,
});
let schema_string = serde_json::to_string(json_schema).unwrap_or_default();
let user_message = json!({
"role": "user",
"content": format!(
"Output Json schema:\n
{schema_string}\n\n
{instructions}"
),
});
json!({
"model": self.as_str(),
"max_tokens": max_tokens,
"temperature": temperature,
"messages": vec![
system_message,
user_message,
],
})
}
///
/// This function leverages DeepSeek API to perform any query as per the provided body.
///
/// It returns a String the Response object that needs to be parsed based on the self.model.
///
async fn call_api(
&self,
api_key: &str,
body: &serde_json::Value,
debug: bool,
) -> Result<String> {
//Get the API url
let model_url = self.get_endpoint();

//Make the API call
let client = Client::new();

//Send request
let response = client
.post(model_url)
.header(header::CONTENT_TYPE, "application/json")
.bearer_auth(api_key)
.json(&body)
.send()
.await?;

let response_status = response.status();
let response_text = response.text().await?;

if debug {
info!(
"[debug] DeepSeek API response: [{}] {:#?}",
&response_status, &response_text
);
}

Ok(response_text)
}

///
/// This method attempts to convert the provided API response text into the expected struct and extracts the data from the response
///
fn get_data(&self, response_text: &str, _function_call: bool) -> Result<String> {
//Convert API response to struct representing expected response format
let completions_response: DeepSeekAPICompletionsResponse =
serde_json::from_str(response_text)?;

//Parse the response and return the assistant content
completions_response
.choices
.iter()
.filter_map(|choice| choice.message.as_ref())
.find(|&message| message.role == Some("assistant".to_string()))
.and_then(|message| {
message
.content
.as_ref()
.map(|content| sanitize_json_response(content))
})
.ok_or_else(|| anyhow!("Assistant role content not found"))
}

// This function allows to check the rate limits for different models
fn get_rate_limit(&self) -> RateLimit {
// DeepSeek documentation: https://api-docs.deepseek.com/quick_start/rate_limit
// "DeepSeek API does NOT constrain user's rate limit. We will try out best to serve every request."
RateLimit {
tpm: 100_000_000, // i.e. very large number
rpm: 100_000_000,
}
}

// Accepts a [0-100] percentage range and returns the target temperature based on model ranges
fn get_normalized_temperature(&self, relative_temp: u32) -> f32 {
// Temperature range documentation: https://api-docs.deepseek.com/quick_start/parameter_settings
let min = 0.0f32;
let max = 1.5f32;
map_to_range_f32(min, max, relative_temp)
}
}
2 changes: 2 additions & 0 deletions src/llm_models/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
pub mod anthropic;
pub mod aws;
pub mod deepseek;
pub mod google;
pub mod llm_model;
pub mod mistral;
Expand All @@ -8,6 +9,7 @@ pub mod perplexity;

pub use anthropic::AnthropicModels;
pub use aws::AwsBedrockModels;
pub use deepseek::DeepSeekModels;
pub use google::GoogleModels;
pub use llm_model::LLMModel;
pub use llm_model::LLMModel as LLM;
Expand Down

0 comments on commit dbef82a

Please sign in to comment.