Skip to content

Commit 66edcae

Browse files
committed
docs(adaptive): describe logical keying; keep string key_strategy inputs
Address CodeRabbit review on #818. - Replace the stale "exact-match" wording on `AdaptiveConfig.response_cache`, `ResponseCacheConfig`, `KeyOutcome::Key`, and the `response_cache` module doc, and add a TOML `key_strategy = "logical"` example to the `ResponseCacheConfig` doc comment. - `ResponseCacheConfig.to_dict()` in Python no longer raises `AttributeError` when `key_strategy` is a plain wire string. Enum members serialize through `.value`; strings pass through unchanged so unsupported values reach native validation with the `response_cache.unsupported_key_strategy` diagnostic, matching the Go string alias and the Node.js runtime. - Add Python regression coverage for the string and unsupported-string paths. The user-facing docs under `docs/configure-plugins/adaptive/response-cache.mdx` stay deferred to #819, which is open for exactly that. Signed-off-by: Zhongxuan (Daniel) Wang <52872691+ZhongxuanWang@users.noreply.github.com>
1 parent 6c08e10 commit 66edcae

6 files changed

Lines changed: 53 additions & 9 deletions

File tree

crates/adaptive/src/config.rs

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,11 @@ pub struct AdaptiveConfig {
3434
/// Adaptive Cache Governor settings.
3535
#[serde(default, skip_serializing_if = "Option::is_none")]
3636
pub acg: Option<AcgComponentConfig>,
37-
/// Opt-in exact-match LLM response and tool-result cache. When present,
38-
/// the adaptive plugin installs the response-cache execution intercept(s).
37+
/// Opt-in LLM response and tool-result cache. When present, the adaptive
38+
/// plugin installs the response-cache execution intercept(s). Reuse
39+
/// requires the same normalized request under the default
40+
/// `key_strategy = "exact_request"`; `"logical"` additionally ignores
41+
/// tool-description wording and tool ordering.
3942
#[serde(default, skip_serializing_if = "Option::is_none")]
4043
pub response_cache: Option<ResponseCacheConfig>,
4144
/// Adaptive-local unsupported-config policy.
@@ -191,8 +194,21 @@ impl Default for AcgComponentConfig {
191194
}
192195
}
193196

