本教程将带你从零开始,一步步构建一个具备工具调用能力的简单 Agent 框架。即使你是编程新手,也能跟着教程完成并能成功运行代码。
📚 目录
- 什么是 Agent?
- 准备工作
- 第一步:构建"大脑"- 统一的大模型适配层
- 第二步:实现基础 Agent - 对话记忆系统
- 第三步:装备工具 - 让 Agent 能"做事"
- 第四步:消息标准化 - 统一通信格式
- 第五步:整合一切 - 完整的 ToolAgent
- 运行与测试
- 常见问题排查
- 总结与进阶
什么是 Agent?
🤖 概念理解
Agent(智能体) = 大模型(大脑)+ 记忆系统 + 工具调用能力 + 任务规划能力
比喻:
- 大模型 = 大脑(思考、理解、决策)
- 记忆系统 = 记忆(记住对话历史)
- 工具调用 = 手脚(执行实际任务,如搜索、计算)
- 任务规划 = 规划能力(分解复杂任务)
🎯 我们的实现目标
构建一个简单的 Agent,具备以下能力:
- ✅ 与用户对话并保持上下文记忆
- ✅ 调用 Google 搜索工具获取实时信息
- ✅ 自动决定何时调用工具
- ✅ 综合工具结果生成最终答案
准备工作
1️⃣ 创建项目目录
# 在你的桌面创建项目目录
cd ~/Desktop
mkdir agent
cd agent
2️⃣ 创建依赖管理文件
创建 requirements.txt 文件:
openai>=1.0.0
python-dotenv>=1.0.0
google-search-results>=2.4.2
3️⃣ 安装依赖
pip3 install -r requirements.txt
4️⃣ 配置环境变量
创建 .env 文件(不要提交到 Git):
# 方案1:使用腾讯混元(推荐,有免费额度)
API_KEY=sk-your-hunyuan-api-key
BASE_URL=https://api.hunyuan.cloud.tencent.com/v1
MODEL_NAME=hunyuan-turbos-latest
# 方案2:使用 OpenAI
# API_KEY=sk-your-openai-api-key
# BASE_URL=https://api.openai.com/v1
# MODEL_NAME=gpt-3.5-turbo
# Google 搜索 API(用于工具功能)
SERPAPI_API_KEY=your-serpapi-key
获取 API Key:
- 混元: https://api.hunyuan.cloud.tencent.com/v1 (新用户有免费额度)
- OpenAI: https://platform.openai.com/
- SerpAPI: https://serpapi.com/ (注册后免费 100 次搜索)
第一步:构建"大脑"- 统一的大模型适配层
🎯 目标
创建一个统一的大模型调用接口,屏蔽不同厂商的 API 差异。
📝 创建文件:llm.py
# llm.py
from openai import OpenAI
import os
from dotenv import load_dotenv
load_dotenv() # 加载环境变量
class LLMClient:
def __init__(self):
# 通过环境变量配置,轻松切换不同厂商
self.client = OpenAI(
api_key=os.getenv("API_KEY"),
base_url=os.getenv("BASE_URL")
)
self.model = os.getenv("MODEL_NAME", "gpt-4o")
def chat(self, messages, tools=None):
"""
统一的对话入口,屏蔽底层差异
参数:
messages: 对话历史列表
tools: 工具定义列表(可选)
返回:
OpenAI API 的响应对象
"""
params = {
"model": self.model,
"messages": messages,
}
if tools:
params["tools"] = tools
# 这里为了演示简洁,暂不开启流式输出
return self.client.chat.completions.create(**params)
💡 代码解释
核心设计思想:
-
环境变量配置
api_key=os.getenv("API_KEY") base_url=os.getenv("BASE_URL")- 将敏感信息(API Key)放在
.env文件中 - 避免硬编码在代码里,提高安全性
- 将敏感信息(API Key)放在
-
统一接口
def chat(self, messages, tools=None): return self.client.chat.completions.create(**params)- 封装 OpenAI API 的调用细节
- 提供简单易用的
chat()方法 - 支持
tools参数,让 Agent 能调用工具
-
易于切换厂商
- 只需修改
.env中的BASE_URL和MODEL_NAME - 代码完全不需要改动
- 只需修改
🔍 为什么需要这个适配层?
没有适配层的问题:
# ❌ 硬编码,不安全,难以维护
import openai
response = openai.ChatCompletion.create(
model="gpt-4o",
messages=[...],
api_key="sk-xxx" # 暴露在代码中
)
使用适配层的好处:
# ✅ 简洁、安全、易切换
from llm import LLMClient
llm = LLMClient()
response = llm.chat([{"role": "user", "content": "你好"}])
第二步:实现基础 Agent - 对话记忆系统
🎯 目标
创建一个能记住对话历史的 Agent,实现多轮对话。
📝 创建文件:agent.py
# agent.py (基础版)
from llm import LLMClient
class SimpleAgent:
def __init__(self):
self.llm = LLMClient()
# 初始化记忆,设定基础背景
self.messages = [{"role": "system", "content": "你是一个乐于助人的AI助手。"}]
def chat(self, user_input):
# 1. 接收:将用户输入存入记忆
self.messages.append({"role": "user", "content": user_input})
# 2. 思考:带着所有历史记录去请求 LLM
response = self.llm.chat(self.messages)
content = response.choices[0].message.content
# 3. 闭环:将 AI 的回答也存入记忆
self.messages.append({"role": "assistant", "content": content})
return content
💡 代码解释
核心概念:消息角色
# system: 设定 AI 的身份和行为规则
{"role": "system", "content": "你是一个乐于助人的AI助手。"}
# user: 用户的输入
{"role": "user", "content": "我叫小明"}
# assistant: AI 的回答
{"role": "assistant", "content": "你好小明,很高兴认识你!"}
工作流程(三步闭环):
为什么需要记忆?
- 大模型本身是无状态的,每次调用都是独立的
- 通过传递历史消息,让 AI 理解上下文
- 实现多轮对话和连贯性
🧪 测试基础 Agent
创建测试文件 test_agent.py:
# test_agent.py
from agent import SimpleAgent
def main():
agent = SimpleAgent()
print("=== 简单的AI助手 ===")
print("输入'quit'退出\n")
while True:
user_input = input("你: ")
if user_input.lower() == 'quit':
break
response = agent.chat(user_input)
print(f"Agent: {response}\n")
if __name__ == "__main__":
main()
运行测试:
python3 test_agent.py
示例对话:
=== 简单的AI助手 ===
输入'quit'退出
你: 我叫susie
Agent: 你好susie!我是SimpleAgent,有什么我可以帮助你的吗?
你: 你记得我叫什么吗?
Agent: 当然记得,你叫susie!
你: quit
第三步:装备工具 - 让 Agent 能"做事"
🎯 目标
实现工具注册和调用系统,让 Agent 能执行实际任务(如 Google 搜索)。
📝 创建文件:tools.py
# tools.py
import inspect
import json
import os
from functools import wraps
from google_search_results import GoogleSearch
class ToolRegistry:
"""工具注册中心:管理所有可用工具"""
def __init__(self):
self.tools = {} # 存函数实体
self.schemas = [] # 存函数说明书(JSON Schema)
def register(self, func):
"""
魔法装饰器:自动将 Python 函数转换为 OpenAI Tool Schema
"""
name = func.__name__ # 函数名
doc = func.__doc__ or "No description" # 函数文档字符串
# 解析函数签名
sig = inspect.signature(func)
params = {"type": "object", "properties": {}, "required": []}
# 遍历参数,生成 JSON Schema
for param_name, param in sig.parameters.items():
params["properties"][param_name] = {
"type": "string",
"description": f"参数 {param_name}"
}
if param.default == inspect.Parameter.empty:
params["required"].append(param_name)
# 1. 生成说明书(给 LLM 看)
self.schemas.append({
"type": "function",
"function": {
"name": name,
"description": doc,
"parameters": params
}
})
# 2. 存储函数(实际执行)
self.tools[name] = func
@wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
def execute(self, name, args_json):
"""执行器:根据 LLM 的指令运行代码"""
if name in self.tools:
try:
args = json.loads(args_json) # 将 JSON 字符串转为字典
print(f"⚙️ [System] 正在调用工具: {name} 参数: {args}")
return self.tools[name](**args) # 执行函数,解包参数
except Exception as e:
return f"Error: {str(e)}"
return "Tool not found"
# 创建全局注册表
registry = ToolRegistry()
# ========== 定义工具 ==========
@registry.register
def google_search(query: str):
"""
当用户询问当前事件、新闻、天气或不知道的信息时,使用此工具进行搜索。
"""
try:
search = GoogleSearch({
"q": query,
"api_key": os.getenv("SERPAPI_API_KEY"),
})
# 数据清洗:只提取前两条结果的标题和摘要,节省 Token
results = search.get_dict().get("organic_results", [])[:2]
return "\n".join([
f"- {r.get('title')}: {r.get('snippet')}"
for r in results
])
except Exception as e:
return f"搜索失败: {e}"
💡 代码解释
核心设计:装饰器模式
@registry.register
def google_search(query: str):
"""函数描述"""
pass
装饰器做了什么?
-
提取函数信息
- 名称:
google_search - 描述:函数的文档字符串
- 参数:从类型注解
query: str中提取
- 名称:
-
生成 JSON Schema(给 LLM 看的"说明书")
{
"type": "function",
"function": {
"name": "google_search",
"description": "当用户询问当前事件、新闻、天气或不知道的信息时,使用此工具进行搜索。",
"parameters": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "参数 query"
}
},
"required": ["query"]
}
}
}
- 存储函数(实际执行用的)
self.tools['google_search'] = google_search # 存储真实的 Python 函数
为什么用装饰器?
没有装饰器:
# 需要手动写一遍函数,再写一遍 JSON Schema
def google_search(query: str):
pass
schema = {...} # 手动写 JSON Schema,容易出错
registry.schemas.append(schema)
registry.tools['google_search'] = google_search
使用装饰器后:
# 只需写一次函数,自动生成说明书
@registry.register
def google_search(query: str):
"""函数描述"""
pass
工具执行流程:
第四步:消息标准化 - 统一通信格式
🎯 目标
创建消息辅助函数,确保消息格式标准化,避免手动构造字典出错。
📝 创建文件:message.py
# message.py
def user_msg(content):
"""用户消息"""
return {"role": "user", "content": content}
def assistant_msg(content=None, tool_calls=None):
"""
助手消息
- content: 文本回复(可选)
- tool_calls: 工具调用请求(可选)
"""
msg = {"role": "assistant"}
if content:
msg["content"] = content
if tool_calls:
msg["tool_calls"] = tool_calls
return msg
def tool_msg(tool_call_id, content):
"""
工具消息
- tool_call_id: 工具调用 ID(关键!)
- content: 工具返回结果
"""
return {
"role": "tool",
"tool_call_id": tool_call_id,
"content": str(content)
}
💡 代码解释
为什么需要标准化?
手动构造消息的问题:
# ❌ 容易出错
messages.append({"role": "user", "content": "你好"})
messages.append({"rol": "assistant", "content": "你好"}) # 拼写错误!
messages.append({"role": "tool", "content": "结果"}) # 缺少 tool_call_id!
使用辅助函数:
# ✅ 标准化,不易出错
messages.append(user_msg("你好"))
messages.append(assistant_msg(content="你好"))
messages.append(tool_msg("call_123", "搜索结果"))
核心概念:tool_call_id(快递单号)
LLM: "帮我搜索北京天气" → 生成单号 call_abc123
↓
调用搜索工具
↓
工具返回结果,附上单号 call_abc123
↓
LLM 看到单号,知道这是北京天气的搜索结果
消息类型总结:
| 函数 | role | 必需字段 | 可选字段 | 用途 |
|---|---|---|---|---|
user_msg() | “user” | content | - | 用户输入 |
assistant_msg() | “assistant” | - | content, tool_calls | LLM 的回复(文本或工具调用) |
tool_msg() | “tool” | tool_call_id, content | - | 工具执行结果 |
第五步:整合一切 - 完整的 ToolAgent
🎯 目标
将前面所有组件整合,实现具备工具调用能力的完整 Agent。
📝 创建文件:tool_agent.py
# tool_agent.py
import json
from llm import LLMClient
from tools import registry # 导入工具注册表
from message import user_msg, assistant_msg, tool_msg
class ToolAgent:
def __init__(self):
self.llm = LLMClient() # 大脑
self.tools = registry # 工具箱
# self.messages 记忆系统
self.messages = [{
"role": "system",
"content": "你是一个全能助手,可以调用工具获取实时信息。如果觉得已经足够回复用户了,就不用再调用工具了。"
}]
def chat(self, user_input):
"""
支持工具调用的 ReAct 循环入口
注意:为了演示逻辑清晰,这里暂时使用非流式
"""
# 1. 存入用户问题
self.messages.append(user_msg(user_input))
# 设置最大循环次数,防止 Agent 陷入死循环
max_turns = 5
turn_count = 0
while turn_count < max_turns:
turn_count += 1
# 2. 调用 LLM(带上 tools 定义)
print(f"\n🤖 [Thinking] 第 {turn_count} 轮思考...")
response = self.llm.chat(
messages=self.messages,
tools=self.tools.schemas # 关键:把工具说明书发给 LLM
)
response_msg = response.choices[0].message
# 3. 判断 LLM 的意图
tool_calls = response_msg.tool_calls
if tool_calls:
# === 情况 A: LLM 想要调用工具 ===
# A1. 先把 LLM 的"调用指令"存入记忆(这步不能省!)
ai_msg_dict = assistant_msg(
content=response_msg.content,
tool_calls=tool_calls
)
self.messages.append(ai_msg_dict)
# A2. 遍历所有工具调用请求(LLM 可能一次想调多个工具)
for tool_call in tool_calls:
func_name = tool_call.function.name
func_args = tool_call.function.arguments
call_id = tool_call.id
print(f"✋ [Action] 正在调用工具: {func_name} ...")
# A3. 执行真正的 Python 代码
tool_result = self.tools.execute(func_name, func_args)
# A4. 构建 Tool Message(观察结果)
# 关键:一定要带上 call_id,否则 LLM 不认账
tm = tool_msg(tool_call_id=call_id, content=tool_result)
self.messages.append(tm)
print(f"👀 [Observation] 工具返回: {tool_result[:50]}...")
# A5. 循环继续 -> 回到 while 开头,把结果发给 LLM
else:
# === 情况 B: LLM 没调工具,直接回复 ===
content = response_msg.content
self.messages.append(assistant_msg(content=content))
return content
return "❌ 任务太复杂,超过最大循环次数。"
# --- 演示代码 ---
if __name__ == "__main__":
# 确保 .env 里配置了 SERPAPI_API_KEY
agent = ToolAgent()
while True:
try:
user_input = input("\n👤 你:").strip()
# 检查用户输入
if user_input.lower() in ['exit', 'q', '退出']:
print("\n👏再见!")
break
# 如果用户输入为空,跳过
if not user_input:
continue
final_answer = agent.chat(user_input)
print("\n" + "=" * 30)
print(f"🤖 Final Answer:\n{final_answer}")
print("=" * 30)
except KeyboardInterrupt:
print("\n\n 🙏 检测到中断,退出程序")
break
except Exception as e:
print(f"\n❌ 发生错误: {e}")
import traceback
traceback.print_exc() # 打印详细的错误信息
💡 代码解释
核心设计:ReAct 模式
ReAct = Reasoning + Acting(推理 + 行动)
用户:今天北京天气怎么样?
↓
LLM 思考:我需要查询天气信息
↓
LLM 行动:调用搜索工具
↓
观察结果:北京今天晴天,25度
↓
LLM 思考:现在我有了信息,可以回答用户了
↓
LLM 行动:生成最终回复
为什么需要循环?
一次对话可能需要多次工具调用:
用户:帮我规划北京一日游
↓
第1轮:LLM 决定先了解北京天气 → 调用搜索工具
↓
第2轮:LLM 得到天气信息,继续思考需要知道景点 → 再次调用搜索
↓
第3轮:LLM 得到景点信息,综合所有信息 → 生成完整旅游规划
完整流程图:
关键设计点:
-
存储调用指令(A1)
self.messages.append(assistant_msg( content=response_msg.content, tool_calls=tool_calls ))- 必须将 LLM 的工具调用请求存入记忆
- 否则下次调用时 LLM 会忘记
-
tool_call_id(A4)
tm = tool_msg(tool_call_id=call_id, content=tool_result)- 必须带上调用 ID,让 LLM 知道结果对应哪个工具
-
防止死循环
max_turns = 5- 最多 5 轮工具调用,避免无限循环
运行与测试
🚀 运行 ToolAgent
python3 tool_agent.py
📝 示例对话
场景1:询问天气(需要调用工具)
👤 你:今天北京天气怎么样?
🤖 [Thinking] 第 1 轮思考...
✋ [Action] 正在调用工具: google_search ...
⚙️ [System] 正在调用工具: google_search 参数: {'query': '北京天气'}
👀 [Observation] 工具返回: - 北京今天天气预报: 晴天,气温25°C...
🤖 [Thinking] 第 2 轮思考...
==============================
🤖 Final Answer:
根据搜索结果,北京今天是个好天气!晴天,气温25°C,非常适合外出活动。
==============================
场景2:简单对话(不需要工具)
👤 你:你好
🤖 [Thinking] 第 1 轮思考...
==============================
🤖 Final Answer:
你好!我是全能助手,有什么可以帮助你的吗?
==============================
场景3:退出
👤 你:q
👏再见!
或使用 Ctrl+C(Mac 上是 Cmd+C)
👤 你:^C
🙏 检测到中断,退出程序
常见问题排查
❌ 问题1:ModuleNotFoundError
错误信息:
ModuleNotFoundError: No module named 'openai'
解决方法:
pip3 install -r requirements.txt
❌ 问题2:API Key 错误
错误信息:
Error code: 401 - Unauthorized
解决方法:
- 检查
.env文件中的API_KEY是否正确 - 确认没有多余空格或引号
- 重启脚本
❌ 问题3:余额不足
错误信息:
Error code: 402 - Insufficient Balance
解决方法:
- 充值 大模型 账户
- 或切换到其他 API(如 Ollama 本地模型)
❌ 问题4:搜索失败
错误信息:
搜索失败: Invalid API Key
解决方法:
- 在
.env文件中配置SERPAPI_API_KEY - 到 https://serpapi.com/ 注册获取免费 Key
❌ 问题5:无法退出
现象: 输入 q 后程序不退出
解决方法:
- 检查代码中是否有
break语句 - 确保保存了文件并重启脚本
- 使用
Ctrl+C强制退出
项目文件结构
agent/
├── llm.py # 大模型适配层
├── agent.py # 基础 Agent(有记忆)
├── tools.py # 工具注册和执行
├── message.py # 消息标准化
├── tool_agent.py # 完整的 ToolAgent
├── test_agent.py # 测试文件
├── requirements.txt # 依赖管理
└── .env # 环境变量(不要提交到 Git)
核心架构图
总结与进阶
🎯 我们实现了什么?
- ✅ 统一的大模型适配层 - 轻松切换不同厂商
- ✅ 对话记忆系统 - 实现多轮对话
- ✅ 工具注册和执行 - 让 Agent 能"做事"
- ✅ 消息标准化 - 确保通信规范
- ✅ ReAct 循环 - 推理+行动的智能决策
🚀 可以继续扩展的方向
-
添加更多工具
@registry.register def get_weather(city: str): """获取天气信息""" pass @registry.register def calculate(expression: str): """计算数学表达式""" pass -
记忆优化
- 实现记忆窗口(限制历史长度)
- 重要信息提取和总结
- 长期记忆存储(数据库)
-
任务规划
- 分解复杂任务
- 多步骤执行
- 目标导向
-
流式输出
- 实时显示 LLM 回复
- 提升用户体验
-
多模态能力
- 图像处理
- 语音交互
- 文件操作
📚 推荐学习资源
- OpenAI API 文档: https://platform.openai.com/docs
- LangChain 框架: https://langchain.com/
- AutoGPT: https://github.com/Significant-Gravitas/AutoGPT
- ReAct 论文: https://arxiv.org/abs/2210.03629
🎉 恭喜你!
如果你跟着这篇教程一步步完成了所有步骤,恭喜你已经成功构建了自己的第一个 AI Agent 框架!
这是一个简单但完整的实现,涵盖了 Agent 的核心概念。你可以基于这个框架继续探索和扩展,构建更复杂的智能应用。
有什么问题或想法?欢迎继续交流! 😊

626

被折叠的 条评论
为什么被折叠?



