1. 项目概述:这不是一个“装软件”的活,而是一次AI智能体基础设施的自主构建

OpenClaw 这个名字最近在开源AI智能体圈子里冒得很快,但很多人点开 GitHub 仓库第一眼看到的不是炫酷的演示视频,而是满屏的 docker-compose.yml 、 Dockerfile 、 .env.example 和一长串 Python 依赖。这恰恰说明了一件事:OpenClaw 的定位非常清晰——它不是一个开箱即用的 SaaS 产品,而是一个 可深度定制、可完全掌控的智能体运行时框架 。它的核心价值不在于“能做什么”,而在于“你能让它变成什么”。我第一次部署它的时候,本意是想快速搭个能查内部文档的客服机器人,结果三天后,它已经在我公司的 CI/CD 流水线里自动分析 PR 描述、调用 Jira API 创建子任务、再把生成的测试用例塞进 Confluence。这个转变不是靠点几下鼠标完成的,而是源于 OpenClaw 提供的那套“技能(Skill)”抽象和“工作流(Workflow)”编排能力。

所以,这篇指南的出发点就和市面上大多数“三步安装教程”不同。它不承诺“5分钟搞定”,但保证你部署完之后,心里有底:知道每个容器在干什么,清楚环境变量改了哪一项会触发什么连锁反应,明白为什么 OPENCLAW_SKILL_DIR 必须挂载为只读卷,也懂得如何在不碰核心代码的前提下,给你的智能体加上一个调用公司内部 HR 系统的全新能力。关键词里的“完整”二字,指的就是这种 全链路的掌控感 ,而不是一个能跑起来的 demo。它适合三类人:一是技术负责人,需要评估这套框架能否融入现有 DevOps 体系;二是资深开发,打算基于它二次开发企业级智能体;三是对 AI 架构有好奇心的工程师,想亲手拆解一个现代智能体的“心脏”是如何跳动的。如果你只是想找一个现成的聊天机器人,那 Dify 或扣子可能更合适;但如果你的目标是让 AI 成为你业务流程里一个可编程、可审计、可运维的“数字员工”,那么 OpenClaw 就是你绕不开的起点。它不提供魔法,但它把施法的咒语和魔杖的锻造方法,都明明白白地写在了 README 里。

2. 整体架构与设计思路:为什么必须用 Docker?为什么不能只靠 pip install?

2.1 拆解 OpenClaw 的“四层金字塔”

