feat: AIBridge Rust 重构(agn-sdk v1 -> aibridge v2.0.0)#2
Merged
Conversation
将 agn-sdk 用 Rust 重构为跨语言 SDK aibridge 的设计文档。 - 五语言(Python/JS/Go/JVM/.NET)原生 import - Rust 核心 + 双入口绑定(PyO3/napi 直连 + C ABI) - v2 破坏性升级,MVP 优先 agnes/火山/gemini/openai
- workspace 根 + 4 crate (aibridge-core/ffi/python/node) - aibridge-core 与 aibridge-ffi 编译通过 - rust-toolchain + .gitignore 更新 - 实现计划文档
实现 .NET (C#) 绑定,通过 P/Invoke 调 aibridge-ffi cdylib。 实现文件: - AIBridge.csproj:net8.0 类库 + 可执行 hello(OutputType=Exe, StartupObject=Hello) - Native.cs:P/Invoke 声明全部 aibridge_* 函数 + SafeHandle 封装(字符串/字节自动释放)+ 运行时动态库解析(支持 AIBRIDGE_LIB_PATH 环境变量与仓库 target/debug 回退) - Models.cs:数据模型(ChatRequest/ChatMessage/ChatCompletion/ChatCompletionChunk/SpeechRequest/SpeechResult 等),System.Text.Json 反序列化,字段名对齐 Rust serde snake_case - AibridgeException.cs:异常体系(AibridgeException + 11 个子类,按 last_error 的 code 字段映射) - Client.cs:Client 封装(IDisposable),Chat/Speech/ChatStreamAsync(IAsyncEnumerable<ChatCompletionChunk>),async 方法用 Task.Run 包装阻塞 FFI - Hello.cs:hello world 示例(Program Main),验证 echo 适配器 chat 回显 + stream 3 chunk + speech 15 字节 - build.sh:构建脚本(cargo build ffi + dotnet build + dotnet run) FFI 遗留问题处理: 1. last_error 线程局部:每个 FFI 失败后同线程立即读 last_error 转存(ChatStreamAsync 在 Task.Run 同一 ThreadPool 线程内读取) 2. stream_next 串行:await foreach 天然串行,不并发 next 3. aibridge_bytes_free / aibridge_string_free:用 AibridgeBytesHandle / AibridgeStringHandle(SafeHandle)兜底释放,MarshalAndFree 先拷贝后释放 4. client / stream 句柄:Client.Dispose(Interlocked 原子化防 double-free)调 aibridge_client_destroy;ChatStreamAsync finally 调 aibridge_stream_destroy 验收: - cargo build -p aibridge-ffi 已产 libaibridge.dylib(target/debug/) - dotnet SDK 未装(沙箱无法 sudo 安装),hello world 待 dotnet 环境验证 - build.sh 提供完整构建+运行流程,装好 dotnet 后执行 ./build.sh run 即可
实现 OpenAiCompatAdapter 作为 OpenAI 兼容协议(agnes/openai/azure 等约 80% 适配器)的共享地基,
子适配器(阶段1.1 openai / 1.2 agnes)可复用其 chat/chat_stream/image_generate/
embed/list_models 实现,仅 override base_url、provider_type、特有参数等差异。
主要内容:
- OpenAiCompatAdapter struct:持有 HttpClient、ProviderConfig、provider_type、
provider_name、base_url、capabilities
- 核心方法实现 OpenAI 兼容协议:
- chat:POST /chat/completions,构造请求体(model/messages/temperature/
max_tokens/tools/stream 等),解析响应 → ChatCompletion
- chat_stream:POST /chat/completions stream=true,自实现 SSE 行流解析器
(LinesStream)解析 data: <json> → ChatStream,支持心跳/空行/[DONE]
- image_generate:POST /images/generations → ImageResult
- embed:POST /embeddings → EmbeddingResult
- list_models:GET /models → Vec<ModelInfo>(含模型类型推断)
- 参数映射:预置 openai_compatible_mapping()(用 ParameterMapping 机制,OpenAI
协议即通用名故透传)
- 错误映射:map_api_error 提取 OpenAI error.message / retry_after,
按 HTTP 状态码分类(401→Authentication,429→RateLimit+retry_after,
404→ModelNotFound,400→Validation,5xx→Api)
- 请求体构造:通用参数序列化 + extra 透传 + 流式附带 stream_options.include_usage
- 设计为可复用:方法均为 pub,子适配器通过组合委托调用,Rust 无继承故采用
'地基结构 + trait 方法转发' 模式
单测(45 个,mockito mock HTTP):
- chat 正常路径:解析 completion / 发送 temperature+max_tokens / extra 透传 /
tool_calls 解析
- chat 错误路径:401/429(含 retry_after)/404/500/400/unsupported
- chat_stream:SSE 多 chunk 解析 / 心跳空行 / stream=true + stream_options /
401 / 429
- image_generate:url 解析 / b64 解析 / 401 / 429 / unsupported
- embed:多向量解析 / 单输入 / 401 / 404 / unsupported
- list_models:正常 / 类型过滤 / 401 / 429 / 500
- 错误映射单元测试 + 参数映射测试 + base_url 兜底 + build_chat_body 测试
新增 dev-dependencies:mockito 1
adapters/mod.rs 导出 openai_compat 模块;暂不注册到工厂(阶段1.1/1.2 注册)。
验收:cargo test -p aibridge-core 235 通过(190 原有 + 45 新增),
clippy --all-targets -D warnings 无警告,cargo build -p aibridge-ffi 通过,
cargo fmt 已格式化。
五语言错误 code 对齐 Rust aibridge-core error.rs 的 AibridgeError::code() 实际返回值(authentication_error / rate_limit_error / timeout_error 等): - Go (bindings/go/error.go):errCodeAuthentication/RateLimit/Network/Timeout 四个 常量补齐 _error 后缀,更新注释示例 - .NET (bindings/dotnet/AIBridge/AibridgeException.cs):MapByCode switch 与 Authentication/RateLimit/Timeout 三个子类构造的 code 字符串统一为 Rust 实际值 - Python/Node/JVM 原本已对齐,无需修改 跨语言一致性验证(echo adapter): - 四语言 hello world(Python/Node/Go/JVM)chat/stream/speech 输出语义一致 (chat="hello [echo]",stream 3 chunk 拼接="hello [echo]",speech=15 字节) - 四语言未知 provider 错误探针 code 均为 provider_not_found - .NET 因本机无 dotnet SDK 仅做代码级对齐确认 - 一致性测试文档与探针脚本置于 tests/consistency/
aibridge-ffi 和 aibridge-python 的 [lib] name 都为 "aibridge",导致 cargo build 把两个 cdylib 都输出到 target/debug/libaibridge.dylib 互相覆盖。 maturin develop 后 dylib 变成 PyO3 版本(无 C FFI 符号),Go/JVM 链接失败。 修复(仅改 aibridge-python,ffi 的 [lib] name="aibridge" 保持不变以匹配 Go/JVM/.NET 的 -laibridge 链接): - Cargo.toml: [lib] name 从 "aibridge" 改为 "_aibridge",cdylib 产物变为 lib_aibridge.dylib,与 libaibridge.dylib 分离 - src/lib.rs: #[pymodule] 函数名从 aibridge 改为 _aibridge(生成 PyInit__aibridge 符号与 lib name 一致),加 #[pyo3(name = "aibridge")] 保持模块 Python 名 - pyproject.toml: [tool.maturin] 加 module-name = "aibridge",把 Rust pymodule _aibridge 映射到 Python 顶层模块 aibridge,import aibridge 不受影响 验证: - maturin develop 无 warning,import aibridge 正常 - examples/hello_python.py chat/stream/speech 全部通过 - cargo build -p aibridge-ffi 产 libaibridge.dylib(6 个 C FFI 符号,0 PyInit) - Go hello world 链接 libaibridge 通过 - JVM hello world 通过 - cargo build --workspace 0 warning
Python(crates/aibridge-python/src/lib.rs): chat_stream 的 __anext__ 原先用 RUNTIME.block_on 预取 chunk,echo adapter 无 IO 瞬时完成能跑通,但真实 adapter(openai/agnes/gemini 的 reqwest IO) 会阻塞 asyncio 事件循环。 重构为 PyO3 0.28 内置 Coroutine 桥接模式: - __anext__ 同步返回 pyo3::coroutine::Coroutine(可 await 的 Python 对象), 其包裹的 Rust future 在全局 tokio runtime 上 spawn 消费 core ChatStream。 - 真实 IO 在 tokio worker 线程执行,asyncio 线程仅 await JoinHandle(Pending 时让出,其他协程可运行),不阻塞事件循环。 - Coroutine 的 AsyncioWaker 自动通过 asyncio.Future + call_soon_threadsafe 把 "chunk 就绪" 通知桥接回 asyncio 事件循环(PyO3 内置实现,无需手写 asyncio loop 引用),await 期间让出线程。 - 移除手写的 NextAwaitable(__await__/__iter__/__next__ 协议)及模块注册, 改用 Coroutine 内置的 send/__next__/__await__。 - GIL:future 在 tokio 上 await stream 期间不持 GIL,仅在拿到 chunk 后 Python::attach 构造 pyclass。 - 流结束 future 返回 Err(StopAsyncIteration);chunk 出错返回对应 AibridgeError 子类;正常 chunk 返回 Ok(chunk) → StopIteration(chunk)。 Node(crates/aibridge-node/src/lib.rs): 经核查无需重构。当前 mpsc channel + #[napi] async fn next() 模式不阻塞 libuv 事件循环:napi 2.x 用多线程 tokio runtime,#[napi] async fn 的 future 与 spawn 的消费 task 都在 tokio worker 线程调度,recv().await Pending 时让出 tokio 线程,libuv 事件循环自由运行。JS await Promise 让 其他任务运行,与 Python 端机制等价。 验证: - cargo build --workspace 通过 - cargo clippy -p aibridge-python -p aibridge-node -- -D warnings 无警告 - maturin develop + python examples/hello_python.py:echo chat_stream 3 chunk 拼接 'hello [echo]' 正确 - napi build + node examples/hello_node.js:echo chatStream 3 chunk 拼接 'hello [echo]' 正确 - 真实 IO(openai/agnes/gemini)未验证(无 API key),但重构基于 "tokio task 消费 stream + asyncio await Future" 的非阻塞推理
新增 docs/PROGRESS.md:完整进度(阶段0-2a 完成,23 provider,810 单测)+ 接手指南(环境、命令、worktree 模式、阶段 2b/2c 要点、关键约束)+ 提交历史,供其他 agent 接手。 AGENTS.md 顶部加 Rust 重构指向提示。
实现 KlingAdapter(独立协议,非 OpenAI 兼容):
- video_create:POST /videos/generations(文生视频)/ /videos/image2video(图生视频)
- video_poll:GET /videos/generations/{task_id}
- list_models:硬编码 kling-v1 / v1-5 / v2
- 认证:Bearer Token;默认 base_url https://api.klingai.com/v1
- 状态映射:submitted/queued→pending、processing、succeed/success→success、failed/error→failed
- 视频 URL:data.task_result.videos[0].url;错误:task_status_msg/error
- 错误映射:401→Authentication、429→RateLimit、404→ModelNotFound、400→Validation、5xx→Api
- 不支持能力(chat/image/embed/audio)走 trait 默认实现返 UnsupportedCapability
- 71 个 mockito 单测覆盖正常/错误/边界路径
对应 Python v1 agn/adapters/kling.py。
ElevenLabs TTS 适配器(独立协议,xi-api-key 认证)。
- speech: POST /text-to-speech/{voice_id},二进制音频返回
- list_voices: GET /voices,按语言过滤;recommend_voices 复用默认实现按性别+limit 过滤
- 错误映射: 401→Authentication / 429→RateLimit / 400→Validation / 404→VoiceNotAvailable / 5xx→Api
- 内置 46 个音色别名表(Rachel→voice_id),未知名称原样透传
- 64 个单测,覆盖正常/错误路径(401/429/400/404/500)/不支持能力/纯函数
- .github/workflows/ci.yml: 6 job 跨平台矩阵(Rust core+ffi / Python /
Node / Go / JVM / .NET),覆盖 linux/macos/windows × amd64/arm64,
缓存 cargo/npm/gradle,触发于 push 到 feat/aibridge-rust-rewrite + PR
- scripts/: 7 个 bash 打包脚本(build-ffi/python/node/go/jvm/dotnet
+ release 一键构建),中文注释,set -euo pipefail,可本地运行
- package.json: napi.triples 配置多平台目标(defaults + additional)
- build.gradle.kts: copyNativeLib task 把 libaibridge 打进 jar
(JNA classpath 约定路径 {os}/{arch}/,按平台 classifier)
- AIBridge.csproj: NuGet 打包 runtimes/{rid}/native/(.NET 标准)
不实际发布(无 token),仅 CI 配置 + 打包脚本就绪。
…els/list_voices) 在已有 chat/chat_stream/speech 基础上补全 Client 全部能力绑定: 新增方法(Client): - image_generate(model, prompt, size, n, negative_prompt, reference_images, mask, response_format, **kwargs) -> ImageResult - video_create(model, prompt, width, height, num_frames, frame_rate, mode, reference_images, negative_prompt, seed, **kwargs) -> VideoTask - video_poll(task_id, model="") -> VideoStatus - transcribe(model, file, language, prompt, response_format, temperature, **kwargs) -> TranscriptionResult - embed(model, input, **kwargs) -> EmbeddingResult - list_models(model_type=None) -> list[ModelInfo] - list_voices(language=None) -> list[VoiceInfo] - recommend_voices(language=None, gender=None, limit=10) -> list[VoiceInfo] 新增 11 个 #[pyclass] 数据模型: - ImageData / ImageResult - VideoTask / VideoStatus - TranscriptionSegment / TranscriptionWord / TranscriptionResult - EmbeddingItem / EmbeddingResult(含 get_embeddings()) - ModelInfo(type 字段经 #[getter(r#type)] 暴露为关键字 type) - VoiceInfo 实现要点: - **kwargs 经 Option<Py<PyDict>> 接收(async fn 需 'static future), 经 py_to_json 转 serde_json::Value 填入 core extra 透传 - 字符串默认参数用 &str(async &self 方法支持借用,配合字面量默认值) - file/reference_images/mask 支持 str(路径/URL)或 bytes,经 py_to_file_input 转 core FileInput - video_poll 的 model &str 先 to_string 再 spawn(满足 'static) - TaskStatus 经 task_status_to_str 转字符串暴露给 Python - 错误沿用现有 map_error 映射到 AibridgeError 子类层级 验证(echo adapter,免认证): - cargo build -p aibridge-python 通过 - cargo clippy -p aibridge-python -- -D warnings 无警告 - maturin develop 成功 - examples/hello_python_full.py 全部能力通过(chat/image/video_create+poll /transcribe/embed/list_models/list_voices/recommend_voices/speech) - 原 hello_python.py 回归通过
- .env.example:四 provider(agnes/openai/gemini/火山)key 模板,可上传 GitHub - examples/test_real_providers.py:从 .env 读 key,list_models() 实时拉真实 model 名,测四 provider 各能力,不硬编码 key - .env 已 gitignore,不上传 GitHub
…i_reference) 真实 API 测试发现 video_create 报 [validation_error] Input should be 'ti2vid', 'keyframes' or 'multi_reference'。原实现直接把统一 VideoMode 序列化为 text2video/image2video/keyframes/multiimage 透传,与 agnes 平台 要求的 mode 不一致。 新增 map_video_mode 将统一 VideoMode 显式映射到 agnes 平台 mode: - Text2Video / Image2Video -> ti2vid - Keyframes -> keyframes - Multiimage -> multi_reference build_video_body 改用映射函数;更新 5 处单测断言;新增 map_video_mode 单测覆盖全部 4 种 VideoMode 映射。
…nt 变量 - agnes 全能力(chat/image/video)真实 API 验证通过 - 火山 list_models 通,image/video 需接入点 ID(方舟规则) - .env.example 加 VOLCENGINE_IMAGE/VIDEO_ENDPOINT 变量 + base_url 说明
- .NET:DllImportSearchPath.Default→null、SetHandle→构造函数、Assembly using、DllImportResolver 签名 - Go:CGO LDFLAGS 路径修正(target/debug→target/release) - Python wheel:CI maturin build 路径修正(workspace target/wheels/)
- Go windows-x64:Rust 改用 x86_64-pc-windows-gnu target(与 MinGW gcc 兼容,修复 __chkstk 链接失败) - Python macos-universal2:maturin --target universal2-apple-darwin(修复 --universal2 参数不存在) - 移除已弃用的 macos-x64 job(Rust 核心+ffi / Node .node,24h 超时)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
概述
将 Python
agn-sdk(v1.3.3, ~19700 行) 用 Rust 重构为跨语言 SDKaibridgev2.0.0,支持五种语言直接 import。主要变更
验证
v2 不兼容 v1(包名 agn-sdk -> aibridge,API 重新设计)。老用户请参考 迁移指南。
文档
合并后