模板方法模式实战:重构Agent工具审批,告别重复代码

设计模式-模板方法实战

什么是模板方法

在软件设计模式中,模板方法设计模式是一种行为型设计模式,其核心思想是在抽象类中定义一个算法的骨架(即模板),并将某些具体的实现步骤延迟到子类去完成

实战概览

在自己编写的agent框架中实现工具调用需要审批

为什么做这篇

在学习设计模式时,应当结合实战。不久前刚学到的模板方法设计模式,今天在搓自己的agent框架实现功能------工具调用需要审批时刚好用得上

代码实战

未使用模板方法的缺陷

要实现的功能:工具调用需要审批

tool_registry.py中,调用的逻辑伪代码如下

python 复制代码
# 触发审批功能
approved = self.approval_tool.approve(tool, parameters, tool_call_id)
# 根据审批结果,调用对应的功能
if not approved:
	return '未通过审批!'
#开始调用工具
tool.run(parameters, tool_call_id)
# ...省略

根据要实现的功能,不难得出在调用Tool前需要触发审批的逻辑,由于审批的实现有多种,比如可以实现以下审批:

  1. 交互式命令行审批
  2. Web界面审批
  3. AI辅助审批

鉴于有多种实现,我们需要先创建一个抽象类,让子类来实现对应的审批逻辑,上述代码块approval_tool是对应的实现类对象

python 复制代码
class ApprovalTool(ABC):
    """审批工具基类"""
    
    @abstractmethod
    def approve(self, tool: Tool, parameters: Dict[str, Any], tool_call_id: str) -> bool:
        """具体的审批逻辑,由子类实现
        
        Args:
            tool (Tool): 被调用的工具实例
            parameters (Dict[str, Any]): 工具参数
            tool_call_id (str): 工具调用ID
        """
        pass

然后让子类去实现该抽象方法,例子如下(交互式命令行审批):

python 复制代码
class DefaultApprovalTool(ApprovalTool):
    """默认审批工具"""
    def approve(self, tool: Tool, parameters: Dict[str, Any], tool_call_id: str) -> bool:
        max_attempts = self.max_attempts
        while max_attempts > 0:
            is_approved = input(f"{prompt}\n是否批准该工具调用?(y/n): ")
            if is_approved.lower() == "y":
                return True
            elif is_approved.lower() == "n":
                return False
            else:
                print(f"无效输入,请输入 'y' 或 'n'\n 剩余尝试次数: {max_attempts - 1}")
                max_attempts -= 1
        print("超过最大尝试次数,默认拒绝该工具调用")
        return False

至此,一个审批工具实现类便以完成,伪代码使用示例如下(approval_tool为对应的实现类对象):

python 复制代码
# 触发审批功能
approved = self.approval_tool.approve(tool, parameters, tool_call_id)
# 根据审批结果,调用对应的功能
if not approved:
	return '未通过审批!'
#开始调用工具
tool.run(parameters, tool_call_id)
# ...省略

缺陷展现

在完成该功能后,假设想为工具审批 添加自动批准指定工具、需要审批的指定工具 功能,那么这些逻辑都需要在实现类中编写,这些功能在代码编写上都是相似的,每个实现类都要为实现这些功能编写重复性的代码,这并不利于日后的维护和保持代码简洁优雅

模板方法设计模式可以有效解决上述缺陷

使用模板方法

再次回顾:在软件设计模式中,模板方法设计模式是一种行为型设计模式,其核心思想是在抽象类中定义一个算法的骨架(即模板),并将某些具体的实现步骤延迟到子类去完成

为了解决上述缺陷,我们需要使用模板方法设计模式,现在我们修改ApprovalTool类,将抽象方法改为do_approve()

python 复制代码
class ApprovalTool(ABC):
    """审批工具基类"""
    
    def __init__(self,
                 auto_approve_tools: Optional[List[Type[Tool]]] = None,
                 require_approval_tools: Optional[List[Type[Tool]]] = None,
                 auto_approve_if_no_rules: bool = False
                 ):
        """初始化审批工具
        假设一个tool在auto_approve_tools和require_approval_tools中都存在,则以require_approval_tools为准,即需要审批。
        假设一个tool既不在auto_approve_tools中,也不在require_approval_tools中,则根据auto_approve_if_no_rules的值来决定是否需要审批。

        
        Args:
            auto_approve_tools (Optional[List[Type[Tool]]], optional): 自动批准的工具列表. Defaults to None.
            require_approval_tools (Optional[List[Type[Tool]]], optional): 需要审批的工具列表. Defaults to None.
            auto_approve_if_no_rules (bool, optional): 当工具不在任何列表中时是否自动批准. Defaults to False.
        """
        self.auto_approve_tools = auto_approve_tools or []
        self.require_approval_tools = require_approval_tools or []
        self.auto_approve_if_no_rules = auto_approve_if_no_rules
        
    def approve(self, tool: Tool, parameters: Dict[str, Any], tool_call_id: str) -> bool:
        """
        根据工具类型和审批规则决定是否批准工具调用
        Args:
            tool (Tool): 被调用的工具实例
            parameters (Dict[str, Any]): 工具参数
            tool_call_id (str): 工具调用ID
        Returns:
            bool: 是否批准工具调用
        """
        if type(tool) in self.require_approval_tools:
            return self.do_approve(tool, parameters, tool_call_id)
        if type(tool) in self.auto_approve_tools:
            return True
        if self.auto_approve_if_no_rules == False:
            return self.do_approve(tool, parameters, tool_call_id)
        else:
            return True

    @abstractmethod
    def do_approve(self, tool: Tool, parameters: Dict[str, Any], tool_call_id: str) -> bool:
        """具体的审批逻辑,由子类实现
        
        Args:
            tool (Tool): 被调用的工具实例
            parameters (Dict[str, Any]): 工具参数
            tool_call_id (str): 工具调用ID
        """
        pass

