Agent 智能体开发(三)Agent 自定义框架搭建

Agent 智能体开发(三)Agent 自定义框架搭建

在上篇文章中,我们已经掌握了基本的 Agent 架构范式。现阶段随着 Agent 技术的不断成熟与落地,行业主要形成了四种主流的 Agent 开发方式:

  • 开源智能体框架: 框架将智能体共有的、重复性的工作(如主会话循环、状态管理、工具调用、记忆与上下文等)进行抽象和封装,从而屏蔽非通用的底层实现,让开发者更专注于业务逻辑。比较经典的框架包括 LlamaIndex、LangChain/LangGraph、AutoGen、AgentScope、CAMEL、DeerFlow2.0(字节)等;
  • 低代码平台: 低代码平台通过图形化、模块化的方式来快速、直观地搭建、调试和部署智能体应用,从而降低了开发门槛。比较主流的平台包括 Coze(字节)、Dify、FastGPT 等;
  • 运行时 SDK: 大模型厂商针对自有 CLI/IDE 中的 Agent 执行引擎进行封装,相比开源框架来说封装程度和定制化程度更高,包含各厂商自有的功能特性,用于开发者在应用中快速集成特定厂商能力。常见的 SDK 包括 Claude Agent SDK(Claude Code)、Qoder Agent SDK(Qoder) 等;
  • 自定义框架: 只使用基础 LLM SDK,自行实现消息管理、工具调用、记忆上下文等能力封装,可基于个人需求灵活定制需求功能。

本文将从零开始,逐步构建一个简单的自定义智能体框架,这实际上是一个从"使用者"向"构建者"转变的过程。这种转变带来的价值是长远的。在实际应用中,不同场景对智能体的需求可能是差异巨大的,往往都需要在通用框架基础上做二次开发。

一、LLM 客户端拓展

拓展当前的 LLM 客户端,将流式输出和非流式输出两种方式分别进行封装,并且流式输出方式支持返回 yield 惰性迭代器,可以由调用方按需处理返回结果。

python 复制代码
"""统一LLM客户端 - 基于OpenAI原生API"""

class BaseLLMClient:
    """
    LLM 客户端
    它用于调用任何兼容OpenAI接口规范的服务,支持使用流式和非流式两种响应模式。
    """
    def __init__(self, model: str=None, apiKey: str=None, baseUrl: str=None, timeout: int=None):
        """
        初始化客户端:优先使用传入参数,如果未提供则从环境变量加载
        """
        self.model = model or os.getenv("LLM_MODEL_ID")
        apiKey = apiKey or os.getenv("LLM_API_KEY") # 不保存敏感配置信息仅用于创建客户端
        baseUrl = baseUrl or os.getenv("LLM_BASE_URL")
        timeout = timeout or int(os.getenv("LLM_TIMEOUT", 60))

        # 任意配置为空则抛出异常
        if not all([self.model, apiKey, baseUrl]):
            raise ValueError("参数异常:模型ID、API密钥或服务地址未定义")

        # 创建 OpenAI 接口规范客户端
        self.client = OpenAI(api_key=apiKey, base_url=baseUrl, timeout=timeout)

    def invoke(self, messages: List[Dict[str, str]], **kwargs) -> str:
        """
        非流式调用LLM,直接返回完整响应。
        """
        print(f"🧠 正在调用 {self.model} 模型...")
        try:
            # 获取模型响应(非流式输出)
            response = self.client.chat.completions.create(
                model=self.model,
                messages=messages,
                temperature=kwargs.get('temperature', 0.7),
                max_tokens=kwargs.get('max_tokens', None),
                **{k: v for k, v in kwargs.items() if k not in ['temperature', 'max_tokens']}
            )
            # 处理模型响应
            result = response.choices[0].message.content
            print(f"✅ 大语言模型响应成功: {result}")
            return result
        except Exception as e:
            print(f"❌ 调用LLM时发生错误: {e}")
            raise LLMException(f"LLM调用失败: {str(e)}")

    def stream_invoke(self, messages: List[Dict[str, str]], **kwargs) -> Iterator[str]:
        """
        流式调用LLM,返回流式响应片段。
        Yields:
            str: 流式响应的文本片段
        """
        print(f"🧠 正在调用 {self.model} 模型...")
        try:
            # 获取模型响应(非流式输出)
            response = self.client.chat.completions.create(
                model=self.model,
                messages=messages,
                temperature=kwargs.get('temperature', 0.7),
                max_tokens=kwargs.get('max_tokens', None),
                stream=True,
                **{k: v for k, v in kwargs.items() if k not in ['temperature', 'max_tokens']}
            )
            # 处理模型响应
            print("✅ 大语言模型响应成功:")
            for chunk in response:
                if not chunk.choices:
                    continue
                content = chunk.choices[0].delta.content or ""
                if content:
                    print(content, end="", flush=True) # 打字机输出
                    yield content
            print()  # 在流式输出结束后换行
        except Exception as e:
            print(f"❌ 调用LLM时发生错误: {e}")
            raise LLMException(f"LLM调用失败: {str(e)}")

二、基础组件定义

LLM 客户端解决了与大语言模型通信的关键问题。不过为了更加工程化、结构化,我们还需要搭建配套的基础组件来支撑上层应用的构建,包括数据流处理、配置管理、异常封装等。

1. 消息封装

为了确保智能体与模型之间信息传递的标准化,我们需要定义框架内统一的消息格式。消息的封装应当包含消息角色、消息内容两个核心属性,并增加时间戳和拓展字段为后续功能演进预留空间。

python 复制代码
"""消息封装"""

"""
消息角色枚举
- user:用户输入内容
- assistant:上下文或历史记录
- system:全局设定及规则
- tool:工具调用返回结果
"""
MessageRole = Literal["user", "assistant", "system", "tool"]


