YAshhh29 opened a new pull request, #71575: URL: https://github.com/apache/airflow/pull/71575
When durable execution replays a cached model response or tool result, it compares a fingerprint of the current request against the fingerprint stored with the cache entry. Requests that cannot be serialized to JSON fingerprint as `None`, and `None == None` is `True` — so the guard passed for precisely the requests it could not verify, and a stale entry was replayed. ```diff - if cached_fingerprint == fingerprint: + if fingerprint is not None and cached_fingerprint == fingerprint: ``` **This is a behaviour change, not only a bug fix.** A step whose request cannot be fingerprinted now re-runs live on retry rather than replaying, which costs a model call. The existing code was ambiguous about which behaviour was intended: `fingerprint.py` logged *"cached responses for this step replay without verification"* (fail open), while the `reason=` branch immediately below the comparison explains *"entry predates fingerprinting or the request could not be fingerprinted"* — a message that could never be reached, because that case short-circuited into the replay path. This PR resolves the contradiction in favour of not replaying what cannot be verified, and documents the contract in `agent.rst`. ### Behaviour before and after The screenshots below drive the real `CachingModel` and `CachingToolset` directly (no pytest, no mocked guard). Each run prints the guard line straight out of `caching_model.py`, so the version under test is visible in the output. The scenario is identical in both: a cached answer for Q3 revenue is stored with a `None` fingerprint, then the step is retried with a request about Q4 revenue that also cannot be fingerprinted. **Before — guard as it is on `main`** <img width="1506" height="651" alt="image" src="https://github.com/user-attachments/assets/c2418e4f-5b10-46a5-ba59-cc9a54b3b012" /> The cached Q3 answer is returned in response to the Q4 question, with `live calls: 0` and `replayed steps: 1`. The model is never consulted, and nothing verified that the two requests matched. **After — guard with this PR** <img width="1485" height="678" alt="image" src="https://github.com/user-attachments/assets/92d7949c-7872-43d8-9aeb-d2179df207a1" /> The unverifiable step runs live: `live calls: 1`, `replayed steps: 0`, and the answer matches the question that was actually asked. ### Testing `test_unverifiable_current_request_treated_as_miss` and `test_unverifiable_current_call_treated_as_miss` fail without the change and pass with it. | `providers/common/ai/tests/unit/common/ai/durable` | result | | --- | --- | | guard as it is on `main` | `2 failed, 85 passed` | | guard with this PR | `87 passed` | --- ##### Was generative AI tooling used to co-author this PR? - [X] Yes — GitHub Copilot (Claude Opus 5) Generated-by: GitHub Copilot (Claude Opus 5) following [the guidelines](https://github.com/apache/airflow/blob/main/contributing-docs/05_pull_requests.rst#gen-ai-assisted-contributions) -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected]
