modify: 流式输出
This commit is contained in:
@@ -264,6 +264,93 @@ async def upload(file: UploadFile = File(...)):
|
||||
|
||||
> **约定**:插件 `__init__.py` 中导入 `router` 实例(`from .routes import router`),网关通过 `getattr(module, "router", None)` 自动发现。
|
||||
|
||||
### 流式端点(SSE)
|
||||
|
||||
基于 Zenoh Query/Reply 原生"多回复"实现流式输出:Worker 的 `@rpc_method` 返回生成器时,
|
||||
框架逐块 `query.reply()` 推送(见 `utils/worker` README「流式端点」),网关以 SSE
|
||||
(`text/event-stream`)逐块转发给 HTTP 客户端。**无额外端口、无额外进程、无新依赖**;
|
||||
未声明 `stream` 的端点行为与之前完全一致。
|
||||
|
||||
#### TOML 配置
|
||||
|
||||
在方法节中声明 `stream = true`(示例 `config/workers/example/stream_chat.toml`):
|
||||
|
||||
```toml
|
||||
[POST]
|
||||
summary = "模拟 AI 流式对话(SSE,逐 token 输出)"
|
||||
auth_required = false
|
||||
stream = true
|
||||
timeout = 300.0 # 流总寿命上限(秒,zenoh query deadline 硬上限)
|
||||
stream_idle_timeout = 10.0 # 网关侧连续无数据块的最大间隔(秒)
|
||||
params = ["prompt: str", "tokens: int"]
|
||||
```
|
||||
|
||||
| 字段 | 默认值 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `stream` | `false` | 流式端点:以 SSE(`text/event-stream`)输出 |
|
||||
| `timeout` | `3.0` | 流总寿命上限(秒)。**流式端点必须配大**(如 300s),到期后 zenoh 丢弃后续回复 |
|
||||
| `stream_idle_timeout` | `10.0` | 空闲超时(秒):超过该间隔无新数据块则网关终止流 |
|
||||
|
||||
> `timeout`(总寿命硬上限)与 `stream_idle_timeout`(空闲超时)是两回事,流式场景两个都要配。
|
||||
|
||||
#### 请求与响应
|
||||
|
||||
- 请求与普通端点一致(GET 走 query/path 参数,POST/PUT/PATCH 走 body 模型),鉴权依赖同样生效
|
||||
- 响应为 `text/event-stream`,每块一个 `data: {json}\n\n` 事件(SSE 标准分帧)
|
||||
- 响应头:`Cache-Control: no-cache`、`X-Accel-Buffering: no`(配合反代关闭缓冲)
|
||||
- OpenAPI 不声明流式端点的响应 schema(逐块 JSON dict,无法用单一模型描述)
|
||||
|
||||
```bash
|
||||
curl -N -X POST localhost:8000/api/v1/calc/stream_chat \
|
||||
-H 'Content-Type: application/json' -d '{"prompt":"你好","tokens":3}'
|
||||
# data: {"token":"你好·1","index":0}
|
||||
# data: {"token":"你好·2","index":1}
|
||||
# data: {"token":"你好·3","index":2}
|
||||
# data: {"done":true}
|
||||
```
|
||||
|
||||
#### 错误语义
|
||||
|
||||
| 场景 | 表现 |
|
||||
| --- | --- |
|
||||
| 无可用 worker(首个数据块前) | HTTP 503(与普通端点一致) |
|
||||
| Worker 中途异常 / 流式端点初始化失败 | SSE error 事件 `data: {"error":"..."}` 收尾(HTTP 200 已提交,无法再改状态码) |
|
||||
| 空闲超时 | SSE error 事件 `data: {"error":"流式空闲超时 ..."}` 收尾 |
|
||||
| 客户端断开 | 网关终止迭代,Worker 侧 reply 失败自动停流,不空转 |
|
||||
|
||||
#### 部署注意
|
||||
|
||||
生产环境经 nginx 反代时需关闭缓冲,否则 SSE 会被整段缓冲、失去逐块即时性:
|
||||
|
||||
```nginx
|
||||
location /api/v1/ {
|
||||
proxy_buffering off;
|
||||
}
|
||||
```
|
||||
|
||||
#### 本地联调前注意
|
||||
|
||||
流式能力在 `chongming-worker` 框架(`utils/worker/`,独立仓库)。**本地未 push 的框架改动
|
||||
不会自动生效**——venv 里安装的是 `uv sync` 从 git 源拉取的旧版副本,旧版 `@rpc_method`
|
||||
不认 `stream` 参数,会报 `TypeError: rpc_method() got an unexpected keyword argument 'stream'`
|
||||
并连锁导致契约校验失败(启动终止)。本地联调时先以可编辑方式装入 venv:
|
||||
|
||||
```bash
|
||||
uv pip install -e utils/worker
|
||||
```
|
||||
|
||||
正式发布仍走「开发与发布检查清单」流程(工具包 push → `uv lock --upgrade-package
|
||||
chongming-worker` → `uv sync`,可编辑安装会被锁文件版本替换)。
|
||||
|
||||
#### 测试
|
||||
|
||||
```
|
||||
uv run python test_stream_worker.py # Worker 框架流式回复单测
|
||||
uv run python test_stream_gateway.py # 网关 call_stream 单测
|
||||
uv run python test_stream_e2e_zenoh.py # 真实 Zenoh 会话端到端(peer 模式,无需 router)
|
||||
uv run python test_stream_route.py # 路由层 SSE 输出与兼容性回归(非流式端点不变)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 进程插件与混合部署
|
||||
@@ -450,6 +537,9 @@ python publisher.py example add '{"a":3,"b":5}' # 调用 add
|
||||
5. 重启服务
|
||||
```
|
||||
|
||||
流式端点(SSE)在此基础上额外两步:TOML 方法节声明 `stream = true` 并配大 `timeout`;
|
||||
`@rpc_method(..., stream=True)` 函数返回生成器(yield 逐块),详见上文「流式端点(SSE)」。
|
||||
|
||||
### 常用命令
|
||||
|
||||
```bash
|
||||
@@ -566,6 +656,8 @@ uv pip install -e utils/<name> # 安装/更新工具包子包
|
||||
| `internal` | `<操作>.toml` | `true` 则仅限内部 RPC,不注册 HTTP 路由 |
|
||||
| `cacheable` | `<操作>.toml` | 端点是否可缓存 |
|
||||
| `summary` | `<操作>.toml` | 端点描述(用于 OpenAPI / 日志) |
|
||||
| `stream` | `<操作>.toml` | `true` 则流式输出(SSE),需同时配大 `timeout` |
|
||||
| `stream_idle_timeout`| `<操作>.toml` | 流式空闲超时(秒,默认 10),超时无新数据块则终止流 |
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user