diff --git a/docs/archive/README.md b/docs/archive/README.md index f1bb5b0..8ce56e5 100644 --- a/docs/archive/README.md +++ b/docs/archive/README.md @@ -7,3 +7,4 @@ | 2026-08-11 | Campaign 架构深化与正式线交付 | 已完成并部署 | [campaign-architecture-deepening-20260811-v1.0.md](campaign-architecture-deepening-20260811-v1.0.md) | | 2026-08-21 | 智能评估架构深化(scheduler 抽取 / 去重内化 / 状态机归一 / 残留清理 / 标签单一出口) | 已完成并推送 | [intelligent-eval-architecture-deepening-20260821-v1.2.html](intelligent-eval-architecture-deepening-20260821-v1.2.html) | | 2026-08-24 | 架构审查:五个深化候选(可见性接缝 / 智能作业归一 / repository 拆分 / 前端读取接缝 / api.ts 拆域) | 审查报告,候选已全部实施 | [codebase-architecture-review-20260824.html](codebase-architecture-review-20260824.html) | +| 2026-08-24 | 全面代码库健康审查:八个候选(Campaigns.tsx 拆分 / repository 拆分 / 工具函数收敛 / 编排下沉 / lifecycle 拆分 / db 拆分 / 测试补全 / Engine 解耦) | 审查报告,候选 1-7 已全部实施,候选 8 刻意不做 | [codebase-architecture-review-health-20260824.html](codebase-architecture-review-health-20260824.html) | diff --git a/docs/archive/codebase-architecture-review-health-20260824.html b/docs/archive/codebase-architecture-review-health-20260824.html new file mode 100644 index 0000000..93ce7a5 --- /dev/null +++ b/docs/archive/codebase-architecture-review-health-20260824.html @@ -0,0 +1,656 @@ + + + + + +AgentEvalTool 架构审查报告 — 2026-08-24 + + + + + + +
+ + +
+

AgentEvalTool 架构审查报告

+

2026-08-24 · 全面代码库健康审查

+
+
+
15,609
+
后端代码行 (101 文件)
+
+
+
11,576
+
前端代码行 (75 文件)
+
+
+
18,289
+
后端测试行 (101 文件)
+
+
+
1,347
+
前端测试行 (12 文件)
+
+
+
+ + +
+
+

架构全景

