refactor(agent): make runner consume required runtime
This commit is contained in:
@@ -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
@@ -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],
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user