聊《我用数据分析经验做了次 AI 项目,最先失效的是旧方法》之前,先说一句实在的:别急着背概念,先看它在真实项目里到底解决什么问题。
摘要
先把这篇文章的目标说清楚:看完之后,你应该能判断这件事值不值得做,以及从哪里动手。
上周做需求评审,产品经理提了一个很典型的场景:“老板想看销售漏斗,以前我要写 SQL 跑三天,现在想用自然语言直接出图,最好还能自动下钻。”
我点头说是,但在心里打了个问号。因为我知道,一旦这个需求从 Jupyter Notebook 里的 Demo 变成公司内网的生产服务,最大的坑根本不是模型答不对题,而是谁有资格看什么数据。
做数据分析出身的朋友,转型做大模型应用(特别是 Analytics Agent)时,最容易陷入一种误区:认为只要 Prompt 写得够好,模型就能像资深分析师一样思考。但实际上,Demo 阶段的“万能钥匙”在生产环境就是“安全漏洞”。今天不聊虚的,就聊聊我是怎么从一个只会写 SQL 的数据分析师,一步步把“智能分析 Agent”从玩具变成能扛住高并发、高安全要求的产品的,以及在这个过程中,我们被迫放弃的那些“聪明”想法。
目录
- 从“自由联想”到“严格边界”的阵痛
- 指标解释 Agent:让模型懂业务,而不是懂 SQL
- 数据工具调用:从“直接连库”到“沙箱隔离”
- 项目案例:一个“失败”的优化
- 总结:从“炫技”回归“工程”
从“自由联想”到“严格边界”的阵痛
记得刚接触 LLM 时,为了追求“智能”,我给 Agent 赋予了极大的自由度。Prompt 里写着:“你可以访问数据库,自由查询任何表,如果发现数据异常,尝试自行修正并给出建议。”
这在本地跑test.py时效果惊艳:模型能自主发现某字段为空,然后尝试用均值填充,最后画出一张漂亮的折线图。
直到有一次,测试同事随手问了一句:“帮我查一下上个月所有用户的身份证号后四位。”
模型愣了一下,反问:“请问您有管理员权限吗?”
那一刻我背脊发凉。虽然它拒绝了,但如果我把它部署在内网,并且没有做好严格的 RBAC(基于角色的访问控制) 隔离,后果不堪设想。
这就是传统 BI 和大模型应用的第一次剧烈碰撞:
- 传统 BI:权限在数据源层解决,SQL 执行前就过滤了。
- 传统 LLM 应用:权限往往在 Prompt 层“祈求”,而大模型并不理解什么是“敏感”,它只理解“概率”。
我的取舍:我砍掉了“自由查询任意表”的功能。现在的 Agent,入口不再是“你想问什么”,而是“你被允许看什么”。
指标解释 Agent:让模型懂业务,而不是懂 SQL
很多同行一上来就搞 Text-to-SQL,这其实是把活儿干反了。对于复杂的业务分析,直接生成 SQL 容易出错,尤其是涉及到多表 JOIN 和业务口径定义时。
我的做法是引入一层“指标解释 Agent”。
这一步的核心价值在于标准化。我们在知识库(Knowledge Base)里不只存表结构,而是存“业务定义”。
比如,“GMV”在我们的系统里定义为“支付成功金额”,不包含退款,且时间截断点为“用户点击支付按钮的时间”,而非“银行扣款时间”。这些信息,模型在生成 SQL 之前,必须先进行检索和确认。
这里有一个具体的代码片段,展示我们是如何通过 LangChain 的 Tool 调用机制,强制模型先确认业务口径,再执行查询的:
from langchain.tools import tool @tool def verify_metric_definition(metric_name: str) -> str: """ 验证指标的业务定义。 在生成任何查询之前,必须先调用此工具获取该指标的标准口径。 返回值为该指标的计算公式、关联表及时间窗口定义。 """ # 模拟从向量数据库中检索业务元数据 db_lookup = { "GMV": "计算逻辑:sum(order_amount),时间窗:created_at >= now() - 1 month,状态:paid_only", "DAU": "计算逻辑:count(distinct user_id),时间窗:login_time,去重策略:当日首次登录" } return db_lookup.get(metric_name, f"未找到指标 {metric_name} 的定义,请联系数据负责人。") # 在 Agent 的工作流中,verify_metric_definition 被置于首位 # 如果用户问"上个月GMV多少",Agent 首先调用 verify_metric_definition("GMV") # 只有拿到“paid_only”和“created_at”这些关键字段,才会进入下一步 SQL 生成这种做法看似增加了步骤,降低了响应速度,但它极大地减少了“幻觉式回答”。对于企业级应用来说,准确性 > 响应速度 > 灵活性。
数据工具调用:从“直接连库”到“沙箱隔离”
传统的 Text-to-SQL 是直接让模型连接数据库。在生产环境中,这是绝对禁止的。
我们采用的架构是:LLM -> SQL 生成器 -> SQL 校验器 -> 只读副本查询 -> 结果格式化。
其中,“SQL 校验器”是关键。它不仅仅检查语法,还要检查语义。比如,模型生成了DELETE FROM users,校验器会直接拦截并报错;如果模型生成了SELECT * FROM sensitive_table,校验器会根据当前用户的 Token 权限进行二次过滤。
我还做了一个决定:不使用通用的大模型直连数据库,而是封装了一套数据查询 API。
import requests def safe_query_executor(user_token, query_params): """ 安全查询执行器 1. 验证 User Token 对应的权限范围 2. 将 query_params 转换为预编译 SQL 参数 3. 返回脱敏后的 JSON 数据 """ # 假设 user_permissions 是从中间件获取的用户角色数据 permissions = get_user_permissions(user_token) # 强制注入权限过滤条件,防止模型越权 final_query = inject_permission_filter(query_params, permissions) try: response = requests.post(API_ENDPOINT, json={"sql": final_query}) if response.status_code == 200: return format_data(response.json(), permissions['mask_level']) else: raise Exception(f"Query failed: {response.text}") except Exception as e: log_error(e, user_token) return {"error": "查询失败,请稍后重试"}这种架构下,模型甚至不知道数据库的具体表名,它只知道“我有权限查哪些维度的数据”。这不仅解决了安全问题,还实现了多租户隔离。
项目案例:一个“失败”的优化
上个季度,我们试图优化一个“异常检测”功能。业务方希望 Agent 能自动发现销售额骤降的原因,并给出归因建议。
Demo 阶段,模型表现得像个天才分析师,它会说:“销售额下降可能是因为竞品降价,或者是物流延迟。”——这都是废话,因为模型根本不知道内部数据。
后来我们调整了方向:不做“原因推测”,只做“数据切片”。
Agent 不再主动给出因果结论,而是根据用户的问题,自动生成一系列对比查询。例如,当用户问“为什么昨天销量跌了?”,Agent 不会瞎猜,而是返回一组结构化数据:
1. 昨日 vs 前日 GMV 对比。
2. 各渠道昨日转化率对比。
3. 各区域昨日订单量分布。
然后,它会给用户三个选项:“请查看渠道 A 的详细数据”、“请查看区域 B 的库存情况”、“请导出完整日报”。
这个改动看起来“智能”程度下降了,但可用性提升了三倍。因为在前端界面,用户需要的是可操作的洞察,而不是模型一本正经地胡说八道。这也符合我们现在的验收标准:Agent 的输出必须是可验证的、可追溯的,而不是不可控的文本生成。
总结:从“炫技”回归“工程”
从数据分析转到 LLM 应用开发,最大的心路历程不是学会写 Prompt,而是学会克制。
1. 克制模型的自由:用权限控制和元数据管理替代“信任”。
2. 克制业务的期望:不要指望 Agent 成为全知全能的分析师,让它成为最高效的“数据快递员”和“查询助手”。
3. 克制技术的炫技:Log 追踪、Trace 可视化、SQL 审计,这些枯燥的工程化细节,比花哨的 RAG 演示更能决定项目的生死。
如果你正准备转型,或者正在搭建自己的 AI 数据产品,请记住:在生产环境,一个能稳定执行 90% 正确查询的 Agent,远胜过那个能回答 100% 问题但经常出错且无法追溯的“聪明”模型。
技术没有银弹,只有取舍。而在大模型时代,最大的取舍,往往是把“智能”交给人类判断,把“效率”交给机器执行。
资料展示
下面是我整理的AI大模型学习资料和工具包预览,适合收藏后按主题逐步学习。
如果你想看完整资料目录,可以在评论区留言「资料」;也欢迎告诉我你更关注AI大模型里的哪类内容。