Skip to content

Comments

Allow configuring a default cache key at the plugin level#1729

Open
Georgehe4 wants to merge 2 commits intomaximhq:mainfrom
Georgehe4:feat/default-cache-key
Open

Allow configuring a default cache key at the plugin level#1729
Georgehe4 wants to merge 2 commits intomaximhq:mainfrom
Georgehe4:feat/default-cache-key

Conversation

@Georgehe4
Copy link
Contributor

@Georgehe4 Georgehe4 commented Feb 22, 2026

Summary

Enable default caching

  • Right now, requests without an explicit x-bf-cache-key header are not automatically cached.
  • Setting default_cache_key alters the behavior
  • When default_cache_key is unset (empty string), existing behavior is preserved: caching is opt-in per request.

Changes

  • Add a default_cache_key, which can be set via
{
  "plugins": [
    {
      "enabled": true,
      "name": "semantic_cache",
      "config": {
        "default_cache_key": "global",
        "dimension": 1536,
        "provider": "openai",
        "embedding_model": "text-embedding-3-small"
      }
    }
  ]
}
bifrost:
  plugins:
    semanticCache:
      enabled: true
      config:
        default_cache_key: "global"
        dimension: 1536
        provider: "openai"
        embedding_model: "text-embedding-3-small"

Type of change

  • Bug fix
  • Feature
  • Refactor
  • Documentation
  • Chore/CI

Affected areas

  • Core (Go)
  • Transports (HTTP)
  • Providers/Integrations
  • Plugins
  • UI (Next.js)
  • Docs

How to test

Set default_cache_key

{
  "plugins": [
    {
      "enabled": true,
      "name": "semantic_cache",
      "config": {
        "default_cache_key": "global",
        "dimension": 1536,
        "provider": "openai",
        "embedding_model": "text-embedding-3-small"
      }
    }
  ]
}

Screenshots/Recordings

If UI changes, add before/after screenshots or short clips.

Breaking changes

  • Yes
  • No

If yes, describe impact and migration instructions.

Related issues

Link related issues and discussions. Example: Closes #123

Security considerations

Note any security implications (auth, secrets, PII, sandboxing, etc.).

Checklist

  • I read docs/contributing/README.md and followed the guidelines
  • I added/updated tests where appropriate
  • I updated documentation where needed
  • I verified builds succeed (Go and UI)
  • I verified the CI pipeline passes locally if applicable

requests without an explicit x-bf-cache-key header are automatically
cached. When unset (empty string), existing behavior is preserved:
caching is opt-in per request.
@Georgehe4 Georgehe4 changed the title Allow configuring a default cache key at the plugin level so that Allow configuring a default cache key at the plugin level Feb 22, 2026
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 22, 2026

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Added support for a default cache key in semantic caching. When configured, requests without an explicit cache key are automatically cached using the default key, improving cache coverage without per-request configuration.
  • Configuration

    • New configuration option to set the default cache key for the semantic cache provider.
  • Tests

    • Added tests covering default-key caching behavior, override by per-request keys, and disabled-default behavior.

Walkthrough

Adds a configurable default_cache_key to the semantic cache: Helm template, Go plugin config and runtime hooks, JSON schema, and tests so requests without a per-request cache key may use the configured default for caching.

Changes

Cohort / File(s) Summary
Helm template helper
helm-charts/bifrost/templates/_helpers.tpl
Wire inputConfig.default_cache_key into generated semantic cache config as scConfig.default_cache_key.
Go plugin implementation
plugins/semanticcache/main.go
Added DefaultCacheKey string to Config; PreLLMHook/PostLLMHook use DefaultCacheKey when no per-request cache key is present (affects cache decision path).
Schema
transports/config.schema.json
Added default_cache_key string property to semanticcache provider config schema with descriptive text.
Tests
plugins/semanticcache/plugin_default_cache_key_test.go
New tests covering: caching with default key, per-request key overriding default, and behavior when default is empty. Uses existing test harness and cache-hit assertions.

Sequence Diagram(s)

