feat(cache-fingerprint): 新增字段级指纹,支持逐字段 sha256 和字节数追踪

在请求缓存指纹中为每个 cache_relevant 字段单独计算 sha256 和字节数,
便于精确定位哪些字段发生了变化。fingerprint version 升级为 2。
This commit is contained in:
fawney19
2026-03-17 02:57:36 +08:00
parent b6cc0bc3a7
commit b9b0a75fe4
2 changed files with 41 additions and 1 deletions

View File

@@ -101,6 +101,22 @@ def _extract_cache_relevant_payload(
return subset, sorted(subset.keys()) return subset, sorted(subset.keys())
def _build_field_fingerprints(payload: Any, field_names: list[str]) -> dict[str, dict[str, Any]]:
if not isinstance(payload, Mapping) or not field_names:
return {}
field_fingerprints: dict[str, dict[str, Any]] = {}
for field_name in field_names:
if field_name not in payload:
continue
field_sha256, field_bytes = _hash_json_payload(payload[field_name])
field_fingerprints[field_name] = {
"sha256": field_sha256,
"bytes": field_bytes,
}
return field_fingerprints
def build_request_cache_fingerprint( def build_request_cache_fingerprint(
provider_request_body: Any, provider_request_body: Any,
*, *,
@@ -117,13 +133,14 @@ def build_request_cache_fingerprint(
normalized_format, normalized_format,
) )
cache_relevant_sha256, cache_relevant_bytes = _hash_json_payload(cache_relevant_payload) cache_relevant_sha256, cache_relevant_bytes = _hash_json_payload(cache_relevant_payload)
field_fingerprints = _build_field_fingerprints(cache_relevant_payload, cache_relevant_keys)
top_level_keys = [] top_level_keys = []
if isinstance(provider_request_body, Mapping): if isinstance(provider_request_body, Mapping):
top_level_keys = sorted(str(key) for key in provider_request_body.keys()) top_level_keys = sorted(str(key) for key in provider_request_body.keys())
fingerprint: dict[str, Any] = { fingerprint: dict[str, Any] = {
"version": 1, "version": 2,
"provider_api_format": normalized_format, "provider_api_format": normalized_format,
"payload_sha256": payload_sha256, "payload_sha256": payload_sha256,
"payload_bytes": payload_bytes, "payload_bytes": payload_bytes,
@@ -131,6 +148,7 @@ def build_request_cache_fingerprint(
"cache_relevant_bytes": cache_relevant_bytes, "cache_relevant_bytes": cache_relevant_bytes,
"top_level_keys": top_level_keys, "top_level_keys": top_level_keys,
"cache_relevant_keys": cache_relevant_keys, "cache_relevant_keys": cache_relevant_keys,
"field_fingerprints": field_fingerprints,
} }
prompt_cache_key = _get_prompt_cache_key(provider_request_body) prompt_cache_key = _get_prompt_cache_key(provider_request_body)

View File

@@ -65,8 +65,10 @@ def test_build_request_cache_fingerprint_is_stable_for_dict_key_reordering() ->
assert fingerprint_a is not None assert fingerprint_a is not None
assert fingerprint_b is not None assert fingerprint_b is not None
assert fingerprint_a["version"] == 2
assert fingerprint_a["payload_sha256"] == fingerprint_b["payload_sha256"] assert fingerprint_a["payload_sha256"] == fingerprint_b["payload_sha256"]
assert fingerprint_a["cache_relevant_sha256"] == fingerprint_b["cache_relevant_sha256"] assert fingerprint_a["cache_relevant_sha256"] == fingerprint_b["cache_relevant_sha256"]
assert fingerprint_a["field_fingerprints"] == fingerprint_b["field_fingerprints"]
assert fingerprint_a["prompt_cache_key"] == "pcache-123" assert fingerprint_a["prompt_cache_key"] == "pcache-123"
assert fingerprint_a["cache_relevant_keys"] == [ assert fingerprint_a["cache_relevant_keys"] == [
"input", "input",
@@ -75,6 +77,8 @@ def test_build_request_cache_fingerprint_is_stable_for_dict_key_reordering() ->
"prompt_cache_key", "prompt_cache_key",
"tools", "tools",
] ]
assert fingerprint_a["field_fingerprints"]["instructions"]["bytes"] > 0
assert fingerprint_a["field_fingerprints"]["tools"]["bytes"] > 0
def test_build_request_cache_fingerprint_ignores_non_prompt_fields_in_cache_hash() -> None: def test_build_request_cache_fingerprint_ignores_non_prompt_fields_in_cache_hash() -> None:
@@ -89,6 +93,8 @@ def test_build_request_cache_fingerprint_ignores_non_prompt_fields_in_cache_hash
assert fingerprint_b is not None assert fingerprint_b is not None
assert fingerprint_a["payload_sha256"] != fingerprint_b["payload_sha256"] assert fingerprint_a["payload_sha256"] != fingerprint_b["payload_sha256"]
assert fingerprint_a["cache_relevant_sha256"] == fingerprint_b["cache_relevant_sha256"] assert fingerprint_a["cache_relevant_sha256"] == fingerprint_b["cache_relevant_sha256"]
assert fingerprint_a["field_fingerprints"] == fingerprint_b["field_fingerprints"]
assert "temperature" not in fingerprint_a["field_fingerprints"]
def test_build_request_cache_fingerprint_tracks_prompt_changes() -> None: def test_build_request_cache_fingerprint_tracks_prompt_changes() -> None:
@@ -102,6 +108,18 @@ def test_build_request_cache_fingerprint_tracks_prompt_changes() -> None:
assert fingerprint_a is not None assert fingerprint_a is not None
assert fingerprint_b is not None assert fingerprint_b is not None
assert fingerprint_a["cache_relevant_sha256"] != fingerprint_b["cache_relevant_sha256"] assert fingerprint_a["cache_relevant_sha256"] != fingerprint_b["cache_relevant_sha256"]
assert (
fingerprint_a["field_fingerprints"]["instructions"]["sha256"]
!= fingerprint_b["field_fingerprints"]["instructions"]["sha256"]
)
assert (
fingerprint_a["field_fingerprints"]["input"]["sha256"]
== fingerprint_b["field_fingerprints"]["input"]["sha256"]
)
assert (
fingerprint_a["field_fingerprints"]["tools"]["sha256"]
== fingerprint_b["field_fingerprints"]["tools"]["sha256"]
)
def test_sanitize_request_metadata_preserves_cache_fingerprint( def test_sanitize_request_metadata_preserves_cache_fingerprint(
@@ -163,8 +181,10 @@ async def test_message_telemetry_record_success_keeps_response_shape_and_adds_fi
metadata = captured["metadata"] metadata = captured["metadata"]
assert metadata["model_version"] == "gpt-5.4-2026-03-01" assert metadata["model_version"] == "gpt-5.4-2026-03-01"
assert "response" not in metadata assert "response" not in metadata
assert metadata["cache_fingerprint"]["version"] == 2
assert metadata["cache_fingerprint"]["provider_api_format"] == "openai:cli" assert metadata["cache_fingerprint"]["provider_api_format"] == "openai:cli"
assert metadata["cache_fingerprint"]["prompt_cache_key"] == "pcache-123" assert metadata["cache_fingerprint"]["prompt_cache_key"] == "pcache-123"
assert "instructions" in metadata["cache_fingerprint"]["field_fingerprints"]
@pytest.mark.asyncio @pytest.mark.asyncio
@@ -203,5 +223,7 @@ async def test_message_telemetry_record_failure_keeps_request_metadata_and_adds_
metadata = captured["metadata"] metadata = captured["metadata"]
assert metadata["perf"]["ttfb_ms"] == 12 assert metadata["perf"]["ttfb_ms"] == 12
assert metadata["cache_fingerprint"]["version"] == 2
assert metadata["cache_fingerprint"]["provider_api_format"] == "openai:cli" assert metadata["cache_fingerprint"]["provider_api_format"] == "openai:cli"
assert metadata["cache_fingerprint"]["prompt_cache_key"] == "pcache-123" assert metadata["cache_fingerprint"]["prompt_cache_key"] == "pcache-123"
assert "input" in metadata["cache_fingerprint"]["field_fingerprints"]