+
+
+
+graph TB + subgraph Frontend["前端 (React + TypeScript)"] + Pages["Pages
11 页面组件
~4,100 行"] + Components["Components
~25 共享组件
~3,600 行"] + API["API Layer
10 域模块
~720 行"] + Read["Read Module
泛化资源接缝
~430 行"] + Hooks["Hooks
7 hooks
~640 行"] + end + + subgraph Backend["后端 (Python 3.11 + FastAPI)"] + Routers["Routers
12 路由
~2,100 行"] + Domain["Domain Layer
intelligent_eval / evaluation / exploration"] + Storage["Storage Layer
6 repositories
~2,300 行"] + Channels["Channels
3 adapters
~700 行"] + end + + subgraph Persistence["持久化"] + SQLite["SQLite
15+ 表定义
~700 行"] + end + + Pages --> API + Pages --> Components + Pages --> Read + Read --> API + Hooks --> API + API --> Routers + Routers --> Domain + Domain --> Storage + Domain --> Channels + Storage --> SQLite +
+ +
+ 整体评价:分层清晰,storage 不依赖 web/channels,domain 层通过 repository 模式与存储交互。 + 后端测试覆盖率高(测试行 > 源码行)。主要问题集中在前端大组件后端 god module重复工具函数三个方向。 +
+
+
+ + +

改进候选点

+ + +
+
+
+

1. Campaigns.tsx — 前端 God Component

+

frontend/web/src/pages/Campaigns.tsx · 1,018 行

+
+ Strong +
+
+
+

问题

+

整个活动管理页面(列表、创建表单、详情视图、报告渲染、时间线、分析区、对比视图、探索式评测)全部塞在一个 1,018 行的组件里。包含 9 个 return 语句、286 个 JSX 标签。任何局部修改都需要在这个巨文件中导航,AI 代理处理时容易丢失上下文。

+
+ +
+
+
Before — 单一巨组件
+
+Campaigns.tsx (1,018 lines)
+├── 列表视图 + 筛选
+├── 创建表单 (FormDrawer)
+├── 展开行 (expandedRowRender)
+│ ├── 时间线 (WindowTimeline)
+│ ├── Run 时间线 (CampaignRunTimeline)
+│ ├── 分析区 (AnalysisSection)
+│ ├── 对比区 (PeriodComparisonSection)
+│ └── 探索区 (ExplorationSection)
+├── 报告视图 (单 report 查看)
+├── 本地 StatusBadge, KpiItem, SectionTitle
+└── fmtPct(), targetName(), rateColorName() +
+
+
+
After — 按视图职责拆分
+
+pages/Campaigns.tsx (~200 lines)
+├── 列表壳 (table + filter + drawer)
+└── 路由到子视图
+
+components/campaigns/
+├── CampaignForm.tsx (创建/编辑表单)
+├── CampaignDetail.tsx (展开行容器)
+├── CampaignReport.tsx (单报告视图)
+├── CampaignAnalysis.tsx (分析区)
+└── CampaignStatusBadge.tsx (状态标签)
+
+utils/campaignFormat.ts (fmtPct, targetName) +
+
+
+ +
+

收益

+
    +
  • Locality:修改分析区只动 CampaignAnalysis.tsx,不会意外影响列表
  • +
  • LeverageCampaignForm 可被其他入口复用(如从报告页快速创建新活动)
  • +
  • 可测试性:当前零测试,拆分后每个子组件可独立测试
  • +
+
+
+
+ + +
+
+
+

2. storage/repository.py — 六合一 Repository 文件

+

backend/agenteval/storage/repository.py · 814 行

+
+ Strong +
+
+
+

问题

+

一个文件包含 BaseRepositoryTargetRepositoryScenarioRepositoryRunRepositoryCampaignRepositoryResultRepository 六个类。Phase 2+3 拆分后仍残留 814 行。每次修改某个域的 repository 都要在这个大文件中定位,且所有域的 import 都指向同一个模块(from agenteval.storage.repository import ...),形成宽接缝。

+
+ +
+
+
Before — 六合一
+
+graph LR + A[storage/repository.py
814 lines] --> B[BaseRepository] + A --> C[TargetRepository] + A --> D[ScenarioRepository] + A --> E[RunRepository] + A --> F[CampaignRepository] + A --> G[ResultRepository] +
+
+
+
After — 按域拆分
+
+graph LR + A[storage/base_repository.py] --> B[BaseRepository] + C[storage/target_repository.py] --> D[TargetRepository] + E[storage/scenario_repository.py] --> F[ScenarioRepository] + G[storage/run_repository.py] --> H[RunRepository] + I[storage/campaign_repository.py] --> J[CampaignRepository] + K[storage/result_repository.py] --> L[ResultRepository] +
+
+
+ +
+ 注意:已有先例——async_job_repository.py(247 行)、exploration_repository.py(135 行)、model_config_repository.py(114 行)、file_repository.py(156 行)都已独立拆分。剩余 6 个是 Phase 2+3 未触及的遗留。 +
+ +
+

收益

+
    +
  • Locality:修改 Run 查询逻辑只动 run_repository.py
  • +
  • 一致性:与已有的 async_job/exploration/model_config/file repository 对齐
  • +
  • 减少 import 耦合:需要 RunRepository 的模块不再被迫 import 整个 814 行文件
  • +
+
+
+
+ + +
+
+
+

3. 前端重复工具函数 — personaLabel / fmtPct / targetName

+

跨 10+ 文件,至少 5 种重复模式

+
+ Worth exploring +
+
+
+

问题

+

三个工具函数在多个组件中各自实现,逻辑完全相同:

+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
函数出现次数位置
personaLabel()3ExecutionProcess.tsx:30, EvalOverview.tsx:9, EvalReport.tsx:24
fmtPct()5Campaigns.tsx:118, PeriodComparisonSection.tsx:31, ExplorationSection.tsx:34, CampaignRunTimeline.tsx:96, Reports.tsx:499
targetName()2Campaigns.tsx:150, IntelligentEvals.tsx:105
_translate() 错误翻译3routers/intelligent_evals.py:64, routers/exploration.py:45, routers/model_configs.py:29
+
+ +
+
+
Before — 各自实现
+
+// ExecutionProcess.tsx:30
+function personaLabel(persona: Record<string, unknown>): string {
+  return (persona.background as string) || '未设置人设';
+}
+
+// EvalOverview.tsx:9
+function personaLabel(persona: Record<string, unknown>): string {
+  return (persona.background as string) || '未设置人设';
+}
+
+// Campaigns.tsx:118
+function fmtPct(v: number | null) {
+  return v == null ? '—' : `${(v * 100).toFixed(1)}%`;
+} +
+
+
+
After — 收敛到 utils
+
+// utils/format.ts
+export function fmtPct(v: number | null): string {
+  return v == null ? '—' : `${(v * 100).toFixed(1)}%`;
+}
+
+// utils/persona.ts
+export function personaLabel(
+  persona: Record<string, unknown>
+): string {
+  return (persona.background as string)
+    || '未设置人设';
+}
+
+// 所有消费者 import from utils +
+
+
+ +
+

收益

+
    +
  • Locality:修改百分比格式(如加千位分隔符)只需改一处
  • +
  • 一致性:消除不同文件中格式微妙的差异
  • +
  • 深度:工具函数本身就是深模块的候选——小接口,行为确定
  • +
+
+
+
+ + +
+
+
+

4. runs.py Router — 编排逻辑泄漏到路由层

+

backend/agenteval/web/routers/runs.py · 211 行

+
+ Worth exploring +
+
+
+

问题

+

_run_evaluation()(35-66 行)是一个后台协程,在 router 文件内创建 EvalEngine、管理 session 生命周期、调用 webhook。这是 use-case 编排逻辑,应该住在 service/use-case 层。同样,get_run_logs()(139-211 行)在 router 内做大量数据组装。

+
+ +
+
+
Before — 编排在 Router
+
+graph TB + A[POST /api/runs] --> B[routers/runs.py] + B --> C[_run_evaluation()
后台协程] + C --> D[EvalEngine] + C --> E[get_session] + C --> F[send_run_webhook] + B --> G[get_run_logs
数据组装 70 行] +
+
+
+
After — 编排下沉到 Service
+
+graph TB + A[POST /api/runs] --> B[routers/runs.py
~50 lines] + B --> C[services/run_execution.py
RunExecutionService] + C --> D[EvalEngine] + C --> E[send_run_webhook] + B --> F[services/run_read.py
RunReadService] +
+
+
+ +
+

收益

+
    +
  • 可测试性:编排逻辑可在无 FastAPI 上下文下单元测试
  • +
  • 复用:CLI 入口也可调用同一 service 触发评测运行
  • +
  • Seam 清晰:router 只做 HTTP 翻译,service 做编排
  • +
+
+
+
+ + +
+
+
+

5. intelligent_eval/lifecycle.py — 单体状态机

+

backend/agenteval/intelligent_eval/lifecycle.py · 866 行

+
+ Worth exploring +
+
+
+

问题

+

整个智能评估领域的状态机 + 所有领域操作集中在一个文件:create、delete、list、submit_plan、approve、reject、cancel、open_session、conduct_turn、close_session、submit_report,加上所有转换守卫。866 行,import 了 5 个模块。这是项目中最深的领域模块,但深度以"大"而非"杠杆"衡量。

+
+ +
+ 权衡:这个文件是智能评估的核心状态机。ADR-0008 明确要求加深智能评估接缝。近期已经历多轮重构(watchdog 收敛、scheduler 拆分、decision-log 去重)。进一步拆分需要谨慎——状态转换的完整性是这个模块的核心价值,拆散可能反而增加跨文件追踪转换的成本。 +
+ +
+
+
当前 — 单体 lifecycle
+
+lifecycle.py (866 lines)
+├── _TRANSITIONS 转换表
+├── create_eval()
+├── delete_eval()
+├── list_evals()
+├── submit_plan()
+├── approve() / reject()
+├── cancel()
+├── open_session()
+├── conduct_turn() ← 最复杂
+├── close_session()
+└── submit_report() +
+
+
+
可选 — 按阶段职责拆分
+
+lifecycle.py (~200 lines)
+├── _TRANSITIONS 转换表
+├── create_eval() / delete_eval()
+├── list_evals()
+└── _transition() 守卫
+
+plan_management.py
+├── submit_plan() / approve() / reject()
+
+session_execution.py
+├── open_session() / conduct_turn()
+├── close_session()
+
+report_submission.py
+└── submit_report() +
+
+
+ +
+

收益

+
    +
  • 可测试性conduct_turn() 的测试不需要 setup 整个状态机
  • +
  • Locality:修改计划审批逻辑只动 plan_management.py
  • +
  • 风险:状态转换完整性需要 characterization 测试锁定
  • +
+
+
+
+ + +
+
+
+

6. storage/db.py — 15+ 表定义堆积

+

backend/agenteval/storage/db.py · 709 行

+
+ Speculative +
+
+
+

问题

+

所有 SQLModel 表定义(EvalTargetDB、ScenarioDB、EvalRunDB、TurnDB、EvalResultDB、CampaignDB、IntelligentEvalDB、IntelligentEvalSessionDB、IntelligentEvalMessageDB、IntelligentEvalDecisionLogDB、IntelligentEvalTaskQueueDB、IntelligentEvalConfigSnapshotDB、ExplorationSessionDB、ExplorationMessageDB、ModelConfigDB、FileCategoryDB、FileRecordDB 等 17+ 个表)+ engine 初始化 + session 工厂全在一个文件。随着新领域实体增加,这个文件会持续增长。

+
+ +
+ 审慎建议:当前 709 行尚在可控范围。拆分的收益主要在"找表定义时不用翻太多行"。但如果未来再加 5+ 表(如 A/B 测试、回归基线),建议按领域拆分为 db/targets.pydb/evaluations.pydb/intelligent_eval.py 等。现阶段优先级低于候选 1-4。 +
+
+
+ + +
+
+
+

7. 前端测试覆盖空白

+

12 测试文件 vs 75 源文件 · 页面组件零覆盖

+
+ Speculative +
+
+
+

问题

+

前端测试集中在 intelligent_eval/ 子组件和 read/ 模块(最近重构新增),但以下区域完全无测试:

+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
未覆盖区域代码行数风险
所有 Page 组件 (11 个)~4,100 行高 — 用户直接交互界面
RunList, CaseDetail, PeriodComparison 等共享组件~1,200 行中 — 被多页面复用
useRunSession, useFiles, usePolling hooks~480 行中 — 含 WS/REST 副作用
所有 API 模块 (10 个)~720 行低 — 薄包装,但类型安全靠 tsc
+
+ +
+ 建议策略:不为测试而测试。优先在修改 Page 组件时(如候选 1 拆分 Campaigns.tsx)同步补测试。hooks 的测试优先级高于 Page(副作用逻辑更密集)。API 层靠 TypeScript 类型检查即可。 +
+
+
+ + +
+
+
+

8. EvalEngine 与 Storage 耦合

+

backend/agenteval/evaluation/engine.py · 629 行

+
+ Speculative +
+
+
+

问题

+

EvalEngine 直接 import get_sessionRunRepositoryResultRepository,在引擎内部管理数据库写入。核心评测执行引擎不是存储无关的——它既是领域逻辑(发消息、执行规则),又是持久化逻辑(写 run、写 result)。同样模式出现在 campaign_runner.pyanalysis.pyexploration/judge.py

+
+ +
+graph LR + subgraph Engine["EvalEngine (629 lines)"] + A[send message] --> B[execute rules] + B --> C[write to DB] + end + subgraph Storage["直接依赖 Storage"] + D[get_session] + E[RunRepository] + F[ResultRepository] + end + Engine --> Storage +
+ +
+ 审慎建议:解耦 Engine 需要引入 Unit-of-Work 或 Repository 注入模式,改造量大。当前通过 MockChannel + 内存 DB 测试已能覆盖。除非需要替换存储后端(如迁移到 Postgres),否则改造 ROI 不高。标记为 speculative。 +
+
+
+ + +
+
+

总结与优先推荐

+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
#候选点强度改造量核心收益
1Campaigns.tsx 拆分Strong消除前端最大 god component
2storage/repository.py 拆分Strong与已有拆分模式对齐
3前端重复工具函数收敛Worth消除 10+ 处重复
4runs.py 编排下沉WorthRouter 回归纯翻译
5lifecycle.py 状态机拆分Worth降低核心状态机认知负荷
6storage/db.py 表定义拆分Speculative找表定义更快
7前端测试覆盖补全Speculative页面级回归保护
8Engine/Storage 解耦Speculative存储可替换性
+
+ +
+

Top Recommendation: 候选 2 — storage/repository.py 拆分

+

+ 理由:改造量最小(已有 4 个独立 repository 先例,模式成熟),收益确定性最高(与已有模式对齐,消除 814 行 god file),风险最低(纯机械拆分 + import 路径更新,不涉及逻辑变更)。 + 建议作为下一步立即执行的改进。 +

+

+ 次选:候选 3(前端重复工具函数收敛),同样是小改造、高确定性收益。 +

+
+
+
+ +
+ + + +