## 新入库(`docs/演示用/`)
- `代码库全面审查报告-2026-09-14.md`
- `代码修改方案-2026-09-14.md`
- `记忆系统排查报告-2026-09-14.md`
- `记忆系统修复文档-2026-09-14.md`
- `文档一致性审计报告-2026-09-14.md`
- `多Worker接入方案-2026-09-14.md`
## 全量校对(32 个既有文档 + `AGENTS.md`)
跨 39 个文件、**1125 insertions / 148 deletions**。
⚠️ **这批改动同样不是本次会话写的**。我抽样核对过性质:是**实质内容补充**而不是
格式/换行转换。例如 `docs/44-演示流程.md` 新增两条"2026-09-14 补注":
- `启动金融Agent平台.bat` 只在**桌面**上,仓库里只有 `启动平台.bat` 这一份
(两份由同一个 `tools/make_launcher_bat.py` 产出,改完 `start.ps1` 重跑它一起更新);
- `advisor_t`(9020) 与 `offsite_t`(9006) **不在 `tools/seed_test_rbac.py` 的演示用户里**
(那里只有 `cust_t`/`risk_t`/`admin_t`/`review_t` 四个),由 `grant_*.py` 系列创建,
**重跑种子不会重建它们** —— 换机器时这两个账号登录失败,要先查 `sys_user` 有没有这两行,
而不是查密码。
这两条都是对的地方,与我这一路踩到的现象一致(我确实用到了 `advisor_t`/`offsite_t`)。
**我没有逐字审阅全部 39 个文件**,只抽样确认了改动性质与规模。若其中有需要复核的段落,
请指明文件,我逐处核对。
11 KiB
基金行情数据底座接入开发计划
⚠️ MVP 完成标准已过期。 文中「F0-F4 全部通过」为当时快照;实际 F0-F6 已完成。另有部分「当前实现」的描述已随底座演进而变化。
(此批注由 2026-09-11 只读审计加入;原文未改动。详见
docs/superpowers/ARCHIVE-2026-09-11-文档清理归档.md§3)
⚠️ 2026-09-14 补充:本文完全没写的两件运行期事实(重要)
1. 行情有效期只有 15 分钟 —— 这是演示/验收最容易翻的一环。
app/service/trade_service.py的MAX_QUOTE_AGE = timedelta(minutes=15)(L79)。 超时后所有委托一律 503FUND_QUOTE_UNAVAILABLE(app/core/errors.py:260-264), 且没有自动刷新。症状看起来像"行情源坏了",实际只是数据放久了。 补刷命令(立即生效、无需重启服务):.\.venv\Scripts\python.exe tools\sync_market_prices.py2. 两个行情/净值同步脚本未被本文登记:
tools/sync_market_prices.py—— 刷实时行情(上条)。tools/sync_nav_history.py—— 刷历史净值(访客端GET /api/v1/products/{product_code}/nav-history用它)。3. 工具超时的依据:
query_fund_quote的工具超时是 15 秒(app/service/agent/bootstrap.py:278)。 理由(代码内注释):EastmoneyAdapterFactory单码最坏预算 = 4.0s + 0.2s(退避) + 4.0s(重试) = 8.2s, 15s ≈ 8.2 × 1.8。旧的 5 秒会在适配器自己的重试预算之前就撞工具超时,永远拿不到结果。启动链:
启动金融Agent平台.bat(桌面)/启动平台.bat(仓库)会按序做:找解释器 → 检查 MySQL/Redis/Milvus → 刷新行情 → 起 API 与 Agent Worker → 等 API 应答 → 开浏览器。 解释器门槛是「版本 ≥ 3.11 且能import fastapi, sqlalchemy, asyncmy, pydantic」, 不能只看--version(曾因此选中 3.10 环境,报错却出现在行情刷新那步,看着像行情源坏了)。
版本:v1.0
目标:将外部基金行情能力接入公共 MVC+S 底座,供客服、投顾、风控 Agent 统一只读使用。
当前范围:场内基金模拟交易相关行情;场外运营数据不纳入本计划。
一、建设目标
将外部行情脚本改造成公共底座能力:
东方财富 Adapter
→ FundQuoteService
→ ToolRegistry / ToolExecutor
→ 客服、投顾、风控 Agent
业务组员只调用统一工具,不直接访问东方财富接口、HTTP 客户端、缓存或数据库。
二、明确不做的事情
- 第一阶段不修改已有数据库表、字段和交易流程;
- 不把行情快照写入场内交易表;
- 不实现代客下单、成交确认或持仓修改;
- 不让 Agent 直接调用外部行情接口;
- 不在本阶段创建可运行示例 Agent;
- 不把场外基金运营数据混入本行情服务。
三、现有脚本拆分映射
原 hq.py 能力 |
新底座位置 | 处理要求 |
|---|---|---|
| 东方财富 HTTP 调用 | app/infrastructure/fund_market_adapter.py |
改为异步、超时、重试、响应校验 |
| 交易时段判断 | FundQuoteService |
固定北京时间并可测试注入时钟 |
| 基金名称缓存 | 行情缓存适配器 | 允许 Redis,不使用进程全局作为唯一缓存 |
| 历史净值和收益率合并 | app/service/fund_quote_service.py |
统一 DTO、Decimal、降级标识 |
| 中文字典输出 | Controller/Agent 展示层 | 内部 DTO 使用英文稳定字段 |
| 固定南方基金代码 | 配置或代码白名单 | 先保留白名单,后续配置中心化 |
四、阶段计划
F0:基线和契约冻结
目标:确认接入不会破坏现有底座和数据库规则。
任务:
- 对照
AGENTS.md、00 基线和 02 建表设计; - 确认本阶段只新增 Python 模块、工具注册和测试;
- 定义
FundQuoteQuery、FundQuote、FundQuoteSourceDTO; - 定义错误分类:外部行情不可用返回可识别的降级结果,不泄露供应商异常;
- 确认行情工具权限码为
fund:quote:read。
验收:
- DTO 字段、类型和空值规则确定;
- 没有数据库迁移;
- 没有修改既有表和字段;
- 接口契约可被 Service、ToolExecutor 和测试共同引用。
F1:外部行情 Adapter
目标:把 hq.py 的外部请求改造成底座 Infrastructure 适配器。
新增文件:
app/infrastructure/fund_market_adapter.py
app/core/fund_contracts.py
tests/unit/infrastructure/test_fund_market_adapter.py
任务:
- 使用
httpx.AsyncClient,禁止同步网络调用阻塞 Worker; - 封装名称、实时行情、历史净值、区间收益率四类请求;
- 统一请求超时、有限重试和指数退避;
- 校验外部 JSON 结构和基金代码;
- 将 HTTP、JSON、字段缺失转换为
RecoverableAgentError或降级结果; - 供应商日志只记录接口类别、基金数量和异常类型,不记录完整响应正文;
- 使用
Decimal保存净值和收益率; - 所有时间统一使用 Asia/Shanghai 语义。
验收:
- 外部接口成功、超时、HTTP 错误、非法 JSON、字段缺失均有测试;
- 测试不依赖真实东方财富网络,使用
httpx.MockTransport; - Adapter 不导入 Agent、Controller 或数据库 Session。
F2:FundQuoteService
目标:将多个外部接口结果合并成稳定的行情业务结果。
新增文件:
app/service/fund_quote_service.py
tests/unit/service/test_fund_quote_service.py
任务:
- 实现按基金代码、基金类型、日期和数量查询;
- 保留场内基金白名单;
- 合并实时行情和历史净值;
- 盘中使用实时行情,非盘中使用最新收盘净值;
- 返回
quote_source、quote_time、is_intraday、degraded; - 单个产品失败不得导致全部产品无原因失败;
- 外部行情不可用时返回结构化降级结果;
- 不将实时行情解释为成交、委托或持仓结果。
验收:
- 交易时段和非交易时段测试通过;
- 基金类型筛选、数量限制、非法日期和未知类型测试通过;
- 实时失败回退历史、历史失败返回降级结果的测试通过;
- Service 不直接解析 HTTP 响应细节。
F3:缓存和健康状态
目标:降低外部接口压力,并让组员得到可识别的降级状态。
任务:
- 基金名称缓存建议 1 天;
- 收盘净值缓存建议 5-30 分钟;
- 实时行情缓存建议 30-60 秒;
- Redis 可用时使用 Redis,不可用时回退无缓存请求或结构化降级;
- 不将进程级字典作为多 Worker 唯一缓存;
- 增加行情数据源健康状态,不把供应商故障伪装成正常行情;
- 记录数据源成功率、延迟、降级次数和最后成功时间。
验收:
- Redis 正常、Redis 不可用、缓存过期和缓存击穿测试通过;
- 缓存故障不阻塞主流程;
- 返回结果能够区分实时、缓存、收盘和降级来源。
F4:注册公共只读工具
目标:让所有业务 Agent 通过统一工具使用行情。
工具契约:
工具名:query_fund_quote
权限码:fund:quote:read
只读:是
允许角色:customer、advisor、operator、risk_operator、admin
超时:5 秒
任务:
- 定义严格的 Pydantic 输入模型;
- 在
bootstrap中注册ToolDefinition; - 由
ToolExecutor统一检查权限、角色、意图白名单和超时; - 结果自动生成工具来源引用和审计记录;
AgentDefinition.allowed_tools只能缩小权限;- 业务 Agent 不能绕过工具调用 Adapter。
验收:
- 正常调用、未授权、未列入意图白名单、参数错误、超时和外部失败均有测试;
- 工具结果不含供应商原始异常和敏感配置;
- 来源引用能通过公共合规审查。
F5:配置中心和管理规则
目标:把基金白名单、数据源开关、缓存时间和限流参数逐步纳入配置治理。
第一阶段:
- 外部接口地址、超时和密钥引用写入
.env或运行配置; - 基金白名单保留在代码常量,避免未经审核扩大产品范围。
后续阶段:
- 使用
platform_config_item增加行情配置命名空间; - 配置发布、审核、激活、回滚必须经过已有配置中心;
- 配置变更写审计并通过 Outbox 通知缓存失效;
- 不通过配置中心扩大 Agent 的工具权限上限。
验收:
- 配置缺失时失败关闭或使用明确的安全默认值;
- 密钥只使用
secret_ref,不返回明文; - 发布版本可追溯、可回滚。
当前实现:RuntimeConfigService.fund_quote() 读取当前激活版本的
namespace=fund_market、config_key=default;query_fund_quote 工具加载该配置,
FundQuoteRuntimeConfig 校验基金白名单和缓存 TTL。配置缺失、类型错误或配置中心异常时使用代码
默认值,不会因配置中心异常导致行情功能整体不可用。
F6:业务组员接入
目标:客服、投顾和风控 Agent 只消费统一行情工具。
组员需要做:
- 在
AgentDefinition.allowed_tools声明query_fund_quote; - 在对应意图白名单中声明
fund_quote; - 在
handle()中调用self.call_tool(...); - 对行情缺失、降级和非交易时段给出业务解释;
- 补充本业务域的权限和边界测试。
组员禁止做:
- 直接导入
hq.py; - 直接调用东方财富 URL;
- 自己读取行情缓存或解析外部 JSON;
- 根据行情直接下单或修改交易数据;
- 把行情数据写入交易表。
F7:是否落行情快照表
判断条件:只有在需要历史回测、监管留痕、行情对账或跨进程长期查询时才进入本阶段。
候选新增表:
fund_quote_snapshot
fund_quote_source_record
前置要求:
- 先更新 00、02 数据库文档;
- 证明没有修改任何既有表名和字段定义;
- 设计唯一键、来源、采集时间、有效时间和幂等策略;
- 增加 Alembic 迁移、schema fingerprint 和真实 MySQL 验收;
- 只新增表,不改写场内交易表。
五、建议开发顺序和完成标准
F0 契约冻结
→ F1 Adapter
→ F2 Service
→ F3 缓存/健康
→ F4 公共工具
→ F5 配置治理
→ F6 业务接入
→ F7 可选落库
MVP 完成标准:F0-F4 全部通过;不要求新增数据库表。长期版本再评估 F5-F7。
六、测试命令
python -m pytest tests/unit/infrastructure/test_fund_market_adapter.py tests/unit/service/test_fund_quote_service.py -q -p no:cacheprovider
python -m pytest -q -p no:cacheprovider
python -m ruff check app tests tools alembic
python -m mypy app
python tools/audit_schema.py
任何数据库变更都必须额外执行 schema fingerprint,并在 TODO 中记录基线对比结果。