OpenClaw一文详细了解-手搓OpenClaw-4 Tool Runtime

0. 为什么要手搓 OpenClaw

OpenClaw 很强,但完整工程体量也很大。对于大多数开发者来说,直接阅读全量代码会有三个痛点:

  • 模块多:Gateway、Agent、Tools、Sessions、Channels 互相耦合
  • 路径长:一条消息从输入到回复,跨越多个子系统
  • 调试难:没有自己的"最小版本",很难定位问题

所以这个系列采用一个更实用的学习路径:
先做最小闭环,再逐步补齐能力。


1. 目标

用 Python 从 0 到 1 复现 OpenClaw 的核心能力:

  • Agent Loop(工具调用 + 多轮推理)
  • Session 与并发隔离
  • 记忆系统(短期 + 长期)
  • Skills 系统(分层加载)
  • Web/Telegram 等渠道接入

第一篇的阶段目标是:

  • 跑起 FastAPI 服务
  • 打通一个最小 /v1/chat 对话接口
  • 具备会话隔离与并发控制(每会话锁 + 全局信号量)

2. 目标架构

用户输入: CLI/Web/Telegram/Discord
Gateway Server
SessionManager
Session Lock + Global Semaphore
Agent Loop
Prompt Builder
LLM Provider Adapter
Tool Runtime
exec/web/search/read/write...
Memory Manager
短期会话历史
长期记忆: MEMORY.md + 日志
Knowledge RAG
BM25 + Embedding + RRF + Rerank
Skill Registry
L1 元数据
L2 指令加载
L3 资源加载
Cron Scheduler

3. 本篇目标

把工具能力从"占位函数"升级为可扩展的 Tool Runtime:有协议、有注册中心、有执行超时和错误边界。


2. 本篇范围

做什么

  • 工具协议:namedescriptionschemarun()
  • 参数校验:Pydantic
  • 执行约束:超时、异常包装、结果裁剪
  • 内置最小工具:echoread_file(受限目录)、web_fetch(可选)

3. 实现步骤

  1. 定义 ToolSpecToolRuntime
  2. 给每个工具增加输入/输出模型
  3. 在 Agent Loop 中接入工具调度
  4. 统一 tool event 日志(start/success/fail)

3.1 目标

到第四篇结束,具备 3 个可被模型调用并可演示 的工具:

  1. echo:最小闭环工具(验证协议链路)
  2. read_file:读取文件(只读 + 沙箱路径限制)
  3. write_file:写入文件(沙箱限制 + 覆盖策略)
  4. list_files(目录浏览)

每个工具都有:

  • JSON Schema(给模型)
  • Pydantic 入参校验(给执行器)
  • 错误语义(参数错误、权限错误、执行错误)
  • 演示 case(成功 + 失败)

3.2 项目代码

这里贴出了项目里一些片段。

A. 工具协议定义:ToolSpec

文件:openclaw_py/app/core/tools.py

python 复制代码
@dataclass
class ToolSpec:
    name: str
    description: str
    args_model: type[BaseModel]
    handler: ToolHandler
    timeout_seconds: float = 8.0

    def to_openai_tool_schema(self) -> dict[str, Any]:
        schema = self.args_model.model_json_schema()
        return {
            "type": "function",
            "function": {
                "name": self.name,
                "description": self.description,
                "parameters": {
                    "type": "object",
                    "properties": schema.get("properties", {}),
                    "required": schema.get("required", []),
                },
            },
        }

这段代码把"工具给谁看"的两个侧面统一了:

  • 给模型看:to_openai_tool_schema()(用于工具选择)
  • 给执行器看:args_model + handler(用于实际执行)

B. 执行入口:ToolRuntime.execute()

文件:openclaw_py/app/core/tools.py