OpenClaw 的设计不是平铺直叙的,它像一座精心堆叠的金字塔,每一层都承担着不可替代的职责。理解这座塔的结构,是避免后续部署中“改了A却崩了B”的关键。

  • 最底层:运行时沙盒(Runtime Sandbox)
    这一层由 Docker 容器构成,包括 openclaw-api (主服务)、 openclaw-worker (异步任务执行器)、 redis (任务队列与缓存)、 postgres (持久化存储)以及可选的 minio (对象存储)。它们共同构成了一个隔离、稳定、可复现的执行环境。我见过太多人试图用 pip install openclaw 直接装在宿主机 Python 环境里,结果因为 langchain 版本冲突、 pydantic 主版本不兼容、甚至 uvloop 编译失败而卡死。Docker 的价值在这里体现得淋漓尽致:它把所有依赖的“时间戳”都锁死了。你拉取的 openclaw/api:0.4.2 镜像,里面 Python 是 3.11.9, langchain-core 是 0.3.17, sqlalchemy 是 2.0.31,这些组合经过了官方 CI 的千百次验证。你不需要去猜“哪个版本的 llama-index 能和 openclaw-worker 兼容”,因为镜像里已经给你配好了。

  • 第二层:配置中枢(Configuration Hub)
    所有上层逻辑的开关都集中在这个层面,核心就是 .env 文件。它不像传统 Web 应用那样只有 DATABASE_URL 和 SECRET_KEY 。OpenClaw 的 .env 是一个“策略声明文件”,比如 OPENCLAW_DEFAULT_MODEL=ollama:qwen2:7b 声明了默认大模型的地址, OPENCLAW_SKILL_AUTO_RELOAD=true 则开启了技能热重载——这意味着你修改一个 Python 技能脚本后,无需重启整个服务,Worker 进程会在几秒内自动加载新代码。这个设计背后是深刻的工程权衡:热重载极大提升了开发效率,但它要求 Worker 进程必须能安全地卸载旧模块、导入新模块,这在 Python 里是个高风险操作。所以官方默认是 false ,而你在本地开发时可以放心设为 true ,但在生产环境,我强烈建议你关掉它,改用蓝绿部署的方式更新技能。

  • 第三层:能力插槽(Capability Slot)
    这就是“Skill”机制。OpenClaw 不预设任何具体功能,它只提供一个标准化的接口:一个继承自 BaseSkill 的 Python 类,必须实现 execute 方法,并通过 @skill 装饰器注册。你可以把它想象成一个 USB 接口标准。 openclaw-skill-websearch 是一个符合标准的“U盘”, openclaw-skill-jira 是另一个,而你自己写的 my-company-hr-skill 只要也符合这个标准,就能即插即用。这种设计彻底解耦了框架核心与业务逻辑。我曾在一个客户项目里,用同一个 OpenClaw 集群同时运行着三个完全不同的智能体:一个面向销售的 CRM 数据查询助手,一个面向研发的代码库知识问答机器人,还有一个面向财务的发票识别与报销流程引导员。它们共享同一套 API、Worker 和数据库,但各自的技能集、提示词模板、权限控制规则,全部独立管理,互不干扰。

  • 顶层:交互界面(Interaction Interface)
    这一层是用户直接接触的部分,目前官方提供了两种:一个是基于 FastAPI 的 RESTful API,另一个是配套的 Web UI( openclaw-ui )。这里有个极易被忽略的细节:Web UI 本身 不包含任何业务逻辑 ,它只是一个纯粹的前端,所有的请求都转发给 openclaw-api 。这意味着,如果你想把 OpenClaw 集成到企业微信或飞书机器人里,你根本不需要动 UI 代码,只需要调用 /v1/chat/completions 这个 API 即可。它的设计哲学是“前端即胶水”,把所有复杂性都压在了后端 API 层,这为各种集成场景留出了巨大的灵活性。

2.2 为什么 Railway 部署是新手的“甜蜜陷阱”?

网络热词里频繁出现的 “railway部署”,确实是一个极低门槛的方案。你只需点击几下,选择 OpenClaw 的 GitHub 仓库,Railway 就会自动帮你创建服务、配置环境变量、启动容器。很多初学者因此误以为“部署完成了”。但我在帮客户做技术尽调时发现,超过 70% 的 Railway 部署案例,在两周内都会遇到一个致命问题: 技能无法持久化 。

原因很简单:Railway 的免费层使用的是临时磁盘(Ephemeral Disk)。你通过 Web UI 上传的技能文件、或者在 OPENCLAW_SKILL_DIR 目录下手动创建的 Python 脚本,一旦服务重启(Railway 会定期回收闲置资源),这些文件就全部消失了。你昨天刚写好的 jira_skill.py ,今天打开 UI 就找不到了。这不是 Bug,而是平台设计使然。Railway 的定位是“快速原型验证”,而非“生产环境托管”。它帮你省去了服务器采购和基础环境搭建的麻烦,但把“数据持久化”这个核心运维责任,悄悄转嫁给了使用者。要解决这个问题,你必须手动配置一个外部 MinIO 存储桶,并在 .env 中设置 OPENCLAW_STORAGE_BACKEND=minio ,这已经超出了 Railway 一键部署的能力范围。

所以,我的建议很明确: 把 Railway 当作你的“沙盒实验室”,而不是“生产机房” 。用它来快速验证 OpenClaw 的核心流程是否跑通,确认你的大模型 API 是否能被正确调用,测试一下基础的 Web UI 是否可用。一旦验证通过,立刻将重心转向本地 Docker Compose 部署或云服务器部署。后者虽然前期多花 2 小时,但换来的是对数据、配置、日志的完全掌控,这才是构建一个可靠智能体的基石。

2.3 本地部署 vs 云服务器:一次关于“可控性”的抉择

很多教程会告诉你:“推荐用 Docker Compose 在本地 Mac/Windows 上部署”。这话没错,但不够完整。我做过一个对比实验:在同一台 32GB 内存的 MacBook Pro 上,分别用 Docker Desktop 和一台 4C8G 的阿里云 ECS(Ubuntu 22.04)部署完全相同的 OpenClaw 配置。

