zcbot/core/executor.py

70 lines
2.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.

"""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 名;主要给测试 / 诊断用。"""