feat: add tool-call fallback and resilient batch exports
This commit is contained in:
1 parent
ab066733b8
commit
d31eb2a19d
32 files changed
+538
-72
No files matched your search
@@ -107,23 +107,56 @@ async def test_invalid_target_can_be_corrected_before_limit(operator, monkeypatc
|
||||
@pytest.mark.parametrize("limit", [1, 3])
|
||||
async def test_empty_responses_fail_at_configured_limit(operator, monkeypatch, limit):
|
||||
monkeypatch.setattr(settings, "ARTEMIS_OPERATOR_MAX_NO_PROGRESS_ATTEMPTS", limit)
|
||||
monkeypatch.setattr(settings, "ARTEMIS_TOOL_FALLBACK_MAX_NO_PROGRESS_ATTEMPTS", 3)
|
||||
fallback = Mock()
|
||||
get_fallback = Mock(return_value=fallback)
|
||||
monkeypatch.setattr("artemis.agents.operator.operator.get_llm", get_fallback)
|
||||
invoke = AsyncMock(return_value=AIMessage(content="I will click now."))
|
||||
monkeypatch.setattr("artemis.agents.operator.operator.acomplete", invoke)
|
||||
node, state = operator
|
||||
with pytest.raises(OperatorNoProgressError, match="未提供可执行的工具调用"):
|
||||
await node._invoke_llm_loop(Mock(), [], [], [], state)
|
||||
assert invoke.await_count == limit
|
||||
assert invoke.await_count == limit + 3
|
||||
get_fallback.assert_called_once_with(ctx=node.ctx, name="operator", use_fallback=True)
|
||||
assert invoke.call_args_list[-1].args[0] is fallback.bind_tools.return_value
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_operator_fallback_is_scoped_to_current_step(operator, monkeypatch):
|
||||
node, state = operator
|
||||
primary = Mock()
|
||||
fallback = Mock()
|
||||
get_fallback = Mock(return_value=fallback)
|
||||
monkeypatch.setattr("artemis.agents.operator.operator.get_llm", get_fallback)
|
||||
invoke = AsyncMock(side_effect=[AIMessage(content="no tool")] * 3 + [click(), click()])
|
||||
monkeypatch.setattr("artemis.agents.operator.operator.acomplete", invoke)
|
||||
|
||||
first = await node._invoke_llm_loop(primary, [], [], [], state)
|
||||
second = await node._invoke_llm_loop(primary, [], [], [], state)
|
||||
|
||||
assert first[0] and second[0]
|
||||
assert get_fallback.call_count == 1
|
||||
assert [call.args[0] for call in invoke.call_args_list] == [
|
||||
primary.bind_tools.return_value,
|
||||
primary.bind_tools.return_value,
|
||||
primary.bind_tools.return_value,
|
||||
fallback.bind_tools.return_value,
|
||||
primary.bind_tools.return_value,
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_invalid_calls_and_prose_share_bounded_recovery(operator, monkeypatch):
|
||||
invoke = AsyncMock(side_effect=[click("missing"), AIMessage(content="Retry."), click("14")])
|
||||
invoke = AsyncMock(
|
||||
side_effect=[click("missing"), AIMessage(content="Retry."), click("14")] + [click("14")] * 3
|
||||
)
|
||||
monkeypatch.setattr("artemis.agents.operator.operator.get_llm", Mock(return_value=Mock()))
|
||||
monkeypatch.setattr("artemis.agents.operator.operator.acomplete", invoke)
|
||||
node, state = operator
|
||||
messages = []
|
||||
with pytest.raises(OperatorNoProgressError, match="校验仍失败"):
|
||||
await node._invoke_llm_loop(Mock(), messages, [], [], state)
|
||||
assert invoke.await_count == 3
|
||||
assert invoke.await_count == 6
|
||||
assert any(isinstance(m, ToolMessage) and m.status == "error" for m in messages)
|
||||
assert not any(isinstance(m, ToolMessage) and m.content == "Action Recorded" for m in messages)
|
||||
|
||||
|
||||
Reference in new issue
Block a user