fix(llm): commit the staged head hash unconditionally on a recorded response

An unchained request with no system message staged None, and the record
step skipped the commit, so the previous head hash survived a transcript
that never received a head; a later chained request restoring that head
would have omitted it. The staged value is now committed as-is, None
included. Also pins each rejection predicate of is_usable_compression_point
with its own test.
This commit is contained in:
Alex committed 2026-09-05 11:26:25 +01:00
1 parent 5837c300df
commit 427d85d737
3 files changed
+51 -4

No files matched your search

+6 -4
View File
@@ -1423,10 +1423,12 @@ class OpenAILLM(BaseLLM):
if rid:
self._last_response_id = rid
# The provider recorded this request, so the head it carried (or
# kept) is now the chain's head.
if self._pending_system_hash is not None:
self._chain_system_hash = self._pending_system_hash
self._pending_system_hash = None
# kept) is now the chain's head — including "no head at all", when
# an unchained request went out without a system message: a stale
# hash would let a later chained request omit a head this
# transcript never received.
self._chain_system_hash = self._pending_system_hash
self._pending_system_hash = None
self._last_response_call_ids = self._function_call_ids(response)
usage = getattr(response, "usage", None)
if usage is not None:
@@ -499,3 +499,34 @@ class TestUnusableSavedPoints:
assert result.compression_performed
assert svc.compress_and_save.call_args.kwargs["start_index"] == 2
@pytest.mark.unit
class TestUsablePointPredicates:
"""Each rejection predicate of ``is_usable_compression_point`` on its own."""
def _with_later_point(self, **point):
conv = _compressed_conversation()
conv["queries"].append({"prompt": "q4", "response": "r4"})
conv["compression_metadata"]["compression_points"].append(
{"timestamp": "2026-09-04T09:00:00+00:00", "query_index": 3, **point}
)
return conv
def test_a_positive_count_does_not_rescue_a_blank_summary(self):
conv = self._with_later_point(compressed_summary=" ", compressed_token_count=12)
summary, recent = CompressionService(llm=None, model_id="m").get_compressed_context(conv)
assert summary == "S"
assert [q["prompt"] for q in recent] == ["q2", "q3", "q4"]
def test_a_zero_count_does_not_rescue_a_non_blank_summary(self):
conv = self._with_later_point(compressed_summary="newer", compressed_token_count=0)
summary, recent = CompressionService(llm=None, model_id="m").get_compressed_context(conv)
assert summary == "S"
assert [q["prompt"] for q in recent] == ["q2", "q3", "q4"]
def test_a_missing_count_with_a_summary_is_usable(self):
conv = self._with_later_point(compressed_summary="newer")
summary, recent = CompressionService(llm=None, model_id="m").get_compressed_context(conv)
assert summary == "newer"
assert [q["prompt"] for q in recent] == ["q4"]
+14
View File
@@ -402,3 +402,17 @@ def test_output_capped_response_still_records_chain_state(monkeypatch):
assert llm._responses_gen("gpt-5.6", _messages(), tools=None) == "partial"
assert llm._last_response_id == "resp_len"
assert llm._chain_system_hash is not None
@pytest.mark.unit
def test_recorded_request_without_a_head_clears_the_committed_hash(monkeypatch):
llm = _make_llm(monkeypatch)
llm._build_responses_input(_messages("sys v1"), None)
_accepted(llm, "resp_1")
# An unchained fallback with no system message at all is recorded: the
# new transcript holds no head, so the old hash must not survive it.
llm._build_responses_input([{"role": "user", "content": "q"}], None)
_accepted(llm, "resp_2")
assert llm._chain_system_hash is None
chained, _ = llm._build_responses_input(_messages("sys v1"), "resp_2")
assert _roles(chained) == ["system", "user"]