Yeda AI Tips · #154

Español

Log a Hash of the Messages Array Before Every Call

Your AI agent is not dumb. Your code is editing its memory behind its back. The model answers perfectly in a clean playground, then goes sideways the moment it runs through your app. Before you file a bug against the model, look at the one thing you control: the messages array you send on every call.

Why the model is fine but your agent isn't

Chat APIs are stateless. The provider keeps no memory of your conversation — you own the full history and resend the entire messages list on every request (Anthropic and OpenAI both work this way). That design is clean, but it means every layer between your user and the model gets a chance to touch the array first: a summarizer that trims old turns, a retriever that injects context, a middleware that reorders system messages, a retry that drops the last turn. The model only ever sees the array you hand it. If that array is quietly wrong, the model's answer is quietly wrong — and it looks like the model hallucinated.

The one-line audit: hash the history

You can't eyeball a 40-message array on every call. So don't. Compute a stable hash of the messages right before you send them, and log it:

import hashlib, json

def messages_fingerprint(messages):
    blob = json.dumps(messages, sort_keys=True, ensure_ascii=False)
    return hashlib.sha256(blob.encode("utf-8")).hexdigest()[:12]

fp = messages_fingerprint(messages)
logger.info("llm.call turns=%d fp=%s", len(messages), fp)
response = client.chat.completions.create(model=MODEL, messages=messages)

Now the rule is trivial to check: the hash should only change when a new turn is appended. If the fingerprint changes between two calls but no user or assistant turn was added, some layer rewrote history. sort_keys=True makes the serialization order-independent, so key ordering never triggers a false alarm — a changed hash means the content or shape of the array actually changed.

Reading the signal

What you seeWhat it means
Hash changes, turns count went up by 1Normal — a new turn was appended.
Hash changes, turns count unchangedA layer edited an existing turn in place.
Hash changes, turns count went downSomething dropped or truncated history.
System message hash differs across callsPrompt caching just broke — every call is a cache miss.
Same fingerprint, wrong answerNow you can trust the array — investigate the model or params.

That last row is the real payoff: the fingerprint lets you rule the plumbing out. Once the hash is stable and correct, you've earned the right to blame the model.

Power tricks

messages[0] (or the system field). A byte-identical system prefix is what prompt caching keys on; if that hash drifts — an interpolated timestamp, a request ID, a username — you're paying full input price on every call and probably don't know it.

different hashes, log a compact diff of the array (or the per-message hashes) so you see which turn was mutated, not just that something was.

at the boundary, and assert the fingerprint only advances when you append. This catches a context-mangling regression in CI instead of in production.

call as you can — ideally in the client wrapper itself. Anything logged upstream of a middleware can't see what that middleware did.

Resources

Yeda AI Tips — one AI tip a day keeps the busywork away.