Files
hermes-agent/tests/agent/test_native_preflight_estimate.py
T
liuhao1024 222fda3d6b fix(compression): use the route-aware pruned estimate in the mid-turn pre-API guard
The #96155 fix (#96644) made the turn-prologue preflight estimate the
checkpoint-pruned native Responses payload, but the independent mid-turn
pre-API pressure guard in conversation_loop still estimated the full
assembled durable history. On a compacted native-Codex session the
generic figure overstates the wire by orders of magnitude (the issue's
deterministic probe: 1,037,241 generic vs 6,036 pruned, 171x), so the
guard false-tripped a 600-second local compression the main request
never needed — the live sequence shows the actual request then fit at
164k input tokens against a 765k threshold (#96995).

Extract the guard's pressure figure into _midturn_request_pressure_tokens
and mirror the turn-prologue: when native Responses compaction is proven
eligible, use estimate_native_responses_preflight_tokens (system prompt
and tools included, checkpoint-pruned); otherwise keep the generic
message+tools figure. Passing the assembled api_messages alongside
effective_system counts the system prompt exactly once — the estimator's
converter skips system-role rows and adds the prompt separately.
total_chars (verbose log proxy) and the non-codex paths are unchanged.

Fixes #96995
2026-08-30 05:15:46 -07:00

149 lines
5.4 KiB
Python

"""Native Responses preflight must count the checkpoint-pruned wire (#96155)."""
from types import SimpleNamespace
from agent.codex_responses_adapter import estimate_native_responses_preflight_tokens
from agent.model_metadata import estimate_request_tokens_rough
from agent.turn_context import _preflight_request_tokens
def _codex_agent(**over):
agent = SimpleNamespace(
api_mode="codex_responses",
provider="openai-codex",
model="gpt-5.6",
base_url="https://chatgpt.com/backend-api/codex",
_base_url_hostname="chatgpt.com",
_base_url_lower="https://chatgpt.com/backend-api/codex",
codex_responses_native_compaction=True,
compression_enabled=True,
_codex_reasoning_replay_enabled=True,
context_compressor=SimpleNamespace(threshold_tokens=765_000),
tools=None,
)
for key, value in over.items():
setattr(agent, key, value)
return agent
def _history_with_checkpoint():
# Pre-checkpoint assistant/tool rows are dropped from the wire; user
# asks are retained. A durable estimate that counts those dropped rows
# is what falsely tripped local compression in #96155.
pre = []
for i in range(30):
pre.append({"role": "user", "content": f"ask {i}"})
pre.append({"role": "assistant", "content": "working " + ("tool output " * 400)})
pre.append(
{
"role": "tool",
"content": "result " + ("payload " * 400),
"tool_call_id": f"call-{i}",
}
)
checkpoint_turn = {
"role": "assistant",
"content": "checkpointed turn",
"codex_reasoning_items": [
{
"type": "compaction",
"encrypted_content": "blob",
"_issuer_kind": "codex_backend",
}
],
}
tail = [{"role": "user", "content": "follow-up after checkpoint"}]
return pre + [checkpoint_turn] + tail
def test_returns_none_when_api_mode_is_not_codex_responses():
agent = _codex_agent(api_mode="chat_completions")
assert estimate_native_responses_preflight_tokens(agent, _history_with_checkpoint()) is None
def test_returns_none_when_native_compaction_is_disabled():
agent = _codex_agent(codex_responses_native_compaction=False)
assert estimate_native_responses_preflight_tokens(agent, _history_with_checkpoint()) is None
def test_returns_none_when_model_is_outside_gpt56_family():
agent = _codex_agent(model="gpt-5.2")
assert estimate_native_responses_preflight_tokens(agent, _history_with_checkpoint()) is None
def test_pruned_estimate_is_far_below_durable_transcript():
agent = _codex_agent()
messages = _history_with_checkpoint()
generic = estimate_request_tokens_rough(messages)
native = estimate_native_responses_preflight_tokens(agent, messages)
assert native is not None
assert generic > native * 2
assert native < 8_000
def test_preflight_wrapper_uses_pruned_estimate_when_eligible():
agent = _codex_agent()
messages = _history_with_checkpoint()
native = estimate_native_responses_preflight_tokens(agent, messages)
wrapped = _preflight_request_tokens(agent, messages, "")
assert native is not None
assert wrapped == native
def test_preflight_wrapper_falls_back_to_generic_when_ineligible():
agent = _codex_agent(api_mode="chat_completions")
messages = _history_with_checkpoint()
generic = estimate_request_tokens_rough(messages)
assert _preflight_request_tokens(agent, messages, "") == generic
# ── Mid-turn pre-API guard parity (#96995) ────────────────────────────────
# The mid-turn guard in conversation_loop must measure the same pruned wire
# payload the turn-prologue preflight does (#96644/#96155); before #96995 it
# used the generic durable-history estimate and false-tripped 600s local
# compression on compacted native-Codex sessions.
def test_midturn_pressure_uses_pruned_estimate_when_eligible():
from agent.conversation_loop import (
_midturn_request_pressure_tokens,
estimate_messages_tokens_rough,
)
agent = _codex_agent()
messages = [{"role": "system", "content": "be brief"}] + _history_with_checkpoint()
native = estimate_native_responses_preflight_tokens(
agent, messages, system_prompt="be brief"
)
generic = estimate_messages_tokens_rough(messages)
assert native is not None
assert generic > native * 2
# The assembled api_messages carry the system row; the helper must not
# double-count it (converter skips system rows, system_prompt adds it once).
assert _midturn_request_pressure_tokens(
agent, messages, "be brief", generic
) == native
def test_midturn_pressure_falls_back_to_generic_plus_tools_when_ineligible():
from agent.conversation_loop import (
_estimate_tools_tokens_rough,
_midturn_request_pressure_tokens,
estimate_messages_tokens_rough,
)
agent = _codex_agent(
api_mode="chat_completions",
tools=[{"type": "function", "function": {"name": "t", "parameters": {}}}],
)
messages = [{"role": "system", "content": "be brief"}] + _history_with_checkpoint()
approx = estimate_messages_tokens_rough(messages)
assert _midturn_request_pressure_tokens(
agent, messages, "be brief", approx
) == approx + _estimate_tools_tokens_rough(agent.tools)