class Message(BaseModel):
    """消息类"""

    content: str # 消息内容
    role: MessageRole # 消息角色
    timestamp: datetime = None # 时间戳
    metadata: Optional[Dict[str, Any]] = None # 拓展参数

    def __init__(self, content: str, role: MessageRole, **kwargs):
        super().__init__(
            content=content,
            role=role,
            timestamp=kwargs.get('timestamp', datetime.now()),
            metadata=kwargs.get('metadata', {})
        )

    def to_dict(self) -> Dict[str, Any]:
        """转换为 OpenAI API 字典格式"""
        return {
            "role": self.role,
            "content": self.content
        }

    def __str__(self) -> str:
        return f"[{self.role}] {self.content}"

2. 配置管理

配置管理的职责是将代码中硬编码配置参数集中起来,并支持从环境变量中读取,使框架的行为易于调整和扩展。

python 复制代码
"""配置管理"""


class Config(BaseModel):
    """配置类"""

    # LLM配置
    default_model: str = "gpt-3.5-turbo"
    default_provider: str = "openai"
    temperature: float = 0.7
    max_tokens: Optional[int] = None

    # 系统配置
    debug: bool = False
    log_level: str = "INFO"

    # 其他配置
    max_history_length: int = 100

    @classmethod
    def from_env(cls) -> "Config":
        """从环境变量创建配置"""
        return cls(
            debug=os.getenv("DEBUG", "false").lower() == "true",
            log_level=os.getenv("LOG_LEVEL", "INFO"),
            temperature=float(os.getenv("TEMPERATURE", "0.7")),
            max_tokens=int(os.getenv("MAX_TOKENS")) if os.getenv("MAX_TOKENS") else None,
        )

    def to_dict(self) -> Dict[str, Any]:
        """转换为字典"""
        return self.dict()

3. 异常处理

在框架中统一异常处理类型,搭建完善的异常体系。

python 复制代码
"""异常体系"""

class CommonException(Exception):
    """基础异常类"""
    pass

class LLMException(CommonException):
    """LLM相关异常"""
    pass

class AgentException(CommonException):
    """Agent相关异常"""
    pass

class ConfigException(CommonException):
    """配置相关异常"""
    pass

class ToolException(CommonException):
    """工具相关异常"""
    pass

三、工具系统实现

在之前的智能体范式篇章中,我们已经简单实践了工具的使用。工具调用是智能体架构的核心能力,它决定了智能体工作的上限,在成熟的智能体框架中,工具系统必定是及其完善和丰富的,需要覆盖工具的创建、注册、发现与使用等全生命周期,且设计机制能够在复杂场景下进行拓展。

1. 工具基础

  • Tool基类定义:工具基类是整个工具系统的核心抽象,它定义了所有工具必须遵循的接口规范;
  • ToolParameter参数定义:参数抽象让工具能够精确描述自己的参数需求,支持类型检查、默认值设置和文档自动生成;
python 复制代码
class ToolParameter(BaseModel):
    """工具参数定义"""
    name: str # 参数名称
    type: str # 参数类型
    description: str # 参数说明
    required: bool = False # 是否必须
    default: Any = None # 默认值


class Tool(ABC):
    """工具基类"""

    def __init__(self, name: str, description: str):
        self.name = name # 工具名称
        self.description = description # 工具描述

    @abstractmethod
    def run(self, parameters: Dict[str, Any]) -> str:
        """执行工具"""
        pass

    @abstractmethod
    def get_parameters(self) -> List[ToolParameter]:
        """获取工具参数定义"""
        pass

    def validate_parameters(self, parameters: Dict[str, Any]) -> bool:
        """验证工具参数"""
        required_params = [p.name for p in self.get_parameters() if p.required]
        return all(param in parameters for param in required_params)

    def to_dict(self) -> Dict[str, Any]:
        """转换为字典格式"""
        return {
            "name": self.name,
            "description": self.description,
            "parameters": [param.dict() for param in self.get_parameters()]
        }

    def __str__(self) -> str:
        return f"Tool(name={self.name})"

    def __repr__(self) -> str:
        return self.__str__()

2. 工具管理器/注册表

工具管理器是工具系统的管理中枢,它负责提供工具的注册、发现、执行等核心能力,进行工具的统一调度。在工具类型上支持:

  • Tool工具注册:适合复杂工具,支持完整的参数定义和验证(实现 Tool 抽象);
  • 函数工具注册:适合简单工具,快速集成现有函数;
python 复制代码
"""工具管理中心:工具注册、工具获取、工具使用"""

class ToolRegistry:
    """
    工具管理器:提供工具的注册、管理和执行功能。
    支持两种工具注册方式:
    1. Tool工具注册:注册封装好的Tool对象
    2. 函数工具注册:直接注册已有函数作为工具
    """

    def __init__(self):
        self._tools: dict[str, Tool] = {} # Tool工具集合
        self._functions: dict[str, dict[str, Any]] = {} # 函数工具集合

    def register_tool(self, tool: Tool):
        """
        注册Tool工具
        """
        if tool.name in self._tools:
            print(f"⚠️ 警告:工具 '{tool.name}' 已存在,将被覆盖。")

        self._tools[tool.name] = tool
        print(f"✅ Tool工具 '{tool.name}' 已注册。")

    def register_function(self, name: str, description: str, func: Callable[[str], str]):
        """
        函数工具注册
        Args:
            name: 工具名称
            description: 工具描述
            func: 可调用函数,接受字符串参数并返回字符串结果
        """
        if name in self._functions:
            print(f"⚠️ 警告:工具 '{name}' 已存在,将被覆盖。")

        self._functions[name] = {
            "description": description,
            "func": func
        }
        print(f"✅ 函数工具 '{name}' 已注册。")

    def unregister(self, name: str):
        """注销工具"""
        if name in self._tools:
            del self._tools[name]
            print(f"🗑️ 工具 '{name}' 已注销。")
        elif name in self._functions:
            del self._functions[name]
            print(f"🗑️ 工具 '{name}' 已注销。")
        else:
            print(f"⚠️ 工具 '{name}' 不存在。")

    def clear(self):
        """清空工具"""
        self._tools.clear()
        self._functions.clear()
        print("🧹 所有工具已清空。")

    def get_tool(self, name: str) -> Optional[Tool]:
        """获取Tool工具"""
        return self._tools.get(name)

    def get_function(self, name: str) -> Optional[Callable]:
        """获取函数工具"""
        func_info = self._functions.get(name)
        return func_info["func"] if func_info else None

    def execute_tool(self, name: str, input_text: str) -> str:
        """
        执行工具
        Args:
            name: 工具名称
            input_text: 输入参数
        Returns:
            工具执行结果
        """
        # 优先查找Tool对象
        if name in self._tools:
            tool = self._tools[name]
            try:
                # 简化参数传递,直接传入字符串
                return tool.run({"input": input_text})
            except Exception as e:
                return f"错误:执行工具 '{name}' 时发生异常: {str(e)}"

        # 查找函数工具
        elif name in self._functions:
            func = self._functions[name]["func"]
            try:
                return func(input_text)
            except Exception as e:
                return f"错误:执行工具 '{name}' 时发生异常: {str(e)}"

        else:
            return f"错误:未找到名为 '{name}' 的工具。"

    def get_tools_description(self) -> str:
        """
        获取所有可用工具的格式化描述字符串
        """
        descriptions = []

        # Tool对象描述
        for tool in self._tools.values():
            descriptions.append(f"- {tool.name}: {tool.description}")

        # 函数工具描述
        for name, info in self._functions.items():
            descriptions.append(f"- {name}: {info['description']}")

        return "\n".join(descriptions) if descriptions else "暂无可用工具"

