【个人记账理财助手】 大模型上下文管理选型文档

一、背景与目标

在我们的记账应用中,Text-to-SQL 的每轮查询都需要将一段上下文塞进 LLM 的上下文窗口,让模型理解"库长什么样"和"怎么查"。这段上下文包含三部分:

上下文类型 内容 体积 特点
DDL 表结构定义(users, accounts, transactions, categories…) ~5-10 KB 相对固定,偶有变动
业务文档 分类体系、金额规则、时间表达规范 ~3-5 KB 基本固定
示例 SQL (问题, SQL) 问答对,高频查询 每个 pair ~0.5-2 KB 持续增长,按需检索

核心矛盾:所有上下文加起来远超 LLM 的上下文窗口(即使 DeepSeek-V3 有 128K,也不意味着把所有东西无脑塞进去效果就好——检索噪声会拉低准确率)。

本文档的目标:选型一套上下文管理策略,在有限的上下文窗口内,只放最相关的信息,让 Text-to-SQL 准确率最高、token 消耗最少。


二、核心问题:什么是"好的上下文"?

2.1 上下文过量的代价

全部 DDL + 全部文档 + 全部示例 SQL → 塞进 prompt

后果:
├── 检索噪声:不相关的表结构干扰 LLM 判断
├── Lost in the Middle:关键信息被淹没在长文本中间位置
├── Token 浪费:每次请求都花冤枉钱(DeepSeek ¥2/百万输入 token)
└── 延迟增加:长 prompt 的首 token 生成时间更长

2.2 上下文不足的代价

只塞 DDL,不给示例 SQL → LLM 自己猜怎么写

后果:
├── 准确率低:没有 few-shot 的约束,SQL 风格随意
├── 不理解业务语义:"外卖"和"餐饮"的层级关系需要 example 才能学会
└── 时间表达混乱:没有示例,"上个月"可能被翻译成不同的 SQL

目标:在"太多"和"太少"之间找到最优平衡点。


三、上下文管理全景对比

3.1 可选策略一览

策略 核心思路 Token 节省 准确率影响 实现复杂度
全量注入 所有 DDL + 文档 + 示例一次性塞入 0%(基准) 中(噪声干扰)
RAG 检索 语义检索最相关的 DDL/文档/示例 ~60-80% 高(前提是检索准)
分层检索 先查表级 → 再查列级 → 再查示例 SQL ~70-85% 高(精确匹配) 中高
上下文压缩 用 LLM 或专用模型压缩 prompt 长度 ~40-60% 中高(有信息损失)
KV Cache 复用 重复部分的 Key-Value 缓存(前缀缓存) ~30-50% 推理 无损失 依赖 API 支持
规则引擎降级 高频简单查询不走 LLM,走预置模板 ~90% 调用 无损失(精确匹配)
多 Agent 分工 不同 Agent 负责不同表/任务,各自小上下文 ~50-70% 高(需编排)

3.2 记账场景的适配

策略 在我们场景中的适用性 理由
全量注入 ❌ 不推荐 ~15 张表 + 文档 + 示例,超出合理范围
RAG 检索 核心策略 Vanna 本身基于 RAG,已有社区实践
分层检索 推荐增强 记账场景表多但关联明确,分层检索效果更好
上下文压缩 ⚠️ 可选 对 DDL 做压缩可能丢失列注释等关键信息
KV Cache 复用 ✅ 可选 DeepSeek 支持 prefix caching,可缓存 DDL 前缀
规则引擎降级 推荐 "本月总支出"等高频查询直接走模板,0 token 成本
多 Agent 分工 ❌ 过度设计 10 张表以内不需要多 Agent,单人记账场景简单

最终推荐策略组合:RAG 检索 + 分层检索 + 规则引擎降级。


四、策略一:RAG 检索(基础层)

4.1 Vanna 默认的 RAG 流程

提问 → 向量检索(DDL + 文档 + 示例 SQL)→ 拼装 prompt → LLM 生成 SQL

