Building Custom AI Agents with OpenClaw: Developer Guide

Complete developer guide for building custom AI agents with OpenClaw. Architecture patterns, skill definitions, deployment strategies, and integration blueprints.

E
ECOSIRE Research and Development Team
|2026年3月19日4 分钟阅读785 字数|

使用 OpenClaw 构建自定义 AI 代理:开发人员指南

构建可投入生产的人工智能代理与编写聊天机器人不同。代理必须感知上下文、推理不完整的信息、执行多步骤计划并从故障中恢复——所有这些都无需人工监督。 OpenClaw 是一个专门为这种级别的操作自主性而构建的企业人工智能代理平台,从一开始就为开发人员提供了结构化的运行时、技能组合模型和一流的可观察性。

本指南是为想要从零开始部署、受监控的 OpenClaw 代理的工程师编写的。我们涵盖架构内部、技能创作、内存管理、编排挂钩以及在生产负载下保持代理可靠的部署模式。

要点

  • OpenClaw 代理由技能(原子能力)、内存层和规划执行序列的 Orchestrator 组成。
  • Agent Manifest 文件在运行前声明所有依赖项、权限和工具绑定。
  • 技能是无状态函数,接受类型化输入并发出类型化输出——可测试性是内置的。
  • 工作记忆、情节记忆和长期记忆层满足不同的保留和检索需求。
  • 挂钩(运行前、运行后、错误时)让您可以注入监控、速率限制和回退逻辑,而无需修改核心技能代码。
  • OpenClaw 的沙箱模式可让您在本地重放生产跟踪以进行调试,而无需实时 API 调用。
  • 多代理切换使用类型化消息总线——代理之间没有传递原始字符串。
  • ECOSIRE 为企业团队提供托管 OpenClaw 实施、自定义技能库和持续优化。

了解 OpenClaw 代理模型

每个 OpenClaw 代理都是由四个原语组成:技能内存工具编排器

技能是代理能力的原子单位。技能是接受类型化输入模式并返回类型化输出模式的函数。技能可以是同步的或异步的,并且它们显式声明其外部依赖项。示例:ParseInvoiceSendSlackMessageQueryCRMContactGenerateReport

工具是外部系统绑定。 OpenClaw 附带用于 REST API、数据库、文件系统、浏览器和消息队列的内置工具。您可以在代理清单中注册工具,并在运行时通过依赖项注入将它们注入到技能中。

内存分为三层。工作记忆保存着当前的任务状态——代理的草稿本。情节记忆存储已完成的任务历史记录,可通过当前会话中的语义相似性检索这些任务历史记录。长期记忆在整个会话中持续存在,并存储学到的事实、用户偏好和领域知识。

Orchestrator是推理核心。它接收目标声明,查询可用的技能注册表,构建执行计划并监视每个步骤。当一项技能失败时,协调者会决定是否重试、替换替代技能或升级给人类。

单个代理的架构图如下所示:

User Request
     ↓
[ Orchestrator ]
     ↓ plan
[ Skill Selector ] → [ Skill Registry ]
     ↓ execute
[ Skill Instance ]
     ↓ tool calls
[ Tool Layer ] → [ External Systems ]
     ↓ result
[ Memory Writer ]
     ↓ store
[ Working / Episode / Long-Term Memory ]
     ↓ next step or done
[ Orchestrator ] → response

此循环将持续下去,直到编排器确定目标已满足或达到停止条件。


设置您的开发环境

在编写第一个技能之前,您需要 OpenClaw SDK 和本地代理运行时。

npm install @openclaw/sdk @openclaw/runtime @openclaw/cli
npx openclaw init my-agent --template=typescript

init 命令生成具有以下结构的项目:

my-agent/
  agent.manifest.json   # Agent declaration
  skills/               # Skill implementations
  tools/                # Tool registrations
  memory/               # Memory adapter config
  tests/                # Skill and integration tests
  .openclaw/            # Local runtime state

agent.manifest.json 是最重要的文件。它声明了 OpenClaw 引导代理所需的一切:

{
  "name": "invoice-processor",
  "version": "1.0.0",
  "runtime": "node-20",
  "skills": [
    "skills/extract-line-items.ts",
    "skills/validate-vendor.ts",
    "skills/post-to-erp.ts"
  ],
  "tools": {
    "erp": { "type": "rest", "baseUrl": "${ERP_BASE_URL}", "auth": "bearer" },
    "storage": { "type": "s3", "bucket": "${DOCS_BUCKET}" }
  },
  "memory": {
    "working": { "ttl": 3600 },
    "episode": { "backend": "redis", "maxItems": 500 },
    "longTerm": { "backend": "postgres", "table": "agent_facts" }
  },
  "permissions": ["read:invoices", "write:erp", "read:vendors"]
}

环境变量在运行时注入,并且从不存储在清单中。


写下你的第一个技能

技能遵循严格的合同。它们接受与声明的模式匹配的 input 对象,接收注入的 toolsmemory,并返回 output 对象或抛出类型化的 SkillError

import { defineSkill, SkillError } from "@openclaw/sdk";
import { z } from "zod";

