93 lines
3.7 KiB
Python
93 lines
3.7 KiB
Python
"""工具定义:ToolSpec 描述工具元数据与请求构造方式,ToolResult 为执行产出。
|
||
|
||
字段口径对齐 docs/architecture/service-boundaries.md(工具定义字段、风险分级、
|
||
权限策略)。工具的实际执行有两条路径:
|
||
- 沙箱路径:``build_request`` 产出沙箱 ExecRequest,由 SandboxClient 执行(文件/shell 类)。
|
||
- 直接执行路径:``executor`` 非 None 时由 runtime 注入 http_client、密钥等上下文直接执行
|
||
(HTTP 类工具如 fetch_url,不走沙箱)。
|
||
|
||
本模块是纯领域数据结构(无 IO):``ExecRequest`` 仅在类型注解中引用(``TYPE_CHECKING``),
|
||
运行时不依赖 ``integrations``;实际 ExecRequest 的构造在 ``application/tool_runtime/``。
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
from collections.abc import Awaitable, Callable
|
||
from dataclasses import dataclass, field
|
||
from typing import TYPE_CHECKING, Any, Protocol
|
||
|
||
from app.modules.safety.governor import PermissionPolicy
|
||
|
||
if TYPE_CHECKING:
|
||
# 仅类型注解用:build_request 产出沙箱 ExecRequest,但本模块运行时不依赖 integrations
|
||
# (保持 modules 零 IO 依赖);实际构造在 application/tool_runtime/builtins。
|
||
from app.integrations.sandbox.models import ExecRequest
|
||
|
||
ToolInput = dict[str, Any]
|
||
RequestBuilder = Callable[[ToolInput], "ExecRequest"]
|
||
# 前向引用 ToolResult(定义在下方),避免循环依赖;TYPE_CHECKING 下解析为真实类。
|
||
ToolExecutor = Callable[[ToolInput, dict[str, Any] | None], Awaitable["ToolResult"]]
|
||
|
||
|
||
class ToolRuntimeLike(Protocol):
|
||
"""Agent loop 所需的最小工具运行时契约。"""
|
||
|
||
async def execute(
|
||
self,
|
||
tool: str,
|
||
input: ToolInput,
|
||
*,
|
||
run_id: str | None = None,
|
||
character_id: str | None = None,
|
||
trace_id: str | None = None,
|
||
confirmed: bool = False,
|
||
context: dict[str, Any] | None = None,
|
||
) -> ToolResult: ...
|
||
|
||
|
||
@dataclass(frozen=True)
|
||
class ToolResult:
|
||
"""工具执行产出(映射沙箱 ExecResponse 或 executor 直接产出)。"""
|
||
|
||
ok: bool
|
||
exit_code: int | None
|
||
stdout: str
|
||
stderr: str
|
||
duration_ms: int
|
||
timeout: bool
|
||
truncated: bool
|
||
trace_id: str
|
||
|
||
|
||
@dataclass(frozen=True)
|
||
class ToolSpec:
|
||
"""工具规格:名称、风险、权限策略与请求构造器/执行器。
|
||
|
||
执行路径二选一:
|
||
- ``executor`` 非 None:直接执行,跳过沙箱(HTTP 类工具);``build_request`` 此时可为占位。
|
||
- ``executor`` 为 None:走沙箱路径,用 ``build_request`` 构造 ExecRequest。
|
||
"""
|
||
|
||
name: str
|
||
description: str
|
||
risk_level: str # L0–L4
|
||
permission_policy: PermissionPolicy
|
||
build_request: RequestBuilder = field(default_factory=lambda: _placeholder_builder)
|
||
default_timeout_ms: int = 30_000
|
||
side_effect: bool = True
|
||
audit_required: bool = True
|
||
# 是否按 run/trace 隔离工作目录(文件类工具):runtime 会把 workdir 改写为
|
||
# ``<workdir>/<run_key>``,防跨 run 信息泄漏与文件名冲突;run_key 经 sanitize 防路径注入。
|
||
isolated_workspace: bool = False
|
||
# 非 None 时走直接执行路径(HTTP 类工具),跳过沙箱;context 由 runtime 注入。
|
||
executor: ToolExecutor | None = None
|
||
# 工具入参 JSON Schema(供 LLM 生成正确参数),None 表示无参数。
|
||
input_schema: dict[str, Any] | None = None
|
||
|
||
|
||
def _placeholder_builder(
|
||
_input: ToolInput,
|
||
) -> ExecRequest: # pragma: no cover - 仅占位,executor 工具不走此路径
|
||
"""executor 工具的 build_request 占位:正常不应被调用(runtime 见 executor 非 None 会跳过)。"""
|
||
raise NotImplementedError("该工具使用 executor 直接执行,不应调用 build_request")
|