ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

基于OpenAI Agents API构建企业级自然语言取数Agent实战

基于OpenAI Agents API构建企业级自然语言取数Agent实战 数据分析这件事最怕的不是没数据而是业务方一句帮我拉个数之后你得反复确认口径、写 SQL、导 Excel、再解释为什么数字和上周对不上。我所在的团队每周要处理几十个这样的临时取数需求SQL 写得再快也扛不住这种碎片化的消耗。后来我们基于 OpenAI 的 Agents API 搭了一套自然语言取数 Agent业务同学直接用中文提问Agent 自己理解意图、生成 SQL、查库、把结果整理成表格返回整个过程还带权限校验和审计日志。这套东西上线三个月临时取数需求下降了大概七成剩下的三成基本是特别复杂的跨库分析。下面我把整套方案的搭建思路、踩过的坑和关键细节完整拆一遍适合有一定后端基础、想在企业内部落地 Text-to-SQL 能力的同学参考。1. 为什么选 Agents API 而不是自己拼 Prompt1.1 从一次性问答到多轮工具调用的认知转变很多人做 Text-to-SQL 的第一反应是写一个 System Prompt把建表语句塞进去让模型直接吐 SQL然后执行。这个方案在单表、简单查询上确实能跑但一旦涉及多表关联、字段歧义、需要先探查数据分布再决定怎么查的场景一次性生成就很容易翻车。原因很简单——模型在生成 SQL 之前根本没有机会看一眼真实的数据长什么样。Agents API 的核心价值在于它把思考—行动—观察这个循环做成了原生能力。你给它注册工具比如执行 SQL、查询表结构、采样数据模型会在需要的时候主动调用这些工具拿到结果后再决定下一步。这跟人写 SQL 的过程几乎一样先看表结构再采样几行数据确认字段含义然后写查询发现报错就改最后核对结果。这种多轮交互的能力是单纯拼 Prompt 做不到的。我实测下来同一个复杂查询涉及三张表关联加时间窗口聚合纯 Prompt 方案的首次正确率大概在 40% 左右而 Agents 方案能到 85% 以上。差距主要来自模型可以在生成前主动探查 schema 和数据样例。1.2 Agents API 相比 Assistants API 的关键差异OpenAI 之前有个 Assistants API也能做工具调用但我们最终选了 Agents API主要看中几点。第一是响应模式更灵活Agents API 支持流式输出中间步骤业务方能看到 Agent 正在查表结构执行查询等待过程不焦虑。第二是工具定义更贴近函数调用的语义参数 schema 用 JSON Schema 描述跟我们后端的类型系统对接很顺。第三是状态管理更清晰每一轮的工具调用和结果都有明确的结构方便我们做审计和回放。提示如果你之前用过 Assistants API迁移到 Agents API 时最大的变化是工具调用的返回结构。Assistants 用的是submit_tool_outputsAgents 这边更接近标准的 function calling 循环需要自己维护消息历史。别想着一步到位先把单轮工具调用跑通再往上叠。1.3 企业场景下安全可控到底指什么标题里我特意加了安全可控这不是套话。企业里让 AI 直接连数据库最怕的是三件事一是模型生成DROP TABLE或者UPDATE这种写操作二是越权查询普通员工问出了财务数据三是 SQL 性能失控一个全表扫描把生产库拖垮。这三件事必须在架构层面解决不能指望 Prompt 里写一句请不要执行危险操作就完事。我们的做法是三层防护SQL 解析层做语句类型白名单只允许SELECT权限层根据提问人的角色注入行级过滤条件执行层加超时和行数限制。这三层任何一层不通过请求直接拒绝模型连执行的机会都没有。后面会详细讲每一层怎么实现。2. 环境搭建PostgreSQL 与分析库的准备2.1 数据库选型与版本考量我们选 PostgreSQL 作为底层数据源一方面是团队本来就在用另一方面是它的information_schema和pg_catalog非常完善Agent 探查表结构、字段类型、索引信息时能拿到很全的元数据。版本上建议用 PostgreSQL 16 或 17这两个版本在并行查询和 JSON 处理上有明显优化Agent 生成的复杂聚合查询跑起来更快。如果你是在 Windows 上做本地开发PostgreSQL 有便携版可以直接解压运行不用装服务适合快速验证。生产环境还是老老实实走标准安装配好postgresql.conf里的shared_buffers、work_mem这些参数。我见过有人本地测试用默认配置work_mem只有 4MB稍微复杂点的排序就落磁盘误以为是 Agent 生成的 SQL 有问题排查半天才发现是数据库配置的锅。2.2 给 Agent 单独建一个只读账号这一步千万别省。不要用超级用户或者业务主账号去连库专门建一个只读角色CREATE ROLE agent_readonly WITH LOGIN PASSWORD your_strong_password; GRANT CONNECT ON DATABASE analytics TO agent_readonly; GRANT USAGE ON SCHEMA public TO agent_readonly; GRANT SELECT ON ALL TABLES IN SCHEMA public TO agent_readonly; ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO agent_readonly;最后那句ALTER DEFAULT PRIVILEGES很关键它保证以后新建的表也自动给这个角色只读权限不然每次加表都要手动授权迟早会漏。另外建议再配一个statement_timeout在角色级别限制单条查询最长执行时间ALTER ROLE agent_readonly SET statement_timeout 30s;这样即使 Agent 生成了一个性能很差的查询30 秒后也会被数据库自动掐断不会一直占着连接。2.3 元数据缓存表的设计Agent 每次提问都去查information_schema会很慢尤其是表多的时候。我们的做法是建一张元数据缓存表定时刷新Agent 探查表结构时直接查这张缓存表CREATE TABLE meta_table_info ( table_name TEXT PRIMARY KEY, table_comment TEXT, columns JSONB, row_count_estimate BIGINT, updated_at TIMESTAMP DEFAULT NOW() );columns字段用 JSONB 存字段名、类型、注释、是否可空这些信息。刷新脚本每天凌晨跑一次把information_schema.columns和pg_stat_user_tables的数据合并进去。这样 Agent 一次查询就能拿到整张表的完整画像比逐个字段去查快得多。实测在 200 张表的库上元数据查询从平均 1.2 秒降到了 80 毫秒。3. Agent 的核心工具设计与实现3.1 工具一schema 探查工具这是 Agent 最常用的工具输入是表名或关键词输出是匹配表的完整结构。实现上要注意两点一是支持模糊匹配业务方说订单表实际表名可能是orders或biz_order得能匹配上二是返回内容要精简别把整张表的几百个字段全塞回去按相关性排序优先返回被问到的字段。def explore_schema(keyword: str, max_tables: int 5) - dict: # 先按表名和注释做模糊匹配 matched search_meta_tables(keyword, limitmax_tables) result [] for t in matched: result.append({ table_name: t[table_name], comment: t[table_comment], columns: t[columns], # 已按相关性排序 row_count: t[row_count_estimate] }) return {tables: result}工具描述description要写得足够清楚告诉模型什么时候该用它。我一开始描述写得太简单模型经常跳过探查直接生成 SQL后来改成当你不确定表名、字段名或字段含义时必须先调用此工具确认禁止凭猜测生成 SQL命中率明显提升。3.2 工具二数据采样工具光看 schema 有时候不够比如一个status字段类型是integer但到底是 1 代表成功还是 0 代表成功光看结构看不出来。这时候需要采样几行真实数据def sample_data(table_name: str, limit: int 5) - dict: # 严格校验表名防止注入 if not is_valid_table(table_name): return {error: invalid table name} sql fSELECT * FROM {table_name} LIMIT %s rows execute_readonly(sql, (limit,)) return {rows: rows, columns: get_column_names(rows)}这里有个安全细节表名不能直接拼进 SQL必须先用白名单校验。我维护了一个合法表名的集合采样前先检查不在集合里直接拒绝。别小看这一步模型有时候会幻觉出一个不存在的表名如果不校验轻则报错重则被利用做注入。3.3 工具三SQL 执行工具带三层防护这是最核心也最危险的工具。我们的实现分三步走第一步用 SQL 解析库我们用的sqlglot解析生成的 SQL检查语句类型。只允许SELECT遇到INSERT、UPDATE、DELETE、DROP、ALTER等一律拒绝。同时检查有没有多语句用分号分隔的有也拒绝。import sqlglot def validate_sql(sql: str) - tuple[bool, str]: try: statements sqlglot.parse(sql, readpostgres) except Exception as e: return False, fparse error: {e} if len(statements) ! 1: return False, multiple statements not allowed stmt statements[0] if not isinstance(stmt, sqlglot.exp.Select): return False, only SELECT allowed return True, 第二步注入行级权限过滤。比如销售角色只能看自己区域的订单我们在解析出的 AST 上给涉及orders表的查询自动加上WHERE region xxx条件。这一步用 AST 改写比字符串替换靠谱得多不会因为子查询、别名搞乱。第三步执行时加限制。除了数据库层面的statement_timeout应用层再加一个LIMIT兜底防止返回几百万行把内存撑爆。我们默认限制 10000 行超过就截断并提示用户结果过多请缩小查询范围。3.4 工具四结果格式化工具Agent 拿到原始数据后需要整理成人能看的形式。这个工具负责把结果集转成 Markdown 表格同时做基本的数值格式化千分位、百分比、保留小数位。如果结果行数超过 50 行就只返回前 20 行加一个汇总统计避免刷屏。def format_result(rows: list, columns: list, max_rows: int 50) - dict: if len(rows) max_rows: preview rows[:20] summary compute_summary(rows, columns) return { preview: to_markdown(preview, columns), total_rows: len(rows), summary: summary, note: 结果较多仅展示前20行及汇总 } return {table: to_markdown(rows, columns), total_rows: len(rows)}4. 权限与审计让 Agent 在企业里敢用4.1 基于角色的行级过滤注入行级权限是整个方案里最需要仔细设计的地方。我们的做法是维护一张权限映射表记录每个角色对每张表的过滤条件角色表名过滤条件可见字段销售ordersregion {user_region}除 cost 外全部财务orders无全部运营usersstatus ! deletedid, name, created_at客服ticketsassignee {user_id}除 internal_note 外全部Agent 生成 SQL 后我们在 AST 层面遍历所有表引用查这张映射表把对应的过滤条件 AND 进去。字段级权限则通过改写 SELECT 列表实现把无权访问的字段替换成NULL或者直接剔除。这里有个坑如果用户问的字段恰好是他无权访问的直接剔除会导致 SQL 报错比如GROUP BY里引用了被剔除的字段。我们的处理是先检查查询涉及的所有字段只要有任何一个越权直接拒绝整个请求并提示您无权访问部分字段而不是偷偷改掉。这样行为可预期也避免用户拿到错误的数字。4.2 完整的审计日志设计每一次 Agent 交互都要留痕这是企业合规的硬要求。我们的审计表结构CREATE TABLE agent_audit_log ( id BIGSERIAL PRIMARY KEY, user_id TEXT NOT NULL, user_role TEXT NOT NULL, question TEXT NOT NULL, generated_sql TEXT, executed_sql TEXT, status TEXT, -- success / rejected / error reject_reason TEXT, row_count INT, duration_ms INT, created_at TIMESTAMP DEFAULT NOW() );注意generated_sql和executed_sql分开存。前者是模型原始生成的后者是经过权限注入和改写后实际执行的。排查问题时对比这两个能快速定位是模型生成错了还是权限改写出了问题。我们上线第一个月就靠这个对比发现了一个 bug某个角色的过滤条件注入时把OR条件错误地 AND 到了子查询外面导致越权。没有这个日志这种问题很难发现。4.3 敏感操作的二次确认机制虽然我们只允许SELECT但有些查询本身就很敏感比如导出全部用户手机号。这类查询即使语法合法也应该拦截。我们的做法是维护一个敏感模式列表比如查询结果包含手机号、身份证、银行卡字段且行数超过阈值时触发二次确认需要用户填写申请理由并通知数据管理员审批。这个机制不是技术难点难的是平衡体验和安全。阈值设太低正常查询天天被拦业务方会骂人设太高等于没有。我们最后定的规则是敏感字段 行数超过 1000 才触发上线后每周大概触发 3 到 5 次基本都能被合理审批误报率可以接受。5. 实测中暴露的问题与调优5.1 模型生成的 SQL 方言不匹配PostgreSQL 和 MySQL 在语法上有不少差异比如日期函数、字符串拼接、LIMIT写法。我们用的是 PostgreSQL但模型有时候会生成 MySQL 风格的 SQL比如用DATE_FORMAT而不是TO_CHAR。解决办法是在 System Prompt 里明确写清楚目标数据库是 PostgreSQL 16请使用 PostgreSQL 方言并且在工具返回错误时把数据库的报错信息原样返回给模型让它自己纠正。实测发现把报错信息返回后模型第二次生成正确的概率很高大概 90% 以上。所以不要怕报错报错反而是 Agent 自我修正的机会。关键是要把错误信息完整传回去别自己吞掉。5.2 复杂查询的拆解策略有些问题一句话里包含多个子问题比如上个月华东区销售额 top10 的品类以及这些品类各自的退货率。这种查询涉及聚合、排序、关联一次性生成容易出错。我们的做法是在 System Prompt 里引导模型遇到复合问题先拆成子问题逐个生成 SQL 执行最后汇总。为此我们加了一个计划工具模型可以先输出一个执行计划列出要执行的子查询我们审核后再让它逐个执行。这个工具在复杂查询上效果很好但会增加交互轮次简单查询就别用了不然响应变慢。5.3 缓存高频查询降低延迟企业里很多取数需求是重复的比如每天早上都有人问昨天的订单量。我们做了一个查询缓存把问题文本 用户角色作为 key缓存生成的 SQL 和结果有效期设成可配置默认 1 小时。命中缓存时直接返回响应从几秒降到几十毫秒。但缓存有个坑数据更新后缓存没失效会返回旧数据。我们的处理是对于涉及今天昨天这类时间敏感的问题缓存有效期缩短到 5 分钟同时提供一个手动刷新入口业务方发现数据不对可以强制刷新。另外缓存 key 里必须包含用户角色不然销售查到的结果可能被财务复用造成越权。5.4 处理agent execution terminated due to error这类中断Agent 执行过程中断是常见问题原因可能是工具超时、模型返回格式异常、网络抖动。我们的处理原则是任何中断都要有明确的用户提示不能静默失败。同时记录中断时的上下文方便复现。具体做法是给每个工具调用包一层 try-catch捕获异常后返回结构化的错误信息给模型让模型决定是重试还是放弃。如果连续三次工具调用都失败就终止整个会话返回查询失败请稍后重试或联系管理员。千万别让 Agent 无限重试那样既浪费 token 又拖慢响应。6. 上线后的效果与后续扩展方向6.1 真实数据准确率与效率的变化上线三个月我们统计了一组数据。简单查询单表、条件明确的首次正确率 96%复杂查询多表关联、聚合首次正确率 82%经过一轮修正后能到 94%。平均响应时间简单查询 2.3 秒复杂查询 8.7 秒。业务方自助取数的比例从 0 涨到了 68%数据团队从取数机器里解放出来开始做真正的分析工作。当然也有翻车的时候。有一次模型把一个LEFT JOIN写成了INNER JOIN导致结果少了一批数据业务方没发现直接用在了周报里。后来我们加了一个校验对于涉及关联的查询Agent 要额外执行一次行数对比如果关联前后行数差异超过阈值提示用户确认。这个校验又拦下了几次类似问题。6.2 从单库到跨库查询的演进现在这套方案只连了一个 PostgreSQL 库。下一步我们想扩展到跨库查询比如订单在 PostgreSQL日志在 ClickHouse。技术上的难点是 Agent 需要知道哪个问题该查哪个库以及跨库关联怎么做。初步想法是给每个库注册独立的工具集在 System Prompt 里描述每个库的职责让模型自己路由。跨库关联则通过把中间结果拉到应用层做 join 来实现虽然效率低点但胜在简单可控。6.3 给准备落地的团队几条实在建议第一别一上来就追求全自动。先做Agent 生成 SQL 人工审核执行的半自动模式跑一两个月积累足够的正确率数据再逐步放开。第二权限设计要前置不要等出了越权事故再补那时候改造成本高得多。第三审计日志从第一天就要有它不仅是合规要求更是你调优的唯一依据。第四多和业务方沟通他们的问题表述方式会直接影响 Agent 的理解效果收集一批典型问法去优化 Prompt比闭门造车强。我在实际运维中还发现一个小技巧把用户经常问的问题和对应的正确 SQL 整理成一个示例库作为 few-shot 示例动态注入到 System Prompt 里。这个库不用大二三十条高质量的示例就能显著提升模型对业务口径的理解。我们加了示例库之后涉及活跃用户有效订单这类有特定业务定义的查询正确率提升特别明显。这个库要持续维护每次发现模型理解错了口径就把正确写法补进去慢慢就形成了一个业务知识沉淀。
返回列表