Files
llm-speed-tester/API.md
T

392 lines
16 KiB
Markdown
Raw 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.
# LLM 速度测试台 · API 文档
> 系统所有页面功能均通过 REST API 提供,前端(网页)只是这些 API 的一个可视化客户端。
> 任何页面可见/可操作的数据都可以通过下面的接口调用或访问。
- **服务地址:** `http://<IP>:16097`
- **数据格式:** 请求/响应均为 `application/json`(导出类接口除外)
- **鉴权:** 内部工具,当前无鉴权;如需对外暴露,建议在网关/Nginx 层加访问控制
- **测试启动为异步**`POST /api/tests` 返回后,任务在后台线程执行,用 `GET /api/tests/<id>` 或日志轮询接口跟踪进度
---
## 目录
1. [健康检查](#1-健康检查)
2. [提供商配置](#2-提供商配置-configs)
3. [连接测试](#3-连接测试)
4. [速度测试](#4-速度测试-tests)
5. [测试详情与日志](#5-测试详情与日志)
6. [折线图(data-chart-tool](#6-折线图data-chart-tool)
7. [导出(Excel / JSON](#7-导出excel--json)
8. [数据模型](#8-数据模型)
9. [curl 使用示例](#9-curl-使用示例)
---
## 1. 健康检查
### `GET /api/health`
返回服务状态与正在运行的测试。
**响应:**
```json
{ "ok": true, "port": 16097, "running_tests": [] }
```
---
## 2. 提供商配置(Configs
### `GET /api/configs`
列出所有已保存配置(不返回 API Key 明文,仅 `has_key` 标记)。
**响应:**
```json
[
{ "id": 2, "name": "epyc-test", "provider": "openai",
"base_url": "http://121.40.164.32:18003/v1",
"model": "unsloth/Qwen3.8-27B-Q4_K_M", "temperature": 0.7, "has_key": 1 }
]
```
### `POST /api/configs`
新增配置。请求体字段:`name`(必填), `provider`, `base_url`, `api_key`, `model`, `temperature`
**请求:**
```json
{ "name": "DeepSeek", "provider": "openai",
"base_url": "https://api.deepseek.com/v1",
"api_key": "sk-xxx", "model": "deepseek-chat", "temperature": 0.7 }
```
**响应:** `{ "ok": true, "id": 3 }`
### `GET /api/configs/<id>`
获取单个配置(**含 API Key 明文**,用于前端回填;注意保管)。
### `PUT /api/configs/<id>`
更新配置,**局部更新**(只改请求里出现的字段)。
**请求:** `{ "model": "deepseek-v4-flash" }` → 响应 `{ "ok": true, "id": 3 }`
### `DELETE /api/configs/<id>`
删除配置。响应 `{ "ok": true }`
---
## 3. 连接测试
### `POST /api/configs/test`
验证 API Key / Base URL / 模型名连通性。只要流式请求成功返回(HTTP 200 + 收到响应流)即视为连通;支持推理型模型(Qwen3/DeepSeek 思维链)。
**请求:** 同配置对象(`provider`, `base_url`, `api_key`, `model`, `temperature`
**成功响应:**
```json
{
"ok": true, "total_ms": 1308.2,
"note": "",
"metrics": { "prompt_tokens": 63, "output_tokens": 32, "output_chars": 110,
"ttft_ms": 715.9, "prefill_speed": 86.7, "decode_speed": 52.7, "total_ms": 1308.2 }
}
```
> `note` 非空表示连接正常但未返回正文(可能为只输出思维链的模型)。
**失败响应:** `{ "ok": false, "error": "HTTP 401: ..." }`
---
## 4. 速度测试(Tests
### `POST /api/tests`
启动一次速度测试(异步,立即返回测试 id)。
**请求体:**
```json
{
"config": {
"provider": "openai",
"name": "Qwen3 对比",
"base_url": "http://121.40.164.32:18003/v1",
"api_key": "sk-xxx",
"model": "unsloth/Qwen3.8-27B-Q4_K_M",
"temperature": 0.7
},
"gen": {
"name": "Qwen3 不同上下文长度速度对比",
"context_lengths": [512, 2048, 4096, 8192, 16384, 32768, 65536, 131072],
"max_tokens": 128,
"samples": 2,
"warmup": true,
"avoid_cache": true
}
}
```
**gen 字段说明:**
| 字段 | 类型 | 默认 | 说明 |
|------|------|------|------|
| `name` | string | `""` | 测试名称/主题(会存入测试记录并展示在历史与详情) |
| `context_lengths` | number[] | `[512,2048,4096,8192,16384,32768,65536,131072]` | 要测试的上下文长度列表,每个长度独立校准+预热+采样 |
| `max_tokens` | number | `128` | 解码输出 token 长度 |
| `samples` | number | `2` | 每个(长度×并发)组合的采样次数 |
| `concurrency_levels` | number[] | `[1]` | 并发数列表(默认单流)。>1 时每采样同时发起 N 个并行流,聚合为整批吞吐指标;多档自动并排对比 |
| `warmup` | bool | `true` | 测试前空转预热(不计速度,按并发数预热) |
| `avoid_cache` | bool | `true` | 随机前缀避免缓存命中(每个并发流独立前缀) |
**响应:** `{ "ok": true, "id": 9 }`
### `GET /api/tests`
测试历史列表,支持**分页 + 搜索 + 筛选 + 排序**:
| 参数 | 说明 |
|------|------|
| `page` | 页码,默认 1 |
| `page_size` | 每页条数,默认 20,最大 100 |
| `q` | 关键词搜索(匹配 id/名称/模型/提供商/状态/时间) |
| `status` | 状态筛选,逗号多选:`done,error,canceled,running` |
| `provider` | 提供商筛选:`openai` / `anthropic` / `google` |
| `sort` | 排序字段:`id`/`created_at`/`name`/`provider`/`model`/`status`/`samples`/`ttft`/`prefill`/`decode`/`total_ms`,默认 `id` |
| `order` | 排序方向:`asc` / `desc`,默认 `desc`(数值字段空值恒排最后) |
**响应:**
```json
{ "ok": true, "items": [ { "id": 9, "created_at": "2026-08-23 18:52:00", "status": "done",
"provider": "openai", "model": "unsloth/Qwen3.8-27B-Q4_K_M",
"name": "Qwen3 不同上下文长度速度对比",
"error": "",
"summary": { "samples_ok": 2, "samples_total": 2, "avg_ttft_ms": 1808.7, ... } } ],
"total": 31, "page": 1, "page_size": 20, "total_pages": 2 }
```
### `DELETE /api/tests`
一键清空全部测试历史(测试记录 + 采样指标 + 日志),不可恢复。响应 `{ "ok": true }`
### `GET /api/tests/export`
一键导出全部历史记录为 JSON 文件下载(`Content-Disposition: attachment`,文件名带时间戳)。每条含基本字段 + 配置(**API Key 打码为 `******`**)+ 生成参数 + 汇总指标,不含逐条采样与完整日志。
### `POST /api/tests/<id>/cancel`
停止正在运行的测试。响应 `{ "ok": true, "msg": "正在停止..." }`
### `DELETE /api/tests/<id>`
删除测试及其全部采样与日志。响应 `{ "ok": true }`
---
## 5. 测试详情与日志
### `GET /api/tests/<id>`
完整测试详情:基本信息 + 配置(API Key 已打码)+ 生成参数 + 汇总 + 每次采样 + 完整日志。
**响应结构:**
```json
{
"id": 9, "created_at": "...", "status": "done",
"provider": "openai", "model": "...", "name": "...", "error": "",
"config": { "base_url": "...", "api_key": "sk-x****", ... },
"gen": { "name": "...", "context_lengths": [512, 2048], "max_tokens": 128, "samples": 1, "concurrency_levels": [1, 2, 4], "warmup": true, "avoid_cache": true },
"summary": {
"samples_total": 6, "samples_ok": 6,
"concurrency_levels": [1, 2, 4],
"calibration_chars_per_token": 1.82,
"avg_ttft_ms": 1808.7, "min_ttft_ms": 1122.8, "max_ttft_ms": 2494.6,
"avg_prefill_speed": 694.2, "min_prefill_speed": 515.7, "max_prefill_speed": 872.7,
"avg_decode_speed": 54.7, "min_decode_speed": 54.4, "max_decode_speed": 55.0,
"avg_prompt_tokens": 1378.0, "avg_output_tokens": 128.0,
"avg_total_ms": 4148.4, "min_total_ms": 3449.9, "max_total_ms": 4846.9,
"by_length": {
"512": { "samples_total": 3, "samples_ok": 3, "avg_ttft_ms": 1122.8, "avg_prefill_speed": 515.7, "avg_decode_speed": 55.0, "avg_prompt_tokens": 579, "avg_output_tokens": 128, "avg_total_ms": 3449.9 },
"2048": { "samples_total": 3, "samples_ok": 3, "avg_ttft_ms": 2494.6, "avg_prefill_speed": 872.7, "avg_decode_speed": 54.4, "avg_prompt_tokens": 2177, "avg_output_tokens": 128, "avg_total_ms": 4846.9 }
},
"by_concurrency": {
"1": { "samples_total": 2, "samples_ok": 2, "avg_ttft_ms": 1122.8, "avg_prefill_speed": 515.7, "avg_decode_speed": 55.0, "avg_stream_decode": 55.0, "avg_prompt_tokens": 579, "avg_output_tokens": 128, "avg_total_ms": 3449.9 },
"2": { "samples_total": 2, "samples_ok": 2, "avg_ttft_ms": 2494.6, "avg_prefill_speed": 872.7, "avg_decode_speed": 108.0, "avg_stream_decode": 54.0, "avg_prompt_tokens": 1158, "avg_output_tokens": 256, "avg_total_ms": 4846.9 }
},
"by_length_concurrency": { "512": { "1": {...}, "2": {...} }, "2048": {...} }
},
"runs": [
{ "run_index": 1, "context_length": 512,
"metrics": { "prompt_tokens": 579, "output_tokens": 128, "cached_tokens": 0,
"ttft_ms": 1122.8, "prefill_speed": 515.7, "decode_speed": 55.0, "total_ms": 3449.9 },
"error": "" }
],
"logs": [ { "id": 1, "level": "INFO", "msg": "═══ 开始速度测试 ═══", "rel": 0.0, "ts": "..." } ]
}
```
### `GET /api/tests/<id>/logs?after=<id>`
增量日志(前端轮询用)。`after` 为上次取到的最大日志 id,返回其后新增日志 + 最新状态/汇总/最后采样。
**响应:**
```json
{
"status": "running", "error": "",
"summary": {},
"last_run": { "...": "..." },
"logs": [ { "id": 68, "level": "METRIC", "msg": "...", "rel": 3.21 } ],
"after": 73
}
```
---
## 6. 折线图(data-chart-tool
> 调用 [data-chart-tool](http://192.168.2.8:12007/hz4th_coder/data-chart-tool.git) 的 `/api/chart` 接口生成**双Y轴折线图**:左轴=预填充速度(虚线)、右轴=解码速度(实线)、X 轴=上下文长度。本系统在服务端代理转发,前端只需请求本服务的两个接口。
### `GET /api/tests/<id>/chart`
生成折线图 PNG 图片(内部调用 data-chart-tool `/api/chart`,地址可在 `config.py``CHART_API_BASE` 修改)。
**成功响应:** `Content-Type: image/png`(可直接用于 `<img>` / 下载)
**失败响应:**
```json
{ "ok": false, "error": "无成功采样数据,无法画图" } // 400
{ "ok": false, "error": "测试不存在" } // 404
{ "ok": false, "error": "图表服务不可用: ..." } // 502
```
### `GET /api/tests/<id>/chart-data`
返回画图所用的 CSV 数据与图表请求配置,方便手动快速复制。
**响应:**
```json
{
"ok": true,
"csv": "上下文长度, 预填充速度(tok/s), 解码速度(tok/s)\n4096, 126.90, 22.80\n8192, 132.20, 22.40",
"rows": [[4096, 126.9, 22.8], [8192, 132.2, 22.4]],
"payload": {
"data": "...", "chartType": "line", "title": "...",
"dualYAxis": true,
"leftAxisName": "预填充速度(tok/s)", "rightAxisName": "解码速度(tok/s)",
"seriesTypes": ["line", "line"], "seriesAxis": [0, 1],
"seriesStyles": ["dashed", "solid"], "width": 1000, "height": 560, "pixelRatio": 2
}
}
```
> `csv` 即画图数据(第一列=上下文长度,第二列=预填充速度,第三列=解码速度),前端「复制画图数据」按钮复制的就是它。
### `GET /api/tests/<id>/concurrency-chart`
并发对比折线图 PNG(X 轴=**并发数**,左轴=预填充速度虚线、右轴=解码速度实线),用于直观展示吞吐随并发的变化。仅当本次测试包含**多个并发档**时才有数据。
**失败响应:** `{ "ok": false, "error": "无并发分组采样数据(本次测试可能只测了单流),无法画图" }`400
### `GET /api/tests/<id>/concurrency-chart-data`
并发对比画图数据(CSV + 图表请求配置)。
```json
{
"ok": true,
"csv": "并发数, 预填充速度(tok/s), 解码速度(tok/s)\n1, 767.30, 43.40\n2, 1388.20, 70.90\n4, 2085.00, 78.70",
"rows": [[1, 767.3, 43.4], [2, 1388.2, 70.9], [4, 2085.0, 78.7]],
"payload": { "data": "...", "chartType": "line", "title": "...", "dualYAxis": true, ... }
}
```
### `POST /api/chart`
通用图表代理:把任意 data-chart-tool `/api/chart` 请求体转发过去并返回 PNG(多测试对比面板用)。请求体即 data-chart-tool 的参数(`data`/`chartType`/`seriesTypes`/`seriesStyles`/`dualYAxis`…)。
**成功响应:** `Content-Type: image/png`
### `POST /api/compare`
把多个测试结果放在一起对比。
**请求:** `{ "ids": [9, 10, 11] }`(最多 20 个)
**响应:**
```json
{
"ok": true,
"rows": [ { "id": 9, "label": "#9 xxx", "created_at": "...", "name": "...", "model": "...",
"concurrency_levels": [1, 2, 4], "by_concurrency": {...},
"samples_ok": 6, "samples_total": 6,
"avg_ttft_ms": 1122.8, "avg_prefill_speed": 515.7, "avg_decode_speed": 55.0,
"avg_stream_decode": 55.0, "avg_output_tokens": 128, "avg_total_ms": 3449.9 } ],
"bar_csv": "测试, 预填充速度(tok/s), 解码速度(tok/s)\n#9 xxx, 515.70, 55.00",
"bar_payload": { "data": "...", "chartType": "bar", "seriesTypes": ["bar", "bar"], "seriesStyles": ["hollow", "solid"], ... },
"line": { // 仅当所选测试存在 >=2 个共同并发档时返回,否则为 null
"csv": "并发数, #9 xxx, #10 yyy\n1, 55.00, 52.10\n2, 108.00, 99.30\n4, 190.20, 175.60",
"payload": { "data": "...", "chartType": "line", ... },
"levels": [1, 2, 4]
}
}
```
---
## 7. 导出(Excel / JSON
### `GET /api/tests/<id>/export.xlsx`
导出 Excel 报告(**3 个 Sheet**:汇总 / 采样明细 / 日志),`Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet`
- **汇总**:测试信息(含并发数列表)+ 整体统计指标(平均/最大/最小)+ 按上下文长度分组 + **按并发数分组**(整批吞吐,含单流均解码)
- **采样明细**:每次采样的上下文长度、**并发**、流成功/总数与全部指标
- **日志**:完整测试日志
### `GET /api/tests/<id>/export.json`
导出完整测试数据为 JSON(与 `GET /api/tests/<id>` 一致,API Key 打码),`Content-Type: application/json`
---
## 8. 数据模型
| 表 | 说明 | 关键字段 |
|----|------|----------|
| `configs` | 保存的接口配置 | id, name, provider, base_url, api_key, model, temperature |
| `tests` | 测试记录 | id, status(running/done/error/canceled), provider, model, **name**, config_json, gen_cfg_json, summary_json, error |
| `test_runs` | 每次采样 | id, test_id, run_index, **context_length**, metrics_json, errormetrics 含 `concurrency`/`streams_total`/`streams_ok`/`avg_stream_decode`/`streams`(每流明细) |
| `logs` | 测试日志 | id, test_id, level, msg, rel |
**summary 整体指标字段:**
`avg_/min_/max_` 前缀 × `ttft_ms` / `prefill_speed` / `decode_speed` / `total_ms`,以及 `avg_prompt_tokens` / `avg_output_tokens` / `avg_cached_tokens` / `best_ttft_ms`= min_ttft_ms)。
**分组字段:** `by_length`(按上下文长度)、`by_concurrency`(按并发数,含 `avg_stream_decode` 单流均解码)、`by_length_concurrency`(长度×并发全网格)、`concurrency_levels`(本次测试的并发档列表)。
---
## 9. curl 使用示例
```bash
BASE=http://<IP>:16097
# 健康检查
curl $BASE/api/health
# 新增配置
curl -X POST $BASE/api/configs -H 'Content-Type: application/json' \
-d '{"name":"Qwen3","provider":"openai","base_url":"http://121.40.164.32:18003/v1","api_key":"sk-xxx","model":"unsloth/Qwen3.8-27B-Q4_K_M","temperature":0.7}'
# 测试连接
curl -X POST $BASE/api/configs/test -H 'Content-Type: application/json' \
-d '{"provider":"openai","base_url":"http://121.40.164.32:18003/v1","api_key":"sk-xxx","model":"unsloth/Qwen3.8-27B-Q4_K_M"}'
# 启动速度测试(异步,含多并发档)
curl -X POST $BASE/api/tests -H 'Content-Type: application/json' -d '{
"config": {"provider":"openai","base_url":"http://121.40.164.32:18003/v1","api_key":"sk-xxx","model":"unsloth/Qwen3.8-27B-Q4_K_M"},
"gen": {"name":"并发对比","context_lengths":[2048],"max_tokens":128,"samples":2,"concurrency_levels":[1,2,4],"warmup":true,"avoid_cache":true}
}'
# 查询测试列表 / 详情
curl "$BASE/api/tests?page=1&page_size=20"
curl "$BASE/api/tests?q=Qwen&status=done,error&sort=decode&order=desc&page=1&page_size=10"
# 一键清空全部历史(不可恢复)
curl -X DELETE $BASE/api/tests
# 一键导出全部历史(JSON 文件下载)
curl -OJ $BASE/api/tests/export
curl $BASE/api/tests/9
# 画图数据(CSV
curl $BASE/api/tests/9/chart-data
# 折线图 PNG(预填充左轴虚线 / 解码右轴实线,X=上下文长度)
curl -o chart.png $BASE/api/tests/9/chart
# 并发对比折线图(X=并发数)
curl -o cc.png $BASE/api/tests/9/concurrency-chart
# 多测试对比
curl -X POST $BASE/api/compare -H 'Content-Type: application/json' -d '{"ids":[9,10,11]}'
# 导出
curl -OJ $BASE/api/tests/9/export.xlsx
curl $BASE/api/tests/9/export.json
```