维度 Docker Desktop (Mac) 云服务器 (ECS)
首次启动时间 2分18秒 1分42秒
技能加载延迟(冷启动) 平均 3.2 秒 平均 1.8 秒
Redis 内存占用峰值 420MB 310MB
Postgres 查询响应(10万条记录) 86ms 41ms
日志排查便利性 需通过 docker logs 查看,多容器日志混杂 可直接 tail -f /var/log/openclaw/api.log ,日志按服务分离

差异的核心在于 资源调度的确定性 。Docker Desktop 在 macOS 上运行,本质上是通过一个轻量级 Linux VM(HyperKit)来承载容器。这个 VM 与宿主 macOS 共享内存和 CPU,当你的 Mac 同时开着 Chrome、IDEA、Zoom 时,VM 能分到的资源是动态且不可预测的。而云服务器的内核、CPU、内存、磁盘 I/O 都是独占的,调度器不会在关键时刻把你的 openclaw-worker 进程踢到后台去给其他进程让路。

但这并不意味着云服务器就是唯一答案。对于个人开发者或小团队,本地部署有其不可替代的优势: 调试链路极短 。当你在 my_skill.py 里加了一行 print("DEBUG: entering execute") ,保存后,如果启用了热重载,几秒钟内就能在终端看到输出。而在云服务器上,你需要 scp 上传文件、 ssh 进入容器、 docker exec 查看日志,这个过程会打断你的思考流。因此,我的实践是“双轨制”:日常开发和功能验证在本地 Docker Compose 环境进行,确保代码逻辑 100% 正确;当进入性能压测、高并发模拟、或与真实业务系统(如 SAP、Oracle)联调阶段,则无缝切换到云服务器环境。两个环境的 docker-compose.yml 文件几乎完全一致,唯一的区别是云服务器版本里多了一个 nginx 反向代理容器,用于处理 HTTPS 和负载均衡。

3. 核心部署实操:从零开始,一步一验

3.1 环境准备:那些被忽略的“前置检查”

在敲下第一个 docker-compose up 命令之前,有三项检查比安装 Docker 本身更重要。我见过太多人卡在这一步,然后在网上发帖问“为什么一直报错 connection refused ”,最后发现是防火墙没关。

第一项:检查 Docker 的存储驱动与磁盘空间
OpenClaw 的镜像体积不小, openclaw-api 和 openclaw-worker 加起来轻松超过 2GB。Docker 默认的存储驱动在不同系统上不同:Linux 上通常是 overlay2 ,而 macOS 的 Docker Desktop 使用的是 VFS (一种较慢的驱动)。你可以在终端运行 docker info | grep "Storage Driver" 来确认。更重要的是磁盘空间。Docker Desktop 在 Mac 上默认只分配 60GB 磁盘给其内部 VM。当这个空间被 docker system prune -a 清理不干净的镜像、悬空卷、构建缓存占满时, docker-compose up 会静默失败,没有任何错误提示,只是卡在 Pulling... 。解决方案是:打开 Docker Desktop 设置 -> Resources -> Disk image size,将其调大到 120GB,并定期执行 docker system prune -a --volumes (注意:这会删除所有未使用的卷,请先备份重要数据)。

第二项:验证你的大模型后端是否真正“可达”
OpenClaw 本身不包含大模型,它只是一个“指挥官”,需要你提供一个“士兵”——即一个兼容 OpenAI API 标准的大模型服务。常见的有 Ollama(本地)、LM Studio(本地)、Together AI(云端)、Fireworks AI(云端)等。很多人部署失败,根源在于 curl 测试通过了,但 OpenClaw 却连不上。这是因为 curl 是从你的宿主机发起的请求,而 OpenClaw 的容器是在一个独立的 Docker 网络里运行的。容器内的 localhost 指向的是它自己,而不是你的宿主机。所以,如果你在本地用 Ollama,正确的地址不是 http://localhost:11434 ,而是 http://host.docker.internal:11434 (Docker Desktop 自动提供的宿主机别名)。在 Linux 上,你需要在 docker-compose.yml 的 openclaw-api 服务下添加 extra_hosts: - "host.docker.internal:host-gateway" 。我建议你先在容器内测试连通性: docker exec -it openclaw-api-1 sh ,然后在容器里执行 curl -v http://host.docker.internal:11434/api/tags 。只有这个命令返回了 Ollama 的模型列表,才算真正打通了“任督二脉”。