sequenceDiagram
  actor Client
  participant Bifrost as "Bifrost/Request Handler"
  participant SemanticCache as "semanticcache Plugin"
  participant Cache as "Cache Store"
  participant LLM as "LLM"

  Client->>Bifrost: Send request (maybe no x-bf-cache-key)
  Bifrost->>SemanticCache: PreLLMHook(ctx, req)
  SemanticCache->>SemanticCache: determine cache_key = ctx.key || DefaultCacheKey
  alt cache_key set
    SemanticCache->>Cache: Lookup(cache_key)
    alt cache hit
      Cache-->>SemanticCache: cached response
      SemanticCache-->>Bifrost: return cached response
      Bifrost-->>Client: cached response
    else cache miss
      SemanticCache->>LLM: forward request
      LLM-->>SemanticCache: response
      SemanticCache->>Cache: Store(cache_key, response)
      SemanticCache-->>Bifrost: response
      Bifrost-->>Client: response
    end
  else no cache_key
    SemanticCache->>LLM: forward request (no caching)
    LLM-->>SemanticCache: response
    SemanticCache-->>Bifrost: response
    Bifrost-->>Client: response
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 I found a key beneath the log,

When headers sleep and caches clog,
A default hums, a gentle knock,
Responses nest in one small lock.
🥕🔑✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The PR implements default cache key configuration for semantic caching, but the linked issue #123 is about Files API support for providers like OpenAI/Anthropic, which is completely unrelated. Either link the correct related issues for this default cache key feature, or remove the unrelated Files API issue link.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately reflects the main feature: adding plugin-level configuration for a default cache key in the semantic_cache plugin.
Description check ✅ Passed The PR description covers purpose, changes, type (feature), affected areas (plugins), testing instructions, and indicates no breaking changes. All critical sections are present.
Out of Scope Changes check ✅ Passed All code changes (Go config field, schema property, Helm template updates, and tests) are directly related to implementing the default cache key feature as described in the PR objectives.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
plugins/semanticcache/main.go (1)

39-107: ⚠️ Potential issue | 🔴 Critical

DefaultCacheKey is never populated by UnmarshalJSON — entire feature is dead at runtime.

Config.UnmarshalJSON uses an internal TempConfig to avoid infinite recursion; it then manually copies every field to c. DefaultCacheKey is missing from both TempConfig and the copy block, so after JSON unmarshaling c.DefaultCacheKey is always "". The checks on lines 395 and 539 (plugin.config.DefaultCacheKey != "") therefore never execute, and the feature is completely non-functional when loaded from any JSON/YAML/Helm config.

🐛 Proposed fix
 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"`
 }

And in the manual copy block:

 c.VectorStoreNamespace = temp.VectorStoreNamespace
 c.ConversationHistoryThreshold = temp.ConversationHistoryThreshold
+c.DefaultCacheKey = temp.DefaultCacheKey
 c.Threshold = temp.Threshold
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@plugins/semanticcache/main.go` around lines 39 - 107, The TempConfig used by
Config.UnmarshalJSON is missing the DefaultCacheKey field and you never copy it
into c, so populate TempConfig with DefaultCacheKey string
`json:"default_cache_key,omitempty"` and add c.DefaultCacheKey =
temp.DefaultCacheKey in the manual copy block inside Config.UnmarshalJSON (near
where you set Provider/Keys/EmbeddingModel/etc.) so DefaultCacheKey is correctly
preserved when unmarshaling.
🧹 Nitpick comments (1)
plugins/semanticcache/main.go (1)

538-543: Add a Debug log when DefaultCacheKey is applied in PostLLMHook, consistent with PreLLMHook.

PreLLMHook (line 397) emits a debug log when it falls back to DefaultCacheKey; PostLLMHook is silent. Without the log it's harder to diagnose cache-miss vs. no-key scenarios in production traces.

📝 Proposed fix
 if !ok || cacheKey == "" {
     if plugin.config.DefaultCacheKey != "" {
         cacheKey = plugin.config.DefaultCacheKey
+        plugin.logger.Debug(PluginLoggerPrefix + " Using default cache key: " + cacheKey)
     } else {
         return res, nil, nil
     }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@plugins/semanticcache/main.go` around lines 538 - 543, PostLLMHook currently
