深入理解LLM Agent:从原理到实战
引人入胜的开篇
想象一下,你正在开发一个智能客服系统。用户提出一个复杂问题:“帮我查一下上周五(不是假期)上海的天气,然后根据天气情况推荐一道适合做的菜,并告诉我这道菜的详细做法。” 传统的LLM(大语言模型)能直接给出答案吗?
或许它能尝试回答,但很可能出现以下问题:
- 信息不准确:无法联网获取实时或历史天气数据。
- 逻辑断裂:无法将天气信息与菜谱推荐进行有效关联。
- 步骤遗漏:可能只推荐菜名,却忘了提供做法。
这便是我们常说的LLM的“幻觉”或“能力边界”问题。它们强大的文本生成能力,在面对需要多步骤规划、外部信息查询和复杂逻辑推理的任务时,往往显得力不从心。这就像你给了一个聪明但足不出户的百科全书,它知道很多知识,但无法帮你打电话、上网搜索或动手操作。
问题代码示例:一个“力不从心”的传统LLM调用
import openai
# 假设我们只用OpenAI的Completion API,没有集成外部工具
def simple_llm_query(query):
try:
response = openai.Completion.create(
model="gpt-3.5-turbo-instruct", # 较旧的文本补全模型,便于演示局限性
prompt=f"请回答以下问题:\
\
{query}",
max_tokens=200
)
return response.choices[0].text.strip()
except Exception as e:
return f"调用LLM出错:{e}"
user_query = "帮我查一下上周五(不是假期)上海的天气,然后根据天气情况推荐一道适合做的菜,并告诉我这道菜的详细做法。"
print(f"用户提问:{user_query}\
")
llm_response = simple_llm_query(user_query)
print(f"传统LLM回复:\
{llm_response}\
")
# 预期输出:LLM可能直接编造天气或菜谱,或者表示无法联网查询,缺乏实际操作能力。
# 我们需要的是一个能像人类一样“思考”和“行动”的智能体,这正是LLM Agent的用武之地!
为了解决这些痛点,让LLM从“会说话的大脑”升级为“能思考能行动的智能体”,LLM Agent应运而生。它赋予了LLM规划、记忆、工具使用乃至自我反思的能力,使其能够分解复杂任务、调用外部工具获取最新信息,甚至像人类一样逐步解决问题。在本文中,我们将深入探索LLM Agent的原理,并通过丰富的代码示例,手把手教你构建一个功能强大的智能体。准备好了吗?让我们一起开启LLM Agent的奇妙世界!
核心内容组织
1. 什么是LLM Agent?核心概念解析
LLM Agent,顾名思义,是基于大语言模型构建的“智能代理”或“智能体”。它不仅仅是简单地根据输入生成输出,而是具备了ReAct(Reasoning + Acting)框架的核心思想:
- Reasoning (思考/推理):LLM根据当前任务目标、观察到的信息和可用的工具,生成下一步的行动计划或推理路径。它会像人类一样思考:“我需要做什么?有哪些步骤?应该使用哪个工具?”
- Acting (行动/执行):根据思考的结果,LLM决定调用哪个外部工具,并执行相应的操作。例如,搜索网页、执行代码、调用API等。执行后,Agent会观察(Observation)工具返回的结果,并将其作为下一次推理的依据。
这个“思考-行动-观察”的循环是Agent工作的核心。与传统的Chain(链式调用)不同,Agent的行动路径是动态的、非预设的,它能根据中间结果调整策略,展现出更强的通用性和适应性。
代码示例:一个Agent的“思考-行动”循环概览(伪代码)
# 伪代码:Agent的思考-行动循环
class LLMAgent:
def init(self, llm, tools, memory=None):
self.llm = llm
self.tools = tools # 可用的工具集合
self.memory = memory # 记忆模块
def run(self, task_description):
thought_history = []
while not self.is_task_completed(task_description, thought_history):
# 1. 观察:获取当前环境和历史信息
current_observation = self.get_current_observation(thought_history)
# 2. 思考(Reasoning):LLM根据任务、工具和观察结果进行推理
# 它会生成一个思考过程(Thought)和下一步的行动计划(Action)
thought, action = self.llm.think_and_plan(
task_description,
current_observation,
self.tools.available_tools(),
self.memory.retrieve() if self.memory else None
)
thought_history.append((thought, action))
print(f" 思考:{thought}")
print(f" 计划行动:{action.name}({action.args})\
")
# 3. 行动(Acting):执行LLM规划的行动
tool_output = self.execute_action(action)
thought_history.append((f"工具输出:{tool_output}", None))
print(f" 工具输出:{tool_output}\
")
# 4. 更新记忆(可选)
if self.memory:
self.memory.add_experience(thought, action, tool_output)
# 检查任务是否完成,或是否陷入循环
if self.should_stop(thought_history):
break
final_answer = self.llm.synthesize_final_answer(task_description, thought_history)
return final_answer
# ... 辅助方法如 is_task_completed, get_current_observation, execute_action, should_stop ...
# 不推荐:直接调用LLM,无法根据反馈调整
# def bad_approach(task):
# # 一次性Prompt,期望LLM包办一切,不具备自我修正能力
# response = llm.generate(f"请一步到位完成任务:{task}")
# return response
代码说明:这段伪代码展示了Agent的核心循环。与传统的一次性Prompt不同,Agent会持续“思考-行动-观察”,直到任务完成。llm.think_and_plan是关键,它驱动了Agent的智能行为。而execute_action则负责调用实际的工具。
2. LLM Agent的核心组件:规划(Planning)
规划能力是LLM Agent区别于传统LLM的关键。它使得Agent能够将一个复杂任务分解为一系列可管理的子任务,并确定执行这些子任务的顺序和所需工具。常见的规划策略包括:
- Chain of Thought (CoT):通过让LLM在给出最终答案之前,先生成一系列中间推理步骤,从而提升其复杂推理能力。
- Tree of Thought (ToT):将CoT进一步扩展,探索多个推理路径,并在每个步骤中评估不同的选项,选择最佳路径。
- ReAct Prompting:结合思考(Thought)和行动(Action)指令,引导LLM在每一步都明确其意图和将要执行的操作。
代码示例:使用LangChain构建一个ReAct风格的规划Prompt
# 导入必要的库
from langchain_openai import ChatOpenAI
from langchain.prompts import ChatPromptTemplate, HumanMessagePromptTemplate, SystemMessagePromptTemplate
from langchain.schema import HumanMessage, SystemMessage
# 假设我们有一个LLM实例
llm = ChatOpenAI(model="gpt-4o", temperature=0) # 使用最新的模型,确保推理能力
# 推荐写法:ReAct风格的规划Prompt
def create_react_prompt():
system_template = (
"你是一个强大的AI助手,可以根据用户的需求,"\
"通过思考和调用工具来解决问题。"\
"请遵循以下格式:\
\
"\
"Thought: 我现在应该做什么?\
"\
"Action: tool_name[input]\
"\
"Observation: 工具的输出\
"\
"... (重复 Thought/Action/Observation 直到任务完成)\
"\
"Thought: 我已经完成了所有步骤,现在可以给出最终答案了。\
"\
"Final Answer: 最终的答案。\
\
"\
"可用的工具如下:\
{tools}\
"\
"请开始你的思考和行动。" # 引入tools变量,Agent会根据实际可用的工具进行规划
)
human_template = "{input}"
# 使用ChatPromptTemplate结合System和Human消息
prompt = ChatPromptTemplate.from_messages([
SystemMessagePromptTemplate.from_template(system_template),
HumanMessagePromptTemplate.from_template(human_template),
])
return prompt
# 不推荐:简单、一次性Prompt,缺乏规划能力
# def create_simple_prompt():
# return ChatPromptTemplate.from_template(
# "请直接回答:{input}。不要思考,不要使用工具。" # 剥夺了LLM的规划和工具使用能力
# )
# 示例:如何使用这个Prompt
# 实际运行时,{tools} 和 {input} 会被Agent框架填充
# print(create_react_prompt().format_messages(tools="calculator, search", input="帮我计算 123 * 456"))
# 简单展示Prompt效果 (仅为演示,实际Agent运行更复杂)
# 假设我们只传入一个简单的指令,不实际运行工具
print(" ReAct Prompt 示例:")
react_prompt_content = create_react_prompt().format_messages(
tools="CalculatorTool, SearchTool",
input="今天的日期是?然后计算 100 减去 50 再乘以 2 的结果。"
)[0].content # 获取SystemMessage的内容,模拟LLM接收到的指令
# 我们可以手动模拟LLM的思考过程,尽管这里不会真正执行
print(f"\
--- LLM接收到的指令示例 ---\
{react_prompt_content}\
")
# 期望LLM能够按照Thought/Action的格式给出规划,而不是直接给出答案。
代码说明:ReAct Prompt通过明确的格式引导LLM进行结构化思考和行动。SystemMessageTemplate 定义了Agent的行为规范、可用工具和输出格式,这对于控制Agent行为至关重要。{tools}占位符会被实际的工具列表填充,让LLM知道它有哪些“武器”可以使用。这种方式能显著提升Agent处理复杂任务的能力。
3. LLM Agent的核心组件:工具使用(Tool Usage/Acting)
工具有限的LLM就像被捆住手脚的巨人。Agent通过集成外部工具,扩展了其感知和行动能力,使其能够:
- 获取最新信息:通过搜索引擎(如Google Search)访问互联网。
- 执行计算:通过计算器工具进行精确数学运算。
- 与外部系统交互:调用API(如天气API、数据库API、CRM系统)。
- 执行代码:运行Python解释器来处理复杂逻辑或数据。
设计良好的工具是Agent成功的关键。工具应该有清晰的名称、详细的描述以及明确的输入参数,这样LLM才能正确地理解和使用它们。
代码示例:定义一个计算器工具和一个搜索引擎工具(LangChain实现)
# 导入必要的库
from langchain.tools import BaseTool, tool
from typing import Type, Optional
from pydantic import BaseModel, Field
import requests
# 推荐写法:定义一个自定义的计算器工具
# 使用@tool装饰器简化工具定义,或继承BaseTool
class CalculatorInput(BaseModel):
expression: str = Field(description="数学表达式,例如 '1+1' 或 '2*3'")
@tool("calculator", args_schema=CalculatorInput)
def calculate(expression: str) -> str:
"""用于执行数学计算,例如加减乘除、幂运算等。\
输入必须是有效的Python数学表达式。"""
try:
# 使用eval存在安全风险,实际生产环境应使用更安全的沙箱或表达式解析库
result = str(eval(expression))
return f"计算结果:{result}"
except Exception as e:
return f"计算错误:{e}"
# 推荐写法:定义一个搜索引擎工具 (以Serper API为例,需要API Key)
# 实际使用时,请替换为你的Serper API Key或其他搜索引擎API
class SearchInput(BaseModel):
query: str = Field(description="要搜索的关键词或短语")
@tool("search_web", args_schema=SearchInput)
def search_web(query: str) -> str:
"""通过搜索引擎查询互联网上的信息。\
适用于查找最新事件、实时数据、背景知识等。"""
try:
# 替换为你的Serper API Key
SERPER_API_KEY = "YOUR_SERPER_API_KEY" # 请替换为你的实际API Key
if SERPER_API_KEY == "YOUR_SERPER_API_KEY":
return "Serper API Key 未配置,无法执行搜索。请替换为你的实际Key。"
headers = {
'X-API-KEY': SERPER_API_KEY,
'Content-Type': 'application/json'
}
payload = {"q": query}
response = requests.post('https://google.serper.dev/search', headers=headers, json=payload)
response.raise_for_status() # 检查HTTP响应状态码
search_results = response.json()
# 提取关键信息,避免返回过长的JSON
snippets = [item['snippet'] for item in search_results.get('organic', []) if 'snippet' in item]
if snippets:
return "\
".join(snippets[:3]) # 返回前3条搜索结果的摘要
else:
return "未找到相关搜索结果。"
except requests.exceptions.RequestException as e:
return f"调用Serper API失败:{e}"
except Exception as e:
return f"搜索工具内部错误:{e}"
# 不推荐:工具描述不清或参数模糊
# class BadTool(BaseTool):
# name = "通用工具"
# description = "这个工具能做很多事情。" # 描述过于模糊,LLM无法正确选择和使用
# def _run(self, query: str) -> str:
# return "..."
# def _arun(self, query: str) -> str:
# raise NotImplementedError("BadTool does not support async")
print(" 两个工具已定义:calculator 和 search_web")
# 我们可以通过以下方式访问它们:
# all_tools = [calculate, search_web]
# print(all_tools[0].name)
# print(all_tools[0].description)
代码说明:通过LangChain的@tool装饰器或继承BaseTool,我们可以方便地定义供Agent使用的工具。关键在于为每个工具提供:
- 清晰的
name:Agent在Action中调用的名称。 - 详细的
description:告诉LLM这个工具能做什么,何时使用。这是Agent选择工具的依据! - 明确的
args_schema(使用Pydantic):定义工具的输入参数及其类型和描述,确保LLM能正确构造参数。
好的工具设计能显著提升Agent的“智能”程度和可靠性。请记住,在生产环境中,eval()函数存在安全风险,应使用更安全的表达式解析库。
4. LLM Agent的高级特性:记忆与反思(Memory & Reflection)
记忆(Memory)
没有记忆的Agent就像金鱼一样,无法记住之前的对话或行动。记忆模块允许Agent保留历史信息,从而在多轮对话或复杂任务中保持上下文连贯性,并从过去的经验中学习。记忆可以分为:
- 短期记忆(Short-term Memory):通常指当前对话的上下文,例如最近的几轮对话记录。
- 长期记忆(Long-term Memory):存储更持久、更重要的信息,通常通过向量数据库实现,允许Agent检索相关旧经验。
代码示例:使用LangChain的对话缓冲区记忆(ConversationBufferMemory)
# 导入必要的库
from langchain.memory import ConversationBufferMemory
from langchain.chains import LLMChain
from langchain_openai import ChatOpenAI
from langchain.prompts import PromptTemplate
# 推荐写法:使用ConversationBufferMemory来存储对话历史
llm = ChatOpenAI(model="gpt-4o", temperature=0.5)
# 定义一个简单的对话Prompt
conversation_template = """你是一个友好的AI助手。这是当前的对话历史:
{chat_history}
人类: {input}
AI:"""
conversation_prompt = PromptTemplate.from_template(conversation_template)
# 创建记忆实例
memory = ConversationBufferMemory(memory_key="chat_history") # memory_key要与Prompt中的变量名一致
# 创建一个简单的LLMChain来演示记忆的使用
conversation_chain = LLMChain(llm=llm, prompt=conversation_prompt, memory=memory, verbose=True)
print("--- 记忆功能演示 ---")
print(conversation_chain.run("你好,我是小明。"))
# 第一次对话,记忆为空,LLM直接回复
print(conversation_chain.run("我叫什么名字?"))
# 第二次对话,LLM能从记忆中提取“小明”这个信息
print(f"\
当前记忆内容:\
{memory.load_memory_variables({})}")
# 不推荐:没有记忆的LLM,无法记住之前的对话
# class StatelessLLM:
# def init(self, llm):
# self.llm = llm
# def query(self, text):
# return self.llm.invoke(text) # 每次都是全新的对话,无法上下文连贯
# stateless_llm = StatelessLLM(llm)
# print(stateless_llm.query("你好,我是小红。"))
# print(stateless_llm.query("我叫什么名字?")) # 无法回答
代码说明:ConversationBufferMemory是最简单的记忆形式,它直接存储并返回完整的对话历史。在Agent中,记忆模块会结合规划模块,将历史对话或重要观察结果作为新的上下文输入给LLM,从而实现上下文感知和决策优化。更高级的记忆,如ConversationSummaryMemory或结合向量数据库的长期记忆,可以处理更长的历史记录并进行语义检索。
反思(Reflection)
反思能力让Agent能够像人类一样审查自己的工作,识别错误,并从中学习以改进未来的行动。这通常涉及:
- 自我评估:Agent在完成一个步骤或整个任务后,评估其输出是否满足要求。
- 错误检测与修正:识别工具调用失败、答案不准确、逻辑谬误等问题。
- 经验学习:将成功或失败的经验存储起来,用于指导未来的决策。
代码示例:一个简化的Agent反思机制(基于Prompting)
# 推荐写法:通过Prompt引导LLM进行反思
from langchain_openai import ChatOpenAI
from langchain.schema import HumanMessage, SystemMessage
llm_reflector = ChatOpenAI(model="gpt-4o", temperature=0.3) # 使用单独的LLM或同一个LLM,但使用不同的Prompt
def reflect_on_agent_output(task: str, agent_steps: list, final_answer: str) -> str:
"""根据Agent的执行步骤和最终答案进行反思和评估。"""
reflection_prompt = ChatPromptTemplate.from_messages([
SystemMessage(content=(
"你是一个反思AI,你的任务是评估另一个AI智能体(Agent)"\
"在完成一个任务时的表现。请仔细审阅Agent的思考过程、"\
"工具调用及其输出,以及最终答案。"\
"指出Agent的优点、不足之处,以及如何改进。"\
"如果发现错误或低效之处,请提供具体的改进建议。"\
"评估格式:\
\
优点:\
缺点:\
改进建议:\
"
)),
HumanMessage(content=(
f"原始任务:{task}\
\
"\
f"Agent的执行步骤:\
{'-'*30}\
"
) + "\
".join([f"Thought: {s[0]}\
Action: {s[1]}\
Observation: {s[2]}\
" for s in agent_steps]) + \
(f"Final Answer: {final_answer}\
{'-'*30}"))
])
print("\
正在进行反思...")
response = llm_reflector.invoke(reflection_prompt.format_messages())
return response.content
# 模拟Agent的执行步骤
simulated_agent_steps = [
("查找最新汇率", "search_tool[美元兑人民币汇率]", "美元兑人民币汇率:7.15"),
("计算兑换结果", "calculator[100 * 7.15]", "计算结果:715.0"),
# ("计算错误,应该乘以0.9", "calculator[100 * 7.15 * 0.9]", "计算结果:643.5"), # 模拟一个潜在的错误或遗漏
]
final_ans = "100美元可以兑换715.0人民币。"
# 反思过程
# reflection_result = reflect_on_agent_output("将100美元兑换成人民币", simulated_agent_steps, final_ans)
# print(f"\
反思结果:\
{reflection_result}")
# 不推荐:缺乏反思机制,无法发现并修正错误
# def execute_without_reflection(task, agent_steps, final_answer):
# print(f"Agent直接给出答案:{final_answer}")
# # 即使答案有错,Agent也无法察觉或改进
# pass
代码说明:这里的反思机制是通过一个专门的Prompt将Agent的执行日志(思考、行动、观察)和最终结果提交给另一个LLM进行评估。这个“反思者”LLM会根据预设的指令(System Prompt)来分析Agent的表现,并提供改进建议。在实际应用中,反思的结果可以用来:
- 自动调整Prompt:优化Agent的初始Prompt,使其更有效地规划。
- 更新记忆:将反思得到的经验和教训存储起来,避免未来犯同样的错误。
- 触发重试机制:如果反思发现严重错误,Agent可以重新尝试执行任务。
5. 构建一个实用的LLM Agent:实战案例
现在,我们把前面学到的组件整合起来,构建一个能够查询天气并推荐菜谱的LLM Agent。我们将使用LangChain,因为它提供了丰富的抽象来简化Agent的构建。
代码示例:完整的LLM Agent实现 (天气查询+菜谱推荐)
首先,我们需要定义更多的工具:一个用于天气查询,一个用于菜谱推荐。
# 导入所有必要的库
from langchain_openai import ChatOpenAI
from langchain.agents import AgentExecutor, create_react_agent
from langchain.tools import tool
from langchain.prompts import PromptTemplate
from langchain.memory import ConversationBufferMemory
from langchain.callbacks.base import BaseCallbackHandler
from langchain_core.runnables import RunnableConfig
from typing import List, Dict, Any
import datetime
import requests
import os
# 设置OpenAI API Key (请替换为你的实际Key或设置环境变量)
# os.environ["OPENAI_API_KEY"] = "YOUR_OPENAI_API_KEY"
# os.environ["SERPER_API_KEY"] = "YOUR_SERPER_API_KEY" # 如果需要搜索引擎工具
# 1. 定义工具
# =====================================================================================
class WeatherInput(BaseModel):
city: str = Field(description="要查询天气的城市名称,例如 '上海'。")
date: str = Field(description="要查询的日期,格式为 'YYYY-MM-DD'。例如 '2023-10-26'。"\
"如果是今天,可以传入 '今天';如果是昨天,可以传入 '昨天';"\
"如果是上周五,可以传入 '上周五'。")
@tool("query_weather", args_schema=WeatherInput)
def query_weather(city: str, date: str) -> str:
"""查询指定城市在指定日期的天气情况。\
特别适用于获取历史或未来天气数据。"""
# 真实API调用通常需要注册和API Key,这里用模拟数据。
# 实际项目中,你需要接入如和风天气、OpenWeatherMap等API
print(f"[调用工具] 正在查询 {date} {city} 的天气...")
today = datetime.date.today()
query_date = None
if date == "今天":
query_date = today
elif date == "昨天":
query_date = today - datetime.timedelta(days=1)
elif date == "明天":
query_date = today + datetime.timedelta(days=1)
elif date == "上周五":
# 计算上周五的日期
weekday = today.weekday() # Monday is 0 and Sunday is 6
days_since_friday = (weekday + 2) % 7 # Friday is 4. (4 - weekday - 7) % 7
query_date = today - datetime.timedelta(days=days_since_friday + 7)
elif len(date) == 10 and date[4] == '-' and date[7] == '-':
try:
query_date = datetime.datetime.strptime(date, "%Y-%m-%d").date()
except ValueError:
pass
if not query_date:
return f"日期格式无法识别或计算:{date}。请提供 'YYYY-MM-DD' 或 '今天/昨天/明天/上周五'。"
# 模拟天气数据
if query_date == today:
weather_info = f"{city}{query_date}:晴朗,25°C,微风。"\
f"体感舒适,适合户外活动。"
elif query_date < today:
weather_info = f"{city}{query_date}:多云转阴,18-22°C,有小雨。"\
f"体感凉爽,出门建议带伞。"
else:
weather_info = f"{city}{query_date}:预计晴到多云,20-28°C。"\
f"建议穿着薄外套。"
return weather_info
class RecipeInput(BaseModel):
weather_condition: str = Field(description="当前或未来天气情况,例如 '晴朗,温暖' 或 '阴雨,寒冷'。")
@tool("recommend_recipe", args_schema=RecipeInput)
def recommend_recipe(weather_condition: str) -> str:
"""根据天气情况推荐一道适合的菜肴及简要做法。"""
print(f"[调用工具] 正在根据天气 '{weather_condition}' 推荐菜谱...")
if "晴朗" in weather_condition or "温暖" in weather_condition or "炎热" in weather_condition:
return "推荐菜肴:凉拌黄瓜。\
做法:黄瓜切片,加蒜末、醋、生抽、香油、辣椒油拌匀即可。清爽开胃,适合炎热天气。"
elif "阴雨" in weather_condition or "寒冷" in weather_condition or "下雪" in weather_condition:
return "推荐菜肴:香菇滑鸡。\
做法:鸡肉切块,香菇切片,姜蒜爆香后加入鸡肉翻炒,再加入香菇、生抽、蚝油、少许水焖煮至熟。暖身美味,适合寒冷或阴雨天。"
else:
return "推荐菜肴:家常炒青菜。\
做法:青菜洗净切段,蒜末爆香,加入青菜大火快炒,加盐调味。简单健康,适合各种天气。"
# 2. 初始化LLM
# =====================================================================================
llm = ChatOpenAI(model="gpt-4o", temperature=0)
# 3. 准备工具列表
# =====================================================================================
tools = [query_weather, recommend_recipe]
# 4. 创建Prompt模板 (使用之前定义的ReAct风格Prompt)
# =====================================================================================
# 注意:这里重新定义一下,因为实际Agent会注入工具描述
agent_system_template = (
"你是一个强大的AI助手,可以根据用户的需求,"\
"通过思考和调用工具来解决问题。"\
"请遵循以下格式:\
\
"\
"Thought: 我现在应该做什么?\
"\
"Action: tool_name[input]\
"\
"Observation: 工具的输出\
"\
"... (重复 Thought/Action/Observation 直到任务完成)\
"\
"Thought: 我已经完成了所有步骤,现在可以给出最终答案了。\
"\
"Final Answer: 最终的答案。\
\
"\
"可用的工具如下:\
{tools}\
"\
"请开始你的思考和行动。"
)
# 用于将工具列表转换为LLM可读的字符串格式
# LangChain的create_react_agent会自动处理这个,但这里手动展示其原理
# formatted_tools = "\
".join([f"* {tool.name}: {tool.description}" for tool in tools])
# 创建最终的Prompt,LangChain会自动注入工具信息
# agent_prompt = PromptTemplate.from_template(agent_system_template).partial(tools=formatted_tools)
# LangChain的create_react_agent会构建完整的Prompt,我们只需要提供一个基本模板
# 这里我们使用一个更通用的模板,让create_react_agent来填充大部分内容
agent_prompt = ChatPromptTemplate.from_messages([
SystemMessagePromptTemplate.from_template(
"你是一个有用的助手,可以访问以下工具。"\
"你被鼓励使用工具来回答问题。"\
"如果工具不能直接给出答案,请综合利用工具输出和你的知识来回答。\
"\
"回答问题时请逐步思考,然后决定采取什么行动。\
"\
"请以中文回复。\
"\
"可用工具: {tools}\
"\
"{agent_scratchpad}" # 这是一个重要的占位符,用于Agent记录思考和行动过程
),
HumanMessagePromptTemplate.from_template("{input}")
])
# 5. 创建Agent (使用LangChain的create_react_agent)
# =====================================================================================
agent = create_react_agent(llm, tools, agent_prompt)
# 6. 创建Agent执行器 (用于运行Agent)
# =====================================================================================
# AgentExecutor 是Agent的运行环境,它会处理循环、工具调用等
agent_executor = AgentExecutor(
agent=agent,
tools=tools,
verbose=True, # 开启verbose可以看到Agent的思考和行动过程
handle_parsing_errors=True, # 允许Agent在解析错误时尝试自我修正
max_iterations=10, # 设置最大迭代次数,防止无限循环
memory=ConversationBufferMemory(memory_key="chat_history", return_messages=True) # 添加记忆
)
# 7. 运行Agent
# =====================================================================================
print("\
--- LLM Agent 实战演示 ---\
")
# 首次提问
initial_query = "帮我查一下上周五(不是假期)上海的天气,然后根据天气情况推荐一道适合做的菜,并告诉我这道菜的详细做法。"
print(f"用户提问:{initial_query}\
")
response = agent_executor.invoke({"input": initial_query})
print(f"\
Agent 最终回答:\
{response['output']}\
")
# 再次提问,利用记忆
next_query = "那么明天北京的天气呢?推荐一个适合的菜。"
print(f"用户提问:{next_query}\
")
response_with_memory = agent_executor.invoke({"input": next_query})
print(f"\
Agent 最终回答(利用记忆):\
{response_with_memory['output']}\
")
代码说明:
- 工具定义:我们定义了
query_weather和recommend_recipe两个工具,它们都有清晰的描述和基于Pydantic的输入Schema,确保LLM能正确理解和调用。 - LLM初始化:选择一个强大的LLM(如
gpt-4o)作为Agent的“大脑”。 - Prompt模板:
create_react_agent会基于我们提供的agent_prompt自动构建ReAct风格的Prompt,并注入工具描述和agent_scratchpad(用于记录Agent的中间思考和行动)。 - Agent创建:
create_react_agent函数简化了ReAct Agent的创建过程。 - Agent执行器:
AgentExecutor是运行Agent的核心,它负责驱动Agent的思考-行动循环,处理工具调用,并能集成记忆模块 (ConversationBufferMemory)。
6.verbose=True:这个参数非常重要,它会打印出Agent的详细执行日志,包括每一次的Thought、Action和Observation,这对于调试和理解Agent行为非常有帮助。
通过这个实战案例,我们可以看到LLM Agent如何将复杂任务分解、利用工具获取外部信息,并最终给出完整、准确的答案。它的能力远超传统LLM的单次调用。
进阶内容
性能优化技巧
-
Prompt Engineering:
- 清晰的指令:确保System Prompt清晰、简洁,明确Agent的角色、目标和限制。
- Few-shot示例:在Prompt中提供几个高质量的思考-行动-观察示例,可以显著提高Agent的性能和可靠性。
- 限制工具集:只提供完成当前任务所需的最少工具,避免LLM在不相关的工具中“迷失”。
-
缓存 (Caching):
-
对于重复的LLM调用或工具输出,使用缓存可以减少API成本和响应时间。LangChain提供了多种缓存机制,如
InMemoryCache、SQLiteCache等。 -
代码示例:LangChain缓存配置
from langchain.globals import set_llm_cache from langchain.cache import InMemoryCache, SQLiteCache # 使用内存缓存 set_llm_cache(InMemoryCache()) # 或者使用SQLite缓存 (需要安装 pysqlite3-binary) # set_llm_cache(SQLiteCache(database_path=".langchain_cache.db")) # 后续的LLM调用如果输入相同,将从缓存中获取结果 # llm_with_cache = ChatOpenAI(model="gpt-4o", temperature=0) # print(llm_with_cache.invoke("Hello, world!")) # 第一次调用,会请求API # print(llm_with_cache.invoke("Hello, world!")) # 第二次调用,直接从缓存获取 print(" LLM 缓存已配置。") ` `` 3. 异步调用 (Asynchronous Operations): * 当Agent需要并行调用多个工具或LLM时,使用异步方法 ( `async/await`) 可以显著提高效率,减少等待时间。LangChain的许多组件都支持异步接口。
-
常见陷阱和解决方案
-
幻觉 (Hallucination):
- 陷阱:Agent可能编造不存在的工具、参数或事实。
- 解决方案:强化System Prompt,明确要求Agent只使用提供的工具,并在必要时结合
handle_parsing_errors=True让Agent自我修正。使用更强大的LLM,并通过反思机制检查输出。
-
工具选择错误:
- 陷阱:Agent选择了不合适的工具,或者未能正确构造工具的参数。
- 解决方案:编写清晰、具体且带有使用场景描述的工具描述。Pydantic
args_schema强制类型和结构。提供Few-shot示例。
-
无限循环 (Infinite Loop):
- 陷阱:Agent可能陷入Thought-Action-Observation的循环中,无法收敛到最终答案。
- 解决方案:设置
max_iterations参数限制Agent的步骤数量。设计Prompt时,明确任务完成的条件。引入反思机制,检测并打破循环。
-
Token限制:
- 陷阱:随着对话历史和工具调用的增多,上下文窗口可能会超出LLM的Token限制。
- 解决方案:使用Summarization Memory(如
ConversationSummaryBufferMemory)压缩历史对话。优化工具输出,只返回最关键的信息。限制max_iterations。
对比不同实现方式 (LangChain vs LlamaIndex)
虽然本文主要以LangChain为例,但LlamaIndex也是构建LLM Agent的另一个强大框架。
| 特性 | LangChain | LlamaIndex |
|---|---|---|
| 核心关注点 | 通用Agent编排、链式调用、工具集成、内存管理 | 专注于RAG(检索增强生成)、数据摄取、索引和查询 |
| Agent支持 | 提供了 AgentExecutor、create_react_agent等完善的Agent构建模块 | 通过 QueryEngine、AgentRunner 等实现,与RAG紧密结合 |
| 数据处理 | 通过Loader和Chains进行数据加载和处理 | 强大的数据摄取、索引和查询能力,非常适合处理私有数据 |
| 生态 | 广泛的LLM、工具、Agent类型支持 | 丰富的数据连接器和索引策略 |
如何选择?
- LangChain 更适合需要复杂决策逻辑、多步骤规划、广泛工具集成和多轮对话的通用Agent。
- LlamaIndex 则在需要从大量非结构化数据中检索信息(RAG)并生成答案时表现出色,特别是当数据量庞大且需要高度定制化索引策略时。很多时候,两者可以结合使用,例如使用LlamaIndex的QueryEngine作为LangChain Agent的一个工具。
总结与延伸
核心知识点回顾
- LLM Agent 赋予了LLM“思考、行动、观察、记忆和反思”的能力,使其能够解决传统LLM无法处理的复杂任务。
- ReAct框架(Reasoning + Acting)是Agent运行的核心循环。
- 规划(Planning):通过结构化的Prompt(如ReAct Prompt)引导LLM分解任务,制定行动策略。
- 工具使用(Tool Usage):通过定义清晰、具体的外部工具,扩展Agent的能力边界。
- 记忆(Memory):保持上下文连贯性,支持多轮对话和长期学习。
- 反思(Reflection):让Agent能够自我评估和修正错误,提升可靠性。
实战建议
- 从简单开始:先构建一个只带一两个工具的Agent,逐步增加复杂性。
- 重视Prompt Engineering:它是 Agent 行为的“宪法”,清晰具体的指令至关重要。
- 工具设计至关重要:为每个工具编写详细的描述和严格的参数Schema。工具要单一职责,避免过于复杂。
- 充分利用
verbose模式:观察Agent的每一步思考和行动,是调试和优化的最佳方式。 - 安全性:对于涉及
eval()、外部API密钥等操作,务必注意安全防护和密钥管理。
相关技术栈或进阶方向
- 高级Agent框架:除了LangChain和LlamaIndex,还有如AutoGPT、BabyAGI等自主Agent框架,它们在自动化任务方面走得更远。
- 多Agent系统:让多个Agent协同工作,每个Agent负责不同的任务或角色,共同解决更宏大的问题。
- Agent评估:如何有效地评估Agent的性能和可靠性是一个重要的研究方向,涉及到复杂的度量标准和测试框架。
- RAG (Retrieval Augmented Generation):结合向量数据库,让Agent能够从海量非结构化数据中检索相关信息,进一步增强其知识广度。
- Agent GUI/Ops平台:如LangChain Hub、LlamaCloud等,提供Agent的可视化构建、部署和监控。
LLM Agent是LLM发展的一个重要方向,它正将大语言模型从“对话机器人”推向“智能工作流执行器”。掌握LLM Agent的构建,无疑将为你的开发项目打开全新的可能性。现在,是时候将这些知识付诸实践,构建你自己的智能Agent了!