1use crate::providers::Message;
7use crate::compress::CoherenceDetector;
8use crate::compress::ConversationFocus;
9use crate::compress::complexity::{ComplexityAnalyzer, ComplexityLevel};
10use crate::compress::focus_point::{FocusManager};
11use crate::compress::hardcode_config::HardcodeConfig;
12use anyhow::Result;
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub enum CompressionStage {
17 RemoveLowPriority,
19 SummarizeMedium,
21 CompressHighPriority,
23 EmergencyCompression,
25}
26
27impl CompressionStage {
28 pub fn name(&self) -> &str {
30 match self {
31 CompressionStage::RemoveLowPriority => "Remove Low Priority",
32 CompressionStage::SummarizeMedium => "Summarize Medium",
33 CompressionStage::CompressHighPriority => "Compress High Priority",
34 CompressionStage::EmergencyCompression => "Emergency Compression",
35 }
36 }
37
38 pub fn priority(&self) -> u8 {
40 match self {
41 CompressionStage::RemoveLowPriority => 1,
42 CompressionStage::SummarizeMedium => 2,
43 CompressionStage::CompressHighPriority => 3,
44 CompressionStage::EmergencyCompression => 4,
45 }
46 }
47}
48
49#[derive(Debug, Clone)]
51pub struct ProgressiveCompressor {
52 coherence: CoherenceDetector,
54 focus_manager: Option<FocusManager>,
56 config: ProgressiveConfig,
58 hardcode_config: HardcodeConfig,
60}
61
62#[derive(Debug, Clone)]
64pub struct ProgressiveConfig {
65 target_budget: u32,
67 stage1_threshold: u32,
69 stage2_threshold: u32,
71 stage3_threshold: u32,
73 emergency_threshold: u32,
75 preserve_last_n: usize,
77 coherence_threshold: f32,
79}
80
81impl Default for ProgressiveConfig {
82 fn default() -> Self {
83 Self {
84 target_budget: 8000,
85 stage1_threshold: 12000, stage2_threshold: 16000, stage3_threshold: 20000, emergency_threshold: 25000, preserve_last_n: 3,
90 coherence_threshold: 0.7,
91 }
92 }
93}
94
95impl ProgressiveConfig {
96 pub fn adaptive_configure(messages: &[Message]) -> Self {
104 let complexity = ComplexityAnalyzer::analyze(messages);
105
106 let (stage1, stage2, stage3, emergency, preserve_n) = match complexity {
108 ComplexityLevel::High => {
109 log::info!("检测到高复杂度对话,采用激进压缩策略");
111 (10000, 14000, 18000, 22000, 5)
112 },
113 ComplexityLevel::Medium => {
114 log::info!("检测到中等复杂度对话,采用标准压缩策略");
116 (12000, 16000, 20000, 25000, 3)
117 },
118 ComplexityLevel::Low => {
119 log::info!("检测到低复杂度对话,采用保守压缩策略");
121 (15000, 20000, 25000, 30000, 2)
122 },
123 };
124
125 Self {
126 target_budget: 8000,
127 stage1_threshold: stage1,
128 stage2_threshold: stage2,
129 stage3_threshold: stage3,
130 emergency_threshold: emergency,
131 preserve_last_n: preserve_n,
132 coherence_threshold: 0.7,
133 }
134 }
135
136 pub fn complexity_description(messages: &[Message]) -> &'static str {
138 let complexity = ComplexityAnalyzer::analyze(messages);
139 ComplexityAnalyzer::complexity_description(complexity)
140 }
141}
142
143impl ProgressiveCompressor {
144 pub fn new(config: ProgressiveConfig) -> Self {
146 Self {
147 coherence: CoherenceDetector::default(),
148 focus_manager: None,
149 config,
150 hardcode_config: HardcodeConfig::default(),
151 }
152 }
153
154 pub fn default_config() -> Self {
156 Self::new(ProgressiveConfig::default())
157 }
158
159 pub fn adaptive_create(messages: &[Message]) -> Self {
161 let config = ProgressiveConfig::adaptive_configure(messages);
162 let mut instance = Self::new(config);
163 let complexity = ComplexityAnalyzer::analyze(messages);
165 instance.hardcode_config = HardcodeConfig::from_complexity(complexity);
166 instance
167 }
168
169 pub fn set_focus_manager(&mut self, manager: FocusManager) {
171 self.focus_manager = Some(manager);
172 }
173
174 pub fn with_hardcode_config(mut self, config: HardcodeConfig) -> Self {
176 self.hardcode_config = config;
177 self
178 }
179
180 pub async fn compress(&mut self, messages: &[Message], provider: Option<&dyn crate::providers::Provider>) -> Result<Vec<Message>> {
182 let current_tokens = estimate_tokens(messages);
183
184 if current_tokens <= self.config.target_budget {
186 return Ok(messages.to_vec());
187 }
188
189 let mut result = messages.to_vec();
190 let mut applied_stages = Vec::new();
191
192 for stage in &[
194 CompressionStage::RemoveLowPriority,
195 CompressionStage::SummarizeMedium,
196 CompressionStage::CompressHighPriority,
197 CompressionStage::EmergencyCompression,
198 ] {
199 let tokens = estimate_tokens(&result);
200
201 let threshold = self.get_threshold_for_stage(*stage);
203 if tokens <= threshold && tokens <= self.config.target_budget {
204 break;
205 }
206
207 result = self.apply_stage(result.clone(), *stage, provider).await?;
209 applied_stages.push(*stage);
210
211 if estimate_tokens(&result) <= self.config.target_budget {
213 break;
214 }
215 }
216
217 log::info!(
219 "Progressive compression: {} -> {} tokens, stages applied: {}",
220 current_tokens,
221 estimate_tokens(&result),
222 applied_stages.iter().map(|s| s.name()).collect::<Vec<_>>().join(", ")
223 );
224
225 Ok(result)
226 }
227
228 fn get_threshold_for_stage(&self, stage: CompressionStage) -> u32 {
230 match stage {
231 CompressionStage::RemoveLowPriority => self.config.stage1_threshold,
232 CompressionStage::SummarizeMedium => self.config.stage2_threshold,
233 CompressionStage::CompressHighPriority => self.config.stage3_threshold,
234 CompressionStage::EmergencyCompression => self.config.emergency_threshold,
235 }
236 }
237
238 async fn apply_stage(
240 &mut self,
241 messages: Vec<Message>,
242 stage: CompressionStage,
243 provider: Option<&dyn crate::providers::Provider>,
244 ) -> Result<Vec<Message>> {
245 match stage {
246 CompressionStage::RemoveLowPriority => {
247 self.remove_low_priority(messages)
248 }
249 CompressionStage::SummarizeMedium => {
250 self.summarize_medium(messages, provider).await
251 }
252 CompressionStage::CompressHighPriority => {
253 self.compress_high_priority(messages, provider).await
254 }
255 CompressionStage::EmergencyCompression => {
256 self.emergency_compress(messages, provider).await
257 }
258 }
259 }
260
261 fn remove_low_priority(&self, messages: Vec<Message>) -> Result<Vec<Message>> {
263 let mut result = Vec::new();
264 let mut removed_count = 0;
265
266 let segments = self.coherence.segment_messages(&messages);
268
269 for segment in segments {
270 let filtered = segment.iter()
272 .enumerate()
273 .filter(|(i, msg)| {
274 if messages.len() - i <= self.config.preserve_last_n {
276 return true;
277 }
278
279 if let Some(focus_manager) = &self.focus_manager {
281 let content = self.get_message_content(msg);
282 let relevance = self.calculate_message_focus_relevance(&content, focus_manager);
283
284 if relevance > 0.7 {
286 return true;
287 }
288
289 if relevance > 0.3 {
291 if content.contains("```") || content.contains("?") || content.contains("?") {
292 return true;
293 }
294 }
295
296 if content.len() < 50 && !content.contains("```") {
298 removed_count += 1;
299 return false;
300 }
301
302 return true;
303 } else {
304 let content = self.get_message_content(msg);
306 if content.contains("```") || content.contains("function") || content.contains("fn ") {
307 return true;
308 }
309
310 if content.contains("?") || content.contains("?") ||
311 content.to_lowercase().contains("how") || content.to_lowercase().contains("如何") {
312 return true;
313 }
314
315 if content.len() < 50 {
316 removed_count += 1;
317 return false;
318 }
319
320 true
321 }
322 })
323 .map(|(_, msg)| msg.clone())
324 .collect::<Vec<_>>();
325
326 result.extend(filtered);
327 }
328
329 log::debug!("Stage 1: Removed {} low-priority messages", removed_count);
330 Ok(result)
331 }
332
333 fn calculate_message_focus_relevance(&self, content: &str, focus_manager: &FocusManager) -> f32 {
335 if let Some(current_focus) = focus_manager.current_focus() {
336 let mut score = 0.0_f32;
338
339 let content_lower = content.to_lowercase();
341 for keyword in ¤t_focus.keywords {
342 if content_lower.contains(&keyword.to_lowercase()) {
343 score += 0.2;
344 }
345 }
346
347 for entity in ¤t_focus.entities {
349 if content_lower.contains(&entity.to_lowercase()) {
350 score += 0.3;
351 }
352 }
353
354 for file in ¤t_focus.related_files {
356 if content.contains(&*file.to_string_lossy()) {
357 score += 0.4;
358 }
359 }
360
361 score *= current_focus.importance;
363
364 score *= current_focus.confidence;
366
367 return score.min(1.0);
368 }
369
370 0.5 }
372
373 async fn summarize_medium(&self, messages: Vec<Message>, provider: Option<&dyn crate::providers::Provider>) -> Result<Vec<Message>> {
375 let mut result = Vec::new();
376 let mut summarized_count = 0;
377
378 let segments = self.coherence.segment_messages(&messages);
380
381 for segment in segments {
382 let medium_indices = segment.iter()
384 .enumerate()
385 .filter(|(_, msg)| {
386 let content = self.get_message_content(msg);
387 content.len() >= 100 && content.len() <= 500 && !content.contains("```")
388 })
389 .map(|(i, _)| i)
390 .collect::<Vec<_>>();
391
392 if medium_indices.len() >= 2 {
393 let medium_messages = medium_indices.iter()
395 .map(|&i| segment[i].clone())
396 .collect::<Vec<_>>();
397
398 if let Some(p) = provider {
399 let summary = self.generate_summary(&medium_messages, p).await?;
400
401 let summary_msg = Message {
403 role: crate::providers::Role::Assistant,
404 content: crate::providers::MessageContent::Text(format!("[摘要] {}", summary)),
405 };
406
407 let mut new_segment = Vec::new();
409 for (i, msg) in segment.iter().enumerate() {
410 if medium_indices.contains(&i) {
411 if i == medium_indices[0] {
412 new_segment.push(summary_msg.clone());
413 summarized_count += medium_indices.len();
414 }
415 } else {
416 new_segment.push(msg.clone());
417 }
418 }
419 result.extend(new_segment);
420 } else {
421 result.extend(self.compress_inline(&segment, &medium_indices));
423 }
424 } else {
425 result.extend(segment);
427 }
428 }
429
430 log::debug!("Stage 2: Summarized {} medium-priority messages", summarized_count);
431 Ok(result)
432 }
433
434 async fn compress_high_priority(&self, messages: Vec<Message>, provider: Option<&dyn crate::providers::Provider>) -> Result<Vec<Message>> {
436 let mut result = Vec::new();
437 let mut compressed_count = 0;
438
439 for msg in messages {
440 let content = self.get_message_content(&msg);
442 if content.len() > self.hardcode_config.code_content_threshold && (content.contains("```") || content.contains("fn ") || content.contains("function")) {
443 if let Some(p) = provider {
444 let compressed = self.compress_single_message(&msg, p).await?;
445 result.push(compressed);
446 compressed_count += 1;
447 } else {
448 result.push(self.trim_verbose_message(msg));
449 }
450 } else {
451 result.push(msg);
452 }
453 }
454
455 log::debug!("Stage 3: Compressed {} high-priority verbose messages", compressed_count);
456 Ok(result)
457 }
458
459 async fn emergency_compress(&self, messages: Vec<Message>, provider: Option<&dyn crate::providers::Provider>) -> Result<Vec<Message>> {
461 if let Some(p) = provider {
463 let emergency_summary = self.generate_emergency_summary(&messages, p).await?;
464
465 let last_n = messages.iter().rev().take(self.config.preserve_last_n).rev().cloned().collect::<Vec<_>>();
467
468 let summary_msg = Message {
469 role: crate::providers::Role::Assistant,
470 content: crate::providers::MessageContent::Text(format!("[对话摘要] {}", emergency_summary)),
471 };
472
473 let result = vec![summary_msg]
474 .into_iter()
475 .chain(last_n.into_iter())
476 .collect();
477
478 log::warn!("Emergency compression applied: {} messages -> summary + last {}", messages.len(), self.config.preserve_last_n);
479 Ok(result)
480 } else {
481 Ok(messages.iter().rev().take(self.config.preserve_last_n).rev().cloned().collect())
483 }
484 }
485
486 async fn generate_summary(&self, messages: &[Message], provider: &dyn crate::providers::Provider) -> Result<String> {
488 let context = messages.iter()
489 .map(|m| match &m.content {
490 crate::providers::MessageContent::Text(text) => text.clone(),
491 crate::providers::MessageContent::Blocks(blocks) => {
492 blocks.iter()
493 .filter_map(|b| {
494 if let crate::providers::ContentBlock::Text { text } = b {
495 Some(text.clone())
496 } else {
497 None
498 }
499 })
500 .collect::<Vec<_>>()
501 .join("\n")
502 }
503 })
504 .collect::<Vec<_>>()
505 .join("\n\n");
506
507 let request = crate::providers::ChatRequest {
508 messages: vec![crate::providers::Message {
509 role: crate::providers::Role::User,
510 content: crate::providers::MessageContent::Text(format!(
511 "请简洁总结以下对话要点(不超过200字):\n\n{}",
512 context
513 )),
514 }],
515 tools: vec![],
516 system: Some("你是对话摘要助手,生成简洁准确的总结。".to_string()),
517 think: false,
518 max_tokens: 300,
519 server_tools: vec![],
520 enable_caching: false,
521 };
522
523 let response = provider.chat(request).await?;
524
525 let summary = response.content.iter()
526 .filter_map(|b| {
527 if let crate::providers::ContentBlock::Text { text } = b {
528 Some(text.clone())
529 } else {
530 None
531 }
532 })
533 .collect::<Vec<_>>()
534 .join("");
535
536 Ok(summary)
537 }
538
539 async fn generate_emergency_summary(&self, messages: &[Message], provider: &dyn crate::providers::Provider) -> Result<String> {
541 let context = messages.iter()
542 .map(|m| match &m.content {
543 crate::providers::MessageContent::Text(text) => text.clone(),
544 crate::providers::MessageContent::Blocks(blocks) => {
545 blocks.iter()
546 .filter_map(|b| {
547 if let crate::providers::ContentBlock::Text { text } = b {
548 Some(text.clone())
549 } else {
550 None
551 }
552 })
553 .collect::<Vec<_>>()
554 .join("\n")
555 }
556 })
557 .collect::<Vec<_>>()
558 .join("\n\n");
559
560 let truncated = if context.len() > self.hardcode_config.max_context_length {
562 context.chars().take(self.hardcode_config.max_context_length).collect::<String>()
563 } else {
564 context
565 };
566
567 let request = crate::providers::ChatRequest {
568 messages: vec![crate::providers::Message {
569 role: crate::providers::Role::User,
570 content: crate::providers::MessageContent::Text(format!(
571 "请生成紧急摘要,包含以下关键信息:\n1. 主要讨论主题\n2. 重要决策\n3. 待解决问题\n4. 当前状态\n\n对话内容:\n{}",
572 truncated
573 )),
574 }],
575 tools: vec![],
576 system: Some("你是紧急摘要助手,在对话过长时生成关键信息摘要。".to_string()),
577 think: false,
578 max_tokens: 500,
579 server_tools: vec![],
580 enable_caching: false,
581 };
582
583 let response = provider.chat(request).await?;
584
585 let summary = response.content.iter()
586 .filter_map(|b| {
587 if let crate::providers::ContentBlock::Text { text } = b {
588 Some(text.clone())
589 } else {
590 None
591 }
592 })
593 .collect::<Vec<_>>()
594 .join("");
595
596 Ok(summary)
597 }
598
599 async fn compress_single_message(&self, msg: &Message, provider: &dyn crate::providers::Provider) -> Result<Message> {
601 let content = match &msg.content {
602 crate::providers::MessageContent::Text(text) => text.clone(),
603 crate::providers::MessageContent::Blocks(blocks) => {
604 blocks.iter()
605 .filter_map(|b| {
606 if let crate::providers::ContentBlock::Text { text } = b {
607 Some(text.clone())
608 } else {
609 None
610 }
611 })
612 .collect::<Vec<_>>()
613 .join("\n")
614 }
615 };
616
617 let request = crate::providers::ChatRequest {
619 messages: vec![crate::providers::Message {
620 role: crate::providers::Role::User,
621 content: crate::providers::MessageContent::Text(format!(
622 "请将以下内容精简,保留核心信息:\n\n{}",
623 content
624 )),
625 }],
626 tools: vec![],
627 system: Some("你是内容精简助手,去除冗余保留要点。".to_string()),
628 think: false,
629 max_tokens: 200,
630 server_tools: vec![],
631 enable_caching: false,
632 };
633
634 let response = provider.chat(request).await?;
635
636 let compressed = response.content.iter()
637 .filter_map(|b| {
638 if let crate::providers::ContentBlock::Text { text } = b {
639 Some(text.clone())
640 } else {
641 None
642 }
643 })
644 .collect::<Vec<_>>()
645 .join("");
646
647 Ok(Message {
648 role: msg.role,
649 content: crate::providers::MessageContent::Text(format!("[精简] {}", compressed)),
650 })
651 }
652
653 fn get_message_content(&self, msg: &Message) -> String {
655 match &msg.content {
656 crate::providers::MessageContent::Text(text) => text.clone(),
657 crate::providers::MessageContent::Blocks(blocks) => {
658 blocks.iter()
659 .filter_map(|b| {
660 if let crate::providers::ContentBlock::Text { text } = b {
661 Some(text.clone())
662 } else {
663 None
664 }
665 })
666 .collect::<Vec<_>>()
667 .join("\n")
668 }
669 }
670 }
671
672 fn trim_verbose_message(&self, msg: Message) -> Message {
674 let content = match &msg.content {
675 crate::providers::MessageContent::Text(text) => text.clone(),
676 crate::providers::MessageContent::Blocks(blocks) => {
677 blocks.iter()
678 .filter_map(|b| {
679 if let crate::providers::ContentBlock::Text { text } = b {
680 Some(text.clone())
681 } else {
682 None
683 }
684 })
685 .collect::<Vec<_>>()
686 .join("\n")
687 }
688 };
689
690 let trimmed = if content.len() > self.hardcode_config.max_trimmed_content_length {
692 format!("[精简] {}...", content.chars().take(self.hardcode_config.max_trimmed_content_length).collect::<String>())
693 } else {
694 content
695 };
696
697 Message {
698 role: msg.role,
699 content: crate::providers::MessageContent::Text(trimmed),
700 }
701 }
702
703 pub fn compress_segments(
716 &self,
717 segments: Vec<Vec<Message>>,
718 focus: &ConversationFocus,
719 coherence: &CoherenceDetector,
720 ) -> Result<Vec<Message>> {
721 let mut result = Vec::new();
722
723 for segment in segments {
724 let coherence_score = coherence.calculate_coherence(&segment);
726
727 let focus_score = self.calculate_segment_focus_score(&segment, focus);
729
730 if coherence_score > self.config.coherence_threshold && focus_score > 0.5 {
732 log::debug!(
734 "Segment preserved: coherence={}, focus={}",
735 coherence_score, focus_score
736 );
737 result.extend(segment);
738 } else if coherence_score > self.config.coherence_threshold {
739 if segment.len() <= 3 {
741 result.extend(segment);
742 } else {
743 result.push(segment[0].clone());
745 let middle_indices: Vec<usize> = (1..segment.len() - 1).collect();
747 let compressed_middle = self.compress_inline(&segment, &middle_indices);
748 result.extend(compressed_middle.into_iter().skip(1).take(segment.len() - 3));
749 result.push(segment[segment.len() - 1].clone());
750 }
751 } else if focus_score > 0.5 {
752 for msg in &segment {
754 let msg_focus = self.calculate_message_focus_score(msg, focus);
755 if msg_focus > 0.3 {
756 result.push(msg.clone());
757 }
758 }
759 } else {
760 if !segment.is_empty() {
762 let all_indices: Vec<usize> = (0..segment.len()).collect();
764 let compressed = self.compress_inline(&segment, &all_indices);
765 result.extend(compressed);
766 }
767 }
768 }
769
770 Ok(result)
771 }
772
773 fn calculate_segment_focus_score(&self, segment: &[Message], focus: &ConversationFocus) -> f32 {
775 if segment.is_empty() {
776 return 0.0;
777 }
778
779 let mut total_score = 0.0;
780 for msg in segment {
781 total_score += self.calculate_message_focus_score(msg, focus);
782 }
783
784 total_score / segment.len() as f32
785 }
786
787 fn calculate_message_focus_score(&self, message: &Message, focus: &ConversationFocus) -> f32 {
789 let content = self.get_message_content(message);
791 let content_lower = content.to_lowercase();
792
793 let mut score: f32 = 0.0;
794
795 if let Some(topic) = &focus.current_topic {
797 let topic_keywords: Vec<&str> = topic.split(", ").collect();
798 for kw in topic_keywords {
799 if content_lower.contains(&kw.to_lowercase()) {
800 score += 0.2;
801 }
802 }
803 }
804
805 if let Some(question) = &focus.current_question {
807 let question_lower = question.to_lowercase();
808 for word in question_lower.split_whitespace() {
809 if word.len() > 3 && content_lower.contains(word) {
810 score += 0.1;
811 }
812 }
813 }
814
815 if let Some(focus_manager) = &self.focus_manager {
817 let relevance = self.calculate_message_focus_relevance(&content, focus_manager);
818 score = score.max(relevance);
819 }
820
821 score.min(1.0)
822 }
823
824 fn compress_inline(&self, messages: &[Message], indices: &[usize]) -> Vec<Message> {
826 let mut result = Vec::new();
827 let mut summary_parts = Vec::new();
828
829 for (i, msg) in messages.iter().enumerate() {
830 if indices.contains(&i) {
831 let content = match &msg.content {
833 crate::providers::MessageContent::Text(text) => text.chars().take(100).collect::<String>(),
834 crate::providers::MessageContent::Blocks(_) => "...".to_string(),
835 };
836 summary_parts.push(content);
837
838 if i == indices[indices.len() - 1] {
839 let summary = format!("[摘要] {}", summary_parts.join(" | "));
841 result.push(Message {
842 role: crate::providers::Role::Assistant,
843 content: crate::providers::MessageContent::Text(summary),
844 });
845 }
846 } else {
847 result.push(msg.clone());
848 }
849 }
850
851 result
852 }
853}
854
855fn estimate_tokens(messages: &[Message]) -> u32 {
857 messages.iter()
858 .map(|m| {
859 let content = match &m.content {
860 crate::providers::MessageContent::Text(text) => text.clone(),
861 crate::providers::MessageContent::Blocks(blocks) => {
862 blocks.iter()
863 .filter_map(|b| {
864 if let crate::providers::ContentBlock::Text { text } = b {
865 Some(text.clone())
866 } else {
867 None
868 }
869 })
870 .collect::<Vec<_>>()
871 .join("\n")
872 }
873 };
874 (content.len() / 4) as u32 + 50 })
877 .sum()
878}
879
880#[cfg(test)]
881mod tests {
882 use super::*;
883
884 #[test]
885 fn test_progressive_config_default() {
886 let config = ProgressiveConfig::default();
887 assert_eq!(config.target_budget, 8000);
888 assert_eq!(config.preserve_last_n, 3);
889 }
890
891 #[test]
892 fn test_compressor_creation() {
893 let compressor = ProgressiveCompressor::default_config();
894 assert!(compressor.focus_manager.is_none());
895 }
896
897 #[test]
898 fn test_stage_ordering() {
899 assert!(CompressionStage::RemoveLowPriority.priority() < CompressionStage::SummarizeMedium.priority());
900 assert!(CompressionStage::SummarizeMedium.priority() < CompressionStage::CompressHighPriority.priority());
901 assert!(CompressionStage::CompressHighPriority.priority() < CompressionStage::EmergencyCompression.priority());
902 }
903
904 #[test]
905 fn test_estimate_tokens() {
906 use crate::providers::{Message, MessageContent, Role};
907
908 let messages = vec![
909 Message {
910 role: Role::User,
911 content: MessageContent::Text("This is a test message".to_string()),
912 },
913 ];
914
915 let tokens = estimate_tokens(&messages);
916 assert!(tokens > 0);
917 }
918}