# 全局工具管理器
global_registry = ToolRegistry()

3. 自定义工具实现

自定义工具的开发流程:创建自定义工具(函数/Tool实例) → 注册工具管理器 ToolRegistry → 集成 Agent 使用(最好支持动态集成)

3.1 计算工具(函数类型)

自定义的数学计算工具: 支持基本的四则运算,还涵盖了常用的数学函数和常数,满足大多数计算场景的需求。

python 复制代码
"""函数工具示例:数学计算器"""

def calculate(expression: str) -> str:
    """数学计算函数入口:计算表达式结果"""
    if not expression.strip():
        return "计算表达式不能为空"

    # 支持的基本运算
    operators = {
        ast.Add: operator.add,      # +
        ast.Sub: operator.sub,      # -
        ast.Mult: operator.mul,     # *
        ast.Div: operator.truediv,  # /
        ast.Pow: operator.pow,      # **
    }

    # 支持的基本函数
    functions = {
        'sum': sum,
        'sqrt': math.sqrt,
        'log': math.log,
        'pi': math.pi,
        'e': math.e,
    }

    print(f"🧮 正在计算: {expression}")

    try:
        # 1.解析表达式语法树(AST解析)
        node = ast.parse(expression, mode='eval')
        # 2.递归计算表达式结果
        result = _eval_node(node.body, operators, functions)
        print(f"✅ 计算结果: {str(result)}")
        return str(result)
    except Exception as e:
        error_msg = f"计算失败: {str(e)}"
        print(f"❌ {error_msg}")
        return error_msg

def _eval_node(node, operators, functions):
    """表达式递归求值"""
    if isinstance(node, ast.Constant):
        # 1.常量返回
        return node.value
    elif isinstance(node, ast.BinOp):
        # 2.二元运算
        left = _eval_node(node.left, operators, functions)
        right = _eval_node(node.right, operators, functions)
        op = operators.get(type(node.op))
        return op(left, right)
    elif isinstance(node, ast.Call):
        # 3.函数调用
        func_name = node.func.id
        if func_name in functions:
            args = [_eval_node(arg, operators, functions) for arg in node.args]
            return functions[func_name](*args)
        else:
            raise ToolException(f"{func_name} not found")
    elif isinstance(node, ast.Name):
        # 4.变量引用
        if node.id in functions:
            return functions[node.id]
3.2 搜索工具(Tool类型)

自定义多源搜索工具: 整合多个搜索引擎,能提供更加完备的真实信息。本次我们使用 Tavily API 和 SerpAPI 两个搜索源作为搜索工具可选的搜索引擎后端,针对搜索结果进行智能选择,其中 Tavily API 的密钥可以在 https://www.tavily.com/ 获取。

python 复制代码
"""Tool工具示例:智能搜索工具"""

