# aacode-rs 依赖替代说明
> Python 版 aacode (`pyproject.toml`) 每个依赖在 Rust 版 aacode-rs 中的替代方案、体积影响和决策依据。
---
## 1. 总览
Python 版有 6 个运行时依赖 + 7 个开发依赖 + 3 个可选依赖。
Rust 版 aacode-rs **只有 7 个直接依赖**(其中 `fastshell` 是 workspace 内 crate,其余均为纯 Rust 库,无 C/系统传递依赖),全部在 `aacode-rs/Cargo.toml` 中声明:
```toml
[dependencies]
fastshell = { path = "../fastshell" } # 沙箱 shell + VFS + 内嵌 CPython
serde = { version = "1", features = ["derive"] }
serde_json = "1"
ureq = { version = "2", features = ["json", "tls"] }
regex = "1"
uuid = { version = "1", features = ["v4"] }
base64 = "0.22"
libc = "0.2"
[dev-dependencies]
tiny_http = "0.12" # mock LLM server(集成测试用,不编入 release)
```
Python 版 16 个依赖 -> Rust 版 7 个依赖(+ 1 开发依赖)。下面逐条分析替代方案。
---
## 2. 运行时依赖逐条映射
### 2.1 `openai>=1.0.0` → 手写(`src/llm/openai.rs`)
**Python 用途**:OpenAI 官方 SDK,调用 `chat/completions` 端点(流式 + function calling),同时兼容 DeepSeek / Kimi / Moonshot 等 OpenAI 兼容 API。
**Rust 替代**:**不用任何 SDK,直接手写 HTTP + SSE 解析**。
- HTTP 请求:同 `ureq`(下节)发送 `POST /v1/chat/completions`,手工构造 JSON 请求体
- 流式解析:`src/llm/sse.rs` 自研 SSE 分帧器 + `src/llm/openai.rs::parse_openai_stream` 解析 `delta.content` / `delta.reasoning_content` / `delta.tool_calls`(分片累积)
- 工具调用:`src/tools/schema.rs::to_openai_tools` 将内部 `ToolSchema` 转为 OpenAI native tools 格式
**为什么不直接用 Rust 的 openai SDK**:
- `async-openai`(crates.io 上最完整的 OpenAI Rust 库)依赖 `reqwest` + `tokio` + `tokio-stream` + `eventsource-stream` + `tower`(重试/限流)+ `secrecy` + `derive_builder` 等**大量依赖链**,传递依赖数十个
- 移动端 `.a` 体积敏感:`tokio` 多线程运行时 + `reqwest` + `h2` 显著增加二进制尺寸
- 手写版本依赖零新增(`ureq` 已通过 `fastshell` 在依赖树中),且完全掌控协议边界
### 2.2 `anthropic>=0.25.0` → 手写(`src/llm/anthropic.rs`)
**Python 用途**:Anthropic 官方 SDK,调用 `messages` 端点(流式 + tool_use + extended thinking),兼容 MiniMax / DeepSeek `/anthropic` 端点。
**Rust 替代**:**同 `openai`,手写 Anthropic wire 协议**。
- `POST /v1/messages`,HTTP header `x-api-key` + `anthropic-version: 2023-06-01`
- SSE 解析:`content_block_start` / `content_block_delta`(`text_delta` / `thinking_delta` / `input_json_delta`) / `message_delta.stop_reason`
- 差异处理:system 消息提顶层;tool_result 转成 `role:"user"` 的 `tool_result` block;并行 tool_result 合并进单条 user 消息
- base_url 适配:`/v1` → `/anthropic`(DeepSeek/MiniMax/Moonshot 自动重写端点)
### 2.3 `aiohttp>=3.9.0` → `ureq`(HTTP 客户端)
**Python 用途**:异步 HTTP 客户端,用于 web 搜索(`search_web`)、URL 抓取(`fetch_url`)、LLM API 底层调用、MCP SSE 客户端。
**Rust 替代**:`ureq`(v2.12,同步 + rustls)。
- `web_tools.rs` 中的 `search_web`(SearXNG POST)、`fetch_url`(GET + HTML 清洗)、`search_code`(GitHub API)
- `llm/openai.rs` 和 `llm/anthropic.rs` 的 LLM 流式调用
- `mcp/mod.rs` 的 SSE JSON-RPC 请求
- `tools/multimodal.rs` 的 vision API 调用
**为什么用同步 ureq 而非异步 reqwest**:
- `ureq` 已在 `fastshell` 的依赖树中(用于 curl/wget 命令 + CPython 下载),复用零新增传递依赖
- aacode-rs 的 ReAct 循环本身就是串行的(一个 LLM 调用 → 工具执行 → 下一个 LLM 调用),同步阻塞模型完全够用
- 避免拉入 `tokio`(多线程运行时)+ `reqwest` + `h2`/`hyper` + `tower`,在安卓 `.a` 中这些可占 5-10MB
**一个 ureq 替代了 3 个 Python 网络库**(`openai`/`anthropic`/`aiohttp`)。
### 2.4 `aiofiles>=23.0.0` → `std::fs`(标准库)
**Python 用途**:异步文件 IO(`async with aiofiles.open(...) as f`)。
**Rust 替代**:**标准库 `std::fs`**。
- 会话持久化(`session/mod.rs`):`std::fs::write` + `std::fs::read_to_string` + 原子写(临时文件 + `std::fs::rename`)
- 大输出归档(`context/mod.rs`):同上
- init.md 读写(`context/mod.rs`):`std::fs::read_to_string` / `std::fs::write`
- Todo markdown 文件(`tools/todo.rs`):`std::fs::read_to_string` / `std::fs::write`
- Skills SKILL.md 发现(`tools/skills.rs`):`std::fs::read_dir`
文件操作全部同步。Android 上 `std::fs` 直接可用(无沙箱限制,fastshell VFS 已在命令层管控路径)。
### 2.5 `pyyaml>=6.0` → `serde_json`(格式替换)
**Python 用途**:读取/写入 `aacode_config.yaml` 配置文件。
**Rust 替代**:**`serde` + `serde_json`**,配置改用 JSON 格式。
- JSON 配置文件:`aacode_config.json`(`src/config.rs`)
- 环境变量:`LLM_API_KEY` / `LLM_API_URL` / `LLM_MODEL_NAME` / `LLM_GATEWAY` 等
- taskJson 内联:LLM 参数通过 `aacode_task_start(taskJson)` 传入
- 会话文件:`.aacode/sessions/<id>.json`(`serde` 序列化/反序列化)
**为什么不用 YAML**:Rust 的 YAML 库(`serde_yaml`)体积不小(依赖 `yaml-rust`/`libyaml-sys` 等 C 绑定)。配置优先级上是 env > JSON > 默认,YAML 不是必须的;JSON 格式在所有的 Rust/web/移动端都原生支持,且 serde 生态完善。
### 2.6 `platformdirs>=4.0.0` → 不需要
**Python 用途**:跨平台定位配置/缓存目录(`~/.config/aacode/`, `~/Library/Application Support/aacode/` 等)。
**Rust 替代**:**不需要**。
移动端路径由 App 显式传入(`taskJson.project_path`),无需探测系统目录。桌面 CLI 接受 `-p <path>` 参数或默认使用当前目录。没有缓存/配置目录发现的需求(LLM key 由 env/参数提供,会话文件在项目目录下)。
---
## 3. 开发依赖逐条映射
### 3.1 `pytest>=7.4.0` / `pytest-asyncio>=0.21` / `pytest-cov>=4.0.0` → `cargo test`
**Python 用途**:测试框架(pytest)、异步测试支持(pytest-asyncio)、覆盖率报告(pytest-cov)。
**Rust 替代**:**Rust 内置 `cargo test`**。
- `#[test]` 属性标记测试函数
- `#[cfg(test)] mod tests { ... }` 组织测试模块
- `assert_eq!` / `assert!` / `matches!` 替代 pytest assert
- 异步测试不需要额外框架(Rust `#[tokio::test]` 或在同步上下文中测试)
- 覆盖率可通过 `cargo-llvm-cov` 或 `tarpaulin` 等工具(非项目依赖,CI 可选)
**零额外依赖**:测试框架是 Rust 工具链的一部分。
### 3.2 `hypothesis>=6.0.0` → 录制 SSE fixture
**Python 用途**:属性测试 / 模糊测试(为函数生成大量随机输入验证不变性)。
**Rust 替代**:**暂不引入 `proptest`/`quickcheck`,用录制的真实厂商 SSE fixture 覆盖边界**,效果等效且更贴近实际格式。
`tests/llm_compat.rs` 包含 **23 个离线兼容测试**,每个都是复刻真实厂商观察到的 SSE 数据格式(GPT-4o / DeepSeek / Kimi / Claude / MiniMax 等),涵盖:
- role 首 chunk、null finish_reason、usage chunk、多字节内容、emoji
- reasoning 多段分离、并行工具调用、参数跨 chunk 分片
- 先叙述后调用、keep-alive/blank lines、断流无 [DONE]、流内错误
如后续需属性测试,Rust 生态有 `proptest`/`quickcheck`/`bolero` 可选(零 npm/pip 式的传递依赖膨胀)。
### 3.3 `black>=23.0.0` → `cargo fmt`
**Python 用途**:代码格式化。
**Rust 替代**:**`cargo fmt`**(`rustfmt`),Rust 工具链内置,零依赖。
### 3.4 `mypy>=1.5.0` → `cargo clippy` + Rust 编译器类型检查
**Python 用途**:静态类型检查。
**Rust 替代**:**Rust 编译器本身即强类型检查**(编译时捕获类型错误、生命周期错误、borrow checker)。`cargo clippy` 补充 lint 检查(代码风格、常见错误模式、性能建议)。aacode-rs **clippy 零警告**。
### 3.5 `tiny_http`(Rust 版新增的开发依赖)
`tiny_http = "0.12"`(仅 dev-dependencies,不编入 release)。
**用途**:在 `tests/integration.rs` 中起本地 mock LLM server,提供录制的 SSE 流响应,用于测试完整的 ReAct 循环(LLM 调用 → 工具执行 → 会话持久化 → 协议输出)而不依赖真实网络。
**删除影响**:无。dev-dependencies 不编入 release 产物。
---
## 4. 可选依赖逐条映射
### 4.1 `ripgrep>=14.0.0` → fastshell 内置
**Python 用途**:`rg` 命令用于快速代码搜索。
**Rust 替代**:**fastshell 内置**。执行 `run_shell("rg pattern")` 时:
- **Native 后端**(桌面)→ 使用系统安装的 `rg`(如果已安装),fallback 到 `grep`
- **Fastshell 后端**(移动端)→ fastshell 内置的 `grep` 命令(纯 Rust 实现,零依赖)
### 4.2 `tiktoken` → 字符估算(4:1)
**Python 用途**:精确 token 计数,用于上下文压缩的触发阈值。
**Rust 替代**:**字符估算**(`4 个字符 ≈ 1 token`),不移植 tiktoken。
```rust
// src/session/mod.rs
pub fn estimate_tokens(text: &str) -> usize {
text.chars().count() / 4
}
```
**为什么不移植**:
- tiktoken 需要下载 **BPE 词表文件**(`cl100k_base.tiktoken`,~1.7MB),移动端不现实
- 即使装了 tiktoken,初始化需要网络下载或预置文件 → 冷启动慢
- 估算偏差在上下文压缩场景可接受:触发阈值和压缩策略本身有安全边际(trigger 设为 80% 的 max),±10% 的计数误差不影响压缩结果
### 4.3 `desktop[pyyaml]` → 不需要
**Python 桌面客户端额外配置**。Rust 版配置统一用 JSON + env,不区分桌面/移动端。
---
## 5. 关键库对比:ureq vs reqwest(深入分析)
Rust 生态最主流的 HTTP 库是 `reqwest`,而 aacode-rs 选择了 `ureq`。这是一个审慎的权衡:
### reqwest(异步,v0.13)
**优点**:
- 生态最完整(TLS、代理、cookie、multipart、websocket)
- `eventsource-stream` 提供开箱即用的 SSE 解析
- 异步并发(多请求并行)自然
**对移动端的影响**:
- 依赖链:`reqwest` → `hyper`(+`h2`) → `tokio` → `mio` + `bytes` + `http` + `tower` + `tower-http` ...
- 仅 `tokio` 多线程运行时在 `.a` 静态库中约 **2-4MB**(strip 前)
- `hyper` + `h2`(HTTP/2 支持)额外 1-2MB
- 传递依赖总数 ~30-50 crates
### ureq(同步,v2.12,已选方案)
**优点**:
- 已在 fastshell 的依赖树中(fastshell 用它做 curl/wget/CPython downloader)
- 同步 API,无 async runtime
- 依赖最小:`rustls` + `webpki-roots` + `base64` + `flate2`(仅 ~8 crates)
- 全纯 Rust(rustls 替代 OpenSSL),无系统 TLS 依赖
**取舍**:
- 不支持异步并发 → aacode-rs 的 ReAct 循环本就是串行的,无影响
- HTTP/2 不支持 → LLM API 绝大部分是 HTTP/1.1
- `eventsource-stream` 不可用 → 自研 `sse.rs`(60 行,-0 依赖)
### 体积估算
| ureq + rustls(已复用 fastshell) | ~0MB(零新增) |
| serde + serde_json(已复用 fastshell) | ~0MB(零新增) |
| regex / uuid / base64 / libc(已复用或极轻) | ~1.5MB |
| 如果用 reqwest + tokio + eventsource-stream 代替 ureq | ~8-12MB 新增 |
**结论**:aacode-rs 的 7 个依赖全部已在 fastshell 的传递依赖树中或为极轻量库,增量体积仅 ~1.5MB,对移动端 `.so` 控制极为有利。
---
## 6. 依赖拓扑图
```
aacode-rs (7 deps)
├── fastshell ─────────────── 沙箱 Shell + VFS + CPython
│ ├── ureq ──────────────── 复用于 LLM + web + MCP + multimodal
│ │ ├── rustls
│ │ └── webpki-roots
│ ├── serde + serde_json ── 复用于 config + session + LLM wire + tools
│ ├── regex ─────────────── 复用于危险命令匹配
│ └── uuid ──────────────── 复用于 session id + 归档文件名
├── base64 ────────────────── 多模态图片编码(已复用?见注)
└── libc ──────────────────── isatty(TTY 检测)+ SIGKILL(超时 kill)
注:base64 在 fastshell 依赖树中(用于 HTTP basic auth),aacode-rs 显式声明以确保版本一致。
```
所有依赖均为纯 Rust crate(rustls 通过 `aws-lc-rs` 或 `ring` 提供密码学原语),无系统 OpenSSL/C 库的交叉编译负担。
---
## 7. 替代依赖总结表
| `openai` | 运行时 | 手写 HTTP + SSE(`llm/openai.rs`) | 无 | 0 |
| `anthropic` | 运行时 | 手写 HTTP + SSE(`llm/anthropic.rs`) | 无 | 0 |
| `aiohttp` | 运行时 | `ureq`(复用于 LLM/web/MCP) | 0(已在 fastshell 中) | 0 |
| `aiofiles` | 运行时 | `std::fs`(标准库) | 0 | 0 |
| `pyyaml` | 运行时 | `serde + serde_json`(JSON 替代 YAML) | 0(已在 fastshell 中) | 0 |
| `platformdirs` | 运行时 | 显式路径传入(不需要) | 0 | 0 |
| `ripgrep` | 可选 | fastshell 内置 `grep` + 系统 `rg` | 0 | 0 |
| `tiktoken` | 可选 | 字符估算(4:1) | 0 | 0 |
| `desktop[pyyaml]` | 可选 | JSON + env | 0 | 0 |
| `pytest*` 等 | 开发 | `cargo test` | 0 | 0 |
| `black` | 开发 | `cargo fmt` | 0 | 0 |
| `mypy` | 开发 | Rust 编译器 + `cargo clippy` | 0 | 0 |
| `hypothesis` | 开发 | 录制 SSE fixture(等效覆盖) | 0 | 0 |
| — | 开发 | `tiny_http`(mock LLM server) | 1 dev-only | 0(release) |
**核心数字**:
- Python: 16 个运行时+开发+可选依赖
- Rust: **7 个依赖**(其中 6 个与 fastshell 共享,1 个 base64 极轻量),+ 1 dev-only
- 增量二进制体积: ~1.5MB(release staticlib strip 后更小)
- 零新增 runtime 依赖(全部通过 fastshell 复用)