AgentEvalTool/AGENTS.md

200 lines
12 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.

# AGENTS.md
This file provides guidance to Codex (Codex.ai/code) when working with code in this repository.
## 项目概述
**AgentEvalTool** 是智能体质量评估工具集平台v1.0.0),用于评估 AI 数字员工tutu-api 通道)和 AI 助手OpenClaw的服务质量。Python 3.11 后端 + TypeScript/React 前端SQLite 持久化。领域术语词汇表见 `CONTEXT.md`,关键决策见 `docs/adr/`
## 常用命令
### 后端
```bash
# 安装(含 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/
```
### 前端
```bash
cd frontend/web
npm install
npm run dev # 开发服务器
npm run build # 构建生产产物(部署前必须执行)
npx tsc --noEmit # 类型检查
```
### 数据库迁移
```bash
alembic upgrade head # 升级到最新
alembic revision --autogenerate -m "描述" # 生成新迁移
```
注意:`init_db()` 调用 `SQLModel.metadata.create_all()` 直接建表(用于全新 DBAlembic 用于已有 DB 的增量变更。`migrations/env.py` 设置了 `render_as_batch=True`SQLite 不支持 ALTER TABLEbatch mode 通过重建表来模拟)。
### 部署t480
```bash
scripts/deploy-t480.sh # 一键部署(推荐)
scripts/deploy-t480.sh --skip-build # 仅代码变更,跳过镜像重建
scripts/deploy-t480.sh --dry-run # 预览命令
```
## 核心架构
### 三层结构
```
CLI (Typer) / Web API (FastAPI)
EvalEngine异步评测执行引擎
↓ 通过抽象接口
EvalChannel → 消息通道TutuApiChannel
EvalRule → 评估规则keyword_match / response_time / llm_score
SQLite StorageRepository 模式)
```
### 关键抽象(均在 `backend/agenteval/`
- **`channels/base.py`** — `EvalChannel` 抽象:`send()` / `poll_reply()` / `health_check()`V1 实现为 `TutuApiChannel`tutu-api HTTP + Bearer Token
- **`evaluation/engine.py`** — `EvalEngine`:异步执行,遍历 Scenario.cases → messages → 调用 channel → 执行规则 → 写入 DB通过 `asyncio.Event` cancel_token 支持真取消;`progress_callback` 实时推送事件给 WebSocket
- **`evaluation/rules/`** — `EvalRule` 抽象,三种实现:`keyword.py`(关键词匹配)、`response_time.py`(延迟阈值)、`llm_score.py`LLM-as-judge
- **`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_api_key()`X-API-Key 鉴权)
**扩展性模式**
- **规则注册表**`evaluation/rules/__init__.py``@register_rule` 装饰器将规则类注册到 `_RULE_REGISTRY``get_rule(type, params)` 按名称实例化。添加新规则只需创建类并加装饰器。
- **通道工厂**`channels/factory.py``ChannelFactory` 维护 `ChannelType → EvalChannel` 映射,`register()` 类方法支持插件式注册新通道类型。
### 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` 表示模型自身支持的文本、图像、音频和视频模态,三者禁止混用。厂商、区域、上下文窗口、最大输出和模型特性属于描述性元数据,会写入评测运行快照,但不直接改变网关请求参数。
### 前端
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**ruffline-length 120target 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 跑旧版
## 配置
- `.env`(参考 `.env.example``AGENTEVAL_API_KEY`X-API-Key 鉴权)、`AGENTEVAL_ALLOWED_ORIGINS`CORS首个非 localhost URL 用作 OpenClaw WS Origin、`AGENTEVAL_OPENCLAW_*` 系列(代理上游地址 + 认证 token
- `config/config.json`tutu-api 通道配置(敏感信息,只读挂载到容器)
- 无 CI/CD部署依赖手动执行 `scripts/deploy-t480.sh`
## 测试结构
```
tests/
├── conftest.py # 共享 fixtures内存 SQLite DB、TestClient
├── unit/
│ ├── mock_channel.py # 测试用 MockChannel
│ ├── test_engine.py # EvalEngine 单元测试asyncio_mode=auto
│ ├── test_cascade.py # 级联删除测试
│ └── test_settings.py # Pydantic Settings 测试
└── integration/
└── test_runs_api.py # Runs API 集成测试
```
`pytest.ini_options``pythonpath = ["backend", "."]`,测试直接 import `agenteval``cli`
## 部署关键信息
- **t480**`sola-t480`192.168.8.145:8001Docker 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 代理等功能静默失效。