llm-message-hash-py
Stable canonical sha256 hash of LLM request/message structures.
Two semantically identical Anthropic requests can produce different
sha256(json.dumps(req)) results because Python dict iteration order is
not part of the value, and fields like cache_control change the bytes
without changing what gets sent to the model. This library walks the
value tree, sorts dict keys recursively, drops a configurable set of
fields, and sha256s the canonical bytes.
Useful for prompt-cache lookups, idempotency keys, and dedupe.
Sibling to the Rust crate
llm-message-hash.
Install
pip install llm-message-hash-py
Use
Default (no fields dropped):
from llm_message_hash import hash_request
a = {"model": "claude", "messages": [{"role": "user", "content": "hi"}]}
b = {"messages": [{"content": "hi", "role": "user"}], "model": "claude"}
assert hash_request(a) == hash_request(b)
Per-provider preset (drops cache_control, response-only fields, etc.):
from llm_message_hash import HashOpts, hash_request
with_cc = {
"messages": [{
"role": "user",
"content": [{"type": "text", "text": "hi", "cache_control": {"type": "ephemeral"}}],
}],
}
without_cc = {
"messages": [{
"role": "user",
"content": [{"type": "text", "text": "hi"}],
}],
}
h1 = hash_request(with_cc, HashOpts.for_anthropic())
h2 = hash_request(without_cc, HashOpts.for_anthropic())
assert h1 == h2
You can also get the canonical bytes directly:
from llm_message_hash import canonical_json
s = canonical_json({"b": 1, "a": 2})
assert s == '{"a":2,"b":1}'
Presets
Each preset drops the response-side metadata that varies per call plus provider-specific request fields that do not change semantics:
| Preset | Drops |
|---|---|
HashOpts.for_anthropic() |
cache_control, id, usage, stop_reason, stop_sequence |
HashOpts.for_openai() |
created, id, object, system_fingerprint, usage, finish_reason |
HashOpts.for_bedrock() |
cache_control, usage, stopReason, metrics |
HashOpts.for_gemini() |
usageMetadata, safetyRatings, finishReason |
Extend any preset:
opts = HashOpts.for_anthropic()
opts.drop_keys.add("metadata")
Or build your own:
opts = HashOpts(drop_keys={"trace_id", "request_id"})
Drop key behavior
drop_keys matches exact key names at any depth. A key named in
drop_keys is removed from every dict it appears in, no matter how
deeply nested. List order is preserved (a list is structurally
significant). Strings are case sensitive: "hi" and "Hi" hash
differently. Numbers compare by their JSON representation: 42 and
42.0 are different strings and so hash differently.
What it does NOT do
- No tokenization. The hash is over structure, not token count.
- No semantic equivalence beyond key-order normalization and the drop list.
- No streaming. Pass a complete Python object.
- No HTTP. Does not talk to any LLM provider.
License
MIT
Metadata
Release files for llm-message-hash-py 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| llm_message_hash_py-0.1.0.tar.gz | 7.0 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| llm_message_hash_py-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 13.2 kB
Release files / llm_message_hash_py-0.1.0.tar.gz
| Download URL | llm_message_hash_py-0.1.0.tar.gz |
|---|---|
| Size | 7.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
14a585668783c8fd818e3b447ffe8a5d55c4d40ac44e6fc1037abf2eef55cfe5
|
|
BLAKE2b-256 checksum How to use checksums |
b61b6d9828f00f9ae8872f3217d11557bd87fb77b539b5096ffa6aae4cf0679a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.5
|
Release files / llm_message_hash_py-0.1.0-py3-none-any.whl
| Download URL | llm_message_hash_py-0.1.0-py3-none-any.whl |
|---|---|
| Size | 6.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
a07dabd2e458d467609f57ddc75a9261e595751864ea18aff7fe58c91d52a51c
|
|
BLAKE2b-256 checksum How to use checksums |
c6717acce332c81d2fcfaeede3698f03cbe84c78dad13fa2e4b6ff286494b390
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.5
|
Security for llm-message-hash-py 0.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.