phase 6 part 1: eight cross-stack enterprise scenarios and scenarios index
This commit is contained in:
1 parent
691f738f5f
commit
5b1a7a430f
12 files changed
+1145
-12
No files matched your search
@@ -0,0 +1,81 @@
|
||||
---
|
||||
id: scenario.report-agent
|
||||
title: 报表生成 Agent:自然语言查数、安全的 Text-to-SQL、结果校验、图表和成本控制
|
||||
type: scenario
|
||||
domain: cross
|
||||
stack: [agent-design, function-calling, postgresql, data-pipeline, web-security, react, agent-ops]
|
||||
tags: [Text-to-SQL, 报表, 只读账号, SQL 校验, 语义层, 行级安全, 评测]
|
||||
level: advanced
|
||||
prerequisites: [ai.agent-design.index, backend.postgresql.index]
|
||||
est_hours: 3
|
||||
related: [ai.agent-design.scenario.report-agent, cs.web-security.scenario.ai-app-security, backend.data-pipeline.scenario.third-party-sync]
|
||||
week: [10, 13, 14]
|
||||
---
|
||||
|
||||
# 报表生成 Agent:自然语言查数、安全的 Text-to-SQL、结果校验、图表和成本控制
|
||||
|
||||
## 业务背景与问题
|
||||
|
||||
运营和商家希望用一句话查数据(“上个月各渠道的退款率,按周看趋势”)。早期原型直接让模型写 SQL 执行:
|
||||
|
||||
1. 生成的 SQL 有时口径错误,结果看起来合理但是错的;
|
||||
2. 有一次生成了没有租户条件的查询,看到了其他商家的数据;
|
||||
3. 复杂查询拖慢了业务主库;
|
||||
4. 一个问题调用模型多次,成本不可控。
|
||||
|
||||
## 约束
|
||||
|
||||
- 数据来自同步过来的订单库(见[数据同步](data-sync.md))。
|
||||
- 只读;结果要能解释(口径、SQL)。
|
||||
|
||||
## 方案设计
|
||||
|
||||
1. **语义层**:数据团队以 YAML 定义指标(退款率 = 退款订单数 / 支付订单数)和维度(渠道、周);模型优先选择“指标 + 维度 + 过滤条件”,由代码生成 SQL;覆盖不了的才让模型写 SQL。
|
||||
2. **SQL 安全**(见 [AI 应用安全](../topics/cs/web-security/scenarios/ai-app-security.md)):只读账号、只授权报表视图、连接只读副本;SQL 解析后只允许单条 SELECT 和白名单表;**租户隔离由数据库行级安全(RLS)保证**,会话变量由代码设置,模型无法绕过;强制 LIMIT、语句超时。
|
||||
3. **Agent 流程**:理解 → 澄清(时间范围不明确时先问)→ 选指标 / 生成 SQL → 校验 → 执行 → 结果检查(空结果、异常值)→ 图表配置和文字结论。见[报表 Agent](../topics/ai/agent-design/scenarios/report-agent.md)。
|
||||
4. **可解释**:附口径说明和 SQL(可折叠),用户可“报告错误”进入 Bad Case 库。
|
||||
5. **成本**:模型调用次数上限(中间件)、常见问题缓存、简单问题路由到小模型(见[多模型网关](../topics/ai/llm-serving/scenarios/multi-provider-gateway.md))。
|
||||
6. **评测**:问题 → 期望结果(不是期望 SQL),比较执行结果;CI 中运行。
|
||||
|
||||
## 关键代码
|
||||
|
||||
数据库层的租户隔离(PostgreSQL RLS):
|
||||
|
||||
```sql
|
||||
ALTER TABLE rpt_orders ENABLE ROW LEVEL SECURITY;
|
||||
CREATE POLICY tenant_isolation ON rpt_orders
|
||||
USING (tenant_id = current_setting('app.tenant_id'));
|
||||
GRANT SELECT ON rpt_orders TO report_reader; -- 报表账号只读,且不是表的所有者(所有者默认不受 RLS 限制)
|
||||
```
|
||||
|
||||
执行模型生成的 SQL(示意):
|
||||
|
||||
```python
|
||||
def run_report_sql(sql: str, tenant_id: str) -> list[tuple]:
|
||||
check_select_only(sql, allowed_tables={"rpt_orders", "rpt_refunds"}) # 用 SQL 解析库(如 sqlglot)做结构校验,不用正则
|
||||
with readonly_pool.connection() as conn, conn.transaction():
|
||||
conn.execute("SET LOCAL statement_timeout = '5s'")
|
||||
conn.execute("SELECT set_config('app.tenant_id', %s, true)", (tenant_id,)) # 事务内有效,由代码设置
|
||||
return conn.execute(f"SELECT * FROM ({sql}) AS q LIMIT 1000").fetchall()
|
||||
```
|
||||
|
||||
## 取舍
|
||||
|
||||
| 决策 | 选择 | 原因 |
|
||||
| --- | --- | --- |
|
||||
| 生成方式 | 语义层优先,SQL 兜底 | 口径一致、可控 |
|
||||
| 租户隔离 | RLS + 会话变量 | 不依赖模型写对条件,子查询 / CTE 也覆盖 |
|
||||
| 执行位置 | 只读副本 | 不影响主库 |
|
||||
| 评测方式 | 比较执行结果 | 同一问题有多种正确 SQL |
|
||||
|
||||
## 易错点
|
||||
|
||||
- 让模型自己写租户条件。
|
||||
- 用正则判断 SQL 是否只读(注释、多语句可绕过)。
|
||||
- 报表账号是表的所有者,RLS 不生效(需要 `FORCE ROW LEVEL SECURITY` 或换账号)。
|
||||
- 结果为空时模型“编”一个结论。
|
||||
- 只评测 SQL 能否执行,不评测结果是否正确。
|
||||
|
||||
## 面试怎么讲
|
||||
|
||||
> “报表 Agent 的核心是不信任模型生成的 SQL。常见指标走语义层由代码生成 SQL;必须让模型写时,解析后只允许单条 SELECT 和白名单表,租户隔离交给 PostgreSQL 的行级安全,会话变量由代码设置,模型绕不过去;只读账号连只读副本并设超时。评测集比较执行结果而不是 SQL 文本,每个回答附口径和 SQL,用户一键报错进 Bad Case 库。”
|
||||
Reference in new issue
Block a user