BrainBank
AI 课堂/分步指南General Knowledge

从零写一个 AI Agent:用 Python 搞懂智能体原理

2026/7/18 23:44:56 · 更新于 2026/7/18 23:46:47 · 来源

#ai-agent#tool-use#step-by-step#python#react-pattern

本文介绍了 AI Agent 的底层机制(LLM + Tools + Loop),并使用 Python 演示了如何在不依赖任何框架的情况下实现一个具备“工具调用-执行-结果回传-决策”闭环的最小可行智能体。

很多人第一次接触 Agent,往往是从 LangChain、CrewAI、AutoGen 等重度框架开始的。这些框架文档里的 Chain、Tool、Memory、Planner 等一堆抽象概念,很容易让人觉得 Agent 极其复杂、门槛极高。其实,拨开框架的外壳,Agent 的底层逻辑只有三件事:LLM 负责思考,工具负责行动,循环负责持续推进。

可以说,Agent = LLM + Tools + Loop。理解这个公式,比背诵任何框架的 API 都重要。因为框架会变,但底层的运行机制不会变。下面我们将使用最小化的 Python 代码,带你亲手把这个循环跑起来。


1. Agent 到底是什么?

普通 LLM 的调用是一次性的:用户提问,模型回答,交互结束。而 Agent 则在此基础上引入了**“行动循环”**(Action Loop):

  1. LLM 读取用户问题和当前状态。
  2. LLM 判断是否需要调用外部工具。
  3. 如果需要,程序执行工具并把执行结果返回给 LLM。
  4. LLM 基于新结果继续进行判断。
  5. 直到模型认为任务已完成,输出最终答案

这个模式在学术和工业界常被称为 ReAct(Reasoning + Acting)模式:先推理,再行动,观察结果后继续推理,直到目标达成。

Agent 循环流程图Agent 循环流程图 图1:Agent 循环流程图——用户输入进入 LLM,LLM 要么直接回答,要么调用工具;工具结果回传 LLM 继续思考,直到输出最终答案

什么时候需要 Agent? 当任务需要**“多步判断 + 外部动作”**时(如查资料、算账、调用 API、读写文件),Agent 是最佳选择。如果只是单纯的文本改写、摘要、分类,普通 LLM 调用就已经足够,无需引入复杂的 Agent 机制。


2. 用 Python 写一个最小 Agent

本示例采用 Anthropic Claude API 的 tool_use 功能来实现。OpenAI 的 Function Calling 也是完全相同的设计思想:定义工具 \rightarrow 模型选择工具 \rightarrow 程序执行工具 \rightarrow 结果回传模型。

首先,安装依赖库,并通过环境变量设置你的 API Key(切勿将 Key 直接硬编码在代码中):

pip install anthropic
export ANTHROPIC_API_KEY="你的_API_Key"

2.1 定义工具

工具不是代码函数本身,而是给模型看的**“能力说明书”**。它告诉大模型:这个工具叫什么、能解决什么问题,以及需要传入哪些格式的参数。

import re
import anthropic

client = anthropic.Anthropic()

tools = [{
    "name": "calculator",
    "description": "计算简单数学表达式,只支持数字、加减乘除和括号。",
    "input_schema": {
        "type": "object",
        "properties": {
            "expression": {
                "type": "string",
                "description": "数学表达式,例如 123 * 456 + 789"
            }
        },
        "required": ["expression"]
    }
}]

注:工具的描述越清晰,模型就越不容易选错。这里我们只放一个计算器工具,以便于看清整体链路。

2.2 执行工具

大模型本身并不会真的运行代码,它只会提出决策:“我需要调用 calculator,参数是 x”。真正负责执行这段代码的是你的 Python 主程序。

def run_tool(name: str, args: dict) -> str:
    if name != "calculator":
        return "未知工具"
    
    expr = args["expression"]
    # 使用正则表达式进行简单的白名单过滤,确保安全
    if not re.fullmatch(r"[0-9+\-*/(). ]+", expr):
        return "表达式包含不允许的字符"
    
    try:
        # 在受限环境中执行计算
        return str(eval(expr, {"__builtins__": {}}, {}))
    except Exception as e:
        return f"计算失败:{e}"

