Files
zhuangang/topics/ai/agent-design/scenarios/report-agent.md
T

4.5 KiB

id, title, type, domain, stack, tags, level, prerequisites, est_hours, related, week
id title type domain stack tags level prerequisites est_hours related week
ai.agent-design.scenario.report-agent 运营报表 Agent:规划、只读 SQL 工具、结构化输出和任务进度 scenario ai
agent-design
langgraph
postgresql
fastapi
报表 Agent
Text-to-SQL
规划
结构化输出
任务状态
流式进度
advanced
ai.agent-design.react-planning
3
ai.agent-design.interview
ai.langgraph.persistence-hitl
ai.agent-ops.observability-cost
11
13

运营报表 Agent:规划、只读 SQL 工具、结构化输出和任务进度

业务背景与问题

运营每周要找数据组要报表(“9 月各大区 GMV 和环比”),排队要两三天。希望做一个报表 Agent:用自然语言提需求,自动查数、出图、写结论。原型的问题:

  1. 生成的 SQL 偶尔写错表名,或者把“退款订单”也算进了 GMV;
  2. 有一次生成了没有时间条件的查询,扫描了整张订单表,拖慢了业务库;
  3. 复杂报表要跑一两分钟,页面一直转圈,用户以为卡死了;
  4. 结论里的数字和图表对不上。

约束

  • 只能读,不能写;不能影响线上业务库。
  • 指标口径(GMV、退款率)以数据组的定义为准。

方案设计

  1. 工作流 + 局部 Agent:整体流程固定为“理解需求 → 规划 → 查询 → 出图 → 写结论”,只在“规划和查询”环节让模型多步探索。
  2. 指标口径作为 Skill:把指标定义、表结构、示例 SQL 写成技能文件,按需加载,而不是让模型猜。
  3. SQL 工具的安全边界:连接只读从库 / 数仓;只允许 SELECT(用 SQL 解析器校验,不靠字符串匹配);强制时间范围、LIMIT、语句超时(statement_timeout);先 EXPLAIN 检查预估行数,超过阈值拒绝。
  4. 结构化输出:结论用 Schema 输出(指标名、数值、来源查询 ID),数值必须来自查询结果——生成后程序校验每个数字都能在结果中找到。
  5. 任务状态和进度:任务表记录状态(排队 / 规划中 / 查询中 / 生成中 / 完成 / 失败),LangGraph 节点用 custom 流推送进度,前端通过 SSE 显示;失败的步骤可以重试,不用从头开始。

关键代码

只读 SQL 工具的校验(使用 sqlglot 解析 SQL,示意,API 以官方文档为准):

import sqlglot
from sqlglot import exp


def validate_sql(sql: str) -> str:
    tree = sqlglot.parse_one(sql, read="postgres")
    if not isinstance(tree, exp.Select):
        raise ValueError("只允许 SELECT")
    tables = {t.name for t in tree.find_all(exp.Table)}
    if not tables <= ALLOWED_TABLES:
        raise ValueError(f"不允许访问的表:{tables - ALLOWED_TABLES}")
    if "created_at" not in sql:
        raise ValueError("必须带时间范围条件")
    if not tree.args.get("limit"):
        tree = tree.limit(1000)
    return tree.sql(dialect="postgres")


async def run_query(sql: str) -> list[dict]:
    sql = validate_sql(sql)
    async with readonly_pool.connection() as conn:
        await conn.execute("SET statement_timeout = '15s'")
        return await (await conn.execute(sql)).fetchall()

取舍

选择 优点 缺点
工作流 + 局部 Agent(本方案) 可控、可展示进度、容易评测 灵活性有限
完全自主的 Agent 能处理开放问题 难控制成本和正确性
预定义指标 API(语义层),不让模型写 SQL 口径一致、最安全 需要先建设指标体系,覆盖不到的问题答不了
模型直接写 SQL 覆盖面广 口径和安全风险,需要本方案中的护栏

易错点

  • 只用字符串判断 SELECT,被 WITH ... DELETE 或多语句绕过:用解析器,并且数据库账号本身只读。
  • 连接业务主库查询。
  • 结论中的数字由模型“复述”,不校验来源。
  • 长任务没有进度和超时,用户反复点击生成多个重复任务(要防重复提交)。

面试怎么讲

“报表 Agent 我没有做成完全自主的,而是固定流程,只在规划和查询环节让模型多步探索。指标口径写成 Skill 按需加载;SQL 工具连只读从库,用解析器只允许 SELECT 和白名单表,强制时间范围、LIMIT 和语句超时;结论用结构化输出,每个数字都要能在查询结果里找到;任务有明确的状态,进度通过 SSE 推给前端,失败的步骤可以单独重试。”