
1. 项目缘起一个自研Agent调度系统的“甜蜜负担”去年下半年我们团队为了支撑一个内部AI助手项目决定自研一个Agent调度系统。当时的想法很直接市面上的开源框架要么太重要么功能不匹配自己写一个“轻量级”的既能完全掌控又能完美贴合业务。于是我和另一位同事花了大概两周时间吭哧吭哧写出了第一版。核心逻辑并不复杂就是一个基于Python asyncio的事件循环负责接收任务、解析指令、根据指令类型调用不同的工具函数比如查数据库、调用外部API、生成文本最后再把结果组装返回。整个调度器的核心代码大约700行。初期跑起来确实很爽功能完全自定义加个新工具、改个路由逻辑分分钟搞定。但随着业务场景增多这个“亲儿子”开始暴露出越来越多的问题。首先是可维护性为了处理各种边界情况比如工具调用超时、依赖的工具服务宕机、结果格式校验代码里塞满了if-else和try-except逻辑分支变得极其复杂。其次是扩展性每增加一个新的Agent能力我们称之为一个Skill就需要手动去修改调度器的路由映射和初始化逻辑还要小心翼翼地处理可能存在的依赖冲突。最后是心智负担任何关于Agent执行流程的改动比如想加入一个统一的权限校验层或者修改任务的生命周期管理都需要深入这个700行的“泥潭”里小心翼翼地修改测试成本极高。这个自研调度器就像一个手工打造的精密瑞士军刀刚开始用着顺手但当你需要它变成一套标准化、可批量生产的厨房刀具时它就力不从心了。我们意识到继续投入时间在这个“轮子”上边际效益越来越低而技术债务却在快速累积。是时候看看“外面的世界”了。2. 技术选型为什么最终选择了OpenClaw当我们决定寻找替代方案时目标很明确需要一个足够轻量、架构清晰、专注于调度与编排的开源Agent框架而不是一个全家桶式的AI应用平台。我们对比了几个当时热门的选项包括LangChain、AutoGPT的一些衍生框架以及一些新兴的轻量级项目。LangChain的功能无疑是最全面的但其设计哲学是“大而全”抽象层级较高。对于我们的核心需求——一个纯净、高效的调度中枢——LangChain显得有些臃肿学习曲线也陡峭。我们需要的是调度逻辑的“替换”而不是引入一套全新的、复杂的开发生态。一些新兴的轻量级框架则走向另一个极端它们往往为了极致简洁而牺牲了必要的工程化特性比如缺乏良好的错误处理机制、状态管理简陋或者社区活跃度低遇到问题很难找到解决方案。正是在这个背景下OpenClaw进入了我们的视野。它吸引我的点非常具体极简的核心理念OpenClaw的定位就是“Agent调度与编排框架”。它的核心抽象非常干净Agent执行单元、Skill能力单元、Scheduler调度器和Context执行上下文。这种设计与我们自研系统的核心思想不谋而合但实现得更加优雅和规范。清晰的执行流水线OpenClaw明确地将一次Agent执行分解为Parse指令解析、Plan任务规划、Execute技能执行、Handle结果处理等阶段。每个阶段都是可插拔的组件。这种流水线模型让我们能清晰地看到任务的生命周期便于调试和定制。原生的异步支持基于asyncio构建与现代Python异步生态无缝集成这对于需要同时调度多个I/O密集型Agent任务的场景至关重要。活跃的中文社区与接地气的文档作为国内团队主导的项目其文档和社区讨论更贴近我们的实际开发场景遇到问题时能够更快地获得反馈和思路。决策的关键一击来自于一个简单的对比我用OpenClaw重新实现了我们最核心的一个业务流程——一个包含用户意图识别、多轮对话状态管理、以及调用三个不同外部工具的组合Agent。用OpenClaw的实现只用了72行代码就清晰、结构化地替代了原来散落在调度器和各个工具函数中的近200行胶水代码。这72行代码主要就是定义Skill、配置Agent、并描述执行流程业务逻辑一目了然。代码量的急剧缩减和结构的清晰化让我们立刻看到了迁移的价值。3. 迁移实战从700行“面条代码”到72行声明式配置迁移过程并非简单的“查找替换”而是一次对原有业务逻辑的重新审视和结构化重构。我们的自研调度器代码700行大致可以分解为以下几个混乱的模块模块A (约150行)主事件循环和任务队列管理混杂着日志初始化。模块B (约200行)一个庞大的指令解析函数用正则和字符串匹配硬编码了数十种指令模式。模块C (约180行)工具路由与执行器里面是长长的if-elif链每个分支处理不同的工具调用、错误处理和结果格式化。模块D (约170行)状态管理、上下文存储和响应组装逻辑与前面几个模块深度耦合。我们的迁移策略是“分而治之各个击破”。3.1 第一步梳理与解耦——将“技能”抽象出来首先我们不再将业务能力视为散乱的“工具函数”而是按照OpenClaw的范式将其抽象为独立的Skill。这是一个关键的思维转变。以前自研系统中的一个工具函数:async def query_user_data(user_id: str, query_type: str): 一个混杂的函数查数据库、处理异常、格式化结果 try: if query_type “profile”: data await db.fetch_user_profile(user_id) return {“success”: True, “data”: data, “template”: “profile_v1”} elif query_type “order”: data await db.fetch_user_orders(user_id) # 这里可能还嵌入了某种业务逻辑计算 processed_data some_business_logic(data) return {“success”: True, “data”: processed_data, “template”: “order_list”} else: return {“success”: False, “error”: “Unknown query type”} except DatabaseError as e: log.error(f“Database error for user {user_id}: {e}”) return {“success”: False, “error”: “Service unavailable”}这个函数承担了太多职责路由判断、数据获取、业务处理、错误捕获、返回结构定义。它在调度器中被直接调用两者紧密绑定。现在OpenClaw Skill:from openclaw.skill import BaseSkill, SkillMetadata class UserProfileSkill(BaseSkill): “”“查询用户基本信息”“” metadata SkillMetadata(name“user_profile”, description“Get user‘s basic profile information”) async def execute(self, user_id: str): “”“执行技能只关心核心业务逻辑”“” # 专注数据获取 data await db.fetch_user_profile(user_id) return data # 返回原始数据或简单处理后的数据 class UserOrderSkill(BaseSkill): “”“查询用户订单列表”“” metadata SkillMetadata(name“user_order”, description“Get user’s order list”) async def execute(self, user_id: str): data await db.fetch_user_orders(user_id) # 复杂的业务逻辑可以放在这里或者进一步拆分成服务 processed_data some_business_logic(data) return processed_data变化是显著的单一职责每个Skill只做一件事代码更纯粹。依赖注入Skill的依赖如db可以通过框架的上下文Context或初始化参数注入而不是硬编码。错误处理分离Skill内部可以抛出特定异常由OpenClaw框架统一的错误处理中间件来捕获和转换Skill自身不再需要处理返回格式。3.2 第二步重构调度逻辑——从“过程式”到“声明式”这是迁移的核心。我们拆掉了原来那个庞大的、过程式的调度器。以前调度器需要知道所有指令模式并手动调用对应的工具函数还要处理后续流程。现在我们定义一个Agent它由一系列Skill组成并通过Scheduler来管理执行流程。调度逻辑变成了配置。import asyncio from openclaw import Agent, Scheduler, Context from openclaw.skill import SkillSet from my_skills import UserProfileSkill, UserOrderSkill, WeatherSkill, CalculatorSkill # 导入我们定义的Skill # 1. 定义技能集 skills SkillSet() skills.register(UserProfileSkill()) skills.register(UserOrderSkill()) skills.register(WeatherSkill()) skills.register(CalculatorSkill()) # 2. 创建Agent并为其装配技能 my_agent Agent(name“assistant”, skillsskills) # 3. 创建调度器 scheduler Scheduler() # 4. 运行Agent示例 async def main(): context Context(task“What‘s the weather in Beijing and user 123’s profile?”) result await scheduler.run(agentmy_agent, contextcontext) print(result.output) # asyncio.run(main())这短短二十几行代码就完成了原来调度器核心的初始化、注册和启动工作。复杂的指令解析和路由现在可以通过配置OpenClaw的Parser组件例如基于LLM的意图识别或我们自定义的规则解析器来实现这部分逻辑也从调度器主代码中剥离了出去。3.3 第三步处理边界与增强——利用中间件和钩子自研系统中那些散落的错误处理、日志记录、性能监控代码在OpenClaw中可以通过中间件Middleware和钩子Hook来优雅地实现。例如我们需要为所有Skill调用添加统一的超时控制和指标上报from openclaw.middleware import BaseMiddleware import time from statsd import StatsClient # 假设使用StatsD上报指标 class TimeoutAndMetricsMiddleware(BaseMiddleware): “”“超时控制与指标上报中间件”“” def __init__(self, timeout: float 30.0): self.timeout timeout self.statsd StatsClient() async def around_execute(self, skill, context, fn, *args, **kwargs): “”“在技能执行前后加入逻辑”“” skill_name skill.metadata.name start_time time.time() self.statsd.incr(f“skill.{skill_name}.called”) # 调用次数1 try: # 使用asyncio.wait_for实现超时 result await asyncio.wait_for(fn(*args, **kwargs), timeoutself.timeout) duration (time.time() - start_time) * 1000 # 毫秒 self.statsd.timing(f“skill.{skill_name}.duration”, duration) self.statsd.incr(f“skill.{skill_name}.success”) return result except asyncio.TimeoutError: self.statsd.incr(f“skill.{skill_name}.timeout”) raise TimeoutError(f“Skill {skill_name} execution timeout after {self.timeout}s”) except Exception as e: self.statsd.incr(f“skill.{skill_name}.error”) raise # 将异常原样抛出由上层错误处理中间件处理 # 在创建Scheduler时添加中间件 scheduler Scheduler(middlewares[TimeoutAndMetricsMiddleware(timeout10.0)])通过这种方式我们将横切关注点Cross-cutting Concerns从业务逻辑中彻底解耦。以后要加缓存、加鉴权、改日志格式只需要新增或修改中间件而无需触动任何Skill或Agent的核心代码。4. 降本增效不仅仅是代码行数的减少从700行到72行这个直观的数字背后是多项成本的显著下降和效率的全面提升。1. 开发与维护成本骤降上手成本新同事接手Agent相关开发不再需要理解那700行错综复杂的调度逻辑。他只需要学习OpenClaw的几个核心概念Agent, Skill, Scheduler然后阅读我们清晰定义的各个Skill类即可。 onboarding时间从几天缩短到几小时。修改成本添加一个新功能就是创建一个新的Skill类然后在Agent配置里注册一下。完全不需要考虑它如何被调度、错误如何统一处理、日志如何打。这符合“开闭原则”对扩展开放对修改关闭。调试成本OpenClaw清晰的流水线阶段和活跃的社区使得定位问题变得简单。是Parser没识别对意图还是某个Skill抛了异常或者是中间件处理有问题日志和链路追踪可以很清晰地指向问题阶段而不是在700行代码里大海捞针。2. 系统可靠性与可观测性提升统一的错误处理通过全局错误处理中间件所有未捕获的异常都能被以一种一致的方式处理、记录并转化为用户友好的响应。再也不会因为某个工具函数忘了写try-catch而导致整个服务进程崩溃。标准化指标如上例所示通过中间件可以无侵入地为所有Skill调用添加耗时、成功率、调用量等指标监控告警体系瞬间建立起来。链路追踪可以很容易地在中间件中注入TraceID实现单个用户请求在所有Skill调用间的全链路追踪对于排查复杂问题 invaluable。3. 技术架构的标准化与未来性摆脱“锁死”自研系统是一个孤岛其设计和技术栈完全依赖于最初的开发者。迁移到OpenClaw实际上是接轨了一个有社区支持、持续演进的标准范式。我们可以享受到社区带来的新特性、性能优化和安全修复。组件化与复用定义好的Skill可以像乐高积木一样在不同的Agent间复用和组合。今天为“客服助手”写的QueryFAQSkill明天可以直接用在“运维助手”里。这种复用性带来了巨大的长期收益。为复杂编排铺平道路OpenClaw内置的Scheduler和Plan阶段为未来实现更复杂的任务规划如根据条件动态选择Skill、顺序/并行执行多个Skill提供了坚实的基础。如果靠自研系统来实现这些可能又需要增加几百行难以维护的代码。5. 踩坑与适配迁移过程中的关键决策点迁移并非一帆风顺我们也遇到了一些需要权衡和决策的地方。坑点一异步上下文Context的管理自研系统中我们用一个全局字典来传递请求上下文。OpenClaw使用Context对象。这里的关键决策是哪些数据应该放在Context里哪些应该作为Skill的execute参数我们的原则是与单次请求强相关、且多个Skill可能都需要的信息如user_id,session_id,request_id放入Context。而Skill特定的执行参数如查询的city名称、计算的expression则通过execute方法参数传递。这保证了Skill接口的清晰和可测试性。坑点二Skill的粒度设计最初我们倾向于设计“大而全”的Skill比如一个DataQuerySkill处理所有数据查询。但这很快导致了Skill内部逻辑复杂化违背了初衷。我们最终采纳了“单一职责精细粒度”的原则。例如将DataQuerySkill拆分为UserProfileSkill、OrderQuerySkill、ProductSearchSkill等。这样每个Skill更简单、更易测试、复用性也更高。虽然Skill数量变多了但管理起来反而更轻松因为框架帮我们处理了注册和发现。坑点三与现有服务体系的集成我们的很多“工具”本质上是内部 gRPC 或 HTTP 服务的客户端。自研系统里这些客户端实例是全局单例。在OpenClaw中我们通过依赖注入来解决。具体做法是在创建Skill实例时通过构造函数或框架提供的依赖注入机制将这些客户端实例传入。或者更优雅的方式是利用OpenClaw的Context或自定义的Service Container来托管这些共享依赖。# 方式一通过构造函数注入简单直接 class UserProfileSkill(BaseSkill): def __init__(self, user_service_client): self.client user_service_client async def execute(self, user_id: str): return await self.client.get_profile(user_id) # 使用时 skills.register(UserProfileSkill(user_service_clientmy_grpc_client)) # 方式二利用Context存储更灵活 # 可以在一个前置中间件中将client放入context context.set(“user_service_client”, my_grpc_client) # 在Skill中通过context获取 client context.get(“user_service_client”)坑点四自定义解析器Parser的复杂度OpenClaw默认的解析器可能不适合我们已有的指令格式。我们原有的指令系统是基于关键词和规则的。我们面临选择1彻底改用LLM进行意图识别2基于OpenClaw的BaseParser接口实现自己的规则解析器。 考虑到成本和稳定性我们选择了方案二。实现一个自定义Parser大约花了100行代码但它完美复用了原有的规则库并且将解析逻辑从调度器主代码中彻底剥离成为了一个可独立维护和升级的组件。这本身也是一次有价值的架构优化。6. 总结与展望从“成本中心”到“效率平台”这次从700行自研代码迁移到72行OpenClaw配置的实践远不止一次简单的技术栈更换。它是一次将Agent调度能力从“项目特有的成本中心”转变为“团队共享的效率平台”的过程。对于技术管理者它意味着更低的维护成本、更快的功能迭代速度和更高的系统稳定性。团队可以将精力从维护脆弱的“轮子”转移到创造更有价值的业务逻辑Skill上。对于开发者它意味着更清爽的代码、更明确的职责划分和更强大的调试工具。开发体验从“在迷宫中修改电路”变成了“在图纸上组装模块”。对于系统本身它获得了标准化、可观测、可扩展的坚实基础能够从容应对未来更复杂的Agent编排需求。当然OpenClaw并非银弹。它最适合的场景是构建需要清晰调度和编排逻辑的、多技能协作的Agent系统。如果你的需求极其简单或许一个脚本就够了如果你需要构建一个包含复杂记忆、知识库检索和LLM深度集成的全能型Agent可能需要结合LangChain等更重的框架。但对于我们——以及我相信对于很多需要构建轻量级、高性能、易维护Agent系统的团队——OpenClaw在简洁性、设计理念和工程友好度之间找到了一个非常出色的平衡点。迁移完成后我们那700行“祖传代码”已经被归档封存。新的系统以72行核心配置为骨架辅以数十个清晰定义的Skill和几个通用的中间件平稳运行并支撑着越来越多的业务场景。这次“降本”降的不仅是代码行数更是长期的维护心智负担和潜在的故障风险而“增效”则体现在开发效率、系统可靠性和团队协作的方方面面。