深入理解LLM Agent:从原理到实战

112 阅读26分钟

深入理解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使用的工具。关键在于为每个工具提供:

  1. 清晰的name:Agent在Action中调用的名称。
  2. 详细的description:告诉LLM这个工具能做什么,何时使用。这是Agent选择工具的依据!
  3. 明确的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的表现,并提供改进建议。在实际应用中,反思的结果可以用来:

  1. 自动调整Prompt:优化Agent的初始Prompt,使其更有效地规划。
  2. 更新记忆:将反思得到的经验和教训存储起来,避免未来犯同样的错误。
  3. 触发重试机制:如果反思发现严重错误,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']}\
")

代码说明:

  1. 工具定义:我们定义了query_weatherrecommend_recipe两个工具,它们都有清晰的描述和基于Pydantic的输入Schema,确保LLM能正确理解和调用。
  2. LLM初始化:选择一个强大的LLM(如gpt-4o)作为Agent的“大脑”。
  3. Prompt模板:create_react_agent会基于我们提供的agent_prompt自动构建ReAct风格的Prompt,并注入工具描述和agent_scratchpad(用于记录Agent的中间思考和行动)。
  4. Agent创建:create_react_agent函数简化了ReAct Agent的创建过程。
  5. Agent执行器:AgentExecutor是运行Agent的核心,它负责驱动Agent的思考-行动循环,处理工具调用,并能集成记忆模块 (ConversationBufferMemory)。
    6. verbose=True:这个参数非常重要,它会打印出Agent的详细执行日志,包括每一次的ThoughtActionObservation,这对于调试和理解Agent行为非常有帮助。

通过这个实战案例,我们可以看到LLM Agent如何将复杂任务分解、利用工具获取外部信息,并最终给出完整、准确的答案。它的能力远超传统LLM的单次调用。

进阶内容

性能优化技巧

  1. Prompt Engineering:

    • 清晰的指令:确保System Prompt清晰、简洁,明确Agent的角色、目标和限制。
    • Few-shot示例:在Prompt中提供几个高质量的思考-行动-观察示例,可以显著提高Agent的性能和可靠性。
    • 限制工具集:只提供完成当前任务所需的最少工具,避免LLM在不相关的工具中“迷失”。
  2. 缓存 (Caching):

    • 对于重复的LLM调用或工具输出,使用缓存可以减少API成本和响应时间。LangChain提供了多种缓存机制,如InMemoryCacheSQLiteCache等。

    • 代码示例: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的许多组件都支持异步接口。
      

常见陷阱和解决方案

  1. 幻觉 (Hallucination):

    • 陷阱:Agent可能编造不存在的工具、参数或事实。
    • 解决方案:强化System Prompt,明确要求Agent只使用提供的工具,并在必要时结合handle_parsing_errors=True让Agent自我修正。使用更强大的LLM,并通过反思机制检查输出。
  2. 工具选择错误:

    • 陷阱:Agent选择了不合适的工具,或者未能正确构造工具的参数。
    • 解决方案:编写清晰、具体且带有使用场景描述的工具描述。Pydantic args_schema强制类型和结构。提供Few-shot示例。
  3. 无限循环 (Infinite Loop):

    • 陷阱:Agent可能陷入Thought-Action-Observation的循环中,无法收敛到最终答案。
    • 解决方案:设置max_iterations参数限制Agent的步骤数量。设计Prompt时,明确任务完成的条件。引入反思机制,检测并打破循环。
  4. Token限制:

    • 陷阱:随着对话历史和工具调用的增多,上下文窗口可能会超出LLM的Token限制。
    • 解决方案:使用Summarization Memory(如ConversationSummaryBufferMemory)压缩历史对话。优化工具输出,只返回最关键的信息。限制max_iterations

对比不同实现方式 (LangChain vs LlamaIndex)

虽然本文主要以LangChain为例,但LlamaIndex也是构建LLM Agent的另一个强大框架。

特性LangChainLlamaIndex
核心关注点通用Agent编排、链式调用、工具集成、内存管理专注于RAG(检索增强生成)、数据摄取、索引和查询
Agent支持提供了 AgentExecutorcreate_react_agent等完善的Agent构建模块通过 QueryEngineAgentRunner 等实现,与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能够自我评估和修正错误,提升可靠性。

实战建议

  1. 从简单开始:先构建一个只带一两个工具的Agent,逐步增加复杂性。
  2. 重视Prompt Engineering:它是 Agent 行为的“宪法”,清晰具体的指令至关重要。
  3. 工具设计至关重要:为每个工具编写详细的描述和严格的参数Schema。工具要单一职责,避免过于复杂。
  4. 充分利用verbose模式:观察Agent的每一步思考和行动,是调试和优化的最佳方式。
  5. 安全性:对于涉及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了!