AgentEvalTool/AGENTS.md
sinohqb 1636ad9a66
All checks were successful
CI / test (pull_request) Successful in 4m22s
docs(ai-context): 合并 AI 上下文文档为单一 AGENTS.md 权威源
- AGENTS.md 改为工具无关的唯一权威正文:迁移 AGENT.md/CLAUDE.md 中仍有效的独有内容(判定与报告口径、require_auth 鉴权、ci-check.sh/pre-push、Gitea Actions 生效条件、简体中文约定),头部声明 CLAUDE.md/AGENT.md 为指针
- CLAUDE.md / AGENT.md 缩为入口指针,禁止再写入正文,杜绝三份副本漂移
- 同步修正关键抽象/配置中已过时的鉴权与 CI 描述(v0.5/v1.0 时代内容 vs 实际 v1.3.1 代码)
2026-09-03 11:59:23 +08:00

18 KiB
Raw Blame History

AGENTS.md

本文件是仓库唯一权威的 AI 助手上下文,适用于所有按 AGENTS.md / CLAUDE.md / AGENT.md 约定加载上下文的工具(Claude Code、Codex、Qoder 等)。根目录 CLAUDE.md 与 AGENT.md 仅为入口指针,正文一律维护在本文件。

项目概述

AgentEvalTool 是智能体质量评估工具集平台(版本号以 pyproject.toml 为单一数据源,勿在此硬编码),用于评估 AI 数字员工(tutu-api 通道)和 AI 助手(OpenClaw)的服务质量。Python 3.11 后端 + TypeScript/React 前端,SQLite 持久化。领域术语词汇表见 CONTEXT.md,关键决策见 docs/adr/。

常用命令

后端

# 安装(含 dev 依赖)
pip install -e ".[dev]"

# 启动 Web 后台
agenteval server start --host 0.0.0.0 --port 8000

# 运行全部测试
pytest

# 运行单个测试文件
pytest tests/unit/test_engine.py -v

# 运行带覆盖率的测试
pytest --cov=backend/agenteval --cov-report=term-missing

# Lint
ruff check backend/
ruff format backend/

前端

cd frontend/web
npm install
npm run dev        # 开发服务器
npm run build      # 构建生产产物(部署前必须执行)
npx tsc --noEmit   # 类型检查

数据库迁移

alembic upgrade head   # 升级到最新
alembic revision --autogenerate -m "描述"  # 生成新迁移

注意:init_db() 调用 SQLModel.metadata.create_all() 直接建表(用于全新 DB),Alembic 用于已有 DB 的增量变更。migrations/env.py 设置了 render_as_batch=True(SQLite 不支持 ALTER TABLE,batch mode 通过重建表来模拟)。

部署(t480)

scripts/deploy-t480.sh             # 一键部署(推荐)
scripts/deploy-t480.sh --skip-build  # 仅代码变更,跳过镜像重建
scripts/deploy-t480.sh --dry-run   # 预览命令

核心架构

三层结构

CLI (Typer) / Web API (FastAPI)
        ↓
EvalEngine(异步评测执行引擎)
        ↓ 通过抽象接口
EvalChannel → 消息通道(tutu-api / openclaw / http)
EvalRule    → 评估规则(keyword / response_time / llm_score / safety / semantic / …)
        ↓
SQLite Storage(Repository 模式)

