从零开始构建一个简单的 AI Agent 框架:完整新手教程

本教程将带你从零开始,一步步构建一个具备工具调用能力的简单 Agent 框架。即使你是编程新手,也能跟着教程完成并能成功运行代码。


📚 目录

  1. 什么是 Agent?
  2. 准备工作
  3. 第一步:构建"大脑"- 统一的大模型适配层
  4. 第二步:实现基础 Agent - 对话记忆系统
  5. 第三步:装备工具 - 让 Agent 能"做事"
  6. 第四步:消息标准化 - 统一通信格式
  7. 第五步:整合一切 - 完整的 ToolAgent
  8. 运行与测试
  9. 常见问题排查
  10. 总结与进阶

什么是 Agent?

🤖 概念理解

Agent(智能体) = 大模型(大脑)+ 记忆系统 + 工具调用能力 + 任务规划能力

比喻:

  • 大模型 = 大脑(思考、理解、决策)
  • 记忆系统 = 记忆(记住对话历史)
  • 工具调用 = 手脚(执行实际任务,如搜索、计算)
  • 任务规划 = 规划能力(分解复杂任务)

🎯 我们的实现目标

构建一个简单的 Agent,具备以下能力:

  1. ✅ 与用户对话并保持上下文记忆
  2. ✅ 调用 Google 搜索工具获取实时信息
  3. ✅ 自动决定何时调用工具
  4. ✅ 综合工具结果生成最终答案

准备工作

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)

💡 代码解释

核心设计思想:

  1. 环境变量配置

    api_key=os.getenv("API_KEY")
    base_url=os.getenv("BASE_URL")
    
    • 将敏感信息(API Key)放在 .env 文件中
    • 避免硬编码在代码里,提高安全性
  2. 统一接口

    def chat(self, messages, tools=None):
        return self.client.chat.completions.create(**params)
    
    • 封装 OpenAI API 的调用细节
    • 提供简单易用的 chat() 方法
    • 支持 tools 参数,让 Agent 能调用工具
  3. 易于切换厂商

    • 只需修改 .env 中的 BASE_URLMODEL_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": "你好小明,很高兴认识你!"}

工作流程(三步闭环):

用户输入

存入记忆

调用 LLM
带上完整历史

获取回答

存入记忆

返回给用户

为什么需要记忆?

  • 大模型本身是无状态的,每次调用都是独立的
  • 通过传递历史消息,让 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

装饰器做了什么?

  1. 提取函数信息

    • 名称:google_search
    • 描述:函数的文档字符串
    • 参数:从类型注解 query: str 中提取
  2. 生成 JSON Schema(给 LLM 看的"说明书")

{
  "type": "function",
  "function": {
    "name": "google_search",
    "description": "当用户询问当前事件、新闻、天气或不知道的信息时,使用此工具进行搜索。",
    "parameters": {
      "type": "object",
      "properties": {
        "query": {
          "type": "string",
          "description": "参数 query"
        }
      },
      "required": ["query"]
    }
  }
}
  1. 存储函数(实际执行用的)
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

工具执行流程:

LLM 决定调用工具

返回工具调用指令

ToolRegistry.execute

解析参数 JSON

执行真实的 Python 函数

返回结果给 LLM


第四步:消息标准化 - 统一通信格式

🎯 目标

创建消息辅助函数,确保消息格式标准化,避免手动构造字典出错。

📝 创建文件: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_callsLLM 的回复(文本或工具调用)
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 得到景点信息,综合所有信息 → 生成完整旅游规划

完整流程图:

工具 LLM ToolAgent 用户 工具 LLM ToolAgent 用户 loop [ReAct 循环] "今天北京天气怎么样?" 添加用户消息到记忆 发送 messages + tools 思考:需要搜索天气 返回 tool_calls 存储调用指令 执行 google_search 返回搜索结果 存储 tool_msg 结果 再次发送完整 messages 思考:现在有信息了,可以回答 返回最终答案 存储 assistant 回复 返回最终答案

关键设计点:

  1. 存储调用指令(A1)

    self.messages.append(assistant_msg(
        content=response_msg.content,
        tool_calls=tool_calls
    ))
    
    • 必须将 LLM 的工具调用请求存入记忆
    • 否则下次调用时 LLM 会忘记
  2. tool_call_id(A4)

    tm = tool_msg(tool_call_id=call_id, content=tool_result)
    
    • 必须带上调用 ID,让 LLM 知道结果对应哪个工具
  3. 防止死循环

    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

解决方法:

  1. 检查 .env 文件中的 API_KEY 是否正确
  2. 确认没有多余空格或引号
  3. 重启脚本

❌ 问题3:余额不足

错误信息:

Error code: 402 - Insufficient Balance

解决方法:

  1. 充值 大模型 账户
  2. 或切换到其他 API(如 Ollama 本地模型)

❌ 问题4:搜索失败

错误信息:

搜索失败: Invalid API Key

解决方法:

  1. .env 文件中配置 SERPAPI_API_KEY
  2. 到 https://serpapi.com/ 注册获取免费 Key

❌ 问题5:无法退出

现象: 输入 q 后程序不退出

解决方法:

  1. 检查代码中是否有 break 语句
  2. 确保保存了文件并重启脚本
  3. 使用 Ctrl+C 强制退出

项目文件结构

agent/
├── llm.py           # 大模型适配层
├── agent.py         # 基础 Agent(有记忆)
├── tools.py         # 工具注册和执行
├── message.py       # 消息标准化
├── tool_agent.py    # 完整的 ToolAgent
├── test_agent.py    # 测试文件
├── requirements.txt # 依赖管理
└── .env            # 环境变量(不要提交到 Git)

核心架构图

外部系统

ToolAgent 核心组件

决策

调用

存储

使用

LLMClient
大脑

ToolAgent
思考控制器

ToolRegistry
工具箱

Messages
记忆系统

Message Helper
消息标准化

Google 搜索 API

DeepSeek/其他模型


总结与进阶

🎯 我们实现了什么?

  1. 统一的大模型适配层 - 轻松切换不同厂商
  2. 对话记忆系统 - 实现多轮对话
  3. 工具注册和执行 - 让 Agent 能"做事"
  4. 消息标准化 - 确保通信规范
  5. ReAct 循环 - 推理+行动的智能决策

🚀 可以继续扩展的方向

  1. 添加更多工具

    @registry.register
    def get_weather(city: str):
        """获取天气信息"""
        pass
    
    @registry.register
    def calculate(expression: str):
        """计算数学表达式"""
        pass
    
  2. 记忆优化

    • 实现记忆窗口(限制历史长度)
    • 重要信息提取和总结
    • 长期记忆存储(数据库)
  3. 任务规划

    • 分解复杂任务
    • 多步骤执行
    • 目标导向
  4. 流式输出

    • 实时显示 LLM 回复
    • 提升用户体验
  5. 多模态能力

    • 图像处理
    • 语音交互
    • 文件操作

📚 推荐学习资源

  • 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 的核心概念。你可以基于这个框架继续探索和扩展,构建更复杂的智能应用。

有什么问题或想法?欢迎继续交流! 😊


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值