三个月前,我们数据分析组的小张还在一张张地写SQL报表,接口测试脚本每次接口一改就得跟着改一大片,他甚至为了一个复杂的多表关联查询憋了一下午。后来我把手里那套基于大模型Agent的无脚本自动化框架开放给组里试用,他的第一反应是“这玩意儿能听懂我在问什么?”——等他在对话框里用大白话敲出“我要看上个月华东区每个品类的销售额环比,异常下降的标出来”然后直接拿到一张可用的图表和一段可复查的SQL时,他愣了半天。这就是自然语言驱动的无脚本自动化:把意图理解、任务规划、工具调用这一整套流程交给Agent,让用户不再写代码、不再写脚本,只用说话就能完成从查数到跑测试再到操作网页的完整闭环。
这篇文章是我自己把这套东西从0到1落地的全过程复盘,包括Agent的核心架构、自然语言转SQL的链路设计、生产环境的安全护栏,以及如何把同一套能力泛化到自动化测试和RPA场景。我会把关键代码、Prompt设计、权限模型和踩过的坑都放进来说清楚,适合正在做AI自动化落地、想用大模型改造内部工具链的工程师和数据同学参考。如果你只是想了解概念,这文章也能帮你看到“无脚本自动化”到底能省下什么、哪些事不能省。
1. “无脚本”不是没有脚本:先说清楚边界再谈自动化
很多人一听“无脚本自动化”,第一反应是彻底告别代码、告别脚本,从此一切自动化都由AI包办。这种理解既是趋势,也是坑。我落地这半年最深的体会是:无脚本是相对使用者而言的,不是相对系统而言的。
1.1 无脚本到底省掉了哪层东西
传统自动化的核心资产是脚本:SQL脚本、Python脚本、Selenium用例、Jenkins流水线声明、RPA流程配置。这些脚本有几个共同问题——有学习成本、有维护成本、有环境依赖。你写一段自动化脚本可能要一小时,维护它可能要一整个迭代周期。最崩溃的是业务规则一变,改脚本的时间比重写一遍还长。
无脚本自动化的思路是把脚本这层从用户侧抹掉,用户只提供自然语言描述,系统内部的Agent负责把意图解析成可执行的计划,再调用底层工具去执行。比如用户说“每天上午10点把销售日报发到钉钉群”,传统做法是写一个Python脚本挂到定时任务里,再写个钉钉机器人推送。无脚本化之后,Agent会自己拆解出“定时触发、查数、汇总、推送”这几个步骤,并调用对应的工具函数完成。用户不需要知道SQL怎么写,更不需要知道发钉钉消息要调哪个SDK的哪个方法。
但系统内部依然有脚本,只是这些脚本从“用户维护”变成了“平台沉淀”。我做的框架里,底层工具都是可复用的原子操作函数,Agent负责把它们按需组合。这些原子操作本身需要工程师写代码,但这是一次性成本,之后所有人都在复用。
1.2 为什么是现在才可能做这件事
早些年也有一堆“自然语言转SQL”的尝试,效果普遍很差,核心原因是当时的模型对复杂语义和长上下文的建模能力不够。到了大模型时代,尤其是支持Function Calling和ReAct模式的模型成熟之后,Agent可以做到:
- 理解用户的话并拆解出明确的执行步骤;
- 判断每一步需要调用哪个工具、传什么参数;
- 执行后读取结果,判断是否达到目标;
- 结果不对时自己修正策略,具备基础的反思和重试能力。
这套能力使得自然语言不再是仅供查询的玩具,而是可以驱动机器执行真实业务的接口。注意,它并不聪明到可以完全自主处理一切异常,但配合良好的工具设计和人机校验机制,落地是可行的。
1.3 三条技术路线的选型对比
我做过三轮方案对比,分别是纯Prompt套壳、单Agent+Function Calling、多Agent协作。这里把差异讲清楚,帮你少走弯路。
| 路线 | 核心思路 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| 纯Prompt套壳 | 把用户问题和工具说明都塞进上下文,让模型输出JSON格式指令,外部解析执行 | 实现最简单,几小时能跑通Demo | 工具一多上下文爆炸、格式不稳定、无法处理连续多步任务 | 极少数固定工具的轻量场景 |
| 单Agent+Function Calling | 模型内置工具定义,模型自主决定调用哪个函数,循环执行直到完成任务 | 任务分解和执行连贯,工具扩展容易,有反馈修正能力 | 长任务容易出现错误累积,复杂场景下模型可能陷入重复循环 | 单领域多工具的自动化,是我最终选用的路线 |
| 多Agent协作 | 分为Planner、Executor、Validator等多个角色Agent,各司其职 | 复杂任务拆分清晰,各环节可独立优化,适合大规模流程 | 架构重、调试成本高、token消耗大得多 | 跨部门级复杂流程编排 |
我最终选了单Agent+Function Calling作为主架构,原因很实际:我们的场景以“查询-分析-执行-反馈”为主线,单Agent的循环足够覆盖,而且多Agent之间本身也存在上下文传递的损耗,调试起来让人头大。先跑通单Agent,后续再按需拆成Planner和Validator,比一开始就上重架构要稳。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 自然语言转SQL:Agent架构里的核心链路拆解
“Agent实现把自然语言转换成SQL”是最近社区里最火的方向之一,也是我这个无脚本框架里最先落地、反馈最好的一块。很多人以为这事就是把表结构丢给大模型然后让它输出SQL,实际远没有这么简单。我趟了大概两周的坑,才把这条链路跑到生产可用。
2.1 先让Agent认识你的数据:Schema上下文工程
模型不知道你库里有几张表、每张表什么含义、字段是什么业务口径。如果你只把“users”“orders”这种表名和字段名丢给它,它生成SQL大概率是错的,因为字段名是缩写、是英文、是历史遗留命名,甚至同一个字段在不同表里含义完全不同。
我的做法是建一个Schema管理模块,把以下信息全部喂给Agent:
- 表注释和字段注释(从数据库元数据同步过来);
- 字段类型和枚举值字典(比如status字段的0、1、2分别代表什么);
- 常用查询条件模板(比如“上个月”对应的时间函数写法);
- 业务口径说明(比如“销售额”是按订单支付时间还是创建时间统计);
- 表与表之间的关联关系。
这一步是自然语言转SQL质量的分水岭。Schema信息不全,模型再强也是盲猜;Schema信息到位,模型生成的SQL在语义上先对了一半。
2.2 表召回:别把整个库都塞给模型
十几个表的业务库还好,一旦库里有上百张表,把所有Schema定义都塞进Prompt,上下文直接爆掉,而且无关表的噪音会严重干扰模型判断。我针对性做了表召回:把每张表的注释、字段注释、业务标签做向量化,用户提问时先做向量检索,把最相关的三五张表和字段定义拿出来,再拼接进Prompt。
举一个实际的例子。用户问“上周各渠道的新增用户数对比”,如果全库塞进去,模型可能选中了USER_ACTION_LOG这种埋点大表;用表召回之后,优先命中“用户注册记录表”和“渠道维表”,生成的SQL正确率明显提升。这个检索模块用常见的Embedding模型就行,不需要专门训练,语义相似度匹配在这类场景下效果足够好。
2.3 让Agent自己决定怎么查:Function Calling的妙用
我把查询能力封装成一个函数:execute_sql(sql),Agent在理解用户问题后,自己决定要不要调用、SQL怎么写、参数怎么填。关键点是,Prompt里明确要求Agent在生成SQL时遵循几条硬规则:
- 只做查询,不做修改(防止UPDATE/DELETE/DROP这类危险前缀);
- 默认加LIMIT,除非明确要求全量导出;
- 涉及聚合统计时必须明确GROUP BY字段;
- 若用户用词模糊(比如“最近”“大概”),先通过上下文推断,仍不确定则反问。
2.4 一个Agent循环的伪代码示例
这是我对Agent循环最简单的抽象,单Agent方案的核心就是这么一小段逻辑:
python复制import json
from llm_client import chat_with_function_calling
from db_client import execute_sql, get_schema_by_relevance
TOOLS = [
{
"name": "execute_sql",
"description": "执行一条只读SQL查询,返回查询结果。仅支持SELECT语句。",
"parameters": {
"type": "object",
"properties": {
"sql": {"type": "string", "description": "要执行的SQL语句"}
},
"required": ["sql"]
}
}
]
def nlu_to_sql_agent(user_question: str):
schema_context = get_schema_by_relevance(user_question)
messages = [
{
"role": "system",
"content": (
"你是数据库查询助手。请根据用户问题、表结构上下文和枚举值字典,"
"判断是否需要调用execute_sql来回答问题。"
"生成SQL时严格遵守:只读、加LIMIT、明确GROUP BY字段。"
)
},
{
"role": "user",
"content": f"表结构上下文:\n{schema_context}\n\n用户问题:{user_question}"
}
]
max_rounds = 5
for _ in range(max_rounds):
response = chat_with_function_calling(
messages=messages,
tools=TOOLS
)
if response.function_call:
func_name = response.function_call.name
func_args = json.loads(response.function_call.arguments)
if func_name == "execute_sql":
sql = func_args["sql"]
try:
result = execute_sql(sql)
except Exception as e:
# 把错误信息回传给模型,让它修正SQL后重试
messages.append(response.assistant_message)
messages.append({
"role": "tool",
"name": func_name,
"content": f"SQL执行报错:{e}"
})
continue
# 结果也回传给模型,由模型判断是否已经能回答问题
messages.append(response.assistant_message)
messages.append({
"role": "tool",
"name": func_name,
"content": json.dumps(result, ensure_ascii=False)[:3000]
})
else:
# 模型不再调用工具,直接给出最终回答
return response.content
return "超过最大重试轮数,无法完成查询。"
这个循环里最关键的不是模型本身,而是把“执行报错信息”和“执行结果”回传给模型。第一次生成的SQL有语法错误不要紧,只要数据库报错信息足够明确,模型看过之后大多能自我修正。这就是Agent和普通NL2SQL的最大区别:它会试错,会给结果反馈,而不是一次性生成就完事。
2.5 不是所有SQL都要从零生成
我在实际使用中还做了一个模板复用层:高频问题能命中历史SQL模板的,直接套模板,只有模板覆盖不到的才走完整生成链路。比如“某渠道某时间段的销售额”“某产品线某指标的趋势”,这类问题90%是固定模式,模板复用的好处有二:一是快,二是稳,不会因为模型微小的输出波动导致SQL风格漂移。
模板层我用的是简单的规则匹配+向量召回双重判断。系统里每一条历史SQL都打上“业务域+查询指标+时间粒度”的标签,新问题进来先做标签匹配,命中阈值以上直接用模板,否则交给Agent现场生成。跑了一段时间后,模板命中率能达到六成以上,这块是整个系统响应速度能控制在几秒内的关键。
3. 让AI敢在生产库上跑:安全护栏与SQL质量保障
自然语言转SQL做到能跑是一回事,敢让它在生产库上跑是另一回事。我自己经历过一次后怕的事:测试阶段Agent生成了一条没有WHERE条件的UPDATE语句,虽然被权限系统挡下来了,但那次之后我把安全设计提到了最高优先级。
3.1 最小权限原则:从源头掐死危险操作
数据库账号权限是最底层、最有效的一道防线。我给Agent专用的数据库账号只开了SELECT权限,UPDATE、DELETE、INSERT、ALTER、DROP一律不授权。这样即使Agent真的生成了一条危险的SQL,数据库也会直接拒绝,不会造成数据损失。
这里有一条容易被忽视的细节:如果库里有存储过程、自定义函数,需要检查这些对象执行时是否会修改数据,因为部分数据库的EXECUTE权限可能绕过表级只读限制。最稳妥的做法是给Agent单独使用一个只读副本或只读从库,连主库都不碰。我就是把查询链路全部切到只读从库之后,才真正放心放开给组内使用的。
3.2 强制注入安全边界:LIMIT、超时、行数控制
权限之外,SQL语句本身也要做规范化处理。我的执行层会把生成的SQL做一次静态检查,用正则和解析器双重判断,执行前强制补充或改写几处:
- SQL语句开头必须是SELECT或WITH别名开头的查询语句;
- 自动追加LIMIT 200,若原本已有LIMIT则取两者较小值;
- 查询操作统一走短超时连接,比如10秒没有返回结果就取消;
- 返回结果集大小做截断,防止单条超大文本拖垮内存。
这些控制看起来很基础,但他们就是防止模型幻觉滑向灾难的底气。我在实际运行里见过Agent生成SELECT * FROM a_table JOIN b_table ON 1=1这种笛卡尔积查询,如果没有LIMIT和超时兜底,数据库CPU可能直接被拉满。
3.3 危险词和敏感字段的识别与拦截
规则之外,我还加了一层语义识别拦截。通过一个轻量分类模型和关键词规则结合,对Agent生成的SQL做二次检查,命中以下任一情况就拒绝执行:
- 涉及敏感字段,比如手机号、身份证、银行卡号等,除非用户角色有明确权限;
- 出现“全表查询且无WHERE且表记录数大于10万”的模式;
- 涉及用户隔离数据但SQL里没有租户过滤条件。
这层拦截会把拒绝原因返回给Agent,让Agent尝试补充条件重新生成。比如业务同学问“全国所有的订单金额”,Agent可能只生成SELECT SUM(amount) FROM orders,这在权限规则里属于越权范围,平台会提示“仅显示当前账号权限范围内的汇总”。后来Agent就学会了自动加上WHERE tenant_id=当前账号这类条件。
3.4 质量评测集:让每次Prompt改动都有据可依
自然语言转SQL是个典型的生成式任务,最怕的不是偶尔错,而是“这次改完Prompt,之前对的案例变错了”。所以从第一天起我就维护了一套回归评测集,大约200条问题和对应的正确SQL。每次调整Schema上下文逻辑、改Prompt、换模型版本,都用这套集子跑一遍,统计SQL正确率和执行成功率。
评测结果直接决定我是否敢上线。我的验收线是“核心30条用例必须100%通过,全量200条正确率不低于90%才允许灰度发布”。这个标准并不高,但能很大程度避免模型升级带来的隐性回归。等后期积累的数据量上来了,这200条还可以拆成多个维度的分级评测集。
3.5 危险操作分级:什么自动跑,什么必须人工审批
我最终落地的执行策略是分级:
| 风险等级 | 操作类型 | 执行方式 |
|---|---|---|
| 低风险 | 单表查询、带明确条件、聚合统计 | 自动执行,结果直接返回 |
| 中风险 | 多表关联、子查询、模糊条件 | 自动执行,并附带SQL供用户查看确认 |
| 高风险 | 涉及敏感字段、超大规模表扫描、导出行 | 先冻结,人工审批后才放行 |
这套分级不是靠模型自己判断的,而是规则引擎结合表元数据和权限系统算出来的。模型永远不知道自己在哪个风险等级,它只负责生成SQL,真正放不放行由平台说了算。这符合自动化系统设计的核心原则:决策者必须明确,AI只是建议者和执行者,而非最终判断者。
4. 从查数到干活:自然语言驱动测试脚本与RPA流程
SQL查询只是无脚本自动化的第一个落点。春节前后我把这套Agent能力扩展到了自动化测试和RPA(机器人流程自动化)方向,反响比预想的还大。核心思想一脉相承:把用户意图解析成任务计划,然后调用封装好的原子操作。
4.1 自然语言驱动自动化测试:从用户故事到可执行用例
测试团队天天在维护自动化用例,尤其是Web UI自动化,页面一改定位符全废。我用Agent做了一个“从需求描述直接生成并执行测试”的工具,流程是:
- 用户用自然语言描述需求,比如“验证登录页输入错误密码时提示‘账号或密码错误’”;
- Agent拆解为测试步骤:打开登录页、输入用户名、输入错误密码、点击登录按钮、检查提示文案;
- Agent调用浏览器操作工具执行步骤;
- 执行结果自动与预期对比,每一步生成带截图的报告。
这里和传统“AI生成Selenium代码”有本质区别:不是先生成一段Python脚本再丢给执行器跑,而是Agent直接驱动浏览器操作API,边想边做边验证。脚本只是中间态,甚至不是必须的中间态,Agent自己就是执行器。
底层我用的是Playwright的CDP连接能力,把浏览器操作封装成若干个可被Function Calling调用的工具,包括click、fill、goto、get_text、wait_for_selector等。Agent规划步骤时自动选择工具,执行过程里如果发现元素定位失败,它还能根据页面文本自己调整选择器。
4.2 断言缺失是AI生成测试用例最大的坑
AI生成的测试步骤往往能跑通,但断言部分经常缺斤少两。模型生成的步骤偏重“操作过程”,对“结果验证”的重视程度远不如人。这直接导致一个现象:用例跑完全是绿,但实际功能是坏的——因为根本没验到点子上。
我的对策是在工具层和Prompt层双管齐下:Prompt里强制要求每个场景必须包含至少一个验证性工具调用(assert_text、assert_visible、assert_url等),并且工具层设计了断言失败即中止返回失败信息。刚开始模型偶尔还是会生成“只点按钮不验证结果”的用例,于是我又加了一个独立Validator环节,专门让模型检查“生成步骤里是否覆盖了需求中所有可验证的点”,缺了就补。效果提升非常明显。
4.3 无脚本RPA:让流程描述直接变成可执行的自动化
RPA领域我看了一圈市面产品,包括最近的autoflow、aibote这类工具,思路都在往“用自然语言定义流程”上靠。我自己也做了一个小范围验证,用来处理办公场景里的重复操作:比如“把邮箱里标题含‘日报’的附件下载下来,汇总到本地Excel,再发送企业微信提醒”。
这个场景拆解出来是固定的几步:读邮件列表、匹配主题、下载附件、解析Excel、汇总写入、发送消息。传统RPA需要拖拽流程节点或者录制一遍操作,我这边变成了一段自然语言描述,Agent自己拆分步骤并调用封装好的工具。开发这个功能时最费劲的不是Agent本身,而是把各个办公系统都封装成可靠的API工具。
4.4 任务的原子操作要足够“薄”
做RPA自动化时我踩了一个重要的设计坑:工具封装太厚,导致Agent没得选择。一开始我把“下载日报并汇总”做成了一个大函数,Agent只会一把梭,用户稍微改一点需求,比如“只汇总最近三天的”,函数就没法应对。
后来我把工具拆薄:list_mail_titles()、download_attachment(mail_id, keyword)、read_excel_to_df(path)、append_df_to_excel(df, target_path)、send_wecom_message(text)这种级别。每个工具只做一件事,参数明确,Agent通过组合这些原子工具来完成复杂流程。工具越薄,Agent的规划能力越能发挥出来,适应需求变化的能力也越强。
5. 落地半年踩过的坑:从SQL方言到上下文爆炸
这半年我在整个系统上踩的坑,比写功能的时间还多。有几个问题非常典型,值得单独拎出来说,因为它们不是个例,是自然语言自动化落地时必然要面对的通病。
5.1 SQL方言差异:生成的SQL在MySQL上能用,在数仓上就不行
我们的数据环境比较复杂,MySQL、PostgreSQL、ClickHouse都有。同一句“取上个月最后一天”,三种数据库写法完全不同:MySQL里是LAST_DAY(DATE_SUB(CURDATE(), INTERVAL 1 MONTH)),PostgreSQL里是(date_trunc('month', now()) - interval '1 day')::date,ClickHouse里又是toLastDayOfMonth(now())。模型如果不被告知目标数据库类型,生成的SQL经常会帮你在错误的地方用错误的语法。
我的做法是把数据库类型和方言提示注入到系统Prompt里,并且在表召回结果中同步标注“该表所属数据源类型”,让模型针对性地生成对应方言。另外每个SQL执行器在上报错误时,也要带上“SQL语法错误,emphasize方言不匹配”这类提示,帮助模型在重试时自动切换写法。
5.2 枚举值的歧义:用户说“已完成”,库里存的是1
这是自然语言转SQL里让我最头疼的一类问题。业务同学问“查已完成的订单”,可能对应库里status=1,也可能是status=COMPLETED,还可能是status IN (1,2,3)里的多个取值(比如包含已发货和已签收)。模型如果不知道枚举值映射规则,就会生成一个看似合理实则错误的过滤条件。
我在Schema上下文里额外维护了一份“业务词-枚举值映射表”,专门喂给模型。比如“已完成=1、已失败=2、已退款=3”这种,都会在枚举字段描述里明确列出。同时我在Prompt里加了一条规则:过滤字段是枚举类型时,必须先从映射表中查询合法取值,不知道不猜,宁可反问也不瞎写。
5.3 相对时间表达:今天、上个月、近一周到底怎么算
相对时间是另一个高频错误来源。用户说“近一周”,模型往往写成NOW() - INTERVAL 7 DAY,但也可能跑到昨天;用户说“上个月”,模型需要知道目标库里“自然月”的定义,还要注意月初月末边界。这类问题如果每次都靠模型现场发挥,结果非常不稳定。
我后来在Prompt里固定了时间语义规范:“近一周=从今天往前推7天(不含今天),上个月=自然月的1号到月末,本年度至今=1月1日至今天”,并配了示例SQL。即便如此,模型偶尔还是会偏移,所以我在执行层加了一个“时间条件后校验”,把Agent生成的WHERE条件里的时间区间提取出来,和用户问题里的时间词做比对,偏差超过1天就触发重新生成。
5.4 表太多、上下文爆炸:向量召回不是一劳永逸
前面说了用向量召回解决表过多问题,但后来我遇到更复杂的情况:有些问题涉及的表压根不在用户描述里出现,比如用户问“各渠道成本收益”,可能是“渠道明细表”关联“成本表”再关联“收益汇总表”,向量召回时这三张表任何一张单独和问题的相似度都不高,导致召回不完整,模型就不知道要JOIN哪个表。
我加了一个“关联表扩展”的步骤:命中主表之后,根据元数据里维护的表关系图谱,把关联度较高的二跳表也加进上下文,再让模型去选。并且我在Schema里给每张表增加了“常用关联表”字段,比如订单表会标注“关联用户表、商品表、渠道表”,这样召回模块可以在主表命中后自动补充关联表信息。
5.5 AI生成的测试用例“假绿”:必须单独做断言兜底
前面提过断言缺失的问题,这在UI测试场景里尤为致命。AI生成用例的执行成功率其实不低,但对“验证什么”的理解经常停留在表面。比如验证登录成功,AI可能只检查首页元素是否出现,而没检查用户昵称是否正确,或者没检查跳转URL是否带了身份参数。
我给测试模块加了一个断言补全机制:Agent执行完操作步骤后,再让它基于原始需求生成一组“关键验证点”,并映射到具体的UI元素或接口返回值上。执行器和验证器分离,执行只管操作,验证只管结果。一旦验证点缺失,测试报告会明确标红“未覆盖需求点”,而不是假装全绿。
5.6 长链路任务的错误累积:越到后面越容易跑偏
单Agent在长任务上最大的问题是错误累积:前面一步执行的结果有偏差,后面每一步都基于偏差继续,最后输出离用户的真实意图越来越远。我处理这个问题的办法是“中途校验点”:Agent每完成一个关键步骤,就暂停一次,把当前已收集到的信息和用户确认,确认通过才继续。
这种方式牺牲了一点自动化程度,但对生产场景来说非常值得。比如“批量读取20封邮件后汇总”,Agent每读5封邮件就会简要汇报一次“已读取5封,包含2封日报、3封周报,是否继续”,用户在界面上点一下确认即可。这个设计让长任务的出错成本大大降低,也给了用户随时打断纠偏的机会。
最后再分享两个我在落地过程中的实在体会
第一件事,权限模型一定要在最开始就做绝。任何自然语言自动化系统,用户一旦对AI产生信任,就会把越来越危险的需求交过来。AI本身没有敬畏心,规则和权限才是它的敬畏心。数据查询先锁只读,文件操作先锁目录,所有跨系统动作默认走审批——这些约束加在后端,而不是靠说服模型自觉。
第二件事,从最窄、最痛、最低风险的场景切入。我的SQL查询模块能在组里快速推广,是因为它一开始只面向“销售数据查询”这一个场景,表就十张,权限很清晰,用户也确实是每天都在被SQL折磨的人。先让一小批人用爽,形成口碑,再去扩展测试、RPA这些更复杂的场景,远比一上来就铺全流程要稳妥得多。步子大了,真的容易扯到权限和数据安全这些最疼的地方。
