File size: 6,933 Bytes
69f686e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
# 开发指南

## 环境要求

- Rust **1.95.0+**(见 `rust-toolchain.toml`- Bun **1.3+**(Web 面板构建与开发)
- `cmake``g++``libclang-dev`(编译 `wreq` 依赖的 BoringSSL)
- `just` 命令运行器(用于 `just serve` / `just check` 等快捷命令)

## 首次启动

```bash
# 1. 复制配置
cp config.example.toml config.toml

# 2. 构建 Web 前端(编译时嵌入二进制,每次前端变更需要重构建)
cd web && bun install && bun run build && cd ..

# 3. 运行开发服务器
just serve
```

服务器启动后访问 `http://localhost:22217` 自动跳转到管理面板。

> **前端热更新开发**:同时运行 `cd web && bun run dev`(Vite HMR 模式)
> 和 `just serve`,后端优先使用文件系统 `web/dist/` 目录中的静态文件。
> 无需每次前端改动都重构建二进制。

## Release 构建

```bash
# 1. 构建 Web 前端
cd web && bun install && bun run build && cd ..

# 2. 构建 Release 二进制
cargo build --release

# 3. 运行(也可直接运行二进制,无需 web/dist/ 目录)
./target/release/ds-free-api
```

Release 二进制通过 `rust_embed` 编译时嵌入前端资源,`web/dist/` 目录不存在时
自动使用嵌入资源。发布版无需额外文件。

## CI 自动构建

GitHub Actions(`.github/workflows/release.yml`)在 tag push 时自动执行:

```
build-frontend (bun install --frozen-lockfile + bun run build)
  ├── build-linux-gnu (cargo build)     │
  ├── build-linux-musl (musl-cross)     │── release (tar.gz + zip)
  ├── build-macos (cargo build)  │
  └── build-windows (cargo build)│
  └── docker (ghcr.io image)
```

`build-frontend` 产出 `web-dist` artifact,各编译 job 下载后再执行 `cargo build` /
`cross build`,保证 `rust_embed` 嵌入真实前端文件。

Docker 镜像自动推送到 `ghcr.io/niyueee/ds-free-api:latest`## Docker 部署(生产)

从 ghcr.io 拉取(推荐):

```bash
# 确认已创建 docker/config/ 目录(自动创建或手动 mkdir)
docker compose -f docker/docker-compose.yaml up -d
```

容器首次启动时自动创建最小配置,无需提前准备 `config.toml`。
配置和数据通过 bind mount 持久化到宿主机的 `docker/config/``docker/data/`。

从源码构建本地 Docker 镜像:

```bash
# 1. 构建前端 + 交叉编译二进制
cd web && bun install && bun run build && cd ..
cargo zigbuild --release --target x86_64-unknown-linux-gnu

# 2. 构建 Docker 镜像
docker build -f docker/Dockerfile -t ds-free-api .

# 3. 导出并传输到服务器
docker save ds-free-api | gzip > ds-free-api.tar.gz
scp ds-free-api.tar.gz user@server:/tmp/

# 4. 服务器加载并启动
ssh user@server
docker load < /tmp/ds-free-api.tar.gz
docker compose -f docker/docker-compose.yaml up -d
```

> 服务器原生 x86 环境可直接在服务器上执行上述构建,速度更快。
> Docker 镜像仅包含预编译二进制 + 嵌入的前端资源,无需在容器内编译。

## 命令参考

```bash
# 一键检查(check + clippy + fmt + audit + unused deps)
just check

# 运行测试
cargo test --lib

# 运行 HTTP 服务
just serve

# 统一协议调试 CLI(内置对话/比较/并发等模式)
just adapter-cli

# 使用 e2e 专属配置启动服务
just e2e-serve
```

## e2e 测试

`py-e2e-tests/` 是基于 JSON 场景驱动的端到端测试框架,无需 pytest 依赖。分为三层:

| 层级       | 命令              | 覆盖范围                                              |
| ---------- | ----------------- | ----------------------------------------------------- |
| **Basic**  | `just e2e-basic`  | 基础功能场景(双端点 OpenAI + Anthropic),安全并发数 |
| **Repair** | `just e2e-repair` | 工具调用异常格式修复专项(OpenAI 单端点),安全并发数 |
| **Stress** | `just e2e-stress` | 全部场景 × 3 次迭代,安全并发数 + 1 并发              |

先启动服务端:

```bash
just e2e-serve
```

再在另一个终端运行 e2e 测试:

```bash
# 基础场景测试
just e2e-basic

# 工具修复测试
just e2e-repair
```

场景文件在 `scenarios/` 中按类型独立存放:

```
py-e2e-tests/
├── scenarios/
│   ├── basic/
│   │   ├── openai/         # 7 个基础场景(对话、推理、流式、工具调用、文件上传、图片上传、HTTP链接)
│   │   └── anthropic/      # 7 个基础场景(对话、推理、流式、工具调用、文档上传、图片上传、HTTP链接)
│   └── repair/             # 10 个工具损坏格式场景
├── runner.py               # 单次运行入口
├── stress_runner.py        # 多迭代压测入口
└── config.toml             # e2e 专用服务端配置
```

每个场景为独立 JSON 文件,包含请求参数和校验规则:

```json
{
  "name": "场景名称",
  "endpoint": "openai|anthropic",
  "category": "basic|repair",
  "models": ["deepseek-default", "deepseek-expert", "deepseek-vision"],
  "messages": [{"role": "user", "content": "..."}],
  "tools": [...],
  "tool_choice": "auto",
  "request": {"stream": false},
  "checks": {
    "has_tool_calls": true,
    "tool_names": ["get_weather"],
    "finish_reason": "tool_calls",
    "no_error": true
  }
}
```

### e2e CLI 参数

**`just e2e-basic` 和 `just e2e-repair`(单次运行):**

| 参数 | 作用 |
|------|------|
| `scenario_dir` | 场景目录,如 `scenarios/basic``scenarios/repair` |
| `--endpoint` | 端点过滤:`openai` / `anthropic` |
| `--model` | 模型过滤:`deepseek-default` / `deepseek-expert` |
| `--filter` | 场景名称关键字过滤(多个用空格分隔,如 `--filter 文件 图片`)|
| `--parallel` | 并行数,默认 `账号数 ÷ 2` |
| `--show-output` | 显示模型回复摘要、工具调用、结束原因 |
| `--report` | 输出 JSON 报告路径 |

**`just e2e-stress`(压测):**

| 参数 | 作用 |
|------|------|
| `--iterations` | 每场景迭代次数,默认 3 |
| `--models` | 模型列表过滤 |
| `--filter` | 场景名称关键字过滤(多个用空格分隔)|
| `--parallel` | 并行数,默认 `账号数 ÷ 2 + 1` |
| `--show-output` | 显示模型输出 |
| `--report` | 输出 JSON 报告路径 |

使用示例:

```bash
# 快速验证新加的文件上传场景
just e2e-basic --filter 文件 图片 --show-output

# 仅查看 OpenAI 端点的 expert 模型
just e2e-basic --endpoint openai --model deepseek-expert

# 串行调试
just e2e-basic --endpoint openai --parallel 1 --show-output

# 压测:工具调用修复场景 × 5 次迭代
just e2e-stress --filter 修复 --iterations 5

# 输出 JSON 报告
just e2e-basic --report result.json
```

## 更多文档

- [代码规范](code-style.md)
- [日志规范](logging-spec.md)
- [Prompt 注入策略](deepseek-prompt-injection.md)