Vanna 的做法:把所有训练数据(DDL/documentation/sql)向量化,
查询时用 cosine similarity 找 top-k 最相关的片段。

4.2 默认方案的问题

  1. 混合检索导致精度稀释:DDL、文档、示例 SQL 在同一向量空间,类型差异大,互相干扰
  2. 表级 vs 列级不分:一张大表的 DDL 作为一个 chunk,检索到整张表,但实际只需要其中两列
  3. 未利用结构关系:记账表之间存在外键关联(transactions → categories),纯向量检索不知道这种关系

4.3 改进方向

问题  →  改进
───────────────
混合检索 → 分类型独立索引
粗粒度   → 分层检索(表级 → 列级 → 示例)
无结构   → 引入 schema 关联图

五、策略二:分层检索(增强层,推荐)

5.1 三层检索架构

用户提问:"这个月外卖花了多少钱?"
         │
         ▼
┌─────────────────────────────────────┐
│  Layer 1:表级路由                   │
│  判断问题涉及哪些表                   │
│  结果:transactions + categories     │
└──────────────────┬──────────────────┘
                   ▼
┌─────────────────────────────────────┐
│  Layer 2:列级 + 条件提取             │
│  哪几列?什么过滤条件?               │
│  结果:amount, transaction_date      │
│        category.name = '外卖'        │
│        transaction_date = '本月'     │
└──────────────────┬──────────────────┘
                   ▼
┌─────────────────────────────────────┐
│  Layer 3:示例 SQL 匹配              │
│  检索最相似的问题-SQL pair           │
│  结果:找到"本月餐饮花了多少"的 SQL   │
└──────────────────┬──────────────────┘
                   ▼
         拼装最终 prompt(精简、精准)

5.2 每层的具体设计

Layer 1 — 表级路由

输入:"这个月外卖花了多少钱?"
输出:["transactions", "categories"]

实现方式:
┌── 方案 A:embedding 匹配(将表描述向量化,检索最相关的)
│   "transactions" → "交易记录表:金额、时间、类型、分类"
│   "categories"  → "分类表:名称、父分类"
│   → 匹配到 transactions 和 categories
│
└── 方案 B:关键词匹配(简单可靠,适合固定表名)
    "花了多少钱" → amount(属于 transactions)
    "外卖"       → category name(属于 categories)
    → 命中 transactions + categories

推荐:方案 A(embedding)为主,方案 B 做降级兜底

Layer 2 — 列级 + 条件提取

输入:"这个月外卖花了多少钱?"
输出:
  聚合列:SUM(amount)
  过滤条件:transaction_type = 'expense'
           category.name = '外卖' 或 c.parent.name = '餐饮'
           transaction_date BETWEEN '2026-05-01' AND '2026-05-31'

实现:用一个轻量 LLM 调用或规则做"问题 → 结构化条件"抽取
        这一层输出的不是 SQL,而是业务条件 → 后面组装 SQL 更容易

Layer 3 — 示例 SQL 匹配

输入:"这个月外卖花了多少钱?"
输出:最相似的 (question, sql) pair

检索策略(按优先级):
  1. exact match:相同问题 → 直接复用 SQL(最高优先级)
  2. 同义词 match:"外卖" vs "餐饮" → 替换分类名后复用
  3. embedding match:语义相近的问题 → 作为 few-shot 示例

检索量控制:top-3,太多会稀释注意力(实验表明 3-5 个示例效果最佳)

5.3 最终 prompt 拼装

组装后的 prompt(约 2-3K token,仅为全量的 1/10):

系统:你是个人记账 SQL 专家...

相关表结构:
  transactions(id, user_id, amount, transaction_type, transaction_date, category_id)
    -- 金额正数,expense 表示支出
  categories(id, name, parent_id)
    -- 外卖的父分类是餐饮

业务规则:
  金额单位人民币元
  "这个月" = date_trunc('month', CURRENT_DATE) ~ now()
  分类查询需考虑子类

