## 持续了十年的手动流程

在过去的十年里，我每个月都会进行一次个人财务决算。这个过程完全是手动的：登录多个银行和券商网站，把每一个股票、债券、基金、定期的名称、当前价值和日期，一个个手动录入一个草台班子Excel里。这个表格最终会生成一个汇总视图，展示我的总资产、总负债、以及各类资产的分布占比，为我提供一个财务概览。

这个流程虽然有用，但执行起来非常痛苦。它不仅耗时，重复，而且充满了琐碎的输入和计算，一不小心就弄错了。但意外的是，这么多年我从来没有想过自动化。可能因为我的潜意识里面就觉得这种东西就不可能自动化——要访问多个封闭的金融系统，这在技术上就是不可行的。但是，最近我用AI做了很多之前想都不敢想的事情，就不由自主开始重新思考这个问题：到底能不能能把这个繁琐的任务完全交给机器，从而实现又更省心，又更准确，甚至更高频的财务决算？

## 首次尝试：Plaid的合规高墙

我的第一反应当然就是问[大哥](https://yage.ai/o1-pro.html)怎么办。大哥说，这个简单啊，你可以用一个标准的、程序化的解决方案——一个叫做Plaid的工具。Plaid是一个专业的金融服务API，它可以让我们的app安全地连接到用户的银行账户，然后用统一的格式获取账户信息。比如TurboTax用的就是Plaid来一站拿到用户的各种税表。聊完以后，我感觉，卧槽大哥牛逼。我下面就可以通过一个统一的API，一把拿到所有账户的持仓和价格。问题就搞定了。

我立即申请了一个开发者账号。但是很快就遇到了一个大问题：合规审查。Plaid对申请生产环境（Production）API密钥的用户有着很多变态要求。它给了我一大堆问卷。里面问：“公司是否有专门的信息安全团队？”、“系统补丁的更新频率是多久？”、“是否有内部网络漏洞扫描机制？”以及“客户信息泄露的应急预案是什么？”把我问懵了。我只是个小虾米想要连自己的账户看看余额而已。你吼那么大声干什么？

我瞎几把填了以后，生产密钥申请很快就被驳回了。这里也不能用开发（Development）密钥，因为许多大型银行（如Wells Fargo或Chase）强制要求使用OAuth进行认证，而OAuth认证又必须使用生产密钥。这条路被彻底堵死了。

## 思路转变：GUI作为最后的API

标准的数据接口走不通，我就开始想邪招了。如果全自动不行，那么半自动行不行？这里我的核心痛点在于手动输入数据，如果能解决这个瓶颈，效率仍然可以大幅提高。

这时，我想到了Vision LLM和类似Computer Use的用法。我的新思路是：既然没办法用API直接拿到数据，那么我就从GUI (图形用户界面)上抓。具体流程是，我手动登录银行网站，然后对持仓信息的页面截图。这些截图中包含了所有我需要的信息：股票代码、价格、总价值等。接着，我将这些图片交给一个视觉大模型进行识别和解析，让它把图片中的非结构化信息，转换为结构化的 JSON/CSV 格式。最后，再用一个简单的Python脚本处理这些结构化数据。

我很快用这个想法做了一个实验。我选择了在本地部署的Ollama环境，使用Qwen2.5-VL:32b模型来处理截图。结果非常理想。对于我提供的20多个持仓条目，模型的识别准确率几乎是100%。唯一的错误发生在一个持仓的股数上——我的微软股票的股数非常诡异的带个小数，模型识别不了。这是一个非常小的问题，大不了我把这零点几股卖掉就好。这个probing experiment解决了最大的不确定性，证明了这条路是可能走通的。

## 构建与验证：你信不信任AI处理的财务数据？

方案可行，接下来就是工程实现。但另一个问题成了拦路虎：金融数据极其敏感，我们如何确保AI的处理过程是准确的？换言之，我们怎么对AI的结果建立信任？为了解决这个问题，我设计了一套人机协作的工作流，建立了一个双重验证框架。

### AI辅助的开发流程

首先，我利用AI完成了大部分繁重的工作。

1. 数据结构设计：我让AI分析我过去十年的Excel表格，并基于此设计出一个更规范、更健壮的数据库表结构（最终以简单的CSV文件形式存储，方便我再给其它AI分析）。
2. Prompt设计：我让AI根据这个新的数据结构，为视觉大模型设计了一个专门的Prompt。这个Prompt会指导视觉大模型输出格式高度统一、可直接被下游程序解析的JSON。（这个prompt在文末会贴）
3. 代码生成：我让AI编写了后续处理的Python脚本。这个脚本负责解析视觉大模型输出的JSON文件，与历史数据进行合并，并生成一个可视化的HTML报告供我审查。

整个流程是：我自己手动截图，然后本地的Qwen2.5-VL模型处理这些图片并生成JSON文件。接着，另一个脚本会解析这些JSON，进行数据整合和可视化。如果我审查HTML报告后认为没有问题，只需在命令行输入“yes”，新的数据就会被追加到我的主数据库中。

### 结果驱动与交叉验证

对于AI系统的准确性，我的核心思想是：结果驱动，而非过程驱动。我不关心AI内部的思考过程或者程序是不是完美，我只关心它的最终结果是否准确。如果比如20个结果上面AI都是对的，而且没有明显的pattern，那我就假设这个正确性是可以信任可以推广的。

而在结果验证方面，我采用了交叉验证（Cross-Check）的方法。在过去的手动记账中，我会花费很多精力去核对每个月的总资产。所以它可以看作是一个相对可靠的Ground Truth。我的验证方法是，（用确定性的程序）把AI从Excel里面解析出的所有分项资产的价值加起来，然后与我当月记录的总资产进行比对，看看差了多少。如果这两个数相差超过5%，程序就会报警。

在AI对过去十年的Excel记录转换以后，程序发现大部分月份的误差都在5%以内，但有6个月份的数据差异显著高于这个阈值。我一个个过了这6条记录，发现问题几乎全部出在我当年的手动录入上——有的是金额数字输入错误，有的是日期写错导致无法对齐。这个发现很有意思，这说明这套自动化流程不仅没有犯错，反而帮助我找出了过去十年手动记账中隐藏的错误。进一步说明了自动化的好。

## 结论与思考

这个项目最终花费了我大约两个小时的时间，把一个困扰我十年的手动任务实现了自动化。现在，我可以在几分钟内完成过去需要半个多小时才能完成的工作。更重要的是，数据的准确性和分析的频率都得到了极大的提升。

这次经历也引发了我的一些思考：

1. GUI和API的界限没有我们想得明晰：当API因为合规、商业或者任何原因无法访问时，视觉大模型提供了一种全新的自动化方式。任何软件的GUI，都可以被当作一个非官方的API来用。这为自动化那些封闭的、传统的系统开辟了巨大的可能性。
2. 本地模型的价值：对于处理个人隐私数据（尤其是金融数据）的场景，使用Ollama在本地运行模型对我的心理安全感很重要。这样所有敏感信息都在我自己的设备上。
3. 全自动搞不定不妨半自动：很多时候AI看起来不work的时候，不妨回头想一想初心——我为什么想做这件事，我可以接受怎样的trade off，这个问题怎么绕过去。可以自己想，也可以和AI讨论。这种和AI积极共创的心态往往可以大幅提升用AI完成任务的成功率。

另外，这是我用的VLLM的prompt：

---

我正在处理财务决算工作，需要将金融账户的屏幕截图转换为标准化的JSON格式。

**重要说明:**

- 请保持金额的原始数值，不要进行任何单位转换
- 只输出JSON，不需要其他解释文字
- 如果无法确定某个字段，使用null值

**输出格式要求:**

根据截图类型，输出以下JSON结构之一：

### 1. 账户列表截图
```json
{
  "data_type": "accounts",
  "currency": "USD",
  "items": [
    {
      "name": "[完整账户名称]",
      "balance": [余额数字，保持原始数值],
      "account_type": "[账户类型: checking, savings, brokerage, ira, investment]",
      "account_number_suffix": "[账户号后几位]",
      "full_account_name": "[截图中显示的完整账户名]"
    }
  ]
}
```

### 2. 股票列表截图
```json
{
  "data_type": "stocks", 
  "currency": "USD",
  "items": [
    {
      "ticker": "[股票代码]",
      "amount": [持股数量，整数],
      "balance": [总价值，保持原始数值],
      "price_per_share": [每股价格，如果可见],
      "account_context": "[所属账户信息，如果可见]"
    }
  ]
}
```

**账户类型识别规则:**

- 包含"checking"字样 → "checking"
- 包含"savings"字样 → "savings"  
- 包含"brokerage"字样 → "brokerage"
- 包含"IRA"字样 → "ira"
- Betterment账户类型 → "investment"
- 其他投资账户 → "investment"

请严格按照上述格式处理截图内容。

---

<script async data-uid="49b08a3e73" src="https://yage-ai.kit.com/49b08a3e73/index.js"></script>