falls back to plugin.config.DefaultCacheKey when cacheKey is empty but doesn't
log this; add a debug log (matching the style used in PreLLMHook) at the point
where cacheKey is set to plugin.config.DefaultCacheKey so operators can see when
the default key is applied; reference the PostLLMHook function and ensure the
log message mirrors PreLLMHook's debug text for consistency.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@plugins/semanticcache/main.go`:
- Around line 39-107: The TempConfig used by Config.UnmarshalJSON is missing the
DefaultCacheKey field and you never copy it into c, so populate TempConfig with
DefaultCacheKey string `json:"default_cache_key,omitempty"` and add
c.DefaultCacheKey = temp.DefaultCacheKey in the manual copy block inside
Config.UnmarshalJSON (near where you set Provider/Keys/EmbeddingModel/etc.) so
DefaultCacheKey is correctly preserved when unmarshaling.

---

Nitpick comments:
In `@plugins/semanticcache/main.go`:
- Around line 538-543: PostLLMHook currently falls back to
plugin.config.DefaultCacheKey when cacheKey is empty but doesn't log this; add a
debug log (matching the style used in PreLLMHook) at the point where cacheKey is
set to plugin.config.DefaultCacheKey so operators can see when the default key
is applied; reference the PostLLMHook function and ensure the log message
mirrors PreLLMHook's debug text for consistency.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
plugins/semanticcache/plugin_default_cache_key_test.go (2)

35-35: Prefer bifrost.Ptr() over & for BifrostResponse struct literals.

All five assertion call sites use &schemas.BifrostResponse{...}. Per the project convention, bifrost.Ptr() should be used consistently instead of the address operator.

♻️ Example fix (apply to all five occurrences)
-	AssertNoCacheHit(t, &schemas.BifrostResponse{ChatResponse: response1})
+	AssertNoCacheHit(t, bifrost.Ptr(schemas.BifrostResponse{ChatResponse: response1}))

Based on learnings: "prefer using bifrost.Ptr() to create pointers instead of the address operator (&) even when & would be valid syntactically… Replace occurrences of &value where a *T is expected with bifrost.Ptr(value)."