可以看到抽象方法改成了do_approve()自动批准指定工具、需要审批的指定工具的功能 已在approve()实现,使得每个实现类都不用重复性地编写自动批准指定工具、需要审批的指定工具相关代码。我们再来看下子类(交互式命令行审批)代码:

python 复制代码
class DefaultApprovalTool(ApprovalTool):
    """默认审批工具"""

    def __init__(self,
                 auto_approve_tools: Optional[List[Type[Tool]]] = None,
                 require_approval_tools: Optional[List[Type[Tool]]] = None,
                 auto_approve_if_no_rules: bool = False,                  
                 max_attempts: int = 10):
        """初始化审批工具
        假设一个tool在auto_approve_tools和require_approval_tools中都存在,则以require_approval_tools为准,即需要审批。
        假设一个tool既不在auto_approve_tools中,也不在require_approval_tools中,则根据auto_approve_if_no_rules的值来决定是否需要审批。

        
        Args:
            auto_approve_tools (Optional[List[Type[Tool]]], optional): 自动批准的工具列表. Defaults to None.
            require_approval_tools (Optional[List[Type[Tool]]], optional): 需要审批的工具列表. Defaults to None.
            auto_approve_if_no_rules (bool, optional): 当工具不在任何列表中时是否自动批准. Defaults to False.
            max_attempts (int, optional): 最大尝试次数. Defaults to 10.
        """
        super().__init__(auto_approve_tools, require_approval_tools, auto_approve_if_no_rules)
        self.max_attempts = max_attempts

    def do_approve(self, tool: Tool, parameters: Dict[str, Any], tool_call_id: str) -> bool:
        if isinstance(tool, TerminalTool):
            current_dir = tool.current_dir
            prompt = f"{current_dir}> {parameters.get('command', '')}"
        else:
            prompt = f"工具名称: {tool.name}\n工具描述: {tool.description}\n工具参数: {parameters}"
        max_attempts = self.max_attempts
        while max_attempts > 0:
            is_approved = input(f"{prompt}\n是否批准该工具调用?(y/n): ")
            if is_approved.lower() == "y":
                return True
            elif is_approved.lower() == "n":
                return False
            else:
                print(f"无效输入,请输入 'y' 或 'n'\n 剩余尝试次数: {max_attempts - 1}")
                max_attempts -= 1
        print("超过最大尝试次数,默认拒绝该工具调用")
        return False

可以看到子类只关心实现do_approve(),而自动批准指定工具、需要审批的指定工具 不需要子类关心,这种重复的代码已在ApprovalTool类中的approve()方法编写。我们再来看下实际调用

python 复制代码
class ToolRegistry:
    """
    工具注册表,用于管理和调用工具
    """
    def __init__(self, approval_tool: Optional[ApprovalTool] = None):
        self._tools: Dict[str, Tool] = {}
        self.approval_tool = approval_tool

    def execute_tool(self, tool_call: Union[Dict[str, Any], ChatCompletionMessageFunctionToolCall]) -> Message:
        """
        执行工具

        Args:
            tool_call (Union[Dict[str, Any], ChatCompletionMessageFunctionToolCall]): 工具调用信息,llm返回的工具调用格式,包含工具名称、参数等信息

        Returns:
            Message: 工具执行结果封装的Message对象
        """
        
        # ...省略代码
        tool = self._tools[tool_name]
        if self.approval_tool:
            approved = self.approval_tool.approve(tool, parameters, tool_call_id)
            if not approved:
                return Message(role="tool", content=f"❌ 工具调用未通过用户的审批,已被用户拒绝: {tool_name}", tool_call_id=tool_call_id)

        return tool.run(parameters, tool_call_id)

参考文章

模板模式 | 菜鸟教程

相关推荐
2601_962293244 小时前
Python自动化统计团队工作量并生成可视化仪表盘的脚本方案【指导】
python·数据分析·自动化·可视化·仪表盘
2601_962293795 小时前
OCRmyPDF批处理脚本:使用Python自动化复杂OCR任务
python·自动化·批处理脚本·ocrmypdf·ocr任务
是Dream呀6 小时前
一个 AI Agent 是怎么长出来的:提示词、上下文与 Harness 工程
人工智能·大模型·agent
明月_清风6 小时前
MCP vs ACP vs LSP:AI 时代三大协议的「三足鼎立」
人工智能·网络协议·agent
Java后端的Ai之路6 小时前
20、Python - 备忘录模式
开发语言·人工智能·python·外观模式·备忘录模式
2601_962077717 小时前
基于Python与NLP的新闻事件信息抽取实战:从NER到时空标准化
python·nlp·transformers·spacy·新闻事件抽取
JavaPub-rodert7 小时前
LangChain 从入门到 Agent 实战:用 Python 搭建一个真正能调用工具和知识库的 AI 助手
人工智能·python·langchain
郝学胜-神的一滴8 小时前
Qt 高级编程 045:坐标体系深度实战
开发语言·c++·windows·python·qt·程序人生
m0_380743879 小时前
给 OpenAI API 调用加上模型切换:GPT-5.1 和 Codex 的配置实践
人工智能·python·gpt
2601_962297489 小时前
在python3中、下列输出变量a的正确写法是_2020超星大数据Python免费答案
数据结构·python·算法·编程·字符串操作