70 lines
2.7 KiB
Python
70 lines
2.7 KiB
Python
"""Executor 接口:工具调用的总入口(DESIGN §7.5 落地清单 #5)。
|
||
|
||
`AgentLoop` 不直接调 `tool.execute`,而是 `executor.call_tool(name, args, ctx)`。
|
||
Backend 内部 dispatch:
|
||
|
||
- `HostExecutor`(本步引入):全部 tools in-process,沿用原 `Tool.execute` 行为
|
||
- `DockerExecutor`(Step 3 引入):`shell` / `run_python` 走 `docker exec`,
|
||
其余按 §7.5 #6 信任域二分仍走 host —— 此时 DockerExecutor 内部组合 HostExecutor
|
||
|
||
接口形状刻意保持 backend 无关:`call_tool(name, args, ctx)` 不暴露 `docker exec` /
|
||
`docker cp` / `docker stats` 等 Docker 假设。未来切 gVisor / Firecracker / e2b 时
|
||
应用层零改动,只换 backend driver(§7.5 #5 / §7.9 升级触发表)。
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
from abc import ABC, abstractmethod
|
||
from dataclasses import dataclass
|
||
from pathlib import Path
|
||
from typing import Any, Callable, Dict, List, Optional
|
||
from uuid import UUID
|
||
|
||
|
||
@dataclass
|
||
class ExecCtx:
|
||
"""每次 tool 调用的执行上下文。
|
||
|
||
带身份 / 范围 / 取消钩子;arg-irrelevant 信息从 args 剥离。
|
||
host backend 当前只用 cancel_check;docker backend 会:
|
||
- user_id → 找 / 起 per-user 容器
|
||
- working_dir → 拼 `docker exec --workdir /workspace/<wd_name>`
|
||
- task_id → 临时文件命名空间 `/tmp/zcbot/<task_id>/`
|
||
- cancel_check → 轮询期间响应停止按钮(主动 `kill -- -PGID`)
|
||
"""
|
||
user_id: UUID
|
||
task_id: UUID
|
||
working_dir: Path
|
||
cancel_check: Optional[Callable[[], bool]] = None
|
||
|
||
|
||
@dataclass
|
||
class ToolResult:
|
||
"""工具调用统一返回。
|
||
|
||
普通 `Tool.execute` 返 str;发布产物的工具可返字符串兼容的 rich result。
|
||
`content` 是 LLM 拿到的文本,`artifacts` 只进事件和消息元数据;exit_code 作
|
||
backend 内部使用 hint(0=ok / 1=tool 抛异常 / 2=参数非法 / 124=timeout 等),
|
||
不影响 LLM 接口。
|
||
"""
|
||
content: str
|
||
exit_code: int = 0
|
||
# Optional structured, task-relative deliverables. Existing executors/tools that only
|
||
# return text remain fully compatible.
|
||
artifacts: tuple[dict, ...] = ()
|
||
|
||
|
||
class Executor(ABC):
|
||
"""工具调度抽象 —— 见模块 docstring。"""
|
||
|
||
@abstractmethod
|
||
def call_tool(self, name: str, args: Dict[str, Any], ctx: ExecCtx) -> ToolResult:
|
||
"""执行单次 tool 调用。永远返 ToolResult,不抛异常(异常包成 exit_code=1)。"""
|
||
|
||
@abstractmethod
|
||
def schemas(self) -> List[Dict[str, Any]]:
|
||
"""暴露给 LLM 的 OpenAI tool schema 列表;`AgentLoop._stream_llm` 用。"""
|
||
|
||
@abstractmethod
|
||
def has_tool(self, name: str) -> bool:
|
||
"""schema 列表覆盖的 tool 名;主要给测试 / 诊断用。"""
|