参考示例:
  Q: 这个月餐饮花了多少?
  A: SELECT SUM(amount) FROM transactions t
     JOIN categories c ON t.category_id = c.id
     WHERE t.transaction_type = 'expense'
       AND (c.name = '餐饮' OR c.parent_id = (SELECT id FROM categories WHERE name='餐饮'))
       AND t.transaction_date >= date_trunc('month', CURRENT_DATE)

问题:这个月外卖花了多少钱?

六、策略三:规则引擎降级(兜底层)

6.1 适用场景

对于高频、简单、确定的查询,直接走预置模板,不调 LLM

查询类型 示例 模板
本月总支出 “这个月花了多少钱” SELECT SUM(amount) FROM transactions WHERE ...
本月总收入 “这个月收入多少” 同上,改 type = ‘income’
账户余额 “支付宝还有多少钱” SELECT balance FROM accounts WHERE name LIKE '%支付宝%'
按分类汇总 “本月餐饮花了多少” 模板 + 分类参数
近 N 天流水 “最近 7 天的支出” SELECT ... WHERE date >= now() - interval '7 days'

6.2 如何与 RAG 配合

请求进入
    │
    ├──→ 规则匹配(意图分类)→ 命中 → 执行模板 SQL → 返回
    │
    └──→ 未命中 → RAG 检索 → LLM 生成 → 执行 → 返回
                   ↓
              如果用户确认了正确 SQL → 反哺为模板

6.3 效果

典型场景:80% 的用户查询是"本月花了多少""餐饮支出"这类简单查询
规则引擎覆盖这 80% → LLM 调用减少 80% → 延迟从 ~3s 降到 ~10ms
剩余 20% 复杂查询走 LLM(环比、自定义时间段等)

七、上下文压缩:何时需要?

7.1 压缩 vs 检索的取舍

检索(推荐):精确选择要什么,不要什么
  ✅ 无信息损失
  ✅ 可控性强
  ❌ 依赖检索质量

压缩(备选):全拿来然后压缩
  ✅ 不会遗漏信息
  ❌ 有信息损失(关键列注释可能被压缩掉)
  ❌ 多一次 LLM 调用成本

7.2 记账场景的压缩策略

对于 DDL,不需要通用压缩——更好的做法是"结构化精简":

原始 DDL(约 1.5 KB/表):
  CREATE TABLE transactions (
      id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
      user_id UUID NOT NULL REFERENCES users(id),
      amount NUMERIC(15,2) NOT NULL CHECK (amount > 0),
      ...
  );

精简后(约 0.3 KB/表,保留业务语义):
  transactions(user_id, amount+, transaction_type[income/expense/transfer],
               transaction_date, category_id→categories)
  -- amount 正数,+ 表示 expense 方向为正

压缩率:~80%,且无信息损失(去掉了通用约束、默认值等 LLM 不需要的细节)

7.3 什么时候真正需要压缩

  • DeepSeek 不支持 prefix caching 时:可以用压缩减少输入 token
  • 表结构超过 20 张:分层检索加压缩结合
  • 用户一次问多个问题(批量查询):压缩历史对话

八、各方案的综合对比

维度 全量注入 RAG 检索 分层检索 规则降级 上下文压缩
Token 消耗 基准 ~20-40% ~10-20% ~0-10% ~40-60%
准确率(BIRD 基准) 中等 较高 精确 中高
实现复杂度 中高
可维护性
冷启动速度 立即 需向量化训练 需分层训练 需模板积累 立即
覆盖查询面 100% 依赖检索 依赖检索 仅高频 100%
记账场景推荐 ✅ 基础 ✅ ✅ 核心 ✅ 兜底 ⚠️ 备选

九、综合推荐方案

9.1 推荐架构

                          用户提问
                             │
                             ▼
┌────────────────────────────────────────────┐
│              ① 规则引擎                     │
│  意图分类 → 高频简单查询 → 执行模板 SQL → 返回│
└──────────────────┬─────────────────────────┘
                   │ 未命中
                   ▼