安全提示: 在生产环境中,建议使用专门的数学解析库(如 sympy),避免让模型生成的输入直接进入 eval() 等高权限执行环境。

2.3 构建 Agent 核心循环

Agent 的核心是一个 for 循环:请求模型 \rightarrow 检查是否需要调用工具 \rightarrow 执行工具 \rightarrow 将结果拼回历史记录 \rightarrow 再次请求模型。

def agent(user_input: str, max_steps: int = 5) -> str:
    messages = [{"role": "user", "content": user_input}]
    
    for _ in range(max_steps):
        response = client.messages.create(
            model="claude-opus-4-8",
            max_tokens=1024,
            tools=tools,
            messages=messages,
        )
        
        # 将模型的思考/回答记录到对话历史中
        messages.append({"role": "assistant", "content": response.content})
        
        # 如果模型不需要再调用工具,说明已经得出结论,直接返回
        if response.stop_reason != "tool_use":
            return "".join(
                block.text for block in response.content
                if block.type == "text"
            )
        
        # 否则,处理工具调用
        tool_results = []
        for block in response.content:
            if block.type == "tool_use":
                result = run_tool(block.name, block.input)
                tool_results.append({
                    "type": "tool_result",
                    "tool_use_id": block.id,
                    "content": result,
                })
        
        # 将工具执行结果作为用户视角的信息反馈给模型
        messages.append({"role": "user", "content": tool_results})
        
    return "达到最大循环次数,Agent 停止。"

# 测试运行
print(agent("123 乘以 456 再加上 789 等于多少?"))

典型运行轨迹

  1. 用户提问123 乘以 456 再加上 789 等于多少?
  2. LLM 思考:判断口算容易错,需要调用计算器。生成工具调用请求:calculator({"expression":"123*456+789"})
  3. Python 执行:接收到请求,运行 run_tool 计算得出 56877
  4. 结果回传:将 56877 传回给 LLM。
  5. LLM 总结:结合计算结果,组织语言输出最终答案:“123 乘以 456 再加上 789 等于 56877。”

Agent 运行过程Agent 运行过程 图2:Agent 运行过程——LLM 思考"需要计算器" \rightarrow 调用 calculator \rightarrow 工具返回 56877 \rightarrow LLM 组织最终回答

注意: 图中示例数值仅用于展示流程;如果你更换了数学表达式,计算结果请以程序的实际计算输出为准。

什么时候用这个最小版? 当你想快速学习原理、验证自定义工具的可行性,或者开发内部非关键性小工具时。此版本不适合直接上生产环境——它还缺少权限控制、结构化日志、重试机制、精细的上下文管理和人工确认(Human-in-the-loop)。


3. 拆解 Agent 的 4 个关键机制

机制一:工具定义(Tools Definition)

工具定义是 LLM 的“工具菜单”。模型完全依靠你提供的 namedescription 和参数 schema 来判断“能不能用”、“该不该用”以及“怎么传参”。

  • 避坑指南: 工具描述过于宽泛会导致模型误选。例如,“数据处理”就不如“计算数学表达式并返回结果”精确。工具越多,彼此之间的描述界限就要越清晰。
  • 适用场景: 只要模型需要与外部世界交互(访问数据库、读写文件、调外部 API、网络搜索、复杂计算),就必须为其定义工具。

机制二:模型决策(Model Decision)

Agent 的精髓在于它不是硬编码的流程(如“先调 A 接口,再调 B 接口”)。LLM 会根据实时上下文和工具列表,自主动态决定下一步该干什么。

  • 适用场景: 当业务路径不固定、具有分叉和不确定性时,使用 Agent。如果流程高度固定,用传统编程代码进行串联编排会更稳定、更便宜。

机制三:工具执行(Tool Execution)

在这个架构中,LLM 仅负责“决策”,主程序才负责“执行”。这一职责边界至关重要:工具的运行权限、入参校验、异常处理都应该牢牢控制在你手写的 Python 代码中。

  • 安全法则: 任何时候,都不要让模型直接执行任意的 Shell 脚本、未过滤的 SQL 语句或高权限的系统 API。

机制四:循环终止(Loop Termination)