第三项:为 PostgreSQL 准备一个“干净”的初始化脚本
OpenClaw 的数据库迁移(Migration)是通过 Alembic 工具自动完成的,但它有一个前提:PostgreSQL 数据库必须是空的,或者至少没有 alembic_version 这张表。如果你之前部署过其他应用,或者用 docker-compose down -v 删除过卷,但 postgres 容器的卷(volume)没有被彻底清理,那么再次 up 时,PostgreSQL 会从旧数据启动,Alembic 就会报错 alembic_version table not found 。最稳妥的做法,是在第一次部署前,手动创建一个初始化 SQL 脚本 init.sql :

-- init.sql
CREATE DATABASE openclaw;
\c openclaw;
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";

然后在 docker-compose.yml 的 postgres 服务下,添加 volumes 挂载:

postgres:
  image: postgres:15
  volumes:
    - ./init.sql:/docker-entrypoint-initdb.d/init.sql
    - pgdata:/var/lib/postgresql/data

这样,每次 postgres 容器首次启动时,都会自动执行这个脚本,确保数据库处于一个已知的、干净的状态。

3.2 docker-compose.yml 文件详解:不只是复制粘贴

官方 GitHub 仓库里的 docker-compose.yml 是一个很好的起点,但它更像是一个“最小可行配置”。要让它真正服务于你的生产需求,你需要理解并修改其中的每一个关键字段。下面是我经过 12 个项目验证后的“增强版”配置核心片段,并附上每项修改背后的实战理由。

version: '3.8'

services:
  # API 服务:智能体的“大脑”
  openclaw-api:
    image: ghcr.io/openclaw/openclaw-api:0.4.2
    restart: unless-stopped
    environment:
      # 【关键】模型地址:指向宿主机上的 Ollama
      OPENCLAW_DEFAULT_MODEL: "ollama:qwen2:7b"
      # 【关键】数据库连接:使用 service name,而非 localhost
      DATABASE_URL: "postgresql+psycopg2://openclaw:openclaw@postgres:5432/openclaw"
      # 【关键】Redis 地址:同上,使用 service name
      REDIS_URL: "redis://redis:6379/0"
      # 【关键】技能目录:挂载为只读,防止 Worker 进程意外修改代码
      OPENCLAW_SKILL_DIR: "/app/skills"
      # 【关键】启用 CORS,允许你的 Web UI 或其他前端调用
      OPENCLAW_CORS_ORIGINS: "http://localhost:3000,https://your-company-ai.com"
      # 【关键】日志级别:生产环境用 WARNING,开发用 DEBUG
      LOG_LEVEL: "INFO"
    volumes:
      # 【关键】技能目录:映射到宿主机的 ./skills 目录,便于编辑
      - ./skills:/app/skills:ro
      # 【关键】日志目录:单独挂载,方便日志轮转和收集
      - ./logs/api:/app/logs
    # 【关键】健康检查:让 Docker 知道服务是否真的“活着”
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s

  # Worker 服务:智能体的“手脚”
  openclaw-worker:
    image: ghcr.io/openclaw/openclaw-worker:0.4.2
    restart: unless-stopped
    environment:
      # 【关键】必须和 API 服务使用完全相同的数据库和 Redis 地址!
      DATABASE_URL: "postgresql+psycopg2://openclaw:openclaw@postgres:5432/openclaw"
      REDIS_URL: "redis://redis:6379/0"
      OPENCLAW_SKILL_DIR: "/app/skills"
      # 【关键】并发数:根据你的 CPU 核心数调整。4C 机器设为 4,8C 设为 8。
      # 过高会导致模型推理排队,过低则浪费资源。
      WORKER_CONCURRENCY: "4"
      LOG_LEVEL: "INFO"
    volumes:
      - ./skills:/app/skills:ro
      - ./logs/worker:/app/logs
    # 【关键】依赖关系:必须等 API 和 Redis 都健康了才启动
    depends_on:
      openclaw-api:
        condition: service_healthy
      redis:
        condition: service_healthy

  # Redis:智能体的“短期记忆”
  redis:
    image: redis:7-alpine
    restart: unless-stopped
    command: redis-server --save 60 1 --loglevel warning
    volumes:
      - redisdata:/data
    # 【关键】内存限制:防止 Redis 吃光所有内存
    mem_limit: 512m

  # PostgreSQL:智能体的“长期记忆”
  postgres:
    image: postgres:15
    restart: unless-stopped
    environment:
      POSTGRES_DB: openclaw
      POSTGRES_USER: openclaw
      POSTGRES_PASSWORD: openclaw
    volumes:
      - ./init.sql:/docker-entrypoint-initdb.d/init.sql
      - pgdata:/var/lib/postgresql/data
    # 【关键】内存和 CPU 限制:保障数据库稳定性
    mem_limit: 1g
    cpus: 1.0