关键抽象(均在 backend/agenteval/)

  • channels/base.py — EvalChannel 抽象:send() / poll_reply() / health_check();三种实现 tutu.py(tutu-api HTTP + Bearer Token)/ openclaw.py / http.py,ChannelType 枚举(tutu-api / openclaw / http)与工厂注册见 channels/factory.py
  • evaluation/engine.py — EvalEngine:异步执行,遍历 Scenario.cases → messages → 调用 channel → 执行规则 → 写入 DB;通过 asyncio.Event cancel_token 支持真取消;progress_callback 实时推送事件给 WebSocket
  • evaluation/rules/ — EvalRule 抽象,注册表(__init__.py 导入即注册)已扩到 7 种实现:keyword.py / response_time.py / llm_score.py(LLM-as-judge,支持多维评分)/ safety.py / semantic.py / fluency.py / json_schema.py;跨规则隐式逻辑在 evaluation/implicit_rules.py
  • models.py — 共享 Pydantic 模型:EvalTarget → EvalRun ← Scenario,EvalRun → Turn + EvalResult
  • storage/ — db.py(SQLite StaticPool + SQLModel 表定义)+ repository.py(TargetRepository / ScenarioRepository / RunRepository / ResultRepository)+ file_repository.py(FileCategoryRepository / FileRecordRepository)
  • config/settings.py — Pydantic Settings,加载 AGENTEVAL_ 前缀的环境变量(.env 文件),@lru_cache 单例
  • web/deps.py — FastAPI 依赖注入:get_db()(Session 生命周期管理)、require_auth()(鉴权:X-API-Key 匹配 AGENTEVAL_API_KEY 或 X-Auth-Token 匹配 Web 登录会话,任一通过;两个凭据都未配置时 no-op 放行;require_api_key 是兼容别名)

扩展性模式:

  • 规则注册表(evaluation/rules/__init__.py):@register_rule 装饰器将规则类注册到 _RULE_REGISTRY,get_rule(type, params) 按名称实例化。添加新规则只需创建类并加装饰器。
  • 通道工厂(channels/factory.py):ChannelFactory 维护 ChannelType → EvalChannel 映射,register() 类方法支持插件式注册新通道类型。

判定与报告口径

通过率/判定口径是跨版本演进的敏感点(v0.5 判定语义 → v1.3 go/no-go),改动前先读相关 ADR,避免与报告层分叉:

  • case 判定在 evaluation/case_verdict.py;run 级汇总在 evaluation/run_summary.py——judged_pass_rate 剔除连通用例、pass_rate 含执行失败(ADR-0002/0004)
  • 隐式期望规则(期望叠加)在 evaluation/implicit_rules.py;go/no-go 判定在 evaluation/go_no_go.py
  • 术语与口径基线见 CONTEXT.md

API 端点 / Run 生命周期

POST /api/runs → API 先创建 pending 状态的 run,返回 run_id → 后台 asyncio.Task 调用 EvalEngine.run(existing_run=...) 复用该记录 → ws://…/ws/runs/{run_id} 推送实时进度。

WebSocket 事件类型(/ws/runs/{run_id},由 ConnectionManager.emit() 广播):

事件 触发时机 payload
case_start 开始评测一个 case {case_id, case_index, total_cases}
messages_generated 动态 case 的 AI 生成消息完成 {case_id, messages}
turn_start 开始发送一轮对话 {case_id, turn_index}
turn_end 收到回复 {case_id, turn_index, reply, latency_ms}
turn_error 发送或接收失败 {case_id, turn_index, error}
rule_result 单条规则评估完成 {case_id, turn_index, rule_type, passed, score, reason}
case_end case 评测完成 {case_id, passed, score, error?}
run_completed 整个 run 完成 {run_id, status}
error case 级或 run 级错误 {case_id?, message}

前端 sessionReducer.ts 的 WS_EVENT action 处理以上所有事件类型。

OpenClaw 代理

后端通过 routers/proxy.py 提供 HTTP + WebSocket 反向代理(路径前缀 /openclaw),将请求转发到 OpenClaw 服务(docker-compose 中 openclaw-eval 容器)。WS 桥接会自动注入认证 token 到 connect.authenticate 消息并重写 Origin 头。前端 /openclaw 路由以全屏 iframe 嵌入代理地址。

文件管理模块

/api/files 端点提供分类树 + 文件上传/下载功能。FileCategoryDB 自引用(parent_id)实现树形结构,FileRecordDB 关联分类。文件物理存储在 data/uploads/,按分类子目录组织。删除分类会级联删除子分类 + 文件记录 + 物理文件。

模型配置中心

