mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-29 06:31:32 +00:00
fix(codex): drop transient rs_tmp reasoning replay state
This commit is contained in:
parent
187cf0f257
commit
b1a46b3047
2 changed files with 78 additions and 7 deletions
|
|
@ -932,6 +932,7 @@ def _normalize_codex_response(response: Any) -> tuple[Any, str]:
|
|||
has_incomplete_items = response_status in {"queued", "in_progress", "incomplete"}
|
||||
saw_commentary_phase = False
|
||||
saw_final_answer_phase = False
|
||||
saw_reasoning_item = False
|
||||
|
||||
for item in output:
|
||||
item_type = getattr(item, "type", None)
|
||||
|
|
@ -969,6 +970,7 @@ def _normalize_codex_response(response: Any) -> tuple[Any, str]:
|
|||
raw_message_item["phase"] = normalized_phase
|
||||
message_items_raw.append(raw_message_item)
|
||||
elif item_type == "reasoning":
|
||||
saw_reasoning_item = True
|
||||
reasoning_text = _extract_responses_reasoning_text(item)
|
||||
if reasoning_text:
|
||||
reasoning_parts.append(reasoning_text)
|
||||
|
|
@ -979,6 +981,12 @@ def _normalize_codex_response(response: Any) -> tuple[Any, str]:
|
|||
if isinstance(encrypted, str) and encrypted:
|
||||
raw_item = {"type": "reasoning", "encrypted_content": encrypted}
|
||||
item_id = getattr(item, "id", None)
|
||||
if isinstance(item_id, str) and item_id.startswith("rs_tmp_"):
|
||||
logger.debug(
|
||||
"Skipping transient Codex reasoning item during normalization: %s",
|
||||
item_id,
|
||||
)
|
||||
continue
|
||||
if isinstance(item_id, str) and item_id:
|
||||
raw_item["id"] = item_id
|
||||
# Capture summary — required by the API when replaying reasoning items
|
||||
|
|
@ -1089,13 +1097,13 @@ def _normalize_codex_response(response: Any) -> tuple[Any, str]:
|
|||
finish_reason = "incomplete"
|
||||
elif has_incomplete_items or (saw_commentary_phase and not saw_final_answer_phase):
|
||||
finish_reason = "incomplete"
|
||||
elif reasoning_items_raw and not final_text:
|
||||
# Response contains only reasoning (encrypted thinking state) with
|
||||
# no visible content or tool calls. The model is still thinking and
|
||||
# needs another turn to produce the actual answer. Marking this as
|
||||
# "stop" would send it into the empty-content retry loop which burns
|
||||
# 3 retries then fails — treat it as incomplete instead so the Codex
|
||||
# continuation path handles it correctly.
|
||||
elif (reasoning_items_raw or reasoning_parts or saw_reasoning_item) and not final_text:
|
||||
# Response contains only reasoning (encrypted thinking state and/or
|
||||
# human-readable summary) with no visible content or tool calls. The
|
||||
# model is still thinking and needs another turn to produce the actual
|
||||
# answer. Marking this as "stop" would send it into the empty-content
|
||||
# retry loop which burns retries then fails — treat it as incomplete so
|
||||
# the Codex continuation path handles it correctly.
|
||||
finish_reason = "incomplete"
|
||||
else:
|
||||
finish_reason = "stop"
|
||||
|
|
|
|||
63
tests/agent/test_codex_responses_adapter.py
Normal file
63
tests/agent/test_codex_responses_adapter.py
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
from types import SimpleNamespace
|
||||
|
||||
from agent.codex_responses_adapter import _normalize_codex_response
|
||||
|
||||
|
||||
def test_normalize_codex_response_drops_transient_rs_tmp_reasoning_items():
|
||||
response = SimpleNamespace(
|
||||
status="completed",
|
||||
output=[
|
||||
SimpleNamespace(
|
||||
type="reasoning",
|
||||
id="rs_tmp_123",
|
||||
encrypted_content="opaque-transient",
|
||||
summary=[],
|
||||
),
|
||||
SimpleNamespace(
|
||||
type="reasoning",
|
||||
id="rs_456",
|
||||
encrypted_content="opaque-stable",
|
||||
summary=[SimpleNamespace(text="stable summary")],
|
||||
),
|
||||
SimpleNamespace(
|
||||
type="message",
|
||||
role="assistant",
|
||||
status="completed",
|
||||
content=[SimpleNamespace(type="output_text", text="done")],
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
assistant_message, finish_reason = _normalize_codex_response(response)
|
||||
|
||||
assert finish_reason == "stop"
|
||||
assert assistant_message.content == "done"
|
||||
assert assistant_message.codex_reasoning_items == [
|
||||
{
|
||||
"type": "reasoning",
|
||||
"encrypted_content": "opaque-stable",
|
||||
"id": "rs_456",
|
||||
"summary": [{"type": "summary_text", "text": "stable summary"}],
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
def test_normalize_codex_response_treats_summary_only_reasoning_as_incomplete():
|
||||
response = SimpleNamespace(
|
||||
status="completed",
|
||||
output=[
|
||||
SimpleNamespace(
|
||||
type="reasoning",
|
||||
id="rs_tmp_789",
|
||||
encrypted_content="opaque-transient",
|
||||
summary=[SimpleNamespace(text="still thinking")],
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
assistant_message, finish_reason = _normalize_codex_response(response)
|
||||
|
||||
assert finish_reason == "incomplete"
|
||||
assert assistant_message.content == ""
|
||||
assert assistant_message.reasoning == "still thinking"
|
||||
assert assistant_message.codex_reasoning_items is None
|
||||
Loading…
Add table
Add a link
Reference in a new issue