refactor(agent): make runner consume required runtime

This commit is contained in:
chengyongru
2026-07-10 17:54:34 +08:00
committed by Xubin Ren
parent 3f8170e835
commit b4f069800e
21 changed files with 423 additions and 326 deletions
+34 -33
View File
@@ -7,6 +7,7 @@ from unittest.mock import AsyncMock, MagicMock
import pytest
from agent.runner_helpers import make_run_spec
from nanobot.config.schema import AgentDefaults
from nanobot.providers.base import LLMProvider, LLMResponse, ToolCallRequest
@@ -16,7 +17,7 @@ _MAX_TOOL_RESULT_CHARS = AgentDefaults().max_tool_result_chars
@pytest.mark.asyncio
async def test_runner_calls_hooks_in_order():
from nanobot.agent.hook import AgentHook, AgentHookContext
from nanobot.agent.runner import AgentRunner, AgentRunSpec
from nanobot.agent.runner import AgentRunner
provider = MagicMock(spec=LLMProvider)
call_count = {"n": 0}
@@ -67,8 +68,8 @@ async def test_runner_calls_hooks_in_order():
events.append(("finalize_content", context.iteration, content))
return content.upper() if content else content
runner = AgentRunner(provider)
result = await runner.run(AgentRunSpec(
runner = AgentRunner()
result = await runner.run(make_run_spec(provider,
initial_messages=[],
tools=tools,
model="test-model",
@@ -100,7 +101,7 @@ async def test_runner_calls_hooks_in_order():
@pytest.mark.asyncio
async def test_runner_streaming_hook_receives_deltas_and_end_signal():
from nanobot.agent.hook import AgentHook, AgentHookContext
from nanobot.agent.runner import AgentRunner, AgentRunSpec
from nanobot.agent.runner import AgentRunner
provider = MagicMock(spec=LLMProvider)
streamed: list[str] = []
@@ -126,8 +127,8 @@ async def test_runner_streaming_hook_receives_deltas_and_end_signal():
async def on_stream_end(self, context: AgentHookContext, *, resuming: bool) -> None:
endings.append(resuming)
runner = AgentRunner(provider)
result = await runner.run(AgentRunSpec(
runner = AgentRunner()
result = await runner.run(make_run_spec(provider,
initial_messages=[],
tools=tools,
model="test-model",
@@ -146,7 +147,7 @@ async def test_runner_streaming_hook_receives_deltas_and_end_signal():
async def test_runner_passes_cached_tokens_to_hook_context():
"""Hook context.usage should contain cached_tokens."""
from nanobot.agent.hook import AgentHook, AgentHookContext
from nanobot.agent.runner import AgentRunner, AgentRunSpec
from nanobot.agent.runner import AgentRunner
provider = MagicMock(spec=LLMProvider)
captured_usage: list[dict] = []
@@ -166,8 +167,8 @@ async def test_runner_passes_cached_tokens_to_hook_context():
tools = MagicMock()
tools.get_definitions.return_value = []
runner = AgentRunner(provider)
await runner.run(AgentRunSpec(
runner = AgentRunner()
await runner.run(make_run_spec(provider,
initial_messages=[],
tools=tools,
model="test-model",
@@ -184,7 +185,7 @@ async def test_runner_passes_cached_tokens_to_hook_context():
@pytest.mark.asyncio
async def test_runner_estimates_usage_when_provider_omits_usage(monkeypatch):
from nanobot.agent.hook import AgentHook, AgentHookContext
from nanobot.agent.runner import AgentRunner, AgentRunSpec
from nanobot.agent.runner import AgentRunner
provider = MagicMock(spec=LLMProvider)
captured_usage: list[dict] = []
@@ -205,8 +206,8 @@ async def test_runner_estimates_usage_when_provider_omits_usage(monkeypatch):
)
monkeypatch.setattr("nanobot.agent.runner.estimate_message_tokens", lambda message: 7)
runner = AgentRunner(provider)
result = await runner.run(AgentRunSpec(
runner = AgentRunner()
result = await runner.run(make_run_spec(provider,
initial_messages=[{"role": "user", "content": "hi"}],
tools=tools,
model="test-model",
@@ -225,7 +226,7 @@ async def test_runner_estimates_usage_when_provider_omits_usage(monkeypatch):
@pytest.mark.asyncio
async def test_runner_calls_run_level_hooks_on_success():
from nanobot.agent.hook import AgentHook, AgentRunHookContext
from nanobot.agent.runner import AgentRunner, AgentRunSpec
from nanobot.agent.runner import AgentRunner
provider = MagicMock(spec=LLMProvider)
events: list[tuple] = []
@@ -263,8 +264,8 @@ async def test_runner_calls_run_level_hooks_on_success():
async def on_finally(self, context: AgentRunHookContext) -> None:
events.append(("on_finally", context.stop_reason, context.exception))
runner = AgentRunner(provider)
result = await runner.run(AgentRunSpec(
runner = AgentRunner()
result = await runner.run(make_run_spec(provider,
initial_messages=[{"role": "user", "content": "hi"}],
tools=tools,
model="test-model",
@@ -297,7 +298,7 @@ async def test_runner_calls_run_level_hooks_on_success():
@pytest.mark.asyncio
async def test_runner_run_level_context_is_detached_snapshot():
from nanobot.agent.hook import AgentHook, AgentRunHookContext
from nanobot.agent.runner import AgentRunner, AgentRunSpec
from nanobot.agent.runner import AgentRunner
provider = MagicMock(spec=LLMProvider)
call_count = {"n": 0}
@@ -330,8 +331,8 @@ async def test_runner_run_level_context_is_detached_snapshot():
async def on_finally(self, context: AgentRunHookContext) -> None:
context.messages[0]["content"] = "mutated-finally"
runner = AgentRunner(provider)
result = await runner.run(AgentRunSpec(
runner = AgentRunner()
result = await runner.run(make_run_spec(provider,
initial_messages=[{"role": "user", "content": "hi"}],
tools=tools,
model="test-model",
@@ -351,7 +352,7 @@ async def test_runner_run_level_context_is_detached_snapshot():
@pytest.mark.asyncio
async def test_runner_calls_on_error_for_model_error_result():
from nanobot.agent.hook import AgentHook, AgentRunHookContext
from nanobot.agent.runner import AgentRunner, AgentRunSpec
from nanobot.agent.runner import AgentRunner
provider = MagicMock(spec=LLMProvider)
events: list[tuple] = []
@@ -376,8 +377,8 @@ async def test_runner_calls_on_error_for_model_error_result():
async def on_finally(self, context: AgentRunHookContext) -> None:
events.append(("on_finally", context.stop_reason, context.error))
runner = AgentRunner(provider)
result = await runner.run(AgentRunSpec(
runner = AgentRunner()
result = await runner.run(make_run_spec(provider,
initial_messages=[],
tools=tools,
model="test-model",
@@ -399,7 +400,7 @@ async def test_runner_calls_on_error_for_model_error_result():
@pytest.mark.asyncio
async def test_runner_calls_on_error_and_finally_for_unhandled_exception():
from nanobot.agent.hook import AgentHook, AgentRunHookContext
from nanobot.agent.runner import AgentRunner, AgentRunSpec
from nanobot.agent.runner import AgentRunner
provider = MagicMock(spec=LLMProvider)
events: list[tuple] = []
@@ -429,9 +430,9 @@ async def test_runner_calls_on_error_and_finally_for_unhandled_exception():
async def on_finally(self, context: AgentRunHookContext) -> None:
events.append(("on_finally", context.stop_reason))
runner = AgentRunner(provider)
runner = AgentRunner()
with pytest.raises(RuntimeError, match="provider exploded"):
await runner.run(AgentRunSpec(
await runner.run(make_run_spec(provider,
initial_messages=[{"role": "user", "content": "hi"}],
tools=tools,
model="test-model",
@@ -450,7 +451,7 @@ async def test_runner_calls_on_error_and_finally_for_unhandled_exception():
@pytest.mark.asyncio
async def test_runner_preserves_original_exception_when_finally_hook_fails():
from nanobot.agent.hook import AgentHook, AgentRunHookContext
from nanobot.agent.runner import AgentRunner, AgentRunSpec
from nanobot.agent.runner import AgentRunner
provider = MagicMock(spec=LLMProvider)
@@ -465,9 +466,9 @@ async def test_runner_preserves_original_exception_when_finally_hook_fails():
async def on_finally(self, context: AgentRunHookContext) -> None:
raise RuntimeError("finally exploded")
runner = AgentRunner(provider)
runner = AgentRunner()
with pytest.raises(RuntimeError, match="provider exploded"):
await runner.run(AgentRunSpec(
await runner.run(make_run_spec(provider,
initial_messages=[{"role": "user", "content": "hi"}],
tools=tools,
model="test-model",
@@ -482,7 +483,7 @@ async def test_runner_does_not_report_cancellation_as_error():
import asyncio
from nanobot.agent.hook import AgentHook, AgentRunHookContext
from nanobot.agent.runner import AgentRunner, AgentRunSpec
from nanobot.agent.runner import AgentRunner
provider = MagicMock(spec=LLMProvider)
events: list[tuple] = []
@@ -512,9 +513,9 @@ async def test_runner_does_not_report_cancellation_as_error():
type(context.exception).__name__ if context.exception else None,
))
runner = AgentRunner(provider)
runner = AgentRunner()
with pytest.raises(asyncio.CancelledError):
await runner.run(AgentRunSpec(
await runner.run(make_run_spec(provider,
initial_messages=[{"role": "user", "content": "hi"}],
tools=tools,
model="test-model",
@@ -534,7 +535,7 @@ async def test_runner_preserves_cancellation_when_finally_hook_fails():
import asyncio
from nanobot.agent.hook import AgentHook, AgentRunHookContext
from nanobot.agent.runner import AgentRunner, AgentRunSpec
from nanobot.agent.runner import AgentRunner
provider = MagicMock(spec=LLMProvider)
@@ -549,9 +550,9 @@ async def test_runner_preserves_cancellation_when_finally_hook_fails():
async def on_finally(self, context: AgentRunHookContext) -> None:
raise RuntimeError("finally exploded")
runner = AgentRunner(provider)
runner = AgentRunner()
with pytest.raises(asyncio.CancelledError):
await runner.run(AgentRunSpec(
await runner.run(make_run_spec(provider,
initial_messages=[{"role": "user", "content": "hi"}],
tools=tools,
model="test-model",