┌────────────────────────────────────────────┐
│  ② 分层检索(核心)                          │
│  Layer 1: 表级路由 → 命中哪些表              │
│  Layer 2: 列级+条件提取 → 具体查啥           │
│  Layer 3: 示例 SQL 匹配 → top-3 few-shot    │
└──────────────────┬─────────────────────────┘
                   ▼
┌────────────────────────────────────────────┐
│  ③ Prompt 拼装                             │
│  只包含命中表 + 相关文档 + 匹配示例          │
│  体积控制在 ~2-3K token                     │
└──────────────────┬─────────────────────────┘
                   ▼
┌────────────────────────────────────────────┐
│  ④ DeepSeek API                            │
│  (利用 prefix caching 缓存 DDL 片段)        │
└──────────────────┬─────────────────────────┘
                   ▼
          SQL 执行 + 结果返回

9.2 Token 预算分配

每次查询的 token 预算:~3K(约全量的 1/10)

┌────────────────────────────────────┐
│  Token 预算分配                     │
│                                    │
│  System Prompt      200  (7%)      │
│  命中的表 DDL       800  (27%)     │
│  相关业务文档       500  (17%)     │
│  few-shot 示例     1200  (40%)     │
│  用户问题           300  (10%)     │
├────────────────────────────────────┤
│  总计              ~3000 token     │
└────────────────────────────────────┘

vs 全量注入:~20-30K token
节省:~85-90%,同时提高准确率

9.3 演进路线

Phase 1(MVP)—— 规则引擎 + 简单 RAG
  └── 覆盖 80% 高频查询的规则模板
  └── Vanna 默认 RAG(DDL + 文档 + 示例统一检索)
  └── top-5 检索结果拼装

Phase 2(增强)—— 分层检索
  └── 表级路由:embedding + 关键词双通道
  └── 列级条件提取:轻量 LLM 调用
  └── 示例 SQL 分层匹配:exact → synonym → semantic
  └── token 预算动态分配(按问题复杂度调整)

Phase 3(优化)—— 持续学习
  └── 用户修正 SQL → 自动归入规则模板 or 增强示例库
  └── A/B 测试不同检索策略的准确率
  └── 热力图分析哪些查询类型容易失败,定向补充训练数据

十、风险评估

风险 概率 影响 缓解
检索不准确导致错误 SQL 分层检索+规则兜底后大幅降低;前端保留 SQL 预览确认环节
规则模板覆盖不足 模板逐步积累而非一步到位;未命中时平滑降级到 LLM
DDL 变更后向量库过期 CICD 流水线中加一步:DDL 变更 → 自动重新训练 Vanna
中文时间表达解析错误 Layer 2 做时间标准化,将"上个月""这周"转为具体日期范围
token 预算不足 128K 窗口远超预算;压缩策略做最后兜底

十一、总结

最终推荐:规则引擎降级 + 分层 RAG 检索 组合策略。

层级 方案 覆盖 成本
第一层 规则模板 ~80% 高频查询 ~0 token,~10ms
第二层 分层 RAG 检索 ~18% 中低频查询 ~3K token,~1-2s
第三层 全量检索(兜底) ~2% 极端复杂查询 ~10K token,~3-5s

与"数据库选型"和"应用上下文管理"组合:

┌──────────────────────────────────────────┐
│          应用上下文 (ContextVar)          │
│  user_id / request_id 贯穿全栈            │
├──────────────────────────────────────────┤
│         LLM 上下文管理 (本选型)            │
│  规则引擎 → 分层检索 → prompt 拼装        │
├──────────────────────────────────────────┤
│        Text-to-SQL 引擎 (Vanna)           │
│  RAG 检索 + DeepSeek-V3 API 生成 SQL     │
├──────────────────────────────────────────┤
│          数据层 (PostgreSQL)              │
│  存储 + pgvector + RLS                    │
└──────────────────────────────────────────┘
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