/api/model-configs 统一管理工程使用的外部模型连接。provider 表示调用协议,capability 表示评测用途能力(对话、向量、审核),input_modalities / output_modalities 表示模型自身支持的文本、图像、音频和视频模态,三者禁止混用。厂商、区域、上下文窗口、最大输出和模型特性属于描述性元数据,会写入评测运行快照,但不直接改变网关请求参数。

智能评估执行机制(方案③触发式执行,ADR-0009/0010/0011/0012)

Worker 自动化由平台掌控节奏:web/app.py lifespan 只做 scheduler_runtime.start()/stop(),实际循环在 intelligent_eval/scheduler.py——每 60s(SCAN_INTERVAL_SECONDS)执行一次 scan_once(),单个节拍依次:

  1. requeue_stale_assigned_tasks(task_queue.py):assigned 超时(>10min)且评估 executing → 回 pending(卡死恢复)
  2. expire_stale_running_sessions:running 会话 60min 无新轮次 → expired
  3. watchdog 判死:planning 双闸 / executing 超窗 / 连续触发失败超限 → failed
  4. scan_and_enqueue_tasks:扫描 executing 评估,按时段到期 + 欠账(domain.py::get_attention_reason)→ 入队(每评估去重一条 pending/assigned)
  5. settle_tasks_for_finished_evals:评估离开 executing 后,其 pending/assigned 任务回收为 completed
  6. supplement_decision_logs:agent 未上报决策日志时按状态兜底补录(审计)
  7. analyst 催促:末会话终态 10min 后落账 nudge,交给 worker 触发转 analyst 汇总报告
  8. 触发:fire-and-forget(不阻塞 60s 节拍)唤醒 worker / planner

关键约束(改动此机制前必读):

  • 触发原语:docker exec openclaw-eval timeout 600 openclaw agent --agent main --session-id <prefix>-<ts> -m <msg> --json,subprocess 侧 timeout 660
  • 独立 session:session-id 形如 agenteval-worker-<ts> / agenteval-planner-<ts>,禁止复用 --agent main 的持久 session——main session 多次触发累积上下文缓存(~12 万 token)后 worker 会幻觉输出而不执行 API 步骤(ADR-0010)
  • 时段分布:触发消息强制"仅执行当前时间对应时段(time_distribution)内欠账的会话",绝不创建未来时段会话——未来时段由后续 60s 节拍再次触发;所有时段会话到终态才转 analyst
  • 状态一致性:submit_report 要求会话全部 close 才允许 completed(intelligent_eval/lifecycle/report_submission.py);per-eval 触发冷却 10min 防 60s 节拍堆叠并发 agent

扩展域速览(v1.1+ 新增域,早期架构文档未覆盖)

  • Campaigns(评估活动):evaluation/campaign_{lifecycle,runner,scheduler,read_model,report}.py + models.py 的 CampaignDB;路由在 web/routers/campaigns.py;跨 run 聚合见 comparison.py / analysis.py / go_no_go.py / cost_tracking.py;后台任务登记在 task_registry.py
  • IntelligentEvals(智能评估域):intelligent_eval/——domain.py(优先级/注意力)、lifecycle/(状态机,终止态纪律见 ADR-0011/0012)、task_queue.py、decision_logs.py、config_snapshot.py、read_model.py / report.py;软删语义只在 repository.py(见陷阱 6)
  • Exploration(探索巡检):exploration/patrol.py(巡检)+ judge.py + summary.py,自带 models/errors/lifecycle
  • 模型网关:model_gateway.py 是统一模型调用出口,model_protocols/ 为 openai / anthropic / dashscope / gemini 协议适配器;services/ 承载 runs / model_configs / files 业务
  • AI 协作文档:Gitea Issues 工作流、triage 标签、领域文档约定见 docs/agents/(issue-tracker / triage-labels / domain)

前端

SPA 由 FastAPI 托管(GET /{full_path:path} → index.html)。frontend/web/src/api.ts 是所有接口定义的单一出口,axios 拦截器统一处理错误。useRunSession.ts hook 管理 WebSocket 实时状态。

