language_barrier_core/
chat.rs1use crate::compactor::{ChatHistoryCompactor, DropOldestCompactor};
2use crate::message::{Content, Message};
3use crate::token::TokenCounter;
4use crate::tool::LlmToolInfo;
5use crate::{ModelInfo, Result, ToolDefinition};
6
7pub struct Chat<M: ModelInfo> {
11 pub model: M,
13
14 pub system_prompt: String,
16 pub max_output_tokens: usize,
17
18 pub history: Vec<Message>,
20 token_counter: TokenCounter,
21 #[allow(dead_code)]
22 compactor: Box<dyn ChatHistoryCompactor>,
23
24 pub tools: Option<Vec<LlmToolInfo>>,
26}
27
28impl<M> Chat<M>
29where
30 M: ModelInfo,
31{
32 pub fn new(model: M) -> Self {
34 Self {
35 model,
36 system_prompt: String::new(),
37 max_output_tokens: 2048,
38 history: Vec::new(),
39 token_counter: TokenCounter::default(),
40 compactor: Box::<DropOldestCompactor>::default(),
41 tools: None,
42 }
43 }
44
45 #[must_use]
47 pub fn with_system_prompt(self, prompt: impl Into<String>) -> Self {
48 let p = prompt.into();
49 let mut token_counter = self.token_counter.clone();
50 token_counter.observe(&p);
51
52 let mut new_chat = Self {
53 system_prompt: p,
54 token_counter,
55 ..self
56 };
57
58 new_chat = new_chat.trim_to_context_window();
59 new_chat
60 }
61
62 #[must_use]
64 pub fn with_max_output_tokens(self, n: usize) -> Self {
65 Self {
66 max_output_tokens: n,
67 ..self
68 }
69 }
70
71 #[must_use]
73 pub fn with_history(self, history: Vec<Message>) -> Self {
74 let mut token_counter = TokenCounter::default();
76
77 token_counter.observe(&self.system_prompt);
79
80 for msg in &history {
82 match msg {
83 Message::User { content, .. } => {
84 if let Content::Text(text) = content {
85 token_counter.observe(text);
86 }
87 }
88 Message::Assistant { content, .. } => {
89 if let Some(Content::Text(text)) = content {
90 token_counter.observe(text);
91 }
92 }
93 Message::System { content, .. } | Message::Tool { content, .. } => {
94 token_counter.observe(content);
95 }
96 }
97 }
98
99 let mut new_chat = Self {
100 history,
101 token_counter,
102 ..self
103 };
104
105 new_chat = new_chat.trim_to_context_window();
106 new_chat
107 }
108
109 #[must_use]
111 pub fn with_compactor<C: ChatHistoryCompactor + 'static>(self, comp: C) -> Self {
112 let mut new_chat = Self {
113 compactor: Box::new(comp),
114 ..self
115 };
116
117 new_chat = new_chat.trim_to_context_window();
118 new_chat
119 }
120
121 #[must_use]
123 pub fn add_message(self, msg: Message) -> Self {
124 let mut token_counter = self.token_counter.clone();
125 let mut history = self.history.clone();
126
127 match &msg {
129 Message::User { content, .. } => {
130 if let Content::Text(text) = content {
131 token_counter.observe(text);
132 }
133 }
134 Message::Assistant { content, .. } => {
135 if let Some(Content::Text(text)) = content {
136 token_counter.observe(text);
137 }
138 }
139 Message::System { content, .. } | Message::Tool { content, .. } => {
140 token_counter.observe(content);
141 }
142 }
143
144 history.push(msg);
145
146 let mut new_chat = Self {
147 history,
148 token_counter,
149 ..self
150 };
151
152 new_chat = new_chat.trim_to_context_window();
153 new_chat
154 }
155
156 #[must_use]
158 pub fn push_message(self, msg: Message) -> Self {
159 self.add_message(msg)
160 }
161
162 #[must_use]
164 fn trim_to_context_window(self) -> Self {
165 const MAX_TOKENS: usize = 32_768; let mut history = self.history.clone();
168 let mut token_counter = self.token_counter.clone();
169
170 let new_compactor = Box::<DropOldestCompactor>::default();
174
175 new_compactor.compact(&mut history, &mut token_counter, MAX_TOKENS);
177
178 Self {
179 history,
180 token_counter,
181 compactor: new_compactor as Box<dyn ChatHistoryCompactor>,
182 ..self
183 }
184 }
185
186 pub fn tokens_used(&self) -> usize {
188 self.token_counter.total()
189 }
190
191 #[must_use = "This returns a new Chat with the tool added"]
193 pub fn with_tool(self, tool: impl ToolDefinition) -> Result<Self> {
194 let info = LlmToolInfo {
195 name: tool.name(),
196 description: tool.description(),
197 parameters: tool.schema()?,
198 };
199
200 let tools = match self.tools {
201 Some(mut tools) => {
202 tools.push(info);
203 Some(tools)
204 },
205 None => Some(vec![info]),
206 };
207
208 let new_chat = Self {
209 tools,
210 ..self
211 };
212
213 Ok(new_chat)
214 }
215}