# DJ-temp **Repository Path**: zephyr123_3/dj-temp ## Basic Information - **Project Name**: DJ-temp - **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-07-01 - **Last Updated**: 2026-07-02 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # vLLM Backend for Lab 在 vLLM 上包了一层 FastAPI,对 UE 提供 **会话化多轮对话 + 轮询式流式输出**。 - **会话管理**:服务器发 `session_id`,UE 后续每次发言只带 id,历史由后端维护。 - **轮询式流式输出**:vLLM 的 SSE 块一到就入队,UE 通过 **轮询** `/poll` 接口把攒下的增量取走。节奏(0.1s / 0.05s …)完全由 UE 自己定。 - **为什么不是 SSE**:UE 端没有消费流式事件(SSE)的机制,所以这里改成"发起 + 轮询"两步式 HTTP,UE 只需会发普通 GET / POST。 服务默认监听 `0.0.0.0:9000`。UE 端可以通过 LAN Discovery 自动拿到服务器地址,也可以人工配置。 --- ## 轮询模型(UE 怎么用) 一次对话分两步:**① POST 发起生成(立即返回)→ ② GET 轮询取数(取到 `done` 为止)**。 ``` ① 发起(不再 hold 住连接,很快返回) POST /chat/sessions/{sid}/messages {"content": "..."} → 200 {"status": "streaming"} # 上游校验通过、后台已开始生成 → 4xx/5xx # 上游错误当场原样透传(见下方错误表) ② 轮询(UE 自己定频率) GET /chat/sessions/{sid}/poll → {"text": "你好,我", "done": false} # 自上次 poll 以来新增的文字(已拼成一个串) → {"text": "是助手", "done": false} → {"text": "", "done": true} # done=true:生成结束,UE 停止轮询 ``` - 每次 `poll` 把队列里**当前攒着的所有增量**拼成一个 `text` 串返回并清空;没新数据就返回 `{"text":"","done":false}`。 - 最后一段文字**可能**和 `done:true` 同一次返回,UE 先把 `text` 拼上去、再判断 `done`。 - 生成中途上游断开:`poll` 会返回 `{"text":"<残答>", "done":true, "error":"..."}`,UE 据此收尾。 UE 端伪代码: ```pseudo POST /chat/sessions/{sid}/messages {content} // 200 -> 开始 buffer = "" loop: r = GET /chat/sessions/{sid}/poll // 每隔你想要的间隔取一次 buffer += r.text // 直接拼到显示字符串后面 on_text(r.text) // 例如:逐字打字机效果 if r.done: break sleep(0.05) // 间隔随你,0.1 / 0.05 都行 ``` --- ## 快速上手(curl 端到端) ```bash HOST=http://:9000 # 1) 开 session(system 可选) SID=$(curl -s -X POST $HOST/chat/sessions \ -H 'Content-Type: application/json' \ -d '{"system":"你是一个简洁的中文助手。"}' \ | python -c 'import sys,json;print(json.load(sys.stdin)["session_id"])') echo "session: $SID" # 2) 发一条消息(立即返回 {"status":"streaming"}) curl -s -X POST $HOST/chat/sessions/$SID/messages \ -H 'Content-Type: application/json' \ -d '{"content":"你好,介绍一下你自己"}' # 3) 轮询取回复,直到 done=true(这里用 0.1s 间隔举例) while :; do R=$(curl -s $HOST/chat/sessions/$SID/poll) echo "$R" echo "$R" | grep -q '"done": *true' && break sleep 0.1 done # 4) 第二条(自动带上历史),再轮询一次即可 curl -s -X POST $HOST/chat/sessions/$SID/messages \ -H 'Content-Type: application/json' \ -d '{"content":"刚刚我让你做什么了?"}' # 5) 关闭 session curl -X DELETE $HOST/chat/sessions/$SID ``` --- ## 接口 | Method | Path | 说明 | |---|---|---| | `POST` | `/chat/sessions` | 开启会话,返回 `session_id` | | `POST` | `/chat/sessions/{sid}/messages` | 发一条用户消息,**发起**生成,立即返回 | | `GET` | `/chat/sessions/{sid}/poll` | 轮询取增量,返回 `{"text","done"[,"error"]}` | | `GET` | `/chat/sessions/{sid}` | 调试用,看当前会话的全部历史 | | `DELETE` | `/chat/sessions/{sid}` | 关闭并销毁会话(会取消在跑的生成) | | `GET` | `/health` | 健康检查,仅反映本服务自身 | ### `POST /chat/sessions` **请求体**(可选,整体可省) ```json { "system": "你是一个简洁的中文助手。" } ``` **响应** `200` ```json { "session_id": "a1b2c3d4e5f6..." } ``` `session_id` 是服务器生成的 32 位 hex(uuid4)。UE 应一直保留它,直到主动 `DELETE` 或服务进程重启。 --- ### `POST /chat/sessions/{sid}/messages` **请求体**(`content` 必填) ```json { "content": "你好" } ``` **响应** `200`(生成已在后台开始,UE 应转去轮询 `/poll`) ```json { "status": "streaming" } ``` - 上游 vLLM 返回非 200(模型 id 错、context 超长等)时,本接口**原样透传** vLLM 的 status code + body,且**不**进入轮询流程。UE 应先看 HTTP status。 - **不要并发同一 sid**:同一 session 上一轮还没轮询到 `done` 前又发新消息,会**取消**上一轮生成、以新消息为准。多个 session 之间互不影响、可任意并发。 --- ### `GET /chat/sessions/{sid}/poll` 拉取自上次轮询以来新生成的文字。UE 按自己的节奏反复调用,直到 `done:true`。 **响应** `200` ```json { "text": "新增的一段文字", "done": false } ``` | 字段 | 含义 | |---|---| | `text` | 自上次 `poll` 以来新增的增量(已拼成一个串);没新数据就是 `""` | | `done` | `true` 表示本轮生成结束,UE 收到就停止轮询 | | `error` | 仅在生成中途上游异常时出现,随 `done:true` 一起返回;`text` 里是已收到的残答 | - 生成已结束并被取走后,再 `poll` 会得到 `{"text":"","done":true}`(幂等的"没东西了")。 - 每次 `poll` 会带 `Cache-Control: no-store`,避免中间层缓存。 --- ### `GET /chat/sessions/{sid}` 调试用。返回当前会话的全量 OpenAI 格式历史。 **响应** `200` ```json { "messages": [ { "role": "system", "content": "你是一个简洁的中文助手。" }, { "role": "user", "content": "你好" }, { "role": "assistant", "content": "你好,我是..." } ] } ``` --- ### `DELETE /chat/sessions/{sid}` 清掉会话历史,并取消该 session 上任何在跑的生成。返回 `204 No Content`。删一个不存在的 sid 也返回 `204`(幂等)。 --- ### `GET /health` ```json { "status": "ok" } ``` 只代表本服务进程在跑,**不代表 vLLM 健康**。要看 vLLM 状态请直接打 `http://:8000/health`。 --- ## 错误响应 | Status | Body | 何时 | |---|---|---| | `400` | `{"detail":"missing or empty 'content'"}` | 请求体里 `content` 不是字符串或为空 | | `400` | `{"detail":"invalid JSON body"}` | `/messages` 请求体不是合法 JSON | | `404` | `{"detail":"session not found"}` | sid 不存在或已删(`/messages`、`/poll`、`GET` 都一样) | | `502` | `{"detail":"upstream unreachable: ..."}` | vLLM 进程没起 / 网络不通 | | 其它 4xx/5xx | 透传 vLLM 原始 body | 模型 id 错、prompt 超长等,body 是 vLLM 的 JSON 错误对象 | --- ## 注意事项 1. **不要并发同一 sid**:同一 session 未轮询到 `done` 前又发新消息,旧生成会被取消、以新消息为准。不同 session 可任意并发。 2. **历史不主动截断**:累计 token 数超过 vLLM 的 `max_model_len`(默认 8192)时,下一次发消息会被 vLLM 4xx。这时建议 `DELETE` 旧 session 再 `POST` 新的。 3. **Qwen3 的 `...`**:默认会作为文字出现在 `poll` 的 `text` 里,也会写进 history。不想让 UE 看到思考过程,在 vLLM 启动参数加 `--reasoning-parser qwen3`,本服务无需改动。 4. **没有鉴权**:内网用,不要暴露公网。 5. **进程重启 = session 全没**:内存存储,没有持久化。 6. **被放弃的生成会自动回收**:UE 崩溃、不再轮询到 `done` 的残留流缓冲,后台会在约 1–2 分钟内清掉,不会无限堆积。 7. **上游卡死有超时**:上游连上后长时间不吐字节会触发读超时并释放连接,不会把连接池占满拖垮整个服务。 --- ## 启动 / 配置 ```bash pip install -r requirements.txt python run_server.py ``` 环境变量(默认值见 `config.py`): | 变量 | 默认 | 说明 | |---|---|---| | `VLLM_URL` | `http://127.0.0.1:8000` | 上游 vLLM 地址 | | `MODEL` | `./models/Qwen3-8B-AWQ` | 传给 vLLM 的模型 id(用 `curl http://:8000/v1/models` 查实际 id) | | `SERVE_HOST` | `0.0.0.0` | 本服务监听地址 | | `SERVE_PORT` | `9000` | 本服务监听端口 | --- ## 测试 纯 Python asyncio,Mac / Linux / WSL 行为一致。测试里用一个**假 vLLM 上游**(真端口起在 loopback 上,emit OpenAI 格式 SSE),端到端验证轮询流程。 ```bash pip install -r requirements-dev.txt pytest # 单元 + 集成(进程内驱动后端) python scripts/smoke_e2e.py # 真端口端到端冒烟(后端 + 上游都用 uvicorn 真起) ``` --- ## 项目结构 ``` dj-temp/ ├── config.py # 环境变量 → Settings ├── run_server.py # 入口 ├── requirements.txt # 运行时依赖 ├── requirements-dev.txt # + 测试依赖 ├── pytest.ini ├── scripts/ │ └── smoke_e2e.py # 真端口端到端冒烟 ├── llm_proxy/ │ ├── app.py # FastAPI 工厂(组合根) │ ├── service.py # ChatService:编排一次对话轮次 │ ├── errors.py # 领域异常 │ ├── sessions.py # SessionStore:内存历史 │ ├── api/ # HTTP 传输层(薄路由) │ │ ├── sessions.py # 会话生命周期 │ │ └── messages.py # 发消息 + 轮询 │ ├── streaming/ # 轮询流式机制 │ │ ├── buffer.py # ActiveStream:增量缓冲 │ │ ├── store.py # StreamStore:注册表 + 生命周期 │ │ └── producer.py # pump_upstream:后台生产者 │ └── upstream/ # 上游 vLLM 对接 │ ├── client.py # VLLMClient(可注入 httpx) │ └── sse.py # parse_sse + iter_content_deltas ├── launcher/ # WSL / Windows 启动脚本(未改动) └── tests/ # 单元 + 集成测试 + 假上游 ``` **分层**:`api`(HTTP)→ `service`(编排)→ `streaming` / `sessions`(领域)+ `upstream`(外部依赖)。依赖自上而下单向注入,每层可独立测试。