const ExtractLineItemsInput = z.object({
  documentUrl: z.string().url(),
  documentType: z.enum(["invoice", "receipt", "purchase-order"]),
});

const ExtractLineItemsOutput = z.object({
  lineItems: z.array(
    z.object({
      description: z.string(),
      quantity: z.number(),
      unitPrice: z.number(),
      total: z.number(),
    })
  ),
  confidence: z.number().min(0).max(1),
});

export const ExtractLineItems = defineSkill({
  name: "extract-line-items",
  description: "Extracts line items from a document using OCR and LLM parsing",
  input: ExtractLineItemsInput,
  output: ExtractLineItemsOutput,
  tools: ["storage"],
  async run({ input, tools, memory }) {
    const fileBuffer = await tools.storage.get(input.documentUrl);
    if (!fileBuffer) {
      throw new SkillError("DOCUMENT_NOT_FOUND", `No document at ${input.documentUrl}`);
    }

    // OCR + LLM extraction logic here
    const extracted = await runOcrPipeline(fileBuffer);

    await memory.working.set("lastExtraction", extracted);

    return {
      lineItems: extracted.items,
      confidence: extracted.confidence,
    };
  },
});

技能的关键设计规则:

  • 输入无副作用:技能不应修改其输入对象。
  • 键入错误:始终使用机器可读代码抛出 SkillError,而不是通用 Error
  • 声明工具依赖项:仅注入在 tools 数组中声明的工具。未声明的工具会导致启动验证错误。
  • 显式写入内存:技能不会自动保留状态。故意调用 memory.working.set()

内存管理实践

三个内存层有不同的用途,选择正确的层对于代理的正确性至关重要。

工作内存是一个进程内键值存储,在单个任务运行期间有效。使用它在技能之间传递中间结果,而无需通过输出链。当协调器完成或超时时,它会自动清除。

// Skill A writes
await memory.working.set("vendorId", "VND-4521");

// Skill B reads
const vendorId = await memory.working.get("vendorId");

情节记忆是一个语义搜索存储。当任务完成时,编排器可以选择将摘要写入情节内存中。未来的任务可以检索过去类似的事件来指导他们的推理。

// Query past episodes
const relatedEpisodes = await memory.episode.search(
  "invoice from Acme Corp with disputed line items",
  { topK: 3, minScore: 0.75 }
);

长期记忆是您的代理的持久知识库。将其用于跨会话保存的事实:供应商分类规则、用户偏好、学习的域约束。

// Store a learned fact
await memory.longTerm.upsert({
  key: `vendor:${vendorId}:paymentTerms`,
  value: "NET-30",
  source: "invoice-2024-0312",
  confidence: 0.9,
});

一个常见的错误是过度写入长期记忆。保留它以获得稳定、高可信度的事实。短暂的推理属于工作记忆。


用于可观察性和控制的生命周期挂钩

OpenClaw 为每个技能执行公开四个生命周期挂钩:preRunpostRunonErroronTimeout。在代理清单中或以编程方式在引导文件中注册挂钩。

import { AgentRuntime } from "@openclaw/runtime";

const agent = new AgentRuntime({ manifest: "./agent.manifest.json" });

agent.useHook("preRun", async (ctx) => {
  ctx.metadata.startTime = Date.now();
  console.log(`[${ctx.skill}] starting with input keys: ${Object.keys(ctx.input)}`);
});

agent.useHook("postRun", async (ctx) => {
  const elapsed = Date.now() - ctx.metadata.startTime;
  metrics.record("skill.duration", elapsed, { skill: ctx.skill });
});

agent.useHook("onError", async (ctx) => {
  if (ctx.error.code === "RATE_LIMIT_EXCEEDED") {
    await sleep(ctx.error.retryAfterMs);
    return "retry";
  }
  alerting.send(`Skill ${ctx.skill} failed: ${ctx.error.message}`);
  return "escalate";
});

onError 挂钩返回值控制协调器行为:"retry" 触发重试(最多达到配置的最大值),"escalate" 将任务路由到人工队列,"fail" 立即终止任务。


单独测试技能

因为技能已经输入和输出,所以单元测试很简单。 OpenClaw 的测试实用程序提供所有工具接口的模拟实现。

import { testSkill } from "@openclaw/testing";
import { ExtractLineItems } from "../skills/extract-line-items";

describe("ExtractLineItems", () => {
  it("extracts items from a valid invoice", async () => {
    const result = await testSkill(ExtractLineItems, {
      input: {
        documentUrl: "s3://test-bucket/invoice-001.pdf",
        documentType: "invoice",
      },
      mocks: {
        storage: {
          get: jest.fn().mockResolvedValue(samplePdfBuffer),
        },
      },
    });

    expect(result.lineItems).toHaveLength(3);
    expect(result.confidence).toBeGreaterThan(0.8);
  });

  it("throws DOCUMENT_NOT_FOUND for missing file", async () => {
    await expect(
      testSkill(ExtractLineItems, {
        input: { documentUrl: "s3://test-bucket/missing.pdf", documentType: "invoice" },
        mocks: { storage: { get: jest.fn().mockResolvedValue(null) } },
      })
    ).rejects.toMatchObject({ code: "DOCUMENT_NOT_FOUND" });
  });
});

