借鉴 OpenClaw 等框架思路:构建核心模块与扩展模块分离的轻量级 Agent 框架
本文分享如何借鉴 OpenClaw、LangChain 等开源框架的架构设计思想,在 SkillLite 项目中实现核心模块轻量化与扩展模块可插拔,显著提升系统的可扩展性与可维护性。
本文分享如何借鉴 OpenClaw、LangChain 等开源框架的架构设计思想,在 SkillLite 项目中实现核心模块轻量化与扩展模块可插拔,提升系统的可扩展性与可维护性。
一、背景与动机
在构建 AI Agent 执行引擎时,我们面临一个经典问题:如何在不牺牲灵活性的前提下,保持核心框架的简洁与稳定?
早期版本中,所有工具(文件操作、记忆检索、命令执行等)的逻辑都硬编码在单一模块里,通过冗长的 if-else 进行路由。这带来几个痛点:
-
核心臃肿:每增加一个工具,都要修改核心逻辑,违反开闭原则
-
难以测试:工具与编排逻辑耦合,单元测试成本高
-
扩展困难:第三方想接入自定义工具,缺乏统一入口
在研究了 OpenClaw 的三层架构(Gateway / Channel / LLM)及其 Provider 插件系统、Channel 适配器模式 后,我们决定将类似思想引入 SkillLite:核心只负责编排与注册,具体能力由扩展模块按需注入。
二、OpenClaw 等框架的核心设计启示
2.1 OpenClaw 的三层架构
OpenClaw 将系统分为三层,每层职责清晰:
| 层级 | 职责 | 扩展方式 |
|------|------|----------|
| Gateway | 会话管理、消息调度、权限控制 | 核心稳定,极少改动 |
| Channel | 平台适配(WhatsApp、Telegram 等)、消息路由 | 新增平台 = 新增 Channel 适配器 |
| LLM | 模型接口、Tool Calling、流式响应 | Provider 插件系统,注册即用 |
关键启示:分层 + 注册表。核心层不关心具体实现,只通过接口与注册表与扩展交互。
2.2 Provider 插件系统的精髓
OpenClaw 的 LLM Provider 从硬编码 if-else 演进为插件系统:
// 旧设计:每加一个模型都要改核心代码
if (config.provider === 'anthropic')
return new AnthropicClient();
else if (config.provider === 'openai')
return new OpenAIClient();
// 新设计:统一接口 + 注册表
interface LLMProvider {
name: string;
chat(messages, options): AsyncIterator;
supportTools(): boolean;
}
registry.register(new AnthropicProvider());
registry.register(new OpenAIProvider());
核心思想:定义统一接口,扩展通过注册表注入,核心代码零修改。
2.3 Channel 适配器模式
不同平台的消息格式各异,OpenClaw 通过 Adapter 模式 统一为 StandardMessage,Gateway 和 LLM 层只处理标准格式。新增平台只需实现 adaptMessage() 和 sendMessage()。
三、SkillLite 的架构设计
3.1 两层架构:系统层 + 用户层
我们将 SkillLite 抽象为两层:
┌─────────────────────────────────────────┐
│ System Layer (系统层) │
│ - ToolRegistry (工具注册表) │
│ - 内置工具按模块注册 (file/memory/command) │
│ - 直接访问 workspace │
└──────────────────┬──────────────────────┘
│
▼
┌──────────────────────────────────────────┐
│ User Layer (用户层) │
│ - Skills(可执行,作为工具供 LLM 调用) │
│ - 遵循 AgentSkills 协议 │
└──────────────────────────────────────────┘
-
系统层:提供 ToolRegistry 与内置扩展,负责编排与调度
-
用户层:Skills 作为可执行工具,与内置工具一起供 LLM 调用
3.2 核心模块:纯框架层,零内置工具
核心模块 skilllite/core/ 只包含:
-
ToolRegistry:工具注册、查找、执行
-
AgenticLoop:LLM 调用与工具循环
-
ChatSession:会话、转录、记忆集成
-
PromptBuilder:提示词构建
关键设计:核心层不实现任何具体工具,只定义 register(name, schema, handler) 接口。所有内置工具都来自 extensions/。
3.3 ToolRegistry:借鉴 OpenClaw 的注册表思想
class ToolRegistry:
"""工具注册表 - 统一注册与查找,替代硬编码 if-else 路由"""
def register(self, name: str, schema: Dict, handler: Callable) -> None:
"""注册工具:名称、OpenAI 格式 schema、执行 handler"""
self._tools[name] = ToolEntry(schema=schema, handler=handler)
def get(self, name: str) -> Optional[ToolEntry]:
"""按名称查找"""
return self._tools.get(name)
def execute(self, tool_name: str, tool_input: Dict) -> str:
"""统一执行入口"""
entry = self.get(tool_name)
if not entry:
return f"Error: No executor for tool: {tool_name}"
return entry.handler(tool_input)
执行时只需 ToolRegistry.get(tool_name) 查找,与工具来源(file/memory/command/skill)无关。这与 OpenClaw 的 ProviderRegistry 思路一致。
四、扩展模块:按功能拆分的可插拔设计
4.1 extensions 目录结构
skilllite/
├── core/ # 纯框架层(无内置工具实现)
│ ├── tool_registry.py # ToolRegistry 实现
│ ├── loops.py # AgenticLoop,从 extensions.long_text 引入长文本处理
│ └── chat_session.py # 会话编排
├── extensions/ # 内置扩展(集中维护,通过 register_extensions 引入)
│ ├── __init__.py # register_extensions() 统一注册
│ ├── file.py # read_file, write_file, list_directory, file_exists
│ ├── command.py # run_command, preview_server
│ ├── memory.py # memory_search, memory_write, memory_list
│ └── long_text.py # 长文本截断与分段总结
└── builtin_tools.py # 底层实现(file/command 工具逻辑)
每个扩展模块只负责一类工具,通过 register(registry, ctx, executor) 向 ToolRegistry 注册。
4.2 统一注册入口
def register_extensions(
registry,
ctx: ExtensionsContext,
enable_file_tools: bool = True,
enable_memory_tools: bool = True,
enable_command_tools: bool = True,
) -> None:
"""按需注册扩展工具到 registry"""
if enable_file_tools:
file_tools.register(registry, ctx, executor)
if enable_command_tools:
command_tools.register(registry, ctx, executor)
if enable_memory_tools:
memory_tools.register(registry, ctx)
可配置开关:用户可选择性启用/禁用某类工具,进一步减轻核心负担。
4.3 扩展模块示例:file 与 long_text
file.py:只负责注册,逻辑委托给 builtin_tools
def register(registry, ctx, executor=None) -> None:
for tool_def in get_file_tools():
name = tool_def.get("function", {}).get("name", "")
if name:
registry.register(name, tool_def, executor)
long_text.py:独立的长文本处理策略,被 AgenticLoop 按需引入
-
策略:head + tail(文章首尾最重要)
-
超长工具结果自动分段总结,避免 context overflow
核心的 loops.py 只 from ..extensions.long_text import summarize_long_content,不关心具体实现细节。
五、核心轻量化与可扩展性收益
5.1 核心轻量化
| 维度 | 改进前 | 改进后 |
|------|--------|--------|
| 工具路由 | 硬编码 if-else,每加工具改核心 | ToolRegistry 统一查找,核心零修改 |
| 内置工具 | 散落在 chat_session 等模块 | 集中在 extensions/,按模块注册 |
| 依赖方向 | 核心依赖具体工具实现 | 核心只依赖 ToolRegistry 接口,extensions 依赖 core |
核心代码行数减少,职责更单一,符合「高内聚、低耦合」。
5.2 可扩展性提升
| 场景 | 实现方式 |
|------|----------|
| 新增内置工具 | 新建 extensions/xxx.py,实现 register(),在 register_extensions 中调用 |
| 第三方自定义工具 | 直接 registry.register(name, schema, handler),无需改 core |
| 禁用某类工具 | register_extensions(..., enable_command_tools=False) |
| 长文本策略可配置 | long_text 参数可经 env 或 SkillManager 注入(待实施) |
5.3 与 OpenClaw 的对照
| 设计点 | OpenClaw | SkillLite |
|--------|----------|-----------|
| 注册表 | ProviderRegistry、Channel 注册 | ToolRegistry |
| 适配器 | Channel 适配不同平台消息格式 | 扩展模块适配不同工具类型 |
| 分层 | Gateway / Channel / LLM | System Layer / User Layer + core / extensions |
| 插件化 | Provider 实现接口即注册 | 扩展实现 register() 即注册 |
六、实施经验与后续规划
6.1 已完成的架构优化
-
mcp/server.py 拆分为 handlers/security、handlers/executor、tools
-
init_deps.py 拆分为 audit、resolve、lock 子模块
-
ToolRegistry + extensions 替代硬编码工具路由
6.2 待实施的扩展增强
根据 FINAL_DESIGN.md,计划引入 ExtensionRegistry + enhance 模式,支持 Skills 扩展系统工具,进一步强化可扩展性。
6.3 架构健康度自评
| 维度 | 评分 | 说明 |
|------|------|------|
| 模块边界 | 4/5 | core 与 cli、extensions 分离清晰 |
| 职责单一 | 4/5 | ToolRegistry、AgenticLoop、ChatSession 分工明确 |
| 可扩展性 | 4/5 | 适配器模式、custom_tools、按需注册支持良好 |
七、总结
借鉴 OpenClaw 等框架的分层架构、注册表模式和适配器模式,我们在 SkillLite 中实现了:
-
核心轻量化:core 只负责编排与注册,不实现具体工具
-
扩展可插拔:extensions 按功能拆分,通过
register_extensions统一注入 -
统一执行入口:ToolRegistry 提供
get/execute,与工具来源无关 -
可配置开关:按需启用/禁用某类扩展,减少不必要的依赖
这种设计让新增工具、接入第三方能力、调整策略都无需改动核心代码,显著提升了系统的可维护性与可扩展性。如果你也在构建 Agent 框架,不妨参考 OpenClaw 的架构思想,从「注册表 + 分层」入手,让核心保持稳定,让扩展自由生长。
更多推荐
所有评论(0)