# agent-loadtest **Repository Path**: liukai_1900/agent-loadtest ## Basic Information - **Project Name**: agent-loadtest - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-08-10 - **Last Updated**: 2026-08-20 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # agent-loadtest 基于 [Harbor](https://github.com/harbor-framework/harbor) 的 Agent 性能压测框架。以 YAML 场景驱动,在 Docker 容器中并发运行 Agent 任务,输出吞吐、延迟分层、成功率与资源消耗报告,并支持梯度加压探测最大可承载并发。 设计文档见 [docs/design.md](docs/design.md)。 ## 功能特性 - **4 种压测模式**:单 Agent 单任务 / 单 Agent 多 session 并发 / 多 Agent 各自单任务 / 多 Agent 各自多 session 并发 - **3 种加压策略**:固定并发(fixed)、梯度加压(ramp)、最大并发探测(find_max,按 SLO 逐档判定) - **数据集压测**:直接在场景 YAML 中声明离线数据集路径 + `task_names` 过滤,任务按 session 轮转分配 - **prebuild 预构建**:`agent-loadtest prebuild` 一次性构建任务镜像并切换为 prebuilt 模式,后续 run 跳过每次 `docker compose build` - **Web 界面**:`agent-loadtest web` 启动 Web 控制台--表单化新建 run、运行列表/报告查看、实时日志、停止/删除运行、配置模板、数据集扫描与任务多选 - **失败可见**:每个失败 trial 的完整错误(异常类 + 消息)进入报告、CLI 输出和 Web UI,不再"失败原因未知" - **链路可控**:内置确定性 Mock LLM 服务(OpenAI 兼容)+ EchoLLMAgent 示例(带 lt_spans 埋点),多次压测结果可比 - **容器隔离**:每个 session 独立 Docker compose 项目,互不干扰 - **完整指标**:QPS、avg/P50/P95/P99/max 延迟、LLM/工具/存储/网络分层耗时、任务通过率、报错分类、容器与宿主机资源 - **可视化报告**:Markdown(内嵌 ASCII sparkline)+ matplotlib 资源时间序列图(CPU/内存,按工具颜色标注)+ SVG 图表(并发时序/延迟分布/阶梯图)+ 自包含 HTML Dashboard - **Prometheus 导出**:一键导出 50+ 指标为 Prometheus 文本格式,支持 node_exporter textfile collector - **离线可重算**:全部原始数据落盘(含 stages.json),报告可随时重新生成 ## 环境要求 - Python 3.12+ - [uv](https://docs.astral.sh/uv/) - Docker(本地可访问 `/var/run/docker.sock`) - Harbor 源码(本项目默认以 editable 方式依赖 `/home/liukai/harbor`,路径可在 `pyproject.toml` 的 `[tool.uv.sources]` 中调整) ## 安装 ```bash cd /home/liukai/agent-loadtest uv sync --dev ``` 验证安装: ```bash uv run agent-loadtest --help uv run pytest tests/ -q # 应全部通过 ``` ## 快速开始 ### 1. 跑一个最小压测(模式 a:单 Agent 单任务基线) ```bash uv run agent-loadtest run examples/mode_a_single_agent_single_task.yaml ``` 运行结束后终端会打印完成数、通过率和报告路径: ``` mode=single_agent_single_task sessions=1 concurrency=1 run dir: loadtest-runs/mode-a-baseline__2026-07-29__08-00-00 done: 1 completed, pass rate 1.0 report: loadtest-runs/mode-a-baseline__.../report.md ``` ### 2. 并发压测(模式 b:单 Agent 20 个 session) ```bash uv run agent-loadtest run examples/mode_b_single_agent_multi_session.yaml ``` ### 3. 探测最大并发(模式 d:多 Agent + find_max) ```bash uv run agent-loadtest run examples/mode_d_multi_agent_multi_session.yaml ``` find_max 会按 stages 逐档提高并发(如 4 → 8 → 16),每档结束后校验 SLO(P95 延迟、成功率),一旦不达标即停止——上一档就是最大可承载并发,各档数据记录在报告的 stages 表中。 ## 编写压测场景 场景是一个 YAML 文件,完整示例见 `examples/` 目录。核心结构: ```yaml name: my-loadtest # 场景名,用于命名输出目录 mode: single_agent_multi_session # 4 种模式之一,见下表 pairs: # (agent, task) 配对列表 - agent: name: claude-code # Harbor 内置 agent 名;或用 import_path 指向自定义 Agent model_name: anthropic/claude-sonnet-4-5 # 可选 env: # 注入 agent 的环境变量(如指向 Mock LLM) OPENAI_BASE_URL: http://host.docker.internal:8899/v1 task: /home/liukai/agent-loadtest/tasks/echo-bench # Harbor task 目录 sessions: 20 # 该 pair 总共跑多少个 session n_concurrent: 10 # 该 pair 的并发上限(缺省 = sessions) load: strategy: fixed # fixed | ramp | find_max # ramp / find_max 需要 stages: # stages: # - {concurrency: 4, hold_sec: 60} # ramp:按时间推进 # - {concurrency: 4, sessions: 8} # find_max:按 session 数分档 # slo: # find_max 的停止条件 # p95_latency_sec: 300 # success_rate: 0.9 sampling: resource_interval_sec: 2 # 资源采样间隔(秒) host_metrics: true # 是否采宿主机指标 docker_socket: /var/run/docker.sock retry: max_retries: 0 # 失败重试次数 output_dir: loadtest-runs # 输出根目录 success_reward_threshold: 1.0 # reward >= 阈值视为任务通过 qps_window_sec: 10 # QPS 滑动窗口宽度 ``` ### 模式与 pairs 的约束 | mode | pairs 要求 | 对应压测目标 | |---|---|---| | `single_agent_single_task` | 恰好 1 个 pair,sessions=1 | 单 Agent 单任务基线性能 | | `single_agent_multi_session` | 恰好 1 个 pair | 单 Agent 多 session 并发 | | `multi_agent_single_task` | ≥2 个 pair,每个 sessions=1 | 多 Agent 各自单任务并发 | | `multi_agent_multi_session` | ≥2 个 pair | 多 Agent 各自多 session 并发 | 约束不满足时加载即报错,不会进入执行阶段。 ### 使用离线数据集(dataset) 除单个 `task` 路径外,pair 可以直接声明一个数据集目录(Harbor 数据集布局:目录下每个子目录是一个 task): ```yaml pairs: - agent: name: claude-code model_name: deepseek-v4-flash dataset: path: /home/liukai/harbor-datasets-main/datasets/swebenchpro task_names: # 可选:按任务名(支持 glob)过滤 - "instance_nodebb__nodebb-*" preinstall_agent: claude-code # 可选:把 agent 预装进任务镜像 sessions: 2 # 任务在已解析列表中轮转分配 n_concurrent: 2 ``` `task_names` / `exclude_task_names` 支持 glob 模式;不填则包含全部任务。也可以用 `name`(registry/package 数据集)或 `repo`(git 数据集)代替 `path`,详见 `DatasetSpec`。 ## 预构建任务镜像(prebuild) 默认情况下每次 trial 都会执行 `docker compose build`(尤其 `preinstall_agent` 注入 Dockerfile 后强制重建),重型镜像会让每轮压测都浪费大量等待时间。`prebuild` 把任务一次性切换为 **prebuilt 模式**: ```bash uv run agent-loadtest prebuild [--tag NAME] [--agent claude-code] [--pull] ``` 例如: ```bash uv run agent-loadtest prebuild \ /home/liukai/harbor-datasets-main/datasets/bountybench-detect/fastapi-bounty-0-detect \ --agent claude-code ``` 它的工作方式: 1. (`--agent` 时)注入 preinstall Dockerfile,把 agent 打进镜像; 2. `docker build` 所有镜像并打 tag(`:main`,compose 多服务任务为 `:`); 3. 改写任务定义让后续 run 直接用镜像: - 单服务任务:`task.toml` 写入 `[environment].docker_image`,原 Dockerfile 备份为 `Dockerfile.build-backup`; - 多服务任务(自带 `docker-compose.yaml`):compose 中 `build:` 替换为 `image:` + `pull_policy: never`,原文件备份为 `docker-compose.build-backup.yaml`。 之后 `agent-loadtest run` 启动该任务时只有 `docker compose up`,秒级起容器。 > **注意**:prebuild 之后,scenario YAML 中不要再写 `preinstall_agent`(agent 已在镜像里,再注入会触发强制重建,抵消 prebuild 效果)。镜像内容变化时重新执行一次 prebuild 即可。 ### 加压策略 | strategy | 行为 | 适用场景 | |---|---|---| | `fixed` | 全程固定并发(= 各 pair 并发之和) | 稳态吞吐 / 延迟测量 | | `ramp` | 按 stages 时间表(`hold_sec`)逐档调整并发 | 观察加压过程中指标变化 | | `find_max` | 逐档提额,每档结束按 SLO 判定,失败即停 | 探测最大可承载并发 | ## 使用真实 Agent 压测(claude-code + 第三方 API) 前面的快速示例使用 `oracle`/`nop` 等确定性 Agent 验证链路。要对真实 Agent 压测,以 claude-code 为例: ### 1. 构建预装 claude-code 的 Docker 镜像 claude-code 要求 Node.js >= 22 且安装时需要下载 npm 包,建议在 task 的 Dockerfile 中预装(避免每次 trial 都 apt-get 超时): ```dockerfile # tasks/echo-bench/environment/Dockerfile FROM node:22-bookworm-slim ENV DEBIAN_FRONTEND=noninteractive RUN apt-get update && apt-get install -y --no-install-recommends \ curl bash procps ca-certificates git \ && rm -rf /var/lib/apt/lists/* # 预装 claude-code(docker build 阶段完成,trial 中 install 检查秒过) RUN npm install -g @anthropic-ai/claude-code # 跳过 claude-code 交互式登录(使用第三方 API 代理时必须) RUN echo '{"hasCompletedOnboarding":true}' > /root/.claude.json WORKDIR /app ``` 构建镜像: ```bash docker build -t echo-bench-claude tasks/echo-bench/environment/ ``` ### 2. 设置 API 环境变量 claude-code 通过环境变量读取 API 端点和密钥。**必须在宿主机 shell 中 export**(不是写在 YAML 的 `agent.env` 里),因为 Harbor 从 `os.environ` 读取这些值来触发模型别名逻辑: ```bash export ANTHROPIC_BASE_URL=https://api.your-proxy.com/anthropic export ANTHROPIC_API_KEY=sk-your-key ``` > **注意**:`ANTHROPIC_API_KEY` 比 `ANTHROPIC_AUTH_TOKEN` 优先级更高,claude-code 会优先使用它。 ### 3. 编写场景文件 ```yaml # examples/my_claude_task.yaml name: my-loadtest mode: single_agent_multi_session pairs: - agent: name: claude-code model_name: deepseek-v4-pro # 不带 anthropic/ 前缀! task: /home/liukai/agent-loadtest/tasks/echo-bench sessions: 2 n_concurrent: 10 load: strategy: fixed sampling: resource_interval_sec: 2 host_metrics: true docker_socket: /var/run/docker.sock retry: max_retries: 0 output_dir: loadtest-runs success_reward_threshold: 1.0 qps_window_sec: 10 ``` ### 4. 运行 ```bash uv run agent-loadtest run examples/my_claude_task.yaml ``` 输出示例: ``` mode=single_agent_multi_session sessions=2 concurrency=10 run dir: loadtest-runs/my-loadtest__2026-07-30__11-27-48 done: 2 completed, pass rate 1.0 report: loadtest-runs/my-loadtest__2026-07-30__11-27-48/report.md ``` 报告中会出现 `span:llm`(LLM API 调用耗时)和 `span:tool`(工具执行耗时)分层延迟。 ### 常见排错 | 症状 | 原因 | 解决 | |---|---|---| | `AgentSetupTimeoutError`(360s 超时) | 容器内 apt-get 安装 nodejs/npm 卡住 | 用 `node:22-bookworm-slim` 基础镜像,Dockerfile 预装依赖 | | `error_status: 401 authentication_failed` | API key 未传入或无效 | 确认在宿主机 `export ANTHROPIC_API_KEY=...` | | `error_status: 403 ... does not have permission to use the model: anthropic/deepseek-v4-pro` | model_name 带 `anthropic/` 前缀,代理不认 | YAML 中 `model_name` 去掉 `anthropic/` 前缀 | | `error_status: 403` 仍报权限错误 | 代理端未授权该模型或 token 过期 | 向 API 代理提供商确认模型名称和权限 | | Agent 卡住不动、不输出 | claude-code 首次运行等待交互式登录 | Dockerfile 中写入 `{"hasCompletedOnboarding":true}` 到 `/root/.claude.json` | ## 链路可控压测(Mock LLM) 要让压测结果可复现、隔离 LLM 服务端波动,可使用内置的确定性 Mock LLM: ```bash # 启动 OpenAI 兼容的 Mock 服务(默认端口 8899) uv run agent-loadtest mock-llm --port 8899 --delay-sec 0.5 # 使用脚本化响应(YAML/JSON 列表,按顺序循环返回) uv run agent-loadtest mock-llm --script responses.yaml ``` `--delay-sec` 注入固定"推理延迟",用于区分 Agent 框架自身开销与 LLM 耗时。 在场景中将被测 Agent 指向 Mock 服务(Agent 运行在容器内,需用宿主机可达地址): ```yaml pairs: - agent: name: my-agent env: OPENAI_BASE_URL: http://host.docker.internal:8899/v1 OPENAI_API_KEY: mock ``` ## 细粒度延迟分层(可选埋点) 默认即可获得两层延迟数据: 1. **Trial 分段**:环境启动 / Agent 安装 / Agent 执行 / 验证(来自 Harbor TrialResult) 2. **ATIF 步级**:每步耗时、工具调用次数、token 量(来自 Agent 轨迹) 若需要 **LLM / 工具 / 存储 / 网络** 四类精确分层,让自定义 Agent 在运行时把 span 写入 `AgentContext.metadata["lt_spans"]`: ```python context.metadata.setdefault("lt_spans", []).append({ "kind": "llm", # llm | tool | storage | network "name": "plan", "start": t0, # epoch 秒 "end": t1, "ok": True, "error_class": None, # 失败时填分类,如 "rate_limit" }) ``` 未埋点的 Agent 不受影响,只是报告中分层表为空。 ## Web 控制台 除 CLI 外,可以通过 Web 页面完成压测的全流程操作: ```bash uv run agent-loadtest web [--host 0.0.0.0] [--port 8080] [--output-dir loadtest-runs] ``` 浏览器打开 `http://localhost:8080`: - **新建 run**:表单化配置(模式、agent、数据集/任务、load 策略、采样参数),自动生成场景 YAML;也支持加载 `examples/` 示例 - **数据集扫描**:输入数据集根目录一键扫描,下拉模糊匹配选择子数据集;选中后自动列出任务,Available / Selected 双列表多选 - **配置模板**:保存当前表单配置为模板,下次新建 run 直接加载 - **运行监控**:运行中实时日志输出(增量拉取)、一键终止运行中的任务 - **结果查看**:运行列表、KPI 卡片、延迟分层表、失败详情(每个失败 trial 的完整错误)、资源图表 - **历史管理**:删除历史 run 目录 场景 YAML 也可以先在页面上生成,再用 CLI `run` 执行--两者完全等价。 ## 查看结果 每次运行生成一个独立目录: ``` loadtest-runs/{name}__{UTC 时间戳}/ ├── report.md # 人类可读的汇总报告(内嵌 ASCII sparkline + 失败详情)← 先看这个 ├── report.json # 完整结构化结果(可入库、diff) ├── dashboard.html # 自包含 HTML 仪表盘(浏览器直接打开) ├── events.jsonl # Trial 生命周期事件时间线 ├── stages.json # find_max 阶梯记录(离线重算用) ├── charts/ # 图表:SVG(并发时序/延迟分布/阶梯图)+ matplotlib PNG(资源时间序列) ├── resources/ # 容器级 + 宿主机级资源采样 └── trials/ # 每个 session 的 Harbor 标准输出(result.json、轨迹、日志) ``` `report.md` 包含:吞吐(稳定/峰值 QPS、实测最大并发)、并发时序 sparkline、延迟分层表(avg/P50/P95/P99/max)、延迟分布图、任务通过率与工具成功率、报错分类、**失败详情**(每个失败 trial 的错误类与完整消息)、资源峰值/均值,以及 find_max 的 SLO 阶梯表(含最大可持续并发结论)。 对已有 run 目录重算报告(如调整通过阈值): ```bash uv run agent-loadtest report loadtest-runs/my-loadtest__2026-07-29__08-00-00 \ --reward-threshold 0.8 --qps-window-sec 30 ``` 生成自包含 HTML 仪表盘(浏览器直接打开): ```bash uv run agent-loadtest dashboard loadtest-runs/my-loadtest__2026-07-29__08-00-00 # 输出: loadtest-runs/.../dashboard.html ``` 导出 Prometheus 指标(50+ metric,支持 textfile collector): ```bash uv run agent-loadtest prom-export loadtest-runs/my-loadtest__2026-07-29__08-00-00 \ --output /var/lib/node_exporter/textfile/agent_loadtest.prom ``` ## CLI 参考 | 命令 | 说明 | |---|---| | `agent-loadtest run [--run-dir DIR]` | 执行压测场景 | | `agent-loadtest prebuild [--tag NAME] [--agent claude-code] [--pull]` | 预构建任务镜像,后续 run 免构建 | | `agent-loadtest web [--host H] [--port P] [--output-dir DIR]` | 启动 Web 控制台(新建 run / 监控 / 报告 / 模板) | | `agent-loadtest report [--reward-threshold F] [--qps-window-sec F]` | 重算已有运行的报告(含图表) | | `agent-loadtest dashboard [--output FILE]` | 生成自包含 HTML 仪表盘 | | `agent-loadtest prom-export [--output FILE]` | 导出 Prometheus 文本格式指标 | | `agent-loadtest mock-llm [--host H] [--port P] [--script FILE] [--delay-sec F]` | 启动确定性 Mock LLM 服务 | ## 常见问题 **Q: `concurrency=2` 但只看到一个任务在跑?** `sessions` 是**任务总数**,`n_concurrent` 只是**并发上限**(最多同时跑几个),不会凭空创建任务。`sessions: 1` + `n_concurrent: 2` 意味着只有 1 个任务,2 个并发槽里始终只有 1 个在忙。想看到 2 个任务并行:数据集给 ≥2 个任务(或同一任务跑 2 次),并把 `sessions` 提到 2。 **Q: 报告里容器资源为空?** 确认压测环境为本地 Docker 且 `sampling.docker_socket` 路径正确;采样通过 unix socket 轮询 Docker Engine API,容器按 Harbor 的 compose project 命名规则(`{trial_name}__env`)定位。 **Q: 想先验证链路,不想跑真实 Agent?** 用 Harbor 内置的 `oracle`(直接执行任务参考解,必通过)或 `nop`(空操作)作为 agent,examples 中的示例即采用这两个 agent。 **Q: 并发上不去 / 排队严重?** 实际并发取三者最小值:pair 的 `n_concurrent` 之和、当前 stage 的 `concurrency`、宿主机 Docker 资源。报告 throughput 部分的"实测峰值并发"可以确认真实并发水平。 **Q: 任务从哪来?** 任何 Harbor 格式的 task 目录(含 `task.toml`、`instruction.md`、`environment/`、`tests/`)均可。本项目内置了零外网依赖的 `tasks/echo-bench`(验证纯 bash,适合链路压测与离线环境),也可复用 `harbor/examples/tasks/` 或各 benchmark adapter 生成的任务——注意部分 Harbor 示例任务(如 hello-world)的 verifier 需要联网安装 pytest,外网受限的机器会验证超时。 ## 开发 ```bash uv run pytest tests/ -q # 单元测试 uv run ruff check --fix . # lint uv run ruff format . # 格式化 ```