mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-08-04 11:43:57 +08:00
fix(proxy): improve cache hit rate for Codex/Responses requests
prompt_cache_key was falling back to provider.id when the client did not supply a session, which collapsed every conversation onto a single key and defeated upstream prefix caching. Only emit the key when a real client-provided session/thread identity is available; otherwise let the upstream use its default matching behaviour. Additional fixes that affect cache stability: - Canonicalise (sort) JSON keys in outgoing request bodies and in tool_call arguments / tool_result content so semantically identical requests produce identical byte sequences for upstream prefix caches. - Exempt JSON Schema property maps (properties, patternProperties, definitions, \$defs) from the underscore-prefix filter so user-defined schema keys like _id and _meta survive. - Add a [CacheTrace] debug log with stable hashes for instructions, tools, input and include to help diagnose cache misses. - Thread session_id into the usage logger for request correlation.
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
//! Stable JSON helpers for cache-sensitive request bodies.
|
||||
|
||||
use serde_json::Value;
|
||||
use sha2::{Digest, Sha256};
|
||||
|
||||
pub(crate) fn canonicalize_value(value: Value) -> Value {
|
||||
match value {
|
||||
Value::Array(values) => Value::Array(values.into_iter().map(canonicalize_value).collect()),
|
||||
Value::Object(map) => {
|
||||
let mut entries = map.into_iter().collect::<Vec<_>>();
|
||||
entries.sort_by(|(left, _), (right, _)| left.cmp(right));
|
||||
|
||||
let mut sorted = serde_json::Map::new();
|
||||
for (key, value) in entries {
|
||||
sorted.insert(key, canonicalize_value(value));
|
||||
}
|
||||
Value::Object(sorted)
|
||||
}
|
||||
other => other,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn canonical_json_string(value: &Value) -> String {
|
||||
match value {
|
||||
Value::Null => "null".to_string(),
|
||||
Value::Bool(value) => value.to_string(),
|
||||
Value::Number(value) => value.to_string(),
|
||||
Value::String(value) => serde_json::to_string(value)
|
||||
.expect("serializing a JSON string for canonical output should not fail"),
|
||||
Value::Array(values) => {
|
||||
let parts = values.iter().map(canonical_json_string).collect::<Vec<_>>();
|
||||
format!("[{}]", parts.join(","))
|
||||
}
|
||||
Value::Object(map) => {
|
||||
let mut entries = map.iter().collect::<Vec<_>>();
|
||||
entries.sort_by_key(|(left, _)| *left);
|
||||
let parts = entries
|
||||
.into_iter()
|
||||
.map(|(key, value)| {
|
||||
let key = serde_json::to_string(key).expect(
|
||||
"serializing a JSON object key for canonical output should not fail",
|
||||
);
|
||||
format!("{key}:{}", canonical_json_string(value))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
format!("{{{}}}", parts.join(","))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn short_value_hash(value: Option<&Value>) -> String {
|
||||
let Some(value) = value else {
|
||||
return "absent".to_string();
|
||||
};
|
||||
short_sha256_hex(canonical_json_string(value).as_bytes())
|
||||
}
|
||||
|
||||
pub(crate) fn short_sha256_hex(bytes: &[u8]) -> String {
|
||||
let digest = Sha256::digest(bytes);
|
||||
digest
|
||||
.iter()
|
||||
.take(8)
|
||||
.map(|byte| format!("{byte:02x}"))
|
||||
.collect::<String>()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use serde_json::json;
|
||||
|
||||
#[test]
|
||||
fn canonical_json_string_sorts_nested_object_keys() {
|
||||
let left = json!({
|
||||
"b": 2,
|
||||
"a": {
|
||||
"d": true,
|
||||
"c": [3, {"z": 1, "y": 2}]
|
||||
}
|
||||
});
|
||||
let right = json!({
|
||||
"a": {
|
||||
"c": [3, {"y": 2, "z": 1}],
|
||||
"d": true
|
||||
},
|
||||
"b": 2
|
||||
});
|
||||
|
||||
assert_eq!(canonical_json_string(&left), canonical_json_string(&right));
|
||||
assert_eq!(
|
||||
short_value_hash(Some(&left)),
|
||||
short_value_hash(Some(&right))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn canonicalize_value_sorts_map_storage_order() {
|
||||
let value = canonicalize_value(json!({"b": 2, "a": 1}));
|
||||
|
||||
assert_eq!(serde_json::to_string(&value).unwrap(), r#"{"a":1,"b":2}"#);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user