class SearchTool(Tool):
    """
    智能搜索工具:支持多种搜索引擎后端,智能选择最佳搜索源。
    1. Tavily:专业AI搜索
    2. SerpApi:传统Google搜索
    """

    def __init__(self):
        super().__init__(
            name="search",
            description="智能搜索工具:支持多种搜索源,自动选择最佳结果。"
        )
        self.search_sources = [] # 可用搜索源列表
        self._setup_search_sources() # 初始化设置搜索源

    def _setup_search_sources(self):
        """设置可用的搜索源"""
        # 检查Tavily可用性
        if os.getenv("TAVILY_API_KEY"):
            self.tavily_key = os.getenv("TAVILY_API_KEY")
            try:
                from tavily import TavilyClient
                self.tavily_client = TavilyClient(api_key=self.tavily_key)
                self.search_sources.append("tavily")
                print("✅ Tavily搜索源已启用")
            except ImportError:
                print("⚠️ Tavily库未安装,无法使用Tavily搜索")

        # 检查SerpApi可用性
        if os.getenv("SERPAPI_API_KEY"):
            self.serpapi_key = os.getenv("SERPAPI_API_KEY")
            try:
                from serpapi import SerpApiClient
                self.search_sources.append("serpapi")
                print("✅ SerpApi搜索源已启用")
            except ImportError:
                print("⚠️ SerpApi库未安装,无法使用SerpApi搜索")

        if self.search_sources:
            print(f"🔧 可用搜索源: {', '.join(self.search_sources)}")
        else:
            print("⚠️ 没有可用的搜索源,请先配置API密钥")

    def run(self, parameters: Dict[str, Any]) -> str:
        """执行智能搜索:工具抽象方法实现"""
        query = parameters.get("input", "").strip()

        # 检查查询参数是否合法
        if not query:
            return "❌ 错误:搜索查询内容不能为空"

        # 检查是否有可用的搜索源
        if not self.search_sources:
            return """❌ 没有可用的搜索源,请配置API密钥后重新运行程序。"""

        print(f"🔍 开始执行搜索: {query}")

        # 尝试多个搜索源,返回最佳结果
        for source in self.search_sources:
            try:
                if source == "tavily":
                    result = self._search_with_tavily(query)
                    if result and "未找到" not in result:
                        return f"📊 Tavily AI搜索结果:\n\n{result}"

                elif source == "serpapi":
                    result = self._search_with_serpapi(query)
                    if result and "未找到" not in result:
                        return f"🌐 SerpApi Google搜索结果:\n\n{result}"

            except Exception as e:
                print(f"⚠️ {source} 搜索失败: {e}")
                continue

        return "❌ 所有搜索源均失败,请检查网络连接和API密钥配置"

    def _search_with_tavily(self, query: str) -> str:
        """使用Tavily搜索"""
        if not self.tavily_client:
            return "错误:Tavily未安装,请运行 pip install tavily-python"

        # 执行搜索
        response = self.tavily_client.search(
            query=query,
            search_depth="basic",
            include_answer=True,
            max_results=3
        )

        # 结果处理
        result = f"🎯 Tavily AI搜索直接结果:{response.get('answer', '未找到直接答案')}\n\n"

        result += "🔗 相关结果:\n"
        for i, item in enumerate(response.get('results', [])[:3], 1):
            result += f"[{i}] {item.get('title', '')}\n"
            result += f"    {item.get('content', '')[:150]}...\n"
            result += f"    来源: {item.get('url', '')}\n\n"

        return result

    def _search_with_serpapi(self, query: str) -> str:
        """使用SerpApi搜索"""
        try:
            from serpapi import SerpApiClient
        except ImportError:
            return "错误:SerpApi未安装,请运行 pip install serpapi"

        # 执行搜索
        params = {
            "engine": "google",
            "q": query,
            "api_key": self.serpapi_key,
            "gl": "cn",
            "hl": "zh-cn",
        }
        client = SerpApiClient(params)
        response = client.get_dict()

        # 结果处理:优先寻找最直接的答案
        result = "🎯 SerpApi Google搜索结果:\n\n"
        if "answer_box" in response and "answer" in response["answer_box"]:
            result += f"💡 直接答案:{response['answer_box']['answer']}\n\n"

        if "knowledge_graph" in response and "description" in response["knowledge_graph"]:
            result += f"📖 知识图谱:{response['knowledge_graph']['description']}\n\n"

        if "organic_results" in response and response["organic_results"]:
            result += "🔗 相关结果:\n"
            for i, res in enumerate(response["organic_results"][:3], 1):
                result += f"[{i}] {res.get('title', '')}\n"
                result += f"    {res.get('snippet', '')}\n"
                result += f"    来源: {res.get('link', '')}\n\n"

        return result

    def get_parameters(self) -> List[ToolParameter]:
        """获取工具参数定义:工具抽象方法实现"""
        return [
            ToolParameter(
                name="input",
                type="string",
                description="搜索查询关键词",
                required=True
            )
        ]
3.3 工具测试
python 复制代码
# 加载环境变量
load_dotenv()

def test_calculator_tool():
    # 创建工具注册表
    registry = ToolRegistry()

    # 注册计算器函数
    registry.register_function(
        name="calculator",
        description="简单的数学计算工具,支持基本运算(+,-,*,/)和sqrt函数",
        func=calculate
    )

    print("🧪 测试自定义计算器工具\n")

    # 简单测试用例
    test_cases = [
        "2 + 3",  # 基本加法
        "10 - 4",  # 基本减法
        "5 * 6",  # 基本乘法
        "15 / 3",  # 基本除法
        "sqrt(16)",  # 平方根
    ]

    for i, expression in enumerate(test_cases, 1):
        print(f"测试 {i}: {expression}")
        result = registry.execute_tool("calculator", expression)
        print(f"结果: {result}\n")

def test_search_tool():
    # 创建工具注册表
    registry = ToolRegistry()

    # 创建Tool工具
    search = SearchTool()

    # 注册搜索工具
    registry.register_tool(search)

    print("🔍 测试搜索工具\n")

    # 测试查询
    test_queries = [
        "Python编程语言的历史",
        "人工智能的最新发展",
        "2026年科技趋势"
    ]

    for i, query in enumerate(test_queries, 1):
        print(f"测试 {i}: {query}")
        result = registry.execute_tool("search", query)
        print(f"结果: {result}\n")
        print("-" * 60 + "\n")

if __name__ == "__main__":
    test_calculator_tool() # 测试计算工具
    test_search_tool() # 测试搜索工具

4. 高级特性

接下来我们来探讨工具系统的高级特性,这些特性能够让工具系统在复杂的生产环境中稳定运行,并为Agent提供更强大的能力。

4.1 工具链

在实际应用场景中,智能体通常需要组合使用多个工具来共同完成复杂任务。工具链是指以链表形式组织多个工具串行执行,在这种场景下通常上游工具输出会作为下游工具输入。

python 复制代码
"""高级特性:工具链"""

