1. 项目背景与重构动机
作为SkillLite项目的核心开发者,我想分享我们最近完成的一次重大架构演进——将Python Agent模块全面迁移到Rust实现。这次重构不是简单的语言替换,而是基于项目长期发展需求做出的战略性决策。
1.1 初始架构的痛点分析
我们的初始架构设计于2025年,采用Rust实现沙箱层,Python实现Agent层。这种设计在当时看来非常合理:
- Rust沙箱提供了系统级的安全隔离和性能保证
- Python Agent层则充分利用了Python的快速开发特性和丰富的AI生态
但随着项目发展,这种架构逐渐暴露出两个核心问题:
跨语言集成困境
每个需要集成的语言(Go、TypeScript等)都不得不重复实现Agent的核心逻辑。这不仅增加了维护成本,还可能导致不同语言实现之间的行为差异。想象一下,当我们需要修复一个Agent逻辑的bug时,必须在Python、Go、TypeScript等多个代码库中同步修改,这种工作模式显然不可持续。
边缘设备适配挑战
Python在资源受限环境中的表现不尽如人意:
- 内存占用:Python解释器加上依赖库通常需要50-100MB内存
- 启动速度:冷启动时间在1-3秒左右
- 能耗表现:CPU利用率不理想,影响设备续航
1.2 重构的核心目标
基于这些痛点,我们设定了明确的改造目标:
- 统一执行引擎:消除跨语言重复实现,确保行为一致性
- 边缘设备优化:降低内存占用,提升启动速度和运行效率
- 保持生态兼容:确保现有Python/Go SDK用户的无缝迁移
2. 技术选型与架构设计
2.1 为什么选择Rust?
在评估了多种方案后,我们最终选择了Rust作为统一实现语言,主要基于以下考量:
技术一致性优势
- 沙箱层已经是Rust实现,使用相同语言可以简化系统集成
- 统一的内存管理和错误处理机制
- 一致的构建和部署流程
跨语言集成能力
- Rust可以编译为C ABI兼容的静态/动态库
- 通过FFI(外部函数接口)支持各种语言的调用
- WebAssembly支持可实现浏览器环境运行
性能与资源效率
- 无GC(垃圾回收)机制,内存使用精确可控
- 编译为本地代码,执行效率高
- 启动速度快,适合边缘设备场景
2.2 新架构设计
新的架构采用了分层设计思想:
code复制┌─────────────────────────────────┐
│ 轻量级语言SDK层 │
│ (Python/Go/TypeScript等) │
├─────────────────────────────────┤
│ Rust统一执行引擎层 │
│ (Agent核心逻辑+沙箱) │
└─────────────────────────────────┘
这种设计的关键在于:
- 核心下沉:将所有业务逻辑集中到Rust层实现
- SDK轻量化:各语言SDK仅保留最薄的封装层
- 接口标准化:通过明确的协议定义跨语言交互
3. 核心模块实现细节
3.1 内存管理模块
参考OpenClaw的设计,我们实现了支持多后端的内存管理系统:
rust复制// 内存条目数据结构
#[derive(Serialize, Deserialize)]
pub struct MemoryEntry {
pub id: String,
pub timestamp: u64,
pub content: String,
pub metadata: HashMap<String, String>,
pub embedding: Option<Vec<f32>>, // 用于语义搜索
}
// 内存存储接口
pub trait MemoryBackend: Send + Sync {
async fn add(&mut self, entry: MemoryEntry) -> Result<()>;
async fn search(&self, query: &str, limit: usize) -> Result<Vec<MemoryEntry>>;
async fn snapshot(&self, path: &Path) -> Result<()>;
}
// 实现内存淘汰策略
impl MemoryStore {
pub async fn add_with_eviction(&mut self, entry: MemoryEntry) -> Result<()> {
if self.backend.count().await? >= self.capacity {
let oldest = self.backend.get_oldest().await?;
self.backend.remove(&oldest.id).await?;
}
self.backend.add(entry).await
}
}
设计要点:
- 支持内存、文件系统等多种存储后端
- 实现LRU(最近最少使用)自动淘汰机制
- 内置序列化/反序列化能力
- 线程安全设计(Send + Sync)
3.2 任务规划引擎
任务规划模块借鉴了OpenCode的设计理念:
rust复制pub enum PlanStep {
Action {
name: String,
params: HashMap<String, Value>,
retry_policy: RetryPolicy,
},
Condition {
expr: String,
true_branch: Vec<PlanStep>,
false_branch: Vec<PlanStep>,
},
Parallel {
steps: Vec<PlanStep>,
max_concurrency: usize,
},
}
pub struct PlanExecutor {
pub async fn execute(&self, plan: &Plan) -> Result<ExecutionResult> {
let mut ctx = ExecutionContext::new();
for step in &plan.steps {
match step {
PlanStep::Action { name, params, retry_policy } => {
self.execute_action(name, params, retry_policy, &mut ctx).await?;
}
// 其他步骤类型处理...
}
}
Ok(ctx.into_result())
}
}
关键特性:
- 支持条件分支和并行执行
- 内置重试机制和超时控制
- 执行上下文隔离
- 详细的执行日志记录
4. 跨语言集成方案
4.1 C ABI接口设计
为了实现多语言调用,我们设计了一组简洁的C接口:
rust复制// Rust侧导出函数
#[no_mangle]
pub extern "C" fn skillite_execute(
config_json: *const c_char,
request_json: *const c_char,
) -> *mut c_char {
let config_str = unsafe { CStr::from_ptr(config_json) };
let request_str = unsafe { CStr::from_ptr(request_json) };
// 处理请求并返回结果
let result = match process_request(config_str, request_str) {
Ok(r) => r,
Err(e) => format!("{{\"error\":\"{}\"}}", e),
};
// 返回堆分配的字符串
let c_string = CString::new(result).unwrap();
c_string.into_raw()
}
// 配套的内存释放函数
#[no_mangle]
pub extern "C" fn skillite_free_string(s: *mut c_char) {
unsafe {
if s.is_null() { return; }
CString::from_raw(s)
};
}
4.2 Python SDK实现示例
Python SDK现在只需要做简单的封装:
python复制class Skillite:
def __init__(self, config=None):
self._lib = ctypes.CDLL("libskillite.so")
self._lib.skillite_execute.argtypes = [
ctypes.c_char_p, ctypes.c_char_p
]
self._lib.skillite_execute.restype = ctypes.c_void_p
self._lib.skillite_free_string.argtypes = [ctypes.c_void_p]
def execute(self, task, **params):
request = {"task": task, "params": params}
request_json = json.dumps(request).encode('utf-8')
result_ptr = self._lib.skillite_execute(
self._config_ptr,
request_json
)
result_json = ctypes.string_at(result_ptr).decode('utf-8')
self._lib.skillite_free_string(result_ptr)
return json.loads(result_json)
优化点:
- 内存管理:确保Rust分配的字符串被正确释放
- 错误处理:将Rust错误转换为Python异常
- 类型转换:自动处理JSON序列化/反序列化
5. 性能优化实践
5.1 内存池技术
为了减少内存分配开销,我们实现了对象池:
rust复制pub struct MemoryPool<T> {
objects: Vec<Arc<T>>,
max_size: usize,
}
impl<T: Default> MemoryPool<T> {
pub fn get(&mut self) -> Arc<T> {
if let Some(obj) = self.objects.pop() {
obj
} else {
Arc::new(T::default())
}
}
pub fn recycle(&mut self, obj: Arc<T>) {
if self.objects.len() < self.max_size {
self.objects.push(obj);
}
}
}
使用场景:
- 高频创建/销毁的对象
- 固定大小的缓冲区
- 线程间共享的只读数据
5.2 异步执行优化
针对IO密集型任务,我们优化了异步运行时:
rust复制pub async fn execute_parallel<I, T, F, Fut>(
items: I,
concurrency: usize,
f: F,
) -> Vec<T>
where
I: IntoIterator<Item = T>,
F: Fn(T) -> Fut,
Fut: Future<Output = T>,
{
let semaphore = Arc::new(Semaphore::new(concurrency));
let mut tasks = Vec::new();
for item in items {
let permit = semaphore.clone().acquire_owned().await.unwrap();
tasks.push(tokio::spawn(async move {
let result = f(item).await;
drop(permit);
result
}));
}
let mut results = Vec::new();
for task in tasks {
results.push(task.await.unwrap());
}
results
}
优化效果:
- 并发控制精确
- 资源利用率高
- 避免任务饥饿
6. 实际效果与经验总结
6.1 性能对比数据
我们在多种环境下进行了基准测试:
| 测试场景 | Python实现 | Rust实现 | 提升幅度 |
|---|---|---|---|
| 内存占用 | 85MB | 12MB | 86%↓ |
| 冷启动时间 | 1800ms | 200ms | 9倍↑ |
| 请求吞吐量 | 100 QPS | 450 QPS | 4.5倍↑ |
| 树莓派CPU使用率 | 45% | 18% | 60%↓ |
6.2 关键经验总结
值得推荐的做法:
- 渐进式迁移:先迁移核心模块,再逐步扩大范围
- 接口先行:明确定义跨语言接口规范
- 性能剖析:使用flamegraph等工具定位热点
- 错误处理:建立统一的错误传递机制
需要避免的陷阱:
- 过度优化:不要过早优化非关键路径
- 忽视ABI稳定性:跨语言接口要保持向后兼容
- 忽略构建时间:大型Rust项目编译时间可能较长
- 线程模型不匹配:注意不同语言的线程模型差异
7. 未来发展方向
7.1 WebAssembly支持
我们正在探索将核心模块编译为WASM:
rust复制#[wasm_bindgen]
pub struct SkilliteWasm {
inner: UnifiedAgent,
}
#[wasm_bindgen]
impl SkilliteWasm {
#[wasm_bindgen(constructor)]
pub fn new(config: JsValue) -> Result<SkilliteWasm, JsValue> {
let config: Config = config.into_serde().map_err(to_js_error)?;
let inner = UnifiedAgent::new(config).map_err(to_js_error)?;
Ok(Self { inner })
}
pub async fn execute(&self, task: String) -> Result<JsValue, JsValue> {
let result = self.inner.execute(task).await.map_err(to_js_error)?;
JsValue::from_serde(&result).map_err(to_js_error)
}
}
预期收益:
- 浏览器环境直接运行
- 更好的沙箱隔离
- 与JavaScript生态无缝集成
7.2 分布式执行架构
我们计划实现的分布式执行模型:
code复制 ┌───────────────┐
│ 调度服务 │
└──────┬───────┘
│
┌─────────────────┼─────────────────┐
│ │ │
┌───────▼───────┐ ┌───────▼───────┐ ┌───────▼───────┐
│ 边缘节点1 │ │ 边缘节点2 │ │ 边缘节点3 │
│ (Rust Agent) │ │ (Rust Agent) │ │ (Rust Agent) │
└───────────────┘ └───────────────┘ └───────────────┘
核心设计:
- 基于gRPC的轻量级通信
- 任务分片与结果聚合
- 容错与重试机制
- 资源感知调度
这次架构演进给项目带来了质的提升,不仅解决了当前的技术债务,还为未来的扩展奠定了坚实基础。对于考虑类似重构的团队,我的建议是:明确目标、小步快跑、充分测试,让每次架构调整都带来真正的价值提升。