| |
| |
| |
| package semanticcache |
|
|
| import ( |
| "context" |
| "encoding/json" |
| "fmt" |
| "strconv" |
| "sync" |
| "time" |
|
|
| "github.com/google/uuid" |
|
|
| bifrost "github.com/maximhq/bifrost/core" |
| "github.com/maximhq/bifrost/core/schemas" |
| "github.com/maximhq/bifrost/framework" |
| "github.com/maximhq/bifrost/framework/vectorstore" |
| ) |
|
|
| |
| |
| |
| type Config struct { |
| |
| Provider schemas.ModelProvider `json:"provider"` |
| Keys []schemas.Key `json:"keys"` |
| EmbeddingModel string `json:"embedding_model,omitempty"` |
|
|
| |
| CleanUpOnShutdown bool `json:"cleanup_on_shutdown,omitempty"` |
| TTL time.Duration `json:"ttl,omitempty"` |
| Threshold float64 `json:"threshold,omitempty"` |
| VectorStoreNamespace string `json:"vector_store_namespace,omitempty"` |
| Dimension int `json:"dimension"` |
|
|
| |
| DefaultCacheKey string `json:"default_cache_key,omitempty"` |
| ConversationHistoryThreshold int `json:"conversation_history_threshold,omitempty"` |
| CacheByModel *bool `json:"cache_by_model,omitempty"` |
| CacheByProvider *bool `json:"cache_by_provider,omitempty"` |
| ExcludeSystemPrompt *bool `json:"exclude_system_prompt,omitempty"` |
| } |
|
|
| |
| |
| func (c *Config) UnmarshalJSON(data []byte) error { |
| |
| type TempConfig struct { |
| Provider string `json:"provider"` |
| Keys []schemas.Key `json:"keys"` |
| EmbeddingModel string `json:"embedding_model,omitempty"` |
| CleanUpOnShutdown bool `json:"cleanup_on_shutdown,omitempty"` |
| Dimension int `json:"dimension"` |
| TTL interface{} `json:"ttl,omitempty"` |
| Threshold float64 `json:"threshold,omitempty"` |
| VectorStoreNamespace string `json:"vector_store_namespace,omitempty"` |
| DefaultCacheKey string `json:"default_cache_key,omitempty"` |
| ConversationHistoryThreshold int `json:"conversation_history_threshold,omitempty"` |
| CacheByModel *bool `json:"cache_by_model,omitempty"` |
| CacheByProvider *bool `json:"cache_by_provider,omitempty"` |
| ExcludeSystemPrompt *bool `json:"exclude_system_prompt,omitempty"` |
| } |
|
|
| var temp TempConfig |
| if err := json.Unmarshal(data, &temp); err != nil { |
| return fmt.Errorf("failed to unmarshal config: %w", err) |
| } |
|
|
| |
| c.Provider = schemas.ModelProvider(temp.Provider) |
| c.Keys = temp.Keys |
| c.EmbeddingModel = temp.EmbeddingModel |
| c.CleanUpOnShutdown = temp.CleanUpOnShutdown |
| c.Dimension = temp.Dimension |
| c.CacheByModel = temp.CacheByModel |
| c.CacheByProvider = temp.CacheByProvider |
| c.VectorStoreNamespace = temp.VectorStoreNamespace |
| c.ConversationHistoryThreshold = temp.ConversationHistoryThreshold |
| c.Threshold = temp.Threshold |
| c.DefaultCacheKey = temp.DefaultCacheKey |
| c.ExcludeSystemPrompt = temp.ExcludeSystemPrompt |
| |
| if temp.TTL != nil { |
| switch v := temp.TTL.(type) { |
| case string: |
| |
| duration, err := time.ParseDuration(v) |
| if err != nil { |
| return fmt.Errorf("failed to parse TTL duration string '%s': %w", v, err) |
| } |
| c.TTL = duration |
| case int: |
| |
| c.TTL = time.Duration(v) * time.Second |
| default: |
| |
| ttlStr := fmt.Sprintf("%v", v) |
| if seconds, err := strconv.ParseFloat(ttlStr, 64); err == nil { |
| c.TTL = time.Duration(seconds * float64(time.Second)) |
| } else { |
| return fmt.Errorf("unsupported TTL type: %T (value: %v)", v, v) |
| } |
| } |
| } |
|
|
| return nil |
| } |
|
|
| |
| type StreamChunk struct { |
| Timestamp time.Time |
| Response *schemas.BifrostResponse |
| FinishReason *string |
| } |
|
|
| |
| type StreamAccumulator struct { |
| RequestID string |
| StorageID string |
| Chunks []*StreamChunk |
| IsComplete bool |
| HasError bool |
| FinalTimestamp time.Time |
| Embedding []float32 |
| Metadata map[string]interface{} |
| TTL time.Duration |
| mu sync.Mutex |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| type Plugin struct { |
| store vectorstore.VectorStore |
| config *Config |
| logger schemas.Logger |
| client *bifrost.Bifrost |
| streamAccumulators sync.Map |
| waitGroup sync.WaitGroup |
| } |
|
|
| |
| const ( |
| PluginName string = "semantic_cache" |
| DefaultVectorStoreNamespace string = "BifrostSemanticCachePlugin" |
| PluginLoggerPrefix string = "[Semantic Cache]" |
| CacheConnectionTimeout time.Duration = 5 * time.Second |
| CreateNamespaceTimeout time.Duration = 30 * time.Second |
| CacheSetTimeout time.Duration = 30 * time.Second |
| DefaultCacheTTL time.Duration = 5 * time.Minute |
| DefaultCacheThreshold float64 = 0.8 |
| DefaultConversationHistoryThreshold int = 3 |
| ) |
|
|
| var SelectFields = []string{"request_hash", "response", "stream_chunks", "expires_at", "cache_key", "provider", "model"} |
|
|
| var VectorStoreProperties = map[string]vectorstore.VectorStoreProperties{ |
| "request_hash": { |
| DataType: vectorstore.VectorStorePropertyTypeString, |
| Description: "The hash of the request", |
| }, |
| "response": { |
| DataType: vectorstore.VectorStorePropertyTypeString, |
| Description: "The response from the provider", |
| }, |
| "stream_chunks": { |
| DataType: vectorstore.VectorStorePropertyTypeStringArray, |
| Description: "The stream chunks from the provider", |
| }, |
| "expires_at": { |
| DataType: vectorstore.VectorStorePropertyTypeInteger, |
| Description: "The expiration time of the cache entry", |
| }, |
| "cache_key": { |
| DataType: vectorstore.VectorStorePropertyTypeString, |
| Description: "The cache key from the request", |
| }, |
| "provider": { |
| DataType: vectorstore.VectorStorePropertyTypeString, |
| Description: "The provider used for the request", |
| }, |
| "model": { |
| DataType: vectorstore.VectorStorePropertyTypeString, |
| Description: "The model used for the request", |
| }, |
| "params_hash": { |
| DataType: vectorstore.VectorStorePropertyTypeString, |
| Description: "The hash of the parameters used for the request", |
| }, |
| "from_bifrost_semantic_cache_plugin": { |
| DataType: vectorstore.VectorStorePropertyTypeBoolean, |
| Description: "Whether the cache entry was created by the BifrostSemanticCachePlugin", |
| }, |
| } |
|
|
| type PluginAccount struct { |
| provider schemas.ModelProvider |
| keys []schemas.Key |
| } |
|
|
| func (pa *PluginAccount) GetConfiguredProviders() ([]schemas.ModelProvider, error) { |
| return []schemas.ModelProvider{pa.provider}, nil |
| } |
|
|
| func (pa *PluginAccount) GetKeysForProvider(ctx context.Context, providerKey schemas.ModelProvider) ([]schemas.Key, error) { |
| return pa.keys, nil |
| } |
|
|
| func (pa *PluginAccount) GetConfigForProvider(providerKey schemas.ModelProvider) (*schemas.ProviderConfig, error) { |
| return &schemas.ProviderConfig{ |
| NetworkConfig: schemas.DefaultNetworkConfig, |
| ConcurrencyAndBufferSize: schemas.DefaultConcurrencyAndBufferSize, |
| }, nil |
| } |
|
|
| func (pa *PluginAccount) GetGlobalKeys(ctx context.Context) ([]schemas.Key, error) { |
| return nil, nil |
| } |
|
|
| |
| var Dependencies []framework.FrameworkDependency = []framework.FrameworkDependency{framework.FrameworkDependencyVectorStore} |
|
|
| |
| |
| var ProvidersWithEmbeddingSupport = map[schemas.ModelProvider]bool{ |
| schemas.OpenAI: true, |
| schemas.Azure: true, |
| schemas.Bedrock: true, |
| schemas.Cohere: true, |
| schemas.Gemini: true, |
| schemas.Vertex: true, |
| schemas.Mistral: true, |
| schemas.Ollama: true, |
| schemas.Nebius: true, |
| schemas.HuggingFace: true, |
| schemas.SGL: true, |
| } |
|
|
| const ( |
| CacheKey schemas.BifrostContextKey = "semantic_cache_key" |
| CacheTTLKey schemas.BifrostContextKey = "semantic_cache_ttl" |
| CacheThresholdKey schemas.BifrostContextKey = "semantic_cache_threshold" |
| CacheTypeKey schemas.BifrostContextKey = "semantic_cache_cache_type" |
| CacheNoStoreKey schemas.BifrostContextKey = "semantic_cache_no_store" |
|
|
| |
| requestIDKey schemas.BifrostContextKey = "semantic_cache_request_id" |
| requestStorageIDKey schemas.BifrostContextKey = "semantic_cache_request_storage_id" |
| requestHashKey schemas.BifrostContextKey = "semantic_cache_request_hash" |
| requestEmbeddingKey schemas.BifrostContextKey = "semantic_cache_embedding" |
| requestEmbeddingTokensKey schemas.BifrostContextKey = "semantic_cache_embedding_tokens" |
| requestParamsHashKey schemas.BifrostContextKey = "semantic_cache_params_hash" |
| requestModelKey schemas.BifrostContextKey = "semantic_cache_model" |
| requestProviderKey schemas.BifrostContextKey = "semantic_cache_provider" |
| isCacheHitKey schemas.BifrostContextKey = "semantic_cache_is_cache_hit" |
| cacheHitTypeKey schemas.BifrostContextKey = "semantic_cache_cache_hit_type" |
| ) |
|
|
| type CacheType string |
|
|
| const ( |
| CacheTypeDirect CacheType = "direct" |
| CacheTypeSemantic CacheType = "semantic" |
| ) |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| func Init(ctx context.Context, config *Config, logger schemas.Logger, store vectorstore.VectorStore) (schemas.LLMPlugin, error) { |
| if config == nil { |
| return nil, fmt.Errorf("config is required") |
| } |
| if store == nil { |
| return nil, fmt.Errorf("store is required") |
| } |
| |
| if config.VectorStoreNamespace == "" { |
| logger.Debug(PluginLoggerPrefix + " Vector store namespace is not set, using default of " + DefaultVectorStoreNamespace) |
| config.VectorStoreNamespace = DefaultVectorStoreNamespace |
| } |
| if config.TTL == 0 { |
| logger.Debug(PluginLoggerPrefix + " TTL is not set, using default of 5 minutes") |
| config.TTL = DefaultCacheTTL |
| } |
| if config.Threshold == 0 { |
| logger.Debug(PluginLoggerPrefix + " Threshold is not set, using default of " + strconv.FormatFloat(DefaultCacheThreshold, 'f', -1, 64)) |
| config.Threshold = DefaultCacheThreshold |
| } |
| if config.ConversationHistoryThreshold == 0 { |
| logger.Debug(PluginLoggerPrefix + " Conversation history threshold is not set, using default of " + strconv.Itoa(DefaultConversationHistoryThreshold)) |
| config.ConversationHistoryThreshold = DefaultConversationHistoryThreshold |
| } |
|
|
| |
| if config.CacheByModel == nil { |
| config.CacheByModel = bifrost.Ptr(true) |
| } |
| if config.CacheByProvider == nil { |
| config.CacheByProvider = bifrost.Ptr(true) |
| } |
|
|
| plugin := &Plugin{ |
| store: store, |
| config: config, |
| logger: logger, |
| waitGroup: sync.WaitGroup{}, |
| } |
|
|
| if config.Provider == "" && config.Dimension == 1 { |
| logger.Info(PluginLoggerPrefix + " Starting in direct-only mode (dimension=1, no embedding provider)") |
| } else if config.Provider == "" || len(config.Keys) == 0 { |
| logger.Warn(PluginLoggerPrefix + " Incomplete semantic mode config: missing provider or keys, falling back to direct search only") |
| } else { |
| |
| if bifrost.IsStandardProvider(config.Provider) && !ProvidersWithEmbeddingSupport[config.Provider] { |
| return nil, fmt.Errorf("provider '%s' does not support embedding operations required for semantic cache. Supported providers: openai, azure, bedrock, cohere, gemini, vertex, mistral, ollama, nebius, huggingface, sgl. Note: custom providers based on embedding-capable providers are also supported", config.Provider) |
| } |
|
|
| bifrost, err := bifrost.Init(ctx, schemas.BifrostConfig{ |
| Logger: logger, |
| Account: &PluginAccount{ |
| provider: config.Provider, |
| keys: config.Keys, |
| }, |
| }) |
| if err != nil { |
| return nil, fmt.Errorf("failed to initialize bifrost for semantic cache: %w", err) |
| } |
|
|
| plugin.client = bifrost |
| } |
|
|
| createCtx, cancel := context.WithTimeout(ctx, CreateNamespaceTimeout) |
| defer cancel() |
| if err := store.CreateNamespace(createCtx, config.VectorStoreNamespace, config.Dimension, VectorStoreProperties); err != nil { |
| return nil, fmt.Errorf("failed to create namespace for semantic cache: %w", err) |
| } |
|
|
| return plugin, nil |
| } |
|
|
| |
| |
| |
| |
| |
| func (plugin *Plugin) GetName() string { |
| return PluginName |
| } |
|
|
| |
| func (plugin *Plugin) HTTPTransportPreHook(ctx *schemas.BifrostContext, req *schemas.HTTPRequest) (*schemas.HTTPResponse, error) { |
| return nil, nil |
| } |
|
|
| |
| func (plugin *Plugin) HTTPTransportPostHook(ctx *schemas.BifrostContext, req *schemas.HTTPRequest, resp *schemas.HTTPResponse) error { |
| return nil |
| } |
|
|
| |
| func (plugin *Plugin) HTTPTransportStreamChunkHook(ctx *schemas.BifrostContext, req *schemas.HTTPRequest, chunk *schemas.BifrostStreamChunk) (*schemas.BifrostStreamChunk, error) { |
| return chunk, nil |
| } |
|
|
| func (plugin *Plugin) clearRequestScopedContext(ctx *schemas.BifrostContext) { |
| ctx.ClearValue(requestIDKey) |
| ctx.ClearValue(requestStorageIDKey) |
| ctx.ClearValue(requestHashKey) |
| ctx.ClearValue(requestParamsHashKey) |
| ctx.ClearValue(requestModelKey) |
| ctx.ClearValue(requestProviderKey) |
| ctx.ClearValue(requestEmbeddingKey) |
| ctx.ClearValue(requestEmbeddingTokensKey) |
| ctx.ClearValue(isCacheHitKey) |
| ctx.ClearValue(cacheHitTypeKey) |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| func (plugin *Plugin) PreLLMHook(ctx *schemas.BifrostContext, req *schemas.BifrostRequest) (*schemas.BifrostRequest, *schemas.LLMPluginShortCircuit, error) { |
| provider, model, _ := req.GetRequestFields() |
| |
| var cacheKey string |
| var ok bool |
|
|
| cacheKey, ok = ctx.Value(CacheKey).(string) |
| if !ok || cacheKey == "" { |
| if plugin.config.DefaultCacheKey != "" { |
| cacheKey = plugin.config.DefaultCacheKey |
| plugin.logger.Debug(PluginLoggerPrefix + " Using default cache key: " + cacheKey) |
| } else { |
| plugin.logger.Debug(PluginLoggerPrefix + " No cache key found in context, continuing without caching") |
| return req, nil, nil |
| } |
| } |
|
|
| |
| plugin.clearRequestScopedContext(ctx) |
|
|
| if !isSemanticCacheSupportedRequestType(req.RequestType) { |
| plugin.logger.Debug(PluginLoggerPrefix + " Skipping caching for unsupported request type: " + string(req.RequestType)) |
| return req, nil, nil |
| } |
|
|
| if plugin.isConversationHistoryThresholdExceeded(req) { |
| plugin.logger.Debug(PluginLoggerPrefix + " Skipping caching for request with conversation history threshold exceeded") |
| return req, nil, nil |
| } |
|
|
| |
| requestID := uuid.New().String() |
|
|
| |
| ctx.SetValue(requestIDKey, requestID) |
| ctx.SetValue(requestModelKey, model) |
| ctx.SetValue(requestProviderKey, provider) |
|
|
| performDirectSearch, performSemanticSearch := true, true |
| if ctx.Value(CacheTypeKey) != nil { |
| cacheTypeVal, ok := ctx.Value(CacheTypeKey).(CacheType) |
| if !ok { |
| plugin.logger.Warn(PluginLoggerPrefix + " Cache type is not a CacheType, using all available cache types") |
| } else { |
| performDirectSearch = cacheTypeVal == CacheTypeDirect |
| performSemanticSearch = cacheTypeVal == CacheTypeSemantic |
| } |
| } |
|
|
| if performDirectSearch { |
| shortCircuit, err := plugin.performDirectSearch(ctx, req, cacheKey) |
| if err != nil { |
| plugin.logger.Warn(PluginLoggerPrefix + " Direct search failed: " + err.Error() + " (" + describeRequestShape(req) + ")") |
| |
| shortCircuit = nil |
| } |
|
|
| if shortCircuit != nil { |
| return req, shortCircuit, nil |
| } |
| } |
|
|
| if performSemanticSearch && plugin.client != nil { |
| if req.EmbeddingRequest != nil || req.TranscriptionRequest != nil { |
| plugin.logger.Debug(PluginLoggerPrefix + " Skipping semantic search for embedding/transcription input") |
| |
| |
| if plugin.store.RequiresVectors() && plugin.config.Dimension > 0 { |
| zeroVector := make([]float32, plugin.config.Dimension) |
| ctx.SetValue(requestEmbeddingKey, zeroVector) |
| plugin.logger.Debug(PluginLoggerPrefix + " Using zero vector placeholder for embedding/transcription request storage") |
| } |
| return req, nil, nil |
| } |
|
|
| |
| shortCircuit, err := plugin.performSemanticSearch(ctx, req, cacheKey) |
| if err != nil { |
| plugin.logger.Debug(PluginLoggerPrefix + " Semantic search skipped: " + err.Error() + " (" + describeRequestShape(req) + ")") |
| return req, nil, nil |
| } |
|
|
| if shortCircuit != nil { |
| return req, shortCircuit, nil |
| } |
| } else if !performSemanticSearch && plugin.store.RequiresVectors() && plugin.client != nil { |
| |
| |
| if req.EmbeddingRequest != nil || req.TranscriptionRequest != nil { |
| plugin.logger.Debug(PluginLoggerPrefix + " Skipping embedding generation for embedding/transcription input") |
| |
| |
| if plugin.config.Dimension > 0 { |
| zeroVector := make([]float32, plugin.config.Dimension) |
| ctx.SetValue(requestEmbeddingKey, zeroVector) |
| plugin.logger.Debug(PluginLoggerPrefix + " Using zero vector placeholder for embedding/transcription request storage") |
| } |
| return req, nil, nil |
| } |
|
|
| |
| |
| if plugin.config.Dimension > 0 { |
| zeroVector := make([]float32, plugin.config.Dimension) |
| ctx.SetValue(requestEmbeddingKey, zeroVector) |
| plugin.logger.Debug(PluginLoggerPrefix + " Using zero vector for direct-only cache storage (preserves isolation)") |
| } |
| } |
|
|
| return req, nil, nil |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| func (plugin *Plugin) PostLLMHook(ctx *schemas.BifrostContext, res *schemas.BifrostResponse, bifrostErr *schemas.BifrostError) (*schemas.BifrostResponse, *schemas.BifrostError, error) { |
| if bifrostErr != nil { |
| return res, bifrostErr, nil |
| } |
|
|
| |
| if isLargePayload, ok := ctx.Value(schemas.BifrostContextKeyLargePayloadMode).(bool); ok && isLargePayload { |
| plugin.logger.Debug(PluginLoggerPrefix + " Skipping semantic cache for large payload request") |
| return res, nil, nil |
| } |
| if isLargeResponse, ok := ctx.Value(schemas.BifrostContextKeyLargeResponseMode).(bool); ok && isLargeResponse { |
| plugin.logger.Debug(PluginLoggerPrefix + " Skipping semantic cache for large payload response") |
| return res, nil, nil |
| } |
|
|
| isCacheHit := ctx.Value(isCacheHitKey) |
| if isCacheHit != nil { |
| isCacheHitValue, ok := isCacheHit.(bool) |
| if ok && isCacheHitValue { |
| return res, nil, nil |
| } |
| } |
|
|
| |
| noStore := ctx.Value(CacheNoStoreKey) |
| if noStore != nil { |
| noStoreValue, ok := noStore.(bool) |
| if ok && noStoreValue { |
| plugin.logger.Debug(PluginLoggerPrefix + " Caching is explicitly disabled for this request, continuing without caching") |
| return res, nil, nil |
| } |
| } |
|
|
| |
| cacheKey, ok := ctx.Value(CacheKey).(string) |
| if !ok || cacheKey == "" { |
| if plugin.config.DefaultCacheKey != "" { |
| cacheKey = plugin.config.DefaultCacheKey |
| } else { |
| return res, nil, nil |
| } |
| } |
|
|
| |
| requestID, ok := ctx.Value(requestIDKey).(string) |
| if !ok { |
| return res, nil, nil |
| } |
| storageID := requestID |
| |
| |
| |
| if v, ok := ctx.Value(requestStorageIDKey).(string); ok && v != "" { |
| storageID = v |
| } |
| |
| var embedding []float32 |
| var hash string |
| var shouldStoreEmbeddings = true |
| var shouldStoreHash = true |
|
|
| if ctx.Value(CacheTypeKey) != nil { |
| cacheTypeVal, ok := ctx.Value(CacheTypeKey).(CacheType) |
| if ok { |
| if cacheTypeVal == CacheTypeDirect { |
| |
| |
| if plugin.store.RequiresVectors() { |
| |
| |
| plugin.logger.Debug(PluginLoggerPrefix + " Vector store requires vectors, keeping embedding generation enabled for storage") |
| } else { |
| shouldStoreEmbeddings = false |
| plugin.logger.Debug(PluginLoggerPrefix + " Skipping embedding operations for direct-only cache type") |
| } |
| } else if cacheTypeVal == CacheTypeSemantic { |
| shouldStoreHash = false |
| plugin.logger.Debug(PluginLoggerPrefix + " Skipping hash operations for semantic cache type") |
| } |
| } |
| } |
|
|
| if shouldStoreHash { |
| |
| hash, ok = ctx.Value(requestHashKey).(string) |
| if !ok { |
| plugin.logger.Warn(PluginLoggerPrefix + " Hash is not a string. Continuing without caching") |
| return res, nil, nil |
| } |
| } |
|
|
| extraFields := res.GetExtraFields() |
| requestType := extraFields.RequestType |
|
|
| |
| |
| |
| isEmbeddingOrTranscription := requestType == schemas.EmbeddingRequest || requestType == schemas.TranscriptionRequest |
| needsEmbedding := shouldStoreEmbeddings && !isEmbeddingOrTranscription |
| needsZeroVector := isEmbeddingOrTranscription && plugin.store.RequiresVectors() |
|
|
| if needsEmbedding || needsZeroVector { |
| embeddingValue := ctx.Value(requestEmbeddingKey) |
| if embeddingValue != nil { |
| embedding, ok = embeddingValue.([]float32) |
| if !ok { |
| plugin.logger.Warn(PluginLoggerPrefix + " Embedding is not a []float32, continuing without caching") |
| return res, nil, nil |
| } |
| } |
| |
| |
| } |
|
|
| |
| provider, ok := ctx.Value(requestProviderKey).(schemas.ModelProvider) |
| if !ok { |
| plugin.logger.Warn(PluginLoggerPrefix + " Provider is not a schemas.ModelProvider, continuing without caching") |
| return res, nil, nil |
| } |
|
|
| |
| model, ok := ctx.Value(requestModelKey).(string) |
| if !ok { |
| plugin.logger.Warn(PluginLoggerPrefix + " Model is not a string, continuing without caching") |
| return res, nil, nil |
| } |
|
|
| isFinalChunk := bifrost.IsFinalChunk(ctx) |
|
|
| |
| inputTokens, ok := ctx.Value(requestEmbeddingTokensKey).(int) |
| if ok { |
| isStreamRequest := bifrost.IsStreamRequestType(requestType) |
|
|
| if !isStreamRequest || (isStreamRequest && isFinalChunk) { |
| if extraFields.CacheDebug == nil { |
| extraFields.CacheDebug = &schemas.BifrostCacheDebug{} |
| } |
| extraFields.CacheDebug.CacheHit = false |
| extraFields.CacheDebug.ProviderUsed = bifrost.Ptr(string(plugin.config.Provider)) |
| extraFields.CacheDebug.ModelUsed = bifrost.Ptr(plugin.config.EmbeddingModel) |
| extraFields.CacheDebug.InputTokens = &inputTokens |
| } |
| } |
|
|
| cacheTTL := plugin.config.TTL |
|
|
| ttlValue := ctx.Value(CacheTTLKey) |
| if ttlValue != nil { |
| |
| ttl, ok := ttlValue.(time.Duration) |
| if !ok { |
| plugin.logger.Warn(PluginLoggerPrefix + " TTL is not a time.Duration, using default TTL") |
| } else { |
| cacheTTL = ttl |
| } |
| } |
|
|
| |
| |
| paramsHash, _ := ctx.Value(requestParamsHashKey).(string) |
|
|
| |
| plugin.waitGroup.Add(1) |
| go func() { |
| defer plugin.waitGroup.Done() |
| |
| cacheCtx, cancel := context.WithTimeout(context.Background(), CacheSetTimeout) |
| defer cancel() |
|
|
| |
| unifiedMetadata := plugin.buildUnifiedMetadata(provider, model, paramsHash, hash, cacheKey, cacheTTL) |
|
|
| |
| |
| embeddingToStore := embedding |
| if !shouldStoreEmbeddings { |
| embeddingToStore = nil |
| } |
|
|
| if bifrost.IsStreamRequestType(requestType) { |
| if err := plugin.addStreamingResponse(cacheCtx, requestID, storageID, res, bifrostErr, embeddingToStore, unifiedMetadata, cacheTTL, isFinalChunk); err != nil { |
| plugin.logger.Warn("%s Failed to cache streaming response: %v", PluginLoggerPrefix, err) |
| } |
| } else { |
| if err := plugin.addSingleResponse(cacheCtx, storageID, res, embeddingToStore, unifiedMetadata, cacheTTL); err != nil { |
| plugin.logger.Warn("%s Failed to cache single response: %v", PluginLoggerPrefix, err) |
| } |
| } |
| }() |
|
|
| return res, nil, nil |
| } |
|
|
| |
| |
| func (plugin *Plugin) WaitForPendingOperations() { |
| plugin.waitGroup.Wait() |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| func (plugin *Plugin) Cleanup() error { |
| plugin.waitGroup.Wait() |
|
|
| |
| plugin.cleanupOldStreamAccumulators() |
|
|
| |
| if plugin.client != nil { |
| plugin.client.Shutdown() |
| } |
|
|
| |
| if !plugin.config.CleanUpOnShutdown { |
| plugin.logger.Debug(PluginLoggerPrefix + " Cleanup on shutdown is disabled, skipping cache cleanup") |
| return nil |
| } |
|
|
| |
| ctx, cancel := context.WithTimeout(context.Background(), CacheSetTimeout) |
| defer cancel() |
|
|
| plugin.logger.Debug(PluginLoggerPrefix + " Starting cleanup of cache entries...") |
|
|
| |
| queries := []vectorstore.Query{ |
| { |
| Field: "from_bifrost_semantic_cache_plugin", |
| Operator: vectorstore.QueryOperatorEqual, |
| Value: true, |
| }, |
| } |
|
|
| results, err := plugin.store.DeleteAll(ctx, plugin.config.VectorStoreNamespace, queries) |
| if err != nil { |
| return fmt.Errorf("failed to delete cache entries: %w", err) |
| } |
|
|
| for _, result := range results { |
| if result.Status == vectorstore.DeleteStatusError { |
| plugin.logger.Warn("%s Failed to delete cache entry: %s", PluginLoggerPrefix, result.Error) |
| } |
| } |
| plugin.logger.Info("%s Cleanup completed - deleted all cache entries", PluginLoggerPrefix) |
|
|
| if err := plugin.store.DeleteNamespace(ctx, plugin.config.VectorStoreNamespace); err != nil { |
| return fmt.Errorf("failed to delete namespace: %w", err) |
| } |
|
|
| return nil |
| } |
|
|
| |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| func (plugin *Plugin) ClearCacheForKey(cacheKey string) error { |
| |
| queries := []vectorstore.Query{ |
| { |
| Field: "cache_key", |
| Operator: vectorstore.QueryOperatorEqual, |
| Value: cacheKey, |
| }, |
| { |
| Field: "from_bifrost_semantic_cache_plugin", |
| Operator: vectorstore.QueryOperatorEqual, |
| Value: true, |
| }, |
| } |
|
|
| ctx, cancel := context.WithTimeout(context.Background(), CacheSetTimeout) |
| defer cancel() |
| results, err := plugin.store.DeleteAll(ctx, plugin.config.VectorStoreNamespace, queries) |
| if err != nil { |
| plugin.logger.Warn("%s Failed to delete cache entries for key '%s': %v", PluginLoggerPrefix, cacheKey, err) |
| return err |
| } |
|
|
| for _, result := range results { |
| if result.Status == vectorstore.DeleteStatusError { |
| plugin.logger.Warn("%s Failed to delete cache entry for key %s: %s", PluginLoggerPrefix, result.ID, result.Error) |
| } |
| } |
|
|
| plugin.logger.Debug(fmt.Sprintf("%s Deleted all cache entries for key %s", PluginLoggerPrefix, cacheKey)) |
|
|
| return nil |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| func (plugin *Plugin) ClearCacheForRequestID(requestID string) error { |
| |
| ctx, cancel := context.WithTimeout(context.Background(), CacheSetTimeout) |
| defer cancel() |
| if err := plugin.store.Delete(ctx, plugin.config.VectorStoreNamespace, requestID); err != nil { |
| plugin.logger.Warn("%s Failed to delete cache entry: %v", PluginLoggerPrefix, err) |
| return err |
| } |
|
|
| plugin.logger.Debug(fmt.Sprintf("%s Deleted cache entry for key %s", PluginLoggerPrefix, requestID)) |
|
|
| return nil |
| } |
|
|