【个人记账理财助手】大模型上下文管理选型文档
·
【个人记账理财助手】 大模型上下文管理选型文档
一、背景与目标
在我们的记账应用中,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 默认方案的问题
- 混合检索导致精度稀释:DDL、文档、示例 SQL 在同一向量空间,类型差异大,互相干扰
- 表级 vs 列级不分:一张大表的 DDL 作为一个 chunk,检索到整张表,但实际只需要其中两列
- 未利用结构关系:记账表之间存在外键关联(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 │
└──────────────────────────────────────────┘
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)