- Introduced `battery_report.json` for local data analysis, excluding it from the database. - Enhanced `AGENTS.md` to reflect updated test baseline with 795 passed tests. - Added new metrics for trade flow in `dict_service.py`, improving transaction data analysis. - Updated regex patterns in `guardrail.py` to better handle numeric extraction and prevent misinterpretation of tokens. - Expanded course modules with new content on FR-8/9/10 capabilities and L3 role management. - Improved concurrency handling in transaction processing to ensure accurate alert generation. This update enhances data analysis capabilities and improves the overall structure and clarity of course materials.
9.2 KiB
数据分析 Agent · 代码迭代文档
演化记录:从空壳到可用的 NL2SQL Agent,含模块实现、测试基线、问题修复与待办。 关联:
docs/memory/FRAMEWORK.md(技术选型 / 实现状态)、docs/需求拆解/01-数据分析Agent需求规格.md(需求 D-xx)、docs/项目框架设计/数据分析Agent架构说明书.md。
1. 定位与范围
数据分析 Agent(AnalystAgent)把自然语言问数翻译为一条只读 SELECT 并执行,面对 jinrong_core(模拟 Core 库,L0 事实)与部分 jinrong_agent 表,最终输出「人话解读 + 数据表格 + 元信息」。当前为 analyst 角色全量(full 域)可跑的实现。
约束(不可越界):仅 SELECT 只读 · 不带处置(不写预警)· 数字不可编造 · 留痕审计。
2. 代码演进时间线
| 日期 | 里程碑 | Git | 说明 |
|---|---|---|---|
| 2026-09-05 | 项目脚手架 + 需求/框架定稿 | 0374010 1ddd44a |
FastAPI 分层 / 双库 / LangGraph 选型;分析 Agent 仅空壳 |
| 2026-09-09 | 分析 Agent 全套落地 | b19a241 |
api/service/schema/校验/护栏/缓存/字典/LLM + tests + 文档,41 文件 +3463 行 |
| 2026-09-10 | 三问查数 + 护栏 Decimal/标识误报修复 + 流水消歧 | 工作区 | guardrail.py(Decimal/编号/日期时间/左边界)、dict_service(流水多义),degrade→success/clarify |
| 2026-09-10 | 20 题实测 + 数字护栏误拦截修复 | 工作区 | guardrail.py 两处修复,4 题 degrade → success |
核心逻辑类:app/service/analyst_agent.py(AnalystAgent.run() 7 步编排,兼容 build_graph() LangGraph 适配)。
3. 模块实现清单
| 模块 | 文件 | 职责 | 状态 | 本轮变化 |
|---|---|---|---|---|
| 编排 | service/analyst_agent.py |
消歧→SQL→校验→执行→解读→护栏→留痕 | 已实现 | – |
| 路由 | api/analyst.py |
/chat /dashboard /assets /ops/metrics |
已实现 | – |
| 鉴权 | utils/auth.py + api/deps.py |
JWT(HS256) + 角色→数据域 RBAC | 已实现 | – |
| 数据访问 | service/analytics_repo.py |
只读执行 / 归属白名单 / 留痕 / 资产沉淀 / 空态三分类 | 已实现 | – |
| SQL 校验 | service/sql_guard.py |
五层只读校验(关键字 / 表白名单 / 多语句 / 行级归属 / 粒度) | 已实现 | – |
| 数字护栏 | service/guardrail.py |
解读数字与结果逐字校验(D-10) | 已实现 | 本轮修复 |
| 指标口径 | service/dict_service.py |
指标字典 + 多义词消歧(N-01) | 已实现 | – |
| Schema 提示 | service/schema_meta.py |
注入表/列说明给 LLM | 已实现 | – |
| LLM | service/llm.py |
DeepSeek 对话 / 提取 SQL / 成本估算 | 已实现 | – |
| 查询缓存 | service/cache_service.py |
Redis→内存降级;权限指纹键;TTL 分层(D-06) | 已实现 | – |
| 未接入 | rag_service.py memory_service.py |
RAG 知识库 / L1-L3 业务记忆 | 空壳存根 | – |
请求流转(AnalystAgent.run())
auth 域判定 → 指标消歧(若多义→clarify) → 生成SQL →
SQL 五层校验(失败→deny) → 执行(缓存优先) → 空态三分类 →
LLM 解读 → 数字护栏(失败重试1次→降级) → 组装响应 → 留痕 double-write
4. 需求覆盖(对照需求规格)
| 需求 | 能力 | 落点 |
|---|---|---|
| D-01/02 | NL→SQL、只读查询 | _generate_sql + validate |
| D-04 | 查询/审计留痕 | log_query + log_audit |
| D-06 | 结果/模板缓存 | cache_service |
| D-07 / N-01 | 口径字典 / 消歧 | dict_service |
| D-10 | 数字护栏 | guardrail(本轮加强) |
| D-11 | 资产沉淀 | assets + insert_asset |
| D-12 / N-08 | 看数板 / 运营指标 | dashboard / ops_metrics |
| RBAC | 角色→数据域 | auth.assert_analyst_access |
5. 质量基线
单元测试: tests/test_*.py 覆盖 agent / api / cache / dict / guardrail(14) / sql_guard / llm / repo。改为本机确认 tests/test_guardrail.py OK。
实测基线(2026-09-10,analyst 全量域,20 题):
| 维度 | 结果 |
|---|---|
| 成功返回可用结果 | 18 / 20(90%) |
| 澄清后继续(多义) | 1(「规模」持仓/产品多义,属预期) |
| 白名单误杀 deny | 1(Q17 触发 create 危险词误判) |
| 数字护栏误拦截(修复前) | 4(Q1/Q9/Q19/Q20)→ 修复后可全过 |
| 内容正确性 | 17 / 18 数字无误写;1 题口径跑题(Q7) |
6. 本轮迭代详情(2026-09-10)
背景
用户问「用户持仓前三? 申购前三? 交易流水前三?」验证 NL2SQL 查数。端到端跑通后暴露两个问题:数字护栏把正确回答误判为降级,「交易流水前三」语义漂移。
问题 1:数字护栏误拦截(三类根因)
- Decimal 不被识别:pymysql 对
DECIMAL/SUM(...)返回Decimal,_to_float只认int/float/str,金额被当None丢弃,result_numbers()只剩{0.0, 行数},导致「315.00万元」这类正确解读被判失败,三次查询全部degrade。 - ID/名称/日期里的数字误报:
extract_numbers把CUST-3001拆成-3001、把「沪深300指数」拆出300、把「9月3日10:00」拆出9,从而误判「说错数字」。 - 其中「中文后直接接编号」(客户CUST-3001、产品PROD-510300)会使
\b边界失效(中文也属\w)。
修复 1(app/service/guardrail.py)
_to_float新增Decimal分支。extract_numbers先屏蔽编号 token(CUST-/TRD-/PROD-/STAFF-,不加\b)与日期/时间 token(YYYY-MM-DD、M月D日、HH:MM),再匹配。NUMBER_RE加左边界(?<![\w]),挡掉「沪深300」「中证500」等名称内数字。
问题 2:「交易流水前三」语义漂移
同一问题多次运行,LLM 分别给出「按时间取最新 3 笔」「按交易笔数排序」「按金额排序」三种口径,非确定性(「流水」无口径定义,_detect_ambiguity 拦不住)。
修复 2(app/service/dict_service.py,N-01 / B2)
新增 交易流水金额 与 交易流水笔数 两个口径,别名均含「交易流水/流水」,「流水」成为多义词 → resolve() 返回 Ambiguity,Agent 反问「按金额还是按笔数」;「流水金额/流水笔数」仍各指向唯一口径。
验证
- 单测:
test_guardrail.py14 项(新增 7 项回归)、test_dict_service.py7 项(新增 4 项)全 OK;sql_guard/cache_service/agent编排无回归(合计 42 项)。 - 端到端(进程内 + HTTP 8002):
用户持仓前三、申购前三由degrade → success,数字逐一正确;交易流水前三由漂移 →clarify(反问金额/笔数)。
踩坑备注:① 8000 端口跑的是
customer-service-agent分支(auth/chat),非数据分析 Agent,勿按端口混淆分支;② 旧服务由 node 守护进程自动重启,非管理员 shell 杀 python 无效。
7. 上一轮迭代详情(2026-09-10)
问题:数字护栏误拦截(4 题 degrade)
- Q1/Q20 根因:结果列是比率(
0.1515),解读写百分比(15.15%),护栏未做「比率 ×100」换算即判错。 - Q19 根因:解读「近 30 天」的时间窗数字
30被正则当成数据数字,与结果0比对失败。 - 附带:Q9 依赖 LLM 措辞(是否用「万」)存在波动,与护栏策略耦合。
修复(app/service/guardrail.py)
- 新增
_WINDOW_TOKEN:数字提取前遮挡「近30天/近 30 月」等时间窗口径,防窗口数字污染。 check_numbers():当结果集合含 0~1 的比率时,追加0.01比对刻度(识别n×0.01 ≈ 结果,即百分比=比率×100)。
验证
- 离线确定性用例:Q1/Q19/Q20 通过;编造数字负例(
999999.99)仍被拦截(护栏未削弱)。 - 既有 14 项 guardrail 单测均 OK,无回归。
- 端到端真实接口(重启后端加载新码后):Q1/Q9/Q19/Q20 全部
degrade → success,数字逐一核对正确。
踩坑备注:后端
--reload对已改模块未实时生效,须重启进程验证代码变更。
8. 已知问题与下一步
| # | 问题 | 影响 | 建议 |
|---|---|---|---|
| 1 | Q17:SQL 白名单把 create 当危险词,字段/列名命中即 deny |
一次误拦 | create 仅拦截语句级(CREATE TABLE),字段/列名放行 |
| 2 | Q7:库无「城市」字段时,Agent 静默改按「职业」统计而非澄清/拒绝 | 答非所问 | NL2SQL 对库结构不匹配加「澄清/拒绝」兜底 |
| 3 | 数字护栏对"聚合比率/多位小数"依赖 LLM 措辞 | flaky | 可考虑允许集合追加百分比别名校验以进一步收敛 |
| 4 | guardrail 修复尚无回归用例 | 防复发缺口 | 将 4 题误报场景补入 tests/test_guardrail.py |
| 5 | RAG / 业务记忆仍为空壳 | 能力边界 | 待后续迭代按架构说明书接入 |
9. 验证方式
运行:uvicorn app.main:app --reload → POST /api/analyst/chat(Bearer 开发 token)
探活:GET /health
单测:tests/(guardrail、sql_guard、agent、api 等)
实测:scripts/dev/run_query_battery.py(20 题基线)
配置:.env(DEEPSEEK_API_KEY 必填;缺失则 LLM 报错)