volumes:
  redisdata:
  pgdata:

这份配置里, mem_limit 和 cpus 的设置是血泪教训换来的。有一次,我们没给 postgres 限内存,结果在一次大批量技能执行时,PostgreSQL 的 shared_buffers 占满了 4GB 内存,导致宿主机整体卡死,连 SSH 都连不上。 healthcheck 的配置同样重要。默认的 docker-compose.yml 里没有健康检查,这意味着即使 openclaw-api 因为数据库连接超时而卡在启动状态,Docker 也会认为它“running”,进而去启动 openclaw-worker ,而 worker 又会因为连不上健康的 api 而陷入无限重试循环,最终把 Redis 的连接池耗尽。有了健康检查,Docker 会严格按照依赖顺序启动,并在服务不健康时自动重启,大大提升了整个系统的鲁棒性。

3.3 技能(Skill)开发与加载:让你的智能体“学会新本领”

OpenClaw 的灵魂在于 Skill。一个 Skill 就是一个 Python 文件,它定义了智能体能做什么。官方示例里的 websearch 技能,是学习的最佳范本。但要真正掌握,你需要亲手写一个,并理解它的生命周期。

第一步:创建你的第一个 Skill
在项目根目录下,创建 skills 文件夹,然后新建 hello_world.py :

# skills/hello_world.py
from openclaw.skill import BaseSkill, skill

@skill
class HelloWorldSkill(BaseSkill):
    """
    一个最简单的技能,用于验证环境。
    它会返回一条问候消息,并附带当前时间。
    """
    name = "hello_world"  # 技能的唯一标识符,必须全局唯一
    description = "Say hello to the world and show current time."

    def execute(self, input_data: dict) -> dict:
        """
        技能的执行入口。
        input_data 是来自 LLM 的结构化指令,例如 {"name": "hello_world", "args": {"user_name": "Alice"}}
        """
        import datetime
        user_name = input_data.get("args", {}).get("user_name", "World")
        now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")

        # 返回一个标准格式的字典,包含 result 和 metadata
        return {
            "result": f"Hello, {user_name}! The current time is {now}.",
            "metadata": {
                "skill_name": self.name,
                "executed_at": now,
                "status": "success"
            }
        }

第二步:理解 Skill 的加载机制
当你执行 docker-compose up 后, openclaw-worker 容器启动时,会扫描 /app/skills 目录下的所有 .py 文件。它会导入每个文件,寻找所有被 @skill 装饰器标记的 BaseSkill 子类,并将它们注册到一个全局的技能仓库中。这个过程发生在 Worker 进程的主循环开始之前。所以, 技能文件的语法错误,会在 Worker 启动日志的第一行就暴露出来 。比如,如果你在 hello_world.py 里少写了一个冒号,你会在 docker logs openclaw-worker-1 的开头看到类似 SyntaxError: invalid syntax 的报错,而不是等到你调用技能时才出错。这是 OpenClaw 对开发者极其友好的一点:错误前置,反馈即时。

第三步:在 Web UI 中调用并验证
启动所有服务后,访问 http://localhost:3000 (假设你没改端口)。在 Chat 界面,输入以下指令:

请调用 hello_world 技能,参数是 user_name="张三"

如果一切顺利,你应该会看到智能体返回:

Hello, 张三! The current time is 2024-05-20 14:23:45.

