在实际 AI 项目开发中,我们经常遇到这样的困境:大语言模型(LLM)虽然能理解指令并生成文本,但它无法直接操作数据库、调用外部 API 或执行一个需要多步骤决策的复杂任务。这时,AI Agent 的概念便应运而生。它不是一个单一的模型,而是一个由 LLM 作为“大脑”,结合了工具调用、任务规划、记忆和决策循环的智能系统架构。理解 Agent 的核心架构,是将其从演示概念落地为实际生产力的关键。
本文将深入解析 AI Agent 的核心架构,围绕 LLM、工具调用、循环与上下文工程这四个支柱展开。无论你是希望集成 Agent 能力到现有系统的开发者,还是对构建自主智能体感兴趣的研究者,通过理解这些核心组件如何协同工作,你将能够设计出更可靠、更强大的 AI 应用。我们将从概念入手,逐步拆解每个部分的工作原理、实现方式,并探讨在实际工程中如何将它们组合起来,构建一个能够感知、决策、行动并持续学习的智能体。
1. 理解 AI Agent 的核心架构与工作循环
AI Agent 的本质是一个能够感知环境、自主决策并执行行动以实现目标的软件实体。其核心思想是赋予 LLM “行动”的能力,使其不再仅仅是文本生成器,而是一个可以与环境(包括用户、软件系统、互联网等)交互的代理。
1.1 从“思考”到“行动”:Agent 与纯 LLM 的本质区别
一个纯粹的聊天模型,其工作流程是线性的:接收用户输入(Prompt),经过模型计算,返回文本输出。这个过程是静态的、一次性的。而一个 Agent 的工作流程是动态的、循环的。它接收目标(Goal)或观察(Observation),然后进入一个“思考-行动-观察”的循环。
这个循环通常被称为ReAct (Reasoning + Acting)模式或更广义的Planning-Acting循环。其基本步骤如下:
- 思考/规划:LLM 分析当前状态(包括历史对话、工具调用结果、任务目标),决定下一步该做什么。是直接回答用户,还是需要调用某个工具?
- 行动:如果决定调用工具,Agent 会生成符合工具调用规范的请求(如函数名称和参数)。
- 观察:执行工具调用,获取执行结果(可能是数据、成功/失败状态、错误信息)。
- 整合与再思考:将工具执行结果作为新的观察,与历史上下文一起,再次输入给 LLM,进行下一轮的思考。
这个循环会持续进行,直到 LLM 认为任务已经完成(生成了最终答案),或达到了预设的循环次数/时间限制。
1.2 核心架构四支柱
一个典型的 AI Agent 架构由以下四个相互关联的核心部分组成:
- LLM(大语言模型):作为系统的“大脑”或“推理引擎”。它负责理解任务、分解规划、决定行动策略、解析工具结果并生成最终响应。其提示词(Prompt)工程的质量直接决定了 Agent 的决策能力。
- 工具调用(Tool Calling):作为系统的“手”和“脚”。这是一组可供 LLM 调用的函数或 API,使 Agent 能够突破纯文本的界限,执行具体操作,如查询数据库、计算、调用第三方服务、操作文件等。
- 循环(Loop):作为系统的“工作流引擎”。它管理着上述“思考-行动-观察”的循环流程,控制着 Agent 的状态转换、错误处理和中止条件。这通常由一个外部的编排框架(如 LangChain、LangGraph、AutoGen)或自定义的状态机来实现。
- 上下文工程(Context Engineering):作为系统的“记忆”和“工作台”。它负责管理 Agent 与 LLM 交互时的上下文信息,包括:对话历史、工具调用记录、长期记忆(向量数据库)、当前任务状态以及精心设计的系统提示词。其目标是确保 LLM 在每一步都能获得做出正确决策所需的最相关、最精简的信息。
这四者之间的关系可以概括为:上下文工程为 LLM 提供高质量的输入信息;LLM 基于这些信息进行推理并决定调用哪个工具;循环机制负责执行工具并管理整个流程的推进;工具调用的结果又被反馈回上下文,供下一轮决策使用。
2. LLM 作为推理引擎:提示词与思维链
LLM 是 Agent 的决策核心,但其表现高度依赖于我们如何与之“沟通”,即提示词工程。在 Agent 场景下,提示词远不止是简单的任务描述。
2.1 系统提示词:定义 Agent 的角色与能力边界
系统提示词(System Prompt)在对话开始时一次性注入,用于设定 Agent 的元认知。一个用于工具调用的 Agent,其系统提示词通常包含以下部分:
你是一个专业的助理,能够通过调用工具来帮助用户解决问题。 你拥有以下工具: - `search_web(query: str)`: 使用搜索引擎查询网络信息。 - `execute_sql(sql_query: str)`: 在指定数据库上执行 SQL 查询。 - `calculate(expression: str)`: 计算数学表达式。 - `get_current_weather(city: str)`: 获取指定城市的当前天气。 请遵循以下规则: 1. 当用户问题需要实时数据、计算或外部信息时,你必须优先考虑使用工具。 2. 在决定使用工具前,先简要说明你的思考过程。 3. 调用工具时,必须严格按照提供的函数签名格式输出。 4. 根据工具返回的结果,组织你的最终答案。 5. 如果工具调用失败或结果不明确,请向用户说明情况并尝试其他方法。这个提示词明确了 Agent 的身份、可用工具、核心行为规则,为后续的交互奠定了基调。
2.2 思维链与 ReAct 格式:让思考过程可引导
为了让 LLM 更好地进行逐步推理,我们通常会在用户问题前要求其“逐步思考”,或采用结构化的 ReAct 格式。这对于多步骤任务至关重要。
非结构化思考(效果不稳定):
用户:北京和上海现在的温差是多少? AI:我需要先获取两地的当前温度。让我调用天气工具...结构化 ReAct 格式(推荐,更稳定):
思考:用户想知道北京和上海的温差。这需要两个数据:北京的当前温度和上海的当前温度。我应该先获取北京的温度。 行动:`get_current_weather`(`city`=`北京`) 观察:`{“city”: “北京”, “temperature”: 22, “unit”: “摄氏度”}` 思考:已获取北京温度22°C。现在需要上海的温度。 行动:`get_current_weather`(`city`=`上海`) 观察:`{“city”: “上海”, “temperature”: 28, “unit”: “摄氏度”}` 思考:北京22°C,上海28°C,温差为6°C。现在可以给出答案。 最终答案:北京和上海当前的温差是6摄氏度(上海比北京高6度)。在工程实现中,我们会在每次给 LLM 的提示中,自动拼接上格式化的“思考”、“行动”、“观察”历史,强制 LLM 按照这个结构进行输出,从而方便程序性地解析出“行动”部分(即工具调用指令)。
2.3 LLM 的局限与应对策略
LLM 作为推理引擎并非完美,在 Agent 架构中需注意:
- 幻觉与错误规划:LLM 可能规划出不存在或无效的工具调用步骤。需要在工具调用层进行验证,并在循环中设计重试或 fallback 逻辑。
- 上下文长度限制:复杂的任务历史会耗尽模型的上下文窗口。需要通过上下文工程进行摘要、过滤或分块存储。
- 延迟与成本:每次循环都调用 LLM 会产生显著延迟和 API 成本。对于简单或确定性的工具调用链,可以考虑缓存或预定义工作流来减少 LLM 调用次数。
3. 工具调用:扩展 Agent 的行动边界
工具调用是将 LLM 的“意图”转化为“行动”的桥梁。其核心是让 LLM 能够以结构化的方式描述它想执行的操作。
3.1 工具的定义与描述
一个工具通常对应一个后端函数。定义工具时,除了函数本身,更重要的是提供清晰、机器可读的描述,供 LLM 理解其用途和调用方式。主流框架(如 OpenAI Function Calling, LangChain Tools)都支持类似的定义方式。
以下是一个使用 Python 和 Pydantic 定义工具的示例:
from pydantic import BaseModel, Field from typing import Type # 定义工具的输入参数模型 class WeatherInput(BaseModel): city: str = Field(description="The city name, e.g. 'Beijing' or '上海'") # 定义工具函数 def get_current_weather(city: str) -> str: # 模拟调用天气API weather_data = {"Beijing": "22°C Sunny", "Shanghai": "28°C Cloudy"} return weather_data.get(city, f"Weather data for {city} not found.") # 将函数和描述封装成工具对象(以LangChain为例) from langchain.tools import StructuredTool weather_tool = StructuredTool.from_function( func=get_current_weather, name="get_current_weather", description="Fetches the current weather for a given city.", args_schema=WeatherInput # 提供参数schema,帮助LLM生成正确格式 )description和args_schema中的Field(description=...)是给 LLM 看的“说明书”,必须准确、简洁。
3.2 工具调用的执行流程
当 LLM 决定调用工具时,它会输出一个结构化的请求。框架负责解析这个请求,匹配到对应的工具函数,传入参数并执行。
- LLM 生成调用请求:LLM 输出类似
{"name": "get_current_weather", "arguments": {"city": "Beijing"}}的 JSON。 - 框架路由与执行:Agent 框架根据
name找到注册的weather_tool,将arguments反序列化为WeatherInput对象,然后调用get_current_weather(“Beijing”)函数。 - 处理结果:函数返回的结果(字符串或字典)被捕获,作为“观察”反馈给 LLM。
3.3 工具生态的设计
一个强大的 Agent 依赖于丰富的工具生态。工具可以分为几类:
- 信息获取类:搜索、查询数据库、读取文件/API。
- 逻辑执行类:计算器、代码执行器、条件判断。
- 动作执行类:发送邮件、操作 GUI、控制硬件(通常通过 API)。
- 专用领域类:金融分析、代码审查、设计生成等特定领域的工具。
在设计工具时,应遵循“单一职责”原则,每个工具功能明确,输入输出格式稳定。同时,要为工具设计良好的错误处理,将异常转换为 LLM 能理解的错误信息(如“数据库连接失败”),而不是任其抛出导致整个 Agent 崩溃。
4. 循环与状态管理:构建稳健的工作流
循环是 Agent 的“发动机”,它驱动着整个感知-决策-行动流程。一个简单的while循环足以实现基础功能,但复杂的任务需要更精细的状态管理。
4.1 基础循环模式
一个最基础的 Agent 循环伪代码如下:
def run_agent(user_query, max_steps=10): context = initialize_context(user_query) # 初始化上下文,包含系统提示和历史 for step in range(max_steps): # 1. 思考/规划:LLM基于当前上下文生成响应 llm_response = call_llm(context) # 2. 解析响应,判断是否需要行动 if requires_action(llm_response): action_name, arguments = parse_action(llm_response) # 3. 行动:执行工具 observation = execute_tool(action_name, arguments) # 4. 观察:将行动和结果追加到上下文 context.append(f"行动: {action_name}({arguments})") context.append(f"观察: {observation}") else: # LLM 生成了最终答案,结束循环 final_answer = extract_final_answer(llm_response) return final_answer # 循环达到最大步数,超时退出 return "任务未在限定步骤内完成。"4.2 复杂工作流与状态图
对于涉及条件分支、并行执行或子任务的工作流,简单的线性循环就不够了。这时需要引入状态机或图的概念。LangGraph等框架就是为此而生,它允许你将 Agent 的步骤定义为图中的节点,通过边来控制流程走向。
例如,一个客服 Agent 的工作流可能如下:
开始 -> 意图识别 -> {查询订单 | 退货申请 | 其他问题} 查询订单 -> [需要验证身份] -> 身份验证 -> 执行查询 -> 结束 退货申请 -> 收集退货信息 -> 创建工单 -> 结束 其他问题 -> 转接人工 -> 结束每个节点可以是一个 LLM 调用、一个工具调用或一个判断逻辑。LangGraph 负责维护整个图的状态(State),并根据每个节点的输出决定下一个要执行的节点。这种“静态循环”结构使得复杂、可预测的流程变得清晰和可维护。
4.3 错误处理与循环终止
循环中必须包含健壮的错误处理机制:
- 工具执行错误:网络超时、API 限流、参数错误等。应在
execute_tool中捕获异常,并将友好的错误信息作为“观察”返回给 LLM,让它有机会调整策略(例如重试或选择其他工具)。 - LLM 输出解析错误:LLM 可能返回无法解析为工具调用的文本。需要设计 fallback 策略,例如提示 LLM 重新生成,或直接返回一个默认错误信息给用户。
- 循环终止条件:避免无限循环。常见的终止条件有:
- LLM 明确输出最终答案。
- 达到最大循环步数(
max_steps)。 - 任务超时。
- 用户主动取消。
- 进入预设的终止状态(在图工作流中)。
5. 上下文工程:管理 Agent 的记忆与状态
上下文是 LLM 做出每一次决策的“依据”。随着对话和工具调用的进行,上下文会不断增长。如何高效、精准地管理上下文,是 Agent 性能优化的关键。
5.1 上下文的内容与结构
一次 LLM 调用的典型上下文结构如下:
[系统提示词] [对话历史消息1(用户/助理)] [对话历史消息2] ... [工具调用记录1(思考->行动->观察)] [工具调用记录2] ... [当前用户问题]我们需要管理的就是这个不断增长的列表。
5.2 上下文窗口限制与优化策略
所有 LLM 都有上下文长度限制(如 4K, 16K, 128K tokens)。当上下文超过限制时,最旧的信息会被丢弃。优化策略包括:
- 摘要压缩:将冗长的对话历史或工具调用结果进行摘要。例如,将十轮关于天气的问答摘要为“用户之前询问了北京、上海、广州三地本周的天气趋势”。
# 伪代码:当历史记录过长时,调用LLM进行摘要 if context_length > threshold: summary_prompt = f“请将以下对话历史总结成一段简洁的摘要:{old_history}” new_summary = call_llm(summary_prompt) # 用摘要替换掉旧的历史记录 context.replace(old_history, f“历史摘要:{new_summary}”) - 选择性记忆:并非所有历史信息都同等重要。可以设计规则,只保留与当前任务最相关的片段。向量数据库在此场景下可以发挥作用,将历史信息嵌入存储,每次只检索与当前查询最相关的几条记录注入上下文。
- 关键信息提取:从工具返回的庞大结果(如一大段 JSON 或网页内容)中,提取出关键信息再放入上下文,而不是全部塞入。
- 分窗与滑动窗口:最简单的策略是只保留最近 N 条消息或最近 M 个 token 的内容。
5.3 系统状态与短期记忆
除了对话历史,Agent 自身还需要维护一些“状态”,例如:
- 当前任务目标:用户最初请求的完整描述。
- 已完成的子任务列表。
- 临时变量:如多次工具调用中需要传递的中间结果。
这些状态不应全部塞进给 LLM 的提示词里,而应由外部的循环或状态机(如 LangGraph 的 State)来维护,只在需要时才将相关部分格式化后放入上下文。这实现了“长期记忆”与“工作记忆”的分离。
6. 实战:构建一个简单的查询 Agent
让我们结合以上概念,构建一个能够查询天气和进行简单计算的命令行 Agent。我们将使用 Python 和 LangChain 框架来简化实现。
6.1 环境准备与依赖安装
首先,确保你的 Python 环境(建议 3.8+),并安装必要依赖。我们将使用 OpenAI 的模型(需准备 API Key)和 LangChain。
pip install langchain langchain-openai langchain-community6.2 定义工具
创建agent_tools.py文件,定义两个简单的工具。
# agent_tools.py from langchain.tools import tool import requests import json @tool def get_weather(city: str) -> str: """获取指定城市的当前天气。输入应为城市名,如‘北京’。””” # 注意:此为模拟函数。真实场景应调用如 OpenWeatherMap 的 API。 # 你需要替换为真实的API调用和错误处理。 weather_map = { “北京”: “晴朗,22°C”, “上海”: “多云,28°C”, “广州”: “阵雨,25°C”, } return weather_map.get(city, f“未找到{city}的天气信息。”) @tool def calculate(expression: str) -> str: """计算一个数学表达式。输入应为字符串形式的表达式,如‘3 + 5 * 2’。””” try: # 警告:使用 eval 有安全风险,仅用于演示。生产环境应使用安全计算库如 `ast.literal_eval` 或自定义解析器。 result = eval(expression) return str(result) except Exception as e: return f“计算错误:{e}”6.3 构建 Agent 并运行循环
创建main.py文件,设置 LLM,绑定工具,并实现一个简单的 ReAct 循环。
# main.py import os from langchain_openai import ChatOpenAI from langchain.agents import AgentExecutor, create_react_agent from langchain_core.prompts import PromptTemplate from agent_tools import get_weather, calculate # 1. 设置 OpenAI API Key (请替换为你的密钥) os.environ[“OPENAI_API_KEY”] = “your-api-key-here” # 2. 初始化 LLM llm = ChatOpenAI(model=“gpt-3.5-turbo”, temperature=0) # 3. 准备工具列表 tools = [get_weather, calculate] # 4. 使用 LangChain 内置的 ReAct 提示词模板 prompt = PromptTemplate.from_template(“”” 你是一个乐于助人的助手,可以使用工具。 当你需要获取实时信息或进行计算时,请使用工具。 工具列表: {tools} 使用以下格式: 问题:用户的问题 思考:你需要思考如何一步步解决问题 行动:需要调用的工具名称,必须是[{tool_names}]中的一个 行动输入:工具的输入参数 观察:工具返回的结果 ... (这个思考/行动/观察循环可以重复多次) 思考:我现在知道了最终答案 最终答案:对用户问题的最终回答 开始! 问题:{input} {agent_scratchpad}“””) # 5. 创建 ReAct Agent agent = create_react_agent(llm, tools, prompt) # 6. 创建执行器 agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True, handle_parsing_errors=True) # 7. 运行 Agent if __name__ == “__main__”: while True: user_input = input(“\n请输入您的问题(输入‘退出’结束): “) if user_input.lower() == ‘退出’: break try: result = agent_executor.invoke({“input”: user_input}) print(f“\n助手:{result[‘output’]}”) except Exception as e: print(f“执行出错:{e}”)6.4 运行与验证
在终端运行python main.py,你将看到类似以下的交互过程(verbose=True会打印出内部思考过程):
请输入您的问题(输入‘退出’结束): 北京和上海的天气哪个更热? > 进入新的 AgentExecutor 链... 思考:用户想比较北京和上海的天气哪个更热。我需要分别获取两地的天气信息。 行动:get_weather 行动输入:北京 观察:晴朗,22°C 思考:我得到了北京的天气是22°C。现在需要上海的天气。 行动:get_weather 行动输入:上海 观察:多云,28°C 思考:北京22°C,上海28°C。28°C高于22°C,所以上海更热。 最终答案:上海(28°C)比北京(22°C)更热。 助手:上海(28°C)比北京(22°C)更热。这个简单的例子集成了四大核心组件:LLM (ChatOpenAI)、工具调用 (get_weather,calculate)、由 LangChainAgentExecutor管理的循环、以及通过PromptTemplate实现的上下文工程。
7. 常见问题排查与最佳实践
在开发和部署 AI Agent 时,你会遇到一些典型问题。以下是一些排查思路和工程建议。
7.1 常见问题排查表
| 问题现象 | 可能原因 | 检查点与解决方案 |
|---|---|---|
| Agent 不调用工具,直接回答 | 1. 系统提示词未强调必须使用工具。 2. 工具描述不清晰,LLM 不理解何时使用。 3. LLM 温度(temperature)参数过高,导致输出随机。 | 1. 强化提示词规则,如“你必须使用工具来获取实时数据”。 2. 检查并优化工具的 name和description,确保其意图明确。3. 将 temperature调低(如 0),使输出更确定。 |
| 工具调用格式错误 | 1. LLM 生成的参数格式与工具定义不匹配。 2. 工具参数 schema 定义模糊。 | 1. 在execute_tool前增加参数验证和格式化步骤。2. 使用如 Pydantic 的 BaseModel严格定义参数类型和描述。 |
| 陷入无限循环或重复调用 | 1. 工具返回的结果未能让 LLM 推进任务。 2. 缺少明确的循环终止条件。 3. 上下文混乱,LLM 忘记已执行步骤。 | 1. 优化工具返回的信息,使其更具指导性。 2. 设置最大迭代次数( max_iterations)。3. 实施上下文摘要或清理策略,移除冗余步骤记录。 |
| 处理复杂任务时性能差/成本高 | 1. 每一步都调用 LLM,token 消耗大。 2. 上下文过长,导致处理变慢。 | 1. 对于确定性高的子流程,用预定义工作流(如 LangGraph)替代 LLM 决策。 2. 应用上下文优化策略(摘要、检索)。 3. 考虑使用更小、更快的模型处理简单步骤。 |
| 工具执行失败导致 Agent 卡住 | 工具函数抛出未处理的异常。 | 在工具函数内部进行完整的异常捕获,并返回结构化的错误信息(如{“status”: “error”, “message”: “...”})供 LLM 处理。 |
7.2 生产环境最佳实践
- 可观测性与日志:详细记录每个循环的输入(LLM 提示)、输出(LLM 响应)、工具调用详情及结果。这对于调试和优化至关重要。
- 设置超时与限制:为整个 Agent 运行、每次 LLM 调用、每次工具调用设置超时。严格限制最大循环次数,防止资源耗尽。
- 用户确认与安全:对于涉及写操作、支付、发送信息等敏感工具,应在执行前设计用户确认环节,或在系统层面设置安全审批流程。
- 测试与评估:构建涵盖不同任务类型的测试用例,评估 Agent 的成功率、步骤效率和成本。使用 A/B 测试对比不同提示词或架构的效果。
- 模块化设计:将工具定义、提示词模板、循环逻辑、状态管理分离为独立模块,便于维护、测试和复用。
- 版本控制:对提示词、工具集、工作流定义进行版本控制。任何更改都可能影响 Agent 行为,需要有回滚能力。
AI Agent 的架构将大语言模型的认知能力与软件系统的执行能力相结合,开辟了人机交互的新范式。掌握其核心——LLM 推理、工具调用、循环流程和上下文管理——是构建实用 Agent 的基础。从简单的 ReAct 循环开始,逐步引入状态管理、复杂工作流和记忆优化,你可以根据实际需求设计出从自动化助手到复杂决策系统的各种智能应用。记住,一个稳健的 Agent 不仅是聪明的,更应该是可靠、可控和高效的,这需要我们在工程化的细节上投入持续的努力。