QSTAR 对外暴露 OpenAI 协议兼容端点,任何遵守 OpenAI 协议的客户端都可以零改造对接;只需换 baseURL 与 api_key。
在线模型列表 /v1/models
带令牌可直接查看当前启用模型(返回 object=list)。离线时请在节点内 curl 复测。
用量接口 /api/usage
用量页数据源:总请求数、按模型分布、每日趋势、周期额度。
密钥管理页
自助创建 / 重命名 / 启用禁用 / 撤销 API Key。
三分钟接入
bash
# 1) 取令牌
TOKEN=$(curl -s https://qstar.center/api/user/login \
-H 'Content-Type: application/json' \
-d '{"username":"you","password":"***"}' \
| python3 -c 'import sys,json;print(json.load(sys.stdin)["data"]["token"])')
# 2) 看模型
curl -s https://qstar.center/v1/models -H "Authorization: Bearer $TOKEN"
# 3) 发一句
curl -s https://qstar.center/v1/chat/completions \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"model":"gpt-4o-mini","messages":[{"role":"user","content":"只回复 OK 两个字"}]}'鉴权
- 令牌来源:POST /api/user/login,返回 { code: 0, data: { token, user_id, username } }。
- 失败时走自有信封(HTTP 200 + code != 0),不是 401 —— 客户端必须按 code 判错。
- 携带方式两种:Authorization: Bearer <token> 或 Authorization: <token>(裸 token,小写 bearer 亦可)。
- 校验链路:JWT(HS256) 验签 → 过期时间 → Redis 会话键 users/<uid> 是否存在;登出即失效。
- 防暴破:用户登录 10 次 / 15 分钟,后台登录 8 次 / 10 分钟,超限临时拒绝,请加退避重试。
端点
| 端点 | 方法 | 说明 |
|---|---|---|
| /v1/models | GET | 当前启用模型列表,id 即调用时的 model 值 |
| /v1/chat/completions | POST | 对话补全,支持 SSE 流式与非流式 |
请求字段
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| model | string | 是 | 取自 /v1/models 的 id,不在列表内 → 404 |
| messages | array | 是 | [{ role: system|user|assistant, content }],空数组 → 400 |
| stream | bool | 否 | true 走 SSE,默认 false |
| temperature | number | 否 | 透传上游 |
| max_tokens | int | 否 | 透传上游 |
非流式响应
json
{
"id": "chatcmpl-xxxxxxxxxxxx",
"object": "chat.completion",
"created": 1760000000,
"model": "gpt-4o-mini",
"choices": [
{ "index": 0, "message": { "role": "assistant", "content": "OK" }, "finish_reason": "stop" }
],
"usage": { "prompt_tokens": 0, "completion_tokens": 0, "total_tokens": 0 }
}usage 恒为 0(上游未回传 token 计数),不要用它做计费 / 限额判断。
SSE 流式
Content-Type 为 text/event-stream,响应头带 X-Accel-Buffering: no(禁止 nginx 缓冲)。
text
data: {"id":"chatcmpl-x","object":"chat.completion.chunk","created":1760000000,"model":"gpt-4o-mini","choices":[{"index":0,"delta":{"role":"assistant"}}]}
data: {"id":"chatcmpl-x","object":"chat.completion.chunk","created":1760000000,"model":"gpt-4o-mini","choices":[{"index":0,"delta":{"content":"O"}}]}
data: {"id":"chatcmpl-x","object":"chat.completion.chunk","created":1760000000,"model":"gpt-4o-mini","choices":[{"index":0,"finish_reason":"stop"}]}
data: [DONE]流式上游中途失败时,最后一个分片会追加提示文本(生成失败:<原因>),随后照常发 finish_reason 与 [DONE],并退还已预留算力 —— 客户端无需特判即可优雅收尾。
代码示例
python
from openai import OpenAI
client = OpenAI(base_url="https://qstar.center/v1", api_key=TOKEN)
r = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": "只回复 OK 两个字"}],
)
print(r.choices[0].message.content)
# 流式
for chunk in client.chat.completions.create(
model="gpt-4o-mini", messages=[{"role": "user", "content": "讲个笑话"}], stream=True
):
print(chunk.choices[0].delta.content or "", end="", flush=True)js
const res = await fetch("https://qstar.center/v1/chat/completions", {
method: "POST",
headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` },
body: JSON.stringify({ model: "gpt-4o-mini", messages, stream: true }),
signal,
});
const reader = res.body.getReader();
const dec = new TextDecoder();
let buf = "";
while (true) {
const { done, value } = await reader.read();
if (done) break;
buf += dec.decode(value, { stream: true });
const lines = buf.split("\n");
buf = lines.pop() ?? "";
for (const l of lines) {
const d = l.trim().startsWith("data:") ? l.trim().slice(5).trim() : null;
if (!d) continue;
if (d === "[DONE]") return;
const delta = JSON.parse(d)?.choices?.[0]?.delta?.content;
if (delta) render(delta);
}
}错误码
统一 OpenAI 信封:{ "error": { "message": "…", "type": "…", "code": "…" } }
| HTTP | type | 触发场景 | 客户端建议 |
|---|---|---|---|
| 400 | invalid_request_error | body 解析失败 / messages 为空 | 修请求体,不重试 |
| 401 | invalid_request_error | 缺令牌 / 签名无效 / 已过期 / 会话已登出 | 重新登录取 token |
| 402 | insufficient_quota | 算力余额不足 | 提示充值,不重试 |
| 404 | model_not_found | model 不在 /v1/models 列表内 | 先拉模型列表 |
| 502 | server_error | 上游模型服务异常(已退还算力) | 指数退避重试 |
与 OpenAI 官方协议的差异
| 能力 | 状态 |
|---|---|
| chat/completions 非流式 / 流式 | ✅ 支持 |
| models.list | ✅ 支持 |
| role: system / user / assistant | ✅ 支持 |
| temperature / max_tokens | ✅ 透传 |
| n / top_p / presence_penalty / frequency_penalty | ❌ 忽略 |
| stop / seed / logprobs / response_format / stream_options | ❌ 忽略(不报错) |
| tools / function_call | ❌ 未支持 |
| 视觉输入(image content part) | ❌ 未支持 |
| usage 精确计数 | ⚠️ 恒为 0 |
| /v1/embeddings、/v1/images、/v1/audio 等 | ❌ 未提供 |
缓存与安全约定
所有 /v1/* 响应强制带:Cache-Control: no-store, no-cache, must-revalidate, private, max-age=0;Pragma: no-cache;Expires: 0;Vary: Authorization。
响应随 Authorization 变化。任何中间层若按「仅 URL」缓存,会把已登录用户的响应回给未登录请求,造成越权读取。接入方不要在自有网关对 /v1/* 做响应缓存;若必须缓存,缓存键须包含 Authorization。
外网回归必测三步:① 未登录 GET /v1/models → 401;② 带令牌 → 200;③ 再次未登录 → 401(若为 200 说明缓存投毒复发)。
故障排查速查
| 现象 | 原因 / 排查 |
|---|---|
| 未登录却返回 200 | 缓存投毒;加随机 query 串复测,若结果变回正确即按键缓存命中 |
| 流式一次性返回 | nginx proxy_buffering 未关,或 curl 少了 -N |
| 401 但令牌没过期 | 会话键 users/<uid> 已删(登出 / 被踢),重新登录 |
| 404 但模型名看着对 | 用 /v1/models 的 id,不是展示名 name |
| 部署后仍是旧行为 | 边缘未刷新,需回源刷新(EO purge) |
| 本地 curl 403/520/000 | 沙箱 / 代理假故障;节点内改用 curl -sLk --noproxy '*' |
| 502 | 上游异常,已退还算力,退避重试 |