30行代码,一个循环:这就是AI Agent的核心秘密—Agent Loop

37 阅读8分钟

1. 前言

我们设计一个“寻宝游戏”(也就是链式依赖任务):三个文件形成线索链,大模型必须依次读取它们,才能拼出最终密码。

三个相互关联的文件内容设计如下:

step1.txt 内容如下:

线索1:请去读取 step2.txt 文件获取下一步指示。

step2.txt 内容如下:

线索2:做得好!密码的前半部分是 'Agent',请继续读取 step3.txt 获取后半部分。

step3.txt 内容如下:

线索3:密码的后半部分是 'Loop'。请将密码拼接后告诉用户最终的完整密码是什么。

我们在上一篇文章中已经实现了大模型对文件内容的读取,我们现在使用上一篇实现的功能执行以下内容:

请帮我读取 step1.txt 文件,并严格按照文件中的线索一步步寻找,直到拼凑出最终的完整密码告诉我。

结果如下:

image.png

我们看到只执行了第一步就不再执行了。

所以我们发现上一篇文章实现的功能,大模型是无法在不需人工干预的情况下自主执行多步工具的调用。

所以我们需要一个能反复调用工具、观察结果并继续决策的循环体,这就是 Agent Loop

2. 为什么需要 Agent Loop ?

大型语言模型(LLM)在代码生成、逻辑推理等方面表现惊人。然而,它们本质上是一个“静态”的推理引擎:只能基于已有的上下文生成文本,无法主动与外部世界交互——不能执行命令、读取文件、运行测试、查看报错信息等。为了让大模型操作外部工具,OpenAI 提出了 Function Calling(工具调用)机制,我们也在上一篇文章分析和实现了这个功能。

很明显我们不但需要实现大模型调用工具,还需要实现一个能反复调用工具、观察结果并继续决策的循环体。

因为从前言中的例子我们可以知道在没有循环调用工具的情况下,我们只能手动将工具的输出粘贴回对话中,充当人肉中转站。这不仅低效,更违背了“自主智能体”的初衷。

而 Agent Loop 正是为了解决这一问题而应运而生:将模型与工具连接起来,让模型根据环境反馈自主决定下一步动作,直到任务完成。

那么怎么实现呢?

3. 核心概念:一个循环 + 工具

Agent Loop 的架构极其简洁:

while 模型请求调用工具:
    执行工具调用
    将结果反馈给模型

它的运行流程则可以用下图表示:

+----------+      +-------+      +---------+
|  用户    | ---> |  LLM  | ---> |  工具   |
|  prompt  |      |       |      |  执行    |
+----------+      +---+---+      +----+----+
                     ^                |
                     |    工具结果     |
                     +----------------+
                     (循环到模型不再调用工具)

大模型每次生成响应后,我们需要检查它是否要求调用工具。如果有,就执行对应工具,将结果以“工具消息”的形式追加到对话历史中,然后再次调用大模型。大模型在新的上下文中继续推理,可能再次调用工具,也可能直接给出最终答案。

4. 代码实现

下面我们基于 Python 详细解析 Agent Loop 的实现。

4.1 环境准备

import os
import json
from pathlib import Path
from dotenv import load_dotenv
from openai import OpenAI
# 加载环境变量(如 DEEPSEEK_API_KEY)
load_dotenv()
# ---------- 初始化客户端 ----------
# 创建 OpenAI 客户端实例,使用 DeepSeek API 密钥和基础 URL
client = OpenAI(
    api_key=os.getenv("DEEPSEEK_API_KEY"),
    base_url="https://api.deepseek.com"
)

加载环境变量,初始化 OpenAI 客户端。

4.2 工具定义

# ---------- 工具定义 ----------
tools = [
    {
        "type": "function",
        "function": {
            "name": "read_file",
            "description": "读取文本文件内容。",
            "parameters": {
                "type": "object",
                "properties": {
                    "path": {"type": "string", "description": "要读取的文件路径"},
                    "encoding": {"type": "string", "enum": ["utf-8", "gbk"], "description": "文件编码格式"}
                },
                "required": ["path"]
            }
        }
    }
]

这里使用 OpenAI 的 function calling 格式定义了一个 read_file 工具。工具描述了名称、用途和参数,大模型会根据这些信息决定何时调用、传什么参数。

4.3 工具实现

# ---------- 工具实现 ----------
class ReadFileTool:
    def execute(self, path: str, encoding: str = "utf-8") -> str:
        try:
            file_path = Path(path).expanduser()
            if not file_path.exists():
                return f"❌ 文件不存在: {path}"
            return file_path.read_text(encoding=encoding)
        except Exception as e:
            return f"❌ 读取失败: {str(e)}"

file_tool = ReadFileTool()

工具的执行逻辑被封装在 ReadFileTool 类中。它接收路径和编码,返回文件内容或错误信息。错误信息也作为正常输出返回,让模型能处理异常情况。

上面几个步骤,我们在前面的文章已经实现过了,相信大家不会陌生了。接下来才是本篇文章的重点。

4.4 核心循环:Agent Loop