python 复制代码
async def execute(self, call: ProviderToolCall) -> str:
    spec = self._specs.get(call.name)
    if spec is None:
        return f"tool_error: unknown tool '{call.name}'"

    try:
        payload = json.loads(call.arguments or "{}")
        if not isinstance(payload, dict):
            return "tool_error: arguments must be a JSON object"
    except json.JSONDecodeError:
        return "tool_error: arguments must be valid JSON"

    try:
        args = spec.args_model.model_validate(payload)
    except ValidationError as exc:
        return f"tool_error: invalid arguments: {exc.errors()}"

    try:
        result = await asyncio.wait_for(
            spec.handler(args),
            timeout=spec.timeout_seconds,
        )
    except asyncio.TimeoutError:
        return f"tool_error: tool '{spec.name}' timed out"

这就是"先验参、再执行、最后兜底"的主链路,基本覆盖了真实工具系统的最小可用闭环。

C. 沙箱限制:防止路径越权

文件:openclaw_py/app/core/tools.py

python 复制代码
def _resolve_in_sandbox(sandbox_root: Path, raw_path: str) -> Path:
    raw = Path(raw_path)
    resolved = (sandbox_root / raw).resolve() if not raw.is_absolute() else raw.resolve()
    try:
        resolved.relative_to(sandbox_root)
    except ValueError as exc:
        raise PermissionError(
            f"path '{raw_path}' escapes sandbox root '{sandbox_root}'",
        ) from exc
    return resolved

read_file / write_file 这类工具,安全边界通常比"功能跑通"更重要。

这个检查可以直接拦住 ../ 路径穿越。

D. 内置 4 个演示工具注册

文件:openclaw_py/app/core/tools.py

python 复制代码
runtime.register(ToolSpec(name="echo", ...))
runtime.register(ToolSpec(name="read_file", ...))
runtime.register(ToolSpec(name="write_file", ...))
runtime.register(ToolSpec(name="list_files", ...))

这四个足够覆盖:

  • 协议链路验证(echo
  • 只读文件能力(read_file
  • 写文件能力(write_file
  • 目录可见性(list_files

E. Agent 侧如何接入 Tool Runtime

文件:openclaw_py/app/core/agent.py

python 复制代码
tool_schemas = self.tool_runtime.schemas()
turn = await self.provider.chat(
    messages=self.history,
    tools=tool_schemas,
    tool_choice="auto",
)

for call in turn.tool_calls:
    tool_result = await self.tool_runtime.execute(call)
    self.history.append({
        "role": "tool",
        "tool_call_id": call.id,
        "content": tool_result,
    })

这段把第三篇和第四篇真正串起来了:

  • 第三篇负责循环编排(何时调用、何时收敛)
  • 第四篇负责工具执行(怎么验参、怎么运行、怎么报错)

F. 一次完整调用时序图(请求 -> 工具 -> 回答)

Tool(read/write/list) ToolRuntime Provider Agent FastAPI /v1/chat User Tool(read/write/list) ToolRuntime Provider Agent FastAPI /v1/chat User loop [for each tool_call] POST /v1/chat {session_id, message} agent.chat(message) chat(messages, tools=schema, tool_choice=auto) assistant + tool_calls execute(tool_call) JSON parse + Pydantic validate sandbox check + timeout guard handler(args) result/error tool_result append role=tool to history chat(updated_messages, tools=schema, tool_choice=auto) final assistant text reply {"session_id": "...", "reply": "..."}

这个时序图对应当前项目的真实实现路径,读者可以一边看图,一边对照:

  • openclaw_py/app/core/agent.py
  • openclaw_py/app/core/tools.py
  • openclaw_py/app/core/llm_provider.py

4. 计划修改文件

  • openclaw_py/app/core/tools.py
  • openclaw_py/app/core/agent.py
  • openclaw_py/tests/(工具参数与超时测试)

6. 测试

编写了一个测试run_demo.py

python 复制代码
from __future__ import annotations

import json

import httpx

BASE_URL = "http://127.0.0.1:7788"
SESSION_ID = "demo-tool-runtime"


def _print_title(title: str) -> None:
    print(f"\n== {title} ==")


def send_chat(client: httpx.Client, message: str) -> str:
    resp = client.post(
        f"{BASE_URL}/v1/chat",
        json={"session_id": SESSION_ID, "message": message},
        timeout=60,
    )
    resp.raise_for_status()
    return resp.json()["reply"]


def main() -> None:
    with httpx.Client() as client:
        _print_title("1) Health check")
        health = client.get(f"{BASE_URL}/health", timeout=10)
        health.raise_for_status()
        print(json.dumps(health.json(), ensure_ascii=False, indent=2))

        _print_title("2) echo tool")
        print(
            send_chat(
                client,
                "你现在是工具调用代理。请调用 echo 工具,参数 text 为 "
                "'hello-openclaw-tool-runtime'。调用完成后只返回一句确认结果。",
            ),
        )

        _print_title("3) write_file tool")
        print(
            send_chat(
                client,
                "请调用 write_file 工具,在路径 'demo/runtime-demo.txt' 写入以下内容:"
                "第一行 OpenClaw Tool Runtime Demo;"
                "第二行 This file is generated by tool call."
                "如果文件已存在,请覆盖。完成后简短确认写入成功。",
            ),
        )

        _print_title("4) read_file tool")
        print(
            send_chat(
                client,
                "请调用 read_file 工具读取 'demo/runtime-demo.txt',"
                "然后把读取到的内容原样返回给我。",
            ),
        )

        _print_title("5) list_files tool (optional)")
        print(
            send_chat(
                client,
                "请调用 list_files 工具查看目录 'demo',"
                "并告诉我是否看到了 runtime-demo.txt。",
            ),
        )

    print("\nDemo finished.")


if __name__ == "__main__":
    main()

首先 python run.py 把服务跑起来, 然后运行 python demo/run_demo.py , 大概看到的是如下结果

shell 复制代码
== 1) Health check ==
{
  "ok": true,
  "service": "openclaw-py",
  "active_sessions": 0
}

== 2) echo tool ==
工具调用成功,已确认结果。

== 3) write_file tool ==
文件已成功写入demo/runtime-demo.txt。

== 4) read_file tool ==
OpenClaw Tool Runtime Demo
This file is generated by tool call.