class ToolChain:
    """工具链 - 支持多个工具的编排执行"""

    def __init__(self, name: str, description: str):
        self.name = name # 工具链名称
        self.description = description # 工具链描述
        self.steps: List[Dict[str, Any]] = [] # 工具编排执行列表

    def add_step(self, tool_name: str, input_template: str, output_key: str = None):
        """
        添加工具执行步骤
        Args:
            tool_name: 工具名称
            input_template: 输入模板,支持变量替换
            output_key: 输出结果的键名,用于后续步骤引用
        """
        self.steps.append({
            "tool_name": tool_name,
            "input_template": input_template,
            "output_key": output_key or f"step_{len(self.steps)}_result"
        })
        print(f"✅ 工具链 '{self.name}' 添加步骤: {tool_name}")

    def execute(self, registry: ToolRegistry, input_data: str, context: Dict[str, Any] = None) -> str:
        """
        执行工具链
        Args:
            registry: 工具管理器(执行工具)
            input_data: 初始输入数据
            context: 执行上下文,用于变量替换
        """
        if not self.steps:
            return "❌ 工具链为空,无法执行"

        context = context or {}
        context["input"] = input_data

        print(f"🔗 开始执行工具链: {self.name}")

        for i, step in enumerate(self.steps, 1):
            # 获取工具执行参数
            tool_name = step["tool_name"]
            input_template = step["input_template"]
            output_key = step["output_key"]

            print(f"  步骤 {i}/{len(self.steps)}: {tool_name}")

            # 替换模板中的变量
            try:
                tool_input = input_template.format(**context)
            except KeyError as e:
                return f"❌ 工具链执行失败:模板变量 {e} 替换失败"

            # 执行工具
            try:
                result = registry.execute_tool(tool_name, tool_input)
                context[output_key] = result
            except Exception as e:
                return f"❌ 工具 '{tool_name}' 执行失败: {e}"

            print(f"  ✅ 步骤 {i} 执行完成:{output_key}")

        print(f"🎉 工具链 '{self.name}' 执行完成")
        # 返回最后一步的结果
        final_result = context[self.steps[-1]["output_key"]]
        return final_result

class ToolChainManager:
    """工具链管理器"""

    def __init__(self, registry: ToolRegistry):
        self.registry = registry # 工具管理器
        self.chains: Dict[str, ToolChain] = {} # 工具链集合

    def register_chain(self, chain: ToolChain):
        """注册工具链"""
        self.chains[chain.name] = chain
        print(f"✅ 工具链 '{chain.name}' 已注册")

    def execute_chain(self, chain_name: str, input_data: str, context: Dict[str, Any] = None) -> str:
        """执行指定的工具链"""
        if chain_name not in self.chains:
            return f"❌ 工具链 '{chain_name}' 不存在"

        chain = self.chains[chain_name]
        return chain.execute(self.registry, input_data, context)

    def list_chains(self) -> List[str]:
        """列出所有工具链"""
        return list(self.chains.keys())

    def get_chain_info(self, chain_name: str) -> Optional[Dict[str, Any]]:
        """获取指定工具链信息"""
        if chain_name not in self.chains:
            return None

        chain = self.chains[chain_name]
        return {
            "name": chain.name,
            "description": chain.description,
            "steps": len(chain.steps),
            "step_details": [
                {
                    "tool_name": step["tool_name"],
                    "input_template": step["input_template"],
                    "output_key": step["output_key"]
                }
                for step in chain.steps
            ]
        }

测试如下:

python 复制代码
# 加载环境变量
load_dotenv()

def test_research_chain():
    # 1.创建工具
    # 1.1 创建工具注册表
    registry = ToolRegistry()
    # 1.2 创建搜索工具
    search = SearchTool()
    registry.register_tool(search)
    # 1.3 创建计算工具
    registry.register_function(
        name="calculator",
        description="简单的数学计算工具,支持基本运算(+,-,*,/)和sqrt函数",
        func=calculate
    )

    # 2.创建研究工具链:搜索 -> 计算 -> 总结
    chain = ToolChain(
        name="research_and_calculate",
        description="搜索信息并进行相关计算"
    )
    # 2.1 步骤1:搜索信息
    chain.add_step(
        tool_name="search",
        input_template="{input}",
        output_key="search_result"
    )
    # 2.2 步骤2:基于搜索结果进行计算
    chain.add_step(
        tool_name="calculator",
        input_template="2 + 2 * 3",  # 简单的计算示例
        output_key="calc_result"
    )

    # 3.执行工具链
    # 3.1 创建工具链管理器
    chain_registry = ToolChainManager(registry)
    # 3.2 注册工具链
    chain_registry.register_chain(chain)
    # 3.3 执行工具链
    res = chain_registry.execute_chain("research_and_calculate", "加减乘除基本数学运算的优先级")
    print(res)


if __name__ == "__main__":
    test_research_chain()
4.2 异步工具

在性能优化方面,对于耗时的工具操作我们可以提供异步执行支持,利用异步执行提高并发处理能力。

python 复制代码
"""高级特性:异步工具执行器"""

class AsyncToolExecutor:
    """异步工具执行器"""

    def __init__(self, registry: ToolRegistry, max_workers: int = 4):
        self.registry = registry # 工具管理器
        self.executor = concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) # 异步线程池

    async def execute_tool_async(self, tool_name: str, input_data: str) -> str:
        """异步执行单个工具"""
        loop = asyncio.get_event_loop()

        def _execute():
            return self.registry.execute_tool(tool_name, input_data)

        try:
            result = await loop.run_in_executor(self.executor, _execute)
            return result
        except Exception as e:
            return f"❌ 工具 '{tool_name}' 异步执行失败: {e}"

    async def execute_tools_parallel(self, tasks: List[Dict[str, str]]) -> List[str]:
        """并行执行多个工具"""
        print(f"🚀 开始并行执行 {len(tasks)} 个工具任务")

        # 创建异步任务
        async_tasks = []
        for task in tasks:
            tool_name = task["tool_name"]
            input_data = task["input_data"]
            if not tool_name:
                continue
            async_task = self.execute_tool_async(tool_name, input_data)
            async_tasks.append(async_task)

        # 等待所有任务完成
        results = await asyncio.gather(*async_tasks)

        print(f"✅ 所有工具任务执行完成")
        return results

    def __del__(self):
        """清理资源"""
        if hasattr(self, 'executor'):
            self.executor.shutdown(wait=True)

四、智能体实现

1. 智能体基类

