Files
LangBot/plugins/Runner/LocalAgent/pkg/run_assembly.py
T

129 lines
4.8 KiB
Python

"""Run-scoped capability assembly for the local-agent runner."""
from __future__ import annotations
from dataclasses import dataclass
from typing import Any
from langbot_plugin.api.entities.builtin.provider.message import Message
from langbot_plugin.api.entities.builtin.resource.tool import LLMTool
from langbot_plugin.api.entities.builtin.runner import RunnerContext
from pkg.agent_core import AgentLoopHooks, LangBotContextHooks, LangBotToolExecutor
from pkg.agent_core.langbot import LangBotSteeringPuller
from pkg.config import (
get_max_tool_iterations,
get_max_tool_result_chars,
get_remove_think,
get_tool_execution_mode,
model_reasoning_levels,
parse_model_config,
)
from pkg.context_pipeline import ContextAssembler, ContextBudget, HostContextTokenCounter, LLMContextSummarizer
from pkg.model_calling import build_llm_tools
class NoAuthorizedModelError(Exception):
"""Raised when no configured model is available for this run."""
@dataclass(frozen=True)
class AgentRunAssembly:
"""Everything the LangBot adapter needs to construct an AgentLoop."""
model_ids: list[str]
messages: list[Message]
tools: list[LLMTool]
tool_executor: LangBotToolExecutor
hooks: AgentLoopHooks
streaming: bool
max_tool_iterations: int
tool_execution_mode: str
remove_think: bool
class AgentRunAssembler:
"""Assemble LangBot-authorized run capabilities into AgentLoop inputs."""
def __init__(self, api: Any, ctx: RunnerContext):
self.api = api
self.ctx = ctx
async def assemble(self) -> AgentRunAssembly:
model_ids = self._resolve_model_ids()
if not model_ids:
raise NoAuthorizedModelError("No authorized model for local-agent")
allowed_tools = self._allowed_tool_names()
max_tool_iterations = get_max_tool_iterations(self.ctx.config)
max_tool_result_chars = get_max_tool_result_chars(self.ctx.config)
tool_execution_mode = get_tool_execution_mode(self.ctx.config)
remove_think = get_remove_think(self.ctx.config)
context_budget = ContextBudget.from_context(self.ctx)
reasoning_level = model_reasoning_levels(self.ctx.config).get(model_ids[0], "provider_default")
summarizer = LLMContextSummarizer(
self.api, model_ids[0], remove_think=remove_think, reasoning_level=reasoning_level
)
tools = await self._build_tools(allowed_tools)
token_counter = HostContextTokenCounter(self.api, model_ids[0], tools, reasoning_level=reasoning_level)
context_assembly = await ContextAssembler(
self.api,
self.ctx,
budget=context_budget,
summarizer=summarizer,
token_counter=token_counter,
).assemble()
return AgentRunAssembly(
model_ids=model_ids,
messages=context_assembly.messages,
tools=tools,
tool_executor=LangBotToolExecutor(
self.api,
allowed_tools,
max_result_chars=max_tool_result_chars,
),
hooks=LangBotContextHooks(
context_budget,
summarizer=summarizer,
token_counter=token_counter,
steering_puller=LangBotSteeringPuller(self.api),
),
streaming=self._streaming_supported(),
max_tool_iterations=max_tool_iterations,
tool_execution_mode=tool_execution_mode,
remove_think=remove_think,
)
def _resolve_model_ids(self) -> list[str]:
allowed_model_ids = {model.model_id for model in self.api.get_allowed_models()}
return parse_model_config(self.ctx.config.get("model"), allowed_model_ids)
def _allowed_tool_names(self) -> set[str]:
from pkg.box import SANDBOX_TOOLS
names = {tool.tool_name for tool in self.api.get_allowed_tools()}
if not self.ctx.config.get("box-enabled", True) or not self.ctx.context.available_apis.box:
names -= SANDBOX_TOOLS
return names
def _streaming_supported(self) -> bool:
metadata = getattr(self.ctx.runtime, "metadata", {}) or {}
if not isinstance(metadata, dict):
runtime_supported = True
else:
runtime_supported = bool(metadata.get("streaming_supported", True))
delivery = getattr(self.ctx, "delivery", None)
delivery_supported = bool(getattr(delivery, "supports_streaming", False))
return runtime_supported and delivery_supported
async def _build_tools(self, allowed_tools: set[str]) -> list[LLMTool]:
resources = getattr(self.ctx, "resources", None)
tool_resources = getattr(resources, "tools", None) if resources else None
return await build_llm_tools(self.api, allowed_tools, tool_resources)
__all__ = ["AgentRunAssembler", "AgentRunAssembly", "NoAuthorizedModelError"]