本地部署AI办公机器人:OpenClaw+飞书+ollama实战拆解
先把结果说清楚这套东西做出来之后飞书群里机器人问“本周项目进度怎么样”它能把多维表格拉出来总结成简报群里有人提“帮我查一下昨天工单处理数”它能调工具查数据库再画一张趋势图发回群里。整个链路里模型跑在本地数据不出内网skills还能自动从仓库拉新技能包不用每次手动改代码。这篇就完整拆一下我实际搭建的过程包括为什么选这个组合、ollama本地模型接入的配置细节、skills自动安装的机制、以及最终数据可视化是怎么落地的。踩过的坑也会一并列出来方便你照着做的时候少走弯路。1. 整体设计与思路梳理1.1 为什么是OpenClaw飞书ollama这个组合做办公机器人的方案其实不少之前我也试过直接调云端大模型API接飞书用飞书开放平台的自建应用配合事件订阅和机器人能力理论上也能跑通。但实际用下来有几个问题很难受一是隐私办公场景里的消息、文档、表格数据都要往外送很多团队直接不批准二是成本API按token计费群里高频调用一个月下来费用可观三是联动能力飞书开放平台提供的机器人接口只是收发消息真正要它查数据、写文档、分析表格还得自己写大量胶水代码。OpenClaw这种Agent框架解决的是最后一公里的问题。它本身是个“大脑手脚”的结构大脑接大模型做意图理解和任务规划手脚通过工具调用去操作外部系统。飞书变成了它的一个渠道Channel不只是收发消息还能读写多维表格、发卡片消息、拉群信息。ollama则负责把“大脑”放到本地模型权重跑在自己机器上彻底断掉数据外传的链路。这个组合的定位很清晰OpenClaw当调度中枢飞书当交互界面ollama当推理引擎三者通过配置而非代码深度耦合。出问题的时候排查链路也简单哪一层挂了一眼就能看出来。1.2 架构分层与控制流设计整个系统我分了四层每一层只干自己那点事交互层飞书客户端。用户发消息、收卡片、点按钮全在这一层完成。接入层OpenClaw的飞书Channel。负责处理订阅事件、回调验签、消息格式转换、调用飞书开放API。能力层OpenClaw的技能与工具集。包括内置工具查时间、算数、发HTTP请求和自定义skills查多维表格、读数据库、出报表。推理层ollama服务。装载量化后的开源模型通过OpenClaw的统一LLM接口完成对话补全和函数调用。控制流是单向的。用户在飞书里机器人消息通过事件订阅推给OpenClawOpenClaw把它交给LLM做意图识别若需要工具则从skills里匹配并调用最终结果以文本或卡片形式发回飞书。整个过程里OpenClaw是唯一中介业务逻辑不在飞书侧也不在模型侧而在skills配置里。这样设计的最大好处是热插拔。想换模型改两行配置就行想加能力放一个新的skill进目录就生效想换平台比如从飞书换到钉钉只需要加一个钉钉Channel配置核心技能全部复用。对办公场景来说这种衰减式耦合能省掉大量重复开发。1.3 环境准备与部署规划我用的是Docker Compose部署主要图它好迁移、好回滚。机器配置建议16GB内存起步如果用7B量级的模型8GB也能勉强跑但多轮对话和函数调用容易把上下文撑爆。操作系统没太大讲究Ubuntu、Windows WSL2、macOS都能跑通。部署前明确几个目录的规划claw/OpenClaw主程序目录含配置文件和运行日志。ollama/ollama模型存储目录挂载为Docker卷。skills/自定义技能包目录OpenClaw启动时会扫描这里。data/SQLite和可视化输出目录存放机器人运行数据和生成的图表。端口规划也提前定好ollama默认11434OpenClaw管理面板8000飞书回调不需要开放公网端口——用的长连接模式。这个后面细说。2. 本地ollama模型接入实操2.1 模型选型办公场景不是越大越好本地模型的选择直接决定机器人“智商”和响应速度的平衡。办公场景的典型任务包括意图识别、信息抽取、工具参数提取、简短回答这些任务对模型的中文能力和指令跟随能力要求高但不是所有任务都需要顶尖的推理能力。我最终选了Qwen2.5系7B和14B两个量级都试过。7B模型在16GB内存的机器上响应速度在15-25 token/s之间日常对话和简单工具调用够用14B模型速度掉到8-12 token/s但指令跟随明显更稳尤其在复杂工具参数提取上失误率低很多。如果你的机器内存是32GB往上直接上14B只有16GB就老实跑7B再用量化进一步压缩显存占用。模型下载直接用命令拉取。ollama支持的模型命名规则是模型名:标签标签里常见的有q4_K_M、q8_0这种量化级别标识。办公场景选q4_K_M性价比最高体积小且质量损失不明显。# 拉取7B模型Q4量化 ollama pull qwen2.5:7b-q4_K_M # 拉取14B模型也是Q4量化 ollama pull qwen2.5:14b-q4_K_M2.2 配置OLLAMA_API并测试连通OpenClaw对接ollama用的是OpenAI兼容接口。ollama从某个版本开始原生支持/v1/chat/completions路径这意味着你不需要任何转换层直接在OpenClaw配置文件里把API Base指向ollama地址就行。这一步是很多教程忽略的不少人还在中间套一层代理其实纯属多余。先确保ollama被网络上的其他容器访问。默认配置下ollama只监听本地回环地址Docker容器之间是访问不到的。在启动ollama容器时加一个环境变量搞定docker run -d --name ollama \ -v ollama:/root/.ollama \ -p 11434:11434 \ -e OLLAMA_HOST0.0.0.0 \ ollama/ollama验证服务端是否就绪直接调接口测试curl http://127.0.0.1:11434/v1/chat/completions \ -H Content-Type: application/json \ -d {model:qwen2.5:7b-q4_K_M,messages:[{role:user,content:11等于几}],stream:false}返回带choices字段的JSON就说明通了。如果返回连接拒绝检查容器是否在运行以及防火墙有没有放行11434端口。2.3 OpenClaw侧模型接入配置OpenClaw的模型配置在config.yaml里核心是让OpenClaw知道“大脑”连到哪里、用什么模型、以及关键参数。我贴一下我实际用的配置llm: provider: openai_compatible api_base: http://ollama:11434/v1 api_key: ollama # 本地服务不校验key但字段不能空 model: qwen2.5:14b-q4_K_M temperature: 0.2 max_tokens: 4096 timeout: 120几个参数我特别说明一下。temperature设到0.2办公场景要的是稳定输出不是天马行空。之前试过默认的0.7同一个问题有时返回JSON格式有时返回纯文本解析经常崩。调低之后稳定性明显提升。max_tokens设到4096这是因为工具调用场景下模型往往要先生成一段思维链再加上工具参数token消耗比纯对话大。设太短会被截断工具调用直接失败。timeout设到120秒是因为本地模型推理速度远慢于云端API尤其14B模型在非GPU机器上可能要几十秒才输出完。OpenClaw默认超时如果是30秒大概率会在这环节报错。2.4 优化本地推理速度的几个细节实测下来ollama接入之后最影响体验的不是模型“笨不笨”而是“慢不慢”。几个提速手段按效果排序加num_ctx参数默认上下文长度只有2048多轮对话后模型被迫遗忘前文。在模型调用时显式指定num_ctx: 8192或者用Modelfile设置为默认值能减少因上下文不足导致的重复推理。关闭stream或合理处理流式办公机器人场景不需要逐字输出一次性返回完整结果更省心。OpenClaw配置里把stream关掉减少中间态解析开销。模型常驻内存ollama会自动做模型缓存只要不重启服务第二次开始响应会明显变快。别没事就重启容器。还可以用Modelfile做一个小优化把办公场景常用的system prompt预置进模型省去每次请求都要带的开销FROM qwen2.5:14b-q4_K_M PARAMETER temperature 0.2 PARAMETER num_ctx 8192 SYSTEM 你是办公助理。回答要求简洁、准确必须使用中文。涉及数据处理时优先调用可用工具。然后构建并应用ollama create office-assistant -f Modelfile在配置里把model改成office-assistant即可。3. 飞书机器人接入与事件处理3.1 创建飞书自建应用并开启机器人能力飞书开放平台后台创建一个企业自建应用这一步流程固定填写应用名称和描述开启“机器人”能力拿到App ID和App Secret。需要注意的一点是应用创建后默认是“测试中”状态只有添加了可用成员或者企业自建应用的可用范围机器人才真正能被到。创建成功之后在“权限管理”里开通以下权限漏一个后面功能就跑不通im:message读取与发送单聊/群聊消息im:message.p2p_msg读取用户发给机器人的单聊消息im:chat读取群信息用于获取消息的上下文docs:doc如果后续要操作云文档drive:drive云盘文件读写按需开权限开通后要等几分钟生效别急着测接口。3.2 长连接模式与回调策略飞书机器人有两种接收消息的方式Webhook回调或长连接WebSocket。Webhook方式要求你的服务器有公网地址且必须配置回调URL、加密策略和验签逻辑每次消息还要响应飞书的URL验证请求。这套流程本身不难但要额外维护公网服务而且回调地址一旦换IP就要改配置麻烦。OpenClaw的飞书Channel内置了长连接模式支持。长连接模式下机器人主动跟飞书服务器建立WebSocket连接消息通过这个连接推过来不需要公网入口也省去回调验签。本地开发阶段这个模式几乎是唯一推荐的方式。配置长连接模式的关键点是拿到长连接Key也叫WebSocket Key它在飞书开放平台的“事件与回调”页面里走的是“使用长连接接收事件”这个方案。拿到Key之后重新推送应用确保事件订阅状态是“已确认”。我在配置时遇到过Key过期的问题——长连接Key有效期是2小时过期后需要重新GETOpenClaw的新版本会自动刷新旧版本则要手动更新。3.3 OpenClaw飞书Channel配置在OpenClaw的config.yaml中配置飞书Channelchannels: feishu: enabled: true app_id: cli_something app_secret: your_app_secret mode: websocket websocket_key: your_websocket_key bot_name: 办公室AI助理 max_message_length: 2000其中max_message_length建议设到2000字符飞书通过API发消息限制是单个消息最长150KB但长消息用卡片展示体验更好。OpenClaw默认用文本消息发结果如果LLM返回超长内容容易被截断设这个参数可以在接近上限时自动截断并提示。配置完成后启动OpenClaw观察日志里是否有feishu websocket connected之类的字样。没有的话优先检查app_secret是否写对以及websocket_key是不是最新值。3.4 消息事件与回调处理细节飞书消息事件里需要处理两个核心类型im.message.receive_v1收到消息和url_verification验证URL有效。长连接模式下后者不需要处理OpenClaw自动完成。但消息事件处理有些细节会影响体验过滤OpenClaw默认只在消息中包含机器人时才响应。这在群聊里很重要否则群里所有人聊天都会触发机器人自动回复。单聊场景则全部响应。消息去重飞书有重试推送消息的机制事件ID相同的消息可能收到两次。OpenClaw内部有去重逻辑但基于事件ID的哈希有效期有限机器人被后回复又收到一条带相同内容的群消息有时会造成“复读”。我处理的方式是在skill里判断消息内容是否和上一条相同且时间在3秒内是则直接忽略。富文本消息飞书消息可能是纯文本、富文本、图片、文件等载荷。OpenClaw会转成统一的消息对象但富文本中提取的纯文本偶尔会带多余换行和空格建议在LLM提示词里加一句“忽略无关空白和格式符号”。4. Skills机制与自动安装实践4.1 Skills是什么和插件有什么区别Skills是OpenClaw里的可复用能力包本质是把“提示词工具函数配置”打包成一个目录。它不同于普通插件的地方在于插件是代码层面的模块Skills是声明式的用描述和示例告诉模型“这个技能在什么场景用、怎么用”实际执行时由模型决定是否调用以及如何填充参数。举个例子。我想让机器人能查某个多维表格的数据并做简单统计传统做法是写一段Python代码注册一个函数处理消息中的特定指令。用Skills则是写一个manifast.yaml描述能力范围一个schema.json定义工具入参再加一个run.py写执行逻辑。模型看到用户的消息自动决定“这里需要调用query_bitable工具”然后从消息里抽取表格ID、筛选条件之类参数填进schema最终落到飞书多维表格API调用上。这个机制最大的价值是降低新增能力的门槛。传统机器人每加一个功能就要改代码、重新部署Skills机制下放一个新目录进skills/就完事模型靠描述就能自动“学会”用这个工具。4.2 编写一个自定义Skill查多维表格我用一个实际例子演示技能包的目录结构和关键文件skills/ ├── bitable-query/ │ ├── SKILL.md │ ├── schema.json │ ├── run.py │ └── requirements.txtSKILL.md的核心作用不是给人看的文档而是给模型看的“工具说明书”。内容要写清楚这个skill是什么、触发条件是什么、参数怎么填、使用限制是什么# 多维表格查询工具 - 功能根据用户输入查询飞书多维表格数据支持筛选、排序、分页并返回格式化结果 - 触发场景当用户提到“查表格”、“看数据”、“统计一下”、“工单量多少”等意图时 - 输入参数app_token表格Token、table_id数据表ID、page_size分页大小默认10、filter筛选条件 - 输出格式Markdown表格schema.json描述模型需要提取的参数结构本质上是一段JSON Schema{ name: query_bitable, description: 查询飞书多维表格数据并返回结构化结果, parameters: { type: object, properties: { app_token: { type: string, description: 多维表格App Token }, table_id: { type: string, description: 数据表ID }, filter: { type: string, description: 筛选条件如状态 已处理 }, page_size: { type: integer, description: 每页返回条数, default: 10 } }, required: [app_token, table_id] } }run.py是实际执行函数。这里需要注意OpenClaw调用工具函数时是同步等待返回的所以run.py里不要做耗时太长的操作比如一次拉全量数据再过滤应该尽量把筛选条件push到飞书API的参数里先缩小数据量再返回。飞书多维表格API支持filter参数把filter字符串转成API的filterConditions格式能节省大量token。4.3 自动安装Skills的配置与目录管理OpenClaw支持通过配置指定一个或多个skills目录启动时自动扫描安装。这一步是很多初学者会忽略的默认情况下OpenClaw只加载内置技能放在skills/里的自定义包如果不注册模型根本不知道有这玩意儿。配置方式skills: auto_install: true directories: - ./skills - /shared/skills-repo scan_interval: 60 require_manifest: trueauto_install: true开启后OpenClaw会在启动时扫描目录、解析每个子目录的SKILL.md和schema.json注册到工具表。scan_interval设置60秒意味着动态新增的skill最多等60秒就能被识别不用重启服务。我建议用git仓库管理skills目录。把skills/初始化为一个独立的git仓库团队成员各自fork、修改、提PR合并后机器人侧只需要git pull就能拿到新技能自动安装逻辑会帮它动态加载。内网环境可以用gitlab或者gitea托管一样的效果。实测下来多人协作时这个流程比“改代码后重新部署”要顺滑得多非技术背景的同学也能通过写SKILL.md的方式贡献技能。4.4 模型正确调用Skills的调优经验skills机制的瓶颈往往不是skill写得不好而是模型“不知道什么时候该用、用哪个”。Qwen2.5系模型在工具调用能力上表现尚可但偶尔会出现该调不调、不该调乱调的情况。我的调优方法是在每次会话的system prompt里列出可用skill清单。OpenClaw默认会把所有技能描述拼进system prompt如果你装了太多skill模型容易看糊涂。我做法是只保留高频skill的详细描述低频的用一句“you can call it if needed”带过减少干扰。在SKILL.md里写明负向触发条件。比如“如果用户只是闲聊不要调用此工具”。这能大幅减少误触发。用few-shot示例强化参数抽取。在SKILL.md里加两个示例一个成功抽取参数的例子一个缺失参数时的处理示例。模型在抽取参数时会更稳定尤其是日期、数字这类容易被描述得模糊的值。5. 数据可视化落地5.1 可视化什么数据怎么存既然叫“数据可视化”先要明确机器人能沉淀什么数据。我的设计里主要采集这几类消息量与活跃度机器人每天接收的消息数、回复数、来源群聊、发送耗时工具调用统计各skills被调用的次数、成功失败分布、平均耗时模型与系统性能请求延迟、token消耗、上下文长度分布、模型推理耗时数据来源有两个一个是OpenClaw自带的日志它会以JSON格式输出每轮请求的完整链路另一个是飞书侧的消息记录。我建议不要直接消费日志文件而是用一个Python脚本定时解析日志并写入SQLite建两张核心表message_logs和tool_calls。CREATE TABLE message_logs ( id INTEGER PRIMARY KEY AUTOINCREMENT, msg_id TEXT UNIQUE, chat_id TEXT, user_id TEXT, content TEXT, reply_text TEXT, latency_ms INTEGER, created_at DATETIME ); CREATE TABLE tool_calls ( id INTEGER PRIMARY KEY AUTOINCREMENT, tool_name TEXT, success INTEGER, cost_ms INTEGER, input_summary TEXT, created_at DATETIME );写入频率不用太高每处理完一轮请求写一次即可。SQLite在办公场景的数据量下完全扛得住不需要上MySQL。5.2 可视化方案选型可视化层我的选择原则是“开发量小、部署简单、足够好看”。办公机器人数据量不大服务端渲染静态HTML是性价比最高的方案。Python的matplotlibjinja2模板就够用了不用上ECharts、Grafana那些重型方案——它们确实酷炫但给内部展示的机器人数据上Grafana属于杀鸡用牛刀。我实际用的是Python生成静态报告脚本流程从SQLite查昨天的数据聚合结果用matplotlib绘制柱状图按小时的消息量、折线图响应延迟变化、饼图工具调用占比用jinja2渲染一个包含图表和统计数字的HTML页面把页面存到data/reports/目录并通过飞书机器人定时推送到指定群定时任务用crontab或者系统的定时器每天早上9点执行一次昨天数据汇总。5.3 定时报告推送的实现实现定时报告推送的代码逻辑核心是OpenClaw定时触发一个自定义skill从SQLite取数、生成图表、通过飞书发送。这一步我用的是一个名为daily_report的skill。SKILL.md里定义它每天触发一次触发词是“日报生成”或者定时任务的system消息。run.py里做的事情由这几段构成import sqlite3 import matplotlib matplotlib.use(Agg) import matplotlib.pyplot as plt import os from datetime import datetime, timedelta import requests def generate_charts(db_pathdata/analytics.db, report_dateNone): if report_date is None: report_date (datetime.now() - timedelta(days1)).strftime(%Y-%m-%d) conn sqlite3.connect(db_path) cur conn.cursor() # 消息量按小时分布 cur.execute( SELECT strftime(%H, created_at) AS hour, COUNT(*) FROM message_logs WHERE date(created_at) ? GROUP BY hour , (report_date,)) hourly cur.fetchall() # 工具调用成功率 cur.execute( SELECT tool_name, success, COUNT(*) FROM tool_calls WHERE date(created_at) ? GROUP BY tool_name, success , (report_date,)) tool_stats cur.fetchall() conn.close() ...图表生成后用飞书API发送注意飞书发图片用的是image_key需要先把图片上传到飞书临时素材库# 上传图片并获取image_key with open(chart_path, rb) as f: resp requests.post( https://open.feishu.cn/open-apis/im/v1/images, headers{Authorization: fBearer {tenant_access_token}}, files{image: (chart_path, f, image/png)}, data{image_type: message} ) image_key resp.json()[data][image_key] # 发送图片消息 requests.post( https://open.feishu.cn/open-apis/im/v1/messages, headers{Authorization: fBearer {tenant_access_token}}, json{ receive_id: chat_id, msg_type: image, content: json.dumps({image_key: image_key}) } )对于需要定时自动触发的场景OpenClaw配置里加一个定时任务条目让它每天9点调用daily_reportskill。注意定时任务跑的时候没有用户消息上下文skill实现里要判断触发来源是“定时触发”就自动取chat_id配置值是“用户主动请求”则取当前会话的chat_id。5.4 可视化看板展示效果做完之后的展示效果大概是这样早上9点飞书一个群里机器人发出一条卡片消息卡片里除了文字摘要昨天共处理消息128条回复成功122条失败6条失败率4.7%还附带4张图表24小时消息分布柱状图、工具调用成功率饼图、平均响应延迟折线图、模型token消耗趋势图。群成员点开就能看到过去的运行情况不用登录任何后台。这套可视化方案最大价值不是“好看”而是让机器人运行情况可观测。模型接入了、skills装了不等于万事大吉——延迟涨了、工具调用失败率高了、某些时段消息堆积没回复这些从日志里都能发现但没有人会天天翻日志。图表化以后异常一眼就能看出来比如工具成功率突然掉到60%以下多半是某个skill的API权限过期了这时候去排查就有方向了。6. 常见问题与排查技巧6.1 飞书机器人收不到消息症状应用创建好了OpenClaw也显示长连接已连接但群里机器人没有响应。排查顺序确认应用是否发布自建应用只有“创建”但没有“发布”到企业的话只有可用范围内的成员能触发。确认长连接Key是否过期这个Key有效期2小时。如果你是用OpenClaw旧版本并且Key是手动配置的过期后连接会静默断开表面上看起来连上了实际收不到事件。新版本会自动刷新但确认一下日志里是否有刷新动作更稳妥。确认机器人是否被正确有些群里如果群成员不能机器人消息根本发不出来。在群设置里检查“群机器人”权限。确认事件订阅里是否启用了im.message.receive_v1如果只配置了长连接Key但没订阅消息事件推送链路是断的。6.2 ollama推理正常但OpenClaw经常报错典型报错是connection timed out或request cancelled。原因几乎都是推理耗时超过了请求超时时间。本地跑14B模型在无GPU的机器上长文本输入首次推理可能超过2分钟而OpenClaw侧默认超时可能只有30秒。处理方式调大OpenClaw的LLM timeout配置到120秒及以上给模型配置更短的上下文num_ctx从8192降到4096以加快推理把非关键任务分流比如日报生成这种定时任务可以单独用更小的7B模型处理让14B专注在线对话6.3 Skills动态安装后不生效合入新skill到skills/目录后OpenClaw日志显示扫描到了但模型就是不调用。这种情况通常不是扫描问题而是模型在system prompt里没有看到足够清晰的技能描述。OpenClaw组装提示词时会把扫描到的skill说明塞进system prompt但描述过长会被截断或“被忽略”。我的处理办法保持每个SKILL.md的description部分不超过60个token把触发条件写清楚定期“清理”不常用的技能包减少system prompt体积用硬性的关键词标记比如“当用户输入包含‘报表’‘统计’‘分析’时必须使用daily_report工具”6.4 工具调用时模型生成了参数但执行报500如果本地模型偶发返回非JSON格式的“思考过程”工具解析器就会失败。降低temperature能明显减少这类问题还有一种办法是给模型配置里加一条response_format约束让模型严格按JSON结构输出工具参数。OpenClaw的LLM标准接口支持response_format{type:json_object}但ollama的某些模型对严格的JSON mode支持一般实测下来Qwen2.5系在temperature0.2时出错的概率已经很低了不需要额外处理。6.5 定时任务跑完没发消息定时任务触发时代码里有一步需要拿tenant_access_token这个token有效期2小时缓存过期后需要重新用app_id和app_secret换。如果定时任务在token过期前后执行可能出现获取token失败但代码没捕获异常的静默失败。调试时直接把token获取部分打印出来看状态码是401还是其他错误能快速定位是有效期问题还是权限问题。7. 收尾的几点心得一开始做这个项目的时候我的预期是“把模型接进飞书就行”结果真正花时间的不是模型接入而是让它稳定地在办公场景里干活。try过几个不同的模型最终确定用小量化Qwen2.5跑日常但说实话想让它稳定输出重活还得靠14B。如果你预算不多、机器允许宁可少跑几个别的服务也给模型留够内存。第二个体会是自建应用和长连接模式是办公机器人开发的“正道”。之前用Webhook方式折腾过验签、公网穿透纯属浪费时间。飞书官方的长连接方案在没有公网IP的内网环境里几乎是唯一合理的解法而OpenClaw把它集成成了配置项省掉了很多繁琐的订阅流程。再就是Skills自动安装这套机制刚接触会觉得“不就是个插件目录吗”用深了才发现它真正解决的是“让非程序员也能给机器人加能力”的问题。团队里有个同事不会写代码但他把业务查询规则整理好我用半天时间转成SKILL.md机器人就学会了一项新能力。这比“手写分支逻辑判断”的模式灵活太多了。最后一个小建议可视化别贪多图表核心是让异常暴露出来。先把延迟、失败率、调用量这三个指标盯住跑两三周后再根据实际情况加维度。可视化不是目标可观测才是。