Files
group_fqcd_jr/docs/04-开发文档评审报告.md

522 lines
32 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 开发文档评审报告
> 评审对象:`docs/01-通用Agent平台开发设计.md`、`docs/02-数据库建表设计.md`、`docs/03-平台端到端流程文档.md`
> 评审基准:docs 目录 2026-09-08 版三份文档(01 标称 v2.1;02、03 未标注版本)
> 评审方式:逐行通读 + 三份文档交叉引用核对 + 按文档内容推演可实施性
> 评审结论:**三件套综合 80/100(B+),可作为实施基线;P0 项闭环前不建议派发给编码 Agent**
>
> **复审状态**:本文 §1-§6 与附录 A/B 针对 v2.1 版本,行号引用以 v2.1 为准。团队已完成修订,v3.0 复审结果见 **附录 C**;v3.0 综合评分 **90/100(A-)**,MVC+S 架构合规性 **92/100**。
---
## 1. 总评分
| 文档 | 得分 | 等级 | 结论 |
|---|---|---|---|
| `01-通用Agent平台开发设计.md` | **86 / 100** | A- | 架构与契约达到可直接编码水准,存在 3 处自相矛盾 |
| `02-数据库建表设计.md` | **77 / 100** | B+ | 6 张专项表 DDL 质量高,基础表基线不自包含,缺关键表 |
| `03-平台端到端流程文档.md` | **76 / 100** | B+ | 流程与职责边界清晰,落点与 02 冲突,缺幂等细节 |
| **三件套综合(01 占 40%,02/03 各占 30%)** | **80 / 100** | **B+** | 可进入实施,须先闭环 5 项 P0 |
### 1.1 分维度得分
| 维度 | 权重 | 01 | 02 | 03 |
|---|---|---|---|---|
| 完整性与自包含性 | 20 | 16 | 13 | 14 |
| 内部一致性与准确性 | 20 | 16 | 15 | 15 |
| 架构与技术合理性 | 25 | 23 | 20 | 19 |
| 安全 / 合规 / 风控 | 15 | 14 | 13 | 13 |
| 可测试性与可验收性 | 10 | 9 | 8 | 8 |
| 工程可执行性 / 可维护性 | 10 | 8 | 8 | 7 |
| **合计** | **100** | **86** | **77** | **76** |
### 1.2 等级标准
| 等级 | 分数 | 含义 |
|---|---|---|
| A | 90-100 | 优秀,可直接落地 |
| B | 75-89 | 良好,修订后可落地 |
| C | 60-74 | 需较大重构 |
| D | < 60 | 需重写 |
---
## 2. 总体评价
### 2.1 值得肯定的部分
1. **架构分层可执行**。01 §3.1 的分层职责表明确列出每层"不允许承担的职责",比只写"职责"的文档强一个档次;§3.3 给出 Controller/Service 的最小代码约束。
2. **模板方法约束到位**。`run_stream()` 标记 `@final`,业务子类只实现 `handle()`,§15.3 用契约测试扫描受保护方法(01:1296-1313),把"不许重写骨架"从口头约定变成可执行检查。
3. **契约设计专业**。全部跨层对象使用 frozen dataclass,集合用 `tuple`/`frozenset`,依赖用 `Protocol` 倒置,Repository 与外部客户端全部隐藏在 Service 之后(01:491)。
4. **配置权威链清晰**。01 §7.3 的 5 级优先级(安全硬约束 > 代码上限 > 数据库配置 > 代码默认值 > 环境变量),并明确"数据库只能缩小不能扩大",工具取三层交集(01:1050-1054)。
5. **降级矩阵完整**。01 §12 与 03 §13 对 Redis/Milvus/Neo4j/模型/工具/审计/事件逐项给出处理策略,且区分"降级"与"阻断"。
6. **编码 Agent 交接模板可直接使用**。01 §15.5 把任务正文、允许/禁止修改范围、必测项、验收命令、完成回报格式全部固化,这是同类文档中罕见的工程化细节,可当任务单直接派发。
7. **专项表 DDL 规范**。02 §7 的 6 张表统一带 CHECK 约束、版本列、审核人、`created_at`/`updated_at`、审计语义,索引设计考虑了查询路径(队列索引 `(status, priority, created_at)`、前缀索引 + `phrase_hash` 规避长文本误判)。
8. **职责边界写得好**。03 §2 的"禁止事项"列和 §10.3「Agent 不能确认、关闭或升级预警」把 AI 与人的决策边界钉死,这是金融场景的必备约束。
### 2.2 结构性问题(一句话概括)
**01 是权威源,02 和 03 承接它的能力,但各自都漏了一个 01 已经承诺的持久化载体**——澄清轮次、通用领域事件、工具调用记录。这是"设计已完成、数据模型没跟上"的典型缺口,越晚补代价越大。
---
## 3. 分文档详评
### 3.1 `01-通用Agent平台开发设计.md`(86 / A-)
**扣分明细**
| 编号 | 问题 | 位置 | 严重度 |
|---|---|---|---|
| A1 | `clarification_round` 无持久化载体,澄清/转人工逻辑无法实现 | 01:245、744 | P0 |
| A2 | 通用领域事件要求 Outbox,但 02 无对应表 | 01:1125 | P0 |
| A3 | `allowed_tools` 按意图配置(02)与单集合消费(01)冲突 | 01:273、1261 | P0 |
| A4 | 合规策略来源不一致:主流程用 `config`,降级用 `definition` | 01:583 vs 816 | P1 |
| A5 | `CoreResult` 互斥语义矛盾:正文"不能同时为空",实现"恰好一个非空" | 01:368 vs 770 | P1 |
| A6 | 目录树缺 `bootstrap.py`、`AgentAuthorizer`,但 §7.1/§15.3 引用 | 01:139-152 vs 996、1292 | P1 |
| A7 | 伪流式与"首个安全 Token 延迟"指标冲突 | 01:579-636、907、1131 | P1 |
| A8 | `save_user_message` 在记忆召回前,与七步描述不符;异常路径遗留孤儿用户消息 | 01:573 vs 925 | P1 |
| A9 | `@final` 仅静态约束,契约测试用 `__dict__` 扫描可被 `setattr` 绕过 | 01:1312 | P2 |
| A10 | "核心公共逻辑覆盖率 ≥80%"未界定范围 | 01:1167 | P2 |
| A11 | 意图集合能否被数据库扩展未明确(§7.3 只列角色/入口/工具为代码上限) | 01:1040 | P2 |
| A12 | 幂等键仅提要求,无幂等表 / Redis key 规范 | 01:936 | P2 |
| A13 | Prompt 与模型参数"必须版本化并写入审计"无落地载体 | 01:1087 | P2 |
| A14 | 示例代码 `asdict` 在第 794 行使用,导入语句在第 853 行 | 01:794、853 | 文案 |
**详细说明(关键三项)**
- **A4 合规策略来源不一致**:主流程 `guard_output(raw_reply, config.compliance_policy, context)`(01:583)使用解析后的策略;降级分支 `fallback_service.reply_for(error.code, self.definition.compliance_policy)`(01:816)使用代码默认值。若数据库配置收紧过策略,降级路径会绕过该收紧。**建议**:统一改为 `config.compliance_policy`,或在 `_persist_degraded_result` 签名中传入 `config`。
- **A5 互斥语义矛盾**:01:368 写"两者不能同时为空",01:770 的实现是 `if has_direct == has_material: raise`,即同时禁止两者**同时非空**。**建议**:正文改为"`direct_reply` 与 `generation_material` 必须恰好提供一个",与实现对齐。
- **A7 伪流式**:当前顺序为「全量生成 → 合规校验 → 落库 → 审计 → 发事件 → 按 256 字符切片发 `delta`」(01:579-636)。SSE 的价值退化为分块传输,`delta` 并非 token 级增量,首字延迟 ≈ 全量生成 + 合规 + 落库时间。**建议**:要么在 §13 删除"首个安全 Token 延迟"指标并明确"合规优先、非真流式"的取舍;要么设计"完整句子缓冲 + 句级合规"方案并单独评估泄漏风险。
### 3.2 `02-数据库建表设计.md`(77 / B+)
**扣分明细**
| 编号 | 问题 | 位置 | 严重度 |
|---|---|---|---|
| B1 | 基础 33 张表以外部《合并后数据库表设计last.md》为基线,该文件不在仓库,§12 验收无法执行 | 02:5 | P0 |
| B2 | 无会话主表,`session_id` 仅为逻辑外键,澄清轮次与会话状态无处存储 | 02:97 | P0 |
| B3 | 无通用领域事件 Outbox 表,仅 `memory_sync_outbox` | 02:74 | P0 |
| B4 | `conversation_message` 缺 `tool_calls` 列,与 03:138 冲突 | 02:120-126 | P0 |
| B5 | `fin_knowledge_meta` 有效期过滤用 `CURRENT_DATE`,与"统一 UTC"约定冲突 | 02:172-173 vs 36 | P1 |
| B6 | `uk_feedback_message_customer(message_id, customer_id)`:MySQL 中 NULL 不参与唯一性,匿名反馈可重复刷票 | 02:334 | P1 |
| B7 | "同一时刻仅一个有效版本"仅靠服务层保证,缺数据库级加固 | 02:385、489 | P1 |
| B8 | 无归档 / 分区 / 容量策略;`conversation_message`、`interaction_audit`、`fin_market_price`、`memory_sync_outbox` 只增不减 | 02 全文 | P1 |
| B9 | 02、03 无版本号与变更记录(仅 01 标 v2.1) | 02 / 03 头部 | P1 |
| B10 | `ALTER TABLE ... AFTER trace_id` 未说明前置列存在性检查 | 02:132 | P2 |
**详细说明(关键两项)**
- **B6 NULL 唯一性陷阱**:MySQL 唯一索引中多个 NULL 视为互不相等,因此 `customer_id IS NULL` 的匿名反馈可无限重复插入,`uk_feedback_message_customer` 对其完全失效。文档虽要求"必须由 API 限流"(02:346),但把约束责任从数据库移到应用层。**建议**:`customer_id` 使用 `0` 作为匿名哨兵值,或增加 `client_fingerprint` 列参与唯一键。
- **B7 有效版本唯一性**:`agent_reply_template` 与 `agent_intent_config` 的"同一时刻仅一个有效版本"依赖发布服务事务保证(02:385、489),并发发布存在窗口。**建议**:用生成列 + 唯一索引在数据库层强制(见 §5.1 补丁)。
### 3.3 `03-平台端到端流程文档.md`(76 / B+)
**扣分明细**
| 编号 | 问题 | 位置 | 严重度 |
|---|---|---|---|
| C1 | 要求落库 `tool_calls`,02 无该列 | 03:138 | P0 |
| C2 | 澄清轮次存储未定义,§5.3 "每轮只追问一个关键槽位"无法实现 | 03:112 | P0 |
| C3 | 异常矩阵缺"事件发布失败"行,与 01 §12 不对齐 | 03:361-372 vs 01:1125 | P1 |
| C4 | 无版本号与变更记录 | 03 头部 | P1 |
| C5 | 缺幂等、超时预算、重试上限的端到端数值 | 03 §4、§5 | P2 |
| C6 | 全程纯文本流程,跨 4 张表事务(§9)缺时序图 / 状态图 | 03 §9、§10 | P2 |
| C7 | Redis 实时行情不可用时交易流程如何处理未说明 | 03:248 | P2 |
**说明**:03 的职责边界、异常矩阵、数据落点表(§14)质量高,主要问题是从 01/02 继承的落点缺口,而非自身叙述缺陷。
---
## 4. 问题清单汇总
### 4.1 P0:不闭环不能开工(5 项)
| # | 问题 | 涉及文档 | 建议修复 |
|---|---|---|---|
| P0-1 | `clarification_round` 无持久化载体 | 01:245、744 / 02:97 / 03:112 | 新增 `svc_conversation_session` 表(见 §5.1) |
| P0-2 | 通用领域事件无 Outbox 表 | 01:1125 / 02:74 | 新增 `domain_event_outbox` 表(见 §5.1) |
| P0-3 | `tool_calls` 落点缺失,01/02/03 不一致 | 03:138 / 02:120-126 | `conversation_message` 增补 `tool_calls JSON`(见 §5.1) |
| P0-4 | `allowed_tools` 按意图配置 vs 单集合消费 | 02:402 / 01:273、1261 | 修改 `ResolvedAgentConfig` 契约(见 §5.2) |
| P0-5 | 数据库基线不自包含,验收无法执行 | 02:5 | 基线 DDL 并入 02 或作为附录提交 |
### 4.2 P1:编码前应修正(8 项)
| # | 问题 | 位置 |
|---|---|---|
| P1-1 | 合规策略来源不一致(`config` vs `definition`) | 01:583 vs 816 |
| P1-2 | `CoreResult` 互斥语义矛盾 | 01:368 vs 770 |
| P1-3 | 目录树缺 `bootstrap.py`、`AgentAuthorizer` | 01:139-152 vs 996、1292 |
| P1-4 | 伪流式与首字延迟指标冲突 | 01:579-636、907、1131 |
| P1-5 | 用户消息落库顺序与七步描述不符;异常路径孤儿消息 | 01:573 vs 925 |
| P1-6 | `CURRENT_DATE` 与 UTC 约定冲突 | 02:172-173 vs 36 |
| P1-7 | 匿名反馈唯一索引因 NULL 失效 | 02:334 |
| P1-8 | 无归档 / 分区策略;02、03 无版本号 | 02 全文、03 头部 |
### 4.3 P2:建议增强(6 项)
| # | 问题 | 位置 |
|---|---|---|
| P2-1 | 幂等键无表 / Redis key 规范 | 01:936 / 03:4.2 |
| P2-2 | Prompt 版本化无落地载体 | 01:1087 |
| P2-3 | 意图集合能否被数据库扩展未明确 | 01:1040 |
| P2-4 | `@final` 仅静态约束,契约测试可绕过 | 01:1312 |
| P2-5 | 覆盖率范围未界定;缺超时预算与 SLA 数值 | 01:1167 / 03 §4-5 |
| P2-6 | 缺时序图 / 状态图;Redis 行情故障路径未定义 | 03 §9、§10 |
---
## 5. 建议修复方案
### 5.1 P0 表结构补丁 DDL
> 以下 DDL 与 02 §7 风格一致,需通过 Alembic 生成迁移,并在空库、旧版本库、回滚路径各演练一次。
#### 5.1.1 `svc_conversation_session`(修复 P0-1)
承载会话状态与澄清轮次,使 01:744 的低置信澄清/转人工逻辑可实现。
```sql
CREATE TABLE svc_conversation_session (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
session_id VARCHAR(64) NOT NULL,
user_id BIGINT UNSIGNED NOT NULL,
portal VARCHAR(32) NOT NULL,
agent_type VARCHAR(32) NULL,
status VARCHAR(16) NOT NULL DEFAULT 'active',
clarification_round TINYINT UNSIGNED NOT NULL DEFAULT 0,
message_count INT UNSIGNED NOT NULL DEFAULT 0,
last_intent VARCHAR(32) NULL,
started_at DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
last_active_at DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
ended_at DATETIME(6) NULL,
created_at DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
updated_at DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6)
ON UPDATE CURRENT_TIMESTAMP(6),
PRIMARY KEY (id),
UNIQUE KEY uk_session_id (session_id),
KEY idx_session_user (user_id, last_active_at),
KEY idx_session_status (status, last_active_at),
CONSTRAINT fk_session_user FOREIGN KEY (user_id) REFERENCES sys_user(id),
CONSTRAINT chk_session_status
CHECK (status IN ('active', 'ended', 'transferred', 'expired')),
CONSTRAINT chk_session_clarification
CHECK (clarification_round <= 10)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
COMMENT='Agent会话状态与澄清轮次';
```
配套约定:
- `RequestContextBuilder` 每次请求读取该行,将 `clarification_round` 写入 `RequestContext`。
- 澄清轮次递增使用条件更新:`UPDATE ... SET clarification_round = clarification_round + 1 WHERE session_id = ? AND clarification_round = ?`,避免并发重复澄清。
- Redis 作为热缓存,MySQL 为权威来源;Redis 丢失可从本表恢复。
- `ended_at` 仅在 `end_session = true` 或超时过期时写入。
#### 5.1.2 `domain_event_outbox`(修复 P0-2)
承载 01 §5.4 `EventPublisher` 与 01:882 `build_domain_events()` 产出的通用领域事件,与 `memory_sync_outbox` 职责分离。
```sql
CREATE TABLE domain_event_outbox (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
event_id CHAR(36) NOT NULL,
event_type VARCHAR(64) NOT NULL,
aggregate_type VARCHAR(32) NOT NULL,
aggregate_id VARCHAR(64) NOT NULL,
trace_id VARCHAR(64) NOT NULL,
payload JSON NOT NULL,
status VARCHAR(16) NOT NULL DEFAULT 'pending',
retry_count INT UNSIGNED NOT NULL DEFAULT 0,
next_retry_at DATETIME(6) NULL,
last_error VARCHAR(500) NULL,
occurred_at DATETIME(6) NOT NULL,
published_at DATETIME(6) NULL,
created_at DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
updated_at DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6)
ON UPDATE CURRENT_TIMESTAMP(6),
PRIMARY KEY (id),
UNIQUE KEY uk_domain_event_id (event_id),
KEY idx_domain_outbox_pending (status, next_retry_at, id),
KEY idx_domain_outbox_aggregate (aggregate_type, aggregate_id),
KEY idx_domain_outbox_trace (trace_id),
CONSTRAINT chk_domain_outbox_status
CHECK (status IN ('pending', 'published', 'failed', 'dead'))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
COMMENT='通用领域事件Outbox';
```
配套约定:
- 事件与业务写入(`conversation_message`、`interaction_audit`)在同一事务提交,满足 02 §10 的原子性要求。
- `uk_domain_event_id` 保证消费者幂等,重复投递不会重复处理。
- 重试采用指数退避写 `next_retry_at`,超过阈值置 `dead` 并告警。
#### 5.1.3 `conversation_message` 增补 `tool_calls`(修复 P0-3)
```sql
ALTER TABLE conversation_message
ADD COLUMN tool_calls JSON NULL
COMMENT '脱敏后的工具调用摘要数组' AFTER source_references;
```
同步修正 03 §5.6 与 01 §6.2:`AgentResult.tool_calls` 序列化后写入该列,字段结构与 SSE `tools` 事件(01:628 `serialize_tool_calls`)保持一致,禁止写入未脱敏参数。
#### 5.1.4 有效版本唯一性加固(修复 P1-7 的同类问题)
```sql
ALTER TABLE agent_reply_template
ADD COLUMN active_key VARCHAR(96) GENERATED ALWAYS AS
(IF(status = 'active', CONCAT(template_code, ':', locale), NULL)) STORED,
ADD UNIQUE KEY uk_reply_template_active_one (active_key);
ALTER TABLE agent_intent_config
ADD COLUMN active_key VARCHAR(128) GENERATED ALWAYS AS
(IF(status = 'active', CONCAT(agent_type, ':', intent_code), NULL)) STORED,
ADD UNIQUE KEY uk_intent_config_active_one (active_key);
```
利用"唯一索引中 NULL 不参与比较"的特性,非 `active` 行 `active_key` 为 NULL 可无限共存,`active` 行则强制唯一,把服务层约定升级为数据库级约束。
#### 5.1.5 时间过滤修正(修复 P1-6)
02:172-173 的 `CURRENT_DATE` 依赖会话时区,与 02:36"统一使用 UTC"冲突。修正为:
```sql
review_status = 'published'
AND status = 'active'
AND (effective_date IS NULL OR effective_date <= UTC_DATE())
AND (expire_date IS NULL OR expire_date > UTC_DATE())
```
或在仓储层显式传入已计算的 UTC 日期参数,避免 SQL 依赖会话时区。
### 5.2 代码契约修正清单(01)
| 编号 | 修正内容 | 位置 |
|---|---|---|
| F1 | `_persist_degraded_result` 使用 `config.compliance_policy`,签名增加 `config` 参数 | 01:802-817 |
| F2 | `CoreResult` 注释改为"`direct_reply` 与 `generation_material` 必须恰好提供一个" | 01:368 |
| F3 | 目录树补 `service/agent/bootstrap.py`、`service/agent/authorizer.py`,并定义 `AgentAuthorizer` 协议 | 01:139-152 |
| F4 | `ResolvedAgentConfig.allowed_tools` 改为 `Mapping[str, frozenset[str]]`(按意图),或在文档中明确"Agent 级并集 + 工具内二次校验" | 01:273 |
| F5 | §13 明确"合规优先、非真流式",删除或重新定义"首个安全 Token 延迟" | 01:1131 |
| F6 | §6.3 第 1 步补充"保存用户消息",与 §6.2 实际顺序一致;明确 `AgentRequestError` 后孤儿用户消息的处理策略 | 01:920-926 |
| F7 | §7.3 第 2 条补充 `supported_intents` 是否为代码级上限 | 01:1040 |
| F8 | 契约测试增加运行期守卫或 CI AST 检查,替代仅依赖 `__dict__` 扫描 | 01:1312 |
| F9 | §14.1 界定"核心公共逻辑"的具体模块范围 | 01:1167 |
### 5.3 流程文档补强(03)
| 编号 | 修正内容 |
|---|---|
| G1 | §13 异常矩阵增加"事件发布失败 → Outbox 异步重试,不丢失关键事件" |
| G2 | §5.3 补充澄清轮次的读取与递增路径,指向 `svc_conversation_session` |
| G3 | §9 补充 Redis 实时行情不可用时的处理(拒绝下单 / 降级为最近收盘价并明确提示,二选一) |
| G4 | 增加会话生命周期时序图与交易事务时序图 |
| G5 | 头部补充版本号、修订记录与关联文档索引 |
---
## 6. 放行条件与后续动作
### 6.1 放行条件(全部满足才可派发编码)
- [ ] P0-1 ~ P0-5 全部闭环,且 02 的基线 DDL 已并入或附于文档内
- [ ] 三份文档的交叉引用一致性核对通过(见附录 A)
- [ ] P1 项已修正或已登记为有责任人的待办
- [ ] 02、03 补齐版本号与修订记录
### 6.2 建议动作顺序
1. **补表**:执行 §5.1 的 4 组 DDL 补丁,更新 02 的表总览(39 → 42 张)。
2. **修契约**:按 §5.2 修正 01 的 9 处代码级不一致。
3. **对齐流程**:按 §5.3 补强 03 的异常矩阵与关键路径。
4. **重跑核对**:以附录 A 为检查表,逐行核对三份文档的交叉引用。
5. **派发**:使用 01 §15.5 的交接模板,附本次补丁后的表结构与契约。
### 6.3 工作量估算
| 阶段 | 内容 | 预估 |
|---|---|---|
| 补表 + 迁移脚本 | §5.1 四组 DDL 及 Alembic 版本 | 0.5 - 1 人日 |
| 契约修正 | §5.2 九项,以文档修改为主 | 0.5 人日 |
| 流程补强 | §5.3 五项,含两张时序图 | 0.5 人日 |
| 交叉核对 | 附录 A 全表 | 0.5 人日 |
| **合计** | | **约 2 人日** |
---
## 附录 A:交叉引用一致性核对表
| 核对项 | 01 | 02 | 03 | 状态 |
|---|---|---|---|---|
| Agent 类型命名(`customer_service` / `advisor` / `risk` / `operations`) | §5.1 | 权限表 | §6.1 | ✅ 一致 |
| 客服五类意图(`faq`/`product_inquiry`/`policy_explain`/`chitchat`/`transfer_human`) | — | 02:429 | §6.1 | ✅ 一致 |
| `conversation_message` 字段(`intent`/`confidence`/`source_references`) | §6.3 | 02:120-126 | §5.6 | ⚠️ 缺 `tool_calls` |
| 转人工工单与风险工单分离 | — | 02:111、220 | §6.4、§10.2 | ✅ 一致 |
| 知识有效过滤条件(`published + active + 有效期`) | §12 | 02:170-174 | §6.2、§7 | ⚠️ `CURRENT_DATE` 时区 |
| 记忆提取触发条件 | §6.2 | — | §12.2 | ✅ 一致 |
| 降级策略矩阵 | §12 | — | §13 | ⚠️ 03 缺事件发布失败 |
| Outbox 可靠投递 | §12 | 仅 `memory_sync_outbox` | §5.7 | ❌ 缺通用事件表 |
| 会话状态与澄清轮次 | §5.2、§6.2 | ❌ 无表 | §5.3 | ❌ 缺表 |
| 工具白名单来源 | §7.3 | 按意图配置 | — | ❌ 语义冲突 |
| 适当性校验(C1-C5/R1-R5) | §10 | `fin_risk_assessment` | §8 | ✅ 一致 |
| 交易范围限定(场内模拟,不建场外表) | §1 | §2 | §9、§11 | ✅ 一致 |
---
## 附录 B:评审发现统计
| 严重度 | 数量 | 分布 |
|---|---|---|
| P0(阻断实施) | 5 | 01: 3 项、02: 4 项、03: 2 项(含交叉项) |
| P1(编码前修正) | 8 | 01: 5 项、02: 3 项、03: 1 项 |
| P2(建议增强) | 6 | 01: 4 项、03: 2 项 |
| 文案级 | 1 | 01: 1 项 |
> 注:单项问题可能跨多份文档,故分文档计数之和大于总数。
---
## 附录 C:v3.0 复审结果
> 复审对象:01(v3.0,1575 行)、02(v3.0,812 行)、03(v3.0,462 行)、新增 `docs/00-新数据库基线设计.md`(1137 行)与 `AGENTS.md`
> 复审方式:四份文档逐行通读 + 基线逐字段比对 + MVC+S 分层约束逐项核对
> 复审结论:**综合 90/100(A-),MVC+S 架构合规性 92/100(A);上一轮 P0 项 5/5 全部闭环**
### C.1 评分变化
| 文档 | v2.1 | v3.0 | 变化 | 等级 |
|---|---|---|---|---|
| `01-通用Agent平台开发设计.md` | 86 | **93** | +7 | A |
| `02-数据库建表设计.md` | 77 | **88** | +11 | B+ |
| `03-平台端到端流程文档.md` | 76 | **89** | +13 | A- |
| **三件套综合** | 80 | **90** | **+10** | **A-** |
| **MVC+S 架构合规性** | 未评 | **92** | — | **A** |
### C.2 MVC+S 架构合规性检查表
| # | 检查项 | 判据 | 结论 |
|---|---|---|---|
| 1 | 四层职责边界明确 | 01 §3.1 同时给出职责与"不允许承担的职责" | ✅ 符合 |
| 2 | Controller 薄层,无业务判断 | 01 §3.3 示例仅路由 + 调 Service + 返回 View | ✅ 符合 |
| 3 | Controller 不访问 Model/ORM | 01 §3.1、02 §3 | ✅ 符合 |
| 4 | Service 编排业务,不解析 HTTP 细节 | 01 §3.1、§573 | ✅ 符合 |
| 5 | Model 仅做实体映射与持久化 | 01 §3.1、02 §3 | ✅ 符合 |
| 6 | View 仅做格式转换,无业务判断与 DB 访问 | 01 §3.1、§131 | ✅ 符合 |
| 7 | 依赖方向 Controller → Service → Repository → Model | 01 §227 | ⚠️ 方向正确,但"`Service -> View`"一句表述有歧义 |
| 8 | Agent 属于 Service 层,非第五层 | 01 §59、AGENTS.md 第 6 条 | ✅ 符合 |
| 9 | 业务 Agent 不得直连 MySQL/Redis/Milvus/Neo4j | 01 §227、§541 | ✅ 符合 |
| 10 | 业务 Agent 必须经 AgentFactory 创建,不绕过鉴权/记忆/合规/审计/事件 | 01 §6.1、§16.3 契约测试、AGENTS.md 第 7 条 | ✅ 符合 |
| 11 | 事务边界收敛在 Service 层 | 01 §5.4 `AgentPersistenceService`、02 §11 | ✅ 符合 |
| 12 | 新增 8 张底座表未破坏分层 | 02 §8,均由 Service 经 Repository 访问 | ✅ 符合 |
| 13 | 新增 6 个服务(Session/Idempotency/ConfigCenter/ModelRouter/Relationship/Persistence)均在 `app/service/` 下 | 01 §4 | ✅ 符合 |
| 14 | 横切关注点(合规、审计、事件)统一在底座,未下放业务 Agent | 01 §11、§13、§16.1 | ✅ 符合 |
| 15 | 场外运营流程独立,不写入场内交易表 | 01 §1、02 §2、03 §11、AGENTS.md 第 8 条 | ✅ 符合 |
**架构层面待改进(不影响合规判定)**
| # | 项 | 说明 |
|---|---|---|
| A-1 | 01 §227 表述歧义 | "`Service -> View` 仅返回中立结果,由 Controller 选择 JSON 或 SSE View" 中箭头方向与后半句矛盾,应改为"Service 不依赖 View;Controller 选择 View" |
| A-2 | Service 层粒度膨胀 | `app/service/` 下已有 15 个子包,应用层编排与基础设施适配器混放;建议在文档中区分 `service/application`(编排)与 `service/infrastructure`(模型网关、工具、存储适配) |
| A-3 | `AgentRequest` 定位 | HTTP 请求模型定义在 Service 层 `contracts.py`,Controller 与 Service 共用同一对象;严格 MVC+S 下 Controller 应持有 API DTO,Service 持有命令对象,建议补充设计说明 |
| A-4 | `app/model/dto/` 归属模糊 | DTO 位于 Model 层,可能造成 Model 被上层反向依赖,文档未说明其用途与依赖方向 |
### C.3 上一轮问题修复验证
**P0(5 项):全部闭环 ✅**
| 编号 | 问题 | 修复证据 |
|---|---|---|
| P0-1 | 澄清轮次无持久化载体 | 新增 `svc_conversation_session`(02 §8.1);01 §6.4 规定"从会话表读取、旧值条件原子递增、Redis 只做缓存";03 §4.2、§5.3 同步 |
| P0-2 | 通用领域事件无 Outbox | 新增 `domain_event_outbox`(02 §8.2);01 §5.4 新增 `AgentPersistenceService.complete_run` 同事务写 Outbox;03 §5.6、§5.7 |
| P0-3 | `tool_calls` 落点缺失 | 澄清基线已含 `tool_calls`(00:853),02 §6.1 明确沿用既有定义,01 `AgentResult` 保留该字段 |
| P0-4 | `allowed_tools` 按意图 vs 单集合 | `ResolvedAgentConfig.allowed_tools_by_intent: Mapping[str, frozenset[str]]`(01:307);§7.3 明确"按当前意图求交集,禁止先求并集";`RiskAgent` 示例已同步(01:1401) |
| P0-5 | 基线不自包含 | 新增 `docs/00-新数据库基线设计.md` 纳入项目,`AGENTS.md` 第 1 条固化为不可变基线 |
**P1(8 项):修复 5、部分 1、未修 2**
| 编号 | 问题 | 状态 | 说明 |
|---|---|---|---|
| P1-1 | 合规策略来源不一致 | ✅ 已修 | `_persist_degraded_result` 增加 `config` 参数并优先使用解析结果(01:862、870、896) |
| P1-2 | `CoreResult` 互斥语义矛盾 | ✅ 已修 | 01:408 改为"必须恰好提供一个" |
| P1-3 | 目录树缺 `bootstrap.py`/`authorizer` | ✅ 已修 | 01:157-158 已补 |
| P1-4 | 伪流式与首字延迟指标冲突 | ✅ 已修 | 01 §12 明示"合规优先、不承诺 Token 级真流式";指标改为"首个安全文本片段延迟"(01:1264) |
| P1-5 | 用户消息先于记忆召回落库、孤儿消息 | ⚠️ 部分 | 顺序未调整;`AgentRequestError` 后遗留用户消息仍未定义处理策略 |
| P1-6 | `CURRENT_DATE` 与 UTC 冲突 | ✅ 已修 | 02:199-200 改为 `UTC_DATE()` |
| P1-7 | 匿名反馈唯一索引 NULL 失效 | ❌ 未修 | 02:361 仍是 `(message_id, customer_id)`,02:373 仍依赖 API 限流 |
| P1-8 | 无归档/分区策略 | ❌ 未修 | 02 全文仍无分区、冷归档或容量规划 |
**P2(6 项):修复 4、部分 1、未修 2**
| 编号 | 问题 | 状态 |
|---|---|---|
| P2-1 | 幂等无落地载体 | ✅ 新增 `request_idempotency`(02 §8.3)+ 01 §6.4 幂等范围定义 |
| P2-2 | Prompt 版本化无载体 | ✅ 新增 `prompt_template_version`(02 §8.8) |
| P2-3 | 意图集合可否扩展未明确 | ✅ 01:1106 明确"数据库不能新增 `supported_intents` 之外的意图" |
| P2-4 | `@final` 可被绕过 | ❌ 仍为 `__dict__` 扫描(01:1455) |
| P2-5 | 覆盖率范围未界定;缺 SLA 数值 | ⚠️ 部分:已补模型超时 15s、重试 1 次、最多 3 端点(01:1168-1170);覆盖率范围仍未界定 |
| P2-6 | 缺时序图;Redis 行情故障未定义 | ⚠️ 部分:03:293 已补行情不可用拒绝下单;时序图仍未补 |
### C.4 v3.0 新发现的问题
| 编号 | 问题 | 位置 | 严重度 |
|---|---|---|---|
| N1 | **`fin_knowledge_meta.content_text` 偏离基线**:基线定义为 `MEDIUMTEXT NOT NULL`,02 实施为 `NULL` 并声明"后续不得更改",属对基线字段定义的单方面变更,未登记偏差 | 00:875、1028 vs 02:181、192 | P1 |
| N2 | **`DomainEvent` 缺 `aggregate_type`**:Outbox 表要求该列 `NOT NULL`,但 01 的事件契约无此字段,填充规则未定义 | 02:503 vs 01:484-489 | P1 |
| N3 | **03 §3 流程顺序与 §5.6 矛盾**:总流程把"View 生成结果"排在"保存 `conversation_message`"之前,而 §5.6 与 01:963 要求持久化成功后才发送最终 SSE | 03:42-46 vs 03:153 | P1 |
| N4 | **02 关系图与 DDL 不一致**:关系图声明 `svc_conversation_session 1 --- N request_idempotency`,但 `request_idempotency` 表无 `session_id` 字段 | 02:132 vs 02:530-554 | P2 |
| N5 | `AgentRequest.metadata: dict[str, Any]` 与 §5.1"不传递无结构的顶层 dict"自相矛盾 | 01:237 vs 259 | P2 |
| N6 | `agent_reply_template` / `agent_intent_config` 的"同一时刻仅一个有效版本"仍靠服务层保证,未采用 `config_release` 已有的生成列唯一约束方案 | 02:412、02:575 | P2 |
| N7 | `model_routing_rule.fallback_endpoint_ids` 用 JSON 数组存储端点 ID,无法用外键保证端点有效性,应用层校验规则未定义 | 02:669 | P2 |
| N8 | 02 §13 验证清单口径与基线不一致:"原 39 张表"包含 02 自建的 6 张专项表,而基线仅有 33 张表 | 02:793 vs 00 §4 | P2 |
### C.5 v3.0 剩余待办
| 优先级 | 事项 |
|---|---|
| P1 | N1 在 02 中登记基线偏差或提请修订基线;N2 为 `DomainEvent` 补 `aggregate_type` 或定义推导规则;N3 修正 03 §3 流程顺序;P1-5 明确孤儿用户消息策略 |
| P2 | P1-7 匿名反馈唯一性、P1-8 归档分区、P2-4 契约测试加固、N4-N8 |
| 架构 | A-1 修正 §227 表述;A-2 划分 Service 层应用/基础设施边界;A-3 说明 `AgentRequest` 定位;A-4 明确 `model/dto` 依赖方向 |
### C.6 复审结论
v3.0 把上一轮的核心缺口全部补齐:会话状态、幂等、通用 Outbox、配置发布中心、多模型路由、Prompt 版本、Neo4j 受控关系视图均有了持久化载体与流程定义,并且新增的 8 张表全部是新增表、表名与基线无冲突,符合 `AGENTS.md` 的不可变基线约束。
**架构判定:符合 MVC+S**。四层职责、依赖方向、Agent 归属、横切关注点收敛均满足约束,业务 Agent 的越权路径在文档层面已被封堵(契约测试 + 工厂 + `@final` + 依赖注入)。
**放行判定:可作为实施基线,建议先修 N1-N3 三项 P1(约 0.5 人日)再派发编码 Agent。**
## 附录 D:v3.1 整改记录
> 整改日期:2026-09-09
> 决策依据:项目负责人对附录 C 问题逐项确认;本附录只记录 v3.1 的处理范围,不改写 v2.1/v3.0 的原始评审结论。
### D.1 已处理项
| 对应意见 | v3.1 整改结果 |
|---|---|
| 记忆提取事件事务一致性 | `complete_run()` 接收提取判定,并在保存助手消息、审计和幂等完成状态的同一事务内写入 `memory.extraction_requested` Outbox;提交后由 Worker 消费。 |
| N2 | `DomainEvent` 补齐 `event_id`、`aggregate_type` 和 `occurred_at`,所有示例事件均给出完整字段。 |
| N3 | 03 总流程调整为先提交消息、审计、幂等状态和 Outbox,再发送最终 JSON 或 SSE;仅 `start` 可以提前发送。 |
| N4 | `request_idempotency` 增加 `session_id`、索引及到 `svc_conversation_session` 的外键,DDL 与关系图一致。 |
| N5 | `AgentRequest.metadata` 改为禁止额外键的结构化模型,只允许 `locale`、`client_version` 和 `ui_entry`。 |
| N6 | 回复模板和意图配置增加 `active_key` 生成列及唯一索引,由数据库保证每个业务组合最多一个 `active` 版本。 |
| N7 | 新增 `model_routing_fallback` 权威关系表,对备用端点建立外键和顺序唯一约束;原 JSON 字段仅保留为兼容快照。 |
| 可恢复运行实体 | 根据接口设计新增 `agent_run`,保存运行状态、Worker 租约、结果定位和安全重试信息;`run_id` 与 `trace_id` 分离。 |
### D.2 明确暂不处理项
| 对应意见 | 决策 |
|---|---|
| 缺少 33 张基础表的可执行 DDL | 本轮不处理。 |
| N1 `content_text` 基线差异 | 本轮不处理。 |
| N8 及表数口径文字问题 | 不专项处理;仅因新增一张平台表将实际总数同步为 48。 |
| 评分与等级不一致 | 本轮不处理,保留原评审记录。 |
### D.3 v3.1 数据库规模
数据库设计现由 33 张基础业务表、6 张智能客服专项表和 10 张平台底座增量表组成,共 49 张。v3.1 仅增加字段和新表,不重命名、删除或改变任何已有表名及已有字段定义。