Agent 智能体开发(三)Agent 自定义框架搭建
- Hello-Agents GitHub: https://github.com/datawhalechina/Hello-Agents
- Hello-Agents Online: https://hello-agents.datawhale.cc
在上篇文章中,我们已经掌握了基本的 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接口、标准化消息系统、实现工具注册机制等共同构成了一个完备的技术底座,为后续章节的学习奠定了基础。