stop_reason != "tool_use" 时,代表模型认为拿到了足够的信息,准备输出最终答案。此外,必须在代码中硬编码 max_steps(最大循环次数),防止大模型陷入逻辑死循环。

  • 适用场景: 所有 Agent 系统都必须有强制的循环上限,否则一旦遭遇模型“幻觉”或死循环,Token 消耗和调用成本将彻底失控。

4. 从原型到生产:实用 Agent 必备的三大能力

教学版 Agent 能够帮助我们快速掌握核心原理,但若要走向实际业务,必须补齐以下三层能力:

从最简 Agent 到实用 Agent 的演进从最简 Agent 到实用 Agent 的演进 图3:从最简 Agent 到实用 Agent 的演进——补循环上限、补错误回传、补上下文管理

  1. 强力的循环上限(Loop Limit): 必须用 max_steps 严格限制最大执行轮数,避免模型在“搜索 \rightarrow 总结失败 \rightarrow 再搜索”的无效路径中无限打转。
  2. 优雅的错误回传(Error Propagation): 当外部工具执行失败(如网络超时、参数报错)时,不要选择直接抛出异常崩溃或隐瞒错误。应该将详细的错误信息作为 tool_result 反馈给大模型。模型在看到“参数格式不合法”或“数据库连接超时”后,往往能自我纠错,尝试更换策略或重新格式化参数。
  3. 上下文管理(Context Management): 伴随每一轮的工具调用,对话历史(Messages)会呈指数级增长。对于短任务,保留全部历史即可;对于复杂长任务,必须引入历史截断、滚动摘要或外部向量记忆(Memory),否则很快就会超出模型的最大 Token 限制或导致调用成本剧增。

5. 框架选择:原生开发 vs 流行框架

方案适合场景优点缺点 / 坑点
原生 API + 循环原理学习、简单工具、定制化需求极度透明、易于调试、无第三方包黑盒所有基建(重试、日志、状态)需自己手写
LangChain快速原型开发、经典 RAG 应用生态庞大、开箱即用组件极多抽象层次过多、API 变化频繁、排查问题难
CrewAI明确的多角色协同任务任务与角色组织清晰,易于编写多 Agent 协作简单任务容易过度设计,增加系统复杂性
AutoGen复杂的多 Agent 自由对话适合学术研究、高度自由的协作模式开发调试链路极长,运行行为较难预测

强烈建议的学习路线: 先写原生 Agent,通过原生 API 搞懂 LLM + Tool + Loop 的流转细节;再去学习和选用框架。否则,当框架报错时,你很难分清到底是模型没选对工具、JSON Schema 写错、工具内部执行失败,还是框架封装层本身的 Bug。


6. 进阶练习与思考

如果你想继续深入掌握 Agent,建议尝试以下三个经典的动手练习:

  1. 添加文件读取工具:编写一个 read_file 工具,让 Agent 能够根据用户指示读取并分析本地的 .txt.csv 文件。
  2. 添加网络检索工具:接入一个简单的搜索 API(如 Tavily 或 DuckDuckGo),让 Agent 能够查询实时外部资料。
  3. 持久化对话历史:将 messages 历史记录保存到本地 JSON 文件或 SQLite 数据库中,实现一个具有“持久记忆”的 Agent。

完成这三个练习后,你会更加深刻地理解 Agent 的核心精髓:模型负责决策,工具负责行动,循环负责推进,边界负责安全。


7. 参考资料


核心要点

  • Agent 的极简公式extAgent=extLLM+extTools+extLoop ext{Agent} = ext{LLM} + ext{Tools} + ext{Loop}。其中大模型充当决策大脑,Python 程序充当执行肢体。
  • ReAct 运行模式:Agent 的本质是“推理(Reasoning)”与“行动(Acting)”的交替循环,模型根据工具返回的结果持续修正并推进任务。
  • 安全与控制边界:大模型仅负责生成“调用工具的意图”,具体工具的权限控制、安全校验与代码执行必须由宿主程序严格把关。
  • 生产环境三要素:一个可落地的实用 Agent 必须具备循环上限控制工具错误回传自我纠错以及精细的上下文与记忆管理

学习地图

