//! 参考插件(多语言演进规划阶段 1 的参考实现) //! //! 演示 Rust SDK 的完整能力,按 `--type` 对位不同插件: //! - `--type rsdemo`(默认):独立演示插件,覆盖普通/流式/错误/user_info 全形态 //! - `--type demo`:与 Python demo 插件同 type 混布对拍(storage 未配置时降级 //! 返回与 Python 一致的错误语义,验证负载均衡在两实现间透明分发) //! //! 模型一致性:请求/响应模型由端点 TOML 生成(`generated_models.rs`), //! 启动时做指纹校验——TOML 变更而未重新生成模型会告警。 //! //! 用法(项目根目录): //! cargo run --example demo -- --type rsdemo //! cargo run --example demo -- --type demo // 模型由 gen_models 生成(crate 根,勿手动修改): // cargo run --bin gen_models -- --types rsdemo,demo --out generated_models.rs #[path = "../generated_models.rs"] mod generated_models; use std::sync::Arc; use std::time::{SystemTime, UNIX_EPOCH}; use chongming_worker_rs::gen; use chongming_worker_rs::prelude::*; use chongming_worker_rs::{HandlerRegistry, ServeOptions}; use clap::Parser; use serde_json::{json, Value}; use tracing_subscriber::EnvFilter; /// 参考插件:Rust 实现 chongming 进程插件(对位 Python SDK 行为) #[derive(Parser, Debug)] #[command(name = "chongming-worker-rs-demo", about = "Rust 进程插件参考实现")] struct Args { /// 插件类型(rsdemo / demo) #[arg(long, default_value = "rsdemo")] r#type: String, /// 副本数(覆盖插件配置中的 count) #[arg(long)] count: Option, /// 全局配置文件路径(读取 [zenoh] 节) #[arg(long, default_value = "config.toml")] config: String, /// 实例 id(多实例全局分摊用;缺省 {hostname}:{pid}) #[arg(long)] instance: Option, } fn now_ts() -> f64 { SystemTime::now() .duration_since(UNIX_EPOCH) .map(|d| d.as_secs_f64()) .unwrap_or(0.0) } fn deser(data: &Value) -> Result { serde_json::from_value(data.clone()).map_err(|e| format!("参数解析失败: {e}")) } // ==================== rsdemo 端点(使用 rsdemo 生成模型) ==================== /// rsdemo.hello:普通 GET 端点(对位 Python demo.hello,强类型请求/响应) async fn hello_rsdemo(request: Request) -> Result { let req: generated_models::rsdemo::HelloGetRequest = deser(&request.data)?; let resp = generated_models::rsdemo::HelloGetResponse { greeting: format!("Hello, {}!", req.name), worker_id: Some(request.worker_id), timestamp: now_ts(), }; Ok(Reply::Json(serde_json::to_value(&resp).map_err(|e| e.to_string())?)) } /// rsdemo.echo:POST 端点,演示 user_info 透传(网关认证后注入 meta.user_info) async fn echo(request: Request) -> Result { let req: generated_models::rsdemo::EchoPostRequest = deser(&request.data)?; let resp = generated_models::rsdemo::EchoPostResponse { echo: req.msg, user_info: request.user_info, worker_id: Some(request.worker_id), timestamp: now_ts(), }; Ok(Reply::Json(serde_json::to_value(&resp).map_err(|e| e.to_string())?)) } /// rsdemo.stream:流式端点(SSE)。tokens 控制块数;tokens > 50 时中途抛错 /// 演示 `{"error": ...}` 收尾块(网关收到后终止流)。 async fn stream_chat(request: Request) -> Result { let req: generated_models::rsdemo::StreamPostRequest = deser(&request.data)?; let tokens = req.tokens.clamp(0, 100) as usize; let stream = async_stream::stream! { for i in 0..tokens { if i == 25 && tokens > 50 { yield Err("模拟流中异常(第 26 块)".to_string()); return; } yield Ok(json!({"index": i, "chunk": format!("chunk-{i}")})); tokio::time::sleep(std::time::Duration::from_millis(50)).await; } }; Ok(Reply::Stream(Box::pin(stream))) } /// rsdemo.error:普通端点,恒抛业务异常,演示非流式错误回复格式 /// ({"result":0.0,"operation":"error","error":...,"worker":...}) async fn always_error(_request: Request) -> Result { Err("模拟业务异常(rsdemo.error)".to_string()) } // ==================== demo 端点(使用 demo 生成模型,与 Python 混布对拍) ==================== /// demo.hello:与 Python workers/demo 对位(强类型模型来自 demo 生成模型) async fn hello_demo(request: Request) -> Result { let req: generated_models::demo::HelloGetRequest = deser(&request.data)?; let resp = generated_models::demo::HelloGetResponse { greeting: format!("Hello, {}!", req.name), worker_id: Some(request.worker_id), timestamp: now_ts(), }; Ok(Reply::Json(serde_json::to_value(&resp).map_err(|e| e.to_string())?)) } /// storage 未配置降级(演示:Rust 插件无需 Minio 依赖即可对位 demo type) async fn storage_not_configured(_request: Request) -> Result { Err("storage 未配置(本 Rust 参考插件未实现 S3/Minio 客户端)".to_string()) } fn main() -> Result<(), String> { let args = Args::parse(); tracing_subscriber::fmt() .with_env_filter( EnvFilter::try_from_default_env() .unwrap_or_else(|_| EnvFilter::new("info")), ) .init(); // 模型一致性校验:端点 TOML 是唯一事实来源,TOML 变更后须重新运行 gen_models check_models_fingerprint(&args.r#type); let mut registry = HandlerRegistry::new(); // ---- rsdemo 独立演示插件 ---- registry.register("rsdemo.hello", "GET", hello_rsdemo); registry.register("rsdemo.echo", "POST", echo); registry.register("rsdemo.stream", "POST", stream_chat); registry.register("rsdemo.error", "GET", always_error); // ---- demo 混布对拍:与 Python demo 插件同 type(同配置、同描述符)---- registry.register("demo.hello", "GET", hello_demo); registry.register("demo.storage.presign_upload", "POST", storage_not_configured); registry.register("demo.storage.presign_download", "GET", storage_not_configured); registry.register("demo.storage.upload_bytes", "POST", storage_not_configured); registry.register("demo.storage.download_bytes", "GET", storage_not_configured); let opts = ServeOptions { config_path: args.config, config_dir: "config/workers".into(), count: args.count, instance: args.instance, }; tokio::runtime::Builder::new_multi_thread() .enable_all() .build() .map_err(|e| format!("创建 tokio runtime 失败: {e}"))? .block_on(chongming_worker_rs::serve::serve( &args.r#type, Arc::new(registry), opts, )) } /// TOML ↔ 生成模型一致性校验:指纹不匹配时告警并提示重新生成 fn check_models_fingerprint(worker_type: &str) { let fp = match gen::fingerprint(worker_type, "config/workers") { Ok(f) => f, Err(e) => { tracing::warn!("[{worker_type}] 计算模型指纹失败(跳过一致性校验): {e}"); return; } }; let expected = match worker_type { "rsdemo" => generated_models::rsdemo::FINGERPRINT, "demo" => generated_models::demo::FINGERPRINT, _ => "", }; if fp != expected { tracing::warn!( "[{worker_type}] 模型指纹不匹配:端点 TOML 已变更但未重新生成模型。\ 运行: cargo run --bin gen_models -- --types rsdemo,demo --out generated_models.rs" ); } else { tracing::info!("[{worker_type}] 模型一致性校验通过(TOML ↔ 生成模型)"); } }