1. 从“渴了才喝水”到“定时提醒”:一个程序员的健康自救
不知道你有没有过这样的体验:早上九点坐到工位前,打开IDE,开始沉浸式coding。等回过神来,一看时间,已经下午两点了。不仅午饭忘了吃,更可怕的是,你发现自己从坐下到现在,一口水都没喝。喉咙干得像撒哈拉沙漠,身体已经在发出警报,但你的大脑还沉浸在解决那个该死的Bug的兴奋感里。这种状态,我们程序员太熟悉了。长期如此,什么肾结石、尿酸高、皮肤干燥,各种毛病都找上门来。
我以前也这样,直到有一次体检,医生看着我的报告直摇头,说你这饮水量严重不足。我才意识到,靠“渴了再喝”这种原始生理信号,在高度专注的编程状态下是完全失效的。我们需要的是外部干预,一个温和但坚定的“打断”。市面上有很多喝水提醒软件,功能花里胡哨,有的甚至要付费订阅。但作为一个程序员,我的第一反应是:这东西,我自己能不能搓一个?要足够轻量、完全可控、不打扰核心工作流,还能有点趣味性。
这就是“OpenClaw”进入我视野的原因。最近,围绕OpenClaw的讨论非常火热,从安装部署到接入飞书、微信,玩法很多。但在我看来,它最吸引我的核心价值是:一个高度可编程、可通过自然语言驱动的自动化智能体框架。这意味着,我不需要去写复杂的GUI或者研究系统通知API,我只需要用人类语言告诉它:“每隔一小时,提醒我站起来活动一下,喝口水”,它就能自己去理解、规划并执行这个任务。这简直就是为我们这种“懒”但又追求极致的程序员量身定做的。
所以,我决定用OpenClaw,花一分钟时间,手搓一个专属于我的、AI驱动的喝水提醒助手。这个项目没有复杂的业务逻辑,没有庞大的代码库,它的全部意义就在于:用最小的代价,解决一个真实、高频的痛点,并享受“自己动手,丰衣足食”的乐趣。下面,我就带你完整走一遍这个“一分钟手搓”的过程,并分享其中我踩过的坑和总结的经验。
2. OpenClaw极速部署:绕过那些复杂的配置陷阱
在开始我们的“喝水提醒器”之前,得先让OpenClaw跑起来。网络上关于OpenClaw安装的教程很多,但信息也相当零碎和矛盾,特别是对于只是想快速体验一下的新手。有人推荐Docker,有人推荐源码,还有各种关于ollama_base_url、default_model的配置错误。我的目标是极简、快速、一次成功,所以我会带你走一条最稳妥的路径。
2.1 环境选择与准备:为什么是Docker?
部署OpenClaw,主要有三种方式:1. 本地Python环境直接安装;2. 使用Docker Compose;3. 结合Ollama部署本地大模型。对于我们的“一分钟手搓”目标,我强烈推荐Docker Compose方案。原因如下:
- 环境隔离,避免污染:OpenClaw依赖的Python包可能和你本地开发环境冲突,用Docker可以完美避开“装了这个,那个坏了”的经典困境。
- 一键启动,复杂度低:相比源码安装需要处理虚拟环境、依赖版本、系统服务等问题,Docker Compose通常只需要一个配置文件和一个启动命令。
- 网络配置清晰:Docker容器内的网络和端口映射是明确的,排错时思路更清晰。
所以,请确保你的机器上已经安装了Docker和Docker Compose。如果没有,去Docker官网下载安装,这是基础,此处不赘述。
2.2 获取并调整docker-compose.yml:关键参数解析
OpenClaw官方或社区通常会提供一个docker-compose.yml模板。但直接使用很可能失败,我们需要进行关键调整。以下是一个经过我实测可用的精简版本,我将其保存为docker-compose.yml文件:
version: '3.8' services: openclaw: image: crestodian/openclaw:latest # 使用稳定的镜像标签,而非latest有时更安全 container_name: openclaw restart: unless-stopped ports: - "3000:3000" # 将容器内的3000端口映射到宿主机的3000端口 environment: - OPENCLAW_LOG_LEVEL=INFO - OPENCLAW_DATABASE_URL=sqlite:///./data/openclaw.db # 使用SQLite,简单 - OPENCLAW_SERVER_HOST=0.0.0.0 # 重要!允许外部访问 volumes: - ./data:/app/data # 持久化数据,避免容器重启后数据丢失 - ./skills:/app/skills # 挂载本地skills目录,方便自定义技能 command: uvicorn openclaw.main:app --host 0.0.0.0 --port 3000 --reload关键点与避坑指南:
- 镜像选择:
crestodian/openclaw:latest是一个常见的社区镜像。如果latest拉取失败或启动有问题,可以尝试指定一个具体的版本号,如crestodian/openclaw:2.7.9。网络上搜索“openclaw 2.7.9免费版”提到的就是这个版本。 - 端口映射:
“3000:3000”左边是宿主机端口,右边是容器内端口。你可以把左边的3000改成任何你喜欢的、未被占用的端口(如8080)。 OPENCLAW_SERVER_HOST:这是最大的坑!很多教程忽略了这一点。如果不设置为0.0.0.0,容器内的服务只会监听本地回环地址,你从宿主机浏览器通过localhost:3000或通过IP访问都会失败。务必检查!- ** volumes(卷挂载)**:挂载
./data是为了持久化数据库和配置;挂载./skills是我们后续自定义“喝水提醒”技能的关键,它让你可以在宿主机上编辑文件,直接影响容器内的技能库。 - ** command**:这里的命令直接启动了OpenClaw的FastAPI应用。
--reload参数在开发时很有用,但生产环境建议去掉。
2.3 启动与验证:看到界面才算成功
在包含docker-compose.yml文件的目录下,打开终端,执行一条命令:
docker-compose up -d-d参数代表后台运行。如果一切顺利,你会看到Docker拉取镜像并启动容器。接下来,验证服务是否正常:
- 查看容器状态:
docker-compose ps。应该看到openclaw服务的状态是Up。 - 查看日志:
docker-compose logs -f openclaw。观察启动日志,有没有ERROR报错。常见的错误包括:端口被占用(换端口)、环境变量配置错误、镜像拉取失败等。如果看到类似Application startup complete.的日志,基本就成功了。 - 访问Web界面:打开浏览器,访问
http://localhost:3000(如果你改了端口,就换成对应的,如http://localhost:8080)。如果能看到OpenClaw的Web UI界面(可能是一个简单的聊天窗口或管理面板),恭喜你,部署成功!
注意:如果访问不到,请按以下顺序排查:① 确认容器状态是
Up;② 确认宿主机防火墙是否放行了对应端口;③ 检查docker-compose logs是否有明确错误;④ 确认OPENCLAW_SERVER_HOST环境变量已设置为0.0.0.0。
至此,OpenClaw的运行环境已经就绪。它现在就像一个空有大脑(框架)但没有技能(具体能力)的智能体。接下来,我们要教给它第一个,也是最重要的一个技能:提醒我喝水。
3. 手搓核心技能:让OpenClaw学会“定时提醒”
OpenClaw的能力扩展依赖于“Skill”(技能)。一个Skill本质上就是一个Python文件,里面定义了这个技能能做什么、需要什么参数、以及具体的执行逻辑。我们的目标是创建一个“定时提醒”技能。网络上有很多复杂的Skill例子,但对于喝水提醒,我们要追求极致简单。
3.1 创建技能文件:结构比想象中简单
首先,在宿主机上,进入与docker-compose.yml同级的目录(如果你按照上面挂载了./skills)。创建一个名为drink_reminder_skill的文件夹,并在里面创建__init__.py和skill.py两个文件。结构如下:
你的项目目录/ ├── docker-compose.yml ├── data/ (自动生成) └── skills/ └── drink_reminder_skill/ ├── __init__.py └── skill.py__init__.py可以是空文件,它的存在是为了让Python将这个目录识别为一个包。核心全部在skill.py中。
3.2 编写skill.py:详解每一个参数与装饰器
打开skill.py,开始编写代码。我将逐段解释,让你不仅会抄,更懂为什么这么写。
import asyncio from datetime import datetime from typing import Any, Dict from openclaw.skills import skill, BaseSkill @skill( name="drink_water_reminder", description="设置一个周期性喝水提醒。", inputs={ "interval_minutes": { "type": "integer", "description": "提醒间隔时间(分钟)。", "required": True, "default": 60 }, "reminder_message": { "type": "string", "description": "提醒时显示的消息。", "required": False, "default": "💧 时间到!该站起来活动一下,喝口水啦!" } } ) class DrinkWaterReminderSkill(BaseSkill): """ 一个简单的周期性喝水提醒技能。 """ async def execute(self, inputs: Dict[str, Any]) -> Dict[str, Any]: """ 执行技能:启动一个后台异步任务,每隔指定分钟发送一次提醒。 """ interval = inputs["interval_minutes"] message = inputs.get("reminder_message", "💧 时间到!该喝水啦!") # 获取技能上下文中的消息发送函数(这通常是OpenClaw框架注入的) # 这里假设框架提供了一个 `send_message` 方法来发送提醒。 # 实际方法名可能需要根据OpenClaw版本调整,例如可能是 `notify_user` 或 `send_notification`。 send_notification = self.context.get("send_message") if not send_notification: return { "success": False, "message": "无法发送通知:未找到消息发送接口。" } self.logger.info(f"开始喝水提醒任务,间隔 {interval} 分钟。") # 启动一个后台任务,不阻塞当前执行 asyncio.create_task(self._reminder_loop(interval, message, send_notification)) return { "success": True, "message": f"喝水提醒已启动,每隔 {interval} 分钟提醒一次。" } async def _reminder_loop(self, interval_minutes: int, message: str, sender): """ 后台循环任务,负责定时发送提醒。 """ while True: await asyncio.sleep(interval_minutes * 60) # 转换为秒 try: # 这里调用发送函数。在实际OpenClaw中,这可能会触发桌面通知、发送聊天消息等。 # 例如:await sender(text=message) # 由于我们是在极简演示,这里先打印日志,并假设sender是一个可调用的异步函数。 self.logger.info(f"发送提醒:{message}") # 模拟发送动作。在实际集成中,你需要根据OpenClaw的API替换这里。 if callable(sender): await sender(message) except Exception as e: self.logger.error(f"发送提醒时出错:{e}")代码深度解析与避坑点:
@skill装饰器:这是OpenClaw框架识别一个技能的关键。它定义了技能的元数据。name: 技能的唯一标识符,后续我们通过这个名字来调用技能。description: 对技能的自然语言描述,这很重要!因为OpenClaw的LLM(大语言模型)会通过这个描述来理解何时该调用这个技能。inputs: 定义技能需要的输入参数。这里我们定义了两个:interval_minutes: 整数类型,必须提供,默认60分钟。这个设计很关键,它让技能变得可配置。reminder_message: 字符串类型,非必须,有一个友好的默认值。这增加了技能的灵活性。
BaseSkill类与execute方法:所有技能都必须继承BaseSkill并实现execute方法。这个方法是技能的入口点,当OpenClaw决定调用这个技能时,就会运行它。inputs参数:接收到的就是用户在调用时提供的参数,会匹配@skill装饰器中定义的inputs结构。- 异步
async:execute方法是异步的。这是因为OpenClaw本身是异步框架,为了不阻塞主线程,特别是在执行定时、IO等操作时。
核心逻辑
_reminder_loop:这是实现定时功能的关键。asyncio.create_task(): 我们将循环任务创建为一个独立的“后台任务”。这样execute方法就能立即返回响应(“提醒已启动”),而不会一直卡在那里等待循环结束。这是编写友好技能的重要技巧。asyncio.sleep(): 用于实现等待。注意这里乘以60,将分钟转换为秒。sender函数:这是最大的假设和需要适配的点!代码中的sender是我假设的一个由OpenClaw框架提供的、用于发送通知的函数。在实际的OpenClaw版本中,这个函数可能叫send_message、notify,或者需要通过特定的API通道(如WebSocket)来发送。你需要根据你部署的OpenClaw版本的实际API来修改这部分。我们的初版目标只是让技能被成功加载和调用,所以先用logger.info打印日志来验证循环是工作的。
错误处理:在循环中使用了
try...except来捕获发送消息时可能出现的异常,并用logger.error记录。这能防止因为一次发送失败导致整个提醒任务崩溃。
3.3 加载技能:让OpenClaw发现它
创建好技能文件后,我们需要让OpenClaw容器知道这个技能的存在。因为之前我们在docker-compose.yml中已经将本地的./skills目录挂载到了容器的/app/skills,所以现在只需要重启OpenClaw容器,它就会自动扫描并加载新技能。
docker-compose restart openclaw重启后,再次查看日志:docker-compose logs -f openclaw。你应该能在日志中看到类似Loaded skill: drink_water_reminder的信息,这表明你的技能已经被成功加载。
如果没看到加载信息,请检查:① 技能文件夹和文件的路径、命名是否正确;② 技能类是否正确定义并使用了@skill装饰器;③ 挂载的卷是否生效(可以进入容器查看:docker exec -it openclaw ls /app/skills)。
4. 与智能体对话:用自然语言激活你的提醒
技能加载成功后,它还是一个静止的工具。我们需要通过OpenClaw的“大脑”——即集成的大语言模型(LLM)——来理解和调用这个技能。这就是OpenClaw最有趣的地方:你不需要写调用代码,只需要用自然语言告诉它。
4.1 配置LLM连接:给OpenClaw装上“大脑”
默认情况下,OpenClaw可能没有配置LLM,或者配置的是需要API Key的在线模型(如OpenAI)。为了快速测试,我推荐使用Ollama在本地运行一个开源模型,完全免费且隐私安全。
- 安装并启动Ollama:前往Ollama官网下载安装。安装后,在终端运行
ollama run qwen2.5:7b(这是一个不错的轻量级中文模型)。这会拉取镜像并运行模型服务,默认API端口是11434。 - 配置OpenClaw连接Ollama:这通常需要修改OpenClaw的配置文件。由于我们使用Docker,可以通过环境变量或配置文件挂载来设置。最直接的方法是修改
docker-compose.yml,添加Ollama相关的环境变量:
environment: - OPENCLAW_LOG_LEVEL=INFO - OPENCLAW_DATABASE_URL=sqlite:///./data/openclaw.db - OPENCLAW_SERVER_HOST=0.0.0.0 # 新增Ollama配置 - OPENCLAW_LLM_PROVIDER=ollama - OPENCLAW_OLLAMA_BASE_URL=http://host.docker.internal:11434 # 关键!从容器内访问宿主机的服务 - OPENCLAW_OLLAMA_MODEL=qwen2.5:7b关键点:host.docker.internal是一个特殊的DNS名称,指向宿主机,允许容器内部访问宿主机上运行的服务(这里是Ollama)。如果你的OpenClaw和Ollama不在同一台机器,则需要使用宿主机的真实IP。
- 重启并验证:
docker-compose down然后docker-compose up -d。查看日志,应该能看到OpenClaw成功连接Ollama模型的提示。
4.2 自然语言触发:像和人说话一样下指令
现在,打开浏览器,访问OpenClaw的Web UI(通常是localhost:3000)。你应该能看到一个聊天界面。试着输入:
“请帮我设置一个每隔45分钟提醒我喝水休息一下的提醒,消息就说‘起来动动,看看远方’。”
接下来,神奇的事情会发生(如果一切配置正确):
- 意图理解:OpenClaw的LLM会分析你的这句话,识别出你的意图是“设置一个周期性提醒”。
- 技能匹配:LLM会根据已加载技能的描述(就是我们写在
@skill装饰器里的description),发现drink_water_reminder技能的描述(“设置一个周期性喝水提醒”)与当前用户意图高度匹配。 - 参数提取:LLM会从你的自然语言中提取出关键参数:
interval_minutes=45,reminder_message=“起来动动,看看远方”。 - 技能调用与执行:OpenClaw框架会调用
drink_water_reminder技能的execute方法,并传入提取到的参数。于是,我们的后台循环任务就启动了。 - 结果反馈:你会在聊天窗口收到技能
execute方法返回的消息:“喝水提醒已启动,每隔 45 分钟提醒一次。”
此时,查看OpenClaw容器的日志(docker-compose logs -f openclaw),你应该会看到:
INFO: Loaded skill: drink_water_reminder ... (用户请求日志) INFO: 开始喝水提醒任务,间隔 45 分钟。 INFO: 发送提醒:起来动动,看看远方。(每过45分钟,就会重复出现“发送提醒”的日志)。
4.3 从日志到真实通知:打通“最后一公里”
到目前为止,我们的提醒还只是停留在服务器日志里。如何让它真正弹到你的电脑桌面上?这就需要根据你的操作系统和偏好,修改技能中的sender函数实现。
方案一:集成系统通知(跨平台)你可以使用Python的plyer库。首先,需要在技能容器内安装这个包。修改docker-compose.yml,在openclaw服务下添加volumes挂载一个自定义的requirements.txt文件,并在启动命令中安装。
- 创建
requirements.txt,内容为plyer。 - 修改
docker-compose.yml:volumes: - ./data:/app/data - ./skills:/app/skills - ./requirements.txt:/app/requirements.txt # 挂载依赖文件 command: sh -c "pip install -r /app/requirements.txt && uvicorn openclaw.main:app --host 0.0.0.0 --port 3000 --reload" - 修改
skill.py中的_reminder_loop方法内的发送部分:
这种方式会在你的电脑上弹出原生系统通知。from plyer import notification # ... 在 _reminder_loop 函数内 ... try: # 发送系统通知 notification.notify( title='喝水提醒', message=message, app_name='OpenClaw健康助手', timeout=10 # 通知显示10秒 ) self.logger.info(f"系统通知已发送:{message}")
方案二:发送消息到即时通讯工具(如飞书、微信)这就是网络热词中“openclaw接入飞书/微信”所做的事情。这需要更复杂的配置,例如在飞书开放平台创建应用、获取权限、配置Webhook等。你需要编写另一个技能或修改现有技能,调用飞书或企业微信的API来发送消息。这超出了“一分钟手搓”的范围,但却是OpenClaw更强大的应用场景。
对于我们最简单的需求,方案一已经足够。它实现了从“自然语言指令”到“桌面物理通知”的完整闭环。你现在拥有了一个完全由自己控制、通过AI对话即可管理的智能喝水提醒器。
5. 优化、扩展与日常使用心得
一个能跑通的demo只是开始,要让这个小工具真正融入日常工作流,变得可靠、好用,还需要一些优化和深度思考。
5.1 技能功能的增强与健壮性提升
最初的技能版本还有很多可以改进的地方:
单例控制:目前的技能,每次对话说“设置提醒”,都会启动一个新的后台循环任务。如果你不小心说了两次,就会有两个重复的提醒任务在跑。我们需要在技能类里加一个类变量或利用上下文来标记任务状态,确保同一技能只运行一个实例。
class DrinkWaterReminderSkill(BaseSkill): _task = None # 类变量,存储后台任务引用 async def execute(self, inputs): if self._task and not self._task.done(): return {"success": False, "message": "喝水提醒任务已经在运行中。"} # ... 其他逻辑 ... self._task = asyncio.create_task(self._reminder_loop(...))提供停止命令:一个好的技能应该能随时被终止。我们可以创建另一个技能,或者扩展当前技能,通过一个特定的输入参数(如
action: “stop”)来取消_task。# 在 inputs 中增加一个 action 参数 inputs={ "action": { "type": "string", "description": "执行的操作,'start' 或 'stop'。", "required": False, "default": "start" }, # ... 其他参数 ... } # 在 execute 方法中根据 action 判断更丰富的提醒消息:可以让提醒消息不那么枯燥,比如随机从一句鼓励的话列表中选取一条,或者附带一些健康小贴士。
5.2 与现有工作流的无缝集成
- VS Code插件:如果你主要使用VS Code,可以探索是否有OpenClaw的VS Code插件,或者自己写一个简单的插件,在IDE内直接与OpenClaw对话设置提醒,体验更沉浸。
- 命令行调用:除了Web UI,OpenClaw通常也提供REST API。你可以写一个简单的Shell脚本
remind_me.sh,内容是用curl调用OpenClaw的API来触发技能,然后给这个脚本设置一个键盘快捷键(如Cmd+Shift+H),一键启动喝水提醒。 - 作息结合:将技能升级为“健康守护者”。除了喝水,还可以增加“久坐提醒”、“眼保健操提醒”、“颈椎活动提醒”。只需要修改循环逻辑,或者创建多个类似的技能。
5.3 我踩过的坑与核心经验
- Docker网络是首要难题:
OPENCLAW_SERVER_HOST=0.0.0.0和host.docker.internal这两个配置是解决大部分“连接不上”问题的关键。前者解决“外面访问不了容器”,后者解决“容器访问不了宿主机本地服务”。 - 技能加载失败静默无声:如果技能文件有语法错误,OpenClaw可能在启动时不会明确报错,只是默默跳过。一定要养成查看完整启动日志的习惯,搜索你的技能名是否出现在“Loaded skill”列表中。
- LLM的理解偏差:有时你对AI说“提醒我喝水”,它可能不会调用你的技能,而是自顾自地生成一段提醒文字。这说明你的技能描述(
description)不够精准,或者LLM的“技能调用”功能未正确启用。优化技能描述,确保其清晰、唯一地定义了功能边界(例如:“设置一个周期性的喝水提醒任务”)。 - 异步任务的资源管理:我们的技能创建了
asyncio.create_task,这是一个“fire and forget”的后台任务。如果OpenClaw服务重启,这个任务就消失了。对于需要持久化的定时任务,更好的做法是利用系统的定时任务(如cron)来定期调用OpenClaw的API,或者使用更专业的后台任务队列(如Celery),但这复杂度就上去了。对于喝水提醒这种轻量级、可接受中断的场景,当前方案是性价比最高的。
回过头看,从“渴了才喝水”到拥有一个AI智能体管家,整个过程的核心并不是多高深的技术,而是一种思维转变:将重复性的、容易被遗忘的自我管理需求,抽象成一个可编程、可对话的自动化技能。OpenClaw这样的框架,降低了实现这一转变的门槛。你不需要是全栈工程师,只需要一点Python基础,加上清晰的思路,就能打造贴合自己习惯的数字助手。
这个“一分钟手搓”的喝水提醒器,就是一个最好的起点。它简单,但完整地走通了从想法、到部署、到技能开发、再到自然语言交互的整个链路。当你看到第一个由你自己描述、自己实现、自己触发的AI提醒出现时,那种感觉,比解决一个生产Bug还要美妙。因为它真正服务于你,服务于你的健康。