== 5) list_files tool (optional) ==
是的,在demo目录中看到了runtime-demo.txt文件。

Demo finished.

5. 下一篇衔接

第 5 篇单独讲 Session 并发:为什么"同会话串行"是必要条件,以及如何验证不串线。

6. 看到这里,不妨支持一下

如果你已经看到这里,说明你对"手搓 OpenClaw"是真的感兴趣。

这套系列会持续把代码和踩坑都开源出来,不走玄学,尽量做到每篇都能复现。

如果这篇对你有帮助,欢迎随手支持一下:

  • 点个,让我知道这条路线是有价值的
  • 点个关注,后续 3~12 篇更新不会错过
  • 点个收藏,后面实操时可以随时回来看代码片段
  • 有余力的话,来个打赏,我会把更多时间投入到高质量连载里

你的每一次反馈,都会直接影响这个系列更新的速度和深度。 我们下一篇见。

相关推荐
星爷AG I12 小时前
20-4 长时工作记忆(AGI基础理论)
人工智能·agi
#卢松松#13 小时前
用秒悟(meoo)制作了一个GEO查询小工具。
人工智能·创业创新
zandy101113 小时前
Agentic BI 架构实战:当AI Agent接管数据建模、指标计算与可视化全链路
人工智能·架构
数字供应链安全产品选型13 小时前
关键领域清单+SBOM:834号令下软件供应链的“精准治理“逻辑与技术落地路径
人工智能·安全
Flying pigs~~13 小时前
RAG智慧问答项目
数据库·人工智能·缓存·微调·知识库·rag
zuozewei13 小时前
从线下到等保二级生产平台:一次公有云新型电力系统 AI 部署复盘
人工智能
sanshanjianke13 小时前
AI辅助网文创作理论研究初步总结(一):AI辅助网文创作系统
人工智能·ai写作
碳基硅坊13 小时前
OpenClaw 落地应用实践:把 AI 从“能聊“变成“能干活“
人工智能·openclaw
β添砖java13 小时前
深度学习(13)PyTorch神经网络基础
人工智能·深度学习
天疆说13 小时前
【哈密顿力学】深入解读航天器交会最优控制中的Hamilton函数
人工智能·算法·机器学习