194-
/// Configuration for the adaptive plugin's exact-match LLM response and
195-
/// opt-in tool-result cache feature.
197+
/// Configuration for the adaptive plugin's LLM response and opt-in
198+
/// tool-result cache feature.
199+
///
200+
/// Keys derive from the normalized request. `key_strategy` selects how much
201+
/// of that request stays key-significant:
202+
///
203+
/// ```toml
204+
/// [components.config.response_cache]
205+
/// namespace = "dev-harness"
206+
/// # "exact_request" (the default) requires the same normalized request.
207+
/// # "logical" additionally lets reuse survive tool-description edits and
208+
/// # tool reordering; tool names, parameter schemas, and every other request
209+
/// # field stay key-significant. The two strategies never share entries.
210+
/// key_strategy = "logical"
211+
/// ```
196212
#[derive(Debug, Clone, Serialize, Deserialize)]
197213
#[serde(default)]
198214
pub struct ResponseCacheConfig {

crates/adaptive/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ pub mod learner;
3434
pub mod plugin_component;
3535
#[cfg(feature = "redis-backend")]
3636
pub mod redis;
37-
/// Opt-in exact-match LLM response and tool-result cache.
37+
/// Opt-in LLM response and tool-result cache with exact-request or logical keying.
3838
pub mod response_cache;
3939
mod runtime;
4040
/// Storage backends and backend traits for adaptive state persistence.

crates/adaptive/src/response_cache/key.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ const INTERNAL_DISPATCH_BACKEND_HEADER: &str = "x-nemo-relay-internal-dispatch-b
4040
/// Result of deriving a cache key for a request.
4141
#[derive(Debug, Clone, PartialEq, Eq)]
4242
pub enum KeyOutcome {
43-
/// A usable exact-match key fingerprint (`"sha256:…"`).
43+
/// A usable key fingerprint (`"sha256:…"`).
4444
Key(String),
4545
/// The request is intentionally not cacheable; the reason is a short,
4646
/// stable label suitable for telemetry.

python/nemo_relay/adaptive.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -394,7 +394,9 @@ class ResponseCacheConfig:
394394
bypass_rate: Probability in ``[0.0, 1.0]`` of skipping the cache and running live.
395395
cache_nondeterministic: Cache nondeterministic requests too; ``False``
396396
caches only requests explicitly pinned deterministic (``temperature`` = 0).
397-
key_strategy: Typed key derivation strategy.
397+
key_strategy: Typed key derivation strategy. A plain wire string is
398+
passed through unchanged so unsupported values reach validation
399+
with a field-specific diagnostic.
398400
header_allowlist: Request headers folded into the key; never auth headers.
399401
backend: Cache storage backend (``in_memory`` or ``redis``).
400402
tools: Opt-in tool-result cache; ``None`` leaves it off.
@@ -419,7 +421,11 @@ def to_dict(self) -> JsonObject:
419421
"priority": self.priority,
420422
"bypass_rate": self.bypass_rate,
421423
"cache_nondeterministic": self.cache_nondeterministic,
422-
"key_strategy": self.key_strategy.value,
424+
"key_strategy": (
425+
self.key_strategy.value
426+
if isinstance(self.key_strategy, ResponseCacheKeyStrategy)
427+
else self.key_strategy
428+
),
423429
"header_allowlist": self.header_allowlist,
424430
"backend": _normalize(self.backend),
425431
"tools": _normalize(self.tools),

python/nemo_relay/adaptive.pyi

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,9 @@ class ResponseCacheConfig:
247247
bypass_rate: Probability in ``[0.0, 1.0]`` of skipping the cache and running live.
248248
cache_nondeterministic: Cache nondeterministic requests too; ``False``
249249
caches only requests explicitly pinned deterministic (``temperature`` = 0).
250-
key_strategy: Typed key derivation strategy.
250+
key_strategy: Typed key derivation strategy. A plain wire string is
251+
passed through unchanged so unsupported values reach validation
252+
with a field-specific diagnostic.
251253
header_allowlist: Request headers folded into the key.
252254
backend: Cache storage backend (``in_memory`` or ``redis``).
253255
tools: Opt-in tool-result cache; ``None`` leaves the tool surface off.

python/tests/test_adaptive_config.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,26 @@ def test_response_cache_key_strategy_enum_serializes(self):
181181

182182
assert config.to_dict()["key_strategy"] == "logical"
183183

184+
def test_response_cache_key_strategy_string_serializes(self):
185+
config = ResponseCacheConfig(
186+
namespace="logical-cache",
187+
key_strategy=cast(ResponseCacheKeyStrategy, "logical"),
188+
)
189+
190+
assert config.to_dict()["key_strategy"] == "logical"
191+
192+
def test_response_cache_unsupported_key_strategy_string_reaches_validation(self):
193+
config = ResponseCacheConfig(
194+
namespace="unsupported-strategy",
195+
key_strategy=cast(ResponseCacheKeyStrategy, "semantic"),
196+
)
197+
198+
assert config.to_dict()["key_strategy"] == "semantic"
199+
200+
report = plugin.validate(plugin.PluginConfig(components=[ComponentSpec(AdaptiveConfig(response_cache=config))]))
201+
codes = {diag["code"] for diag in report["diagnostics"]}
202+
assert "response_cache.unsupported_key_strategy" in codes
203+
184204
def test_response_cache_default_preserves_positional_policy_argument(self):
185205
policy = ConfigPolicy(unknown_field="error")
186206
config = AdaptiveConfig(1, None, None, None, None, None, None, policy)

0 commit comments

Comments
 (0)