Also applies to: 49-49, 83-83, 106-106, 120-120

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@plugins/semanticcache/plugin_default_cache_key_test.go` at line 35, Replace
uses of the address operator for BifrostResponse struct literals with the
project helper function: instead of &schemas.BifrostResponse{...} wrap the
struct literal with bifrost.Ptr(...). Update all five assertion call sites (e.g.
the AssertNoCacheHit/AssertCacheHit invocations that currently pass
&schemas.BifrostResponse{...}) to pass bifrost.Ptr(schemas.BifrostResponse{...})
so the code uses bifrost.Ptr and the schemas.BifrostResponse type consistently.

64-84: TestDefaultCacheKey_PerRequestKeyOverridesDefault only verifies the miss, not the hit — test can pass with caching entirely disabled.

The test caches with the default key, then asserts that a different per-request key is a miss. But it never asserts that a repeat request with the same default key would be a hit. If caching is completely broken, the override key also trivially misses and the test passes vacuously. Add an intermediate same-default-key request after WaitForCache to confirm the cache is actually populated before testing the override:

♻️ Proposed strengthened assertion
 	WaitForCache()
 
+	// Confirm the default key itself hits the cache before testing override
+	ctx1b := schemas.NewBifrostContext(context.Background(), schemas.NoDeadline)
+	response1b, err1b := setup.Client.ChatCompletionRequest(ctx1b, testRequest)
+	if err1b != nil {
+		if err1b.Error != nil {
+			t.Fatalf("Baseline cache-hit request failed: %v", err1b.Error.Message)
+		}
+		t.Fatalf("Baseline cache-hit request failed: %v", err1b)
+	}
+	AssertCacheHit(t, &schemas.BifrostResponse{ChatResponse: response1b}, string(CacheTypeDirect))
+
 	// Same request but with a DIFFERENT per-request key — should miss
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@plugins/semanticcache/plugin_default_cache_key_test.go` around lines 64 - 84,
After the initial request using the default key (schemas.NewBifrostContext(...))
and WaitForCache(), add a repeat request using the same default context (call
setup.Client.ChatCompletionRequest with a new ctxDefault2 created by
schemas.NewBifrostContext) and assert that this second default-key response is a
cache hit (use AssertCacheHit with the returned response) before running the
existing override-key request and AssertNoCacheHit; this ensures the cache was
actually populated prior to testing that
CreateContextWithCacheKey("override-key") produces a miss.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@plugins/semanticcache/plugin_default_cache_key_test.go`:
- Around line 26-28: The early checks that do "if err1 != nil { return }" after
the first ChatCompletionRequest silently abort the test and mask failures;
change those to either fail the test with t.Fatalf (e.g., t.Fatalf("first
ChatCompletionRequest failed: %v", err1)) or explicitly skip with t.Skipf when
external infra is unavailable. Locate the three occurrences that check err1
after the first ChatCompletionRequest call in plugin_default_cache_key_test.go
(the same pattern appears twice more later) and replace the silent return with a
t.Fatalf or t.Skipf consistent with the test's intent.

---

Nitpick comments:
In `@plugins/semanticcache/plugin_default_cache_key_test.go`:
- Line 35: Replace uses of the address operator for BifrostResponse struct
literals with the project helper function: instead of
&schemas.BifrostResponse{...} wrap the struct literal with bifrost.Ptr(...).
Update all five assertion call sites (e.g. the AssertNoCacheHit/AssertCacheHit
invocations that currently pass &schemas.BifrostResponse{...}) to pass
bifrost.Ptr(schemas.BifrostResponse{...}) so the code uses bifrost.Ptr and the
schemas.BifrostResponse type consistently.
- Around line 64-84: After the initial request using the default key
(schemas.NewBifrostContext(...)) and WaitForCache(), add a repeat request using
the same default context (call setup.Client.ChatCompletionRequest with a new
ctxDefault2 created by schemas.NewBifrostContext) and assert that this second
default-key response is a cache hit (use AssertCacheHit with the returned
response) before running the existing override-key request and AssertNoCacheHit;
this ensures the cache was actually populated prior to testing that
CreateContextWithCacheKey("override-key") produces a miss.

Comment on lines +26 to +28
if err1 != nil {
return
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Silent return on first-request error masks failures across all three tests.

All three tests share the same pattern: if the first ChatCompletionRequest call returns a non-nil error, the test function silently returns. The test reports as passed with nothing verified. This contrasts with the second-request error handling in each test, which correctly calls t.Fatalf. If the embedding service or LLM backend is unavailable, CI will stay green while no assertion is ever executed.

Replace with t.Fatalf (hard fail) or t.Skipf (intentional infrastructure skip) consistently:

🐛 Proposed fix (applied to all three occurrences)
-	if err1 != nil {
-		return
-	}
+	if err1 != nil {
+		if err1.Error != nil {
+			t.Fatalf("First request failed: %v", err1.Error.Message)
+		}
+		t.Fatalf("First request failed: %v", err1)
+	}

Also applies to: 66-69, 101-104

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@plugins/semanticcache/plugin_default_cache_key_test.go` around lines 26 - 28,
The early checks that do "if err1 != nil { return }" after the first
ChatCompletionRequest silently abort the test and mask failures; change those to
either fail the test with t.Fatalf (e.g., t.Fatalf("first ChatCompletionRequest
failed: %v", err1)) or explicitly skip with t.Skipf when external infra is
unavailable. Locate the three occurrences that check err1 after the first
ChatCompletionRequest call in plugin_default_cache_key_test.go (the same pattern
appears twice more later) and replace the silent return with a t.Fatalf or
t.Skipf consistent with the test's intent.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Files API Support

1 participant