From 33778feb70f2ad74a0890cf9aa8837f7732020ef Mon Sep 17 00:00:00 2001 From: Ray Date: Thu, 27 Aug 2026 03:07:37 +0800 Subject: [PATCH 1/2] test: the messages max_tokens test follows anthropic 1.1's stop policy anthropic 1.1.0 replaced the runner's refusal special case with an explicit stop-reason table: every non-tool_use stop is terminal and its tool_use blocks are never executed (1.0 executed a max_tokens turn's complete blocks). The envelope already keys on the runner's history, so the product adapts by design; only the test had the 1.0 behavior baked in. It now asserts the version-appropriate shape on both sides, and the run_messages comment describing the old behavior is reworded to name the policy split. Verified: 434 green on anthropic 1.1.0 (CI's failing config) and on 0.120.2 (the pre-1.1 branch); no-frameworks collection stays clean. --- pageindex/local_chat.py | 11 ++++++----- tests/test_local_chat.py | 29 ++++++++++++++++++++++++----- 2 files changed, 30 insertions(+), 10 deletions(-) diff --git a/pageindex/local_chat.py b/pageindex/local_chat.py index 4634d7a65..e93449c51 100644 --- a/pageindex/local_chat.py +++ b/pageindex/local_chat.py @@ -1130,11 +1130,12 @@ def capture(params): envelope["usage"] = _anthropic_usage(turns, envelope.get("usage") or {}) # The full turn sequence (assistant tool_use + user tool_result + final), # valid for verbatim append to the caller's history. The runner appends - # a turn to its params only when it executed tools from it — content - # carried tool_use blocks and the turn was not a refusal. stop_reason - # alone cannot tell: a max_tokens turn with complete tool_use blocks - # still executes. Whether final's tool_use ids already sit in the - # history is the ground truth for "already appended". + # a turn to its params only when it executed tools from it, and which + # turns qualify is vendor policy (anthropic < 1.1 executes a max_tokens + # turn's complete tool_use blocks; 1.1+ treats every non-tool_use stop + # as terminal), so stop_reason alone cannot tell. Whether final's + # tool_use ids already sit in the history is the ground truth for + # "already appended". new_messages = [_dump_message(message) for message in conversation[len(prepared):]] final_blocks = [_dump_block(item) for item in final.content] diff --git a/tests/test_local_chat.py b/tests/test_local_chat.py index 4605c52a5..64d09cdd6 100644 --- a/tests/test_local_chat.py +++ b/tests/test_local_chat.py @@ -664,8 +664,17 @@ def test_responses_stream_events_validate_as_official_events( try: import anthropic _HAS_ANTHROPIC = True + # anthropic 1.1 made every non-tool_use stop reason terminal: the runner + # no longer executes tool_use blocks from a max_tokens-cut turn. + try: + _ANTHROPIC_RUNS_CUT_TOOL_TURNS = tuple( + int(piece) for piece in anthropic.__version__.split(".")[:2] + ) < (1, 1) + except ValueError: + _ANTHROPIC_RUNS_CUT_TOOL_TURNS = False # unparseable: assume current except ImportError: _HAS_ANTHROPIC = False + _ANTHROPIC_RUNS_CUT_TOOL_TURNS = False needs_anthropic = pytest.mark.skipif(not _HAS_ANTHROPIC, reason="anthropic not installed") @@ -1653,9 +1662,12 @@ def test_messages_max_turns_truncation_round_trippable(client, store_path, def test_messages_tool_use_cut_by_max_tokens_not_duplicated(client, store_path, fake_anthropic): - """A max_tokens turn with complete tool_use blocks still executes and - is appended by the runner — keying the re-append guard on stop_reason - duplicated the tool_use id and broke verbatim continuation.""" + """A max_tokens turn carrying complete tool_use blocks: anthropic < 1.1 + executes and appends it, 1.1+ treats it as terminal and never executes. + The envelope keys on the runner's history rather than stop_reason + (keying on stop_reason once duplicated the tool_use id), so on either + side the id never duplicates and the appendable messages stay valid + for verbatim continuation.""" seed_doc(store_path, "pi-a", "report.pdf") calls = fake_anthropic([ _anthropic_message([_anthropic_tool_use()], "max_tokens"), @@ -1666,8 +1678,15 @@ def test_messages_tool_use_cut_by_max_tokens_not_duplicated(client, assert len(calls) == 1 assert result["stop_reason"] == "max_tokens" roles = [message["role"] for message in result["messages"]] - assert roles == ["assistant", "user"] # tool_use, tool_result — no dup - assert json.dumps(result["messages"]).count('"tu_1"') == 2 # use + result + if _ANTHROPIC_RUNS_CUT_TOOL_TURNS: + assert roles == ["assistant", "user"] # tool_use then tool_result + assert json.dumps(result["messages"]).count('"tu_1"') == 2 # use + result + else: + # Unexecuted tool_use has no tool_result: stripped from the + # appendable history, still visible in content. + assert roles == [] + assert json.dumps(result["messages"]).count('"tu_1"') == 0 + assert [block["type"] for block in result["content"]] == ["tool_use"] @needs_anthropic From d559f21ec5823c4ad520fe6dcb866a7aae8a3f45 Mon Sep 17 00:00:00 2001 From: Ray Date: Thu, 27 Aug 2026 03:34:45 +0800 Subject: [PATCH 2/2] docs: the run_messages comment keeps the principle, drops the version numbers The rationale to preserve is that execution policy belongs to the vendor and only the runner's history is ground truth; the exact 1.0/1.1 switch point lives in the previous commit's message. --- pageindex/local_chat.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/pageindex/local_chat.py b/pageindex/local_chat.py index e93449c51..e32932d2b 100644 --- a/pageindex/local_chat.py +++ b/pageindex/local_chat.py @@ -1131,11 +1131,10 @@ def capture(params): # The full turn sequence (assistant tool_use + user tool_result + final), # valid for verbatim append to the caller's history. The runner appends # a turn to its params only when it executed tools from it, and which - # turns qualify is vendor policy (anthropic < 1.1 executes a max_tokens - # turn's complete tool_use blocks; 1.1+ treats every non-tool_use stop - # as terminal), so stop_reason alone cannot tell. Whether final's - # tool_use ids already sit in the history is the ground truth for - # "already appended". + # turns qualify is vendor policy that has changed across anthropic + # releases, so stop_reason alone cannot tell. Whether final's tool_use + # ids already sit in the history is the ground truth for "already + # appended". new_messages = [_dump_message(message) for message in conversation[len(prepared):]] final_blocks = [_dump_block(item) for item in final.content]