From 49dfb7c4584e1edeb62a08cd214b658486d3bbf0 Mon Sep 17 00:00:00 2001 From: host Date: Tue, 27 Oct 2026 05:28:59 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20goal=20=E5=AE=A3=E5=91=8A=E6=A3=80?= =?UTF-8?q?=E6=B5=8B=E5=AE=B9=E5=BF=8D=E8=A1=8C=E5=B0=BE=E5=8F=A5=E5=8F=B7?= =?UTF-8?q?=EF=BC=8C=E4=BF=AE=E6=8F=90=E7=A4=BA=E4=B8=8E=E6=A3=80=E6=B5=8B?= =?UTF-8?q?=E8=87=AA=E7=9F=9B=E7=9B=BE=E8=87=B4=E7=A9=BA=E8=BD=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit _llm_declared_done 此前要求回合文本里单独一行恰好等于 目标已达成,但 run_goal_loop 三处提示都让模型输出 目标已达成。(带句号);模型照做后检测永不触发,实弹 goal 任务空转 292 轮才被手动取消。改成比对前去行尾空白 与句末标点(。!?;,、. 等),提示不动、单独一行要求 不动,行内带其它文字仍不算。 回归测试 test_goal_marker_trailing_punctuation_counts:裸标记、 句号、感叹号、句号前空格、行尾空格算达成;前缀文字、逗号 后续、反问、空串不算。 --- host.py | 18 ++++++++++++++---- tests/test_host.py | 15 +++++++++++++++ 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/host.py b/host.py index 98ece6f..6a475be 100644 --- a/host.py +++ b/host.py @@ -1706,14 +1706,24 @@ GOAL_DONE_MARKER = "目标已达成" def _llm_declared_done(text): - """模型是否宣告目标达成:回合文本里单独一行恰好是标记行。 + """模型是否宣告目标达成:回合文本里单独一行是标记行。 只有完整跑完的回合才查标记(调用方保证):超时/中断回合的 - 部分文本即使含标记也不算,否则没做完也会提前结束。""" + 部分文本即使含标记也不算,否则没做完也会提前结束。 + + 行尾句号要容忍:提示本身让模型输出“目标已达成。”(带句 + 号),若检测要求整行恰好等于无句号的标记,模型照做也永 + 远不算达成、循环空转(2026-10-27 实弹 292 轮教训)。所以 + 比对前先去行尾空白与句末标点,但行内其它文字一律不算 + (“xx目标已达成”“目标已达成,继续”仍不算,单独一行要 + 求不动)。""" if not text: return False - return any(line.strip() == GOAL_DONE_MARKER - for line in text.splitlines()) + punct = "。!!??;;,,、.." + for line in text.splitlines(): + if line.strip().rstrip(punct).strip() == GOAL_DONE_MARKER: + return True + return False def run_goal_loop(server, state, state_path, task, goal_text, verify, diff --git a/tests/test_host.py b/tests/test_host.py index c6a2bcd..5592521 100644 --- a/tests/test_host.py +++ b/tests/test_host.py @@ -922,6 +922,21 @@ class GoalLoopLongTaskTest(unittest.TestCase): self.assertIn("目标已达成", srv.prompts[0]) self.assertIn("目标已达成", srv.prompts[1]) + def test_goal_marker_trailing_punctuation_counts(self): + # 2026-10-27 实弹教训:提示让模型输出“目标已达成。”(带 + # 句号),检测若要求整行恰好等于无句号标记,循环空转 + # 292 轮。行尾句末标点必须容忍,但单独一行要求不动。 + self.assertTrue(host._llm_declared_done("done\n目标已达成")) + self.assertTrue(host._llm_declared_done("done\n目标已达成。")) + self.assertTrue(host._llm_declared_done("done\n目标已达成!")) + self.assertTrue(host._llm_declared_done("done\n目标已达成 。")) + self.assertTrue(host._llm_declared_done("done\n目标已达成。 ")) + self.assertFalse(host._llm_declared_done("done\nxx目标已达成")) + self.assertFalse(host._llm_declared_done("done\n目标已达成,继续")) + self.assertFalse(host._llm_declared_done("done\n目标已达成吗?")) + self.assertFalse(host._llm_declared_done("")) + self.assertFalse(host._llm_declared_done(None)) + def test_goal_llm_decided_marker_ignored_on_timeout_turn(self): # 超时回合的部分文本即使含标记行也不算达标,必须继续跑。 class FakeServer: