Files
hunjing/backend/app/modules/tool_runtime/definition.py
T
2026-08-21 16:00:13 +08:00

93 lines
3.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""工具定义: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")