aborist/qa/ implements the cache-first answer flow from the providence
whitepaper, scaled up to v9.8's full 8-dim admissibility invariant.
cache_key = SHA-256 of:
source_root | question_hash | model_profile_hash | conversation_hash
| governance_policy_hash | schema_version | canonicalization_version
| chunking_version
Any drift in any dimension yields a distinct cache_key — prior records
cannot serve. Falsification states (failed/stale/quarantined) gate
every cache hit.
- qa/keys.py — pure hash functions, deterministic & testable
- qa/client.py — ChatClient Protocol + StubClient + OpenAI-compatible
HTTP client (vllm/llama.cpp/uncloseai compatible)
- qa/runner.py — ask(): lookup -> hit (no LLM call, hit_count++)
OR miss (call client, write record, audit event,
proof binds answer to source root)
- cli.py — `aborist ask` and `aborist providence` subcommands
- pyproject.toml — httpx promoted from extras to core (used by both
html and qa); selectolax stays in [html] extras
Smoke (StubClient, no network): cache miss writes record with chunk_0
Merkle proof reconstructing source_root; cache hit returns same record
without calling client; 1085 audit events chained 0 breaks across
ingest/derive/evict/rehydrate/providence_write.
96 lines
3.1 KiB
Python
96 lines
3.1 KiB
Python
"""The 8-dim Merkle-AGI v9.8 cache_key.
|
|
|
|
v9.8 invariant: no answer is reused unless all eight match and the
|
|
record is live (not failed/stale/quarantined):
|
|
|
|
1. source_root — content fingerprint of the document
|
|
2. question_hash — SHA-256 of normalized question text
|
|
3. model_profile_hash — model_id + revision + quantization
|
|
4. conversation_hash — full canonical OpenAI messages array
|
|
5. governance_policy_hash — sampling/policy parameters dict
|
|
6. schema_version — aborist DB schema version
|
|
7. canonicalization_version — text normalization rules
|
|
8. chunking_version — chunker name & parameters
|
|
|
|
Bumping ANY of these eight dimensions yields a distinct cache_key, so
|
|
prior records cannot be served. This is the runtime drift detection
|
|
the providence whitepaper compresses into "cache_key = source_root +
|
|
':' + question_hash" — that's a simplification; the rigorous form is
|
|
all eight dimensions hashed together.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import hashlib
|
|
import json
|
|
|
|
from aborist.document import canonicalize
|
|
|
|
|
|
def _sha256(s: str) -> str:
|
|
return hashlib.sha256(s.encode("utf-8")).hexdigest()
|
|
|
|
|
|
def _canonical_json(obj) -> str:
|
|
return json.dumps(obj, sort_keys=True, separators=(",", ":"), ensure_ascii=False)
|
|
|
|
|
|
def question_hash(question: str) -> str:
|
|
"""SHA-256 of canonicalized + lowercased question text."""
|
|
return _sha256(canonicalize(question).lower())
|
|
|
|
|
|
def model_profile_hash(
|
|
model_id: str, revision: str = "", quantization: str = ""
|
|
) -> str:
|
|
"""SHA-256 of model identity. Bumping any field bumps the cache key."""
|
|
return _sha256(f"{model_id}|{revision}|{quantization}")
|
|
|
|
|
|
def conversation_hash(messages: list[dict]) -> str:
|
|
"""SHA-256 of canonical JSON of the full OpenAI messages array.
|
|
|
|
Order matters: a 6-turn dialogue arriving at the same final question
|
|
produces a different hash than a single-turn ask.
|
|
"""
|
|
return _sha256(_canonical_json(messages))
|
|
|
|
|
|
def governance_policy_hash(policy: dict) -> str:
|
|
"""SHA-256 of canonical JSON of the sampling/policy dict.
|
|
|
|
Includes temperature, top_p, max_tokens, and the system prompt — any
|
|
of those changing means the answer is governed differently and the
|
|
cache must miss.
|
|
"""
|
|
return _sha256(_canonical_json(policy))
|
|
|
|
|
|
def cache_key(
|
|
source_root: str,
|
|
question_hash_value: str,
|
|
model_profile_hash_value: str,
|
|
conversation_hash_value: str,
|
|
governance_policy_hash_value: str,
|
|
schema_version: str,
|
|
canonicalization_version: str,
|
|
chunking_version: str,
|
|
) -> str:
|
|
"""SHA-256 of all 8 dimensions joined with '|'.
|
|
|
|
Any drift in any dimension produces a distinct cache_key.
|
|
"""
|
|
return _sha256(
|
|
"|".join(
|
|
[
|
|
source_root,
|
|
question_hash_value,
|
|
model_profile_hash_value,
|
|
conversation_hash_value,
|
|
governance_policy_hash_value,
|
|
schema_version,
|
|
canonicalization_version,
|
|
chunking_version,
|
|
]
|
|
)
|
|
)
|