Agent 类是整个框架的顶层抽象。它定义了一个智能体应该具备的通用行为和属性,但并不关心具体的实现方式。我们通过 Python 的 abc (Abstract Base Classes) 模块来实现它,这强制所有具体的智能体实现(如后续章节的 SimpleAgent, ReActAgent 等)都必须遵循同一个"接口"。

python 复制代码
"""Agent基类"""

from abc import ABC, abstractmethod

class Agent(ABC):
    """
    Agent基类
    - name:Agent名称
    - llm:LLM客户端
    - system_prompt:系统提示词
    - config:配置参数
    """

    def __init__(
            self,
            name: str,
            llm: BaseLLMClient,
            system_prompt: Optional[str] = None,
            config: Optional[Config] = None
    ):
        self.name = name
        self.llm = llm
        self.system_prompt = system_prompt
        self.config = config or Config()
        self._history: list[Message] = [] # 历史上下文

    @abstractmethod
    def run(self, input_text: str, **kwargs) -> str:
        """运行Agent"""
        pass

    def add_message(self, message: Message):
        """添加消息到历史记录"""
        self._history.append(message)

    def clear_history(self):
        """清空历史记录"""
        self._history.clear()

    def get_history(self) -> list[Message]:
        """获取历史记录"""
        return self._history.copy()

    def __str__(self) -> str:
        return f"Agent(name={self.name}, model={self.llm.model})"

    def __repr__(self) -> str:
        return self.__str__()

2. ReActAgent

基于现有的框架和组件体系,我们对上文中实现的 ReActAgent 范式智能体实现进行重构和优化、集成框架工具系统,并保持核心逻辑(思考 -> 行动 -> 观察循环)不变。

python 复制代码
"""ReActAgent智能体示例"""

DEFAULT_REACT_PROMPT = """
你是一个具备推理和行动能力的AI助手。你可以通过思考分析问题,然后调用合适的工具来获取信息,最终给出准确的答案。

## 可用工具
{tools}

## 工作流程
请严格按照以下格式进行回应,每次只能执行一个步骤:

Thought: 分析当前问题,思考需要什么信息或采取什么行动。
Action: 选择一个行动,格式必须是以下之一:
- `{{tool_name}}[{{tool_input}}]` - 调用指定工具
- `Finish[最终答案]` - 当你有足够信息给出最终答案时

## 重要提醒
1. 每次回应必须包含Thought和Action两部分
2. 工具调用的格式必须严格遵循:工具名[参数]
3. 只有当你确信有足够信息回答问题时,才使用Finish
4. 如果工具返回的信息不够,继续使用其他工具或相同工具的不同参数

## 当前任务
**Question:** {question}

## 执行历史
{history}

现在开始你的推理和行动:
"""


"""
ReAct (Reasoning and Acting) Agent: 推理与行动结合的智能体

1. 分析问题并制定行动计划
2. 调用外部工具获取信息
3. 基于观察结果进行推理
4. 迭代执行直到得出最终答案
"""
class ReActAgent(Agent):

    def __init__(
            self,
            name: str,
            llm: BaseLLMClient,
            tool_registry: ToolRegistry,
            system_prompt: Optional[str] = None,
            config: Optional[Config] = None,
            max_steps: int = 5,
            custom_prompt: Optional[str] = None
    ):
        """
        初始化ReActAgent

        Args:
            name: Agent名称
            llm: LLM实例
            tool_registry: 工具注册表
            system_prompt: 系统提示词
            config: 配置对象
            max_steps: 最大执行步数
            custom_prompt: 自定义提示词模板
        """
        super().__init__(name, llm, system_prompt, config)
        self.tool_registry = tool_registry
        self.max_steps = max_steps
        self.current_history: List[str] = []
        self.prompt_template = custom_prompt if custom_prompt else DEFAULT_REACT_PROMPT # 设置提示词模板:用户自定义优先,否则使用默认模板
        print(f"✅ {name} 初始化完成,最大步数: {max_steps}")

    def run(self, input_text: str, **kwargs) -> str:
        """
        运行ReAct Agent
        Args:
            input_text: 用户问题
            **kwargs: 其他拓展参数
        """
        self.current_history = []
        current_step = 0

        print(f"\n🤖 {self.name} 开始处理问题: {input_text}")

        while current_step < self.max_steps:
            current_step += 1
            print(f"\n--- 第 {current_step} 步 ---")

            # 1.构建提示词
            tools_desc = self.tool_registry.get_tools_description()
            history_str = "\n".join(self.current_history)
            prompt = self.prompt_template.format(
                tools=tools_desc,
                question=input_text,
                history=history_str
            )

            # 2.调用LLM
            messages = [{"role": "user", "content": prompt}]
            response_text = self.llm.invoke(messages, **kwargs)

            # 3.解析输出
            thought, action = self._parse_output(response_text)
            if thought:
                print(f"🤔 思考: {thought}")

            # 4. 检查完成条件
            if action and action.startswith("Finish"):
                final_answer = re.match(r"Finish\[(.*)\]", action, re.DOTALL).group(1)
                # 更新历史
                self.add_message(Message(input_text, "user"))
                self.add_message(Message(final_answer, "assistant"))
                return final_answer

            # 5.执行工具调用
            if action:
                tool_name, tool_input = self._parse_action(action)
                if not tool_name:
                    self.current_history.append("Observation: 无效的Action格式,请检查。")
                    continue
                print(f"🎬 行动: {tool_name}[{tool_input}]")
                observation = self.tool_registry.execute_tool(tool_name, tool_input)
                # 更新历史
                self.current_history.append(f"Action: {action}")
                self.current_history.append(f"Observation: {observation}")

        # 6.达到最大步数
        print("⏰ 已达到最大步数,流程终止。")
        final_answer = "抱歉,我无法在限定步数内完成这个任务。"
        # 保存到历史记录
        self.add_message(Message(input_text, "user"))
        self.add_message(Message(final_answer, "assistant"))

        return final_answer

    def _parse_output(self, text: str) -> Tuple[Optional[str], Optional[str]]:
        """
        解析LLM返回的响应,提取模板中的Thought和Action
        """
        # Thought: 正则表达式匹配到 'Action:' 或文本末尾
        thought_match = re.search(r"Thought:\s*(.*?)(?=\nAction:|$)", text, re.DOTALL)
        # Action: 正则表达式匹配到文本末尾
        action_match = re.search(r"Action:\s*(.*?)$", text, re.DOTALL)
        # 获取最终结果
        thought = thought_match.group(1).strip() if thought_match else None
        action = action_match.group(1).strip() if action_match else None
        return thought, action


    def _parse_action(self, action_text: str) -> Tuple[Optional[str], Optional[str]]:
        """解析Action字符串:提取工具名称和工具输入(仅提取一对)"""
        # tool[tool_input] -> tool + tool_input
        match = re.match(r"(\w+)\[(.*)\]", action_text, re.DOTALL)
        if match:
            return match.group(1), match.group(2)
        return None, None

