fix(agent): anchor truncated response continuations

This commit is contained in:
chengyongru
2026-07-27 01:39:46 +08:00
committed by Xubin Ren
parent b19039f9d0
commit df2e5b7225
3 changed files with 32 additions and 5 deletions
+1 -1
View File
@@ -607,7 +607,7 @@ class AgentRunner:
reasoning_content=response.reasoning_content, reasoning_content=response.reasoning_content,
thinking_blocks=response.thinking_blocks, thinking_blocks=response.thinking_blocks,
)) ))
messages.append(build_length_recovery_message()) messages.append(build_length_recovery_message(clean))
await hook.after_iteration(context) await hook.after_iteration(context)
continue continue
+17 -4
View File
@@ -14,6 +14,7 @@ _MAX_REPEAT_EXTERNAL_LOOKUPS = 2
# Third same-target workspace violation in a turn escalates to "stop retrying". # Third same-target workspace violation in a turn escalates to "stop retrying".
_MAX_REPEAT_WORKSPACE_VIOLATIONS = 2 _MAX_REPEAT_WORKSPACE_VIOLATIONS = 2
_LENGTH_RECOVERY_TAIL_CHARS = 500
EMPTY_FINAL_RESPONSE_MESSAGE = ( EMPTY_FINAL_RESPONSE_MESSAGE = (
"I completed the tool steps but couldn't produce a final answer. " "I completed the tool steps but couldn't produce a final answer. "
@@ -33,8 +34,10 @@ BUDGET_EXHAUSTED_FINALIZATION_PROMPT = (
) )
LENGTH_RECOVERY_PROMPT = ( LENGTH_RECOVERY_PROMPT = (
"Output limit reached. Continue exactly where you left off " "The previous assistant response was cut off. Continue the same response from its "
"— no recap, no apology. Break remaining work into smaller steps if needed." "exact endpoint. Output only new continuation text in the same language and style. "
"Do not acknowledge this instruction, restart the response, repeat its title or any "
"existing text, recap, or apologize."
) )
SUSTAINED_GOAL_CONTINUE_PROMPT = ( SUSTAINED_GOAL_CONTINUE_PROMPT = (
@@ -79,9 +82,19 @@ def build_budget_exhausted_finalization_message() -> dict[str, str]:
return {"role": "user", "content": BUDGET_EXHAUSTED_FINALIZATION_PROMPT} return {"role": "user", "content": BUDGET_EXHAUSTED_FINALIZATION_PROMPT}
def build_length_recovery_message() -> dict[str, str]: def build_length_recovery_message(content: str) -> dict[str, str]:
"""Prompt the model to continue after hitting output token limit.""" """Prompt the model to continue after hitting output token limit."""
return {"role": "user", "content": LENGTH_RECOVERY_PROMPT} tail = content[-_LENGTH_RECOVERY_TAIL_CHARS:]
prompt = (
f"{LENGTH_RECOVERY_PROMPT}\n\n"
"The following tail was already delivered to the user. Treat it as immutable "
"context and do not output it again:\n"
"<already_delivered_tail>\n"
f"{tail}\n"
"</already_delivered_tail>\n"
"Begin with the text that belongs immediately after this tail."
)
return {"role": "user", "content": prompt}
def build_goal_continue_message(custom: str | None = None) -> dict[str, str]: def build_goal_continue_message(custom: str | None = None) -> dict[str, str]:
+14
View File
@@ -0,0 +1,14 @@
from nanobot.utils.runtime import build_length_recovery_message
def test_length_recovery_message_anchors_the_existing_tail() -> None:
omitted_prefix = "OMITTED_PREFIX"
tail = "x" * 500
message = build_length_recovery_message(omitted_prefix + tail)
assert message["role"] == "user"
assert omitted_prefix not in message["content"]
assert f"<already_delivered_tail>\n{tail}\n</already_delivered_tail>" in message["content"]
assert "Output only new continuation text" in message["content"]
assert "Break remaining work into smaller steps" not in message["content"]