synaptic-cache 0.4.0

LLM response caching: InMemory, Semantic, CachedChatModel
Documentation
use std::sync::Arc;

use async_trait::async_trait;
use synaptic_core::{ChatModel, ChatRequest, ChatResponse, ChatStream, SynapticError};

use crate::LlmCache;

/// A ChatModel wrapper that caches responses using an [`LlmCache`].
///
/// Cache keys are generated by serializing the [`ChatRequest`] to JSON.
/// Streaming (`stream_chat`) delegates directly to the inner model without caching.
pub struct CachedChatModel {
    inner: Arc<dyn ChatModel>,
    cache: Arc<dyn LlmCache>,
}

impl CachedChatModel {
    /// Create a new cached model wrapping the given model and cache.
    pub fn new(inner: Arc<dyn ChatModel>, cache: Arc<dyn LlmCache>) -> Self {
        Self { inner, cache }
    }
}

#[async_trait]
impl ChatModel for CachedChatModel {
    async fn chat(&self, request: ChatRequest) -> Result<ChatResponse, SynapticError> {
        let key = serde_json::to_string(&request)
            .map_err(|e| SynapticError::Cache(format!("failed to serialize request: {e}")))?;

        // Check cache first
        if let Some(cached) = self.cache.get(&key).await? {
            return Ok(cached);
        }

        // Cache miss: call inner model
        let response = self.inner.chat(request).await?;

        // Store in cache
        self.cache.put(&key, &response).await?;

        Ok(response)
    }

    fn stream_chat(&self, request: ChatRequest) -> ChatStream<'_> {
        // Streaming bypasses the cache
        self.inner.stream_chat(request)
    }
}