Skip to content
This repository was archived by the owner on Apr 3, 2026. It is now read-only.

Commit 6765557

Browse files
authored
feat(llm): cherry-pick from deepseek ullm (#7)
* feat(core): introduce the chat interface * feat(core): parse the response * feat(core): parse stream response * feat(deepseek): support message sending * feat(core): configure stream in provider * chore(core): wrap the chat interface * chore(deepseek): construct the request * feat(deepseek): sync the request type * feat(deepseek): support stream response * chore(ullmm): introduce the test command * feat(ullm): introduce command line interfaces * chore(core): introduce system template * feat(ullm): support cli config * chore(ullm): support RUST_LOG * feat(ullm): chat with cli * chore(clippy): make clippy happy
1 parent 7618dcc commit 6765557

25 files changed

Lines changed: 1788 additions & 40 deletions

Cargo.lock

Lines changed: 403 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,22 @@
11
[workspace]
22
resolver = "3"
3-
members = ["legacy/*"]
3+
members = ["crates/*", "llm/*", "legacy/*"]
44

55
[workspace.package]
66
version = "0.0.9"
77
edition = "2024"
88
authors = ["clearloop <tianyi.gc@gmail.com>"]
99
license = "MIT"
1010
repository = "https://github.com/clearloop/cydonia"
11+
documentation = "https://cydonia.docs.rs"
12+
keywords = ["llm", "agent", "ai"]
1113

1214
[workspace.dependencies]
1315
model = { path = "legacy/model", package = "cydonia-model" }
1416
candle = { path = "crates/candle", package = "cydonia-candle" }
17+
ucore = { path = "crates/core", package = "ullm-core" }
18+
deepseek = { path = "llm/deepseek", package = "ullm-deepseek" }
19+
1520

1621
# crates.io
1722
anyhow = "1"
@@ -31,6 +36,7 @@ toml = "0.9.8"
3136
tracing = "0.1"
3237
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
3338

39+
3440
# legacy dependencies
3541
candle-core = "0.8.1"
3642
candle-nn = "0.8.1"
@@ -40,5 +46,3 @@ llamac-sys = { version = "0.1.86", package = "llama-cpp-sys-2" }
4046
once_cell = "1.21"
4147
rand = "0.9.2"
4248
tokenizers = "0.21.0"
43-
44-

crates/core/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,6 @@ serde.workspace = true
1515
futures-core.workspace = true
1616
reqwest.workspace = true
1717
schemars.workspace = true
18+
19+
[dev-dependencies]
20+
serde_json.workspace = true

crates/core/src/chat.rs

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
//! Chat abstractions for the unified LLM Interfaces
2+
3+
use crate::{
4+
LLM, Response, Role, StreamChunk,
5+
message::{AssistantMessage, Message, ToolMessage},
6+
};
7+
use anyhow::Result;
8+
use futures_core::Stream;
9+
use serde::Serialize;
10+
11+
/// A chat for the LLM
12+
pub struct Chat<P: LLM> {
13+
/// The chat configuration
14+
pub config: P::ChatConfig,
15+
16+
/// Chat history in memory
17+
pub messages: Vec<ChatMessage>,
18+
19+
/// The LLM provider
20+
pub provider: P,
21+
}
22+
23+
impl<P: LLM> Chat<P> {
24+
/// Send a message to the LLM
25+
pub async fn send(&mut self, message: Message) -> Result<Response> {
26+
self.messages.push(message.into());
27+
self.provider.send(&self.config, &self.messages).await
28+
}
29+
30+
/// Send a message to the LLM with streaming
31+
pub fn stream(&mut self, message: Message) -> impl Stream<Item = Result<StreamChunk>> {
32+
self.messages.push(message.into());
33+
self.provider.stream(&self.config, &self.messages)
34+
}
35+
}
36+
37+
/// A chat message in memory
38+
#[derive(Debug, Clone, Serialize)]
39+
#[serde(untagged)]
40+
pub enum ChatMessage {
41+
/// A user message
42+
User(Message),
43+
44+
/// An assistant message
45+
Assistant(AssistantMessage),
46+
47+
/// A tool message
48+
Tool(ToolMessage),
49+
50+
/// A system message
51+
System(Message),
52+
}
53+
54+
impl From<Message> for ChatMessage {
55+
fn from(message: Message) -> Self {
56+
match message.role {
57+
Role::User => ChatMessage::User(message),
58+
Role::Assistant => ChatMessage::Assistant(AssistantMessage {
59+
message,
60+
prefix: false,
61+
reasoning: String::new(),
62+
}),
63+
Role::System => ChatMessage::System(message),
64+
Role::Tool => ChatMessage::Tool(ToolMessage {
65+
tool: String::new(),
66+
message,
67+
}),
68+
}
69+
}
70+
}

crates/core/src/config.rs

Lines changed: 74 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,11 @@
11
//! Configuration for a chat
22
3+
use crate::{Tool, ToolChoice};
4+
use serde::{Deserialize, Serialize};
5+
36
/// Chat configuration
4-
#[derive(Debug, Clone)]
7+
#[derive(Debug, Clone, Deserialize, Serialize)]
58
pub struct Config {
6-
/// The model to use
7-
pub model: &'static str,
8-
9-
/// Whether to enable thinking
10-
pub think: bool,
11-
129
/// The frequency penalty of the model
1310
pub frequency: i8,
1411

@@ -18,15 +15,27 @@ pub struct Config {
1815
/// Whether to return the log probabilities
1916
pub logprobs: bool,
2017

18+
/// The model to use
19+
pub model: String,
20+
2121
/// The presence penalty of the model
2222
pub presence: i8,
2323

24-
/// Whether to stream the response
25-
pub stream: bool,
24+
/// Stop sequences to halt generation
25+
pub stop: Vec<String>,
2626

2727
/// The temperature of the model
2828
pub temperature: f32,
2929

30+
/// Whether to enable thinking
31+
pub think: bool,
32+
33+
/// Controls which tool is called by the model
34+
pub tool_choice: ToolChoice,
35+
36+
/// A list of tools the model may call
37+
pub tools: Vec<Tool>,
38+
3039
/// The top probability of the model
3140
pub top_p: f32,
3241

@@ -36,6 +45,61 @@ pub struct Config {
3645
/// The number of max tokens to generate
3746
pub tokens: usize,
3847

39-
/// Whether to return the usage information
48+
/// Whether to return the usage information in stream mode
4049
pub usage: bool,
4150
}
51+
52+
impl Config {
53+
/// Create a new configuration
54+
pub fn new(model: impl Into<String>) -> Self {
55+
Self {
56+
model: model.into(),
57+
..Default::default()
58+
}
59+
}
60+
61+
/// Add a tool to the configuration
62+
pub fn tool(mut self, tool: Tool) -> Self {
63+
self.tools.push(tool);
64+
self
65+
}
66+
67+
/// Set tools for the configuration
68+
pub fn tools(mut self, tools: Vec<Tool>) -> Self {
69+
self.tools = tools;
70+
self
71+
}
72+
73+
/// Set the tool choice for the configuration
74+
pub fn tool_choice(mut self, choice: ToolChoice) -> Self {
75+
self.tool_choice = choice;
76+
self
77+
}
78+
79+
/// Set stop sequences for the configuration
80+
pub fn stop(mut self, sequences: Vec<String>) -> Self {
81+
self.stop = sequences;
82+
self
83+
}
84+
}
85+
86+
impl Default for Config {
87+
fn default() -> Self {
88+
Self {
89+
frequency: 0,
90+
json: false,
91+
logprobs: false,
92+
model: "deepseek-chat".into(),
93+
presence: 0,
94+
stop: Vec::new(),
95+
temperature: 1.0,
96+
think: false,
97+
tool_choice: ToolChoice::None,
98+
tools: Vec::new(),
99+
top_logprobs: 0,
100+
top_p: 1.0,
101+
tokens: 1000,
102+
usage: true,
103+
}
104+
}
105+
}

crates/core/src/lib.rs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,25 @@
11
//! Core abstractions for Unified LLM Interface
22
33
pub use {
4+
chat::{Chat, ChatMessage},
45
config::Config,
56
message::{Message, Role},
67
provider::LLM,
7-
tool::Tool,
8+
reqwest::{self, Client},
9+
response::{
10+
Choice, CompletionTokensDetails, FinishReason, LogProb, LogProbs, Response,
11+
ResponseMessage, TopLogProb, Usage,
12+
},
13+
stream::{Delta, StreamChoice, StreamChunk},
14+
template::Template,
15+
tool::{FunctionCall, Tool, ToolCall, ToolChoice},
816
};
917

18+
mod chat;
1019
mod config;
1120
mod message;
1221
mod provider;
22+
mod response;
23+
mod stream;
24+
mod template;
1325
mod tool;

crates/core/src/message.rs

Lines changed: 42 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,26 @@
11
//! Turbofish LLM message
22
3-
use derive_more::Display;
3+
use serde::{Deserialize, Serialize};
44

55
/// A message in the chat
6-
#[derive(Debug, Clone)]
6+
#[derive(Debug, Clone, Deserialize, Serialize)]
77
pub struct Message {
8-
/// The role of the message
9-
pub role: Role,
10-
118
/// The content of the message
129
pub content: String,
10+
11+
/// The name of the message
12+
pub name: String,
13+
14+
/// The role of the message
15+
pub role: Role,
1316
}
1417

1518
impl Message {
1619
/// Create a new system message
1720
pub fn system(content: impl Into<String>) -> Self {
1821
Self {
1922
role: Role::System,
23+
name: String::new(),
2024
content: content.into(),
2125
}
2226
}
@@ -25,6 +29,7 @@ impl Message {
2529
pub fn user(content: impl Into<String>) -> Self {
2630
Self {
2731
role: Role::User,
32+
name: String::new(),
2833
content: content.into(),
2934
}
3035
}
@@ -33,32 +38,52 @@ impl Message {
3338
pub fn assistant(content: impl Into<String>) -> Self {
3439
Self {
3540
role: Role::Assistant,
41+
name: String::new(),
3642
content: content.into(),
3743
}
3844
}
45+
}
3946

40-
/// Create a new tool message
41-
pub fn tool(content: impl Into<String>) -> Self {
42-
Self {
43-
role: Role::Tool,
44-
content: content.into(),
45-
}
46-
}
47+
/// A tool message in the chat
48+
#[derive(Debug, Clone, Deserialize, Serialize)]
49+
pub struct ToolMessage {
50+
/// The message
51+
#[serde(flatten)]
52+
pub message: Message,
53+
54+
/// The tool call id
55+
#[serde(alias = "tool_call_id")]
56+
pub tool: String,
57+
}
58+
59+
/// An assistant message in the chat
60+
#[derive(Debug, Clone, Deserialize, Serialize)]
61+
pub struct AssistantMessage {
62+
/// The message
63+
#[serde(flatten)]
64+
pub message: Message,
65+
66+
/// Whether to prefix the message
67+
pub prefix: bool,
68+
69+
/// The reasoning content
70+
#[serde(alias = "reasoning_content")]
71+
pub reasoning: String,
4772
}
4873

4974
/// The role of a message
50-
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Display)]
75+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Deserialize, Serialize)]
5176
pub enum Role {
5277
/// The user role
53-
#[display("user")]
78+
#[serde(rename = "user")]
5479
User,
5580
/// The assistant role
56-
#[display("assistant")]
81+
#[serde(rename = "assistant")]
5782
Assistant,
5883
/// The system role
59-
#[display("system")]
84+
#[serde(rename = "system")]
6085
System,
6186
/// The tool role
62-
#[display("tool")]
87+
#[serde(rename = "tool")]
6388
Tool,
6489
}

0 commit comments

Comments
 (0)