
1. 项目概述从零到一在QClaw上构建你的专属AI记账员最近在捣鼓鹅厂开源的OpenClaw生态特别是它的核心应用QClaw这玩意儿本质上是一个AI Agent的集成开发与运行平台。简单来说它让你能像搭积木一样把各种AI能力Skill和业务流程Agent组合起来创造出能自主完成复杂任务的智能体。我琢磨着与其看官方文档里那些“高大上”的案例不如自己动手搞一个最贴近生活、最能检验平台易用性的项目——一个能理解我自然语言指令、自动帮我记账的智能小助手。这个想法源于一个很实际的痛点每天咖啡、午餐、网购零零碎碎的花销手动记吧太麻烦用传统记账App吧又得自己分类、输入金额坚持不了几天。如果有个AI我直接对它说“今天中午吃了碗牛肉面花了28”或者把外卖订单截图扔给它它就能自动解析、分类并记录到我的账本里那该多省心。而QClaw正好提供了实现这个想法的土壤。它内置的Agent框架负责理解和规划任务丰富的Skill库或自己开发Skill则提供了具体的执行能力比如调用大模型分析文本、操作数据库、发送消息通知等。通过这个“智能记账小助手”项目我们不仅能深入理解OpenClaw/QClaw中Agent、Skill、工作流编排这些核心概念的实际应用还能掌握从需求分析、Skill开发、Agent配置到最终部署上线的完整流程。无论你是对AI Agent开发感兴趣的开发者还是想为自己或团队打造个性化效率工具的产品、运营同学这个实战案例都能提供一条清晰的路径。接下来我就把自己从构思到实现的过程包括踩过的坑和总结的经验毫无保留地分享出来。2. 核心架构与设计思路拆解在动手写代码之前理清整个智能体的运作逻辑至关重要。我们不能指望直接告诉AI“帮我记账”它就能完美执行。需要把“智能记账”这个模糊的需求拆解成QClaw平台能理解和执行的标准化模块。2.1 智能记账的业务流程分解一个完整的智能记账流程可以抽象为“感知-理解-决策-执行”四个环节这正好对应了Agent的典型思考模式。感知Input小助手如何接收我的记账指令这里有多种渠道自然语言输入我在QClaw的对话界面直接输入文本例如“记录下午买咖啡拿铁一杯32元。”文件/图片上传我上传一张外卖小票、电子账单截图或购物凭证的图片。第三方消息接入将来可以扩展为接入飞书、钉钉或微信在这些聊天工具里直接小助手记账。理解Comprehension这是AI大模型发挥核心作用的地方。我们需要一个Skill专门负责解析输入内容。对于文本输入需要从中提取关键实体金额32、商品/服务拿铁、类别餐饮-咖啡、时间下午/或默认为当前时间、支付方式可选项如微信。对于图片输入则需要先进行OCR光学字符识别提取文字再进行上述的实体提取。这里可以串联两个Skill一个OCR Skill一个文本解析Skill。决策Planning ActionAgent根据理解的结果决定下一步做什么。在我们的场景里决策相对简单将提取的结构化数据持久化存储到数据库中。但设计上要留有扩展性比如如果解析失败Agent应该决定回复用户“无法识别请重新描述”如果识别出是“查询本月总支出”这类指令则应触发另一个查询数据库的Skill。执行Execution调用具体的Skill来完成决策。核心是一个数据存储Skill它负责连接数据库如MySQL、SQLite或腾讯云开发TCB执行INSERT操作。此外还可以有一个通知反馈Skill在记账成功后给用户发送一条确认消息比如“已记录餐饮-咖啡32元”。2.2 QClaw中的组件映射Agent、Skill与工作流理解了业务流程后我们需要将其映射到QClaw的核心组件上。Skill技能这是平台最小的可执行单元相当于一个封装好的函数或微服务。对于本项目我们需要开发或配置以下Skill文本解析Skill调用大模型API如DeepSeek、GPT、文心一言通过精心设计的Prompt从用户文本中提取结构化账目信息。这是智能的核心。OCR识别Skill调用OCR服务如腾讯云OCR、百度AI开放平台OCR将图片中的文字提取出来输出给文本解析Skill。数据存储Skill接收结构化的账目数据将其写入数据库。这里涉及数据库连接、SQL执行或ORM操作。反馈通知Skill可选。用于向用户发送操作结果。Agent智能体Agent是大脑它本身不干具体活但负责统筹。它根据用户的输入Intent意图和上下文Context决定调用哪一个或哪几个Skill并按照什么顺序工作流来调用。我们的“智能记账小助手”本身就是一个Agent。意图识别Agent需要判断用户是想“记账”还是“查询”。初期我们可以用简单的关键词匹配如输入中包含“记录”、“花了”、“买了”则触发记账流程后期可以引入更复杂的NLU自然语言理解模型。工作流编排这是Agent设计的精髓。例如对于图片输入工作流是接收输入-调用OCR Skill-将OCR结果传给文本解析Skill-调用数据存储Skill-调用反馈Skill。对于纯文本输入则跳过OCR步骤。QClaw提供了图形化或YAML配置的方式来编排这个流程。记忆与上下文Memory/Context为了让对话更连贯Agent需要记住上下文。例如用户说“昨天那杯咖啡”Agent需要能关联到之前的对话中提到的咖啡金额和类别。QClaw通常提供了会话级别的上下文管理能力我们在设计Skill时可以通过输入输出参数来传递这些上下文信息。2.3 技术选型与工具考量在具体实现前有几个关键的技术选型需要确定这直接影响到开发的复杂度和最终效果。大模型选型文本解析Skill的效果好坏90%取决于大模型的能力和Prompt工程。考虑到国内网络的便利性和成本我优先选择了国内可直接调用的模型。DeepSeek性价比极高API稳定在中文场景下表现优秀非常适合作为本项目的主力模型。腾讯混元/文心一言作为鹅厂生态内的选择集成可能更顺畅但需要关注API的开放程度和调用成本。Prompt设计要点给模型的指令必须清晰。例如“你是一个记账助手请从用户输入中提取消费记录。严格按照JSON格式输出包含字段amount数字单位元 item商品/服务名 category一级分类餐饮、交通、购物、娱乐、生活、其他 date字符串格式YYYY-MM-DD如果未提及则用今天日期 payment支付方式微信、支付宝、现金、银行卡、其他。如果无法提取则输出 {“error”: “无法识别消费记录”}。”数据库选型数据存储需要轻量、易部署。SQLite本地文件数据库无需安装服务器零配置非常适合个人使用的单机版小助手。数据文件可以直接管理。MySQL/PostgreSQL如果需要多端同步或未来考虑多用户可以选择这类关系型数据库但需要额外部署。腾讯云开发TCB如果希望数据云端存储且与微信小程序等生态打通TCB的云数据库是一个无缝集成的选择。不过对于初版个人项目SQLite的简洁性更具吸引力。OCR服务选型腾讯云OCR通用印刷体识别精度高有免费额度与QClaw同属腾讯云生态接入方便。百度OCR同样提供高精度服务免费额度策略略有不同。本地OCR引擎如PaddleOCR如果对数据隐私极度敏感可以考虑在本地部署PaddleOCR但这会显著增加部署复杂度和资源消耗。对于V1.0版本建议先使用云服务API。注意在QClaw中配置这些外部服务的API密钥时务必使用平台提供的“密钥管理”或“环境变量”功能切勿将密钥硬编码在Skill代码或配置文件中以防泄露。3. 实战开发从Skill编写到Agent组装理论清晰后我们进入动手环节。我会以最核心的文本解析Skill和数据存储Skill为例展示如何在QClaw中创建一个完整的Skill并最终组装成可运行的Agent。3.1 开发文本解析SkillPython HTTP SkillQClaw支持多种Skill开发方式最常见的是通过Python编写一个HTTP服务QClaw的Agent会向这个服务的特定端点发送请求。我们首先在项目目录下创建这个Skill。1. 创建项目结构my_accounting_agent/ ├── skills/ │ ├── text_parser_skill/ │ │ ├── app.py # Skill主逻辑 │ │ ├── requirements.txt # Python依赖 │ │ └── Dockerfile # 容器化部署文件 │ └── db_writer_skill/ │ ├── app.py │ ├── requirements.txt │ └── Dockerfile ├── agent_config.yaml # Agent的编排配置文件 └── docker-compose.yml # 可选用于本地编排所有服务2. 编写文本解析Skill的核心逻辑 (skills/text_parser_skill/app.py)from flask import Flask, request, jsonify import os import requests import json from datetime import datetime app Flask(__name__) # 从环境变量读取大模型API配置 DEEPSEEK_API_KEY os.getenv(“DEEPSEEK_API_KEY”) DEEPSEEK_API_URL “https://api.deepseek.com/v1/chat/completions” MODEL_NAME “deepseek-chat” # 根据实际使用模型调整 def parse_expense_with_llm(user_text): 调用大模型解析用户文本 headers { “Authorization”: f“Bearer {DEEPSEEK_API_KEY}”, “Content-Type”: “application/json” } # 精心设计的Prompt引导模型输出结构化JSON system_prompt “““你是一个专业的记账助手。你的任务是从用户输入的自然语言中精确提取消费记录信息。 请严格按照以下JSON格式输出不要有任何额外的解释或标记 { “amount”: 数字单位元, “item”: “商品或服务名称“, “category”: “一级分类仅限餐饮、交通、购物、娱乐、生活、其他“, “date”: “字符串格式YYYY-MM-DD如果未提及则使用今天日期“, “payment”: “支付方式仅限微信、支付宝、现金、银行卡、其他“ } 如果输入中完全无法提取出消费记录则返回{“error”: “无法识别消费记录”} ””” payload { “model”: MODEL_NAME, “messages”: [ {“role”: “system”, “content”: system_prompt}, {“role”: “user”, “content”: user_text} ], “temperature”: 0.1, # 低随机性确保输出稳定 “response_format”: { “type”: “json_object” } # 强烈建议要求JSON格式输出 } try: response requests.post(DEEPSEEK_API_URL, headersheaders, jsonpayload, timeout30) response.raise_for_status() result response.json() llm_output result[“choices”][0][“message”][“content”] # 解析模型返回的JSON字符串 parsed_data json.loads(llm_output) # 验证必要字段 if “error” in parsed_data: return {“success”: False, “error”: parsed_data[“error”]} # 补充默认值如果未提供日期则用今天 if parsed_data.get(“date”) in [None, “”, “未提及”]: parsed_data[“date”] datetime.now().strftime(“%Y-%m-%d”) return {“success”: True, “data”: parsed_data} except requests.exceptions.RequestException as e: return {“success”: False, “error”: f“API调用失败: {str(e)}”} except (json.JSONDecodeError, KeyError) as e: return {“success”: False, “error”: f“解析模型响应失败: {str(e)}”} app.route(‘/health’, methods[‘GET’]) def health_check(): 健康检查端点QClaw会调用此接口确认Skill存活 return jsonify({“status”: “healthy”}), 200 app.route(‘/parse’, methods[‘POST’]) def parse_expense(): 主处理端点QClaw Agent会将用户输入传递到此 data request.json if not data or ‘text’ not in data: return jsonify({“success”: False, “error”: “缺少输入文本参数 ‘text’”}), 400 user_input data[‘text’] # 可以在此处添加简单的输入清洗或预处理 result parse_expense_with_llm(user_input) return jsonify(result) if __name__ ‘__main__’: port int(os.getenv(“PORT”, 5000)) app.run(host‘0.0.0.0’, portport)3. 创建依赖文件与Dockerfilerequirements.txt:flask2.3.0 requests2.31.0Dockerfile:FROM python:3.9-slim WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY . . CMD [“python”, “app.py”]实操心得在Prompt中明确要求JSON格式输出 (response_format:json_object)并给出严格的字段枚举如category和payment的可选值能极大提高模型返回数据的规范性和可用性减少后续处理的错误。同时一定要在代码中做好异常处理和默认值补充比如自动填充当天日期。3.2 开发数据存储SkillSQLite版接下来我们创建负责持久化的Skill。这里以SQLite为例。skills/db_writer_skill/app.py:from flask import Flask, request, jsonify import sqlite3 import os from datetime import datetime import logging app Flask(__name__) logging.basicConfig(levellogging.INFO) DB_PATH os.getenv(“DB_PATH”, “/data/accounting.db”) # 数据库文件路径可通过环境变量配置 def init_database(): 初始化数据库创建表如果不存在 conn sqlite3.connect(DB_PATH) cursor conn.cursor() cursor.execute(“”“ CREATE TABLE IF NOT EXISTS expenses ( id INTEGER PRIMARY KEY AUTOINCREMENT, amount REAL NOT NULL, item TEXT NOT NULL, category TEXT NOT NULL, date TEXT NOT NULL, payment TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) ”“”) conn.commit() conn.close() logging.info(f“Database initialized at {DB_PATH}”) app.route(‘/health’, methods[‘GET’]) def health_check(): return jsonify({“status”: “healthy”}), 200 app.route(‘/record’, methods[‘POST’]) def record_expense(): 接收结构化数据并写入数据库 data request.json if not data: return jsonify({“success”: False, “error”: “No data provided”}), 400 required_fields [‘amount’, ‘item’, ‘category’, ‘date’] for field in required_fields: if field not in data: return jsonify({“success”: False, “error”: f“Missing required field: {field}”}), 400 try: conn sqlite3.connect(DB_PATH) cursor conn.cursor() cursor.execute(“”“ INSERT INTO expenses (amount, item, category, date, payment) VALUES (?, ?, ?, ?, ?) ”“”, (data[‘amount’], data[‘item’], data[‘category’], data[‘date’], data.get(‘payment’, ‘其他’))) conn.commit() record_id cursor.lastrowid conn.close() logging.info(f“Record inserted: ID{record_id}, Item{data[‘item’]}, Amount{data[‘amount’]}”) return jsonify({“success”: True, “id”: record_id, “message”: “记录成功”}) except sqlite3.Error as e: logging.error(f“Database error: {e}”) return jsonify({“success”: False, “error”: f“数据库写入失败: {str(e)}”}), 500 except Exception as e: logging.error(f“Unexpected error: {e}”) return jsonify({“success”: False, “error”: f“服务器内部错误: {str(e)}”}), 500 # 应用启动时初始化数据库 with app.app_context(): init_database() if __name__ ‘__main__’: port int(os.getenv(“PORT”, 5001)) app.run(host‘0.0.0.0’, portport)这个Skill提供了/record端点接收JSON格式的账目数据并将其插入SQLite数据库。它同样有一个/health端点供QClaw进行健康检查。3.3 在QClaw中配置与编排AgentSkill服务开发完成后我们需要在QClaw的Web管理界面或通过配置文件中将它们“注册”并串联起来。1. 部署Skill服务首先需要将两个Skill服务运行起来。可以在本地用Docker运行# 在text_parser_skill目录下 docker build -t accounting-text-parser . docker run -d -p 5000:5000 -e DEEPSEEK_API_KEYyour_key_here accounting-text-parser # 在db_writer_skill目录下 docker build -t accounting-db-writer . docker run -d -p 5001:5001 -v $(pwd)/data:/data -e DB_PATH/data/accounting.db accounting-db-writer确保服务启动后可以通过curl http://localhost:5000/health和curl http://localhost:5001/health访问到健康检查接口。2. 在QClaw中创建Skill登录QClaw管理后台找到“技能管理”或“Skill Center”。创建文本解析Skill名称expense_text_parser类型HTTP端点URLhttp://host.docker.internal:5000/parse(如果在同一台机器上) 或你的服务器公网IP/域名。方法POST输入参数映射配置将Agent接收到的用户输入 ({{input.text}}) 映射到Skill期望的text字段。输出参数映射配置将Skill返回的data对象包含amount, item等字段提取出来供后续Skill使用。创建数据存储Skill名称expense_db_writer类型HTTP端点URLhttp://host.docker.internal:5001/record方法POST输入参数映射配置将上游解析出的data对象整个作为请求体发送。3. 编排Agent工作流创建一个新的Agent例如命名为smart_accounting_assistant。触发器设置为“手动触发”或“消息触发”对应QClaw的聊天输入框。工作流设计使用图形化编排工具。开始节点接收用户输入user_input。条件判断节点可选判断输入是文本还是图片。如果是图片先跳转到OCR Skill需额外创建。这里我们先实现文本流程。文本解析Skill节点调用expense_text_parser输入为user_input。判断节点检查expense_text_parser的输出中success是否为true。如果为false跳转到“失败回复”节点。数据存储Skill节点调用expense_db_writer输入为解析成功的data。成功回复节点组装成功消息如“已成功记录消费{{data.item}}{{data.amount}}元类别{{data.category}}”。失败回复节点返回解析失败或存储失败的错误信息。通过这样的拖拽和连线一个具备基本逻辑的智能记账Agent就配置完成了。保存并发布后你就可以在QClaw的聊天界面中这个Agent或者直接向它发送消息进行测试。4. 效果测试、优化与问题排查Agent配置好后真正的挑战才刚刚开始让它稳定、可靠、聪明地工作。这部分是文档里不会写的“踩坑实录”。4.1 测试用例设计与效果评估不要用模糊的句子测试要设计有代表性的用例覆盖边界情况。标准用例“中午吃麦当劳花了45.5元”。期望正确提取金额45.5商品“麦当劳”类别“餐饮”日期为今天。隐含类别“充了100话费”。期望金额100商品“话费”类别“生活”。复杂描述“昨天下午在淘宝给女朋友买了一条裙子288用支付宝付的”。期望金额288商品“裙子”类别“购物”日期为昨天支付方式“支付宝”。模糊/错误用例“我今天心情很好”。期望返回错误信息“无法识别消费记录”。图片测试上传一张外卖订单截图测试OCR解析的串联流程是否通畅。实测结果与调优 初期测试我发现模型有时会把“奶茶”分到“其他”而不是“餐饮”有时日期格式不对。这就需要迭代优化Prompt细化分类在Prompt中给“餐饮”增加例子“包括正餐、零食、咖啡、奶茶、酒水等”。强化格式在Prompt中强调“date字段必须且只能是YYYY-MM-DD格式”。处理歧义对于“充话费”模型可能纠结于这是“消费”还是“充值”。在Prompt中明确“话费充值、水电煤缴费归类为‘生活’”。 每次Prompt修改后都需要用一批测试用例重新验证效果。4.2 常见问题与排查技巧在实际运行中你会遇到各种各样的问题。下面是一个快速排查清单问题现象可能原因排查步骤与解决方案Agent调用Skill超时或失败1. Skill服务未启动或崩溃。2. 网络不通容器间/跨主机。3. Skill的/health端点未正确响应。1. 检查Skill容器日志docker logs container_id。2. 在QClaw服务器上使用curl直接测试Skill的health端点。3. 确保QClaw配置的Skill URL正确如果是Docker环境使用host.docker.internal或服务名如果使用docker-compose。文本解析Skill返回“无法识别”1. 大模型API密钥错误或额度用尽。2. 用户输入过于模糊。3. Prompt设计有缺陷。1. 检查API密钥环境变量是否正确设置查看模型服务商后台的调用日志和余额。2. 在Skill代码中增加调试日志打印出发生错误的原始输入和模型原始响应。3. 将出错的用例加入到Prompt中作为反例指导模型。例如“如果输入是‘我今天很开心’应返回错误。”数据存储成功但数据库查不到1. 数据库文件权限问题。2. 插入操作出错但被捕获未向上游返回错误。3. 事务未提交。1. 检查运行Skill容器的用户对数据库文件所在目录是否有读写权限。2. 在db_writer_skill的代码中仔细检查异常处理逻辑确保任何错误都通过success: false返回。3. 确认SQLite连接在执行INSERT后执行了commit()。工作流逻辑错误例如该调用OCR时没调用Agent的条件判断配置错误。在QClaw的Agent编排界面仔细检查每个判断节点的条件表达式。例如判断是否为图片的条件可能是{{input.type}} ‘image’确保变量名与触发器传递的参数一致。使用调试模式查看每个节点的输入输出。处理图片时流程中断1. OCR Skill未正确开发或部署。2. 图片上传后QClaw未正确将文件内容或URL传递给下游Skill。1. 单独测试OCR Skill确保它能接收图片并返回文本。2. 查阅QClaw文档了解文件类型输入的处理规范。可能需要配置一个“文件预处理”节点将图片二进制流转换为OCR Skill可接受的Base64编码或临时文件URL。避坑指南日志日志还是日志在每个Skill的关键步骤接收请求、调用外部API前、得到结果后、发生异常时都打上详细的日志。使用结构化的JSON日志格式方便用ELK等工具收集查询。当问题出现时清晰的日志链是定位问题的唯一捷径。4.3 性能优化与扩展思考当基本功能跑通后可以考虑以下优化和扩展方向让你的小助手更加强大记忆与上下文让Agent记住对话上下文。例如用户说“和刚才一样”Agent能引用上一条消费记录。这需要在QClaw中启用Agent的“记忆”功能并在Skill间传递session_id或user_id将数据临时存储或关联查询。数据查询与分析开发一个query_skill支持自然语言查询如“我这个月吃饭花了多少钱”。这需要另一个大模型调用将自然语言转换为SQL查询语句Text-to-SQL然后执行并返回结果。这是一个更有挑战性但也更有价值的Skill。多模态输入除了图片未来可以支持语音输入。增加一个speech_to_text_skill调用语音识别ASR服务将语音转为文本再流入现有的文本解析流程。部署与监控将Skill容器化后使用Kubernetes或简单的docker-compose进行编排管理。为每个Skill添加Prometheus指标暴露端点监控其响应时间、成功率和错误率。成本控制大模型API调用是主要成本。可以在文本解析Skill中加入缓存机制对于完全相同的用户输入直接返回缓存结果。同时设置每天/每月的调用额度预警。通过这个项目你不仅得到了一个实用的智能记账工具更重要的是走通了一个标准的AI Agent应用开发闭环需求分析 - 架构设计 - Skill开发 - Agent编排 - 测试部署 - 迭代优化。这个模式可以复用到无数其他场景比如智能客服、自动化周报生成、会议纪要助手等等。QClaw这样的平台降低了AI Agent的开发门槛但如何设计一个真正解决实际问题的、鲁棒的智能体依然需要我们深入思考业务逻辑和技术细节。