2026-07-21 16:15:18 +08:00
|
|
|
|
# chongming-worker
|
|
|
|
|
|
|
2026-08-05 18:22:01 +08:00
|
|
|
|
通用 Worker 框架核心,提供 Zenoh RPC 端点的装饰器定义、参数解析、生命周期管理和配置加载。
|
2026-07-21 16:15:18 +08:00
|
|
|
|
|
2026-08-05 18:22:01 +08:00
|
|
|
|
---
|
2026-07-21 16:15:18 +08:00
|
|
|
|
|
2026-08-05 18:22:01 +08:00
|
|
|
|
## 安装
|
2026-07-21 16:15:18 +08:00
|
|
|
|
|
2026-08-05 18:22:01 +08:00
|
|
|
|
```bash
|
|
|
|
|
|
uv pip install -e utils/worker
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 核心功能
|
|
|
|
|
|
|
2026-08-14 14:33:19 +08:00
|
|
|
|
- **`@rpc_method` 装饰器**:将异步函数注册为 Zenoh Queryable 端点(同 `subject#METHOD` 冲突注册即报错)
|
2026-08-05 18:22:01 +08:00
|
|
|
|
- **自动参数解析**:根据 HTTP 方法从 query 参数或 JSON body 解析请求
|
|
|
|
|
|
- **Pydantic 模型绑定**:`auto_bind_models()` 自动绑定生成的模型
|
2026-08-14 14:33:19 +08:00
|
|
|
|
- **信封协议**:请求负载统一为 `{"meta": {...}, "data": {...}}`,运行时按 `meta.method` 分派
|
|
|
|
|
|
- **Worker 运行时**:`create_worker` / `create_workers` 管理生命周期(结构化日志)
|
2026-08-12 10:19:05 +08:00
|
|
|
|
- **`Plugin` 基类**:统一插件初始化 / 清理生命周期(幂等、日志、数据库、后台任务、清理钩子)
|
2026-08-05 18:22:01 +08:00
|
|
|
|
- **配置加载**:`load_worker_config` 读取 TOML 配置
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
2026-08-12 10:19:05 +08:00
|
|
|
|
## Plugin 基类(推荐)
|
|
|
|
|
|
|
|
|
|
|
|
`chongming_worker.plugin.Plugin` 统一了各插件 `__init__.py` 的初始化/清理风格,
|
|
|
|
|
|
消除重复样板(数据库初始化、global_var 写入、幂等保护、后台任务、日志、清理钩子)。
|
|
|
|
|
|
|
|
|
|
|
|
### 最小示例
|
|
|
|
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
|
# src/plugins/my_plugin/__init__.py
|
|
|
|
|
|
import logging
|
|
|
|
|
|
|
|
|
|
|
|
from chongming_worker.plugin import Plugin
|
|
|
|
|
|
|
|
|
|
|
|
from . import router # noqa: F401 触发 @rpc_method 注册
|
|
|
|
|
|
# from .routes import router # 可选:自定义 FastAPI 路由(网关自动发现)
|
|
|
|
|
|
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class MyPlugin(Plugin):
|
|
|
|
|
|
type = "my_plugin"
|
2026-08-17 17:26:59 +08:00
|
|
|
|
db_name = "my_plugin.db" # SQLite:自动在 data_dir 下建库;config 含 database.url 时优先走 PostgreSQL;不需要则置 None
|
2026-08-12 10:19:05 +08:00
|
|
|
|
auto_bind = True # 首次初始化时自动 auto_bind_models(self.type)
|
|
|
|
|
|
|
|
|
|
|
|
async def on_init(self):
|
2026-08-17 17:26:59 +08:00
|
|
|
|
# 数据库已由基类自动初始化(self.db,SQLite 或 PostgreSQL)
|
2026-08-12 10:19:05 +08:00
|
|
|
|
self.set_var("db", self.db) # 写入插件 global_var 模块
|
|
|
|
|
|
# self.spawn_task(self._background_loop()) # 后台任务(cleanup 自动取消)
|
|
|
|
|
|
# self.register_cleanup(self._close_client) # 注册清理钩子(逆序执行)
|
|
|
|
|
|
|
|
|
|
|
|
async def on_cleanup(self):
|
|
|
|
|
|
# 关闭自定义客户端等(后台任务与数据库由基类统一处理)
|
|
|
|
|
|
...
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-08-17 17:26:59 +08:00
|
|
|
|
# PostgreSQL 接入(可选):在插件 config.toml 配置 database 段后,
|
|
|
|
|
|
# 基类 init_database() 检测到 url 即走 Database.from_config 接入 PG::
|
|
|
|
|
|
#
|
|
|
|
|
|
# # config/workers/my_plugin/config.toml
|
|
|
|
|
|
# [workers.config.database]
|
|
|
|
|
|
# type = "postgresql"
|
|
|
|
|
|
# url = "postgresql+asyncpg://user:pwd@127.0.0.1:5432/mydb"
|
|
|
|
|
|
# generate_schemas = true
|
|
|
|
|
|
|
2026-08-12 10:19:05 +08:00
|
|
|
|
# 暴露给 app lifespan 的模块级入口(bound method,签名与旧写法一致)
|
|
|
|
|
|
plugin = MyPlugin()
|
|
|
|
|
|
create_workers = plugin.create_workers
|
|
|
|
|
|
cleanup = plugin.cleanup
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 子类可覆写点
|
|
|
|
|
|
|
|
|
|
|
|
| 属性 / 方法 | 说明 |
|
|
|
|
|
|
| -------------- | ---------------------------------------------------------------------------- |
|
|
|
|
|
|
| `type` | 插件类型(worker type),匹配 subject 前缀与 `config/workers/<type>`(必填) |
|
2026-08-17 17:26:59 +08:00
|
|
|
|
| `db_name` | 默认 SQLite 文件名(放 `data_dir` 下);config 含 `database.url` 时优先走 PostgreSQL;`None` 表示不自动建库 |
|
2026-08-12 10:19:05 +08:00
|
|
|
|
| `auto_bind` | 首次初始化时是否自动 `auto_bind_models(self.type)` |
|
|
|
|
|
|
| `on_init()` | 首次 `create_workers` 时调用一次,初始化资源 |
|
|
|
|
|
|
| `on_cleanup()` | `cleanup` 时调用,清理自定义资源 |
|
|
|
|
|
|
|
|
|
|
|
|
### 框架自动处理
|
|
|
|
|
|
|
2026-08-14 14:33:19 +08:00
|
|
|
|
- **幂等**:重复 `create_workers` 只初始化一次(`asyncio.Lock` 守卫,并发调用安全)
|
2026-08-12 10:19:05 +08:00
|
|
|
|
- **统一日志**:`logger` 自动带 `[type]` 前缀
|
2026-08-17 17:26:59 +08:00
|
|
|
|
- **数据库**:`init_database()` 统一建库建表、记录,`cleanup` 自动 `dispose()`(SQLite 按 `db_name`;config 含 `database.url` 时走 PostgreSQL)
|
2026-08-14 14:33:19 +08:00
|
|
|
|
- **global_var**:`set_var()` / `get_var()` 统一读写插件 `global_var` 模块;`cleanup` 时自动将已发布的变量重置为 `None`,避免业务代码拿到已释放的资源
|
2026-08-12 10:19:05 +08:00
|
|
|
|
- **后台任务**:`spawn_task()` 跟踪,`cleanup` 时自动取消并 `gather`
|
|
|
|
|
|
- **清理钩子**:`register_cleanup()` 注册,`cleanup` 时逆序执行
|
2026-08-14 14:33:19 +08:00
|
|
|
|
- **委托**:自动调用 `runtime.create_workers` 创建 Worker 副本(经 `_create_workers_impl`,测试可替换桩)
|
2026-08-12 10:19:05 +08:00
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
2026-08-05 18:22:01 +08:00
|
|
|
|
## API
|
|
|
|
|
|
|
|
|
|
|
|
### `@rpc_method` 装饰器
|
|
|
|
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
|
from chongming_worker.decorators import rpc_method
|
|
|
|
|
|
from ..models.pydantic import AddGetRequest, AddGetResponse
|
|
|
|
|
|
|
|
|
|
|
|
@rpc_method(
|
|
|
|
|
|
subject="example.add",
|
|
|
|
|
|
method="GET",
|
|
|
|
|
|
response_model=AddGetResponse,
|
|
|
|
|
|
)
|
|
|
|
|
|
async def add(request: AddGetRequest) -> AddGetResponse:
|
|
|
|
|
|
return AddGetResponse(result=request.a + request.b)
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
| 参数 | 类型 | 说明 |
|
|
|
|
|
|
| ---------------- | ----------------- | ---------------------------------------- |
|
|
|
|
|
|
| `subject` | `str` | Zenoh 主题(必需),如 `"example.add"` |
|
|
|
|
|
|
| `method` | `str` | HTTP 方法:`"GET"` / `"POST"` / `"MQTT"` |
|
|
|
|
|
|
| `response_model` | `type[BaseModel]` | Pydantic 响应模型类 |
|
2026-08-21 14:39:11 +08:00
|
|
|
|
| `stream` | `bool` | 流式端点:函数返回生成器,框架逐块多回复推送(默认 `false`) |
|
2026-08-05 18:22:01 +08:00
|
|
|
|
|
|
|
|
|
|
**装饰器行为**:
|
|
|
|
|
|
|
2026-08-14 14:33:19 +08:00
|
|
|
|
| method | 参数来源 | 请求绑定 |
|
|
|
|
|
|
| ------ | ------------------ | ------------------------ |
|
|
|
|
|
|
| `GET` | URL query string | TOML `params` 定义 |
|
|
|
|
|
|
| `POST` | JSON body | 自动绑定到 Pydantic 模型 |
|
|
|
|
|
|
| `MQTT` | MQTT payload JSON | 同 POST |
|
|
|
|
|
|
|
2026-08-21 14:39:11 +08:00
|
|
|
|
### 流式端点(SSE 多回复)
|
|
|
|
|
|
|
|
|
|
|
|
`@rpc_method(..., stream=True)` 的函数返回**生成器**(async / sync 均可),框架自动将每个
|
|
|
|
|
|
`yield` 的 dict 作为独立 Zenoh 回复块推送(Query/Reply 原生多回复),网关以 SSE
|
|
|
|
|
|
(`text/event-stream`)逐块转发给 HTTP 客户端。无需端口、进程或新依赖:
|
|
|
|
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
|
@rpc_method(subject="example.stream_chat", method="POST", stream=True)
|
|
|
|
|
|
async def stream_chat(prompt: str = "你好", tokens: int = 10):
|
|
|
|
|
|
for i in range(tokens):
|
|
|
|
|
|
await asyncio.sleep(0.05) # 模拟逐 token 生成
|
|
|
|
|
|
yield {"token": f"{prompt}·{i + 1}", "index": i}
|
|
|
|
|
|
yield {"done": True}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**协议约定**(框架自动处理,插件无需关心底层实现):
|
|
|
|
|
|
|
|
|
|
|
|
- 生成器正常结束 = 流结束。**没有协议层结束哨兵**;`{"done": True}` 这类字段只是业务标记
|
|
|
|
|
|
- 生成器中途异常 → 框架发送 `{"error": ...}` 收尾块后停流
|
|
|
|
|
|
- 客户端断开 / 查询被网关放弃 → 逐块 reply 失败时框架立即停流,避免 Worker 空转
|
|
|
|
|
|
- 每个块必须是可 JSON 序列化的对象,单块建议小于 64KB(zenoh 消息上限内)
|
|
|
|
|
|
|
|
|
|
|
|
**网关侧配合**:对应 TOML 方法节声明 `stream = true` 并配大 `timeout`(流总寿命上限),
|
|
|
|
|
|
详见网关仓库 README「流式端点(SSE)」。非流式端点(不声明 `stream`)行为完全不变。
|
|
|
|
|
|
|
2026-08-14 14:33:19 +08:00
|
|
|
|
### 请求信封协议
|
|
|
|
|
|
|
|
|
|
|
|
Worker 运行时收到的请求负载统一为信封结构(网关 HTTP 路由 / MQTT Bridge / `chongming-request` Client 自动组装):
|
|
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
|
{
|
|
|
|
|
|
"meta": { "method": "GET", "user_info": {...}, "timeout": 30, "cacheable": false },
|
|
|
|
|
|
"data": { "a": 1, "b": 2 }
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
- `meta.method` 决定分派到哪个 `@rpc_method`(GET/POST/.../MQTT),缺省 `GET`
|
|
|
|
|
|
- `meta.user_info` 注入到签名为 `user_info: dict | None = None` 的处理器参数
|
|
|
|
|
|
- `data` 为业务参数,经请求模型校验后传给处理器
|
|
|
|
|
|
- 非信封载荷(无 `meta`/`data`)按旧行为兜底:整包视为 `data`、method 取 GET,并输出一次告警
|
|
|
|
|
|
|
|
|
|
|
|
### 注册表冲突检测
|
|
|
|
|
|
|
|
|
|
|
|
`@rpc_method` 注册时以 `subject#METHOD` 为唯一键;同一键重复注册且函数不同时直接抛
|
|
|
|
|
|
`ValueError`(含双方函数位置),不再静默覆盖——静默覆盖会悄悄改变运行时分派行为。
|
2026-08-05 18:22:01 +08:00
|
|
|
|
|
|
|
|
|
|
### `auto_bind_models(worker_type)`
|
|
|
|
|
|
|
|
|
|
|
|
自动将 `gen_models` 生成的 Pydantic 模型绑定到对应端点,省去手动导入和绑定。
|
2026-07-21 16:15:18 +08:00
|
|
|
|
|
|
|
|
|
|
```python
|
2026-08-05 18:22:01 +08:00
|
|
|
|
from chongming_worker import auto_bind_models
|
2026-07-21 16:15:18 +08:00
|
|
|
|
|
2026-08-05 18:22:01 +08:00
|
|
|
|
auto_bind_models("example")
|
2026-07-21 16:15:18 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-08-05 18:22:01 +08:00
|
|
|
|
### `create_workers`
|
|
|
|
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
|
from chongming_worker.runtime import create_workers
|
|
|
|
|
|
|
|
|
|
|
|
tasks = create_workers(
|
|
|
|
|
|
session=zenoh_session,
|
|
|
|
|
|
config=plugin_config,
|
|
|
|
|
|
count=3,
|
|
|
|
|
|
worker_manager=worker_manager,
|
|
|
|
|
|
)
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
| 参数 | 类型 | 说明 |
|
|
|
|
|
|
| ---------------- | --------------- | ----------------- |
|
|
|
|
|
|
| `session` | `zenoh.Session` | Zenoh 会话 |
|
|
|
|
|
|
| `config` | `dict` | 插件配置 |
|
|
|
|
|
|
| `count` | `int` | Worker 副本数 |
|
|
|
|
|
|
| `worker_manager` | `WorkerManager` | Worker 管理器实例 |
|
|
|
|
|
|
|
|
|
|
|
|
### `load_worker_config`
|
|
|
|
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
|
from chongming_worker.config import load_worker_config
|
|
|
|
|
|
|
|
|
|
|
|
cfg = load_worker_config("example")
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 插件集成模式
|
|
|
|
|
|
|
2026-08-14 14:33:19 +08:00
|
|
|
|
> 推荐使用上文的 **Plugin 基类**(`src/plugins/` 下所有插件均已迁移到该风格);
|
|
|
|
|
|
> 以下为不依赖基类的传统写法,仅框架层面保留兼容,新插件请勿采用。
|
2026-08-12 10:19:05 +08:00
|
|
|
|
|
2026-08-05 18:22:01 +08:00
|
|
|
|
```python
|
2026-08-14 14:33:19 +08:00
|
|
|
|
# src/plugins/my_plugin/__init__.py(旧式写法,已不推荐)
|
2026-08-05 18:22:01 +08:00
|
|
|
|
from chongming_worker import auto_bind_models
|
|
|
|
|
|
from chongming_worker.runtime import create_workers as _create_workers
|
|
|
|
|
|
from .router import * # 触发 @rpc_method 注册
|
|
|
|
|
|
|
|
|
|
|
|
auto_bind_models("my_plugin")
|
|
|
|
|
|
|
2026-08-12 10:19:05 +08:00
|
|
|
|
_initialized = False
|
|
|
|
|
|
|
2026-08-05 18:22:01 +08:00
|
|
|
|
async def create_workers(session, config, count, worker_manager):
|
|
|
|
|
|
"""插件入口:初始化资源,启动 Worker"""
|
2026-08-12 10:19:05 +08:00
|
|
|
|
global _initialized
|
|
|
|
|
|
if _initialized:
|
|
|
|
|
|
return _create_workers(session, config, count, worker_manager)
|
2026-08-05 18:22:01 +08:00
|
|
|
|
from chongming_database import Database
|
|
|
|
|
|
from .global_var import set_db
|
|
|
|
|
|
|
|
|
|
|
|
db = Database(...)
|
|
|
|
|
|
await db.initialize()
|
|
|
|
|
|
set_db(db)
|
2026-08-12 10:19:05 +08:00
|
|
|
|
_initialized = True
|
2026-08-05 18:22:01 +08:00
|
|
|
|
return _create_workers(session, config, count, worker_manager)
|
|
|
|
|
|
|
2026-08-14 14:33:19 +08:00
|
|
|
|
async def cleanup(worker_item: str):
|
2026-08-05 18:22:01 +08:00
|
|
|
|
from .global_var import db
|
|
|
|
|
|
await db.dispose()
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 导出清单
|
|
|
|
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
|
from chongming_worker import (
|
|
|
|
|
|
RPCMethod, rpc_method, # 装饰器
|
|
|
|
|
|
bind_model, auto_bind_models, # 模型绑定
|
|
|
|
|
|
get_rpc_methods, get_all_rpc_methods, clear_registry, # 注册表
|
|
|
|
|
|
generate_route_configs, # 路由配置生成
|
|
|
|
|
|
load_worker_config, # TOML 配置加载
|
|
|
|
|
|
WorkerConfig, OperationConfig, # 配置类型
|
|
|
|
|
|
create_worker, create_workers, # 运行时
|
2026-08-12 10:19:05 +08:00
|
|
|
|
Plugin, # 统一生命周期基类
|
2026-08-05 18:22:01 +08:00
|
|
|
|
)
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 依赖
|
2026-07-21 16:15:18 +08:00
|
|
|
|
|
2026-08-05 18:22:01 +08:00
|
|
|
|
- `zenoh` — Zenoh Python 绑定
|
|
|
|
|
|
- `pydantic` — 数据验证与序列化
|
|
|
|
|
|
- `tomli` — TOML 配置解析
|