使用沙盒模式进行集成测试。沙盒根据您当前的技能代码重播记录的生产跟踪,在回归到达实时系统之前捕获它们。

npx openclaw sandbox replay --trace=traces/invoice-20240315.json

部署到生产环境

OpenClaw 代理可以部署为 Docker 容器、无服务器功能或长时间运行的进程。企业部署的推荐模式是任务队列后面的容器化代理池。

FROM openclaw/runtime:node-20
WORKDIR /agent
COPY package.json pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile
COPY . .
RUN npx openclaw build
CMD ["npx", "openclaw", "serve", "--workers=4"]

对于高吞吐量场景,请使用自动缩放规则配置代理池。 OpenClaw 运行时在 /metrics 处公开 Prometheus 指标,包括队列深度、技能延迟百分位数、错误率和内存使用情况 - 将这些指标连接到您的警报堆栈。

上线前的生产检查清单:

  • 所有环境变量都位于密钥管理器(AWS Secrets Manager、HashiCorp Vault)中,而不是 .env 文件中。
  • 内存后端(Redis、PostgreSQL)配置了连接池。
  • maxConcurrentTasks 设置与您的基础设施容量相匹配。
  • 外部工具调用的速率限制与供应商 API 限制相匹配。
  • 为耗尽重试的任务配置死信队列。
  • 启用分布式跟踪 (OpenTelemetry),并且跟踪流向您的可观测平台。

常见问题

OpenClaw 编排器如何决定接下来运行哪个技能?

协调器结合使用目标分解和技能匹配。它将顶级目标分解为子目标,然后使用针对每个子目标描述的语义相似性来查询技能注册表。技能按相关性得分、依赖性可用性和历史成功率进行排名。编排器构建技能执行的有向无环图并在开始之前解决依赖关系。

一个技能可以直接调用另一个技能吗?

不——技能不应该直接调用其他技能。跨技能协调是协调者的责任。如果您需要在技能 B 运行之前获得技能 A 的输出,请在 Orchestrator 计划中声明依赖关系。这使技能保持无状态且可单独测试。复合技能是个例外,它被明确标记为编排原语。

技能执行期间外部 API 关闭时会发生什么?

onError 钩子拦截失败。如果挂钩返回 "retry",则协调器将等待配置的退避间隔并重试该技能。用尽重试后,任务将移至死信队列。如果您为相同的功能注册了后备技能,编排器将在放弃之前尝试它。部分任务状态保留在工作内存中,因此任务可以从上次成功的技能中恢复。

如何处理技能在运行时所需的秘密?

代理清单中声明的​​工具在启动时使用凭据进行初始化,而不是针对每个技能进行初始化。您的机密管理器会在容器启动时填充清单中引用的环境变量(${ERP_BASE_URL}${DOCS_BUCKET} 等)。技能永远不会看到原始凭证——它们与运行时注入的预先验证的工具实例进行交互。

代理可以拥有的技能数量是否有限制?

技能注册表没有硬性限制,但如果您注册数百个具有重叠描述的技能,实际编排质量就会降低。将相关技能分组到技能包中,并使用清晰、明确的描述。对于非常大的技能库,请考虑拆分为专门的代理,并使用多代理编排将任务路由到正确的代理。

OpenClaw 代理可以在不依赖云的情况下在本地运行吗?

是的。 OpenClaw 与云无关。运行时、内存后端和工具层都可以配置为使用本地基础设施。 Redis 可以在本地运行,长期内存后端可以指向本地 PostgreSQL 实例,工具集成可以针对内部 API。唯一的外部调用是对 LLM 提供商进行协调器推理 - 如果需要,您可以将其配置为使用本地 LLM。

如何在不中断正在运行的任务的情况下对代理技能进行版本控制?

技能在代理清单中通过语义版本控制进行版本控制。运行时支持在滚动部署期间同时运行多个技能版本。进行中的任务继续使用他们开始时使用的技能版本;新任务选择最新版本。对技能输入/输出模式的重大更改需要主要版本提升以及使用该技能输出的任何代理的迁移计划。


后续步骤

构建生产级人工智能代理需要的不仅仅是好的代码,还需要故障模式、扩展模式和特定领域技能库的操作经验,而这些经验需要数月的时间来开发和完善。

ECOSIRE 的 OpenClaw 自定义技能服务 提供端到端代理开发:需求分析、技能架构、与现有系统的集成、测试、部署和持续优化。我们的团队构建了用于文档处理、ERP 自动化、客户支持、财务分析等的 OpenClaw 代理。

与 OpenClaw 专家交谈 讨论您的自动化需求并获取自定义开发路线图。

E

作者

ECOSIRE Research and Development Team

在 ECOSIRE 构建企业级数字产品。分享关于 Odoo 集成、电商自动化和 AI 驱动商业解决方案的洞见。

通过 WhatsApp 聊天