第一阶段:原理与概念建立

  • 理解核心公式:掌握 Agent = LLM + Tools + Loop 的核心,明确智能体与单次大模型调用的区别。
  • ReAct 范式:学习 Reasoning + Acting 机制,理解“思考-行动-观察”的循环。

第二阶段:原生 Tool Calling 实践

  • 工具定义 (Schema):学习使用 JSON Schema 描述函数,这是模型理解工具的唯一接口。
  • 模型决策解析:学会如何从 API 响应中提取 tool_use 决策信息。

第三阶段:构建 Agent 闭环循环

  • 状态维护:维护对话历史(messages),不断将工具执行结果作为新上下文喂给模型。
  • 安全与终止控制:设定最大循环次数(max_steps),对执行工具进行权限校验,防止失控死循环。

第四阶段:工程化演进

  • 错误回传机制:工具运行失败时学会将错误格式化并反馈给大模型,让其具备自我纠错能力。
  • 生产框架评估:在理解底层原理后,按需接入 LangChain 或 CrewAI 等高层框架以提高开发效率。

动手实践——分步指南

  1. 初始化开发环境: 安装 Anthropic SDK 并配置环境变量:
pip install anthropic
export ANTHROPIC_API_KEY='your_api_key_here'
  1. 定义工具描述 (Schema): 在 Python 中定义 calculator 工具,使用标准 JSON Schema 结构描述输入参数:
tools = [{
    'name': 'calculator',
    'description': '计算简单数学表达式,只支持数字、加减乘除和括号。',
    'input_schema': {
        'type': 'object',
        'properties': {
            'expression': {
                'type': 'string',
                'description': '例如 123 * 456'
            }
        },
        'required': ['expression']
    }
}]
  1. 实现工具执行器: 编写实际计算逻辑,对模型输入的表达式进行安全字符限制,防范远程代码注入:
import re
def run_tool(name: str, args: dict) -> str:
    if name != 'calculator': return '未知工具'
    expr = args['expression']
    if not re.fullmatch(r'[0-9+\-*/(). ]+', expr):
        return '错误:存在不合法字符'
    try:
        return str(eval(expr, {'__builtins__': {}}, {}))
    except Exception as e:
        return f'计算失败: {e}'
  1. 编写核心 Loop 控制器: 实现一个 agent 函数,通过 for 循环驱动多轮对话。如果模型返回 stop_reason == 'tool_use',则执行工具,将结果拼入消息历史重新请求;否则直接返回最终自然语言答案:
def agent(user_input: str, max_steps: int = 5):
    messages = [{'role': 'user', 'content': user_input}]
    for _ in range(max_steps):
        response = client.messages.create(model='claude-3-5-sonnet-20241022', max_tokens=1024, tools=tools, messages=messages)
        messages.append({'role': 'assistant', 'content': response.content})
        if response.stop_reason != 'tool_use':
            return response.content[0].text
        # 解析工具调用并执行
        for block in response.content:
            if block.type == 'tool_use':
                result = run_tool(block.name, block.input)
                messages.append({'role': 'user', 'content': [{
                    'type': 'tool_result',
                    'tool_use_id': block.id,
                    'content': result
                }]})
  1. 运行与测试: 实例化 client 并调用 agent('123 乘以 456 再加上 789 等于多少?'),观察控制台输出,验证完整的工具调用循环。

三大推荐资源

  1. 1
    Anthropic Tool Use Documentation

    Anthropic 官方关于工具调用(Tool Use)的完整指南,详细解释了模型如何决定使用工具及如何处理返回结果。

    https://docs.anthropic.com/en/docs/build-with-claude/tool-use

  2. 2
    OpenAI Function Calling Guide

    OpenAI 官方提供的函数调用开发指南,是行业通用的智能体工具调用标准实现方案。

    https://platform.openai.com/docs/guides/function-calling

  3. 3
    ReAct: Synergizing Reasoning and Acting in Language Models

    介绍 ReAct 框架的经典学术论文,阐述了将推理(Reasoning)和行动(Acting)结合的核心原理,是现代 Agent 架构的理论基石。

    https://arxiv.org/abs/2210.03629

链接由 AI 推荐——使用前建议快速核实。