Revert "fix(cheap-gen): 死圈修复 fix①-B 便宜档畸形参数纠偏——n=5 收敛环坐实 heritage 死圈复现"
This reverts commit 26f94f1f0b9137b342c0d37c1f7583d0f03e4369.
This commit is contained in:
parent
3c0c105aeb
commit
e4e4e23564
@ -30,11 +30,9 @@ def build_cheap_breaker(**overrides) -> CircuitBreakerMiddleware:
|
||||
"""
|
||||
overrides.pop("rmb_hard_limit", None)
|
||||
overrides.pop("soft_budget", None)
|
||||
overrides.pop("enable_malformed_correction", None)
|
||||
return CircuitBreakerMiddleware(
|
||||
enable_rmb_gate=True,
|
||||
rmb_hard_limit=cheap_soft_limit(), # 软停线 ¥10(soft 档语义;硬地板 = ×1.5 middleware 内派生)
|
||||
soft_budget=True, # 两段式:越软停线只许收尾、越硬地板 fail-closed(裁决)
|
||||
enable_malformed_correction=True, # fix①-B:便宜档畸形参数纠偏(漏 path 撞满 stuck 前追加补参提示;tier2 不开)
|
||||
**overrides,
|
||||
)
|
||||
|
||||
@ -1,81 +0,0 @@
|
||||
"""test_malformed_correction.py — fix①-B 便宜档畸形参数纠偏(2026-07-08 n=5 收敛环坐实 heritage 死圈复现)。
|
||||
|
||||
守的不变量:
|
||||
· cheap(enable_malformed_correction=True):write 类工具的「漏必填参」结果被追加补参纠偏提示(催补 path / 改 edit_file);
|
||||
· tier2(默认 False):on_acting 该分支不进,工具结果【逐字节不变】(共享中间件对 tier2 零行为变化);
|
||||
· 非「漏参」的正常结果 / 非 write 类工具:不追加(纠偏只打漏参、不误伤);
|
||||
· 不重置 stuck 计数(纠偏不动 _fail_repeat,仍漏就照常撞熔断——本测只验追加行为,计数不变由代码保证)。
|
||||
|
||||
跑:cheap-worker/.venv/bin/python -m pytest tier2/gen-worker/tests/test_malformed_correction.py -q(cwd=tier2/gen-worker)
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[1])) # → tier2/gen-worker/
|
||||
from worker.middleware import CircuitBreakerMiddleware # noqa: E402
|
||||
from agentscope.tool import ToolResponse # noqa: E402
|
||||
from agentscope.message import TextBlock # noqa: E402
|
||||
|
||||
_MISSING = "Input validation failed for tool 'write_file': 'path' is a required property"
|
||||
|
||||
|
||||
class _TC: # mock tool_call(只需 .name)
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
|
||||
|
||||
def _resp(text):
|
||||
return ToolResponse(content=[TextBlock(text=text)])
|
||||
|
||||
|
||||
def _text_of(resp):
|
||||
return "".join(str(getattr(b, "text", "") or "") for b in (resp.content or []))
|
||||
|
||||
|
||||
def _drive(mw, tool_name, resp):
|
||||
"""跑 on_acting(mock tool_call + next_handler 吐 resp),收集 yield 的 items。soft_budget 默认 False → 直进 fix①-B 分支。"""
|
||||
async def next_handler(**kwargs):
|
||||
yield resp
|
||||
|
||||
async def run():
|
||||
out = []
|
||||
async for item in mw.on_acting(None, {"tool_call": _TC(tool_name)}, next_handler):
|
||||
out.append(item)
|
||||
return out
|
||||
|
||||
return asyncio.run(run())
|
||||
|
||||
|
||||
def test_cheap_augments_missing_param_error():
|
||||
mw = CircuitBreakerMiddleware(enable_malformed_correction=True)
|
||||
out = _drive(mw, "write_file", _resp(_MISSING))
|
||||
assert len(out) == 1
|
||||
txt = _text_of(out[0])
|
||||
assert _MISSING in txt # 原错误保留
|
||||
assert "path" in txt and "edit_file" in txt # 追加了补参 + 催 edit_file 短载荷
|
||||
assert "重发本次调用" in txt
|
||||
|
||||
|
||||
def test_tier2_does_not_augment():
|
||||
mw = CircuitBreakerMiddleware() # 默认 enable_malformed_correction=False
|
||||
out = _drive(mw, "write_file", _resp(_MISSING))
|
||||
assert _text_of(out[0]) == _MISSING # 逐字节不变(tier2 零行为变化)
|
||||
|
||||
|
||||
def test_cheap_no_augment_on_normal_result():
|
||||
mw = CircuitBreakerMiddleware(enable_malformed_correction=True)
|
||||
out = _drive(mw, "write_file", _resp("write ok bytes=123"))
|
||||
assert _text_of(out[0]) == "write ok bytes=123" # 非漏参不追加
|
||||
|
||||
|
||||
def test_cheap_no_augment_on_non_write_tool():
|
||||
mw = CircuitBreakerMiddleware(enable_malformed_correction=True)
|
||||
out = _drive(mw, "read_file", _resp(_MISSING))
|
||||
assert _text_of(out[0]) == _MISSING # 非 write 类工具不进纠偏分支
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import pytest
|
||||
sys.exit(pytest.main([__file__, "-q"]))
|
||||
@ -177,38 +177,6 @@ DEFAULT_RMB_HARD_LIMIT = genconfig.get("budget", "rmb_hard_limit", 50.0) # 富
|
||||
# scaffold_init(重开工程)。read/list 零成本只读、check/build/run_gates/finish 是收尾链本体,均放行。
|
||||
DEFAULT_SOFT_STOP_BLOCKED_TOOLS = frozenset({"write_file", "edit_file", "write_source", "scaffold_init"})
|
||||
|
||||
# ── fix①-B:便宜档畸形参数纠偏(2026-07-08 n=5 收敛环坐实 heritage 死圈复现)──
|
||||
# 根因:MiniMax 在长工具调用参数下会漏 write_file/edit_file 的必填 path,AgentScope schema 校验拒 →
|
||||
# 「Input validation failed: 'path' is a required property」;agent 收到默认校验错常不理会、反复漏同参 →
|
||||
# 连撞 stuck_repeat_threshold(默认4)→ stuck 终态杀,九门从未跑、ok=False(fix①-A 给了 edit 替代但没阻止
|
||||
# agent 选 write_file 漏 path)。本纠偏在【非软停态】给这类「漏必填参」的工具结果追加一条比默认校验错更贴脸的
|
||||
# 补参提示(并催改用 edit_file 短载荷),让 agent 在撞满 stuck 阈值前补齐参数出圈。cheap-only
|
||||
# (enable_malformed_correction=True 才开;tier2 默认关、零行为变化);不重置 stuck 计数(仍漏就照常熔断,
|
||||
# 上界由现有 stuck 阈值天然封死、不新增无界重试面,与 fix①-A edit 工具叠加)。
|
||||
_MALFORMED_PARAM_MARKERS = ("is a required property", "input validation failed")
|
||||
_MALFORMED_CORRECTION_TEXT = (
|
||||
"你这次工具调用漏了必填参数(通常是 path)。工具参数必须一次给全:write_file 必须同时给 {path, content}、"
|
||||
"edit_file 必须给 {path, old, new}。请【立即重发本次调用、带齐全部参数】。"
|
||||
"小改优先用 edit_file(只发变化的几行、载荷短、最不易漏参),别对整文件 write_file(长内容最容易把 path 漏掉)。"
|
||||
)
|
||||
|
||||
|
||||
def _is_missing_param_error(resp) -> bool:
|
||||
"""判 ToolResponse 是否是「漏必填参数」的 schema 校验错(fix①-B 纠偏触发条件)。best-effort:抽不到文本返 False。"""
|
||||
try:
|
||||
content = getattr(resp, "content", None)
|
||||
text = ""
|
||||
if isinstance(content, list):
|
||||
for b in content:
|
||||
text += str(getattr(b, "text", "") or "")
|
||||
elif isinstance(content, str):
|
||||
text = content
|
||||
low = text.lower()
|
||||
return any(m in low for m in _MALFORMED_PARAM_MARKERS)
|
||||
except Exception: # noqa: BLE001 —— 判定失败按「非漏参」放行,绝不因此中断工具结果
|
||||
return False
|
||||
|
||||
|
||||
# ── 软停后「finish 逼近」递进升压(工单 c)──
|
||||
# 软停(budget_soft_tripped)已由 on_system_prompt 注入「立即 finish」强提醒 + on_acting 拦新增生成类工具,
|
||||
# 但这是被动系统提示;A1 T8 实测 M3 可能烧推理不理会,80011 生产实录也是「软停触发后仍烧到 step_cap/硬地板才停」。
|
||||
@ -267,7 +235,6 @@ class CircuitBreakerMiddleware(MiddlewareBase):
|
||||
group_ratio: float | None = None,
|
||||
soft_budget: bool = False, # ¥ 越限行为档位(False=hard 单段 fail-closed;True=soft 两段式:软停+硬地板)
|
||||
soft_stop_blocked_tools: Optional[frozenset] = None, # 软停后拦截的「新增生成面」工具名集(None=默认集)
|
||||
enable_malformed_correction: bool = False, # fix①-B:便宜档畸形参数纠偏(True 才开;tier2 默认关、零变化)
|
||||
) -> None:
|
||||
"""
|
||||
Args:
|
||||
@ -338,8 +305,6 @@ class CircuitBreakerMiddleware(MiddlewareBase):
|
||||
self._soft_stop_blocked_tools = (
|
||||
soft_stop_blocked_tools if soft_stop_blocked_tools is not None
|
||||
else DEFAULT_SOFT_STOP_BLOCKED_TOOLS)
|
||||
# fix①-B:便宜档畸形参数纠偏总开关(cheap 熔断工厂置 True;tier2 默认 False = on_acting 该分支不进、零行为变化)。
|
||||
self._malformed_correction = enable_malformed_correction
|
||||
self.group_ratio = group_ratio
|
||||
self._pricing_params = pricing_params # {pricing, qpu, usd_rate} 或 None(惰性取价后填)
|
||||
self._pricing_fetch_tried = pricing_params is not None # 已有则不再去取
|
||||
@ -630,19 +595,6 @@ class CircuitBreakerMiddleware(MiddlewareBase):
|
||||
escalate = None # _acting 仅一个终态 ToolResponse;附加一次即可,防御性置空避免重复
|
||||
yield item
|
||||
return
|
||||
# fix①-B(cheap-only):非软停态下,给「漏必填参」的 write 类工具结果追加补参纠偏提示(催补 path / 改 edit_file
|
||||
# 短载荷),让 agent 在撞满 stuck 阈值前出圈。tier2(_malformed_correction=False)不进本分支、逐字节零行为变化。
|
||||
if self._malformed_correction:
|
||||
tool_call = input_kwargs.get("tool_call") if isinstance(input_kwargs, dict) else None
|
||||
name = getattr(tool_call, "name", None)
|
||||
if name in self._soft_stop_blocked_tools:
|
||||
async for item in next_handler(**input_kwargs):
|
||||
if isinstance(item, ToolResponse) and _is_missing_param_error(item):
|
||||
self._augment_toolresponse(item, _MALFORMED_CORRECTION_TEXT)
|
||||
print(f"[tier2-circuit] 便宜档畸形参数纠偏:{name} 漏必填参 → 追加补参提示"
|
||||
f"(催补 path / 改 edit_file 短载荷,趁 stuck 阈值前出圈)。", flush=True)
|
||||
yield item
|
||||
return
|
||||
async for item in next_handler(**input_kwargs):
|
||||
yield item
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user