Keep a repeated user prompt when the turn is genuinely new (#781) - #855
Open
phanisaimunipalli wants to merge 1 commit into
Open
Keep a repeated user prompt when the turn is genuinely new (#781)#855phanisaimunipalli wants to merge 1 commit into
phanisaimunipalli wants to merge 1 commit into
Conversation
…erger#781) hash_message is timestamp-independent, so answering two different questions with "yes" produces the same hash both times. The merge loop skipped the second one as a duplicate, and because that left the history ending on a ModelResponse, the trailing-response pop then removed the earlier assistant answer as well. Both sides of the exchange disappeared, and the loss was written back to agent._message_history permanently. The comment above the collision guard already described the intended behaviour; the unconditional `continue` one line earlier meant the guard was only ever reached for messages that were not in existing_hashes. Fixed by distinguishing a collision from a real duplicate using the message count. pydantic-ai passes the history it knows about plus this turn's new prompt, so being handed more messages than we hold means the last one is a new turn even when its hash matches an earlier one. The one-line fix suggested in the issue, exempting the last index unconditionally, breaks test_dedupes_by_hash, because an identical resend also ends on a colliding last message. Verified both directions: reverting to the original code fails the new regression test, and the unconditional variant fails test_dedupes_by_hash. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #781.
hash_messageis deliberately timestamp-independent, so answering two different questions withyesproduces the same hash both times:The merge loop skipped the second one as a duplicate. Because that left history ending on a
ModelResponse, the trailing-response pop below it then removed the earlier assistant answer too. Running the real merge on a two-turn conversation:Three messages in, one out, and it is written back to
agent._message_historypermanently. The model never sees the second question and loses its own previous answer.Worth noting the comment already sitting above the collision guard:
That is exactly right, but the unconditional
continueone line earlier meant the guard was only ever reached for messages that were not already inexisting_hashes— so it never protected the case it describes.The fix
A hash collision is not the same thing as a duplicate. pydantic-ai passes the history it already knows about plus this turn's new prompt, so when it hands us more messages than we are holding, the last one is a genuinely new turn regardless of what its hash matches.
Why not the one-liner in the issue
The issue suggests exempting the last index unconditionally (
if h in existing_hashes and i != last_idx). That breakstest_dedupes_by_hash, because an identical resend with no new turn also ends on a colliding last message, and history would grow every time. The message-count check separates the two cases.I verified all three states rather than assuming:
maintest_dedupes_by_hashfailsTesting
Two new tests in
tests/agents/test_compaction.py: one for the repeated prompt, asserting both that the second turn survives and that the earlier assistant answer is not destroyed; and one guarding the opposite direction, that an identical resend with no new turn still dedupes.tests/agents/is 405 passed.ruff checkandruff formatclean at 0.15.