Skip to content
Merged
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
6 changes: 6 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# 说明:.env 已配置 Git 忽略,仅本地生效;.env.example 为公开模板,不会被忽略
# 使用方式:复制本文件并重命名为 .env,在 .env 中的 API_KEY 键填入真实 API Key
# 重要警告:严禁在 .env.example 中写入真实密钥并提交至远程仓库,防止 Token 泄露
# 补充:仓库虽然开启了密钥泄露通知,但仅作兜底防护,请勿依赖该机制

API_KEY=sh-...
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
/target
/.trae
/.trae
.env
10 changes: 10 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 4 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ anyhow = "1.0.102"
tokio = { version = "1.52.3", features = ["full"] }
colored = "3.1.1"
indicatif = "0.18.4"
reqwest = { version = "0.13.3", features = ["stream"] }
reqwest = { version = "0.13.3", features = ["stream", "json"] }
tokio-stream = "0.1.18"
futures = "0.3.32"
futures-util = "0.3.32"
Expand All @@ -38,4 +38,6 @@ zip = "8.6.0"
flate2 = "1.1.9"
tar = "0.4.46"
thiserror = "2.0.18"
hex = "0.4.3"
hex = "0.4.3"
dotenv = "0.15.0"
bytes = "1.11.1"
Empty file added src/core/chat/client.rs
Empty file.
2 changes: 2 additions & 0 deletions src/core/chat/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
mod client;
mod provider;
Empty file.
Empty file.
117 changes: 117 additions & 0 deletions src/core/chat/provider/deepseek/chat.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
use serde::{Deserialize, Serialize};
use serde_json::Value;
use super::{enums::*, message::ChatMessage, tool::*};

#[derive(Debug, Serialize, Deserialize, Default)]
pub struct StreamOptions {
#[serde(skip_serializing_if = "Option::is_none")]
pub include_usage: Option<bool>,
}

#[derive(Debug, Serialize, Deserialize, Default)]
pub struct ResponseFormat {
pub r#type: ResponseFormatType,
}

/// 聊天补全请求
#[derive(Debug, Serialize, Deserialize, Default)]
pub struct CreateChatCompletionRequest {
pub model: String,
pub messages: Vec<ChatMessage>,
#[serde(skip_serializing_if = "Option::is_none")]
pub frequency_penalty: Option<f32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub presence_penalty: Option<f32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub temperature: Option<f32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub top_p: Option<f32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub stop: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub max_tokens: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub stream: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub stream_options: Option<StreamOptions>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tools: Option<Vec<Tool>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tool_choice: Option<ToolChoice>,
#[serde(skip_serializing_if = "Option::is_none")]
pub response_format: Option<ResponseFormat>,
#[serde(skip_serializing_if = "Option::is_none")]
pub logprobs: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub top_logprobs: Option<u8>,
#[serde(skip_serializing_if = "Option::is_none")]
pub user: Option<String>,
}

/// 聊天补全响应
#[derive(Debug, Serialize, Deserialize)]
pub struct ChatCompletionResponse {
pub id: String,
pub object: String,
pub created: i64,
pub model: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub system_fingerprint: Option<String>,
pub choices: Vec<Choice>,
pub usage: Usage,
#[serde(skip_serializing_if = "Option::is_none")]
pub prompt_filter_results: Option<Vec<Value>>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct Choice {
pub index: i32,
pub message: ChatMessage,
#[serde(skip_serializing_if = "Option::is_none")]
pub finish_reason: Option<FinishReason>,
#[serde(skip_serializing_if = "Option::is_none")]
pub logprobs: Option<Logprobs>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct Logprobs {
pub content: Option<Vec<LogprobContent>>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct LogprobContent {
pub token: String,
pub logprob: f32,
#[serde(skip_serializing_if = "Option::is_none")]
pub bytes: Option<Vec<u8>>,
pub top_logprobs: Vec<TopLogprob>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct TopLogprob {
pub token: String,
pub logprob: f32,
#[serde(skip_serializing_if = "Option::is_none")]
pub bytes: Option<Vec<u8>>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct Usage {
pub prompt_tokens: u32,
pub completion_tokens: u32,
pub total_tokens: u32,
#[serde(skip_serializing_if = "Option::is_none")]
pub prompt_tokens_details: Option<PromptTokensDetails>,
#[serde(skip_serializing_if = "Option::is_none")]
pub completion_tokens_details: Option<CompletionTokensDetails>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct PromptTokensDetails {
pub cached_tokens: u32,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct CompletionTokensDetails {
pub reasoning_tokens: u32,
}
38 changes: 38 additions & 0 deletions src/core/chat/provider/deepseek/enums.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum Role {
System,
User,
Assistant,
Tool,
}

#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum ToolType {
Function,
}

#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum ResponseFormatType {
Text,
JsonObject,
}

#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum FinishReason {
Stop,
Length,
ToolCalls,
ContentFilter,
}

impl Default for ResponseFormatType {
fn default() -> Self {
Self::Text
}
}
47 changes: 47 additions & 0 deletions src/core/chat/provider/deepseek/message.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
use serde::{Deserialize, Serialize};
use super::enums::Role;

#[derive(Debug, Serialize, Deserialize)]
#[serde(untagged)]
pub enum MessageContent {
Text(String),
Parts(Vec<ContentPart>),
}

#[derive(Debug, Serialize, Deserialize)]
pub struct ContentPart {
pub r#type: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub text: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub image_url: Option<ImageUrl>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct ImageUrl {
pub url: String,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct ChatMessage {
pub role: Role,
pub content: MessageContent,
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tool_call_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tool_calls: Option<Vec<super::tool::ToolCall>>,
}

impl Default for ChatMessage {
fn default() -> Self {
Self {
role: Role::User,
content: MessageContent::Text(String::new()),
name: None,
tool_call_id: None,
tool_calls: None,
}
}
}
6 changes: 6 additions & 0 deletions src/core/chat/provider/deepseek/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
mod chat;
mod model;
mod enums;
mod tool;
mod message;
mod test;
20 changes: 20 additions & 0 deletions src/core/chat/provider/deepseek/model.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
use serde::{Deserialize, Serialize};

/// GET /models 请求(无参数,空结构体)
#[derive(Debug, Serialize, Deserialize, Default)]
pub struct ListModelsRequest {}

/// 模型列表响应
#[derive(Debug, Serialize, Deserialize)]
pub struct ListModelsResponse {
pub object: String,
pub data: Vec<ModelInfo>,
}

/// 单个模型信息
#[derive(Debug, Serialize, Deserialize)]
pub struct ModelInfo {
pub id: String,
pub object: String,
pub owned_by: String,
}
Loading
Loading