OpenClaw WebSocket Channel开发实战:从零打造自定义 AI 通信通道

OpenClaw WebSocket Channel开发实战:从零打造自定义 AI 通信通道

🎯 项目背景

为什么做这个项目?

最近 OpenClaw 特别火🔥,这是一个强大的个人 AI 助手网关,支持接入 WhatsApp、Telegram、Discord 等 15+ 个消息平台。作为一个技术爱好者,我决定深入学习一下它的架构设计。

学习目标

  • ✅ 理解多通道 AI 网关的架构模式
  • ✅ 掌握 OpenClaw 插件化开发技能
  • ✅ 实践 WebSocket 实时双向通信
  • ✅ 为社区贡献一个实用的教学案例

项目定位:这不是一个生产级项目,而是一个学习性质的教学案例,帮助其他开发者快速上手 OpenClaw 插件开发。

技术栈

前端层:Vue 3 + WebSocket ↓ 服务端:Python + aiohttp + uv ↓ 通道层:Node.js + ws + OpenClaw Plugin SDK ↓ AI 层:OpenClaw Gateway + LLM Provider 

🚀 快速开始

本项目 Gitee 仓库

项目结构

openclaw-websocket-channel/ ├── websocket-service/ # Python WebSocket 服务端 │ ├── app.py # aiohttp 主程序 │ └── requirements.txt # Python 依赖 ├── websocket-web/ # Vue 3 前端 │ ├── src/ │ │ └── App.vue # 主界面 │ └── package.json └── websocket-channel/ # OpenClaw 通道插件 ├── index.ts # 插件主逻辑 └── openclaw.plugin.json 

1. 启动 Python WebSocket 服务端

# 进入服务端目录cd websocket-service # 使用 uv 安装依赖 uv sync# 启动服务端 python app.py # 默认监听:ws://localhost:8765

2. 启动 Vue 前端

# 进入前端目录cd websocket-web # 安装依赖npminstall# 开发模式运行npm run dev # 访问:http://localhost:3000

前端界面功能

  • 💬 实时聊天窗口
  • 🔌 连接状态显示
  • ✉️ 消息收发日志

3. 安装 WebSocket Channel

# 进入通道插件目录cd websocket-channel # 安装到 OpenClaw openclaw plugins install.# 验证安装 openclaw plugins list # 应该看到:websocket-channel

4. 配置 OpenClaw

编辑 ~/.openclaw/config.json(或通过 Web UI):

{"channels":{"websocket-channel":{"enabled":true,"config":{"enabled":true,"wsUrl":"ws://localhost:8765/openclaw"}}}}

配置说明

  • enabled: 启用通道
  • wsUrl: WebSocket 服务端地址
  • 无需 groupPolicy:默认就是开放模式

5. 重启 OpenClaw Gateway

# 如果使用 macOS 应用# 点击菜单栏 OpenClaw → Restart Gateway# 或命令行重启pkill-f openclaw-gateway openclaw gateway run 

6. 测试

  1. 打开浏览器访问前端:http://localhost:3000
  2. 点击 “连接” 按钮
  3. 发送消息:“你好,请介绍一下自己”
  4. 等待 AI 回复…

预期效果

你:你好,请介绍一下自己 AI:你好!我是你的个人 AI 助手,基于 OpenClaw 框架运行。 我可以帮助你回答问题、编写代码、分析数据等。 有什么我可以帮你的吗?😊 

🏗️ 程序架构

整体架构图

┌─────────────────┐ │ 用户浏览器 │ │ (Vue 前端) │ └────────┬────────┘ │ WebSocket │ ws://localhost:8765 ▼ ┌─────────────────┐ │ Python 服务端 │ │ (aiohttp) │ └────────┬────────┘ │ WebSocket │ 长连接 ▼ ┌─────────────────┐ │ Node.js 通道 │ │ (ws 库) │ └────────┬────────┘ │ OpenClaw Plugin API ▼ ┌─────────────────┐ │ OpenClaw │ │ Gateway │ └────────┬────────┘ │ ▼ ┌─────────────────┐ │ AI Provider │ │ (Qwen/Bailian) │ └─────────────────┘ 

数据流详解

