1. 项目概述:Paperclip 不是回形针,而是一个轻量级 AI Agent 协作框架
你搜“paperclip”时,第一反应可能是办公桌抽屉里那枚银色小金属片——但最近在 Node.js 和 React 开发者圈子里,“Paperclip”正悄悄成为高频词。它不是 UI 组件库,也不是前端脚手架,更不是某个大厂新发布的闭源平台。它是一个开源的、面向本地化 AI Agent 协作的轻量级运行时框架,核心定位非常清晰:让开发者用 React 写 Agent 的 UI 逻辑,用 Node.js 编排 Agent 的行为流,用 OpenCLAW 做底层工具调用与记忆管理,三者解耦但无缝协同。我第一次看到它的 GitHub README 时,第一反应是:“这不就是我们团队过去半年反复踩坑后想自己造却没时间做的东西?”——它把 AI Agent 开发中“状态难管、工具难接、UI 难同步、调试难定位”这四大痛点,用一套极简约定+最小 runtime 就压平了。
Paperclip 的本质,是一套协议层 + 运行时胶水。它不替代 LLM 推理(你仍用 Ollama、Llama.cpp 或 OpenRouter),不接管你的 Prompt 工程(你写自己的 system message),也不强制你用某类 Agent 架构(ReAct、Plan-and-Execute、Toolformer 都可插拔)。它只做三件事:定义 Agent 与 UI 之间“该传什么、何时传、怎么序列化”,提供一个基于 Express 的轻量 Node.js Server 作为执行中枢,以及封装 OpenCLAW 的调用链路,让useTool("search_web")这样的 Hook 调用能真正触发浏览器外的真实动作。这意味着:一个 React 组件里写的const result = await useTool("read_file", { path: "/notes/todo.md" }),背后会自动走 OpenCLAW 的文件读取插件,结果再原路返回到组件 state,整个过程对前端开发者透明,且全程可 debug、可重放、可审计。
它适合谁?不是给算法研究员准备的,而是给有真实业务场景、需要快速验证 Agent 流程、又不想被 LangChain 复杂抽象或 AutoGen 繁重配置绑架的全栈工程师。比如你正在做一个内部知识助手,要支持“查文档→摘要→生成会议纪要→发邮件”四步链路;或者你在做自动化测试报告分析,需从 Jenkins API 拉日志、用 LLM 解析失败原因、再调 Jira API 创建 issue——这些都不是 Demo 场景,而是每天要跑、要改、要上线的活儿。Paperclip 的价值,就体现在你改完一个 Tool 插件后,不用重启服务、不用改前端代码、甚至不用动任何配置,刷新页面就能看到新能力生效。这种“改即所见”的开发体验,在当前绝大多数 AI Agent 框架里,是稀缺的。
2. 整体架构设计与选型逻辑:为什么是 Node.js + React + OpenCLAW 这个组合?
2.1 核心分层:UI 层、协调层、执行层的职责边界
Paperclip 的架构不是“大一统”,而是明确划出三层,每层只解决一类问题,且接口契约极其精简:
UI 层(React):只负责渲染、用户输入、状态展示。所有
useTool、useMemory、useAgentState这类 Hook,本质都是 React Query 的封装,它们不直接调用任何外部服务,只向协调层发起标准化请求(HTTP POST /api/tool-call),并监听/api/stream的 SSE 响应。这意味着:你可以用 Vite 启动纯前端 demo,连 Node.js 都不装,只要 mock 掉后端接口,UI 就能跑通逻辑流。协调层(Node.js Express Server):这是 Paperclip 的心脏。它不做推理、不存记忆、不写插件,只做三件事:① 接收 UI 层的 tool call 请求,校验参数合法性;② 将请求转发给 OpenCLAW 执行器,并将 OpenCLAW 返回的结构化结果(含 tool output、next step hint、error trace)原样透传回 UI;③ 提供
/api/memory接口,作为 UI 层与 OpenCLAW 记忆模块之间的代理。这个层的代码量不到 300 行,但它是整个系统可调试、可审计、可监控的关键——所有请求都有 trace id,所有 tool call 都记录 timestamp 和耗时,所有错误都带 stack trace 上下文。执行层(OpenCLAW):它才是真正的“手脚”。Paperclip 不重复造轮子,而是深度集成 OpenCLAW 的 Plugin System。你写一个
web-search.ts插件,导出execute函数,Paperclip 的协调层就会自动把它注册为search_web工具。OpenCLAW 负责处理认证(如 Bing Search API Key)、重试策略、限流熔断、结果缓存,而 Paperclip 只关心“这个工具能不能被 UI 调用”和“调用结果怎么安全传回去”。
提示:这种分层不是为了炫技,而是为了解决实际协作中的“责任归属”问题。前端同学改 UI 逻辑,后端同学维护工具插件,AI 工程师调优 LLM 参数——三组人可以完全并行工作,互不阻塞。我们团队曾用这套模式,让前端在 2 天内完成会议纪要生成 UI,后端在 1 天内接入企业邮箱 SMTP 插件,AI 同学在半天内优化了摘要 prompt,最终 4 天上线 MVP。
2.2 为什么选 Node.js 而非 Python 或 Rust?
Node.js 的选择,源于三个硬性约束:
与 React 生态的零摩擦集成:Vite/Next.js 项目默认支持
importCommonJS 模块,而 OpenCLAW 的 TypeScript 插件生态天然兼容 Node.js。如果换成 Python,就得额外维护一套 FastAPI 服务,前后端跨域、cookie 传递、JWT 验证等都要重新设计;换成 Rust,虽然性能更好,但插件开发门槛陡增,团队里能写 async/await 的人远多于能写 Tokio 的人。热重载与调试体验不可替代:Paperclip 的协调层支持
nodemon --watch src --exec ts-node src/server.ts,改一行代码,服务秒级重启。更重要的是,VS Code 的 Attach to Process 调试能直接断点到tool-call处理函数里,看到req.body的完整结构、openclaw.execute()的返回值、甚至res.send()前的最后一刻状态。Python 的 pdb 或 Rust 的 rust-gdb 在这个场景下,调试效率至少打七折。部署成本与运维熟悉度:CentOS 7.9 上装 Node.js 18.x 是运维同学的日常操作(
curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash - && sudo apt-get install -y nodejs),而部署一个 Python 3.11 + uvicorn + OpenCLAW 的 Docker 镜像,需要额外维护 base image、pip 源、依赖冲突解决。我们线上环境用的是阿里云 ECS,Node.js 进程用 PM2 管理,内存占用稳定在 120MB 以内,比同等功能的 Python 服务低 40%。
注意:有人会质疑 Node.js 的 CPU 密集型任务处理能力。但 Paperclip 的协调层根本不做推理!它只做 JSON 序列化、HTTP 转发、日志记录。真正的 heavy lifting 全部交给 OpenCLAW 的插件——而 OpenCLAW 本身支持 WASM、Python subprocess、甚至远程 gRPC 调用。所以 Node.js 在这里不是“计算引擎”,而是“调度中枢”,它的单线程模型反而是优势:没有线程安全问题,没有竞态条件,状态管理极度简单。
2.3 为什么 React 是 UI 层唯一推荐方案?
React 被选中,不是因为它是“最流行”,而是因为它解决了 Agent UI 的两个独特需求:
细粒度状态响应:Agent 执行是异步、分步、可能中断的。用户点击“生成纪要”按钮后,UI 需要实时显示:“正在读取会议录音… → 正在转文字… → 正在摘要… → 正在生成 Markdown…”。React 的
useState+useEffect组合,配合 Paperclip 提供的useAgentStatus()Hook,能用几行代码实现这种“状态机驱动 UI”的效果。Vue 的 Composition API 虽然也能做到,但其响应式系统在嵌套对象 deep watch 场景下偶有性能抖动;Svelte 的 reactive 声明虽简洁,但社区缺乏成熟的 AI 工具链生态支持。Server Components 的天然适配:Paperclip 的 Next.js 集成方案,允许你把
useToolHook 放在 Server Component 里——这意味着工具调用可以在服务端执行,结果直接注入 HTML,避免了客户端 JS bundle 加载延迟导致的首屏空白。我们实测过:一个包含 3 个 tool call 的会议纪要流程,用 Client Component 实现,TTFB 平均 1.2s;用 Server Component + Streaming,TTFB 降到 380ms,且无需等待 JS 下载解析。
实操心得:不要试图用 React Native 做 Paperclip 的移动端。Paperclip 的核心交互依赖浏览器环境的
fetch、EventSource(SSE)、localStorage(临时 memory cache),而 React Native 的网络栈和存储层与 Web 有本质差异。我们曾尝试用 Expo 构建 iOS 版,结果卡在useTool的 CORS 配置上长达 3 天——最终结论是:Paperclip 的移动端方案,应该是 PWA(渐进式 Web App),而非原生容器。
3. 核心细节解析与实操要点:从零搭建一个可运行的 Paperclip 项目
3.1 环境准备:Node.js 与 OpenCLAW 的版本锁定策略
Paperclip 对 Node.js 版本有明确要求:必须使用 Node.js 18.20.4 LTS 或更高版本,但严禁使用 Node.js 22.x。这不是技术限制,而是生态兼容性决策。Node.js 22 引入了--experimental-shadow-dom和fetch的全局 polyfill,导致 OpenCLAW 的某些依赖(如node-fetch@2.x)出现AbortController is not defined错误。我们实测过 22.12+ 的 17 个 patch 版本,全部存在此问题,官方 issue 已标记为wontfix。
安装步骤必须严格按以下顺序执行:
# 1. 清理旧版本(尤其注意 nvm 的 default alias) nvm uninstall 22 nvm install 18.20.4 nvm alias default 18.20.4 # 2. 验证安装(注意输出必须含 "v18.20.4") node -v # v18.20.4 npm -v # 9.9.2(npm 9.x 是 18.x 的最佳匹配) # 3. 安装 OpenCLAW(必须指定 tag,master 分支不稳定) git clone https://github.com/openclaw/openclaw.git cd openclaw git checkout v0.8.3 # 这是 Paperclip 官方文档指定的兼容版本 npm ci # 严禁用 npm install,ci 保证 lockfile 一致性 # 4. 构建 OpenCLAW(生成 dist 目录,Paperclip 会引用) npm run build提示:CentOS 7.9 用户需额外安装
gcc-c++和python3-devel,否则npm ci会因 node-gyp 编译失败。命令为:sudo yum groupinstall "Development Tools" && sudo yum install python3-devel。Ubuntu 用户则需sudo apt-get install build-essential python3-dev。
OpenCLAW 的配置文件openclaw.config.ts必须启用enableMemory和enableTools,否则 Paperclip 的useMemoryHook 将始终返回空数组:
// openclaw.config.ts export const config = { memory: { enable: true, // 关键!Paperclip 依赖此开关 adapter: 'filesystem', // 开发用,生产建议 redis }, tools: { enable: true, // 关键!否则 useTool 报错 plugins: [ './plugins/web-search', './plugins/read-file', './plugins/write-file', ], }, };3.2 Paperclip 项目初始化:Vite + Express 的双包结构
Paperclip 官方推荐“双包结构”:前端(Vite)和后端(Express)分离为两个独立 package,通过pnpm link或npm link关联。这样做的好处是:前端可单独npm run dev热更新,后端可单独npm run dev:server调试,互不干扰。
初始化命令如下:
# 创建根目录 mkdir my-paperclip-app && cd my-paperclip-app pnpm init -y # 初始化 frontend(Vite + React) pnpm create vite@latest frontend -- --template react-ts cd frontend pnpm add @paperclip/react # Paperclip 官方 React 绑定库 pnpm add -D @types/node # Vite 需要 cd .. # 初始化 backend(Express) pnpm create express@latest backend cd backend pnpm add @paperclip/core openclaw # Paperclip 核心 + OpenCLAW pnpm add -D typescript @types/express @types/node cd ..关键配置文件backend/src/server.ts的最小可行代码:
import express from 'express'; import { createPaperclipServer } from '@paperclip/core'; import { config } from 'openclaw'; // 注意路径需指向你 clone 的 openclaw 目录 const app = express(); app.use(express.json()); // 创建 Paperclip Server 实例,传入 OpenCLAW 配置 const paperclip = createPaperclipServer({ openclawConfig: config, port: 3001, // 与前端 proxy 端口一致 }); app.use('/api', paperclip.router); app.listen(3001, () => { console.log('Paperclip server running on http://localhost:3001'); });前端frontend/vite.config.ts的 proxy 配置必须精确匹配:
export default defineConfig({ server: { proxy: { '/api': { target: 'http://localhost:3001', // 后端端口 changeOrigin: true, rewrite: (path) => path.replace(/^\/api/, ''), // 去掉 /api 前缀 } } } });注意:
rewrite规则极易出错。如果漏掉这行,前端请求/api/tool-call会被转发到后端/api/tool-call,而后端路由是/api/tool-call,导致 404。Paperclip 的路由前缀是/api,所以必须重写为''。
3.3 第一个 Agent 组件:用 React 实现“文件摘要生成器”
我们以一个真实场景为例:用户上传一个.md文件,Agent 自动读取内容、调用 LLM 生成摘要、再将摘要显示在页面上。
前端组件frontend/src/App.tsx的完整代码:
import { useState, useRef, useEffect } from 'react'; import { useTool, useAgentStatus, useMemory } from '@paperclip/react'; function App() { const [file, setFile] = useState<File | null>(null); const [summary, setSummary] = useState<string>(''); const [status, setStatus] = useState<string>(''); const fileInputRef = useRef<HTMLInputElement>(null); // 监听 Agent 状态变化 const agentStatus = useAgentStatus(); useEffect(() => { if (agentStatus.status === 'running') { setStatus(`正在执行: ${agentStatus.currentStep}`); } else if (agentStatus.status === 'completed') { setStatus('执行完成'); } else if (agentStatus.status === 'error') { setStatus(`错误: ${agentStatus.error?.message || '未知'}`); } }, [agentStatus]); // 读取文件并生成摘要 const handleGenerate = async () => { if (!file) return; try { // Step 1: 上传文件到服务端(Paperclip 不处理文件上传,需自行实现) const formData = new FormData(); formData.append('file', file); const uploadRes = await fetch('/api/upload', { method: 'POST', body: formData, }); const { filePath } = await uploadRes.json(); // Step 2: 调用 read_file 工具 const content = await useTool('read_file', { path: filePath }); // Step 3: 调用 llm_summarize 工具(需提前在 OpenCLAW 中注册) const result = await useTool('llm_summarize', { text: content, max_length: 200, }); setSummary(result.summary); } catch (err) { console.error(err); setStatus('执行失败'); } }; return ( <div className="p-4 max-w-2xl mx-auto"> <h1 className="text-2xl font-bold mb-4">文件摘要生成器</h1> <p className="text-gray-600 mb-4">{status}</p> <div className="mb-4"> <input type="file" ref={fileInputRef} onChange={(e) => setFile(e.target.files?.[0] || null)} className="hidden" /> <button onClick={() => fileInputRef.current?.click()} className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600" > {file ? `已选择: ${file.name}` : '选择文件'} </button> </div> <button onClick={handleGenerate} disabled={!file || agentStatus.status === 'running'} className={`px-4 py-2 rounded ${ !file || agentStatus.status === 'running' ? 'bg-gray-400 cursor-not-allowed' : 'bg-green-500 hover:bg-green-600 text-white' }`} > {agentStatus.status === 'running' ? '生成中...' : '生成摘要'} </button> {summary && ( <div className="mt-6 p-4 bg-gray-100 rounded"> <h2 className="font-semibold mb-2">摘要结果:</h2> <pre className="whitespace-pre-wrap">{summary}</pre> </div> )} </div> ); } export default App;实操心得:
useTool的参数必须是 plain object,不能传File、Blob或Date对象,否则序列化会失败。所以文件上传必须走独立接口(如/api/upload),由后端保存到磁盘并返回路径,再把这个路径传给read_file工具。我们曾踩坑:直接useTool('read_file', { file: event.target.files[0] }),结果 OpenCLAW 收到的是[object File]字符串,报错ENOENT。
3.4 OpenCLAW 插件开发:手写一个llm_summarize工具
Paperclip 的威力,最终体现在你能多快写出一个可用的工具插件。下面是一个完整的llm_summarize.ts示例,它调用本地 Ollama 的llama3模型:
// openclaw/plugins/llm-summarize.ts import { ToolPlugin } from 'openclaw'; export const llmSummarize: ToolPlugin = { name: 'llm_summarize', description: '使用 LLM 对文本进行摘要,支持自定义最大长度', parameters: { text: { type: 'string', required: true }, max_length: { type: 'number', required: false, default: 100 }, }, async execute({ text, max_length = 100 }) { // 1. 构造 Ollama API 请求 const response = await fetch('http://localhost:11434/api/chat', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ model: 'llama3', messages: [ { role: 'user', content: `请用中文对以下内容做简洁摘要,不超过 ${max_length} 字:\n\n${text}`, }, ], stream: false, }), }); if (!response.ok) { throw new Error(`Ollama API error: ${response.status} ${response.statusText}`); } const data = await response.json(); const summary = data.message.content.trim(); // 2. 返回结构化结果,Paperclip 会自动注入到 UI state return { summary, word_count: summary.length, model_used: 'llama3', }; }, }; export default llmSummarize;然后在openclaw.config.ts的plugins数组中添加路径:
tools: { enable: true, plugins: [ './plugins/web-search', './plugins/read-file', './plugins/llm-summarize', // 新增这一行 ], },注意:Ollama 必须提前运行(
ollama serve),且llama3模型已下载(ollama pull llama3)。如果 Ollama 未启动,useTool('llm_summarize')会抛出网络错误,Paperclip 的useAgentStatus会自动捕获并显示在 UI 上,无需额外 try/catch。
4. 实操过程与核心环节实现:本地一键部署与生产环境适配
4.1 本地一键部署脚本:3 行命令启动完整环境
Paperclip 官方提供了paperclip-cli,但实测发现其对 Windows 支持不佳,且无法自定义 OpenCLAW 路径。我们团队编写了一个跨平台的deploy.sh(macOS/Linux)和deploy.ps1(Windows),核心逻辑是:
- 检查 Node.js 版本是否为 18.20.4;
- 启动 OpenCLAW(
npm start); - 并行启动 Paperclip 后端(
cd backend && npm run dev); - 并行启动前端(
cd frontend && npm run dev)。
deploy.sh关键片段:
#!/bin/bash # 检查 Node.js 版本 NODE_VERSION=$(node -v | sed 's/v//') if [[ "$NODE_VERSION" != "18.20.4" ]]; then echo "Error: Node.js 18.20.4 required, current: $NODE_VERSION" exit 1 fi # 启动 OpenCLAW(后台运行,日志输出到 openclaw.log) cd ../openclaw && npm start > openclaw.log 2>&1 & # 启动 Paperclip 后端 cd ../backend && npm run dev > backend.log 2>&1 & # 启动前端 cd ../frontend && npm run dev > frontend.log 2>&1 & echo "Paperclip started! Visit http://localhost:5173" echo "Logs: openclaw.log, backend.log, frontend.log"Windows 用户用 PowerShell:
# deploy.ps1 $nodeVersion = node -v if ($nodeVersion -ne "v18.20.4") { Write-Error "Node.js 18.20.4 required, current: $nodeVersion" exit 1 } Start-Process npm -ArgumentList "start" -WorkingDirectory "..\openclaw" -WindowStyle Hidden Start-Process npm -ArgumentList "run dev" -WorkingDirectory "..\backend" -WindowStyle Hidden Start-Process npm -ArgumentList "run dev" -WorkingDirectory "..\frontend" -WindowStyle Hidden Write-Host "Paperclip started! Visit http://localhost:5173"提示:
npm start在 OpenCLAW 中默认监听http://localhost:3000,而 Paperclip 后端监听3001,前端5173,三者端口不冲突。如果端口被占用,修改openclaw/package.json的startscript 即可。
4.2 生产环境部署:Nginx 反向代理与 PM2 进程管理
生产环境不能用npm run dev,必须用 PM2 管理进程,并用 Nginx 做反向代理和静态资源服务。
Nginx 配置/etc/nginx/sites-available/paperclip:
upstream paperclip_backend { server 127.0.0.1:3001; } server { listen 80; server_name your-domain.com; # 前端静态资源 location / { root /var/www/paperclip-frontend; try_files $uri /index.html; } # API 接口代理 location /api/ { proxy_pass http://paperclip_backend/; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; 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; } # OpenCLAW 的 /health 等管理接口(可选) location /openclaw/ { proxy_pass http://127.0.0.1:3000/; } }PM2 配置ecosystem.config.js:
module.exports = { apps: [ { name: 'paperclip-backend', script: './backend/dist/server.js', instances: 1, autorestart: true, watch: false, env: { NODE_ENV: 'production', }, }, { name: 'openclaw', script: 'npm', args: 'start', cwd: '/opt/openclaw', instances: 1, autorestart: true, watch: false, env: { NODE_ENV: 'production', }, }, ], };部署命令:
# 构建前端生产包 cd frontend && npm run build && cp -r dist/* /var/www/paperclip-frontend/ # 构建后端 cd backend && npm run build # 启动 PM2 pm2 start ecosystem.config.js # 保存进程列表 pm2 save # 启用 Nginx sudo ln -sf /etc/nginx/sites-available/paperclip /etc/nginx/sites-enabled/ sudo nginx -t && sudo systemctl reload nginx注意:OpenCLAW 的
filesystemmemory adapter 在生产环境必须换成 Redis,否则多实例部署时 memory 数据不同步。修改openclaw.config.ts:memory: { enable: true, adapter: 'redis', options: { host: '127.0.0.1', port: 6379, db: 0, }, },
4.3 Paperclip 与 Microsoft Teams 的集成:Webhook 模式接入
Paperclip 本身不提供 Teams 集成,但它的/api/tool-call接口是标准 RESTful,可被 Teams 的 Bot Framework 调用。我们团队实现了 Teams Tab + Bot 的混合方案:
Teams Tab:一个 iframe,src 指向
https://your-domain.com/teams-tab,这个页面是 Paperclip 前端的定制版,UI 适配 Teams 的深色主题,并禁用文件上传(Teams 提供自己的文件选择器)。Teams Bot:当用户在聊天中发送
@Paperclip summarize this document,Bot 解析指令,调用 Paperclip 后端的/api/tool-call:POST /api/tool-call { "tool": "llm_summarize", "parameters": { "text": "用户粘贴的长文本...", "max_length": 150 } }Paperclip 返回摘要后,Bot 再把结果发回聊天窗口。
关键点在于身份验证:Teams Bot 的请求必须携带Authorization: Bearer <teams-token>,而 Paperclip 后端需验证此 token 是否来自可信 Teams tenant。我们在backend/src/middleware/auth.ts中添加:
import { verify } from 'jsonwebtoken'; export const teamsAuth = (req: Request, res: Response, next: NextFunction) => { const authHeader = req.headers.authorization; if (!authHeader || !authHeader.startsWith('Bearer ')) { return res.status(401).json({ error: 'Unauthorized' }); } const token = authHeader.split(' ')[1]; try { const decoded = verify(token, process.env.TEAMS_JWT_SECRET!, { issuer: 'https://api.botframework.com', audience: process.env.TEAMS_BOT_ID!, }); (req as any).teamsUser = decoded; next(); } catch (err) { res.status(401).json({ error: 'Invalid token' }); } };然后在server.ts中挂载:
app.post('/api/tool-call', teamsAuth, paperclip.toolCallHandler);实操心得:Teams 的 JWT token 有效期仅 1 小时,且每次聊天会话的 token 不同。不要尝试缓存 token,每次请求都必须验证。我们曾因缓存 token 导致 Bot 在 1 小时后静默失效,排查了整整一天。
5. 常见问题与排查技巧实录:那些官网不会写的坑
5.1 典型问题速查表
| 问题现象 | 可能原因 | 排查命令 | 解决方案 |
|---|---|---|---|
useTool调用后 UI 无响应,Network 面板看不到/api/tool-call请求 | Vite proxy 配置错误 | cat frontend/vite.config.ts | grep -A5 proxy | 检查rewrite是否缺失,确认target端口与后端一致 |
OpenCLAW 启动报错Error: Cannot find module 'openclaw' | openclaw未正确 link 或路径错误 | ls -la ../openclaw/dist | 在backend/package.json中添加"openclaw": "link:../openclaw",再npm install |
useMemory返回空数组 | OpenCLAW 的enableMemory为 false | grep -r "enableMemory" ../openclaw/config | 修改openclaw.config.ts,确保memory.enable: true |
| Teams Bot 调用返回 401,但 token 有效 | JWT secret 不匹配 | echo $TEAMS_JWT_SECRET | 在 PM2 env 中设置TEAMS_JWT_SECRET,而非.env文件(PM2 不读 .env) |
| 本地部署后,SSE 流式响应卡住,UI 显示“正在执行”但无进展 | OpenCLAW 插件中fetch未加signal | grep -r "fetch.*{.*}" ../openclaw/plugins/ | 所有fetch必须传signal: AbortSignal.timeout(30000),防止无限 hang |
5.2 独家避坑技巧:从 37 次失败中总结的经验
技巧 1:OpenCLAW 插件的
execute函数必须async,且返回 Promise
我们曾写过一个同步函数return { result: 'ok' },结果 Paperclip 的useTool一直 pending。原因是 Paperclip 的协调层用await plugin.execute(),如果返回非 Promise,await会立即 resolve,但 Paperclip 的状态机还没准备好接收结果。永远用async function execute() { ... }。技巧 2:前端
useTool的参数 key 名必须与 OpenCLAW 插件parameters定义完全一致
插件定义text: { type: 'string' },调用时必须传{ text: 'hello' },传{ content: 'hello' }会静默失败(OpenCLAW 默认忽略未知参数)。Paperclip 不做参数校验,错误只在 OpenCLAW 日志里体现,很难发现。技巧 3:Paperclip 的
useAgentStatusHook 依赖useEffect的 cleanup,务必在组件卸载时取消订阅
否则路由切换后,旧组件的 status listener 仍在运行,导致内存泄漏。Vite + React 18 的 Strict Mode 会自动 double mount,所以必须在useEffect里返回 cleanup 函数:useEffect(() => { const unsubscribe = paperclip.subscribeToStatus((status) => { setStatus(status); }); return () => unsubscribe(); // 关键! }, []);技巧 4:生产环境必须关闭 OpenCLAW 的
debug模式
OpenCLAW 的config.debug = true会在每个 tool call 后打印 200 行 JSON 日志,导致 PM2 日志文件每小时增长 500MB。线上务必设为false,调试时再开启。技巧 5:不要在 Paperclip 项目里用
create-react-app
CRA 的 webpack 配置与 Paperclip 的@paperclip/react有兼容性问题,会导致useToolHook 的fetch被拦截。Vite 是唯一经过充分测试的 bundler。
5.3 性能调优实录:如何让 Paperclip 在 100 并发下保持稳定
我们做过压力测试:用 Artillery 模拟 100 用户同时上传文件并生成摘要。初始版本在 40 并发时就开始超时(>30s)。优化步骤如下:
- OpenCLAW 层:启用插件级缓存
在llm_summarize.ts中加入内存缓存: