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
+31 -30
View File
@@ -13,6 +13,7 @@ from unittest.mock import AsyncMock, MagicMock
import pytest
from agent.runner_helpers import make_run_spec
from nanobot.agent.hook import AgentHook, AgentHookContext
from nanobot.config.schema import AgentDefaults
from nanobot.providers.base import LLMResponse, ToolCallRequest
@@ -38,7 +39,7 @@ class _RecordingHook(AgentHook):
async def test_runner_preserves_reasoning_fields_in_assistant_history():
"""Reasoning fields ride along on the persisted assistant message so
follow-up provider calls retain the model's prior thinking context."""
from nanobot.agent.runner import AgentRunner, AgentRunSpec
from nanobot.agent.runner import AgentRunner
provider = MagicMock()
captured_second_call: list[dict] = []
@@ -62,8 +63,8 @@ async def test_runner_preserves_reasoning_fields_in_assistant_history():
tools.get_definitions.return_value = []
tools.execute = AsyncMock(return_value="tool result")
runner = AgentRunner(provider)
result = await runner.run(AgentRunSpec(
runner = AgentRunner()
result = await runner.run(make_run_spec(provider,
initial_messages=[
{"role": "system", "content": "system"},
{"role": "user", "content": "do task"},
@@ -86,7 +87,7 @@ async def test_runner_preserves_reasoning_fields_in_assistant_history():
@pytest.mark.asyncio
async def test_runner_emits_anthropic_thinking_blocks():
from nanobot.agent.runner import AgentRunner, AgentRunSpec
from nanobot.agent.runner import AgentRunner
provider = MagicMock()
@@ -106,8 +107,8 @@ async def test_runner_emits_anthropic_thinking_blocks():
tools.get_definitions.return_value = []
hook = _RecordingHook()
runner = AgentRunner(provider)
result = await runner.run(AgentRunSpec(
runner = AgentRunner()
result = await runner.run(make_run_spec(provider,
initial_messages=[{"role": "user", "content": "question"}],
tools=tools,
model="test-model",
@@ -126,7 +127,7 @@ async def test_runner_emits_anthropic_thinking_blocks():
async def test_runner_emits_inline_think_content_as_reasoning():
"""Models embedding reasoning in <think>...</think> blocks should have
that content extracted and emitted, and stripped from the answer."""
from nanobot.agent.runner import AgentRunner, AgentRunSpec
from nanobot.agent.runner import AgentRunner
provider = MagicMock()
@@ -142,8 +143,8 @@ async def test_runner_emits_inline_think_content_as_reasoning():
tools.get_definitions.return_value = []
hook = _RecordingHook()
runner = AgentRunner(provider)
result = await runner.run(AgentRunSpec(
runner = AgentRunner()
result = await runner.run(make_run_spec(provider,
initial_messages=[{"role": "user", "content": "what is the answer?"}],
tools=tools,
model="test-model",
@@ -161,7 +162,7 @@ async def test_runner_emits_inline_think_content_as_reasoning():
async def test_runner_prefers_reasoning_content_over_inline_think():
"""Fallback priority: dedicated reasoning_content wins; inline <think>
is still scrubbed from the answer content."""
from nanobot.agent.runner import AgentRunner, AgentRunSpec
from nanobot.agent.runner import AgentRunner
provider = MagicMock()
@@ -178,8 +179,8 @@ async def test_runner_prefers_reasoning_content_over_inline_think():
tools.get_definitions.return_value = []
hook = _RecordingHook()
runner = AgentRunner(provider)
result = await runner.run(AgentRunSpec(
runner = AgentRunner()
result = await runner.run(make_run_spec(provider,
initial_messages=[{"role": "user", "content": "question"}],
tools=tools,
model="test-model",
@@ -197,7 +198,7 @@ async def test_runner_emits_reasoning_content_even_when_answer_was_streamed():
"""`reasoning_content` arrives only on the final response; streaming the
answer must not suppress it (the answer stream and the reasoning channel
are independent — only the reasoning-already-emitted bit matters)."""
from nanobot.agent.runner import AgentRunner, AgentRunSpec
from nanobot.agent.runner import AgentRunner
provider = MagicMock()
provider.supports_progress_deltas = True
@@ -223,8 +224,8 @@ async def test_runner_emits_reasoning_content_even_when_answer_was_streamed():
progress_calls.append(content)
hook = _RecordingHook()
runner = AgentRunner(provider)
result = await runner.run(AgentRunSpec(
runner = AgentRunner()
result = await runner.run(make_run_spec(provider,
initial_messages=[{"role": "user", "content": "question"}],
tools=tools,
model="test-model",
@@ -244,7 +245,7 @@ async def test_runner_emits_reasoning_content_even_when_answer_was_streamed():
async def test_runner_does_not_double_emit_when_inline_think_already_streamed():
"""Inline `<think>` blocks streamed incrementally during the answer
stream must not be re-emitted from the final response."""
from nanobot.agent.runner import AgentRunner, AgentRunSpec
from nanobot.agent.runner import AgentRunner
provider = MagicMock()
provider.supports_progress_deltas = True
@@ -267,8 +268,8 @@ async def test_runner_does_not_double_emit_when_inline_think_already_streamed():
pass
hook = _RecordingHook()
runner = AgentRunner(provider)
result = await runner.run(AgentRunSpec(
runner = AgentRunner()
result = await runner.run(make_run_spec(provider,
initial_messages=[{"role": "user", "content": "question"}],
tools=tools,
model="test-model",
@@ -289,7 +290,7 @@ async def test_runner_closes_reasoning_stream_after_one_shot_response():
"""A non-streaming response carrying ``reasoning_content`` must emit
both a reasoning delta and an end marker so channels can finalize the
in-place bubble."""
from nanobot.agent.runner import AgentRunner, AgentRunSpec
from nanobot.agent.runner import AgentRunner
provider = MagicMock()
@@ -306,8 +307,8 @@ async def test_runner_closes_reasoning_stream_after_one_shot_response():
tools.get_definitions.return_value = []
hook = _RecordingHook()
runner = AgentRunner(provider)
result = await runner.run(AgentRunSpec(
runner = AgentRunner()
result = await runner.run(make_run_spec(provider,
initial_messages=[{"role": "user", "content": "q"}],
tools=tools,
model="test-model",
@@ -333,7 +334,7 @@ class _StreamRecordingHook(_RecordingHook):
async def test_runner_streams_native_thinking_deltas_without_post_hoc_dup():
"""Anthropic-style ``on_thinking_delta`` should fan out to ``emit_reasoning``;
final ``thinking_blocks`` must not emit again when already streamed."""
from nanobot.agent.runner import AgentRunner, AgentRunSpec
from nanobot.agent.runner import AgentRunner
provider = MagicMock()
@@ -357,8 +358,8 @@ async def test_runner_streams_native_thinking_deltas_without_post_hoc_dup():
tools.get_definitions.return_value = []
hook = _StreamRecordingHook()
runner = AgentRunner(provider)
result = await runner.run(AgentRunSpec(
runner = AgentRunner()
result = await runner.run(make_run_spec(provider,
initial_messages=[{"role": "user", "content": "q"}],
tools=tools,
model="test-model",
@@ -373,7 +374,7 @@ async def test_runner_streams_native_thinking_deltas_without_post_hoc_dup():
@pytest.mark.asyncio
async def test_runner_strips_thinking_tags_from_native_thinking_deltas():
from nanobot.agent.runner import AgentRunner, AgentRunSpec
from nanobot.agent.runner import AgentRunner
provider = MagicMock()
@@ -393,8 +394,8 @@ async def test_runner_strips_thinking_tags_from_native_thinking_deltas():
tools.get_definitions.return_value = []
hook = _StreamRecordingHook()
runner = AgentRunner(provider)
result = await runner.run(AgentRunSpec(
runner = AgentRunner()
result = await runner.run(make_run_spec(provider,
initial_messages=[{"role": "user", "content": "q"}],
tools=tools,
model="test-model",
@@ -409,7 +410,7 @@ async def test_runner_strips_thinking_tags_from_native_thinking_deltas():
@pytest.mark.asyncio
async def test_runner_ignores_empty_thinking_marker_before_final_reasoning():
from nanobot.agent.runner import AgentRunner, AgentRunSpec
from nanobot.agent.runner import AgentRunner
provider = MagicMock()
@@ -432,8 +433,8 @@ async def test_runner_ignores_empty_thinking_marker_before_final_reasoning():
tools.get_definitions.return_value = []
hook = _StreamRecordingHook()
runner = AgentRunner(provider)
result = await runner.run(AgentRunSpec(
runner = AgentRunner()
result = await runner.run(make_run_spec(provider,
initial_messages=[{"role": "user", "content": "q"}],
tools=tools,
model="test-model",