3. GeneralAgent

除了实现范式中的智能体,我们还可以基于 Agent 基类,构建功能完备且遵循框架规范的自定义通用对话智能体,该智能体应当支持基础对话,具备可选的工具调用能力、流式响应和便利的工具管理方法。

python 复制代码
"""自定义Agent示例"""

class GeneralAgent(Agent):
    """通用对话Agent:支持可选的工具调用"""

    def __init__(
            self,
            name: str,
            llm: BaseLLMClient,
            system_prompt: Optional[str] = None,
            config: Optional[Config] = None,
            tool_registry: Optional['ToolRegistry'] = None,
            enable_tool_calling: bool = True
    ):
        """
        初始化GeneralAgent
        Args:
            name: Agent名称
            llm: LLM实例
            system_prompt: 系统提示词
            config: 配置对象
            tool_registry: 工具管理器(可选,如果提供则启用工具调用)
            enable_tool_calling: 是否启用工具调用(只有在提供tool_registry时生效)
        """
        super().__init__(name, llm, system_prompt, config)
        self.tool_registry = tool_registry
        self.enable_tool_calling = enable_tool_calling and tool_registry is not None
        print(f"✅ {name} 初始化完成,工具调用: {'启用' if self.enable_tool_calling else '禁用'}")


    def run(self, input_text: str, max_tool_iterations: int = 3, **kwargs) -> str:
        """
        运行Agent,支持可选的工具调用
        Args:
            input_text: 用户输入
            max_tool_iterations: 会话最大工具调用迭代次数(仅在启用工具时有效)
            **kwargs: 其他拓展参数
        """
        # 1.构建消息列表
        messages = []
        # 1.2 添加系统消息(可能包含工具信息)
        messages.append({"role": "system", "content": self._get_enhanced_system_prompt()})
        # 1.3 添加历史消息
        for msg in self._history:
            messages.append({"role": msg.role, "content": msg.content})
        # 1.3 添加当前用户消息
        messages.append({"role": "user", "content": input_text})

        # 2.如果没有启用工具调用,使用原有对话逻辑
        if not self.enable_tool_calling:
            response = self.llm.invoke(messages, **kwargs)
            self.add_message(Message(input_text, "user"))
            self.add_message(Message(response, "assistant"))
            return response

        # 3.如果启用工具调用,支持多轮工具调用逻辑
        return self._run_with_tools(messages, input_text, max_tool_iterations, **kwargs)


    def _run_with_tools(self, messages: list, input_text: str, max_tool_iterations: int, **kwargs) -> str:
        """支持工具调用的对话运行逻辑"""
        current_iteration = 0 # 当前迭代次数
        final_response = "" # 最终运行结果

        while current_iteration < max_tool_iterations:
            print(f"📍 工具迭代次数 {current_iteration+1}/{max_tool_iterations}")

            # 1. 调用LLM获取当前响应
            response = self.llm.invoke(messages, **kwargs)

            # 2.检查结果中是否有工具调用行为
            tool_calls = self._parse_tool_calls(response)

            # 3.存在工具调用:工具执行
            if tool_calls:
                print(f"🔧 检测到 {len(tool_calls)} 个工具调用")
                # 3.1 执行所有工具调用并收集结果
                tool_results = []
                clean_response = response
                for call in tool_calls:
                    result = self._execute_tool_call(call['tool_name'], call['parameters'])
                    tool_results.append(result)
                    # 从响应中移除工具调用标记
                    clean_response = clean_response.replace(call['original'], "")

                # 3.2 构建包含工具结果的消息
                messages.append({"role": "assistant", "content": clean_response})

                # 3.3 添加工具结果
                tool_results_text = "\n\n".join(tool_results)
                messages.append(
                    {"role": "user", "content": f"工具执行结果:\n{tool_results_text}\n\n请基于这些结果给出完整的回答。"})

                current_iteration += 1
                continue

            # 4.没有工具调用:直接返回最终回答(不再向后迭代)
            final_response = response
            break

        # 4.如果超过最大迭代次数,获取最后一次回答
        if current_iteration >= max_tool_iterations and not final_response:
            final_response = self.llm.invoke(messages, **kwargs)

        # 5.保存到历史记录
        self.add_message(Message(input_text, "user"))
        self.add_message(Message(final_response, "assistant"))

        return final_response

    def _get_enhanced_system_prompt(self) -> str:
        """构建增强的系统提示词,包含工具信息"""
        base_prompt = self.system_prompt or "你是一个有用的AI助手。"

        # 1.未启用工具:返回基本提示词
        if not self.enable_tool_calling or not self.tool_registry:
            return base_prompt

        # 2.启用工具:获取工具描述
        tools_description = self.tool_registry.get_tools_description()
        if not tools_description or tools_description == "暂无可用工具":
            return base_prompt

        tools_section = "\n\n## 可用工具\n"
        tools_section += "你可以使用以下工具来帮助回答问题:\n"
        tools_section += tools_description + "\n"

        tools_section += "\n## 工具调用格式\n"
        tools_section += "当需要使用工具时,请使用以下格式:\n"
        tools_section += "`[TOOL_CALL:{tool_name}:{parameters}]`\n"
        tools_section += "例如:`[TOOL_CALL:search:Python编程]` 或 `[TOOL_CALL:memory:recall=用户信息]`\n\n"
        tools_section += "工具调用结果会自动插入到对话中,然后你可以基于结果继续回答。\n"

        return base_prompt + tools_section

    def _parse_tool_calls(self, text: str) -> list:
        """解析文本中的工具调用"""
        pattern = r'\[TOOL_CALL:([^:]+):([^\]]+)\]' # 正则表达式解析 [TOOL_CALL:{tool_name}:{parameters}]
        matches = re.findall(pattern, text)

        tool_calls = []
        for tool_name, parameters in matches:
            tool_calls.append({
                'tool_name': tool_name.strip(),
                'parameters': parameters.strip(),
                'original': f'[TOOL_CALL:{tool_name}:{parameters}]'
            })

        return tool_calls

    def _execute_tool_call(self, tool_name: str, parameters: str) -> str:
        """执行工具调用"""
        if not self.tool_registry:
            return f"❌ 错误:未配置工具注册表"

        try:
            # 1.获取Tool对象
            tool = self.tool_registry.get_tool(tool_name)
            if tool:
                # 1.1 智能参数解析
                param_dict = self._parse_tool_parameters(tool_name, parameters)
                # 1.2 调用工具
                result = tool.run(param_dict)
                return f"🔧 工具 {tool_name} 执行结果:\n{result}"

            # 2.获取函数工具
            func = self.tool_registry.get_function(tool_name)
            if func:
                result = func(parameters)
                return f"🔧 工具 {tool_name} 执行结果:\n{result}"

        except Exception as e:
            return f"❌ 工具调用失败:{str(e)}"

    def _parse_tool_parameters(self, tool_name: str, parameters: str) -> dict:
        """智能解析工具参数"""
        param_dict = {}

        if '=' in parameters:
            # 1.key=value 或 action=search,query=Python 格式参数
            if ',' in parameters:
                # 多个参数:action=search,query=Python,limit=3
                pairs = parameters.split(',')
                for pair in pairs:
                    if '=' in pair:
                        key, value = pair.split('=', 1)
                        param_dict[key.strip()] = value.strip()
            else:
                # 单个参数:key=value
                key, value = parameters.split('=', 1)
                param_dict[key.strip()] = value.strip()

        else:
            # 2.直接格式参数:根据根据类型推断
            if tool_name == 'search': # 搜索工具
                param_dict = {'query': parameters}
            elif tool_name == 'memory': # 记忆工具
                param_dict = {'action': 'search', 'query': parameters}
            else: # 默认文本工具
                param_dict = {'input': parameters}

        return param_dict


    def stream_run(self, input_text: str, **kwargs) -> Iterator[str]:
        """
        流式运行Agent
        Args:
            input_text: 用户输入
            **kwargs: 其他拓展参数
        Yields:
            Agent响应片段
        """
        print(f"🌊 {self.name} 开始流式处理: {input_text}")

        # 1.构建消息列表
        messages = []
        if self.system_prompt:
            messages.append({"role": "system", "content": self.system_prompt})
        for msg in self._history:
            messages.append({"role": msg.role, "content": msg.content})
        messages.append({"role": "user", "content": input_text})

        # 2.流式调用LLM
        full_response = ""
        for chunk in self.llm.stream_invoke(messages, **kwargs):
            full_response += chunk
            yield chunk

        # 3.保存完整对话到历史记录
        self.add_message(Message(input_text, "user"))
        self.add_message(Message(full_response, "assistant"))

        print(f"✅ {self.name} 流式响应完成")