入站消息(前端 → AI)
1. 用户在 Vue 界面输入消息 ↓ 2. 前端通过 WebSocket 发送到 Python 服务端 ↓ 3. Python 服务端转发给 Node.js 通道 ↓ 4. Node.js 通道的 ws.on("message") 接收 ↓ 5. 标准化消息格式 ↓ 6. 调用 OpenClaw 框架 API ↓ 7. Gateway 调用 AI Provider 生成回复 
出站消息(AI → 前端)
1. AI 生成回复文本 ↓ 2. OpenClaw 调用 deliver 回调 ↓ 3. Node.js 通道通过 WebSocket 发送给 Python 服务端 ↓ 4. Python 服务端转发给 Vue 前端 ↓ 5. 前端界面显示 AI 回复 

💻 Channel开发详解

1. 项目初始化

# 创建插件目录mkdir-p openclaw-websocket-channel/websocket-channel cd openclaw-websocket-channel/websocket-channel # 创建基础文件touch index.ts openclaw.plugin.json package.json 

2. 定义插件元数据

index.ts:

importtype{ ReplyPayload }from"openclaw/auto-reply/types";importtype{ ChannelPlugin, OpenClawConfig }from"openclaw/plugin-sdk";import{ createDefaultChannelRuntimeState }from"openclaw/plugin-sdk";interfaceWebSocketChannelConnection{ ws:any; accountId:string;}interfaceWebSocketChannelAccount{ accountId:string; wsUrl:string; enabled?:boolean; configured?:boolean; dmPolicy?:"pairing"|"allowlist"|"open"|"disabled";}const connections =newMap<string, WebSocketChannelConnection>();let pluginRuntime:any=null;const WebSocketChannel: ChannelPlugin<WebSocketChannelAccount>={ id:"websocket-channel", meta:{ id:"websocket-channel", label:"Websocket Channel", selectionLabel:"Websocket Channel (Custom)", docsPath:"/channels/websocket-channel", blurb:"WebSocket based messaging channel.", aliases:["ws"],},// ... 其他配置};

3. 实现配置适配器

config:{/** * 列出所有配置的账户 ID * @returns 固定返回 ["default"] */listAccountIds:(cfg: OpenClawConfig)=>{return["default"];},/** * 解析账户配置 */resolveAccount:(cfg: OpenClawConfig, accountId:string)=>{const channelCfg = cfg.channels?.["websocket-channel"];if(!channelCfg ||!channelCfg.config){returnundefined;}const config = channelCfg.config asany;return{ accountId:"default", wsUrl: config.wsUrl ||"ws://localhost:8765/openclaw", enabled: config.enabled !==false,};},/** * 检查账户是否已配置 */isConfigured:async(account, cfg)=>{returnBoolean(account.wsUrl && account.wsUrl.trim()!=="");},}

4. 实现状态管理适配器 ⭐关键

status:{/** * 默认运行时状态模板 * ⚠️ 必须实现这个方法,否则 UI 会显示 "0/1 connected" */ defaultRuntime:createDefaultChannelRuntimeState("default",{ wsUrl:null, connected:false, groupPolicy:null,}),/** * 构建通道摘要(用于 UI 显示) */buildChannelSummary:({ snapshot })=>({ wsUrl: snapshot.wsUrl ??null, connected: snapshot.connected ??null, groupPolicy: snapshot.groupPolicy ??null,}),/** * 构建账户完整快照 */buildAccountSnapshot:({ account, runtime })=>({ accountId: account.accountId, enabled: account.enabled, configured: account.configured, wsUrl: account.wsUrl, running: runtime?.running ??false, connected: runtime?.connected ??false, groupPolicy: runtime?.groupPolicy ??null, lastStartAt: runtime?.lastStartAt ??null, lastStopAt: runtime?.lastStopAt ??null, lastError: runtime?.lastError ??null,}),}

为什么需要 defaultRuntime

OpenClaw 的 UI 通过读取通道的 defaultRuntime 来知道要跟踪哪些状态字段。如果没有这个配置:

  • UI 不知道要显示 connected 字段
  • 即使你在 startAccount 中设置了 connected: true
  • UI 也只会显示 “0/1 connected”

正确做法

  1. defaultRuntime 中声明要跟踪的字段(包括 connected: false
  2. startAccount 开始时调用 ctx.setStatus({ connected: true })
  3. UI 就会正确显示 “1/1 connected”

5. 实现网关适配器(核心)

gateway:{/** * 启动 WebSocket 账户连接 */startAccount:async(ctx)=>{const{ log, account, abortSignal, cfg }= ctx; log?.info(`[websocket-channel] Starting WebSocket Channel for ${account.accountId}`);// 获取 runtime APIconst runtime = pluginRuntime;// ⭐ 关键:设置初始状态为已连接 ctx.setStatus({ accountId: account.accountId, wsUrl: account.wsUrl, running:true, connected:true,}); log?.info(`[websocket-channel] Status set: connected=true, running=true`);// 创建 WebSocket 连接const WebSocketLib =awaitimport("ws");const ws =new(WebSocketLib.default asany)(account.wsUrl);// 存储连接 connections.set(account.accountId,{ ws, accountId: account.accountId });// 监听消息事件 ws.on("message",async(data: Buffer)=>{try{// 1. 解析原始消息const rawData = data.toString();const eventData =JSON.parse(rawData);const innerData = eventData.data ||{};// 2. 标准化消息const normalizedMessage ={ id:`${eventData.source ||"websocket"}-${Date.now()}`, channel:"websocket-channel", accountId: account.accountId, senderId: innerData.source || eventData.source ||"unknown", senderName: innerData.source || eventData.source ||"Unknown", text: innerData.content || innerData.text ||"", timestamp: innerData.timestamp || Date.now().toISOString(), isGroup:false, groupId:undefined, attachments:[], metadata:{},}; log?.info(`[websocket-channel] 📨 Received: "${normalizedMessage.text}" from ${normalizedMessage.senderId}`,);// 3. 解析路由const route = runtime.channel.routing.resolveAgentRoute({ cfg, channel:"websocket-channel", accountId: account.accountId, peer:{ kind:"direct", id: normalizedMessage.senderId,},});// 4. 构建消息上下文const ctxPayload = runtime.channel.reply.finalizeInboundContext({ Body: normalizedMessage.text, BodyForAgent: normalizedMessage.text, From: normalizedMessage.senderId, To:undefined, SessionKey: route.sessionKey, AccountId: route.accountId, ChatType:"direct", SenderName: normalizedMessage.senderName, SenderId: normalizedMessage.senderId, Provider:"websocket-channel", Surface:"websocket-channel", MessageSid: normalizedMessage.id, Timestamp: Date.now(),});// 5. 调用框架调度器await runtime.channel.reply.dispatchReplyWithBufferedBlockDispatcher({ ctx: ctxPayload, cfg: cfg, dispatcherOptions:{deliver:async(payload: ReplyPayload,{ kind })=>{ log?.info(`[websocket-channel] Delivering ${kind} reply via WebSocket...`);const currentConn = connections.get(account.accountId);if(!currentConn ||!currentConn.ws || currentConn.ws.readyState !==1){thrownewError("No WebSocket connection available");}// 发送 AI 回复 currentConn.ws.send(JSON.stringify({ type:"reply", content: payload.text ||"", kind,}));},onError:(err,{ kind })=>{ log?.error(`[websocket-channel] Delivery error for ${kind}: ${err.message}`);},},}); log?.info(`[websocket-channel] Message dispatched successfully`);}catch(err){ log?.error(`[websocket-channel] Failed to process message: ${err.message}`);}});// 监听错误和关闭 ws.on("error",(err: Error)=>{ log?.error(`[websocket-channel] ❌ WebSocket error: ${err.message}`); connections.delete(account.accountId);reject(err);}); ws.on("close",()=>{ log?.info(`[websocket-channel] 🔴 Connection closed`); connections.delete(account.accountId);resolve();});// 监听中止信号 abortSignal.addEventListener("abort",()=>{ log?.info(`[websocket-channel] ⏹️ Abort requested`); ws.close();resolve();});// 保持连接运行awaitPromise.race([ connectionPromise,newPromise<void>((resolve)=>{ abortSignal.addEventListener("abort",()=>resolve());}),]); connections.delete(account.accountId);},}

6. 注册插件入口

/** * 注册插件入口 * @param api - 插件 API */exportdefaultfunctionregister(api:any){console.log("[websocket-channel] Registering WebSocket Channel plugin"); pluginRuntime = api.runtime; api.registerChannel({ plugin: WebSocketChannel });}

📚 参考链接

官方文档

示例项目

Read more

【ComfyUI】蓝耘元生代 | ComfyUI深度解析:高性能AI绘画工作流实践

【ComfyUI】蓝耘元生代 | ComfyUI深度解析:高性能AI绘画工作流实践

【作者主页】Francek Chen 【专栏介绍】 ⌈ ⌈ ⌈人工智能与大模型应用 ⌋ ⌋ ⌋ 人工智能(AI)通过算法模拟人类智能,利用机器学习、深度学习等技术驱动医疗、金融等领域的智能化。大模型是千亿参数的深度神经网络(如ChatGPT),经海量数据训练后能完成文本生成、图像创作等复杂任务,显著提升效率,但面临算力消耗、数据偏见等挑战。当前正加速与教育、科研融合,未来需平衡技术创新与伦理风险,推动可持续发展。 文章目录 * 前言 * 一、ComfyUI简介 * (一)ComfyUI概述 * (二)ComfyUI与WebUI的对比 * (三)ComfyUI使用场景 * 二、蓝耘元生代平台简介 * 三、蓝耘元生代平台工作流(ComfyUI)创建 * (一)注册蓝耘智算平台账号 * (二)部署ComfyUI工作流 * (三)ComfyUI初始界面解析 * (四)完成创建工作流 * 四、技术文档说明 * (一)平台架构深度剖析

By Ne0inhk
2026 届毕业生必看:各大学位论文 AIGC 检测率要求汇总,超过这个数真的危险了!

2026 届毕业生必看:各大学位论文 AIGC 检测率要求汇总,超过这个数真的危险了!

一、 前言 随着 2026 届毕业季的临近,很多小伙伴在写论文时都离不开 AI 的辅助。但今年最让大家头疼的不再仅仅是查重率,而是新出的AIGC 疑似度。 很多学校已经明确:如果 AIGC 检测超过阈值,直接取消答辩资格! 今天我就帮大家梳理一下目前主流的检测要求,以及如何正确应对。 二、 各大高校 AIGC 检测率“红线”汇总 虽然各校标准不一,但根据目前各大高校反馈的最新政策,基本可以划分为三个梯度: 风险等级AIGC 疑似度范围学校处理建议安全区< 20%基本无风险,属于合理参考范围。预警区20% - 40%导师需进行人工核查,可能要求提供写作痕迹证据。高危区> 40%极大可能被判定为“代写”或“学术不端”,面临延毕风险。 注意: 部分顶尖院校(如 C9

By Ne0inhk

Stable Diffusion WebUI Forge评估体系深度解析:从理论到实践的全面指南

Stable Diffusion WebUI Forge评估体系深度解析:从理论到实践的全面指南 【免费下载链接】stable-diffusion-webui-forge 项目地址: https://gitcode.com/GitHub_Trending/st/stable-diffusion-webui-forge 在人工智能图像生成领域,评估生成模型的质量已成为衡量技术成熟度的关键环节。Stable Diffusion WebUI Forge作为业界领先的开源项目,构建了一套完整的模型评估体系,帮助用户科学判断生成效果。 评估框架的构建基础 传统视觉评估的局限性 在早期图像生成研究中,评估主要依赖人工主观判断。这种方法存在明显缺陷:耗时耗力、标准不一、难以量化。随着技术进步,客观评估指标应运而生,为AI图像生成提供了可靠的量化标准。 现代评估体系的核心要素 现代评估体系需要同时考量多个维度:生成图像的真实性、多样性、清晰度以及与人类视觉感知的一致性。这些要素共同构成了完整的评估框架。 三大核心指标的技术剖析 分布相似性评估:FID指标 FID指标通过深度学习

By Ne0inhk

Ollama下载模型太慢?试试国内HuggingFace镜像+LLama-Factory组合

Ollama下载模型太慢?试试国内HuggingFace镜像+LLama-Factory组合 在本地跑一个大模型,第一步不是写代码、调参数,而是——等它下载完。 这听起来有点荒诞,却是许多中国开发者的真实日常。当你兴致勃勃地打开终端,输入 ollama run llama3:8b,满心期待地准备开启微调之旅时,现实却给你泼了一盆冷水:进度条纹丝不动,网络连接频繁中断,几个小时过去连基础权重都没拉下来。 问题出在哪?根源就在于——Ollama 默认从 HuggingFace 官方仓库拉取模型,而这个服务器远在海外。对于国内用户来说,这无异于“越洋取经”,不仅速度慢如龟爬,还常因网络波动导致失败重试,白白浪费时间和算力资源。 但其实,我们完全不必硬扛这条路。真正聪明的做法是:绕开公网瓶颈,借助国内镜像高速获取模型 + 使用 LLama-Factory 实现低门槛、高效率的本地微调。这套组合拳不仅能让你把“等待下载”的时间省下来喝杯咖啡,还能让7B甚至13B级别的模型在一张消费级显卡上顺利训练起来。 镜像加速:别再用裸连 HuggingFace

By Ne0inhk