fix:timinggate非法工具会重试

This commit is contained in:
SengokuCola
2026-04-29 17:46:29 +08:00
parent c06a0cdc3d
commit 500d5c11b3
3 changed files with 114 additions and 14 deletions

View File

@@ -3,7 +3,7 @@ from types import SimpleNamespace
import pytest
from src.core.tooling import ToolExecutionResult
from src.core.tooling import ToolExecutionResult, ToolInvocation
from src.llm_models.payload_content.tool_option import ToolCall
from src.maisaka.chat_loop_service import ChatResponse, MaisakaChatLoopService
from src.maisaka.context_messages import AssistantMessage, TIMING_GATE_INVALID_TOOL_HINT_SOURCE
@@ -45,8 +45,12 @@ async def test_timing_gate_invalid_tool_defaults_to_no_reply(monkeypatch: pytest
runtime._enter_stop_state = _enter_stop_state
engine = MaisakaReasoningEngine(runtime) # type: ignore[arg-type]
call_count = 0
async def _fake_timing_gate_sub_agent(**kwargs: object) -> ChatResponse:
nonlocal call_count
del kwargs
call_count += 1
return _build_chat_response([
ToolCall(call_id="invalid-timing-tool", func_name="finish", args={}),
])
@@ -61,6 +65,7 @@ async def test_timing_gate_invalid_tool_defaults_to_no_reply(monkeypatch: pytest
action, response, tool_results, tool_monitor_results = await engine._run_timing_gate(object()) # type: ignore[arg-type]
assert action == "no_reply"
assert call_count == 3
assert response.tool_calls[0].func_name == "finish"
assert runtime.stopped is True
assert tool_monitor_results == []
@@ -68,10 +73,72 @@ async def test_timing_gate_invalid_tool_defaults_to_no_reply(monkeypatch: pytest
assert runtime._chat_history[0].source == TIMING_GATE_INVALID_TOOL_HINT_SOURCE
assert "finish" in runtime._chat_history[0].processed_plain_text
assert tool_results == [
"- retry [非法 Timing 工具]: 返回了 finish将重试 (1/3)",
"- retry [非法 Timing 工具]: 返回了 finish将重试 (2/3)",
"- no_reply [非法 Timing 工具]: 返回了 finish已停止本轮并等待新消息",
]
@pytest.mark.asyncio
async def test_timing_gate_invalid_tool_retries_until_valid(monkeypatch: pytest.MonkeyPatch) -> None:
runtime = SimpleNamespace(
_force_next_timing_continue=False,
_chat_history=[],
log_prefix="[test]",
stopped=False,
)
def _enter_stop_state() -> None:
runtime.stopped = True
runtime._enter_stop_state = _enter_stop_state
engine = MaisakaReasoningEngine(runtime) # type: ignore[arg-type]
responses = [
_build_chat_response([ToolCall(call_id="invalid-timing-tool", func_name="finish", args={})]),
_build_chat_response([ToolCall(call_id="valid-timing-tool", func_name="continue", args={})]),
]
async def _fake_timing_gate_sub_agent(**kwargs: object) -> ChatResponse:
del kwargs
return responses.pop(0)
async def _fake_invoke_tool_call(
tool_call: ToolCall,
latest_thought: str,
anchor_message: object,
*,
append_history: bool = True,
store_record: bool = True,
) -> tuple[ToolInvocation, ToolExecutionResult, None]:
del latest_thought, anchor_message, append_history, store_record
return (
ToolInvocation(tool_name=tool_call.func_name, call_id=tool_call.call_id),
ToolExecutionResult(
tool_name=tool_call.func_name,
success=True,
content="继续执行主流程",
metadata={"timing_action": "continue"},
),
None,
)
monkeypatch.setattr(engine, "_run_timing_gate_sub_agent", _fake_timing_gate_sub_agent)
monkeypatch.setattr(engine, "_invoke_tool_call", _fake_invoke_tool_call)
action, response, tool_results, tool_monitor_results = await engine._run_timing_gate(object()) # type: ignore[arg-type]
assert action == "continue"
assert response.tool_calls[0].func_name == "continue"
assert runtime.stopped is False
assert len(runtime._chat_history) == 2
assert all(message.source != TIMING_GATE_INVALID_TOOL_HINT_SOURCE for message in runtime._chat_history)
assert tool_results == [
"- retry [非法 Timing 工具]: 返回了 finish将重试 (1/3)",
"- continue [成功]: 继续执行主流程",
]
assert tool_monitor_results[0]["tool_name"] == "continue"
def test_timing_gate_invalid_tool_hint_keeps_only_latest() -> None:
old_hint = SimpleNamespace(source=TIMING_GATE_INVALID_TOOL_HINT_SOURCE)
runtime = SimpleNamespace(_chat_history=[old_hint])