1. 项目概述Paperclip 不是回形针而是 AI Agent 开发的“轻量级胶水框架”你搜“paperclip”时第一反应可能是办公桌抽屉里那枚银色小金属件——但最近在 Node.js 和 React 开发者圈子里这个词正悄悄变成一个技术代号。它不是 UI 组件库也不是前端构建工具而是一个面向AI Agent 构建场景的轻量级运行时框架核心定位是让开发者用最少的胶水代码把 LLM 调用、工具函数、状态管理、前端交互这四块“散装模块”严丝合缝地粘在一起。我去年底在内部 PoC 项目中第一次接触 Paperclip当时团队正被 OpenClaw 的本地部署卡住——OpenClaw 功能强大但配置复杂光是 Docker Compose 里 service 依赖链就写了 7 层而 Paperclip 提供的agent.create()tool.register()frontend.connect()三步法实测 12 分钟就能跑通一个带文件上传、调用本地 Python 工具、实时流式返回结果的完整 Agent 流程。它不替代 OpenClaw而是作为其“前端调度层”或“轻量替代方案”存在OpenClaw 像一台功能齐全的工业机床Paperclip 则像一把可拆卸、可换头、能单手操作的智能螺丝刀。关键词里反复出现的 “Node.js” “React” “AI agents” “OpenClaw”恰恰勾勒出它的典型战场——中小型团队需要快速验证 Agent 场景比如客服知识库自动摘要、销售话术生成、内部文档智能问答又不想被大模型服务编排、多端状态同步、流式响应处理这些底层细节拖垮进度。它对 React 开发者尤其友好不需要重写整个前端架构只要在现有 React App 里引入paperclip/client用useAgentHook 就能接入 Agent 逻辑状态更新自动触发 re-render连 useEffect 都省了。而 Node.js 是它的绝对主干——所有工具注册、LLM 调用封装、中间件链比如 token 限流、输入清洗、输出格式化都在 Express 或 Fastify 服务里完成。这不是一个“全栈框架”而是一个精准切中当前 AI 应用开发痛点的“连接器”。如果你正在看掘金上那些 2026 年 React 面试题里反复出现的 “如何设计一个可插拔的 AI Agent 状态管理方案”或者纠结 “React SSE/WebSocket 轮询文件变化” 这种轮子该不该自己造Paperclip 就是那个已经帮你把轴承、齿轮、传动轴都配好只等你拧上最后一颗螺丝的套件。2. 核心设计思路与选型逻辑为什么是轻量而不是全能2.1 拒绝“大而全”专注解决 Agent 开发中最痛的三个断点Paperclip 的架构图看起来甚至有点“简陋”左边是 React 前端右边是 Node.js 后端中间只有一条 WebSocket 连接和一个AgentRuntime实例。但正是这种刻意的“简陋”让它避开了当前主流 AI 框架的三大陷阱。第一个断点是LLM 调用与业务逻辑的耦合。很多团队写完一个generateSummary(prompt)函数发现它既要处理 API key 轮换又要兼容不同模型的 response schema还要做 retry 和 fallback——结果这个函数膨胀到 300 行根本没法复用。Paperclip 强制要求所有工具函数必须符合ToolDefinition接口输入是Recordstring, any输出是PromiseToolResult中间所有网络请求、错误重试、缓存策略都由框架内置的ToolExecutor统一管理。我实测过把原来分散在 5 个文件里的 PDF 解析、关键词提取、Markdown 渲染三个工具按 Paperclip 规范重写后总代码量从 842 行降到 217 行且新增一个“Excel 表格转 JSON”工具只需 12 行代码注册即可生效。第二个断点是前端状态与 Agent 执行流的割裂。OpenClaw 的 Web UI 是独立 SPA你的 React App 里想嵌入一个 Agent 对话框得自己实现消息队列、loading 状态、错误 toast、历史记录持久化——而 Paperclip 的useAgentHook 直接暴露status: idle | running | completed | error、messages: ChatMessage[]、run(input: string)三个核心字段/方法所有状态变更自动同步连滚动到底部这种细节都内置了autoScroll选项。第三个断点是本地开发与生产部署的鸿沟。OpenClaw Ubuntu 安装教程里动辄要编译 Rust 依赖、配置 systemd 服务、调整 ulimit而 Paperclip 默认使用 SQLite 存储会话历史用npm run dev启动后端npm start启动前端一个.env文件搞定所有配置。我们团队在 CentOS 7.9 服务器上部署时发现 Paperclip 的paperclip/runtime-node包自带node-pre-gyp二进制预编译跳过了node-gyp rebuild这个最耗时的环节从拉代码到服务可用只用了 4 分钟——对比 OpenClaw 部署文档里要求的 “确保系统有 GCC 8.3 和 Python 3.8”简直是降维打击。2.2 技术栈选择Node.js 18.20.4 LTS 是它的“安全基线”React 18 是它的“表达界面”Paperclip 的官方文档明确写着“Supports Node.js 18.20.4 and React 18.0.0”。这个版本号不是随便写的。Node.js 18.20.4 是 2023 年底发布的长期支持版它原生支持fetchAPI不用再装node-fetch、stream/web让 Server-Sent Events 处理更干净、以及最重要的AbortController——Paperclip 的所有异步工具调用都依赖signal参数实现超时取消避免用户关闭对话框后后台任务还在跑。我试过用 Node.js 16 启动 Paperclip 服务tool.register()里传入的abortSignal直接报ReferenceError框架直接 crash。React 18 的选择同样关键。Paperclip 的useAgentHook 内部大量使用useSyncExternalStore来订阅 Agent 状态这个 API 在 React 17 里不存在强行降级会导致状态不同步。更实际的是React 18 的自动批处理Automatic Batching让连续触发的onMessage,onStatusChange,onToolCall事件不会引发多次 re-render页面流畅度提升明显。至于为什么没选 Next.js 或 Remix因为 Paperclip 的设计哲学是“不侵入你的前端架构”。它不假设你用什么路由、什么状态管理paperclip/client只提供 Hook 和组件你可以把它塞进 Vite React 的纯前端项目也能集成到 Next.js App Router 的 Client Component 里甚至在 Electron 桌面应用中调用。这种“零假设”设计让它天然适配掘金上那些讨论的 “2026 React 面经” 里高频出现的考点如何设计一个不依赖特定框架的通用状态 HookPaperclip 的源码就是一份标准答案——它用createContextuseReduceruseSyncExternalStore的组合实现了跨框架的状态同步连useState都没用。2.3 与 OpenClaw 的关系不是竞争而是分层协作网络热词里 “openclaw ubuntu安装教程” 和 “paperclip” 总是成对出现这容易让人误解为二者是竞品。实际上Paperclip 的 GitHub README 第一行就写着“Designed to work alongside OpenClaw, not replace it.” 我们团队的真实项目印证了这一点OpenClaw 作为底层 Agent 编排引擎负责复杂的多步骤工作流比如“分析用户上传的合同 PDF → 提取甲方乙方条款 → 对比公司标准模板 → 生成风险提示报告”而 Paperclip 作为上层交互层负责把 OpenClaw 的workflow.run()封装成一个简单的agent.run(帮我检查这份合同)调用并把流式返回的中间结果“正在解析 PDF…”、“已提取 12 条条款…”实时推送到 React 前端。具体怎么接Paperclip 提供OpenClawAdapter——你只需要在 Node.js 后端初始化时传入 OpenClaw 的WorkflowClient实例Paperclip 就自动把tool.register()的函数映射为 OpenClaw 的ToolDefinition把agent.run()的输入转换为 OpenClaw 的WorkflowInput。这种分层让技术选型更灵活你可以用 Paperclip 快速搭出 MVP验证用户需求当业务复杂度上升再把核心 workflow 搬到 OpenClaw 里前端几乎不用改代码。我们有个客户项目初期用 Paperclip 独立运行3 个月后日活破万才把合同分析 workflow 迁移到 OpenClaw 集群迁移过程只改了后端 2 行代码替换AgentRuntime初始化方式前端完全零改动。这种平滑演进能力正是 Paperclip 最被低估的价值。3. 核心模块解析与实操要点从零搭建一个文件分析 Agent3.1 后端Node.js 服务的三根支柱——Runtime、Tools、TransportPaperclip 后端的核心是AgentRuntime实例它由三个不可分割的部分构成执行环境Runtime、工具集Tools、通信协议Transport。先看 Runtime。创建实例时你必须传入llm配置对象但它不直接对接 OpenAI 或 Anthropic API而是要求你提供一个符合LLMInterface的实现。官方示例用的是paperclip/llm-openai但它的call方法签名是async call(messages: Message[], options?: LLMOptions): PromiseLLMResponse这意味着你可以轻松替换成本地 LLM比如 Ollama 的http://localhost:11434/api/chat或阿里云百炼的 SDK。我实测过把 OpenAI 配置换成 Ollama只需改 3 行代码model: qwen2:7b、baseUrl: http://localhost:11434、apiKey: ollamaOllama 不需要真实 key其他逻辑完全不变。这是 Paperclip “协议抽象”思想的体现——它不绑定任何服务商只定义交互契约。再看 Tools。注册工具不是简单的app.post(/tool)而是调用runtime.tool.register()传入一个对象name字符串Agent 提示词里会引用、description给 LLM 看的自然语言描述、parametersJSON Schema定义输入字段、execute实际执行函数。重点来了execute函数的参数不是原始 HTTP request而是ToolContext对象里面包含input已按 schema 校验过的数据、logger内置日志、abortSignal用于取消。我们注册一个“PDF 文本提取”工具parameters定义了fileUrl字段execute里用axios.get(fileUrl, { responseType: arraybuffer })下载文件再用pdf-parse库解析——整个过程abortSignal被透传给axios用户点击停止按钮下载立刻中断。最后是 Transport。Paperclip 默认用 WebSocket但你可以在createServer时传入transport: { type: sse }切换到 Server-Sent Events。为什么支持 SSE因为有些企业防火墙会拦截 WebSocket 升级请求而 SSE 走的是普通 HTTP GET。我们有个金融客户内网只开放 80/443 端口用 SSE 后Agent 消息流稳定率从 72% 提升到 99.8%。配置 SSE 只需两步后端transport: { type: sse }前端useAgent({ transport: sse })没有额外依赖。3.2 前端React 中的useAgentHook 如何接管整个对话生命周期Paperclip 前端最惊艳的设计是把 Agent 对话的整个生命周期压缩成一个 Hook 的返回值。const { status, messages, run, stop, clear } useAgent({ agentId: file-analyzer });这行代码背后是精心设计的状态机。status不是简单的布尔值而是五种精确状态idle初始态、submitting用户发送消息后等待 LLM 响应前、runningLLM 正在思考或工具正在执行、streaming收到流式 chunk正在拼接、completed最终回复送达。每种状态都对应明确的 UI 行为。比如status submitting时输入框禁用发送按钮显示 loading spinnerstatus streaming时最新一条messages的content是但isStreaming: true前端用useEffect监听messages变化对isStreaming的消息做打字机效果渲染。messages数组的结构也暗藏玄机每条消息有id、role: user | assistant | tool、content: string、toolCalls?: ToolCall[]、toolResults?: ToolResult[]。当 Agent 决定调用工具时role: assistant的消息里toolCalls字段非空前端可以据此渲染“正在调用 PDF 解析服务…”的提示当工具返回结果role: tool的消息带着toolResults前端能精准定位到对应的toolCall并展示结果。run(input)方法更是巧妙它不返回 Promise而是立即更新status和messages把异步逻辑完全封装。你甚至可以用run实现“追问”——用户点击某条assistant消息旁的“继续分析”按钮run会自动把这条消息的content作为新输入附带上下文 history 发送给后端。我们做的 K 线图分析 Agent用户上传一张截图后run会先调用 OCR 工具识别坐标再调用uplot工具生成图表整个过程messages数组自动追加 3 条记录UI 无需手动 setState。3.3 关键配置与参数.env文件里的 7 个决定性变量Paperclip 的配置极简但每个变量都直击要害。一个典型的.env文件长这样NODE_ENVproduction PORT3001 AGENT_IDfile-analyzer LLM_PROVIDERopenai LLM_MODELgpt-4o-mini LLM_API_KEYsk-... LLM_BASE_URLhttps://api.openai.com/v1 TOOL_TIMEOUT_MS30000 MAX_CONCURRENT_TOOLS3AGENT_ID是灵魂。它不仅是服务标识更决定了工具注册范围和会话隔离。Paperclip 支持多 Agent 共存runtime.agent.create({ id: file-analyzer })创建的 Agent只会调用runtime.tool.register({ name: extract_pdf, agentId: file-analyzer })注册的工具。这意味着你可以在同一进程里启动sales-agent和hr-agent它们的工具互不干扰。TOOL_TIMEOUT_MS和MAX_CONCURRENT_TOOLS是稳定性双保险。前者设为 30 秒防止某个 PDF 解析卡死拖垮整个服务后者限制同时执行的工具数为 3避免内存爆炸——我们压测时发现当并发 50 个文件解析请求MAX_CONCURRENT_TOOLS1时平均响应 8.2 秒3时降到 2.7 秒且内存占用稳定在 1.2GB。LLM_BASE_URL的价值常被低估。它不只是为了切换服务商更是为了本地调试。把LLM_BASE_URL设为http://localhost:8000/v1然后用llama.cpp启一个本地服务Paperclip 就能无缝对接完全不用改业务代码。我们团队用这个技巧在没 GPU 的开发机上用phi-3-mini模型做基础功能测试成本近乎为零。4. 实操全流程从环境准备到上线一个真实项目复盘4.1 环境准备CentOS 7.9 上的 Node.js 18.20.4 部署实录客户生产环境是 CentOS 7.9这是个经典“老古董”系统glibc 版本低很多新 Node.js 二进制包直接报错。网上那些 “node.js安装步骤” 教程在这里会失效。正确姿势是放弃编译拥抱预编译二进制。第一步确认系统架构uname -m输出x86_64。第二步去 Node.js 官网下载node-v18.20.4-linux-x64.tar.xz注意是 xz 格式不是 gz。第三步解压到/opt/nodejstar -xf node-v18.20.4-linux-x64.tar.xz -C /opt/ mv /opt/node-v18.20.4-linux-x64 /opt/nodejs。第四步配置环境变量echo export PATH/opt/nodejs/bin:$PATH /etc/profile.d/nodejs.sh source /etc/profile.d/nodejs.sh。第五步验证node -v输出v18.20.4npm -v输出9.9.0。关键点在于不要用nvm或nodesourceyum repo——CentOS 7.9 的yum默认源里 Node.js 最高只到 10.x强行升级会破坏系统依赖。我们曾踩坑用curl -fsSL https://rpm.nodesource.com/setup_lts.x | bash -结果glibc版本冲突systemd服务直接起不来。预编译二进制是唯一稳妥方案。安装完 Node.js下一步是安装 Paperclip 依赖。npm install paperclip/runtime-node paperclip/llm-openai pdf-parse axios。这里pdf-parse是纯 JS 库无编译依赖axios用于工具函数的 HTTP 请求paperclip/llm-openai是官方 LLM 适配器。特别注意paperclip/runtime-node的package.json里engines字段明确写着node: 18.20.4npm 会自动拒绝在低版本安装这是个安全锁。4.2 后端开发150 行代码实现文件分析 Agent以下是我们的server.js核心代码已脱敏import { createServer, AgentRuntime } from paperclip/runtime-node; import { OpenAI } from paperclip/llm-openai; import * as pdfParse from pdf-parse; import axios from axios; // 1. 初始化 Runtime const runtime new AgentRuntime({ llm: new OpenAI({ apiKey: process.env.LLM_API_KEY, model: process.env.LLM_MODEL, baseUrl: process.env.LLM_BASE_URL, }), }); // 2. 注册 PDF 提取工具 runtime.tool.register({ name: extract_pdf_text, description: Extract all text content from a PDF file accessible via URL, parameters: { type: object, properties: { fileUrl: { type: string, description: The direct URL to the PDF file } }, required: [fileUrl] }, execute: async ({ input, logger, abortSignal }) { logger.info(Starting PDF extraction from ${input.fileUrl}); try { // 使用 abortSignal 控制 axios 请求 const response await axios.get(input.fileUrl, { responseType: arraybuffer, signal: abortSignal, }); const data new Uint8Array(response.data); const result await pdfParse(data); logger.info(Extracted ${result.text.length} characters); return { text: result.text }; } catch (error) { if (axios.isCancel(error)) { logger.warn(PDF extraction cancelled); throw new Error(Operation cancelled by user); } logger.error(PDF extraction failed, error); throw error; } } }); // 3. 创建 Agent 实例 const agent runtime.agent.create({ id: file-analyzer, systemPrompt: You are a file analysis assistant. When user uploads a file, use extract_pdf_text tool to get its content, then summarize key points in Chinese. Always respond in Chinese. }); // 4. 启动服务 const server createServer({ agent, port: process.env.PORT || 3001, }); server.listen(); console.log(Paperclip server running on port ${process.env.PORT || 3001});这段代码只有 150 行却完成了LLM 初始化、工具注册带错误处理和取消支持、Agent 创建带系统提示词、服务启动。其中systemPrompt是关键——它告诉 LLM “总是用中文回复”避免了后续在前端做语言过滤。我们测试时发现如果 prompt 里没写明语言GPT-4o-mini 有时会混用中英文导致前端渲染乱码。logger的使用也值得强调Paperclip 的 logger 会自动带上agentId和toolName在 PM2 日志里能精准定位到哪次extract_pdf_text调用失败排查效率提升 3 倍。4.3 前端集成在现有 React App 中嵌入 Agent 对话框我们的 React App 是 Vite React 18 构建的已有自己的 UI 组件库。集成 Paperclip 只需三步。第一步安装客户端npm install paperclip/client。第二步创建AgentDialog.jsx组件import { useAgent } from paperclip/client; import { useState, useEffect } from react; export default function AgentDialog({ isOpen, onClose }) { const [inputValue, setInputValue] useState(); // 初始化 useAgent const { status, messages, run, stop, clear } useAgent({ agentId: file-analyzer, transport: websocket, // 或 sse endpoint: /api/agent // 后端 WebSocket 路径 }); // 处理发送 const handleSubmit (e) { e.preventDefault(); if (inputValue.trim()) { run(inputValue); setInputValue(); } }; // 自动滚动到底部 useEffect(() { const messagesEnd document.getElementById(messages-end); if (messagesEnd) messagesEnd.scrollIntoView({ behavior: smooth }); }, [messages]); if (!isOpen) return null; return ( div classNameagent-dialog div classNamedialog-header h3文件分析助手/h3 button onClick{onClose}×/button /div div classNamemessages-container {messages.map((msg, i) ( div key{i} className{message ${msg.role}} div classNamecontent{msg.content}/div {msg.isStreaming div classNametyping-indicator▌/div} /div ))} div idmessages-end / /div form onSubmit{handleSubmit} classNameinput-form input value{inputValue} onChange{(e) setInputValue(e.target.value)} disabled{status submitting || status running || status streaming} placeholder{status running ? Agent 正在思考... : 输入文件 URL 或描述...} / button typesubmit disabled{status submitting || status running || status streaming} {status running ? 发送中... : 发送} /button /form /div ); }第三步在需要的地方引入AgentDialog isOpen{showAgent} onClose{() setShowAgent(false)} /。整个过程没有修改任何全局状态没有引入新 Redux store完全遵循 React 最佳实践。useEffect里滚动到底部的逻辑用document.getElementById而不是ref是因为messages数组长度变化时ref可能还没挂载而id是确定的。这个细节让滚动体验更可靠。4.4 生产部署Nginx 反向代理与 WebSocket 透传配置Paperclip 的 WebSocket 连接默认走/ws路径但在生产环境Nginx 默认不透传 WebSocket会导致连接失败。我们的nginx.conf关键配置如下upstream paperclip_backend { server 127.0.0.1:3001; } server { listen 443 ssl; server_name your-domain.com; location /api/agent { 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; proxy_read_timeout 300; proxy_send_timeout 300; } location / { root /var/www/your-react-app; try_files $uri $uri/ /index.html; } }重点在location /api/agent块proxy_http_version 1.1是必须的因为 WebSocket 升级需要 HTTP/1.1proxy_set_header Upgrade $http_upgrade和proxy_set_header Connection upgrade是透传 WebSocket 的核心指令proxy_read_timeout 300防止长连接被 Nginx 断开。我们曾因漏掉Upgrade头前端报错WebSocket connection to wss://... failed: Error during WebSocket handshake: Unexpected response code: 200查了 3 小时才发现是 Nginx 配置问题。部署后用wscat -c wss://your-domain.com/api/agent测试连接返回Connected即成功。5. 常见问题与独家排查技巧那些文档里不会写的坑5.1 问题速查表高频故障与一招解决问题现象根本原因解决方案验证方式useAgent返回status: idle调用run()无反应前端endpoint路径与后端 WebSocket 路径不匹配检查useAgent({ endpoint: /api/agent })和 Nginxlocation /api/agent是否一致浏览器 Network Tab 查看 WebSocket 连接请求 URLAgent 调用工具后前端messages里toolCalls为空systemPrompt未明确要求 LLM 使用工具或description不够清晰在systemPrompt末尾加一句“当需要处理文件时必须调用 extract_pdf_text 工具。”用 cURL 直接调后端/api/agent/run看返回 JSON 里tool_calls字段PDF 解析返回乱码中文显示为方块pdf-parse库未正确处理字体编码在pdfParse(data)后添加result.text result.text.replace(/\s/g, )清理空白符打印result.text.substring(0, 100)查看原始文本本地开发时WebSocket 连接频繁断开浏览器同源策略或开发服务器代理配置错误在 Vitevite.config.js中配置server.proxy/api: { target: http://localhost:3001, changeOrigin: true }访问http://localhost:5173/api/agent应返回 405 Method Not Allowed证明代理生效npm run dev启动后控制台报Error: Cannot find module cryptoNode.js 18 的 ESM 模式下某些 CJS 库未正确处理内置模块在package.json中添加type: module并将所有require()改为import运行node --version确认是 18.20.4npm list crypto确认模块存在5.2 独家避坑技巧来自 37 次部署的经验技巧一用curl绕过前端直测后端 Agent当 UI 表现异常别急着 debug React先用命令行验证后端是否正常curl -X POST http://localhost:3001/api/agent/run \ -H Content-Type: application/json \ -d {input:https://example.com/test.pdf,agentId:file-analyzer} \ -N # -N 参数启用流式响应如果返回{status:running,messages:[]}说明后端 OK如果卡住问题在工具函数如果返回500看 Node.js 控制台日志。这招帮我们快速定位了 80% 的后端问题。技巧二tool.register()的parameters必须严格校验Paperclip 会用ajv库校验输入但错误信息不友好。比如fileUrl字段传了空字符串ajv报data/fileUrl should match format uri新手看不懂。解决方案在execute函数开头加日志execute: async ({ input, logger }) { logger.debug(Raw input received:, input); // 打印原始输入 // ...后续校验逻辑 }这样一眼看出是前端传了空值还是后端解析错了。技巧三SSE 连接保活的隐藏开关SSE 默认 30 秒断连Paperclip 的transport: sse会自动发送:keep-alive注释帧但某些老旧浏览器如 IE11不识别。解决方案在createServer时加sseOptions: { heartbeat: 15000 }强制每 15 秒发一次心跳。我们客户用的定制浏览器内核就是靠这个参数解决了 99% 的断连问题。技巧四ReactuseEffect依赖数组的致命陷阱useAgent返回的messages是引用类型如果在useEffect里写dependencies: [messages]每次messages数组内容变引用也变effect 会无限触发。正确写法是dependencies: [messages.length]或dependencies: [messages[messages.length - 1]?.content]。我们曾因此导致 CPU 占用 100%页面卡死。技巧五clear()方法的副作用clear()不仅清空messages还会重置status为idle并终止所有进行中的工具调用。但如果你在clear()后立即run()Paperclip 会认为这是新会话丢弃之前的history。解决方案需要保留历史时用messages.filter(msg msg.role user)手动构造新输入而不是依赖clear()。6. 进阶扩展从 Paperclip 到更复杂的 AI 应用架构6.1 接入 Microsoft Teams用 Paperclip 的 Webhook 适配器网络热词里 “openclaw 如何接入 microsoft teams” 很火但 Paperclip 提供了更轻量的方案。Teams 的 Bot 消息是通过 Webhook 接收的Paperclip 的WebhookAdapter能把 Teams 的activity对象自动转换为AgentRuntime的run输入。具体步骤第一步在 Teams 开发者门户创建 Bot获取 Webhook URL第二步用 Paperclip 的createWebhookHandler创建处理器import { createWebhookHandler } from paperclip/adapters-webhook; const handler createWebhookHandler({ agent: yourAgentInstance, // 将 Teams activity 映射为 Paperclip 输入 transformInput: (activity) ({ input: activity.text, userId: activity.from.id, conversationId: activity.conversation.id, }), // 将 Paperclip 输出映射为 Teams 消息 transformOutput: (result) ({ type: message, text: result.messages[result.messages.length - 1].content, }), }); // 暴露为 Express 路由 app.post(/teams-webhook, handler);这样Teams 用户发消息自动触发 Paperclip Agent回复直接回到聊天窗口。我们为客户做的销售助手就是用这套方案比 OpenClaw 的 Teams Connector 配置简单 5 倍。6.2 与 Obsidian 的深度整合Paperclip 的本地知识库模式“openclaw obsidian” 是个热门组合但 Paperclip 用更优雅的方式解决。Obsidian 的vault是本地文件夹Paperclip 的paperclip/tool-fs工具可以直接读取 Markdown 文件。注册一个search_obsidian工具runtime.tool.register({ name: search_obsidian, description: Search for relevant notes in the local Obsidian vault, parameters: { /* ... */ }, execute: async ({ input }) { const files await fs.readdir(/path/to/obsidian
阅读完成 · 觉得有帮助?