路由采用 keep-alive 标签页模式(非标准 <Routes>):所有页面同时挂载在 DOM 中,通过 display: block/none 切换可见性,保证页面状态在切 tab 时不丢失。路由配置集中在 App.tsx 的 routeConfigs 数组中,Zustand tabStore 管理标签页的打开/关闭/激活状态,react-router 仅用于 URL 同步。

三种状态管理策略:

  • Zustand(stores/tabStore.tsx):全局标签页状态,唯一一个 store
  • useReducer(hooks/sessionReducer.ts):评测会话状态(cases/turns/results/progress),纯 reducer 无副作用,hook 负责 WS/REST 副作用
  • useState:各页面本地数据,无全局缓存(不使用 SWR/React Query)

UI 交互一致性标准(ADR-0005,全部管理页必须遵守):

  • 页壳:PageWrapper inline fullHeight;详情/报告用页内 view 切换或右侧 Drawer 弹出(非新路由),列表常驻
  • 表格:Empty 空态;超过 20 行才分页(pageSize 20);禁止 scroll.y calc hack,外层 div overflowY: auto
  • 表单:components/FormDrawer.tsx(宽 640、页脚取消/确定、submitting loading、destroyOnClose)
  • 危险操作(删除/取消/停止):Popconfirm
  • 反馈:axios 拦截器统一 message,页面不重复弹错
  • 活动状态轮询:usePolling(fn, ms, enabled) 5 秒静默轮询(reload(true))

Vite 开发代理:dev server 端口 3000,/api、/ws、/openclaw 全部代理到 localhost:8000;生产构建时 dist/ 由 FastAPI 直接托管。

Ant Design CSS-in-JS 冲突:Ant Design v5 的 emotion 运行时注入会覆盖静态 CSS 的 flex/height 规则。Runs 页采用"导航与内容分离"方案——Tabs 组件仅用于渲染导航头(.nav-only-tabs CSS 隐藏 .ant-tabs-content-holder),实际内容 div 由 flex 直接控制高度,绕过 Ant Design 内部 DOM。

构建注意:@monaco-editor/react 独立拆分为 vendor-monaco chunk(vite.config.ts 的 manualChunks)。设计 Token 在 tokens.ts 集中管理(组件直接 import,非 CSS 变量方案)。

开发约定

  • 版本号单一数据源:pyproject.toml → 由 scripts/sync_version.py 同步到 package.json,部署脚本自动执行
  • Python lint:ruff,line-length 120,target py310
  • 前端类型检查:tsc --noEmit
  • 文档文件名带版本号,如 architecture-v1.0.md
  • SQLite 必须用 StaticPool + check_same_thread=False,否则多线程下连接耗尽
  • 所有 Router 用 Depends(get_db) 管理 Session,后台任务也需 try/finally 关闭
  • frontend/web/dist/ 是构建产物,部署前必须 npm run build,否则 t480 跑旧版
  • 本地 CI 入口:scripts/ci-check.sh(版本一致性 + ruff + pytest + tsc;--fast 跳过 tsc)。pre-push hook 在 .git/hooks/pre-push(新 clone 需手动重装),push 自动跑 ci-check.sh --fast;紧急 git push --no-verify 后必须补跑
  • 交互与文档使用简体中文

配置

  • .env(参考 .env.example):AGENTEVAL_API_KEY(机器调用凭据,X-API-Key)、AGENTEVAL_ADMIN_PASSWORD(Web 登录口令,设置后走会话 token)、AGENTEVAL_ALLOWED_ORIGINS(CORS,首个非 localhost URL 用作 OpenClaw WS Origin)、AGENTEVAL_OPENCLAW_* 系列(代理上游地址 + 认证 token)
  • config/config.json:tutu-api 通道配置(敏感信息,只读挂载到容器)
  • 无托管 CI 实际运行:Gitea Actions 工作流(.gitea/workflows/ci.yml)需实例启用 Actions + runner 才生效;本地质量门是 pre-push hook;部署依赖手动执行 scripts/deploy-t480.sh

