From c954088bd3a5e2d146e6a16a3fd958e5d6049e7a Mon Sep 17 00:00:00 2001 From: Pratham-Mishra04 Date: Fri, 8 May 2026 17:22:38 +0530 Subject: [PATCH] refactor: removes cleanup_on_shutdown from semantic cache --- .../scripts/validate-helm-config-fields.sh | 2 - core/providers/anthropic/types.go | 18 +++--- docs/deployment-guides/helm/plugins.mdx | 1 - docs/features/semantic-caching.mdx | 8 --- docs/migration-guides/v1.5.0.mdx | 54 ++++++++++++++++ .../values-semantic-search-redis.yaml | 1 - .../values-semantic-search-weaviate.yaml | 1 - helm-charts/bifrost/templates/_helpers.tpl | 3 - helm-charts/bifrost/values.schema.json | 3 - helm-charts/bifrost/values.yaml | 1 - .../semanticcache/config_unmarshal_test.go | 44 ++++++------- plugins/semanticcache/main.go | 38 ------------ plugins/semanticcache/plugin_api_test.go | 61 +++++++------------ plugins/semanticcache/plugin_core_test.go | 18 +++--- .../semanticcache/plugin_no_mutation_test.go | 2 - plugins/semanticcache/plugin_paths_test.go | 30 ++++----- .../semanticcache/plugin_vectorstore_test.go | 1 - plugins/semanticcache/test_utils.go | 15 ++--- transports/bifrost-http/handlers/plugins.go | 2 +- transports/config.schema.json | 4 -- 20 files changed, 132 insertions(+), 175 deletions(-) diff --git a/.github/workflows/scripts/validate-helm-config-fields.sh b/.github/workflows/scripts/validate-helm-config-fields.sh index 352ef88cde..11de5c7356 100755 --- a/.github/workflows/scripts/validate-helm-config-fields.sh +++ b/.github/workflows/scripts/validate-helm-config-fields.sh @@ -637,7 +637,6 @@ bifrost: cache_by_model: true cache_by_provider: false exclude_system_prompt: true - cleanup_on_shutdown: true vector_store_namespace: "bifrost-cache" otel: enabled: true @@ -710,7 +709,6 @@ assert_field_value 'plugins: semantic_cache conversation_history_threshold' '.pl assert_field_value 'plugins: semantic_cache cache_by_model' '.plugins.[4].config.cache_by_model' 'true' assert_field_value 'plugins: semantic_cache cache_by_provider' '.plugins.[4].config.cache_by_provider' 'false' assert_field_value 'plugins: semantic_cache exclude_system_prompt' '.plugins.[4].config.exclude_system_prompt' 'true' -assert_field_value 'plugins: semantic_cache cleanup_on_shutdown' '.plugins.[4].config.cleanup_on_shutdown' 'true' assert_field_value 'plugins: semantic_cache vector_store_namespace' '.plugins.[4].config.vector_store_namespace' '"bifrost-cache"' # OTEL plugin diff --git a/core/providers/anthropic/types.go b/core/providers/anthropic/types.go index 35874f658a..08eb9a7111 100644 --- a/core/providers/anthropic/types.go +++ b/core/providers/anthropic/types.go @@ -1200,18 +1200,18 @@ const ( type AnthropicToolName string const ( - AnthropicToolNameComputer AnthropicToolName = "computer" - AnthropicToolNameWebSearch AnthropicToolName = "web_search" - AnthropicToolNameWebFetch AnthropicToolName = "web_fetch" - AnthropicToolNameBash AnthropicToolName = "bash" - AnthropicToolNameTextEditor AnthropicToolName = "str_replace_based_edit_tool" + AnthropicToolNameComputer AnthropicToolName = "computer" + AnthropicToolNameWebSearch AnthropicToolName = "web_search" + AnthropicToolNameWebFetch AnthropicToolName = "web_fetch" + AnthropicToolNameBash AnthropicToolName = "bash" + AnthropicToolNameTextEditor AnthropicToolName = "str_replace_based_edit_tool" // AnthropicToolNameTextEditorLegacy is the name required for text_editor_20250124 // and text_editor_20250429. Newer text_editor_20250728+ use AnthropicToolNameTextEditor. AnthropicToolNameTextEditorLegacy AnthropicToolName = "str_replace_editor" - AnthropicToolNameCodeExecution AnthropicToolName = "code_execution" - AnthropicToolNameMemory AnthropicToolName = "memory" - AnthropicToolNameToolSearchBM25 AnthropicToolName = "tool_search_tool_bm25" - AnthropicToolNameToolSearchRegex AnthropicToolName = "tool_search_tool_regex" + AnthropicToolNameCodeExecution AnthropicToolName = "code_execution" + AnthropicToolNameMemory AnthropicToolName = "memory" + AnthropicToolNameToolSearchBM25 AnthropicToolName = "tool_search_tool_bm25" + AnthropicToolNameToolSearchRegex AnthropicToolName = "tool_search_tool_regex" ) type AnthropicToolComputerUse struct { diff --git a/docs/deployment-guides/helm/plugins.mdx b/docs/deployment-guides/helm/plugins.mdx index 887cf85600..c0f8b695b8 100644 --- a/docs/deployment-guides/helm/plugins.mdx +++ b/docs/deployment-guides/helm/plugins.mdx @@ -178,7 +178,6 @@ Two modes: | `bifrost.plugins.semanticCache.config.cache_by_model` | Include model name in cache key | `true` | | `bifrost.plugins.semanticCache.config.cache_by_provider` | Include provider name in cache key | `true` | | `bifrost.plugins.semanticCache.config.exclude_system_prompt` | Exclude system prompt from cache key | `false` | -| `bifrost.plugins.semanticCache.config.cleanup_on_shutdown` | Delete cache data on pod shutdown | `false` | **Semantic mode (with OpenAI embeddings + Weaviate):** diff --git a/docs/features/semantic-caching.mdx b/docs/features/semantic-caching.mdx index 5413649ea2..d2eb9b2cba 100644 --- a/docs/features/semantic-caching.mdx +++ b/docs/features/semantic-caching.mdx @@ -198,7 +198,6 @@ bifrostConfig := schemas.BifrostConfig{ "embedding_model": "text-embedding-3-small", "dimension": 1536, - "cleanup_on_shutdown": true, "ttl": "5m", "threshold": 0.8, @@ -281,7 +280,6 @@ bifrost: config: dimension: 1 ttl: "5m" - cleanup_on_shutdown: true cache_by_model: true cache_by_provider: true ``` @@ -299,7 +297,6 @@ bifrost: "config": { "dimension": 1, "ttl": "5m", - "cleanup_on_shutdown": true, "cache_by_model": true, "cache_by_provider": true } @@ -671,16 +668,11 @@ The semantic cache automatically handles cleanup to prevent storage bloat: - Clear all entries for a cache key - Restart Bifrost to clear all cache data - -The semantic cache namespace and all its cache entries are deleted when Bifrost client shuts down **only if `cleanup_on_shutdown` is set to `true`**. By default (`cleanup_on_shutdown: false`), cache data persists between restarts. DO NOT use the plugin's namespace for external purposes. - - **Dimension / Provider / Model Changes**: A vector store namespace can only hold vectors of **one** dimension. If you change `dimension` (or switch to an embedding `provider`/`model` that produces a different vector size), the existing namespace is **not** recreated automatically — `CreateNamespace` is a no-op when the class/collection already exists. Subsequent writes will fail (vector-size mismatch) and reads will silently miss. Before saving the change, either: - point `vector_store_namespace` at a fresh name, or - drop the existing class/index in your vector store, or -- set `cleanup_on_shutdown: true` and restart so the old namespace is removed first. --- diff --git a/docs/migration-guides/v1.5.0.mdx b/docs/migration-guides/v1.5.0.mdx index 95d4a49dd8..0cf4eca096 100644 --- a/docs/migration-guides/v1.5.0.mdx +++ b/docs/migration-guides/v1.5.0.mdx @@ -751,6 +751,56 @@ If you parse `cache_debug` and assumed it was either absent or had `cache_hit: t --- +## Breaking Change 16: Semantic Cache `cleanup_on_shutdown` Removed + +The `cleanup_on_shutdown` option on the semantic cache plugin config has been removed. Cache entries and the vector store namespace are no longer deleted when Bifrost shuts down — cache data always persists between restarts. + +**Before:** +```json +{ + "plugins": { + "semantic_cache": { + "config": { + "ttl": "5m", + "cleanup_on_shutdown": true + } + } + } +} +``` + +**After:** +```json +{ + "plugins": { + "semantic_cache": { + "config": { + "ttl": "5m" + } + } + } +} +``` + +The field is no longer part of the config schema and will be rejected by validation. Remove it from `config.json`, Helm values, and any `PUT /api/config` payloads. + +### How to clear cache data + +If you previously relied on `cleanup_on_shutdown: true` to drop the cache on restart, use one of the supported invalidation paths instead: + +- `DELETE /api/cache/clear/{cacheId}` — invalidate a single entry +- `DELETE /api/cache/clear-by-key/{cacheKey}` — invalidate all entries for a cache key +- Drop the vector store class/collection or point `vector_store_namespace` at a fresh name to start clean + +### Dimension / provider / model changes + +The previous `cleanup_on_shutdown: true` + restart workflow was the documented escape hatch for changing `dimension` (or switching to an embedding `provider`/`model` that produces a different vector size). That option is gone. To rotate the namespace now, either: + +- point `vector_store_namespace` at a fresh name, or +- drop the existing class/index in your vector store before restarting + +--- + ## Opting Out: `version: 1` Compatibility Mode If you are not ready to adopt the new deny-by-default semantics, you can add a single field to `config.json` to restore v1.4.x behavior for all allow-list fields loaded from that file: @@ -853,6 +903,10 @@ Remove `allow_direct_keys` from `config.json` and any `PUT /api/config` payloads Replace `DELETE /api/cache/clear/{requestId}` with `DELETE /api/cache/clear/{cacheId}`, and replace `plugin.ClearCacheForRequestID(...)` with `plugin.ClearCacheForCacheID(...)`. Read the cache ID from `extra_fields.cache_debug.cache_id` on the response (now populated on misses too). + + +Drop the `cleanup_on_shutdown` field from the semantic cache plugin config in `config.json`, Helm values, and any API payloads — it is no longer part of the schema. Cache data now always persists across restarts; use the cache clear endpoints or rotate `vector_store_namespace` to drop entries. + --- diff --git a/examples/k8s/examples/values-semantic-search-redis.yaml b/examples/k8s/examples/values-semantic-search-redis.yaml index 4d9cd18224..8867162e2f 100644 --- a/examples/k8s/examples/values-semantic-search-redis.yaml +++ b/examples/k8s/examples/values-semantic-search-redis.yaml @@ -18,7 +18,6 @@ bifrost: cache_by_model: true cache_by_provider: true exclude_system_prompt: true - cleanup_on_shutdown: false vector_store_namespace: "bifrost-semantic-cache" vectorStore: diff --git a/examples/k8s/examples/values-semantic-search-weaviate.yaml b/examples/k8s/examples/values-semantic-search-weaviate.yaml index 7691106be7..0d00d87d1a 100644 --- a/examples/k8s/examples/values-semantic-search-weaviate.yaml +++ b/examples/k8s/examples/values-semantic-search-weaviate.yaml @@ -18,7 +18,6 @@ bifrost: cache_by_model: true cache_by_provider: true exclude_system_prompt: false - cleanup_on_shutdown: false vector_store_namespace: "bifrost-semantic-cache" vectorStore: diff --git a/helm-charts/bifrost/templates/_helpers.tpl b/helm-charts/bifrost/templates/_helpers.tpl index 9849b760f4..1e5c6312ed 100644 --- a/helm-charts/bifrost/templates/_helpers.tpl +++ b/helm-charts/bifrost/templates/_helpers.tpl @@ -1100,9 +1100,6 @@ false {{- if hasKey $inputConfig "exclude_system_prompt" }} {{- $_ := set $scConfig "exclude_system_prompt" $inputConfig.exclude_system_prompt }} {{- end }} -{{- if hasKey $inputConfig "cleanup_on_shutdown" }} -{{- $_ := set $scConfig "cleanup_on_shutdown" $inputConfig.cleanup_on_shutdown }} -{{- end }} {{- $plugin := dict "enabled" true "name" "semantic_cache" "config" $scConfig }} {{- if hasKey .Values.bifrost.plugins.semanticCache "version" }}{{- $_ := set $plugin "version" (.Values.bifrost.plugins.semanticCache.version | int) }}{{- end }} {{- $plugins = append $plugins $plugin }} diff --git a/helm-charts/bifrost/values.schema.json b/helm-charts/bifrost/values.schema.json index 5146bfc353..a8ac1256b3 100644 --- a/helm-charts/bifrost/values.schema.json +++ b/helm-charts/bifrost/values.schema.json @@ -818,9 +818,6 @@ "exclude_system_prompt": { "type": "boolean" }, - "cleanup_on_shutdown": { - "type": "boolean" - }, "vector_store_namespace": { "type": "string" }, diff --git a/helm-charts/bifrost/values.yaml b/helm-charts/bifrost/values.yaml index 3fb87dbf88..c316f441df 100644 --- a/helm-charts/bifrost/values.yaml +++ b/helm-charts/bifrost/values.yaml @@ -420,7 +420,6 @@ bifrost: cache_by_model: true cache_by_provider: true exclude_system_prompt: false - cleanup_on_shutdown: false vector_store_namespace: "" otel: diff --git a/plugins/semanticcache/config_unmarshal_test.go b/plugins/semanticcache/config_unmarshal_test.go index d38ad31fc6..5d2edd44f1 100644 --- a/plugins/semanticcache/config_unmarshal_test.go +++ b/plugins/semanticcache/config_unmarshal_test.go @@ -43,7 +43,6 @@ func TestUnmarshalJSON_AllFields(t *testing.T) { input := `{ "provider": "openai", "embedding_model": "text-embedding-3-small", - "cleanup_on_shutdown": true, "dimension": 1536, "ttl": "10m", "threshold": 0.9, @@ -66,9 +65,6 @@ func TestUnmarshalJSON_AllFields(t *testing.T) { if config.EmbeddingModel != "text-embedding-3-small" { t.Errorf("EmbeddingModel: expected %q, got %q", "text-embedding-3-small", config.EmbeddingModel) } - if !config.CleanUpOnShutdown { - t.Error("CleanUpOnShutdown: expected true") - } if config.Dimension != 1536 { t.Errorf("Dimension: expected 1536, got %d", config.Dimension) } @@ -136,32 +132,32 @@ func TestUnmarshalJSON_TTLFormats(t *testing.T) { func TestUnmarshalJSON_BoolPointerFields(t *testing.T) { tests := []struct { - name string - json string - expectCacheByModel *bool - expectCacheByProv *bool - expectExcludeSys *bool + name string + json string + expectCacheByModel *bool + expectCacheByProv *bool + expectExcludeSys *bool }{ { - name: "all set to true", - json: `{"dimension": 1536, "cache_by_model": true, "cache_by_provider": true, "exclude_system_prompt": true}`, - expectCacheByModel: bifrost.Ptr(true), - expectCacheByProv: bifrost.Ptr(true), - expectExcludeSys: bifrost.Ptr(true), + name: "all set to true", + json: `{"dimension": 1536, "cache_by_model": true, "cache_by_provider": true, "exclude_system_prompt": true}`, + expectCacheByModel: bifrost.Ptr(true), + expectCacheByProv: bifrost.Ptr(true), + expectExcludeSys: bifrost.Ptr(true), }, { - name: "all set to false", - json: `{"dimension": 1536, "cache_by_model": false, "cache_by_provider": false, "exclude_system_prompt": false}`, - expectCacheByModel: bifrost.Ptr(false), - expectCacheByProv: bifrost.Ptr(false), - expectExcludeSys: bifrost.Ptr(false), + name: "all set to false", + json: `{"dimension": 1536, "cache_by_model": false, "cache_by_provider": false, "exclude_system_prompt": false}`, + expectCacheByModel: bifrost.Ptr(false), + expectCacheByProv: bifrost.Ptr(false), + expectExcludeSys: bifrost.Ptr(false), }, { - name: "all omitted", - json: `{"dimension": 1536}`, - expectCacheByModel: nil, - expectCacheByProv: nil, - expectExcludeSys: nil, + name: "all omitted", + json: `{"dimension": 1536}`, + expectCacheByModel: nil, + expectCacheByProv: nil, + expectExcludeSys: nil, }, } diff --git a/plugins/semanticcache/main.go b/plugins/semanticcache/main.go index 798c98a52d..9fefc497f2 100644 --- a/plugins/semanticcache/main.go +++ b/plugins/semanticcache/main.go @@ -31,7 +31,6 @@ type Config struct { EmbeddingModel string `json:"embedding_model,omitempty"` // Model to use for generating embeddings (optional) // Plugin behavior settings - CleanUpOnShutdown bool `json:"cleanup_on_shutdown,omitempty"` // Clean up cache on shutdown (default: false) TTL time.Duration `json:"ttl,omitempty"` // Time-to-live for cached responses (default: 5min) Threshold float64 `json:"threshold,omitempty"` // Cosine similarity threshold for semantic matching (0 = unset → default 0.8) VectorStoreNamespace string `json:"vector_store_namespace,omitempty"` // Namespace for vector store (optional) @@ -684,43 +683,6 @@ func (plugin *Plugin) Cleanup() error { // and stopCh is still in memory. This call evicts those before we return. plugin.cleanupOldStreamAccumulators() - // Only clean up cache entries if configured to do so - if !plugin.config.CleanUpOnShutdown { - plugin.logger.Debug("Cleanup on shutdown is disabled, skipping cache cleanup") - return nil - } - - // Clean up all cache entries created by this plugin - ctx, cancel := context.WithTimeout(context.Background(), CacheSetTimeout) - defer cancel() - - plugin.logger.Debug("Starting cleanup of cache entries...") - - // Delete all cache entries created by this plugin - 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("Failed to delete cache entry: %s", result.Error) - } - } - plugin.logger.Debug("Cleanup completed - deleted all cache entries") - - if err := plugin.store.DeleteNamespace(ctx, plugin.config.VectorStoreNamespace); err != nil { - return fmt.Errorf("failed to delete namespace: %w", err) - } - return nil } diff --git a/plugins/semanticcache/plugin_api_test.go b/plugins/semanticcache/plugin_api_test.go index 908e88149d..78dbbd45af 100644 --- a/plugins/semanticcache/plugin_api_test.go +++ b/plugins/semanticcache/plugin_api_test.go @@ -16,15 +16,15 @@ import ( // Delete / DeleteAll / DeleteNamespace calls so the tests can assert on the // public Clear* APIs and on Cleanup teardown behavior. type observableStore struct { - mu sync.Mutex - chunks map[string]vectorstore.SearchResult - addIDs []string - deleteIDs []string - deleteAllQueries [][]vectorstore.Query - namespaceDeletes int - deleteAllErr error - deleteErr error - deleteAllResults []vectorstore.DeleteResult + mu sync.Mutex + chunks map[string]vectorstore.SearchResult + addIDs []string + deleteIDs []string + deleteAllQueries [][]vectorstore.Query + namespaceDeletes int + deleteAllErr error + deleteErr error + deleteAllResults []vectorstore.DeleteResult } func newObservableStore() *observableStore { @@ -85,10 +85,9 @@ func (s *observableStore) DeleteAll(ctx context.Context, ns string, queries []ve } func (s *observableStore) Close(ctx context.Context, ns string) error { return nil } -func newTestPlugin(t *testing.T, store vectorstore.VectorStore, cleanupOnShutdown bool) *Plugin { +func newTestPlugin(t *testing.T, store vectorstore.VectorStore) *Plugin { t.Helper() cfg := getDefaultTestConfig() - cfg.CleanUpOnShutdown = cleanupOnShutdown return &Plugin{ store: store, config: cfg, @@ -102,7 +101,7 @@ func newTestPlugin(t *testing.T, store vectorstore.VectorStore, cleanupOnShutdow // ----------------------------------------------------------------------------- func TestClearCacheForCacheID_EmptyIDRejected(t *testing.T) { - plugin := newTestPlugin(t, newObservableStore(), false) + plugin := newTestPlugin(t, newObservableStore()) if err := plugin.ClearCacheForCacheID(""); err == nil { t.Fatal("expected error for empty cache ID") } @@ -110,7 +109,7 @@ func TestClearCacheForCacheID_EmptyIDRejected(t *testing.T) { func TestClearCacheForCacheID_PointDelete(t *testing.T) { store := newObservableStore() - plugin := newTestPlugin(t, store, false) + plugin := newTestPlugin(t, store) if err := plugin.ClearCacheForCacheID("cache-abc"); err != nil { t.Fatalf("ClearCacheForCacheID failed: %v", err) @@ -128,7 +127,7 @@ func TestClearCacheForCacheID_PointDelete(t *testing.T) { func TestClearCacheForKey_FiltersByCacheKeyAndPluginMarker(t *testing.T) { store := newObservableStore() - plugin := newTestPlugin(t, store, false) + plugin := newTestPlugin(t, store) if err := plugin.ClearCacheForKey("session-42"); err != nil { t.Fatalf("ClearCacheForKey failed: %v", err) @@ -162,7 +161,7 @@ func TestClearCacheForKey_FiltersByCacheKeyAndPluginMarker(t *testing.T) { // ----------------------------------------------------------------------------- func TestStampCacheDebugForMiss_AlwaysSetsCacheID(t *testing.T) { - plugin := newTestPlugin(t, newObservableStore(), false) + plugin := newTestPlugin(t, newObservableStore()) state := &cacheState{} extra := &schemas.BifrostResponseExtraFields{} @@ -184,7 +183,7 @@ func TestStampCacheDebugForMiss_AlwaysSetsCacheID(t *testing.T) { } func TestStampCacheDebugForMiss_AddsTelemetryWhenSemanticRan(t *testing.T) { - plugin := newTestPlugin(t, newObservableStore(), false) + plugin := newTestPlugin(t, newObservableStore()) state := &cacheState{EmbeddingsInputTokens: 42} extra := &schemas.BifrostResponseExtraFields{} @@ -199,7 +198,7 @@ func TestStampCacheDebugForMiss_AddsTelemetryWhenSemanticRan(t *testing.T) { } func TestStampCacheDebugForMiss_StreamSkipsNonFinalChunks(t *testing.T) { - plugin := newTestPlugin(t, newObservableStore(), false) + plugin := newTestPlugin(t, newObservableStore()) state := &cacheState{} extra := &schemas.BifrostResponseExtraFields{} @@ -216,7 +215,7 @@ func TestStampCacheDebugForMiss_StreamSkipsNonFinalChunks(t *testing.T) { func TestCleanup_SkipsEntryDeletionWhenDisabled(t *testing.T) { store := newObservableStore() - plugin := newTestPlugin(t, store, false) // CleanUpOnShutdown=false + plugin := newTestPlugin(t, store) // CleanUpOnShutdown=false if err := plugin.Cleanup(); err != nil { t.Fatalf("Cleanup failed: %v", err) @@ -232,26 +231,8 @@ func TestCleanup_SkipsEntryDeletionWhenDisabled(t *testing.T) { } } -func TestCleanup_DeletesEntriesAndNamespaceWhenEnabled(t *testing.T) { - store := newObservableStore() - plugin := newTestPlugin(t, store, true) // CleanUpOnShutdown=true - - if err := plugin.Cleanup(); err != nil { - t.Fatalf("Cleanup failed: %v", err) - } - - store.mu.Lock() - defer store.mu.Unlock() - if len(store.deleteAllQueries) != 1 { - t.Fatalf("expected one DeleteAll call, got %d", len(store.deleteAllQueries)) - } - if store.namespaceDeletes != 1 { - t.Fatalf("expected one DeleteNamespace call, got %d", store.namespaceDeletes) - } -} - func TestCleanup_DrainsPendingWriters(t *testing.T) { - plugin := newTestPlugin(t, newObservableStore(), false) + plugin := newTestPlugin(t, newObservableStore()) var done atomic.Bool plugin.writersWg.Add(1) @@ -274,7 +255,7 @@ func TestCleanup_DrainsPendingWriters(t *testing.T) { // ----------------------------------------------------------------------------- func TestCleanupOldCacheStates_ReapsOldEntries(t *testing.T) { - plugin := newTestPlugin(t, newObservableStore(), false) + plugin := newTestPlugin(t, newObservableStore()) plugin.cacheStates.Store("old-1", &cacheState{CreatedAt: time.Now().Add(-2 * cacheStateMaxAge)}) plugin.cacheStates.Store("old-2", &cacheState{CreatedAt: time.Now().Add(-2 * cacheStateMaxAge)}) @@ -298,7 +279,7 @@ func TestCleanupOldCacheStates_ReapsOldEntries(t *testing.T) { // ----------------------------------------------------------------------------- func TestCleanupOldStreamAccumulators_ReapsByLastSeenAt(t *testing.T) { - plugin := newTestPlugin(t, newObservableStore(), false) + plugin := newTestPlugin(t, newObservableStore()) plugin.streamAccumulators.Store("old", &StreamAccumulator{ RequestID: "old", @@ -324,7 +305,7 @@ func TestCleanupOldStreamAccumulators_ReapsByLastSeenAt(t *testing.T) { // ----------------------------------------------------------------------------- func TestBuildStreamingResponseFromResult_ConsumerAbandonment(t *testing.T) { - plugin := newTestPlugin(t, newObservableStore(), false) + plugin := newTestPlugin(t, newObservableStore()) // Build a cached entry with multiple chunks. chunkJSON := `{"chat_response":{"choices":[]}}` diff --git a/plugins/semanticcache/plugin_core_test.go b/plugins/semanticcache/plugin_core_test.go index e0f88464de..a280f662f6 100644 --- a/plugins/semanticcache/plugin_core_test.go +++ b/plugins/semanticcache/plugin_core_test.go @@ -581,11 +581,10 @@ func TestInvalidProviderRejection(t *testing.T) { for _, provider := range unsupportedProviders { t.Run(string(provider), func(t *testing.T) { config := &Config{ - Provider: provider, - EmbeddingModel: "some-model", - Dimension: 1536, - Threshold: 0.8, - CleanUpOnShutdown: false, + Provider: provider, + EmbeddingModel: "some-model", + Dimension: 1536, + Threshold: 0.8, } // Provider validation was moved to request time (global client handles it). @@ -608,11 +607,10 @@ func TestValidProviderAccepted(t *testing.T) { // Test a supported provider (OpenAI) config := &Config{ - Provider: schemas.OpenAI, - EmbeddingModel: "text-embedding-3-small", - Dimension: 1536, - Threshold: 0.8, - CleanUpOnShutdown: false, + Provider: schemas.OpenAI, + EmbeddingModel: "text-embedding-3-small", + Dimension: 1536, + Threshold: 0.8, } // Init should succeed; provider validation happens at request time via the global client. diff --git a/plugins/semanticcache/plugin_no_mutation_test.go b/plugins/semanticcache/plugin_no_mutation_test.go index 340b4fdd9a..d0a65b681f 100644 --- a/plugins/semanticcache/plugin_no_mutation_test.go +++ b/plugins/semanticcache/plugin_no_mutation_test.go @@ -88,8 +88,6 @@ func TestCachingDoesNotMutateRequestSentToProvider(t *testing.T) { Threshold: 0.8, ConversationHistoryThreshold: DefaultConversationHistoryThreshold, VectorStoreNamespace: SharedTestNamespace, - // Do NOT clean up on shutdown — other parallel tests share the namespace. - CleanUpOnShutdown: false, } if err := ensureSharedTestNamespace(context.Background(), store, cfg.Dimension); err != nil { t.Fatalf("ensureSharedTestNamespace: %v", err) diff --git a/plugins/semanticcache/plugin_paths_test.go b/plugins/semanticcache/plugin_paths_test.go index 5ca1ac8c7a..fc6f54920b 100644 --- a/plugins/semanticcache/plugin_paths_test.go +++ b/plugins/semanticcache/plugin_paths_test.go @@ -21,7 +21,7 @@ import ( func TestPostLLMHook_SkipsOnBifrostError(t *testing.T) { store := newObservableStore() - plugin := newTestPlugin(t, store, false) + plugin := newTestPlugin(t, store) ctx := newBaseTestContext() ctx.SetValue(CacheKey, keyForTest(t, "")) @@ -61,7 +61,7 @@ func TestPostLLMHook_SkipsOnBifrostError(t *testing.T) { // ----------------------------------------------------------------------------- func TestShouldSkipCaching_LargePayloadMode(t *testing.T) { - plugin := newTestPlugin(t, newObservableStore(), false) + plugin := newTestPlugin(t, newObservableStore()) ctx := newBaseTestContext() ctx.SetValue(schemas.BifrostContextKeyLargePayloadMode, true) @@ -73,7 +73,7 @@ func TestShouldSkipCaching_LargePayloadMode(t *testing.T) { } func TestShouldSkipCaching_LargeResponseMode(t *testing.T) { - plugin := newTestPlugin(t, newObservableStore(), false) + plugin := newTestPlugin(t, newObservableStore()) ctx := newBaseTestContext() ctx.SetValue(schemas.BifrostContextKeyLargeResponseMode, true) @@ -85,7 +85,7 @@ func TestShouldSkipCaching_LargeResponseMode(t *testing.T) { } func TestShouldSkipCaching_CacheHitReplay(t *testing.T) { - plugin := newTestPlugin(t, newObservableStore(), false) + plugin := newTestPlugin(t, newObservableStore()) ctx := newBaseTestContext() res := &schemas.BifrostResponse{ @@ -102,7 +102,7 @@ func TestShouldSkipCaching_CacheHitReplay(t *testing.T) { } func TestShouldSkipCaching_NoStoreFlag(t *testing.T) { - plugin := newTestPlugin(t, newObservableStore(), false) + plugin := newTestPlugin(t, newObservableStore()) ctx := newBaseTestContext() ctx.SetValue(CacheNoStoreKey, true) @@ -162,7 +162,7 @@ func TestInit_AllowsDirectOnlyMode(t *testing.T) { // ----------------------------------------------------------------------------- func TestPreLLMHook_FallsBackToDirectWhenExecutorMissing(t *testing.T) { - plugin := newTestPlugin(t, newObservableStore(), false) + plugin := newTestPlugin(t, newObservableStore()) // Intentionally do NOT set plugin.embeddingRequestExecutor. req := &schemas.BifrostRequest{ @@ -197,7 +197,7 @@ func TestPreLLMHook_FallsBackToDirectWhenExecutorMissing(t *testing.T) { func TestExpiredEntry_DetectedAndDeleted(t *testing.T) { store := newObservableStore() - plugin := newTestPlugin(t, store, false) + plugin := newTestPlugin(t, store) // Plant an already-expired entry under a deterministic ID. expiredID := "expired-id-1" @@ -295,7 +295,7 @@ func TestUnmarshalJSON_RejectsBadDurationString(t *testing.T) { // ----------------------------------------------------------------------------- func TestStreamReplay_CancelImmediately(t *testing.T) { - plugin := newTestPlugin(t, newObservableStore(), false) + plugin := newTestPlugin(t, newObservableStore()) chunk := `{"chat_response":{"choices":[]}}` streamArray := []string{chunk, chunk, chunk} @@ -331,7 +331,7 @@ func TestStreamReplay_CancelImmediately(t *testing.T) { } func TestStreamReplay_FullDrain(t *testing.T) { - plugin := newTestPlugin(t, newObservableStore(), false) + plugin := newTestPlugin(t, newObservableStore()) chunk := `{"chat_response":{"choices":[]}}` streamArray := []string{chunk, chunk, chunk} @@ -379,7 +379,7 @@ func scopedTestContext(t testing.TB, suffix string) *schemas.BifrostContext { func TestPreLLMHook_EmitsPluginLogOnEmbeddingFailure(t *testing.T) { store := newObservableStore() - plugin := newTestPlugin(t, store, false) + plugin := newTestPlugin(t, store) plugin.SetEmbeddingRequestExecutor(func(_ *schemas.BifrostContext, _ *schemas.BifrostEmbeddingRequest) (*schemas.BifrostEmbeddingResponse, *schemas.BifrostError) { return nil, &schemas.BifrostError{Error: &schemas.ErrorField{Message: "rate limit exceeded"}} }) @@ -438,7 +438,7 @@ func TestPreLLMHook_NoDebugLogsOnFlow(t *testing.T) { // flow (hit/miss). cache_debug already conveys that. Only Warn-level // failure logs should appear on the response. store := newObservableStore() - plugin := newTestPlugin(t, store, false) + plugin := newTestPlugin(t, store) req := &schemas.BifrostRequest{ RequestType: schemas.ChatCompletionRequest, @@ -461,7 +461,7 @@ func TestPreLLMHook_NoDebugLogsOnFlow(t *testing.T) { } func TestResolveCacheTypes_EmitsPluginLogOnInvalidValue(t *testing.T) { - plugin := newTestPlugin(t, newObservableStore(), false) + plugin := newTestPlugin(t, newObservableStore()) ctx := scopedTestContext(t, "") ctx.SetValue(CacheTypeKey, "not-a-cache-type") // wrong type @@ -484,7 +484,7 @@ func TestResolveCacheTypes_EmitsPluginLogOnInvalidValue(t *testing.T) { // ----------------------------------------------------------------------------- func TestGenerateEmbedding_AcceptsInt8Array(t *testing.T) { - plugin := newTestPlugin(t, newObservableStore(), false) + plugin := newTestPlugin(t, newObservableStore()) plugin.SetEmbeddingRequestExecutor(func(_ *schemas.BifrostContext, _ *schemas.BifrostEmbeddingRequest) (*schemas.BifrostEmbeddingResponse, *schemas.BifrostError) { return &schemas.BifrostEmbeddingResponse{ Data: []schemas.EmbeddingData{{ @@ -507,7 +507,7 @@ func TestGenerateEmbedding_AcceptsInt8Array(t *testing.T) { } func TestGenerateEmbedding_AcceptsInt32Array(t *testing.T) { - plugin := newTestPlugin(t, newObservableStore(), false) + plugin := newTestPlugin(t, newObservableStore()) plugin.SetEmbeddingRequestExecutor(func(_ *schemas.BifrostContext, _ *schemas.BifrostEmbeddingRequest) (*schemas.BifrostEmbeddingResponse, *schemas.BifrostError) { return &schemas.BifrostEmbeddingResponse{ Data: []schemas.EmbeddingData{{ @@ -534,7 +534,7 @@ func TestGenerateEmbedding_AcceptsInt32Array(t *testing.T) { // ----------------------------------------------------------------------------- func TestPreLLMHook_ConcurrentSameRequestID(t *testing.T) { - plugin := newTestPlugin(t, newObservableStore(), false) + plugin := newTestPlugin(t, newObservableStore()) req := &schemas.BifrostRequest{ RequestType: schemas.ChatCompletionRequest, diff --git a/plugins/semanticcache/plugin_vectorstore_test.go b/plugins/semanticcache/plugin_vectorstore_test.go index 6d29f08c8b..3e93cc8baa 100644 --- a/plugins/semanticcache/plugin_vectorstore_test.go +++ b/plugins/semanticcache/plugin_vectorstore_test.go @@ -55,7 +55,6 @@ func getDefaultTestConfig() *Config { EmbeddingModel: "text-embedding-3-small", Dimension: 1536, Threshold: 0.8, - CleanUpOnShutdown: true, ConversationHistoryThreshold: DefaultConversationHistoryThreshold, } } diff --git a/plugins/semanticcache/test_utils.go b/plugins/semanticcache/test_utils.go index 6a506f51a4..d9b4084926 100644 --- a/plugins/semanticcache/test_utils.go +++ b/plugins/semanticcache/test_utils.go @@ -415,11 +415,10 @@ type TestSetup struct { // NewTestSetup creates a new test setup with default configuration func NewTestSetup(t *testing.T) *TestSetup { return NewTestSetupWithConfig(t, &Config{ - Provider: schemas.OpenAI, - EmbeddingModel: "text-embedding-3-small", - Dimension: 1536, - Threshold: 0.8, - CleanUpOnShutdown: true, + Provider: schemas.OpenAI, + EmbeddingModel: "text-embedding-3-small", + Dimension: 1536, + Threshold: 0.8, }) } @@ -462,9 +461,6 @@ func NewTestSetupWithVectorStore(t *testing.T, config *Config, storeType vectors if config.VectorStoreNamespace == "" { config.VectorStoreNamespace = SharedTestNamespace } - // Tests must NOT delete the shared namespace at cleanup — other parallel - // tests are still using it. Override any caller default. - config.CleanUpOnShutdown = false // Get the appropriate config for the vector store type var storeConfig interface{} @@ -750,7 +746,6 @@ func CreateTestSetupWithConversationThreshold(t *testing.T, threshold int) *Test Provider: schemas.OpenAI, EmbeddingModel: "text-embedding-3-small", Dimension: 1536, - CleanUpOnShutdown: true, Threshold: 0.8, ConversationHistoryThreshold: threshold, } @@ -764,7 +759,6 @@ func CreateTestSetupWithExcludeSystemPrompt(t *testing.T, excludeSystem bool) *T Provider: schemas.OpenAI, EmbeddingModel: "text-embedding-3-small", Dimension: 1536, - CleanUpOnShutdown: true, Threshold: 0.8, ExcludeSystemPrompt: &excludeSystem, } @@ -778,7 +772,6 @@ func CreateTestSetupWithThresholdAndExcludeSystem(t *testing.T, threshold int, e Provider: schemas.OpenAI, EmbeddingModel: "text-embedding-3-small", Dimension: 1536, - CleanUpOnShutdown: true, Threshold: 0.8, ConversationHistoryThreshold: threshold, ExcludeSystemPrompt: &excludeSystem, diff --git a/transports/bifrost-http/handlers/plugins.go b/transports/bifrost-http/handlers/plugins.go index 3dc4f8353c..71613c15ba 100644 --- a/transports/bifrost-http/handlers/plugins.go +++ b/transports/bifrost-http/handlers/plugins.go @@ -16,9 +16,9 @@ import ( ) type PluginsLoader interface { + GetPluginStatus(ctx context.Context) map[string]schemas.PluginStatus ReloadPlugin(ctx context.Context, name string, path *string, pluginConfig any, placement *schemas.PluginPlacement, order *int) error RemovePlugin(ctx context.Context, name string) error - GetPluginStatus(ctx context.Context) map[string]schemas.PluginStatus } // PluginsHandler is the handler for the plugins API diff --git a/transports/config.schema.json b/transports/config.schema.json index 8e78f1d1a0..66ee0b5ee5 100644 --- a/transports/config.schema.json +++ b/transports/config.schema.json @@ -1440,10 +1440,6 @@ "type": "string", "description": "Model to use for generating embeddings in provider-backed semantic caching. Required when provider is set and not allowed in direct-only mode." }, - "cleanup_on_shutdown": { - "type": "boolean", - "description": "Clean up cache on shutdown (default: false)" - }, "ttl": { "description": "Time-to-live for cached responses (supports duration strings like '5m', '1h' or seconds as number, default: 5min)", "oneOf": [