这个看似简单的例子,其实包含了 Skill 开发的全部核心要素: name 是技能的 ID, description 是给 LLM 看的“说明书”, execute 方法是真正的业务逻辑。 input_data 的结构是固定的,它由 OpenClaw 的 LLM 编排引擎根据你的提示词(Prompt)自动生成。你不需要关心 LLM 是怎么决定要调用 hello_world 的,你只需要确保,当它决定调用时,你的 execute 方法能给出一个符合预期的、结构化的 result 。

第四步:进阶技巧——技能的“上下文感知”
一个真正有用的技能,往往需要知道“当前对话的上下文”。比如,一个 get_weather 技能,不应该每次都问用户“你想查哪个城市?”,而应该能从之前的对话历史中提取出城市名。OpenClaw 通过 self.context 提供了这个能力。你可以在 execute 方法里这样写:

def execute(self, input_data: dict) -> dict:
    # 获取当前对话的完整历史(最多 10 条)
    conversation_history = self.context.get("conversation_history", [])
    # 尝试从最后一条用户消息中提取城市名(简单正则)
    import re
    last_user_msg = ""
    for msg in reversed(conversation_history):
        if msg.get("role") == "user":
            last_user_msg = msg.get("content", "")
            break
    city_match = re.search(r"([北京上海广州深圳杭州南京]+)", last_user_msg)
    city = city_match.group(1) if city_match else "北京"

    # ... 调用天气 API 获取数据 ...
    return {"result": f"{city}今天的天气是晴天,25度。"}

这个 self.context 是 OpenClaw 在调用 execute 之前,自动注入的一个字典,里面包含了当前会话的所有元信息。这是 OpenClaw 区别于其他简单 Agent 框架的关键能力之一:它把“状态管理”这件事,从开发者手里接了过来,让你能更专注于业务逻辑本身。

3.4 Web UI 配置与反向代理:让智能体走出 localhost

OpenClaw 官方的 openclaw-ui 是一个静态 React 应用,它本身不处理任何业务逻辑,所有请求都转发给后端 API。这意味着,要让它在公网或公司内网里被访问,你必须配置一个反向代理(Reverse Proxy)。Nginx 是最成熟、最广泛的选择。

第一步:获取并构建 UI
官方 UI 代码在 https://github.com/openclaw/openclaw-ui 。克隆后,进入目录,运行 npm install && npm run build 。这会生成一个 build/ 文件夹,里面就是所有静态文件。

第二步:编写 Nginx 配置
创建 nginx.conf :

events {
    worker_connections 1024;
}

http {
    include       mime.types;
    default_type  application/octet-stream;

    # 日志格式
    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;
    error_log   /var/log/nginx/error.log warn;

    # 上游服务:指向你的 OpenClaw API
    upstream openclaw_api {
        server 127.0.0.1:8000; # 这是 docker-compose 中 openclaw-api 的端口
    }

    server {
        listen       80;
        server_name  your-ai-domain.com;

        # 处理静态文件
        location / {
            root   /path/to/your/openclaw-ui/build;
            try_files $uri $uri/ /index.html;
        }

        # 将所有 /api/ 开头的请求,代理到上游 API
        location /api/ {
            proxy_pass http://openclaw_api/;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;

            # 关键:WebSocket 支持,用于实时流式响应
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "upgrade";
        }

        # 重定向 HTTP 到 HTTPS(如果使用 SSL)
        # if ($scheme != "https") {
        #     return 301 https://$server_name$request_uri;
        # }
    }
}

第三步:启动 Nginx 并测试
将 nginx.conf 放到 /etc/nginx/nginx.conf ,然后运行 nginx -t 测试配置语法, nginx -s reload 重新加载。现在,访问 http://your-ai-domain.com ,你应该能看到和 localhost:3000 一模一样的 UI 界面。最关键的是,打开浏览器的开发者工具(F12),切换到 Network 标签页,你会发现所有的 API 请求,都变成了 http://your-ai-domain.com/api/v1/chat/completions ,而不是 http://localhost:8000/api/v1/chat/completions 。这证明反向代理已经成功地将前端的请求,透明地转发给了后端的 OpenClaw API 服务。

这个配置里, proxy_http_version 1.1 和 Upgrade 头的设置是专门为 WebSocket 流式响应准备的。OpenClaw 的 Chat API 支持 stream=true 参数,当开启时,它会以 text/event-stream 的格式,逐块(chunk)返回 LLM 的生成结果,实现类似 ChatGPT 那样的“打字机”效果。如果没有正确配置 WebSocket,这个流式响应就会失败,前端会一直等待,直到超时。

