1use anyhow::{Context, Result};
10use serde::{Deserialize, Serialize};
11use std::collections::HashMap;
12use std::sync::Arc;
13use tokio::sync::RwLock;
14use tracing::{debug, info, warn};
15
16use super::EncoderConfig;
17
18const MAX_FILE_SIZE_BYTES: usize = 100 * 1024;
20
21const APPROX_CHARS_PER_TOKEN: f32 = 4.0;
23
24pub struct SemanticEncoder {
26 config: EncoderConfig,
27 tokenizer: Arc<RwLock<Option<CodeTokenizer>>>,
28 model: Arc<RwLock<Option<CodeModel>>>,
29 cache: Arc<RwLock<EmbeddingCache>>,
30}
31
32pub struct CodeTokenizer {
34 model_type: String,
35 max_tokens: usize,
36 vocab_size: usize,
37 special_tokens: HashMap<String, u32>,
38}
39
40pub struct CodeModel {
42 model_type: String,
43 embedding_dim: usize,
44 device: String,
45 initialized: bool,
46}
47
48#[derive(Default)]
50pub struct EmbeddingCache {
51 cache: HashMap<String, CachedEmbedding>,
52 access_order: Vec<String>,
53 max_size: usize,
54}
55
56#[derive(Clone)]
57pub struct CachedEmbedding {
58 embedding: Vec<f32>,
59 timestamp: std::time::Instant,
60 access_count: u32,
61}
62
63#[derive(Debug, Clone)]
65pub struct TokenizedCode {
66 pub tokens: Vec<u32>,
67 pub attention_mask: Vec<u32>,
68 pub token_type_ids: Vec<u32>,
69 pub original_length: usize,
70 pub truncated: bool,
71 pub language: Option<String>,
72}
73
74#[derive(Debug, Clone, Serialize, Deserialize)]
76pub struct CodeEmbedding {
77 pub embedding: Vec<f32>,
78 pub dimension: usize,
79 pub model_version: String,
80 pub content_hash: String,
81 pub language: Option<String>,
82 pub token_count: usize,
83 pub inference_time_ms: u64,
84}
85
86impl SemanticEncoder {
87 pub async fn new(config: EncoderConfig) -> Result<Self> {
89 info!("Creating semantic encoder: {}", config.model_type);
90 info!("Max tokens: {}, embedding dim: {}", config.max_tokens, config.embedding_dim);
91
92 Ok(Self {
93 config,
94 tokenizer: Arc::new(RwLock::new(None)),
95 model: Arc::new(RwLock::new(None)),
96 cache: Arc::new(RwLock::new(EmbeddingCache::new(1000))), })
98 }
99
100 pub async fn initialize(&self) -> Result<()> {
102 info!("Initializing semantic encoder: {}", self.config.model_type);
103
104 let tokenizer = self.create_tokenizer().await
106 .context("Failed to create tokenizer")?;
107
108 *self.tokenizer.write().await = Some(tokenizer);
109
110 let model = self.create_model().await
112 .context("Failed to create model")?;
113
114 *self.model.write().await = Some(model);
115
116 info!("Semantic encoder initialized successfully");
117 Ok(())
118 }
119
120 pub async fn encode(&self, content: &str, language: Option<&str>) -> Result<CodeEmbedding> {
122 let start_time = std::time::Instant::now();
123
124 if content.len() > MAX_FILE_SIZE_BYTES {
126 warn!("Content size {}KB exceeds limit {}KB, truncating",
127 content.len() / 1024, MAX_FILE_SIZE_BYTES / 1024);
128 }
129
130 let content_hash = self.hash_content(content);
132
133 if let Some(cached) = self.get_cached_embedding(&content_hash).await {
135 debug!("Cache hit for content hash: {}", content_hash);
136 return Ok(cached);
137 }
138
139 let tokenized = self.tokenize_content(content, language).await
141 .context("Failed to tokenize content")?;
142
143 let embedding_vec = self.generate_embedding(&tokenized).await
145 .context("Failed to generate embedding")?;
146
147 let inference_time = start_time.elapsed().as_millis() as u64;
148
149 let embedding = CodeEmbedding {
151 embedding: embedding_vec,
152 dimension: self.config.embedding_dim,
153 model_version: self.config.model_type.clone(),
154 content_hash: content_hash.clone(),
155 language: language.map(String::from),
156 token_count: tokenized.tokens.len(),
157 inference_time_ms: inference_time,
158 };
159
160 self.cache_embedding(&content_hash, &embedding).await;
162
163 if inference_time > 50 {
165 warn!("Slow inference: {}ms > 50ms target", inference_time);
166 }
167
168 debug!("Encoded {} tokens in {}ms", tokenized.tokens.len(), inference_time);
169
170 Ok(embedding)
171 }
172
173 pub async fn encode_batch(&self, contents: &[(&str, Option<&str>)]) -> Result<Vec<CodeEmbedding>> {
175 let start_time = std::time::Instant::now();
176
177 if contents.len() > self.config.batch_size {
179 warn!("Batch size {} exceeds limit {}, processing in chunks",
180 contents.len(), self.config.batch_size);
181 }
182
183 let mut results = Vec::with_capacity(contents.len());
184
185 for chunk in contents.chunks(self.config.batch_size) {
187 let chunk_results = self.encode_chunk(chunk).await?;
188 results.extend(chunk_results);
189 }
190
191 let total_time = start_time.elapsed().as_millis() as u64;
192 let avg_time_per_item = total_time / contents.len() as u64;
193
194 info!("Batch encoded {} items in {}ms (avg {}ms/item)",
195 contents.len(), total_time, avg_time_per_item);
196
197 Ok(results)
198 }
199
200 pub fn can_handle_content(&self, content: &str) -> bool {
202 let estimated_tokens = (content.len() as f32 / APPROX_CHARS_PER_TOKEN) as usize;
203 estimated_tokens <= self.config.max_tokens && content.len() <= MAX_FILE_SIZE_BYTES
204 }
205
206 pub async fn get_metrics(&self) -> EncoderMetrics {
208 let cache_guard = self.cache.read().await;
209
210 EncoderMetrics {
211 cache_size: cache_guard.cache.len(),
212 cache_hit_rate: cache_guard.calculate_hit_rate(),
213 max_tokens: self.config.max_tokens,
214 embedding_dim: self.config.embedding_dim,
215 model_type: self.config.model_type.clone(),
216 }
217 }
218
219 async fn create_tokenizer(&self) -> Result<CodeTokenizer> {
222 info!("Loading tokenizer for {}", self.config.model_type);
223
224 let mut special_tokens = HashMap::new();
226 special_tokens.insert("<pad>".to_string(), 0);
227 special_tokens.insert("<unk>".to_string(), 1);
228 special_tokens.insert("<s>".to_string(), 2);
229 special_tokens.insert("</s>".to_string(), 3);
230
231 Ok(CodeTokenizer {
232 model_type: self.config.model_type.clone(),
233 max_tokens: self.config.max_tokens,
234 vocab_size: 50000, special_tokens,
236 })
237 }
238
239 async fn create_model(&self) -> Result<CodeModel> {
240 info!("Loading model {} on device {}", self.config.model_type, self.config.device);
241
242 Ok(CodeModel {
244 model_type: self.config.model_type.clone(),
245 embedding_dim: self.config.embedding_dim,
246 device: self.config.device.clone(),
247 initialized: true,
248 })
249 }
250
251 async fn tokenize_content(&self, content: &str, language: Option<&str>) -> Result<TokenizedCode> {
252 let tokenizer_guard = self.tokenizer.read().await;
253 let tokenizer = tokenizer_guard.as_ref()
254 .ok_or_else(|| anyhow::anyhow!("Tokenizer not initialized"))?;
255
256 let max_chars = (self.config.max_tokens as f32 * APPROX_CHARS_PER_TOKEN) as usize;
258 let content = if content.len() > max_chars {
259 &content[..max_chars]
260 } else {
261 content
262 };
263
264 let tokens = self.mock_tokenize(content, tokenizer).await?;
266 let token_count = tokens.len().min(self.config.max_tokens);
267 let truncated = tokens.len() > self.config.max_tokens;
268
269 Ok(TokenizedCode {
270 tokens: tokens[..token_count].to_vec(),
271 attention_mask: vec![1; token_count],
272 token_type_ids: vec![0; token_count],
273 original_length: content.len(),
274 truncated,
275 language: language.map(String::from),
276 })
277 }
278
279 async fn generate_embedding(&self, tokenized: &TokenizedCode) -> Result<Vec<f32>> {
280 let model_guard = self.model.read().await;
281 let model = model_guard.as_ref()
282 .ok_or_else(|| anyhow::anyhow!("Model not initialized"))?;
283
284 let embedding = self.mock_inference(tokenized, model).await?;
286
287 Ok(embedding)
288 }
289
290 async fn encode_chunk(&self, chunk: &[(&str, Option<&str>)]) -> Result<Vec<CodeEmbedding>> {
291 let mut results = Vec::with_capacity(chunk.len());
292
293 for (content, language) in chunk {
296 let embedding = self.encode(content, *language).await?;
297 results.push(embedding);
298 }
299
300 Ok(results)
301 }
302
303 fn hash_content(&self, content: &str) -> String {
304 use std::collections::hash_map::DefaultHasher;
305 use std::hash::{Hash, Hasher};
306
307 let mut hasher = DefaultHasher::new();
308 content.hash(&mut hasher);
309 self.config.model_type.hash(&mut hasher);
310 format!("{:x}", hasher.finish())
311 }
312
313 async fn get_cached_embedding(&self, hash: &str) -> Option<CodeEmbedding> {
314 let mut cache_guard = self.cache.write().await;
315 cache_guard.get(hash).map(|cached| CodeEmbedding {
316 embedding: cached.embedding.clone(),
317 dimension: self.config.embedding_dim,
318 model_version: self.config.model_type.clone(),
319 content_hash: hash.to_string(),
320 language: None, token_count: 0, inference_time_ms: 0, })
324 }
325
326 async fn cache_embedding(&self, hash: &str, embedding: &CodeEmbedding) {
327 let mut cache_guard = self.cache.write().await;
328 cache_guard.insert(hash.to_string(), CachedEmbedding {
329 embedding: embedding.embedding.clone(),
330 timestamp: std::time::Instant::now(),
331 access_count: 1,
332 });
333 }
334
335 async fn mock_tokenize(&self, content: &str, _tokenizer: &CodeTokenizer) -> Result<Vec<u32>> {
338 let words: Vec<&str> = content.split_whitespace().collect();
340 let mut tokens = Vec::with_capacity(words.len() + 2);
341
342 tokens.push(2); for word in words {
345 let hash = word.bytes().fold(0u32, |acc, b| acc.wrapping_mul(31).wrapping_add(b as u32));
346 tokens.push(hash % 50000); }
348
349 tokens.push(3); Ok(tokens)
352 }
353
354 async fn mock_inference(&self, tokenized: &TokenizedCode, _model: &CodeModel) -> Result<Vec<f32>> {
355 let mut embedding = vec![0.0f32; self.config.embedding_dim];
357
358 for (i, &token) in tokenized.tokens.iter().enumerate() {
359 let idx = (token as usize + i) % self.config.embedding_dim;
360 embedding[idx] += (token as f32) / 50000.0 - 0.5;
361 }
362
363 let norm: f32 = embedding.iter().map(|x| x * x).sum::<f32>().sqrt();
365 if norm > 0.0 {
366 for x in &mut embedding {
367 *x /= norm;
368 }
369 }
370
371 tokio::time::sleep(std::time::Duration::from_millis(5)).await;
373
374 Ok(embedding)
375 }
376}
377
378impl EmbeddingCache {
379 fn new(max_size: usize) -> Self {
380 Self {
381 cache: HashMap::new(),
382 access_order: Vec::new(),
383 max_size,
384 }
385 }
386
387 fn get(&mut self, key: &str) -> Option<&CachedEmbedding> {
388 if let Some(embedding) = self.cache.get_mut(key) {
389 embedding.access_count += 1;
390
391 if let Some(pos) = self.access_order.iter().position(|x| x == key) {
393 self.access_order.remove(pos);
394 }
395 self.access_order.push(key.to_string());
396
397 Some(embedding)
398 } else {
399 None
400 }
401 }
402
403 fn insert(&mut self, key: String, value: CachedEmbedding) {
404 if self.cache.len() >= self.max_size && !self.cache.contains_key(&key) {
406 if let Some(lru_key) = self.access_order.first().cloned() {
407 self.cache.remove(&lru_key);
408 self.access_order.retain(|k| k != &lru_key);
409 }
410 }
411
412 self.cache.insert(key.clone(), value);
414 self.access_order.push(key);
415 }
416
417 fn calculate_hit_rate(&self) -> f32 {
418 if self.cache.is_empty() {
419 return 0.0;
420 }
421
422 let total_accesses: u32 = self.cache.values().map(|e| e.access_count).sum();
423 let hits = total_accesses.saturating_sub(self.cache.len() as u32); if total_accesses == 0 {
426 0.0
427 } else {
428 hits as f32 / total_accesses as f32
429 }
430 }
431}
432
433#[derive(Debug, Clone, Serialize, Deserialize)]
434pub struct EncoderMetrics {
435 pub cache_size: usize,
436 pub cache_hit_rate: f32,
437 pub max_tokens: usize,
438 pub embedding_dim: usize,
439 pub model_type: String,
440}
441
442pub async fn initialize_encoder(config: &EncoderConfig) -> Result<()> {
444 info!("Initializing semantic encoder with config: {:?}", config);
445
446 if config.max_tokens > 4096 {
448 warn!("Max tokens {} exceeds recommended 4096", config.max_tokens);
449 }
450
451 if config.max_tokens < 512 {
452 anyhow::bail!("Max tokens {} too small, minimum 512", config.max_tokens);
453 }
454
455 info!("Encoder initialization complete");
456 Ok(())
457}
458
459#[cfg(test)]
460mod tests {
461 use super::*;
462
463 #[tokio::test]
464 async fn test_encoder_creation() {
465 let config = EncoderConfig {
466 model_type: "codet5-base".to_string(),
467 max_tokens: 2048,
468 embedding_dim: 768,
469 batch_size: 16,
470 device: "cpu".to_string(),
471 };
472
473 let encoder = SemanticEncoder::new(config).await.unwrap();
474 assert!(encoder.can_handle_content("test")); }
476
477 #[tokio::test]
478 async fn test_content_limits() {
479 let config = EncoderConfig {
480 model_type: "codet5-base".to_string(),
481 max_tokens: 2048,
482 embedding_dim: 768,
483 batch_size: 16,
484 device: "cpu".to_string(),
485 };
486
487 let encoder = SemanticEncoder::new(config).await.unwrap();
488
489 let small_content = "def hello(): return 'world'";
491 assert!(encoder.can_handle_content(small_content));
492
493 let large_content = "x".repeat(200_000);
495 assert!(!encoder.can_handle_content(&large_content));
496 }
497
498 #[tokio::test]
499 async fn test_cache_operations() {
500 let mut cache = EmbeddingCache::new(2);
501
502 let embedding1 = CachedEmbedding {
503 embedding: vec![1.0, 2.0],
504 timestamp: std::time::Instant::now(),
505 access_count: 1,
506 };
507
508 let embedding2 = CachedEmbedding {
509 embedding: vec![3.0, 4.0],
510 timestamp: std::time::Instant::now(),
511 access_count: 1,
512 };
513
514 cache.insert("key1".to_string(), embedding1.clone());
516 assert_eq!(cache.cache.len(), 1);
517
518 cache.insert("key2".to_string(), embedding2.clone());
520 assert_eq!(cache.cache.len(), 2);
521
522 let embedding3 = CachedEmbedding {
524 embedding: vec![5.0, 6.0],
525 timestamp: std::time::Instant::now(),
526 access_count: 1,
527 };
528
529 cache.insert("key3".to_string(), embedding3);
530 assert_eq!(cache.cache.len(), 2);
531 assert!(!cache.cache.contains_key("key1")); }
533
534 #[tokio::test]
535 async fn test_encoder_error_handling() {
536 let invalid_config = EncoderConfig {
538 model_type: "invalid-model".to_string(),
539 max_tokens: 0, embedding_dim: 0, batch_size: 0, device: "invalid-device".to_string(),
543 };
544
545 let result = SemanticEncoder::new(invalid_config).await;
546 assert!(result.is_ok() || result.is_err());
548 }
549
550 #[tokio::test]
551 async fn test_batch_encoding() {
552 let config = EncoderConfig {
553 model_type: "codet5-base".to_string(),
554 max_tokens: 2048,
555 embedding_dim: 768,
556 batch_size: 4,
557 device: "cpu".to_string(),
558 };
559
560 let encoder = SemanticEncoder::new(config).await.unwrap();
561 encoder.initialize().await.unwrap();
562
563 let batch_content = vec![
564 "function add(a, b) { return a + b; }",
565 "class User { constructor(name) { this.name = name; } }",
566 "def calculate_sum(numbers): return sum(numbers)",
567 "public static void main(String[] args) { System.out.println(\"Hello\"); }",
568 ];
569
570 for content in batch_content {
572 let result = encoder.encode(content, None).await;
573 assert!(result.is_ok(), "Batch encoding should work for: {}", content);
574 }
575 }
576
577 #[tokio::test]
578 async fn test_cache_hit_rate_calculation() {
579 let mut cache = EmbeddingCache::new(10);
580
581 assert_eq!(cache.calculate_hit_rate(), 0.0);
583
584 let embedding = CachedEmbedding {
585 embedding: vec![1.0, 2.0, 3.0],
586 timestamp: std::time::Instant::now(),
587 access_count: 1,
588 };
589
590 cache.insert("key1".to_string(), embedding.clone());
592 cache.insert("key2".to_string(), CachedEmbedding {
593 access_count: 3,
594 ..embedding.clone()
595 });
596 cache.insert("key3".to_string(), CachedEmbedding {
597 access_count: 5,
598 ..embedding
599 });
600
601 let hit_rate = cache.calculate_hit_rate();
602 assert!(hit_rate >= 0.0 && hit_rate <= 1.0);
603 }
604
605 #[tokio::test]
606 async fn test_content_tokenization_estimation() {
607 let config = EncoderConfig {
608 model_type: "codet5-base".to_string(),
609 max_tokens: 1024,
610 embedding_dim: 512,
611 batch_size: 8,
612 device: "cpu".to_string(),
613 };
614
615 let encoder = SemanticEncoder::new(config).await.unwrap();
616 encoder.initialize().await.unwrap();
617
618 let large_content = "x".repeat(4000); let comment_content = "// ".repeat(100);
621 let test_cases = vec![
622 ("x", true), ("def test(): pass", true), (comment_content.as_str(), true), (large_content.as_str(), true), ];
627
628 for (content, should_handle) in test_cases {
629 let can_handle = encoder.can_handle_content(content);
630 assert_eq!(can_handle, should_handle,
631 "Content handling mismatch for length: {}", content.len());
632 }
633 }
634
635 #[tokio::test]
636 async fn test_encoder_with_different_languages() {
637 let config = EncoderConfig {
638 model_type: "codet5-base".to_string(),
639 max_tokens: 2048,
640 embedding_dim: 768,
641 batch_size: 16,
642 device: "cpu".to_string(),
643 };
644
645 let encoder = SemanticEncoder::new(config).await.unwrap();
646 encoder.initialize().await.unwrap();
647
648 let language_samples = vec![
649 ("rust", "fn main() { println!(\"Hello, world!\"); }"),
650 ("python", "def hello_world():\n print('Hello, world!')"),
651 ("javascript", "function helloWorld() { console.log('Hello, world!'); }"),
652 ("go", "package main\n\nfunc main() { fmt.Println(\"Hello, world!\") }"),
653 ("java", "public class HelloWorld {\n public static void main(String[] args) {\n System.out.println(\"Hello, world!\");\n }\n}"),
654 ];
655
656 for (language, code) in language_samples {
657 let result = encoder.encode(code, Some(language)).await;
658 assert!(result.is_ok(), "Should handle {} code: {}", language, code);
659
660 if let Ok(embedding) = result {
661 assert!(!embedding.embedding.is_empty());
662 assert!(embedding.dimension > 0);
663 }
664 }
665 }
666
667 #[tokio::test]
668 async fn test_concurrent_encoding() {
669 let config = EncoderConfig {
670 model_type: "codet5-base".to_string(),
671 max_tokens: 2048,
672 embedding_dim: 768,
673 batch_size: 4,
674 device: "cpu".to_string(),
675 };
676
677 let encoder = Arc::new(SemanticEncoder::new(config).await.unwrap());
678 encoder.initialize().await.unwrap();
679
680 let mut handles = vec![];
682
683 for i in 0..8 {
684 let encoder_clone = encoder.clone();
685 let handle = tokio::spawn(async move {
686 let content = format!("function test_{}() {{ return {}; }}", i, i);
687 encoder_clone.encode(&content, Some("javascript")).await
688 });
689 handles.push(handle);
690 }
691
692 let mut successful = 0;
694 for handle in handles {
695 if let Ok(result) = handle.await {
696 if result.is_ok() {
697 successful += 1;
698 }
699 }
700 }
701
702 assert!(successful >= 6, "Most concurrent encodings should succeed");
703 }
704
705 #[tokio::test]
706 async fn test_encoder_metrics() {
707 let config = EncoderConfig {
708 model_type: "test-model".to_string(),
709 max_tokens: 1024,
710 embedding_dim: 256,
711 batch_size: 8,
712 device: "cpu".to_string(),
713 };
714
715 let encoder = SemanticEncoder::new(config.clone()).await.unwrap();
716 let metrics = encoder.get_metrics().await;
717
718 assert_eq!(metrics.max_tokens, config.max_tokens);
719 assert_eq!(metrics.embedding_dim, config.embedding_dim);
720 assert_eq!(metrics.model_type, config.model_type);
721 assert!(metrics.cache_hit_rate >= 0.0);
722 }
723
724 #[tokio::test]
725 async fn test_cache_eviction_policy() {
726 let mut cache = EmbeddingCache::new(3);
727
728 for i in 0..3 {
730 let embedding = CachedEmbedding {
731 embedding: vec![i as f32; 5],
732 timestamp: std::time::Instant::now(),
733 access_count: 1,
734 };
735 cache.insert(format!("key{}", i), embedding);
736 }
737
738 assert_eq!(cache.cache.len(), 3);
739
740 if let Some(entry) = cache.cache.get_mut("key1") {
742 entry.access_count += 1;
743 }
744
745 let new_embedding = CachedEmbedding {
747 embedding: vec![99.0; 5],
748 timestamp: std::time::Instant::now(),
749 access_count: 1,
750 };
751 cache.insert("new_key".to_string(), new_embedding);
752
753 assert_eq!(cache.cache.len(), 3);
754 assert!(cache.cache.contains_key("new_key"));
755 }
756
757 #[test]
758 fn test_tokenizer_creation() {
759 let tokenizer = CodeTokenizer {
760 model_type: "test-model".to_string(),
761 max_tokens: 1024,
762 vocab_size: 50000,
763 special_tokens: HashMap::new(),
764 };
765
766 assert_eq!(tokenizer.model_type, "test-model");
767 assert_eq!(tokenizer.max_tokens, 1024);
768 assert_eq!(tokenizer.vocab_size, 50000);
769 }
770
771 #[test]
772 fn test_code_model_initialization() {
773 let model = CodeModel {
774 model_type: "codet5-small".to_string(),
775 embedding_dim: 512,
776 device: "cpu".to_string(),
777 initialized: false,
778 };
779
780 assert_eq!(model.embedding_dim, 512);
781 assert_eq!(model.device, "cpu");
782 assert!(!model.initialized);
783 }
784
785 #[tokio::test]
786 async fn test_initialization_validation() {
787 let valid_config = EncoderConfig {
789 model_type: "codet5-base".to_string(),
790 max_tokens: 2048,
791 embedding_dim: 768,
792 batch_size: 16,
793 device: "cpu".to_string(),
794 };
795
796 let result = initialize_encoder(&valid_config).await;
797 assert!(result.is_ok(), "Valid configuration should initialize successfully");
798
799 let invalid_config = EncoderConfig {
801 max_tokens: 256, ..valid_config.clone()
803 };
804
805 let result = initialize_encoder(&invalid_config).await;
806 assert!(result.is_err(), "Configuration with too few tokens should fail");
807
808 let warning_config = EncoderConfig {
810 max_tokens: 8192, ..valid_config
812 };
813
814 let result = initialize_encoder(&warning_config).await;
815 assert!(result.is_ok(), "Large token configuration should succeed with warning");
816 }
817
818 #[test]
819 fn test_cache_edge_cases() {
820 let mut cache = EmbeddingCache::new(1);
821
822 assert_eq!(cache.cache.len(), 0);
824 assert_eq!(cache.calculate_hit_rate(), 0.0);
825
826 let embedding = CachedEmbedding {
828 embedding: vec![1.0, 2.0],
829 timestamp: std::time::Instant::now(),
830 access_count: 1,
831 };
832
833 cache.insert("single".to_string(), embedding);
834 assert_eq!(cache.cache.len(), 1);
835
836 let zero_cache = EmbeddingCache::new(0);
838 assert_eq!(zero_cache.max_size, 0);
839 }
840}