测试如下:

python 复制代码
# 加载环境变量
load_dotenv()

def test_general_agent():
    llm = BaseLLMClient()
    # 测试带工具的Agent
    print("=== 测试:工具增强对话 ===")
    # 注册计算器函数
    tool_registry = ToolRegistry()
    tool_registry.register_function(
        name="calculator",
        description="简单的数学计算工具,支持基本运算(+,-,*,/)和sqrt函数",
        func=calculate
    )
    # 创建Agent
    general_agent = GeneralAgent(
        name="对话助手",
        llm=llm,
        system_prompt="你是一个智能助手,可以使用工具来帮助用户。",
        tool_registry=tool_registry,
        enable_tool_calling=True
    )
    # 执行会话
    response = general_agent.run("请帮我计算 15 * 8 + 32")
    print(f"响应: {response}\n")

    # 查看对话历史
    print(f"\n对话历史: {len(general_agent.get_history())} 条消息")

if __name__ == "__main__":
    test_general_agent()

至此,我们遵循"分层解耦、职责单一、接口统一"的核心原则,构建了一个基础的智能体框架,通过统一LLM接口、标准化消息系统、实现工具注册机制等共同构成了一个完备的技术底座,为后续章节的学习奠定了基础。

相关推荐
奋斗的源2 小时前
Ollama 本地大模型运行指南:从下载到部署
ai·ollama
AI创界者2 小时前
进阶实战:AsyncIO 异步环境下的装饰器与代理模式演进
人工智能·aigc
tianxuanjg2 小时前
航空批量零件制造采购指南:如何控制批次尺寸波动,锁定稳定供应商
人工智能·经验分享·机器人·无人机·制造·材质
我命由我123452 小时前
ReAct 循环
学习·ai·产品运营·学习方法·ai编程·ai写作·产品
外贸GEO优化2 小时前
ChatGPT、Gemini、Perplexity、Copilot 怎么引中国供应商:广州外贸工厂按平台动作清单
人工智能·chatgpt·copilot
Data analyse4562 小时前
用户路径SQL按会话起止时间聚合的方法
大数据·服务器·前端·数据库·人工智能·sql·数据分析
程序员清风2 小时前
文本字段清洗:字符串、分类值与格式标准化
大数据·人工智能·分类
甲维斯2 小时前
Gemin4 很难说好,但也不算差!
人工智能