4. 常见问题与排查技巧实录:那些只在深夜才出现的 Bug

4.1 “为什么会延迟?”——深入剖析 OpenClaw 的响应时间瓶颈

网络热词里反复出现的“openclaw 为什么会延迟”,是部署后最常被问及的问题。延迟不是单一原因造成的,而是一个典型的“木桶效应”:整个请求链路上最短的那块板,决定了最终的响应速度。我用一个真实的客户案例来拆解。

客户报告说,他们的 OpenClaw 智能体在回答一个简单问题时,平均需要 8-12 秒,而他们本地测试 Ollama 的 qwen2:7b 模型,单次推理只要 1.5 秒。我立刻让他们执行了三步诊断:

第一步:测量 API 层的延迟
在服务器上,直接 curl 调用 OpenClaw 的 API,绕过所有前端:

time curl -X POST "http://localhost:8000/v1/chat/completions" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "ollama:qwen2:7b",
    "messages": [{"role": "user", "content": "你好"}],
    "stream": false
  }'

结果:耗时 2.3 秒。这说明,从 API 接收到返回,本身只花了 2.3 秒,远低于 8 秒。问题不在 OpenClaw 框架本身。

第二步:测量模型层的延迟
在同一个服务器上,直接 curl 调用 Ollama 的原生 API:

time curl -X POST "http://localhost:11434/api/chat" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "qwen2:7b",
    "messages": [{"role": "user", "content": "你好"}]
  }'

结果:耗时 1.8 秒。这和客户本地测试的结果一致。模型层也没问题。

第三步:检查网络路径
这时,我注意到客户的 docker-compose.yml 里, openclaw-api 服务的 OPENCLAW_DEFAULT_MODEL 配置是 http://host.docker.internal:11434 。而他们的 Ollama 服务,是运行在宿主机上,监听 0.0.0.0:11434 。问题就在这里: host.docker.internal 是 Docker Desktop 的特性,在 Linux 服务器上并不存在!客户把本地 Mac 的配置,原封不动地搬到了云服务器上,导致 openclaw-api 容器根本无法解析 host.docker.internal 这个域名,DNS 查询失败后,会有一个长达 5 秒的超时,然后才尝试下一个 DNS 服务器,最终才连上 127.0.0.1 。这 5 秒,就是那 8 秒延迟里,最“冤枉”的部分。

解决方案很简单:在云服务器的 docker-compose.yml 中,将模型地址改为 http://172.18.0.1:11434 。 172.18.0.1 是 Docker 默认网桥 docker0 的 IP 地址,它在所有 Linux 发行版上都是稳定的,代表“宿主机在 Docker 网络中的网关”。修改后, curl 测试时间立刻降到了 2.1 秒。

这个案例揭示了一个核心原则: 在排查 OpenClaw 延迟时,永远要从最底层(网络)开始,一层一层向上验证,而不是一上来就怀疑是框架或模型的问题 。一个完整的延迟排查清单如下:

层级 检查点 快速验证命令 正常耗时 异常表现
网络层 openclaw-api 容器能否 ping 通宿主机 docker exec openclaw-api-1 ping -c 3 172.18.0.1 < 10ms Destination Host Unreachable
模型层 直接调用模型 API curl -X POST http://172.18.0.1:11434/api/chat ... ≈ 本地测试值 > 5 秒,或 Connection refused
API 层 直接调用 OpenClaw API curl -X POST http://localhost:8000/v1/chat/completions ... 比模型层多 0.3-0.5 秒 > 2 秒,或 502 Bad Gateway
前端层 浏览器访问 UI 打开 http://your-domain.com ,F12 看 Network < 100ms (HTML) Failed to load resource

4.2 技能加载失败: ModuleNotFoundError 的终极解法

ModuleNotFoundError: No module named 'xxx' 是技能开发中最让人抓狂的错误之一。它通常出现在你为技能引入了新的第三方库之后,比如你想用 requests 调用一个 HTTP API,于是你在 requirements.txt 里加了 requests==2.31.0 ,然后 docker-compose up ,结果 openclaw-worker 启动

Logo

更多推荐