# -- 核心模式:一个不断调用工具的 while 循环,直到模型停止 --
def agent_loop(messages: list):
    """
    核心代理循环:
    不断地调用大模型,如果模型返回了工具调用,则执行工具并将结果发回给模型,
    直到模型不再需要调用工具并返回最终文本回复为止。
    """
    while True:
        # 调用大模型,传入历史消息、工具列表,并让模型自动决定是否调用工具
        response = client.chat.completions.create(
            model="deepseek-chat", # 使用的模型名称
            messages=messages,
            tools=tools,
            tool_choice="auto"
        )
        msg = response.choices[0].message
        
        # 将助手的回复(可能包含工具调用,也可能是普通文本)添加到历史记录中
        messages.append(msg)
        
        # 如果模型没有调用工具,说明已完成任务,返回模型回复的文本内容
        if not msg.tool_calls:
            return msg.content

        # 否则,模型决定调用工具,遍历所有的工具调用
        for tool_call in msg.tool_calls:
            if tool_call.function.name == "read_file":
                # 解析模型生成的工具调用参数
                args = json.loads(tool_call.function.arguments)
                print(f"\033[33m🔧 调用工具: {tool_call.function.name}, 参数: {args}\033[0m")
                # 执行工具函数
                result = file_tool.execute(**args)
                # 打印工具执行结果的前200个字符
                print(f"✅ 工具执行结果:\n{result[:200]}\n")
                # 将工具执行结果作为 tool 类型的消息追加到历史记录中
                messages.append({
                    "role": "tool",
                    "tool_call_id": tool_call.id,
                    "name": tool_call.function.name,
                    "content": result
                })

上述代码便是 Agent Loop 的核心实现,主要功能如下:

  • 循环调用大模型,每次传入最新的消息列表和工具定义。
  • 将模型的响应(msg)追加到历史。
  • 检查 tool_calls:如果没有,返回 msg.content 结束循环。
  • 如果有工具调用,遍历每个调用,解析参数,执行对应工具,并将结果以 tool 角色消息加入历史(包含 tool_call_id 以便关联)。
  • 循环继续,大模型看到工具结果,会继续分析并决定下一步。

4.5 交互终端

if __name__ == "__main__":
    # 初始化历史消息列表,包含系统提示词,系统提示词,用于指导助手的行为
    history = [
        {"role": "system", "content": "你是一个文件读取助手,必要时可以调用工具帮助用户读取文件内容。"}
    ]
    # 启动交互式终端会话
    while True:
        try:
            # 获取用户输入
            query = input("\033[36m用户 >> \033[0m")
        except (EOFError, KeyboardInterrupt):
            # 处理 Ctrl+D 或 Ctrl+C 退出的情况
            break
        # 处理正常退出的输入
        if query.strip().lower() in ("q", "exit", "退出"):
            break
        
        # 将用户输入追加到历史记录中
        history.append({"role": "user", "content": query})
        # 启动代理循环进行对话
        final_answer = agent_loop(history)
        
        # 打印助手给出的最终答案
        if final_answer:
            print(f"\033[32m助手: {final_answer}\033[0m\n")

交互终端主循环并支持多轮对话,每次用户输入后都调用 agent_loop,并打印大模型的最终答案。整个程序就是一个可在终端交互的自主文件读取助手。

5. 链式依赖任务测试

我们在在终端启动程序后,输入以下内容:

请帮我读取 step1.txt 文件,并严格按照文件中的线索一步步寻找,直到拼凑出最终的完整密码告诉我。

执行结果如下:

image.png

我们可以看到整个过程是完全自主的,大模型根据每次工具返回的线索,动态规划下一步,直到任务完成,这就是 AI Agent 的核心功能 — Agent Loop。

6. Agent Loop 工作原理总结

一个典型的 Agent Loop 包含以下步骤:

  1. 用户输入:用户的消息作为第一条消息加入历史中。

  2. 调用模型:将完整对话历史(包括系统提示)和工具定义发送给 LLM。

  3. 解析响应

    • 如果模型没有调用工具(tool_calls 为空),则返回最终内容,循环结束。
    • 如果模型调用了工具,则执行对应工具。
  4. 执行工具:遍历所有工具调用,运行对应的工具函数,收集结果。

  5. 反馈结果:将每个工具的结果包装成 tool 角色的消息,追加到历史中。

  6. 回到步骤2:模型在新的上下文中再次推理。

这个过程一直持续,直到大模型认为任务完成,不再请求工具。这就是自主智能体的最小闭环

7. 总结

我们用不到30行代码,实现了将大模型的推理能力与外部工具的执行能力无缝连接,使大模型能够根据环境反馈自主决策,完成多步复杂的任务。这就是 AI 智能体的最小核心模式 —— Agent Loop

无论是简单的文件读取,还是复杂的代码编写、代码运行,其背后都是同一个逻辑原理:模型 → 工具 → 结果 → 模型。理解并掌握这个逻辑原理,你就掌握了智能体设计的钥匙。在此基础上,你可以添加策略、记忆、多智能体协作等高级特性,但核心永远不会变——Agent Loop

一个工具 + 一个循环 = 一个智能体,就是智能体的核心秘密。

我是 Cobyte,欢迎添加 v: icobyte,学习交流 AI 全栈。