数据库接入 AI Agent 后 Token 狂飙?别把工作流塞进 Memory,改用 Skill + Script
你可能也遇到过这个场景:
你在手机上给 AI Agent 发一句:
帮我查一下昨天各渠道的订单量,导出成 Excel 发我。
Agent 确实能干活。
它会理解需求、查数据库、整理结果、生成文件。
听起来很美。
可用几次之后你一看账单:Token 怎么烧得这么快?🔥
更气人的是,你明明已经把完整流程写进了 Agent 的 Memory:
- 查数据要先看表结构
- 只能查只读库
- 查询后要格式化
- 文件要导出成 Excel
- 导出后上传到指定位置
结果它有时候照做,有时候乱跑。
问题不在你写得不够细。
问题在于:Memory 根本不是拿来跑流程的。
关键原因:Memory 是背景信息,不是执行引擎
很多人会误会 Memory。
以为把流程写进去,Agent 就会像程序一样稳定执行。
但真实情况是:
Memory 更像“它知道的一些背景”,不是“它必须执行的代码”。
每次你发消息,Agent 还是会重新做这些事:
- 理解你的自然语言
- 回忆 Memory 里的内容
- 判断你想查什么
- 推断应该用哪些表
- 规划查询步骤
- 决定是否导出文件
- 决定如何格式化
- 决定下一步工具调用
这些“思考过程”都要吃 Token。
而且,LLM 的规划不是确定性的。
你今天说“导出昨天数据”,它可能走 A 流程。
你明天说“把昨天数据发我一份”,它可能走 B 流程。
意思差不多,执行路径却变了。
这就是很多 Agent 接数据库后不稳定、成本高的根源。
正确拆法:LLM 只做翻译,脚本负责干活
咱们别让大模型包办一切。
它很聪明,但不适合做每一步都固定的脏活累活。
更好的架构是:
用户自然语言
↓
Agent Skill:识别任务 + 生成 SQL 参数
↓
脚本:执行 SQL / 格式化 / 导出 Excel / 上传文件
↓
返回结果给用户
把任务切成两类:
交给 LLM 的部分
LLM 擅长处理模糊表达。
比如你说:
查一下上周五抖音渠道的支付订单数。
它可以帮你转成结构化查询意图:
{
"date": "2025-01-10",
"channel": "douyin",
"metric": "paid_orders"
}
或者生成 SQL:
SELECT
COUNT(*) AS paid_orders
FROM orders
WHERE channel = 'douyin'
AND pay_status = 'paid'
AND order_date = '2025-01-10';
这才是它该干的活。
交给脚本的部分
这些动作没必要让 LLM 想:
- 连接数据库
- 执行 SQL
- 限制查询超时时间
- 检查返回行数
- 格式化表格
- 生成 Excel / CSV
- 上传到对象存储
- 返回下载链接
这些都应该写进 Python、Shell 或后端接口里。
机器能稳定跑,别让模型每次重新“琢磨”。
一句话:
能用脚本干的事,别让 LLM 干。LLM 负责翻译,脚本负责执行。
为什么 Skill 比 Memory 更适合放工作流?
Memory 放的是背景。
Skill 放的是能力。
这俩差别很大。
你可以把 Memory 想成便利贴:
这个用户常查订单数据。
而 Skill 更像一个封装好的按钮:
当用户要查订单数据时,调用这个能力,按固定输入输出执行。
数据库查询这类任务,天然适合做成 Skill。
因为它通常有明确流程:
- 识别用户想查什么
- 补全查询条件
- 生成 SQL 或结构化参数
- 调用脚本执行
- 返回结果或文件链接
注意,文章里这里的数字只是方便你看清结构,不是让模型靠 Memory 自由发挥。
真正上线时,流程要写进 Skill 和脚本里。
推荐架构:Agent Skill + SQL 模板 + 执行脚本
一个稳一点的数据库 Agent,可以这样设计:
用户:帮我导出昨天各渠道订单量
Agent Skill:
- 判断任务类型:订单统计
- 识别时间:昨天
- 识别维度:渠道
- 识别指标:订单量
- 选择 SQL 模板
- 填入参数
Python Script:
- 执行 SQL
- 生成 Excel
- 上传文件
- 返回链接
Agent:
- 把链接发给用户
核心变化是:
以前让 Agent 从零规划。
现在让 Agent 填空。
成本差别会很明显。
Skill 里应该放什么?
别把一大坨业务说明塞进去。
Skill 要短、准、可执行。
建议放这些内容:
1. 可查询范围
告诉 Agent 哪些数据能查,哪些不能查。
本 Skill 只用于查询订单统计数据。
可查询指标:订单量、支付订单量、GMV、退款金额。
可查询维度:日期、渠道、商品类目、城市。
禁止查询用户手机号、身份证、详细地址等敏感字段。
2. 表结构精简说明
别把整个数据库文档贴进去。
只放当前 Skill 需要的表。
orders 表:
- order_id:订单 ID
- order_date:下单日期,格式 YYYY-MM-DD
- channel:渠道,如 douyin、kuaishou、wechat
- pay_status:支付状态,paid 表示已支付
- amount:订单金额,单位元
- refund_amount:退款金额,单位元
- category:商品类目
- city:城市
3. 常用 SQL 模板
让 Agent 选模板填参数,而不是从零写 SQL。
-- 按渠道统计订单量
SELECT
channel,
COUNT(*) AS order_count
FROM orders
WHERE order_date BETWEEN '{{start_date}}' AND '{{end_date}}'
GROUP BY channel
ORDER BY order_count DESC;
-- 按日期统计 GMV
SELECT
order_date,
SUM(amount) AS gmv
FROM orders
WHERE order_date BETWEEN '{{start_date}}' AND '{{end_date}}'
AND pay_status = 'paid'
GROUP BY order_date
ORDER BY order_date;
4. 输出格式约定
让 Agent 输出结构化参数,别输出一段散文。
推荐这样:
{
"template_name": "orders_by_channel",
"params": {
"start_date": "2025-01-14",
"end_date": "2025-01-14"
},
"export_format": "xlsx"
}
脚本拿到 JSON 后直接跑。
不用猜。
不用问。
不用读一大段自然语言。
Python 脚本怎么写?给你一个简化版
下面是一个简化示例。
真实项目里你要加权限、审计、脱敏、超时控制。
import pandas as pd
import pymysql
from datetime import datetime
SQL_TEMPLATES = {
"orders_by_channel": """
SELECT
channel,
COUNT(*) AS order_count
FROM orders
WHERE order_date BETWEEN %(start_date)s AND %(end_date)s
GROUP BY channel
ORDER BY order_count DESC;
""",
"gmv_by_date": """
SELECT
order_date,
SUM(amount) AS gmv
FROM orders
WHERE order_date BETWEEN %(start_date)s AND %(end_date)s
AND pay_status = 'paid'
GROUP BY order_date
ORDER BY order_date;
"""
}
def run_query(task: dict):
template_name = task["template_name"]
params = task["params"]
export_format = task.get("export_format", "xlsx")
if template_name not in SQL_TEMPLATES:
raise ValueError("不支持的查询模板")
sql = SQL_TEMPLATES[template_name]
conn = pymysql.connect(
host="readonly-db.example.com",
user="readonly_user",
password="your_password",
database="biz_data",
charset="utf8mb4"
)
try:
df = pd.read_sql(sql, conn, params=params)
finally:
conn.close()
filename = f"report_{template_name}_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
if export_format == "csv":
path = f"/tmp/{filename}.csv"
df.to_csv(path, index=False)
else:
path = f"/tmp/{filename}.xlsx"
df.to_excel(path, index=False)
return {
"rows": len(df),
"file_path": path
}
这个脚本做了几件很关键的事:
- 只能执行预设模板
- 参数通过字典传入
- 数据库账号只读
- 导出逻辑固定
- 返回结构固定
Agent 不需要参与这些步骤。
它只需要交出一个合格的 JSON。
手机发消息时,流程应该长什么样?
你在手机上说:
帮我导出昨天各渠道订单量。
Agent 不应该开始长篇分析。
它应该生成类似这样的任务:
{
"template_name": "orders_by_channel",
"params": {
"start_date": "2025-01-14",
"end_date": "2025-01-14"
},
"export_format": "xlsx"
}
脚本执行后返回:
{
"rows": 8,
"file_url": "https://files.example.com/report_orders_by_channel_20250115.xlsx"
}
Agent 回复你:
已导出昨天各渠道订单量,共 8 行数据。文件在这里:xxx
这才是舒服的体验。
没有废话。
没有临场发挥。
Token 也不会莫名其妙爆掉。
Token 为什么能降这么多?
因为大头被砍掉了。
原来的 Agent 可能每次都要读:
- 业务流程说明
- 数据库表结构
- 字段含义
- 查询规则
- 导出规则
- 历史上下文
- 工具调用结果
- 中间推理内容
改成 Skill + Script 后,模型只需要处理:
- 用户这句话想查什么
- 选择哪个模板
- 填哪些参数
工作量小太多。
从“让一个顾问全程操盘”,变成“让前台填一张工单”。
你说哪个省钱?
避坑清单:数据库 Agent 千万别这么做
坑 1:把完整 SOP 塞进 Memory
Memory 不是流程引擎。
写得再详细,也只是参考资料。
Agent 还是可能漏步骤。
坑 2:让 LLM 自由生成 SQL 并直接执行
这个很危险。
一旦生成错 SQL,轻则查错数,重则拖垮数据库。
更别提敏感字段泄露。
建议只允许:
- 预设 SQL 模板
- 白名单字段
- 只读账号
- 查询行数限制
- 超时时间限制
坑 3:每次都把全量表结构塞给模型
几十张表、几百个字段,全塞进去就是烧钱。
按场景拆 Skill。
订单查询一个 Skill。
用户画像一个 Skill。
库存统计一个 Skill。
每个 Skill 只带自己需要的表结构。
坑 4:让 Agent 负责导出 Excel
导出文件是确定性任务。
Python 的 pandas 比 LLM 靠谱多了。
别让模型描述怎么导出。
让脚本直接导出。
坑 5:没有权限和审计
数据库接 Agent,一定要留痕。
至少记录:
- 谁发起的查询
- 原始问题是什么
- 使用了哪个模板
- 查询参数是什么
- 返回多少行
- 文件保存在哪里
- 是否包含敏感字段
别等出事了再补。
那时候就不是优化 Token 了,是写事故复盘。
一个更稳的 Skill 提示词模板
你可以参考下面这个写法:
你是订单数据查询 Skill。
你的任务:
把用户的自然语言查询请求转换为结构化 JSON。
你不执行 SQL。
你不导出文件。
你不编造字段。
你只能从给定 SQL 模板中选择一个。
可用模板:
1. orders_by_channel
用途:按渠道统计订单量
参数:start_date, end_date
2. gmv_by_date
用途:按日期统计 GMV
参数:start_date, end_date
字段说明:
orders.order_date:订单日期,YYYY-MM-DD
orders.channel:渠道
orders.pay_status:支付状态,paid 表示已支付
orders.amount:订单金额
输出要求:
只输出 JSON,不要输出解释文字。
JSON 格式:
{
"template_name": "",
"params": {
"start_date": "YYYY-MM-DD",
"end_date": "YYYY-MM-DD"
},
"export_format": "xlsx"
}
如果用户的问题超出可用模板范围,输出:
{
"error": "unsupported_request",
"message": "当前 Skill 不支持该查询"
}
这个提示词的重点是边界清楚。
Agent 不再“自我发挥”。
它只做选择题和填空题。
什么时候还需要 Memory?
Memory 不是没用。
只是别拿它当工作流。
它适合存这些东西:
- 用户常用的时间口径,比如“昨天”按北京时间算
- 用户偏好的文件格式,比如默认 Excel
- 用户常看的业务线,比如华东区
- 用户常用渠道命名,比如“抖音”对应 douyin
但执行规则、SQL 模板、导出流程,建议放到 Skill 和脚本。
简单说:
Memory 记偏好,Skill 管能力,Script 干重活。
可以照抄的落地步骤
你可以按这个顺序改造现有 Agent:
- 把当前 Memory 里的流程拆出来
- 梳理用户最常问的 10 类数据库问题
- 每类问题设计一个 SQL 模板
- 每个模板只开放必要参数
- 写一个脚本负责执行 SQL 和导出文件
- 把表结构精简后放进对应 Skill
- 让 Agent 输出结构化 JSON
- 脚本校验 JSON 后再执行
- 加上查询超时、行数限制、只读账号
- 记录每次查询日志
改完之后,你会发现 Agent 变“笨”了。
这是好事。
它不再到处乱想。
它只在该聪明的地方聪明。
结论:别让 LLM 扮演数据库工程师
数据库查询 Agent 想要稳定、省钱、可控,关键不是把提示词写得更长。
而是重新分工。
LLM 负责理解人话。
Skill 负责限定能力。
Script 负责执行动作。
数据库负责返回结果。
这套拆开之后,Token 通常能明显下降,流程也会稳很多。
你要的是每天在手机上发一句话就拿到报表,不是看 Agent 在那儿写小作文。
让模型少想一点。
让脚本多干一点。
钱省了,结果也更靠谱。