测试结构

tests/
├── conftest.py          # 根级 fixtures(内存 SQLite DB、TestClient)
├── unit/                # 数十个按领域划分的单测(engine/各规则、campaign_*、intelligent_eval_*、exploration_*、model_*、task_queue 等)
│   └── mock_channel.py  # 测试用 MockChannel
└── integration/         # 数十个集成测试:每 router 一个 test_<area>_api.py(runs/campaigns/intelligent_evals/targets/...)+ 调度与端到端(test_*_scheduler*.py、test_intelligent_eval_e2e.py)
    └── conftest.py      # 集成级 fixtures

pytest 配置集中在 pyproject.toml 的 [tool.pytest.ini_options]:pythonpath = ["backend", "."](测试直接 import agenteval 和 cli)、asyncio_mode = "auto"(async 测试无需显式装饰器)。

部署关键信息

  • t480:sola-t480(192.168.8.145:8001),Docker Compose 双容器(agenteval + openclaw-eval)
  • 端口映射:主机 8001 → 容器 8000(主机 8000 已被占用)
  • 健康检查:curl http://192.168.8.145:8001/api/health 返回 version/commit,脚本验证与本地 pyproject.toml 一致
  • 回滚:docker tag t480-agenteval:<old-version> t480-agenteval:latest && docker compose up -d
  • backend/ 通过 volume 热加载,只有 Python 依赖变更才需要 --build
  • 前端 dist/ 也通过 volume 挂载,因此每次前端变更也需要重新构建(./scripts/deploy-t480.sh 自动处理)
  • openclaw-eval 容器使用 ghcr.io/openclaw/openclaw:latest 镜像,通过 AGENTEVAL_API_URL 环境变量连接 agenteval

已知陷阱

  1. index.css 必须显式 import:Vite 不会自动包含未被引用的 CSS。main.tsx 必须有 import './index.css',否则全局样式(动画、布局覆盖、nav-only-tabs)全部无效且无报错。这是历史遗留 bug(项目创建以来就缺失),已修复但容易在重构时再次丢失。

  2. UTC 时间戳解析:后端返回的 datetime 字符串不含时区标记(如 "2026-07-14T16:40:00"),JS 的 new Date() 会按本地时间解析导致偏差 8 小时。前端必须使用 utils/date.ts 的 toDate() 函数(检测并追加 Z 后缀),禁止直接 new Date(str)。根本修复需后端序列化时附带时区。

  3. Ant Design CSS-in-JS 覆盖:Ant Design v5 的 emotion 运行时注入优先级高于静态 CSS 文件中的 flex/height/overflow 规则。需要弹性布局的容器避免依赖 .ant-tabs-content 等 Ant 内部 DOM,改用导航与内容分离方案。见上方前端架构说明。

  4. SQLite 连接池:必须用 StaticPool + check_same_thread=False。使用默认连接池会导致多线程下连接耗尽(async + FastAPI 线程池混合场景)。

  5. .env 在 t480 上需手动维护:部署脚本不会自动同步 .env 文件,新增环境变量需 SSH 到主机手动补全。容易导致 OpenClaw 代理等功能静默失效。

  6. 智能评估可见性接缝:逻辑删除(deleted 状态)语义只住在 intelligent_eval/repository.py——查询走 IntelligentEvalRepository.visible() 谓词,存在性校验走 require_live_eval(路由层翻译为 404)。禁止在其他模块直查 IntelligentEvalDB 判断删除态,否则删除语义会在多处漂移(Phase 1 收敛前的教训:快照、决策日志、任务监控各自实现了一遍"已删即 404")。

  7. AI 上下文文档结构:AGENTS.md 是仓库唯一权威正文;CLAUDE.md / AGENT.md 仅是指针文件(引导工具读本文件),禁止往指针文件写入正文性内容——三份全量副本时代各自漂移的历史教训(各自持有独有段落、互不一致)勿重演。