# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## 项目概述

**AgentEvalTool** 是智能体质量评估工具集平台（v0.2.0），用于评估 AI 数字员工（tutu-api 通道）和 AI 助手（OpenClaw）的服务质量。Python 3.11 后端 + TypeScript/React 前端，SQLite 持久化。

## 常用命令

### 后端

```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()` 直接建表（用于全新 DB），Alembic 用于已有 DB 的增量变更。`migrations/env.py` 设置了 `render_as_batch=True`（SQLite 不支持 ALTER TABLE，batch 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 Storage（Repository 模式）
```

### 关键抽象（均在 `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/`，按分类子目录组织。删除分类会级联删除子分类 + 文件记录 + 物理文件。

### 前端

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.ts`）：全局标签页状态，唯一一个 store
- **useReducer**（`hooks/sessionReducer.ts`）：评测会话状态（cases/turns/results/progress），纯 reducer 无副作用，hook 负责 WS/REST 副作用
- **useState**：各页面本地数据，无全局缓存（不使用 SWR/React Query）

**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 跑旧版

## 配置

- `.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: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 代理等功能静默失效。
