ds_api/conversation/
deepseek.rs1use std::future::Future;
2use std::pin::Pin;
3
4use futures::stream::BoxStream;
5
6use crate::api::{ApiClient, ApiRequest};
7use crate::error::{ApiError, Result};
8use crate::raw::request::message::{Message, Role};
9
10use crate::conversation::{Conversation, Summarizer, TokenBasedSummarizer};
11
12pub struct DeepseekConversation {
18 client: ApiClient,
19 history: Vec<Message>,
20 summarizer: Box<dyn Summarizer + Send + Sync>,
21 auto_summary: bool,
22}
23
24impl DeepseekConversation {
25 pub fn new(client: ApiClient) -> Self {
27 Self {
28 client,
29 history: vec![],
30 summarizer: Box::new(TokenBasedSummarizer::default()),
31 auto_summary: true,
32 }
33 }
34
35 pub fn with_summarizer(mut self, s: impl Summarizer + 'static) -> Self {
37 self.summarizer = Box::new(s);
38 self
39 }
40
41 pub fn enable_auto_summary(mut self, v: bool) -> Self {
43 self.auto_summary = v;
44 self
45 }
46
47 pub fn with_history(mut self, history: Vec<Message>) -> Self {
49 self.history = history;
50 self
51 }
52
53 pub fn history_mut(&mut self) -> &mut Vec<Message> {
55 &mut self.history
56 }
57
58 fn maybe_do_summary(&mut self) {
60 if self.auto_summary && self.summarizer.should_summarize(&self.history) {
61 let _ = self.summarizer.summarize(&mut self.history);
62 }
63 }
64
65 pub async fn stream_text(
70 &mut self,
71 ) -> Result<BoxStream<'_, std::result::Result<String, ApiError>>> {
72 let req = ApiRequest::builder()
73 .messages(self.history.clone())
74 .stream(true);
75 let stream = self.client.stream_text(req).await?;
76 Ok(stream)
77 }
78}
79
80impl Conversation for DeepseekConversation {
81 fn history(&self) -> &Vec<Message> {
82 &self.history
83 }
84
85 fn add_message(&mut self, message: Message) {
86 self.history.push(message);
87 self.maybe_do_summary();
89 }
90
91 fn push_user_input(&mut self, text: String) {
92 self.history.push(Message::new(Role::User, text.as_str()));
94 self.maybe_do_summary();
96 }
97
98 fn maybe_summarize(&mut self) {
99 self.maybe_do_summary();
100 }
101
102 fn send_once<'a>(
103 &'a mut self,
104 ) -> Pin<Box<dyn Future<Output = Result<Option<String>>> + Send + 'a>> {
105 Box::pin(async move {
106 let req = ApiRequest::builder().messages(self.history.clone());
109 let resp = self.client.send(req).await?;
110 let choice = resp
112 .choices
113 .into_iter()
114 .next()
115 .ok_or_else(|| ApiError::Other("empty choices from API".to_string()))?;
116 let assistant_msg = choice.message;
117 let content = assistant_msg.content.clone();
118
119 self.history.push(assistant_msg);
121
122 self.maybe_do_summary();
124
125 Ok(content)
126 })
127 }
128}
129
130#[cfg(test)]
131mod tests {
132 use super::*;
133 use crate::api::ApiClient;
134
135 #[test]
136 fn conversation_builder_and_push() {
137 let client = ApiClient::new("fake-token");
138 let conv = DeepseekConversation::new(client).with_history(vec![]);
139 assert!(conv.history().is_empty());
140 }
141}