language_barrier_core/
chat.rs1use crate::compactor::{ChatHistoryCompactor, DropOldestCompactor};
2use crate::message::{Content, Message};
3use crate::token::TokenCounter;
4use crate::tool::{LlmToolInfo, ToolChoice};
5use crate::{Result, ToolDefinition};
6
7#[derive(Clone, Debug)]
11pub struct Chat {
12 pub system_prompt: String,
14 pub max_output_tokens: usize,
15
16 pub history: Vec<Message>,
18 token_counter: TokenCounter,
19
20 pub tools: Option<Vec<LlmToolInfo>>,
22
23 pub tool_choice: Option<ToolChoice>,
25}
26
27impl Default for Chat {
28 fn default() -> Self {
29 Self {
30 system_prompt: String::new(),
31 max_output_tokens: 2048,
32 history: Vec::new(),
33 token_counter: TokenCounter::default(),
34 tools: None,
35 tool_choice: None,
36 }
37 }
38}
39
40impl Chat {
41 #[must_use]
43 pub fn with_system_prompt(self, prompt: impl Into<String>) -> Self {
44 let p = prompt.into();
45 let mut token_counter = self.token_counter.clone();
46 token_counter.observe(&p);
47
48 let mut new_chat = Self {
49 system_prompt: p,
50 token_counter,
51 ..self
52 };
53
54 new_chat = new_chat.trim_to_context_window();
55 new_chat
56 }
57
58 #[must_use]
60 pub fn with_max_output_tokens(self, n: usize) -> Self {
61 Self {
62 max_output_tokens: n,
63 ..self
64 }
65 }
66
67 #[must_use]
69 pub fn with_history(self, history: Vec<Message>) -> Self {
70 let mut token_counter = TokenCounter::default();
72
73 token_counter.observe(&self.system_prompt);
75
76 for msg in &history {
78 match msg {
79 Message::User { content, .. } => {
80 if let Content::Text(text) = content {
81 token_counter.observe(text);
82 }
83 }
84 Message::Assistant { content, .. } => {
85 if let Some(Content::Text(text)) = content {
86 token_counter.observe(text);
87 }
88 }
89 Message::System { content, .. } | Message::Tool { content, .. } => {
90 token_counter.observe(content);
91 }
92 }
93 }
94
95 let mut new_chat = Self {
96 history,
97 token_counter,
98 ..self
99 };
100
101 new_chat = new_chat.trim_to_context_window();
102 new_chat
103 }
104
105 #[must_use]
107 pub fn add_message(self, msg: Message) -> Self {
108 let mut token_counter = self.token_counter.clone();
109 let mut history = self.history.clone();
110
111 match &msg {
113 Message::User { content, .. } => {
114 if let Content::Text(text) = content {
115 token_counter.observe(text);
116 }
117 }
118 Message::Assistant { content, .. } => {
119 if let Some(Content::Text(text)) = content {
120 token_counter.observe(text);
121 }
122 }
123 Message::System { content, .. } | Message::Tool { content, .. } => {
124 token_counter.observe(content);
125 }
126 }
127
128 history.push(msg);
129
130 let mut new_chat = Self {
131 history,
132 token_counter,
133 ..self
134 };
135
136 new_chat = new_chat.trim_to_context_window();
137 new_chat
138 }
139
140 #[must_use]
142 pub fn push_message(self, msg: Message) -> Self {
143 self.add_message(msg)
144 }
145
146 #[must_use]
148 fn trim_to_context_window(self) -> Self {
149 const MAX_TOKENS: usize = 32_768; let mut history = self.history.clone();
152 let mut token_counter = self.token_counter.clone();
153
154 let new_compactor = Box::<DropOldestCompactor>::default();
158
159 new_compactor.compact(&mut history, &mut token_counter, MAX_TOKENS);
161
162 Self {
163 history,
164 token_counter,
165 ..self
166 }
167 }
168
169 pub fn tokens_used(&self) -> usize {
171 self.token_counter.total()
172 }
173
174 #[must_use = "This returns a new Chat with the tool added"]
176 pub fn with_tool(self, tool: impl ToolDefinition) -> Result<Self> {
177 let info = LlmToolInfo {
178 name: tool.name(),
179 description: tool.description(),
180 parameters: tool.schema()?,
181 };
182
183 let tools = match self.tools {
184 Some(mut tools) => {
185 tools.push(info);
186 Some(tools)
187 }
188 None => Some(vec![info]),
189 };
190
191 let new_chat = Self { tools, ..self };
192
193 Ok(new_chat)
194 }
195
196 #[must_use = "This returns a new Chat with the tools added"]
198 pub fn with_tools(self, tools: Vec<LlmToolInfo>) -> Self {
199 let new_tools = match self.tools {
200 Some(mut existing_tools) => {
201 existing_tools.extend(tools);
202 Some(existing_tools)
203 }
204 None => Some(tools),
205 };
206
207 Self {
208 tools: new_tools,
209 ..self
210 }
211 }
212
213 #[must_use]
247 pub fn with_tool_choice(self, choice: ToolChoice) -> Self {
248 Self {
249 tool_choice: Some(choice),
250 ..self
251 }
252 }
253
254 #[must_use]
258 pub fn without_tool_choice(self) -> Self {
259 Self {
260 tool_choice: None,
261 ..self
262 }
263 }
264
265 pub fn most_recent_message(&self) -> Option<&Message> {
267 self.history.last()
268 }
269}