1. 引言

在最近的一个 AI Agent 项目中,我陆续编写了 10 个不同的 Agent Skill。每个 Skill 都负责一个独立的业务能力,比如查询天气、发送邮件、调用内部 API、解析文档等。写第一个 Skill 时很顺畅,写第二个也还行,但写到第五个时,我发现自己陷入了 Ctrl+C / Ctrl+V 的泥潭。

每个 Skill 都包含几乎相同的模板代码:参数校验、错误处理、日志记录、结果格式化……10 个 Skill 下来,光这些重复的“骨架”代码就堆了将近 300 行。这不仅让代码库变得臃肿,更可怕的是——每次要修改公共逻辑(比如统一日志格式),就得手动改 10 个文件。

于是,我决定重构。最终,通过抽象公共基类和装饰器模式,我把这 300 行重复代码压缩到了 30 行。这篇文章就来分享我是怎么做到的。

2. 问题重现:一个典型的 Agent Skill 长什么样

先看一个典型的 Skill 实现。假设我们有一个 WeatherSkill,用于查询天气:

class WeatherSkill:
    def __init__(self, name: str = "weather"):
        self.name = name
        self.logger = logging.getLogger(self.name)

    def can_handle(self, intent: str) -> bool:
        return intent == "query_weather"

    def execute(self, params: dict) -> dict:
        # 参数校验
        if "city" not in params:
            return {"success": False, "error": "Missing city parameter"}
        
        # 业务逻辑
        try:
            result = self._query_weather(params["city"])
            # 结果格式化
            return {"success": True, "data": result}
        except Exception as e:
            self.logger.error(f"Failed to query weather: {e}")
            return {"success": False, "error": str(e)}

    def _query_weather(self, city: str) -> dict:
        # 实际的 API 调用
        return {"temperature": 25, "condition": "sunny"}

看起来还不错?但当你写出第 2 个、第 3 个 Skill 时,你会发现 __init__can_handleexecute 里的参数校验、异常捕获、日志记录、返回格式——这些代码几乎一模一样。

3. 重复代码的统计与分析

我整理了 10 个 Skill 的代码,统计了重复部分:

重复模块 每个 Skill 行数 10 个 Skill 总行数
__init__ 初始化 3 行 30 行
can_handle 意图匹配 3 行 30 行
execute 参数校验 5 行 50 行
execute try-except 包裹 6 行 60 行
日志记录 4 行 40 行
结果格式化 3 行 30 行
其他(类型注解、文档字符串等) 6 行 60 行
合计 30 行 300 行

核心业务逻辑(真正的 API 调用、数据处理)只占每个 Skill 的 20% 左右。80% 的代码都是“基础设施”。

4. 重构方案:抽象基类 + 装饰器

我的目标是:让每个 Skill 只写业务逻辑,其余全部由框架自动完成

4.1 定义抽象基类

首先,创建一个 BaseSkill,把公共逻辑全部收进来:

import logging
from abc import ABC, abstractmethod
from functools import wraps

class BaseSkill(ABC):
    def __init__(self, name: str = None):
        self.name = name or self.__class__.__name__.lower()
        self.logger = logging.getLogger(self.name)

    def can_handle(self, intent: str) -> bool:
        # 默认通过类名匹配,子类可覆盖
        return intent == self.name

    def execute(self, params: dict) -> dict:
        # 统一的执行入口,自动包裹校验和异常处理
        try:
            self._validate(params)
            result = self._execute(params)
            return self._format_result(result)
        except Exception as e:
            self.logger.error(f"Execution failed: {e}", exc_info=True)
            return {"success": False, "error": str(e)}

    def _validate(self, params: dict):
        """子类可重写,默认不做校验"""
        pass

    @abstractmethod
    def _execute(self, params: dict) -> dict:
        """子类必须实现:真正的业务逻辑"""
        pass

    def _format_result(self, result: dict) -> dict:
        """子类可重写,默认包装为成功响应"""
        return {"success": True, "data": result}

4.2 用装饰器处理参数校验

对于需要参数校验的 Skill,我写了一个装饰器,避免在每个 _execute 里写 if-else:

def require_params(*required_keys):
    def decorator(func):
        @wraps(func)
        def wrapper(self, params: dict, *args, **kwargs):
            missing = [k for k in required_keys if k not in params]
            if missing:
                raise ValueError(f"Missing required parameters: {missing}")
            return func(self, params, *args, **kwargs)
        return wrapper
    return decorator

4.3 重构后的 Skill 长什么样

现在,一个 Skill 只需要 3 行核心代码:

class WeatherSkill(BaseSkill):
    @require_params("city")
    def _execute(self, params: dict) -> dict:
        return {"temperature": 25, "condition": "sunny"}

另一个 Skill 也类似:

class EmailSkill(BaseSkill):
    def __init__(self):
        super().__init__(name="send_email")

    @require_params("to", "subject", "body")
    def _execute(self, params: dict) -> dict:
        # 真正的发邮件逻辑
        return {"message_id": "12345", "status": "sent"}

5. 效果对比

重构前后对比:

指标 重构前 重构后
每个 Skill 平均代码行数 30 行 3 行
10 个 Skill 总行数 300 行 30 行
公共逻辑修改 改 10 个文件 改 1 个基类
新增一个 Skill 成本 30 行模板 + 业务 3 行业务
单元测试覆盖 难(模板代码干扰) 易(只测业务)

更重要的是,可维护性得到了质的提升。后来我又加了 5 个 Skill,每个只花了 5 分钟写业务逻辑,再也不用复制粘贴了。

6. 总结与思考

这次重构让我深刻体会到:

  1. 重复代码是万恶之源。当你在写第 3 个相似的类/函数时,就该停下来思考抽象方案了。
  2. 抽象基类 + 装饰器是消除 Agent Skill 模板代码的绝佳组合。基类负责骨架,装饰器负责横切关注点(参数校验、权限检查等)。
  3. 不要过度抽象。我的原则是:只有出现 3 次以上重复时,才考虑抽象。过早抽象反而会增加理解成本。
  4. 代码行数不是唯一目标。虽然从 300 行压到了 30 行,但更重要的是代码的可读性可维护性。30 行清晰的代码,远胜 300 行重复的代码。

如果你也在写 Agent 或类似的插件化系统,不妨试试这个模式。你会发现,写代码可以是一件很清爽的事。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