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
+4 -5
View File
@@ -301,12 +301,13 @@ class AgentLoop:
# One file-read/write tracker per logical session. The tool registry is
# shared by this loop, so tools resolve the active state via contextvars.
self._file_state_store = FileStateStore()
self.runner = AgentRunner(provider)
self.runner = AgentRunner()
self.subagents = SubagentManager(
provider=provider,
workspace=workspace,
bus=bus,
model=self.model,
context_window_tokens=self.context_window_tokens,
tools_config=_tc,
max_tool_result_chars=self.max_tool_result_chars,
restrict_to_workspace=restrict_to_workspace,
@@ -456,8 +457,7 @@ class AgentLoop:
self.provider = provider
self.model = model
self.context_window_tokens = context_window_tokens
self.runner.provider = provider
self.subagents.set_provider(provider, model)
self.subagents.set_provider(provider, model, context_window_tokens)
self.consolidator.set_provider(provider, model, context_window_tokens)
self._sync_replay_max_messages()
self._provider_signature = snapshot.signature
@@ -864,7 +864,7 @@ class AgentLoop:
result = await self.runner.run(AgentRunSpec(
initial_messages=initial_messages,
tools=effective_tools,
model=self.model,
runtime=self.llm_runtime(),
max_iterations=self.max_iterations,
max_tool_result_chars=self.max_tool_result_chars,
hook=hook,
@@ -872,7 +872,6 @@ class AgentLoop:
concurrent_tools=True,
workspace=effective_scope.project_path,
session_key=session.key if session else None,
context_window_tokens=self.context_window_tokens,
context_block_limit=self.context_block_limit,
provider_retry_mode=self.provider_retry_mode,
progress_callback=on_progress,
+27 -28
View File
@@ -30,6 +30,7 @@ from nanobot.utils.helpers import (
strip_reasoning_tags,
strip_think,
)
from nanobot.utils.llm_runtime import LLMRuntime
from nanobot.utils.prompt_templates import render_template
from nanobot.utils.runtime import (
EMPTY_FINAL_RESPONSE_MESSAGE,
@@ -61,12 +62,9 @@ class AgentRunSpec:
initial_messages: list[dict[str, Any]]
tools: ToolRegistry
model: str
runtime: LLMRuntime
max_iterations: int
max_tool_result_chars: int
temperature: float | None = None
max_tokens: int | None = None
reasoning_effort: str | None = None
hook: AgentHook | None = None
error_message: str | None = _DEFAULT_ERROR_MESSAGE
max_iterations_message: str | None = None
@@ -74,7 +72,6 @@ class AgentRunSpec:
fail_on_tool_error: bool = False
workspace: Path | None = None
session_key: str | None = None
context_window_tokens: int | None = None
context_block_limit: int | None = None
provider_retry_mode: str = "standard"
progress_callback: Any | None = None
@@ -105,8 +102,7 @@ class AgentRunResult:
class AgentRunner:
"""Run a tool-capable LLM loop without product-layer concerns."""
def __init__(self, provider: LLMProvider):
self.provider = provider
def __init__(self) -> None:
self.context_governor = ContextGovernor()
@staticmethod
@@ -189,7 +185,7 @@ class AgentRunner:
{
"phase": "final_response",
"iteration": iteration,
"model": spec.model,
"model": spec.runtime.model,
"assistant_message": assistant_message,
"completed_tool_results": [],
"pending_tool_calls": [],
@@ -344,15 +340,15 @@ class AgentRunner:
injection_cycles = 0
compacted_tool_call_ids: set[str] = set()
governance_config = ContextGovernanceConfig(
provider=self.provider,
model=spec.model,
provider=spec.runtime.provider,
model=spec.runtime.model,
tools=spec.tools,
workspace=spec.workspace,
session_key=spec.session_key,
max_tool_result_chars=spec.max_tool_result_chars,
context_window_tokens=spec.context_window_tokens,
context_window_tokens=spec.runtime.context_window_tokens,
context_block_limit=spec.context_block_limit,
max_tokens=spec.max_tokens,
max_tokens=spec.runtime.generation.max_tokens,
inflight_start_index=len(spec.initial_messages),
)
@@ -429,7 +425,7 @@ class AgentRunner:
{
"phase": "awaiting_tools",
"iteration": iteration,
"model": spec.model,
"model": spec.runtime.model,
"assistant_message": assistant_message,
"completed_tool_results": [],
"pending_tool_calls": [tc.to_openai_tool_call() for tc in response.tool_calls],
@@ -491,7 +487,7 @@ class AgentRunner:
{
"phase": "tools_completed",
"iteration": iteration,
"model": spec.model,
"model": spec.runtime.model,
"assistant_message": assistant_message,
"completed_tool_results": completed_tool_results,
"pending_tool_calls": [],
@@ -645,7 +641,7 @@ class AgentRunner:
{
"phase": "final_response",
"iteration": iteration,
"model": spec.model,
"model": spec.runtime.model,
"assistant_message": messages[-1],
"completed_tool_results": [],
"pending_tool_calls": [],
@@ -702,16 +698,14 @@ class AgentRunner:
kwargs: dict[str, Any] = {
"messages": messages,
"tools": tools,
"model": spec.model,
"model": spec.runtime.model,
"retry_mode": spec.provider_retry_mode,
"on_retry_wait": spec.retry_wait_callback,
}
if spec.temperature is not None:
kwargs["temperature"] = spec.temperature
if spec.max_tokens is not None:
kwargs["max_tokens"] = spec.max_tokens
if spec.reasoning_effort is not None:
kwargs["reasoning_effort"] = spec.reasoning_effort
generation = spec.runtime.generation
kwargs["temperature"] = generation.temperature
kwargs["max_tokens"] = generation.max_tokens
kwargs["reasoning_effort"] = generation.reasoning_effort
return kwargs
async def _request_model(
@@ -746,7 +740,7 @@ class AgentRunner:
not wants_streaming
and spec.stream_progress_deltas
and spec.progress_callback is not None
and getattr(self.provider, "supports_progress_deltas", False) is True
and getattr(spec.runtime.provider, "supports_progress_deltas", False) is True
)
progress_state: dict[str, bool] | None = None
@@ -774,7 +768,7 @@ class AgentRunner:
async def _stream_recover() -> None:
await hook.on_stream_end(context, resuming=True)
coro = self.provider.chat_stream_with_retry(
coro = spec.runtime.provider.chat_stream_with_retry(
**kwargs,
on_content_delta=_stream,
on_thinking_delta=_thinking,
@@ -805,12 +799,12 @@ class AgentRunner:
context.streamed_content = True
await spec.progress_callback(incremental)
coro = self.provider.chat_stream_with_retry(
coro = spec.runtime.provider.chat_stream_with_retry(
**kwargs,
on_content_delta=_stream_progress,
)
else:
coro = self.provider.chat_with_retry(**kwargs)
coro = spec.runtime.provider.chat_with_retry(**kwargs)
# Streaming requests already have provider-level idle timeouts
# (NANOBOT_STREAM_IDLE_TIMEOUT_S). Do not also apply the outer wall-clock
@@ -985,7 +979,7 @@ class AgentRunner:
messages: list[dict[str, Any]],
) -> LLMResponse:
kwargs = self._build_request_kwargs(spec, messages, tools=None)
return await self.provider.chat_with_retry(**kwargs)
return await spec.runtime.provider.chat_with_retry(**kwargs)
@staticmethod
def _budget_exhausted_finalization_messages(
@@ -1033,7 +1027,12 @@ class AgentRunner:
tools = spec.tools.get_definitions()
except Exception:
tools = None
prompt_tokens, _ = estimate_prompt_tokens_chain(self.provider, spec.model, messages, tools)
prompt_tokens, _ = estimate_prompt_tokens_chain(
spec.runtime.provider,
spec.runtime.model,
messages,
tools,
)
assistant_message = build_assistant_message(
response.content or "",
tool_calls=[tc.to_openai_tool_call() for tc in response.tool_calls],
+18 -5
View File
@@ -26,6 +26,7 @@ from nanobot.security.workspace_access import (
reset_workspace_scope,
workspace_sandbox_status,
)
from nanobot.utils.llm_runtime import LLMRuntime
from nanobot.utils.prompt_templates import render_template
@@ -81,6 +82,7 @@ class SubagentManager:
bus: MessageBus,
max_tool_result_chars: int,
model: str | None = None,
context_window_tokens: int | None = None,
tools_config: ToolsConfig | None = None,
restrict_to_workspace: bool = False,
disabled_skills: list[str] | None = None,
@@ -94,6 +96,7 @@ class SubagentManager:
self.workspace = workspace
self.bus = bus
self.model = model or provider.get_default_model()
self.context_window_tokens = context_window_tokens or defaults.context_window_tokens
self.tools_config = tools_config or ToolsConfig()
self.max_tool_result_chars = max_tool_result_chars
self.restrict_to_workspace = restrict_to_workspace
@@ -113,7 +116,7 @@ class SubagentManager:
if fail_on_tool_error is not None
else defaults.fail_on_tool_error
)
self.runner = AgentRunner(provider)
self.runner = AgentRunner()
self._llm_wall_timeout_for_session = llm_wall_timeout_for_session
self._running_tasks: dict[str, asyncio.Task[None]] = {}
self._task_statuses: dict[str, SubagentStatus] = {}
@@ -149,10 +152,16 @@ class SubagentManager:
ToolLoader().load(ctx, registry, scope="subagent")
return registry
def set_provider(self, provider: LLMProvider, model: str) -> None:
def set_provider(
self,
provider: LLMProvider,
model: str,
context_window_tokens: int | None = None,
) -> None:
self.provider = provider
self.model = model
self.runner.provider = provider
if context_window_tokens is not None:
self.context_window_tokens = context_window_tokens
async def spawn(
self,
@@ -246,11 +255,15 @@ class SubagentManager:
)
token = bind_workspace_scope(workspace_scope) if workspace_scope is not None else None
try:
runtime = LLMRuntime.capture(
self.provider,
self.model,
context_window_tokens=self.context_window_tokens,
).with_generation_overrides(temperature=temperature)
result = await self.runner.run(AgentRunSpec(
initial_messages=messages,
tools=tools,
model=self.model,
temperature=temperature,
runtime=runtime,
max_iterations=self.max_iterations,
max_tool_result_chars=self.max_tool_result_chars,
hook=_SubagentHook(task_id, status),
+1 -1
View File
@@ -38,8 +38,8 @@ class LLMRuntime:
snapshot_signature: tuple[object, ...] | None = None,
) -> LLMRuntime:
"""Capture provider defaults without retaining mutable generation state."""
generation = provider.generation
defaults = GenerationSettings()
generation = getattr(provider, "generation", defaults)
return cls(
provider=provider,
model=model,