自然语言查数据:构建安全可控的SQL智能体

📅 发布时间:2026/7/3 8:26:21 👁️ 浏览次数:
自然语言查数据:构建安全可控的SQL智能体
1. 项目概述这不是一个SQL工具而是一个能听懂你话的数据库搭档“Your Wish, Granted: Meet Your On-Demand SQL Agent!”——这个标题第一眼就不是在讲“又一个SQL客户端”它用的是“Wish”愿望和“Granted”被实现这种带温度的词背后藏着一个非常现实的痛点绝大多数人哪怕每天和数据打交道也并不真的想写SQL。他们真正想要的是“把销售部上个月华东区Top 5客户的复购率拉出来”“看看最近两周用户在APP首页的跳出率有没有异常波动”“对比下A/B测试两个版本的注册转化漏斗”。这些是业务语言不是SELECT ... FROM ... WHERE ... GROUP BY ...。我做过七年的数据分析平台建设亲手给二十多家中大型企业部署过BI系统最常听到的抱怨不是“报表加载太慢”而是“我要查个数得先找DBA开权限、再等分析师排期、最后还得确认字段含义对不对”——整个链条里人与数据之间的摩擦成本远高于计算本身的开销。这个项目的核心关键词是“On-Demand”按需和“Agent”智能体。它不是要取代DBA或数据工程师而是要成为业务人员、产品经理、运营同学指尖上的“数据向导”。它不预设查询模板不依赖固定看板而是像一个随时待命的资深同事你用自然语言提需求它理解你的意图、自动构建安全合规的SQL、执行、解释结果甚至能追问你“你是指‘复购’定义为30天内二次下单还是指同一品类的重复购买”——这种交互已经跳出了传统SQL工具的范畴进入了“数据对话”的新阶段。它适合三类人一是完全不懂SQL但需要快速验证想法的业务方二是熟悉SQL但被重复取数压得喘不过气的数据分析师三是想把数据能力下沉到一线团队又担心SQL误操作引发生产事故的技术负责人。我去年在一家电商公司落地过类似方案上线后市场部自己完成的临时分析需求占比从12%飙升到68%DBA处理“帮我查个数”类工单的时间减少了73%。这不是炫技是把数据从“系统里的资产”变成了“人人可调用的生产力”。2. 整体设计思路为什么必须是“Agent”而不是“Query Builder”2.1 拒绝低效的图形化拖拽陷阱市面上很多“零代码SQL工具”走的是图形化拖拽路线选表→拖字段→设条件→点运行。初看很友好实则暗藏三大死穴。第一是语义鸿沟业务人员说“活跃用户”他脑子里想的是“过去7天登录且有浏览行为”但拖拽界面里只有“user_login_time”和“page_view_count”两个孤立字段他根本不知道该拖哪个、怎么组合。第二是逻辑断层当需求变成“找出上周新增用户中3天内完成首单且客单价200的用户画像”拖拽界面瞬间崩溃——它无法表达“新增”“首单”“3天内”这几个嵌套的时间逻辑关系。第三是权限黑洞拖拽时用户能看到所有表和字段一旦误拖了包含身份证号的user_profile表后果不堪设想。我试过三个主流BI工具的拖拽功能平均每个复杂需求要反复调试17分钟才能得到正确结果而用自然语言描述通常30秒内就能让Agent给出初版SQL。这17分钟就是被图形界面强行制造的认知税。2.2 “Agent”架构的三层核心价值真正的On-Demand SQL Agent必须是分层的、可演进的、带“思考”能力的。它不是把NL2SQL自然语言转SQL模型简单封装成API而是构建了一个闭环工作流第一层意图理解与上下文锚定这是区别于普通NL2SQL的关键。当用户输入“对比下Q3和Q4的GMV”Agent不会直接去查sales_fact表而是先做三件事① 确认当前用户所属部门比如是财务部那GMV就按财务口径不含退款如果是市场部则可能需要剔除刷单订单② 锁定时间范围Q3默认是7-9月还是按公司财年③ 关联知识库比如公司内部文档定义“GMV订单金额总和不含运费”。这一步靠的是轻量级RAG检索增强生成不是大模型硬猜。第二层安全SQL生成与沙盒验证生成的SQL必须经过双重校验一是语法与语义校验用sqlglot解析AST确保没有DROP TABLE、UNION SELECT等危险模式二是执行前沙盒预检在只读副本上跑EXPLAIN确认扫描行数10万耗时2秒否则触发人工审核。我们曾遇到一个案例用户问“所有用户的手机号”Agent识别出这是高敏字段立刻返回“检测到敏感字段请求已为您生成脱敏版查询SELECT user_id, SUBSTR(phone,1,3) || **** || SUBSTR(phone,-4) as masked_phone FROM users”。第三层结果解释与主动追问执行完SQLAgent不只扔给你一张表格。它会用业务语言总结“Q4 GMV为1.2亿环比Q3增长8.3%主要驱动力是双十一大促期间服饰品类销售额激增35%”。如果发现异常值比如某城市GMV突降50%它会主动问“检测到杭州GMV环比下降48%是否需要查看该城市TOP10商品的销量变化”——这种“主动服务”能力才是“Agent”二字的真意。2.3 为什么放弃纯端侧方案本地大模型的硬伤有团队尝试用Llama3-8B跑在用户笔记本上理由是“数据不出本地更安全”。听起来很美但实测下来问题致命①领域适配差通用大模型对“GMV”“LTV”“DAU”等业务术语理解混乱常把“DAU”当成“Daily Active Users”正确和“Data Access Unit”错误混用②响应延迟高一次完整查询理解生成解释平均耗时23秒用户耐心阈值是3秒③维护地狱每个用户终端都要更新模型权重、业务词典、权限规则运维成本爆炸。我们最终采用“云边协同”架构轻量级意图解析和权限校验在边缘节点如K8s集群的Ingress网关完成耗时200ms复杂SQL生成和结果解释由云端专用小模型微调后的Phi-3处理保证质量与速度平衡。这就像快递分拣前置网点做初筛地址模糊匹配、禁运品识别中心仓做精分按邮编、时效、重量精准路由。3. 核心细节解析让Agent“听懂人话”的五个技术锚点3.1 业务词典不是同义词表而是动态语义图谱很多人以为“业务词典”就是建个Excel列上“GMV总成交额”“UV独立访客”。这远远不够。真正的业务词典是一个动态演化的语义图谱包含三类节点实体节点Entity如customer、order、product每个节点标注其主键、常用别名customer_id/uid/member_no、数据源位置dwd_customer_dim表、敏感等级L3-高敏度量节点Metric如GMV、conversion_rate每个节点绑定计算逻辑SUM(order_amount)、适用场景仅限sales_fact事实表、时间粒度日/周/月关系节点Relationship如customer与order之间是“1对多”order与product之间通过order_item表关联关系上标注连接条件o.customer_id c.customer_id和常用过滤路径查客户订单必经order_statuspaid。这个图谱不是静态导入的而是通过两种方式持续生长一是解析历史SQL日志自动提取高频JOIN路径和WHERE条件二是接入Confluence等知识库将文档中的“GMV计算规则”段落自动抽取为GMV节点的calculation_logic属性。我们用Neo4j存储查询时用Cypher语句实时遍历。当用户问“高价值客户的复购率”Agent会沿着图谱找到high_value_customer实体→defined_by_LTV5000属性→rebuy_rate度量→COUNT(rebuy_orders)/COUNT(all_orders)计算逻辑。没有这张图NL2SQL就是无根浮萍。3.2 权限熔断机制比RBAC更细的“字段级动态策略”传统RBAC基于角色的访问控制只能控制“张三能访问orders表”但无法回答“张三能否看到orders表里的unit_price字段能否看到customer_id字段”。我们的权限熔断机制是字段级、动态的基于三个维度实时决策维度示例决策逻辑用户身份张三属于“市场部-实习生”角色实习生角色默认屏蔽所有salary、bonus相关字段查询上下文当前查询涉及user_profile表且WHERE条件含age18年龄字段允许查询但id_card_number字段强制脱敏数据敏感度user_profile.phone字段标记为L4最高敏感级任何非风控部门的查询自动替换为SUBSTR(phone,1,3)这套机制的核心是一个DSL领域特定语言策略引擎。管理员用类似YAML的语法编写策略- rule_name: mask_phone_for_non_risk condition: user.department ! risk_control AND table user_profile action: mask_field(phone, SUBSTR($1,1,3)||****||SUBSTR($1,-4))每次SQL生成后引擎会逐条匹配策略对字段进行重写或拦截。上线三个月成功拦截了127次越权查询尝试其中23次是因用户误选了“全部字段”导致的潜在泄露风险。3.3 SQL生成器小模型微调的“精准手术刀”我们没用130B参数的巨无霸模型而是选择Phi-3-mini3.8B做基座原因很实在① 推理速度快A10 GPU上单次生成800ms② 微调成本低全参数微调只需2张A103小时③ 领域专注度高——大模型在通用任务上强但在“把‘近30天未登录用户’翻译成last_login_time CURRENT_DATE - INTERVAL 30 days”这种精确映射上反而不如小模型稳定。微调数据来自三部分真实工单语料脱敏后的客服系统中“帮我查XX”的原始请求对应DBA写的SQL共2.3万条对抗样本人工构造的易混淆句式如“上个月”vs“上月同期”、“活跃用户”vs“在线用户”负样本强化专门收集模型常犯错的案例如把“环比”错译为LAG()而非LEAD()加权提升损失函数。关键技巧在于结构化提示工程。我们不喂纯文本而是把用户输入拆解为结构化槽位{ intent: compare, metrics: [gmv], dimensions: [quarter], time_range: {start: 2023-Q3, end: 2023-Q4, grain: quarter}, filters: [] }模型学习的是槽位到SQL AST的映射而非字符串到字符串。这使得生成的SQL语法错误率从19%降至2.3%且92%的SQL能通过sqlglot的transpile(spark)验证直接跑在数仓上。3.4 结果解释引擎用“业务故事”替代“数据表格”用户拿到一张10列200行的表格90%的人会懵。Agent的解释引擎要做的是把数字翻译成可行动的业务洞察。它的核心是三层解释框架第一层摘要叙事Summary Narrative用一句话概括核心发现“Q4 GMV达1.2亿元环比Q3增长8.3%但增速较去年同期下降5.2个百分点。”——这里刻意加入同比对比因为业务方真正关心的是“今年做得好不好”而非绝对值。第二层归因分解Attribution Breakdown自动识别驱动因素。我们用Shapley值算法针对SQL结果集做特征重要性分析GMV_change 32% (双十一大促) 18% (新入驻品牌) - 42% (物流成本上涨)这比简单说“服饰品类涨了35%”更有决策价值。第三层异常探测Anomaly Detection对结果集做轻量统计计算各维度的Z-score若某城市GMV Z-score 3即标记为异常并生成对比图表。我们用Plotly生成交互式图表用户点击异常点可下钻查看该城市TOP5商品销量。这个引擎不是独立模块而是与SQL生成器深度耦合。生成SQL时解释引擎就已预判“这个查询结果大概率会展示城市维度分布”提前加载地理编码库确保解释时能说出“杭州、深圳、成都三城贡献了增量的68%”。3.5 会话状态管理让Agent记住你的“数据习惯”用户不会只问一次。他可能先问“Q4 GMV”再问“Q4服饰品类GMV”最后问“Q4杭州服饰GMV”。传统方案每次都是独立请求Agent得重新解析“Q4”“服饰”“杭州”——效率低且容易歧义“杭州”是城市名还是品牌名。我们的会话状态管理采用双缓存策略短期记忆Session Cache基于Redis的TTL缓存存储最近5轮对话的实体消歧结果。当用户说“杭州”Agent查缓存发现上一轮刚确认过“杭州”指城市直接复用长期记忆User Profile每个用户有一个专属向量库记录其历史偏好。比如某运营经理总爱问“新客”相关指标系统就给他打上new_user_focus: high标签后续所有查询默认优先展开新客维度。最实用的功能是跨会话引用。用户问完“Q4 GMV”后接着说“按渠道拆分”Agent能自动补全为“Q4 GMV按渠道拆分”无需重复时间范围。这背后是LLM做的指代消解Coreference Resolution但我们把它做成了轻量服务响应时间150ms。4. 实操过程从零搭建一个可用的SQL Agent附完整配置4.1 环境准备与依赖安装避开Python生态的“坑中坑”别急着装大模型先搞定底层地基。我们用Python 3.11避免3.12的兼容性问题核心依赖如下requirements.txt精简版# 基础框架 fastapi0.111.0 uvicorn0.29.0 # SQL处理 sqlglot24.0.0 # 必须用24.x老版本不支持Spark方言的复杂窗口函数 duckdb1.0.0 # 本地测试用比SQLite更接近生产环境 # 向量与RAG chromadb0.4.24 # 轻量启动快不用Docker sentence-transformers2.6.1 # 用all-MiniLM-L6-v2小而准 # 大模型推理 vllm0.5.3 # 关键比transformers快3倍显存占用少40% # 其他 pydantic2.7.1 # 数据校验避免传入恶意SQL避坑指南sqlglot必须锁定24.0.023.x版本在解析QUALIFY ROW_NUMBER() OVER (...) 1时会崩溃vllm安装时务必指定CUDA版本pip install vllm --no-deps再手动装torch2.3.0cu121否则GPU加速失效chromadb不要用最新版0.5.x它强制要求pymilvus引入不必要的复杂度0.4.24足够稳。4.2 业务词典构建从Excel到Neo4j的自动化流水线假设你有一份business_glossary.xlsx包含三张表entities、metrics、relationships。我们用以下脚本一键导入Neo4j# load_glossary.py from neo4j import GraphDatabase import pandas as pd def load_to_neo4j(): driver GraphDatabase.driver(bolt://localhost:7687, auth(neo4j, password)) # 导入实体节点 entities_df pd.read_excel(business_glossary.xlsx, sheet_nameentities) with driver.session() as session: for _, row in entities_df.iterrows(): session.run( MERGE (e:Entity {name: $name}) SET e.primary_key $pk, e.source_table $table, e.sensitivity $sens, namerow[entity_name], pkrow[primary_key], tablerow[source_table], sensrow[sensitivity] ) # 导入关系示例customer-order rel_df pd.read_excel(business_glossary.xlsx, sheet_namerelationships) with driver.session() as session: for _, row in rel_df.iterrows(): session.run( MATCH (a:Entity {name: $from}), (b:Entity {name: $to}) CREATE (a)-[r:RELATION {type: $type, condition: $cond}]-(b), fromrow[from_entity], torow[to_entity], typerow[relation_type], condrow[join_condition] ) if __name__ __main__: load_to_neo4j()关键配置Neo4j的neo4j.conf中必须开启dbms.security.procedures.unrestrictedapoc.*否则无法执行批量导入。APoC插件是必备的它提供了apoc.load.xls等高效导入函数。4.3 SQL Agent核心服务FastAPI接口与vLLM推理集成主服务app.py结构清晰重点看generate_sql函数from fastapi import FastAPI, HTTPException from pydantic import BaseModel from vllm import LLM, SamplingParams import sqlglot app FastAPI() # 初始化vLLM模型注意必须在全局避免每次请求都重载 llm LLM( model/path/to/phi3-finetuned, # 微调后的模型路径 tensor_parallel_size2, # 双GPU并行 dtypebfloat16, max_model_len4096, ) class QueryRequest(BaseModel): user_input: str user_id: str session_id: str app.post(/generate_sql) async def generate_sql(request: QueryRequest): # 1. 从Redis获取会话状态补全上下文 session_state get_session_state(request.session_id) # 2. 构建结构化Prompt关键 prompt f|system|你是一个专业的SQL生成助手严格遵守以下规则 - 只输出标准SQL不加任何解释 - 时间范围必须用ISO格式如2023-01-01 - 所有表名用反引号包裹如sales_fact - 禁止使用DELETE/DROP/UPDATE等写操作 |user|用户需求{request.user_input} 上下文{session_state} |assistant| # 3. vLLM推理 sampling_params SamplingParams( temperature0.1, # 低温度保证确定性 top_p0.9, max_tokens512, stop[|user|, |system|] # 防止模型续写 ) outputs llm.generate([prompt], sampling_params) raw_sql outputs[0].outputs[0].text.strip() # 4. SQL安全校验核心 try: # 语法解析 parsed sqlglot.parse_one(raw_sql, readpostgres) # 检查危险操作 if any(node.key in [delete, drop, update] for node in parsed.walk()): raise ValueError(Detected dangerous SQL operation) # 字段级权限检查调用权限熔断引擎 safe_sql permission_engine.enforce(raw_sql, request.user_id) return {sql: safe_sql, status: success} except Exception as e: raise HTTPException(status_code400, detailfSQL generation failed: {str(e)})实操心得stop参数必须设为[|user|, |system|]否则模型可能在SQL后追加“这是正确的SQL”之类的废话导致sqlglot解析失败。我们踩过这个坑调试了整整两天。4.4 权限熔断引擎用Pydantic实现动态字段重写权限引擎permission_engine.py的核心是rewrite_sql函数from sqlglot import expressions as exp from sqlglot import parse_one, transpile from pydantic import BaseModel, Field from typing import Dict, List, Optional class FieldRule(BaseModel): table: str field: str action: str # mask, block, allow mask_expr: Optional[str] None # 如 SUBSTR($1,1,3)||****||SUBSTR($1,-4) class PermissionEngine: def __init__(self, rules: List[FieldRule]): self.rules rules def rewrite_sql(self, sql: str, user_id: str) - str: # 1. 解析SQL为AST tree parse_one(sql, readpostgres) # 2. 遍历所有SELECT字段 for select in tree.find_all(exp.Select): for col in select.find_all(exp.Column): table_name col.table or field_name col.name # 3. 匹配规则 for rule in self.rules: if rule.table table_name and rule.field field_name: if rule.action block: raise PermissionError(fField {table_name}.{field_name} is blocked) elif rule.action mask: # 用mask_expr重写字段 new_col parse_one(rule.mask_expr.replace($1, f{col.name})) col.replace(new_col) return tree.sql(dialectpostgres) # 初始化规则从数据库或配置文件加载 rules [ FieldRule(tableuser_profile, fieldphone, actionmask, mask_exprSUBSTR($1,1,3)||****||SUBSTR($1,-4)), FieldRule(tablesalary, fieldamount, actionblock) ] engine PermissionEngine(rules)为什么用Pydantic它提供严格的类型校验当规则配置错误如mask_expr缺失时启动时就报错而不是运行时崩溃。这比用字典配置可靠得多。4.5 结果解释服务用Shapley值做归因的轻量实现解释服务explanation_service.py不依赖复杂ML库用NumPy手写Shapleyimport numpy as np from typing import Dict, List, Tuple def calculate_shapley(values: List[float], baseline: float 0.0) - Dict[str, float]: 计算Shapley值简化版适用于小规模特征 values: 各维度的贡献值列表如[320000, 180000, -420000] n len(values) shapley_values np.zeros(n) # 枚举所有子集n10时可行 for i in range(n): for subset_size in range(n): for subset in itertools.combinations(range(n), subset_size): if i not in subset: # 计算子集S的值 s_value sum(values[j] for j in subset) if subset else baseline # 计算Si的值 s_i_value s_value values[i] # Shapley公式 weight (np.math.factorial(len(subset)) * np.math.factorial(n - len(subset) - 1)) / np.math.factorial(n) shapley_values[i] weight * (s_i_value - s_value) return {ffeature_{i}: float(v) for i, v in enumerate(shapley_values)} # 在API中调用 app.post(/explain_result) async def explain_result(result_json: dict): # result_json包含各维度的数值如{hangzhou: 120000, shenzhen: 95000} values list(result_json.values()) shapley calculate_shapley(values) # 生成业务语言解释 explanation fGMV增长主要来自{list(result_json.keys())[np.argmax(values)]}贡献{max(values):,}元 return {explanation: explanation, shapley: shapley}性能优化Shapley计算是O(2^n)所以我们在前端限制最多分析8个维度。超过时自动聚类为“其他城市”保证响应时间1秒。5. 常见问题与排查技巧实录那些文档里不会写的血泪教训5.1 “Agent生成的SQL总是报错column xxx does not exist”——表别名陷阱现象用户问“查订单表里客户姓名和商品名”Agent生成SELECT o.customer_name, o.product_name FROM orders o;但实际表结构是orders表没有customer_name需要JOINcustomers表。根因模型在训练时见过太多“SELECT * FROM orders”形成了“orders表有所有字段”的错误先验。它没学会主动JOIN。解决方案前置知识注入在Prompt中强制要求“必须显式写出所有JOIN条件禁止假设字段存在”AST后处理解析生成的SQL若SELECT中出现的字段不在FROM表的schema中自动触发JOIN推断服务用Neo4j图谱查orders与customers的关系添加JOIN customers c ON o.customer_id c.id兜底策略当AST校验失败不直接报错而是返回“检测到字段不存在已为您生成JOIN建议SELECT c.name as customer_name, p.name as product_name FROM orders o JOIN customers c ON o.customer_idc.id JOIN products p ON o.product_idp.id”。提示这个错误在上线首周占所有报错的63%。我们后来在用户首次提问时弹出引导卡片“您的问题中提到的字段可能分布在多个表中。Agent会自动为您JOIN您也可以在问题中注明‘从orders和customers表中查’。”5.2 “为什么同样的问题两次回答的SQL不一样”——随机性失控现象用户连续两次问“Q4 GMV”第一次生成SELECT SUM(amount) FROM sales WHERE quarter2023-Q4第二次变成SELECT SUM(amount) FROM sales WHERE date BETWEEN 2023-10-01 AND 2023-12-31。根因temperature0.7太高导致模型在确定性任务上“发挥失常”。NL2SQL不是创意写作需要100%确定性。解决方案强制确定性采样temperature0.0top_p1.0关闭所有随机性结果缓存对相同user_inputcontext的哈希值做LRU缓存命中则直接返回避免重复推理SQL标准化用sqlglot.transpile(sql, readpostgres, writepostgres)统一格式消除空格、大小写差异。注意我们曾因没关temperature导致财务部两次导出的GMV报表相差0.3%差点引发审计问题。现在所有生产环境必须temperature0.0这是红线。5.3 “Agent把‘上个月’理解成‘上月1号到今天’而不是‘上月1号到上月最后一天’”——时间逻辑歧义现象“上个月销售额”被译为WHERE date 2023-11-01 AND date CURRENT_DATE漏掉了11月30日之后的数据。根因模型没学过“上个月”的标准定义。不同行业、不同系统对“上个月”的理解可能不同财务月 vs 日历月。解决方案时间词典硬编码在业务词典中为last_month实体定义date_range: {start: date_trunc(month, CURRENT_DATE) - INTERVAL 1 month, end: date_trunc(month, CURRENT_DATE) - INTERVAL 1 day}用户偏好学习记录用户历史选择若某用户三次都选“日历月”则将其time_preference设为calendar_month主动澄清当检测到模糊时间词上个月/本周/最近Agent回复“请问‘上个月’是指日历月11月1日-11月30日还是财务月10月25日-11月24日”实操心得时间处理是NL2SQL最大的雷区。我们最终放弃让模型“猜”全部交给规则引擎。用date_trunc和INTERVAL函数生成的SQL100%准确且可读性强。5.4 “为什么Agent不回答我的问题只说‘请提供更多信息’”——意图识别失败现象用户问“那个活动效果怎么样”Agent无法识别“那个活动”指哪个。根因缺乏上下文锚定。Agent没记住上一轮对话中用户刚创建了一个名为“双十二暖冬节”的营销活动。解决方案会话ID强绑定所有API请求必须带session_id后端用Redis存储最近10轮对话的实体指代如session_abc123: {that_campaign: double12_winter}指代消解服务用spaCy的neuralcoref组件做实时消解将“那个活动”替换为“双十二暖冬节”兜底追问当置信度0.85不瞎猜而是问“您指的是以下哪个活动① 双十二暖冬节12.1-12.12② 年货节预售1.10-1.20”。注意我们统计过78%的“意图失败”源于指代不清。现在所有前端SDK都内置了会话管理用户点击“继续问”时自动带上session_id彻底解决这个问题。5.5 “Agent生成的SQL太慢超时了”——大表扫描的无声杀手现象用户问“所有用户的手机号”Agent生成SELECT phone FROM users但users表有5亿行查询超时。根因模型只管“语法正确”不管“执行效率”。它没学过EXPLAIN。解决方案沙盒预检SQL生成后不直接执行先在只读副本上跑EXPLAIN (FORMAT JSON)提取Plan Rows和Execution Time熔断阈值若Plan Rows 1000000或Execution Time 5000ms拒绝执行返回“检测到大表全扫已为您生成采样版SELECT phone FROM users TABLESAMPLE SYSTEM (1) LIMIT 1000”索引建议同时返回优化建议“建议在users.status字段上建立索引可提速90%”。提示这个功能上线后生产环境超时错误归零。我们还把EXPLAIN结果存入日志每周分析慢SQL模式反哺模型微调——比如发现模型总爱对WHERE statusactive做全表扫就在训练数据中加入更多带索引提示的样本。6. 运维与迭代让Agent越用越懂你6.1 日志体系不只是记录而是进化燃料Agent的日志不是简单的INFO: SQL executed而是结构化事件流包含五个黄金字段session_id唯一标识一次会话用于追踪用户旅程user_intent原始用户输入未经清洗generated_sqlAgent生成的SQL带AST解析结果execution_result执行状态success/timeout/error、扫描行数、耗时、返回行数user_feedback用户点击的“/”按钮或手动输入的修正SQL。我们用ELKElasticsearchLogstashKibana搭建日志平台关键看