1. 先搞清楚OpenClaw 的 Skill 到底是什么我最近把用了大半年的 OpenClaw 个人助手从啥都能聊两句升级成了能正经管钱的私人金融顾问。这个改造的核心就是给它装了一个金融 Skill——没错就是标题里说的那个东西。先别急着划走我先把背景说清楚因为这决定了你后面看代码时的心态完全不一样。OpenClaw 是一个开源的个人 AI 助手框架和市面上那些只能在云端跑的对话机器人相比它的特点在于本地优先、模块化、可编程。你可以通过挂载各种 Skill 给它加技能每个 Skill 本质上是一组指令、脚本、提示词和配置的集合告诉助手遇到什么数据、按什么逻辑、产出什么结果。我最早只是拿它管日程、抓早报后来发现它最大的价值不在聊天而在把你的私有数据变成可执行的结构化信息。而金融数据恰好是所有人最头疼、最需要隐私、又最需要持续追踪的那类东西。很多人一听到金融顾问就想到荐股、预测行情我的理解完全反过来一个靠谱的私人金融顾问首先是个靠谱的账房先生。它不需要天天跟你聊 K 线它需要做的是——每个月你的钱去了哪、现金流稳不稳、哪类开销在悄悄膨胀、你的储蓄率达标没有、有没有该配置却没配置的应急资金。想清楚这一点金融 Skill 的架构方向就定了先记账、再分类、然后出报告、最后给建议。这也决定了下面的整个技术方案。这套方案适合谁如果你在用 OpenClaw 或类似框架管理个人事务又不想把自己的收支数据交给第三方记账 App更不想每月花两小时手动导账单、做 Excel那这篇内容就是写给你的。哪怕你现在用的不是 OpenClaw只是想把记账 财务分析这套流程自动化里面的数据建模思路、脚本逻辑、踩坑记录同样能直接抄。1.1 为什么需要一个金融 Skill而不是现成记账 App市面上记账 App 不少但我的诉求它们都满足不了第一我的账单分散在储蓄卡、信用卡、若干支付平台每个平台导出的 CSV 格式都不一样第二我希望助手能直接读懂我的账比如这个月餐饮超支了 18%主要来自外卖订单这种结论而不是甩给我四十个饼图第三数据必须留在我自己的机器上我不接受把家庭流水传到某个服务商的服务器里做智能分析。OpenClaw 的本地优先特性刚好对齐这个需求Skill 机制则让我能把解析、分类、分析、汇报整条链路写成可复用的模块账单一变我只改一个插件脚本而不是重开一个 App。1.2 这个金融 Skill 具体能干什么我用表格把最终实现的功能列一下免得后面看得云里雾里功能模块输入输出核心逻辑账单导入银行 / 支付平台导出的 CSV标准化交易记录字段映射 去重自动分类标准化交易记录带类目的账单关键词规则 LLM 兜底月度报告当月账单 历史数据支出分析 / 储蓄率 / 趋势预警指标计算 提示词生成目标追踪用户设定的预算 / 目标达标进度与偏差提醒简单线性对比自然语言问答用户问我上月外卖花了多少具体数字 上下文说明检索式查询 数据约束2. 整体设计一个私人金融顾问该有的模块我一开始犯过一个典型的错误一上来就想着接各种行情接口、做投资组合分析结果账单还没整理利索整个 Skill 就成了一个华而不实的摆设。后来我把思路翻过来——先做账房先生再做顾问。下面这几个模块是我认为绕不开的地基。2.1 数据入口CSV 解析与标准化金融 Skill 的第一步是把数据接进来。不同平台的账单格式差异极大但字段基本逃不出交易时间、对方户名、收支方向、金额、余额、交易备注这几类。我的做法是做一层标准化中间层也就是把各种 CSV 映射成一个统一结构字段固定为date、payee、direction、amount、category、raw、source。后面所有分析和统计都只认这个结构新增平台时只需要写一个新的解析器不影响上层逻辑。这一步最容易被忽略的是数据清洗的正确性优先于完整度。我宁可在解析阶段多滤掉几条无法识别的记录也不允许脏数据混进数据库因为后面所有报告和建议都建立在这些记录之上一条错账比十条缺账更危险。2.2 分类引擎规则优先模型兜底交易分类是金融顾问的阅读理解能力。我见过一些方案让大模型对每一笔交易做分类成本高、速度慢还可能因为每次都重新推理导致同样的交易分到不同类目。成熟的思路是分层处理先用本地关键词规则库做精确分类命中不了的再用大模型做兜底分类然后把结果写回规则库形成越用越准的闭环。举个例子交易备注里带美团的直接归类到餐饮-外卖带中石化的归类到交通-加油。规则命中率能达到七成以上剩下的模糊交易才值得动用模型。这样既控制了成本也保证了分类的稳定性。2.3 报告层从记账员到顾问的跃迁数据收集和分类只是记账员的活顾问的价值体现在输出层。我把报告拆成固定指标和自然语言解读两层。固定指标包括月总支出、分类占比、环比涨幅、储蓄率、预算执行率这些用脚本算好自然语言解读则交给大模型但它只能基于我已经算好的结构化指标生成不能自由发挥去碰原始明细。这个约束非常关键后面我会再展开。2.4 隐私边界所有数据留在本机金融数据是隐私级别最高的个人数据所以我做了一个硬性规定任何情况下账单原始数据不得发送给云端模型唯一允许出本地的是每月报告所需的聚合指标。做法很简单——本地跑一个轻量模型用于分类兜底或者把聚合结果脱敏后再调用远程模型。也许有人觉得这个限制太严格但项目实际跑下来这个坚持反而让我敢把三年的历史账单都导进去分析质量也因此高了很多。3. 实操过程从零把金融 Skill 跑起来下面我开始讲实操。我会用一套简化但完整的示例来说明整个实现过程你可以照着跑通再按自己的平台账单做适配。3.1 Skill 目录结构与清单文件OpenClaw 的 Skill 本质上是一个按约定组织的目录我得先搭好脚手架financial-advisor/ ├── skill.yaml # Skill 清单声明名称、权限、入口 ├── scripts/ │ ├── import_csv.py # 账单解析与标准化 │ ├── categorize.py # 规则分类 LLM 兜底 │ ├── build_report.py # 指标计算与报告生成 │ └── query.py # 自然语言问答的后端查询 ├── data/ │ ├── db.sqlite3 # 本地数据库 │ └── rules.yaml # 分类规则库 ├── prompts/ │ ├── report_prompt.txt # 月度报告生成 │ └── classify_prompt.txt # 兜底分类提示词 └── assets/ └── templates/ # 报告模板skill.yaml 是整个 Skill 的入口描述OpenClaw 会读取它来注册技能。我的配置长这样name: financial-advisor version: 0.3.0 description: 私人金融顾问账单解析、自动分类、月度报告和个性化财务建议 author: local-user permissions: filesystem: - data/ - scripts/ network: # 只允许访问本地模型服务或脱敏后的远程接口 allow: [] triggers: - 帮我记账 - 看下这个月的账单 - 我的钱花哪了 - 报告 entry: scripts/import_csv.py有几个细节说一下trigger 的声明决定了用户什么时候会唤起这个 Skill语句要覆盖高频意图permissions 里的 filesystem 限制是为了防止 Skill 越权读取其他目录安全边界一开始就要划好。3.2 账单解析脚本的实现细节CSV 解析是整个链路里最枯燥、也最容易翻车的地方。我拿一个支付平台导出的账单片段举例交易时间,交易类型,交易对方,商品,收/支,金额(元),支付方式,当前状态,订单号 2025-03-02 12:33:10,商户消费,某某超市,日用百货,支出,86.50,余额宝,支付成功,2025030212331234 2025-03-02 18:05:22,商户消费,外卖平台,餐饮美食,支出,32.00,余额宝,支付成功,2025030218055678 2025-03-03 09:12:08,转账,同事某某,转账,收入,1200.00,余额宝,支付成功,2025030309129876解析脚本要做的事情是把这些字段映射到统一结构我在写这份代码时特别注意了三件事文件名编码、表头跳过、字段清洗。#!/usr/bin/env python3 从支付平台导出的 CSV 导入标准化交易库。 import csv import sqlite3 from pathlib import Path from datetime import datetime STANDARD_FIELDS [date, payee, direction, amount, category, raw, source] def parse_payplatform_csv(path: Path) - list[dict]: 解析平台账单返回标准化交易记录列表。 records [] # 注意某些平台导出的是 GBK 编码直接 open 会乱码 # 我用 errorsreplace 做兜底但不能根治后面排查章节会细讲 with open(path, r, encodingutf-8, errorsreplace) as f: reader csv.reader(f) # 跳过文件头若干行真实账单前几行往往是说明文字 header_line None for i, row in enumerate(reader): if row and row[0] 交易时间: header_line i break # 这里简化处理直接重置文件指针并逐行解析 f.seek(0) for i, row in enumerate(reader): if i header_line: continue if len(row) 9 or not row[0]: continue raw_date, txn_type, payee, product, direction, amount, pay_method, status, order_id row[:9] if status ! 支付成功: continue # 统一时间格式保留到分钟即可 try: dt datetime.strptime(raw_date, %Y-%m-%d %H:%M:%S) except ValueError: dt datetime.strptime(raw_date, %Y-%m-%d %H:%M) records.append({ date: dt.strftime(%Y-%m-%d %H:%M), payee: payee.strip(), direction: 支出 if direction 支出 else 收入, amount: abs(float(amount)), category: 未分类, raw: | .join([payee, product, order_id]), source: path.stem, }) return records def upsert_records(records: list[dict], db_path: Path) - int: 写入数据库按订单号去重。 conn sqlite3.connect(db_path) inserted 0 for rec in records: cur conn.execute( SELECT 1 FROM transactions WHERE raw ? AND date ? AND amount ?, (rec[raw], rec[date], rec[amount]), ) if cur.fetchone(): continue conn.execute( INSERT INTO transactions (date, payee, direction, amount, category, raw, source) VALUES (:date, :payee, :direction, :amount, :category, :raw, :source), rec, ) inserted 1 conn.commit() conn.close() return inserted if __name__ __main__: records parse_payplatform_csv(Path(data/monthly_bill.csv)) inserted upsert_records(records, Path(data/finance.sqlite3)) print(f解析完成{len(records)} 条有效记录新增 {inserted} 条)这里最重要的一行是去重逻辑我用原始备注 日期 金额三要素判重而不是依赖平台给的订单号因为有些平台导出的订单号会缺失或者你合并了多个平台的账单单靠单号反而会漏。这个逻辑实测下来误杀率极低能挡住 90% 以上的重复导入问题。3.3 分类规则的配置与调整分类规则我放在一个 YAML 文件里结构是类目 - 关键词列表。规则要按命中优先级排列因为超市既可能是日用百货也可能被归入餐饮我按实际商户优先的原则决定顺序。餐饮: 外卖: [美团, 饿了么, 外卖, 配送费] 堂食: [餐厅, 餐饮, 小吃, 咖啡, 茶饮] 交通: 加油: [中石化, 中石油, 加油站] 出行: [滴滴, 地铁, 公交, 停车, etc] 日用: 超市: [超市, 便利店, 百货] ...分类脚本的核心逻辑是逐条读交易记录先跑本地规则命中不了再进兜底。#!/usr/bin/env python3 交易分类规则优先模型兜底。 import re import sqlite3 import yaml from pathlib import Path def load_rules(path: Path) - dict: with open(path, r, encodingutf-8) as f: return yaml.safe_load(f) def apply_rule(text: str, rules: dict) - str | None: 按优先级返回第一个命中的类目例如 餐饮-外卖。 for category, subcategories in rules.items(): for sub, keywords in subcategories.items(): for kw in keywords: if kw in text: return f{category}-{sub} return None def llm_fallback(text: str) - str: 调用本地模型或远程脱敏接口做兜底分类。 # 这里省略具体调用细节只给出建议约束 # 1. 传入的 text 只保留商户名和商品名不包含金额和日期 # 2. 输出必须限定在预设类目集合内 return 未分类 def run_classification(db_path: Path, rules_path: Path) - None: rules load_rules(rules_path) conn sqlite3.connect(db_path) rows conn.execute( SELECT id, raw, category FROM transactions WHERE category 未分类 ).fetchall() for tid, raw, _ in rows: category apply_rule(raw, rules) if category is None: category llm_fallback(raw) conn.execute(UPDATE transactions SET category ? WHERE id ?, (category, tid)) conn.commit() conn.close() if __name__ __main__: run_classification( Path(data/finance.sqlite3), Path(data/rules.yaml), )这个分层结构的好处我到第三个月才真切感受到规则库随用随增模型调用量在稳定下降分类准确率却只升不降。头两周会频繁遇到咦这比交易怎么没分类的情况这时候别急着加规则先看看能不能用规则描述清楚描述不清楚的才值得劳驾模型。3.4 月度顾问报告的 Prompt 设计报告输出是用户能直接感知的部分Prompt 设计直接影响顾问的专业感。我的做法是先把所有指标用 Python 聚合好生成一段纯数据的摘要再让模型基于摘要写解读。# build_report.py 的关键部分简化 import sqlite3 import json def compute_metrics(year_month: str) - dict: conn sqlite3.connect(data/finance.sqlite3) rows conn.execute( SELECT category, direction, amount FROM transactions WHERE date LIKE ? , (f{year_month}%,)).fetchall() expense [r for r in rows if r[1] 支出] income [r for r in rows if r[1] 收入] total_spent round(sum(r[2] for r in expense), 2) total_income round(sum(r[2] for r in income), 2) by_cat {} for cat, _, amt in expense: top cat.split(-)[0] by_cat[top] by_cat.get(top, 0) amt saving_rate round((total_income - total_spent) / total_income, 4) if total_income else 0 return { year_month: year_month, total_income: total_income, total_spent: total_spent, saving_rate: saving_rate, top_categories: sorted(by_cat.items(), keylambda x: -x[1])[:5], by_category: by_cat, }然后把 metrics 转成 JSON 塞进 PromptPrompt 模板长这样你是一个严谨的个人财务顾问。以下数据来自用户的本地账单已经过脚本计算真实可靠。 请基于数据生成一份月度财务简报要求 1. 突出最重要的 3 个事实不要逐项罗列 2. 如有环比数据指出变化最大的类目 3. 给 1-2 条可执行的建议建议必须能直接从数据中推出 4. 禁止编造任何数据禁止给出模糊理财建议如建议投资这类空话 数据JSON {metrics_json}为什么非要先算后写因为我吃过亏。最早我让模型直接读账单明细生成报告它煞有介事地写本月支出较上月下降 20%实际上户均消费涨了。模型不仅会算错还会一本正经地编出根本不存在的统计数字。现在所有数字都是脚本算好的模型只负责措辞和结构这个改动之后报告质量是肉眼可见地稳定了。4. 常见问题与排查技巧实录金融 Skill 跑起来不难跑稳是真的要打磨。我在三个多月里踩了不少坑挑几个最有价值的记录下来希望能帮你少走弯路。4.1 账单导出的编码与格式坑国内平台的 CSV 导出编码五花八门有 UTF-8 with BOM 的有 GBK 的还有混着 GB18030 的。我最初直接用 utf-8 打开结果一片乱码分类自然全错。排查思路是不要靠猜先用工具检测编码file -i monthly_bill.csv python3 -c from pathlib import Path; dPath(monthly_bill.csv).read_bytes(); print(d[:200])看前几个字节ef bb bf开头是 UTF-8 BOMd4 c5开头基本是 GBK。稳妥的做法是在解析脚本里加一层编码探测或者统一在导入前手动转码。另一个坑是表头行不固定有些平台会在正式表格前输出两行广告语所以脚本必须找表头而不是跳过固定行数这在我上面给的代码里已经体现了。4.2 重复交易与冲正记录的识别账单导入最隐蔽的问题是重复和冲正。重复常见于你误操作导了两遍同一时间段的数据而冲正是退款、撤销的专用记录。我最早没处理冲正导致某个月的支出虚高了一大截报告把双十一退款解读成了消费异常上涨闹了笑话。解决办法分两路重复靠日期 金额 原始备注三元组去重冲正则靠识别交易备注里的退款撤销冲正等字眼把这笔记录方向翻转或金额置负。这里有个细节很多平台的退款记录不是单独一笔而是和原消费配对出现的识别的时候建议把原交易描述和退款描述做关联而不是简单地把所有退款都算成收入否则也会扭曲收支数据。4.3 分类不准的根治方法很多人一上来就追求 100% 分类准确我觉得这不现实也没必要。有效做法是设一个未分类兜底类目每周花十分钟翻一下未分类交易把新商户的关键词补充进规则库。这样操作两轮之后规则命中率基本稳定在 75%-85%剩余部分交给模型兜底整体准确率能到 95% 以上对个人财务分析完全够用了。另外一个很多人忽略的小细节分类规则里的关键词要按谁先命中谁先赢的原则排列否则会出现便利店被超市类目抢走的反直觉结果。我后来在 rules.yaml 里给每个关键词加了优先级字段彻底解决了规则冲突。4.4 数字幻觉与校验机制这是金融 Skill 里最严重、也最值得写的一节。大模型在生成报告时只要你给的上下文里有数字它就极容易在叙述中圆出一个看起来合理但实际错误的数。常见得离谱的错误包括把支出总额从 4321 写成 4312把环比涨幅从 8.5% 写成 18.5%。我的对策是数据与叙述分离脚本校验一切所有数字只来自脚本计算的 JSONPrompt 里明确告诉模型不允许产生任何数据操作然后在输出端挂一个后置校验脚本把模型生成文本里的所有数字提取出来和标准指标表对比超出一致性阈值的语句直接打回重写。这个仲裁者机制虽然笨但特别管用。# 后置校验核心逻辑伪码 import re def validate_report(text: str, metrics: dict) - bool: # 提取报告中的金额数字 numbers re.findall(r(\d{3,}(?:\.\d{1,2})?), text) allowed {str(metrics[total_spent]), str(metrics[total_income])} for n in numbers: if n not in allowed and n not in str(metrics[saving_rate]): # 命中未知数字则标记校验失败 return False return True这里允许白名单内的数字出现既保留模型表述自由又防住了最常见的幻觉。4.5 时间与历史数据口径不一致另一个容易炸的问题不同平台的账单时间字段含义不一样。有的是交易发生时间有的是结算时间有的带时区后缀。如果不统一口径月底最后一天的跨日交易会跑到下个月去导致月度支出波动异常。我的做法是统一使用交易日发生时间作为标准日期解析时显式丢弃结算时间和时区信息只保留年月日时分。涉及跨年、跨月的报告对比时再做一次日历调整保证口径前后一致。5. 关于后续扩展我想多说两句金融 Skill 跑到今天我最深的体会是这种个人工具的护城河不在于用了多酷的模型而在于数据积累和规则沉淀。你账记了三个月就有了趋势分析的素材规则库积累了两百条关键词分类就越来越像你的真实消费习惯这些是任何通用记账 App 都给不了的东西因为它们不拥有你本地三年账本的延续性。后面我准备给它加两个方向一是预算提醒按照历史消费分布计算各类目的合理预算线周中自动检查进度二是目标规划接入一个简单的复利计算器把每月能存多少和几年后能攒到多少关联起来。如果你也在折腾 OpenClaw 或者类似的本地助手框架我真心建议从金融这类结构化、隐私敏感的场景切入——它既能把框架的能力边界逼出来又能让你感受到工具真正为你在干活的价值。最后一个小技巧所有脚本和规则文件记得纳入版本管理我吃过一次手动改规则把整个 rules.yaml 改挂的亏从那以后任何改动都先进仓库数据无价配置更要小心。
阅读完成 · 觉得有帮助?