8. LangChain框架-2
Agent代理
Agent代理的核心思想是使用语言模型来选择要采取的一系列动作。在链中,动作序列是硬编码的。
在代理中,语言模型用作推理引擎来确定要采取哪些动作以及按什么顺序进行。
因此,在LangChain中,Agent代理就是使用语言模型作为推理引擎,让模型自主判断、调用工具和决定下一步行动。
Agent代理像是一个多功能接口,能够使用多种工具,并根据用户输入决定调用哪些工具,同时能够将一个工具的输出数据作为另一个工具的输入数据。
Tavily在线搜索
构建一个具有两种工具的代理:一种用于在线查找,另一种用于查找加载到索引中的特定数据。
在LangChain中有一个内置的工具,可以方便地使用Tavily搜索引擎作为工具。
访问Tavily(用于在线搜索)注册账号并登录,获取API 密钥
TAVILY_API_KEY申请:https://tavily.com/
最简单的示例
import os
from langchain_community.tools.tavily_search import TavilySearchResults
from dotenv import load_dotenv
load_dotenv()
# 查询 Tavily 搜索 API 并返回 json 的工具
search = TavilySearchResults(tavily_api_key=os.getenv("tavily_key"))
# 执行查询
res = search.invoke("目前市场上苹果手机16的售价是多少?")
print(res)
创建检索器
根据上述查询结果中的某个URL中,获取一些数据创建一个检索器。
# 加载所需的库
import os
from langchain_community.tools.tavily_search import TavilySearchResults
from langchain_community.document_loaders import WebBaseLoader
from langchain_community.vectorstores import FAISS
from langchain_community.embeddings import DashScopeEmbeddings
from langchain_text_splitters import RecursiveCharacterTextSplitter
from dotenv import load_dotenv
load_dotenv()
# 查询 Tavily 搜索 API 并返回 json 的工具
# search = TavilySearchResults()
# # 执行查询
# res = search.invoke("目前市场上苹果手机16的售价是多少?")
# print(res)
# 创建索引器根据上述查询的结果
# 加载HTML内容为一个文档对象
loader = WebBaseLoader("https://news.qq.com/rain/a/20240920A07Y5Y00")
# 读取文档
docs = loader.load()
# print(docs)
# 分割文档
documents = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200).split_documents(docs)
# 向量化
vector = FAISS.from_documents(documents, DashScopeEmbeddings(dashscope_api_key=os.getenv('api_key')))
# 创建检索器
retriever = vector.as_retriever()
# 测试检索结果
print(retriever.get_relevant_documents("目前市场上苹果手机16的售价是多少?"))
对接大模型
创建Agent,这里使用LangChain中一个叫OpenAI functions的代理,然后得到一个AgentExecutor代理执行器
# 加载所需的库
import os
from langchain import hub
from langchain_community.tools.tavily_search import TavilySearchResults
from langchain_community.document_loaders import WebBaseLoader
from langchain_community.vectorstores import FAISS
from langchain_community.embeddings import DashScopeEmbeddings
from langchain_openai import ChatOpenAI
from langchain_text_splitters import RecursiveCharacterTextSplitter
from dotenv import load_dotenv
from langchain.tools.retriever import create_retriever_tool
load_dotenv()
# 查询 Tavily 搜索 API 并返回 json 的工具
search = TavilySearchResults(tavily_api_key=os.getenv("tavily_key"))
# # 执行查询
# res = search.invoke("目前市场上苹果手机16的售价是多少?")
# print(res)
# 创建索引器根据上述查询的结果
# 加载HTML内容为一个文档对象
loader = WebBaseLoader("https://news.qq.com/rain/a/20240920A07Y5Y00")
# 读取文档
docs = loader.load()
# print(docs)
# 分割文档
documents = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200).split_documents(docs)
# 向量化
vector = FAISS.from_documents(documents, DashScopeEmbeddings(dashscope_api_key=os.getenv('api_key')))
# 创建检索器
retriever = vector.as_retriever()
# 测试检索结果
# print(retriever.get_relevant_documents("目前市场上苹果手机16的售价是多少?"))
# 创建一个工具来检索文档
retriever_tool = create_retriever_tool(
retriever,
"iPhone_price_search",
"搜索有关 iPhone 16 的价格信息。对于iPhone 16的任何问题,您必须使用此工具!",
)
# 创建将在下游使用的工具列表
tools = [search, retriever_tool]
# 初始化大模型
llm = ChatOpenAI(api_key=os.getenv("api_key"),
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
model='qwen-plus', temperature=0)
# https://smith.langchain.com/hub
# 获取要使用的提示
prompt = hub.pull("hwchase17/openai-functions-agent")
# 打印Prompt
# print(prompt)
# 使用OpenAI functions代理
from langchain.agents import create_openai_functions_agent
# 构建OpenAI函数代理:使用 LLM、提示模板和工具来初始化代理
agent = create_openai_functions_agent(llm, tools, prompt)
from langchain.agents import AgentExecutor
# 将代理与AgentExecutor工具结合起来
agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)
# 执行代理 进行对比
# agent_executor.invoke({"input": "目前市场上苹果手机16的各个型号的售价是多少?如果我在此基础上加价5%卖出,应该如何定价?"})
agent_executor.invoke({"input": "美国2024年谁胜出了总统的选举?"})
OpenAI Functions Agent
在LangChain中,create_openai_functions_agent是一个便捷的函数,用于创建能够与OpenAI提供的函数交互的代理。这使得开发人员可以创建智能应用程序,通过代理与用户进行更自然、更有效的对话。
from langchain.agents import create_openai_functions_agent, AgentExecutor
from langchain_community.tools.tavily_search import TavilySearchResults
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_openai import ChatOpenAI
from langchain_core.tools import Tool
import os
from dotenv import load_dotenv
load_dotenv()
# 定义查询订单状态的函数
def query_order_status(order_id):
if order_id == "1024":
return "订单 1024 的状态是:已发货,预计送达时间是 3-5 个工作日。"
else:
return f"未找到订单 {order_id} 的信息,请检查订单号是否正确。"
# 定义退款政策说明函数
def company_refund_policy(company_name):
print(company_name)
if company_name == "tom公司":
return "tom公司的退款政策是:在购买后7天内可以申请全额退款,需提供购买凭证。"
else:
print('输入有误')
# 查询年龄
def get_age(name):
if name == "tom":
print(name)
return "我的年龄是56岁!"
else:
print('输入有误')
# 初始化工具
tools = [
TavilySearchResults(max_results=1, tavily_api_key=os.getenv("tavily_key")),
Tool(
name="queryOrderStatus",
func=query_order_status,
description="根据订单ID查询订单状态",
args={"order_id": "订单的ID"}
),
Tool(
name="companyRefundPolicy",
func=company_refund_policy,
description="查询某某公司退款政策详细内容",
args={"company_name": "公司名称"}
),
Tool(
name="getAge",
func=get_age,
description="查询tom年龄大小",
args={"name": "查询tom年龄大小"}
),
]
# 获取使用的提示
prompt = ChatPromptTemplate.from_messages([
("system",
"你是一个客服助手,使用工具回答问题。传递给工具的内容必须是准确的json数据不结尾的括号多加一个,如果是字符串数据必须和输入的保持一致要完整,不是篡改**重要规则**, "
),
# 用户输入变量
("user", "{input}"),
# 关键点:添加代理中间步骤占位符
MessagesPlaceholder(variable_name="agent_scratchpad")
])
# print(prompt)
# 选择将驱动代理的LLM
llm = ChatOpenAI(api_key=os.getenv("api_key"),
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
model='qwen-plus')
# 构建OpenAI函数代理
agent = create_openai_functions_agent(llm, tools, prompt)
# 通过传入代理和工具创建代理执行器
agent_executor = AgentExecutor(agent=agent, tools=tools, handle_parsing_errors=True, verbose=True)
# 定义一些测试询问
queries = [
"请问订单1024的状态是什么?",
"请问tom公司退款政策是什么?",
"2024年谁胜出了美国总统的选举"
]
# 运行代理并输出结果
for input in queries:
response = agent_executor.invoke({"input": input})
print(f"客户提问:{input}")
print(f"代理回答:{response}\n")
# response = agent_executor.invoke({"input": "2024年谁胜出了美国总统的选举"})
# print(f"代理回答:{response}\n")
ReAct Agent
ReAct (Reflective Agent) 是 LangChain 中的一种聊天代理(Agent)类型。它具有以下独特的特点:
- 反思能力:ReAct 代理在给出响应之前,会先对自己的行为和预测进行深入的反思和评估。它会检查自己是否遵循了预先设定的规则和指令,是否达到了预期的目标。
- 自我纠错:如果ReAct代理在反思过程中发现自己存在问题或疏漏,它会主动尝试对自己的行为进行纠正和改正,以修复错误,提高自身的表现。
- 迭代学习:通过不断的反思和自我纠错,ReAct 代理可以在与用户的交互中逐步学习和优化自己的行为方式,不断提高回答的质量和准确性。
- 可解释性:ReAct 代理在给出最终响应时,会同时提供自己的思考过程和决策依据,使得它的行为更加透明和可解释。
这种具备反思和自我纠错能力的 ReAct 代理,在需要较高可靠性和稳定性的应用场景中很有优势,例如智能客服、问答系统、任务执行等。它可以通过持续的自我学习和优化,为用户提供更加智能和可信的交互体验。
Google搜索API:访问 SerpApi ,注册账号,选择相应的订阅计划(Free),然后获取API Key,利用这个API为大模型提供Google搜索工具。
SerpApi:https://serpapi.com/
from langchain_community.agent_toolkits.load_tools import load_tools
from langchain.agents import initialize_agent
from langchain.agents import AgentType
from langchain_openai import ChatOpenAI
from dotenv import load_dotenv
import os
load_dotenv()
# 开启DEBUG 显示具体的日志信息
# langchain.debug = True
# langchain.verbose = True
# 初始化大模型:语言模型控制代理
llm = ChatOpenAI(
api_key=os.getenv("api_key"),
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
model="qwen-plus"
)
# 设置工具:加载使用的工具,serpapi:调用Google搜索引擎
tools = load_tools(["serpapi"], llm=llm, SERPAPI_API_KEY=os.getenv("SERPAPI_API_KEY"))
# 初始化Agent:使用工具、语言模型和代理类型来初始化代理 ZERO_SHOT_REACT_DESCRIPTION 类型的代理可以在没有预先训练的情况下尝试解决新的问题
agent = initialize_agent(tools, llm, agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION, verbose=True)
# 让代理来回答提出的问题
agent.invoke({"input": "目前市场上苹果手机16的售价是多少?用中文回答"})
Self-Ask with Search Agent
Self-Ask with Search是一个通过搜索自我询问的代理,通过询问+答案的机制来帮助大模型寻找事实性问题的过渡性答案,从而引出最终答案。
import os
from dotenv import load_dotenv
from langchain import hub
from langchain.agents import AgentExecutor, create_self_ask_with_search_agent
from langchain_community.tools.tavily_search import TavilyAnswer
from langchain_openai import ChatOpenAI
load_dotenv()
# 将初始化工具,让它提供答案而不是文档
tools = [TavilyAnswer(name="Intermediate Answer", description="Answer Search")]
# 初始化大模型
llm = ChatOpenAI(
api_key=os.getenv("api_key"),
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
model="qwen-plus"
)
# 获取使用提示 可以修改此提示
prompt = hub.pull("hwchase17/self-ask-with-search")
# 使用搜索代理构建自助询问
agent = create_self_ask_with_search_agent(llm, tools, prompt)
# 通过传入代理和工具创建代理执行程序
agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True, handle_parsing_errors=True)
# 运行代理
agent_executor.invoke({"input": "中国有哪些省份呢?"})
LangChain之Tools工具
1. 工具Tools
工具是代理、链或LLM可以用来与世界互动的接口。它们结合了几个要素
- 工具的名称
- 工具的描述
- 该工具输入的JSON模式
- 要调用的函数
- 是否应将工具结果直接返回给用户
LangChain通过提供统一框架集成功能的具体实现。在框架内,每个功能被封装成一个工具,具有自己的输入输出及处理方法。代理接收任务后,通过大模型推理选择适合的工具处理任务。一旦选定,LangChain将任务输入传递给该工具,工具处理输入生成输出。输出经过大模型推理,可用于其他工具的输入或作为最终结果返回给用户。
Langchain地址:https://python.langchain.com/api_reference/community/tools.html
2. 工具的初步认识
# pip install wikipedia
from langchain_community.tools import WikipediaQueryRun
from langchain_community.utilities import WikipediaAPIWrapper
from dotenv import load_dotenv
import os
load_dotenv()
# 初始化工具 可以根据需要进行配置
# 使用包装器WikipediaAPIWrapper进行搜索并获取页面摘要。默认情况下,它将返回前 k 个结果的页面摘要。它通过 doc_content_chars_max 限制文档内容。
api_wrapper = WikipediaAPIWrapper(top_k_results=1, doc_content_chars_max=100)
tool = WikipediaQueryRun(api_wrapper=api_wrapper)
# 工具默认名称
print("name:", tool.name)
# 工具默认的描述
print("description:", tool.description)
# 输入内容 默认JSON模式
print("args:", tool.args)
# 是否直接返回工具的输出。
print("return_direct:", tool.return_direct)
# 可以用字典输入来调用这个工具
print(tool.run({"query": "langchain"}))
# 使用单个字符串输入来调用该工具。
print(tool.run("langchain"))
# 需要科学上网
3. 修改工具
可以修改参数的内置名称、描述和JSON模式。
from langchain_community.tools import WikipediaQueryRun
from langchain_community.utilities import WikipediaAPIWrapper
from pydantic import BaseModel, Field
# 初始化工具 可以根据需要进行配置
api_wrapper = WikipediaAPIWrapper(top_k_results=1, doc_content_chars_max=100)
class WikiInputs(BaseModel):
"""维基百科工具的输入。"""
query: str = Field(
description="维基百科中的查询,字数应在3个字以内"
)
tool = WikipediaQueryRun(
name="wiki-tool",
description="在维基百科中查找内容",
args_schema=WikiInputs,
api_wrapper=api_wrapper,
return_direct=True,
)
# 工具默认名称
print("name:", tool.name)
# 工具默认的描述
print("description:", tool.description)
print(tool.run("langchain"))
4. 自定义工具
- 在LangChain中,自定义工具有多种方法
- @tool装饰器
- @tool装饰器是定义自定义工具的最简单方法。装饰器默认使用函数名称作为工具名称,但可以通过传递字符串作为第一个参数来覆盖此设置。此外,装饰器将使用函数的文档字符串作为工具的描述 - 因此必须提供文档字符串。
from langchain.tools import tool
@tool
def add_number(a: int, b: int) -> int:
"""add two numbers."""
return a + b
print(add_number.name)
print(add_number.description)
print(add_number.args)
res = add_number.run({"a": 10, "b": 20})
print(res)
5. 更多Tools
Tavily Search工具:Tavily的搜索API是一个专门为人工智能代理(llm)构建的搜索引擎,可以快速提供实时、准确和真实的结果。
访问Tavily(https://tavily.com/)注册账号并登录,获取API 密钥
from langchain import hub
from langchain.agents import AgentExecutor, create_openai_tools_agent
from langchain_community.tools.tavily_search import TavilySearchResults
from langchain_openai import ChatOpenAI
import os
from dotenv import load_dotenv
load_dotenv()
tool = TavilySearchResults(max_results=1)
# 使用Tavily搜索工具
tools = [TavilySearchResults(max_results=1, tavily_api_key=os.getenv("TAVILY_API_KEY"))]
# print(tool.run("目前市场上黄金的售价是多少?"))
# https://smith.langchain.com/hub
# 获取要使用的提示
prompt = hub.pull("hwchase17/openai-tools-agent")
# 初始化大模型
llm = ChatOpenAI(
api_key=os.getenv("API_KEY"),
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
model="qwen-plus"
)
# 构建 OpenAI 工具代理
agent = create_openai_tools_agent(llm, tools, prompt)
# 通过传入代理和工具创建代理执行程序
agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)
# 运行代理
agent_executor.invoke({"input": "中国有多少个省份?"})LangChain之Memory
大多数的 LLM 应用程序都会有一个会话接口,允许我们和 LLM 进行多轮的对话,并有一定的上下文记忆能力。但实际上,模型本身是不会记忆任何上下文的,只能依靠用户本身的输入去产生输出。而实现这个记忆功能,就需要额外的模块去保存我们和模型对话的上下文信息,然后在下一次请求时,把所有的历史信息都输入给模型,让模型输出最终结果。
注意TIP: 模型的缓存和记忆不是一回事

# 没有缓存的例子
from dotenv import load_dotenv
from langchain_openai import ChatOpenAI
import os
load_dotenv()
llm = ChatOpenAI(api_key=os.getenv("API_KEY"),
base_url=os.getenv("BASE_URL"),
model_name="qwen-plus")
# 直接提供问题,并调用llm
response = llm.invoke("你好我是黄朝忆")
# print(response)
# print("=" * 50)
print(response.content)
response = llm.invoke("我是谁?")
# 你是一位正在与我对话的用户,可能是学生、工作者、好奇的探索者,或只是想聊聊天的朋友 😊
print(response.content)而在 LangChain 中,提供这个功能的模块就称为 Memory,用于存储用户和模型交互的历史信息。
记忆系统需要支持两种基本操作:读取和写入。
- 在接收到初始用户输入之后但在执行核心逻辑之前,链将从其内存系统中读取并增强用户输入。
- 在执行核心逻辑之后但在返回答案之前,链会将当前运行的输入和输出写入内存,以便在将来的运行中引用它们。

对该图的解释:
1、输入问题: ({"question": ...})
2、读取历史消息: 从Memory中READ历史消息({"past_messages": [...]})
3、构建提示(Prompt): 读取到的历史消息和当前问题会被合并,构建一个新的Prompt
4、模型处理: 构建好的提示会被传递给语言模型进行处理。语言模型根据提示生成一个输出。
5、解析输出: 输出解析器通过正则表达式 regex("Answer: (.*)")来解析,返回一个回答({"answer": ...})给用户
6、得到回复并写入Memory: 新生成的回答会与当前的问题一起写入Memory,更新对话历史。Memory会存储最新的对话内容,为后续的对话提供上下文支持。
Chat Messages
Chat Messages: 最基础的记忆管理方法,是用于管理和存储对话历史的具体实现。它们通常用于构建对话系统,帮助系统保持对话的连续性和上下文。这些消息通常包含了对话的每一轮,包括用户的输入和系统的响应。
from langchain_community.chat_message_histories import ChatMessageHistory
history = ChatMessageHistory()
history.add_user_message("hi!")
history.add_user_message("你好")
history.add_ai_message("whats up?")
print(history.messages)RunnableWithMessageHistory
RunnableWithMessageHistory 包装另一个 Runnable 并为其管理聊天消息历史记录;它负责读取和更新聊天消息历史记录。
本地内存存储
import json
from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain_community.chat_message_histories import ChatMessageHistory
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_openai import ChatOpenAI
from langchain.schema import messages_from_dict, messages_to_dict
from dotenv import load_dotenv
import os
# 加载环境变量(需要包含API_KEY)
load_dotenv()
# 初始化大语言模型(通义千问)
llm = ChatOpenAI(
api_key=os.getenv("API_KEY"), # 从环境变量读取API密钥
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1", # 阿里云兼容端点
model="qwen-plus" # 使用qwen-turbo模型
)
# 创建对话提示模板
prompt = ChatPromptTemplate.from_messages([
# 系统角色设定
("system", "你是一个友好的助手"),
# 历史消息占位符(变量名必须与链配置中的history_messages_key一致)
MessagesPlaceholder(variable_name="history"),
# 用户输入占位符
("user", "{input}")
])
# 构建基础对话链(组合提示模板和语言模型)
base_chain = prompt | llm
# 全局会话存储字典(Key: session_id, Value: ChatMessageHistory实例)
store = {}
def get_session_history(session_id):
"""获取或创建会话历史存储对象
Args:
session_id: 会话唯一标识(用于多会话隔离)
Returns:
对应会话的聊天历史记录对象
"""
if session_id not in store:
store[session_id] = ChatMessageHistory() # 初始化空历史记录
return store[session_id]
# 创建支持历史记录的对话链
conversation = RunnableWithMessageHistory(
base_chain, # 基础对话链
get_session_history=get_session_history, # 历史记录获取方法
input_messages_key="input", # 输入文本的键名
history_messages_key="history" # 历史记录的键名(需与提示模板中的变量名一致)
)
def save_memory(filepath, session_id):
"""保存指定会话的历史记录到文件
Args:
filepath: 文件保存路径(建议使用.json扩展名)
session_id: 要保存的会话ID(默认"default")
"""
history = get_session_history(session_id)
# 将消息对象列表转换为字典格式
dicts = messages_to_dict(history.messages)
# 写入JSON文件(UTF-8编码)
with open(filepath, "w", encoding='utf-8') as f:
json.dump(dicts, f, ensure_ascii=False)
def load_memory(filepath, session_id):
"""从文件加载历史记录到指定会话
Args:
filepath: 历史记录文件路径
session_id: 要加载到的会话ID(默认"default")
"""
with open(filepath, "r", encoding='utf-8') as f:
dicts = json.load(f)
# 将字典转换回消息对象列表
messages = messages_from_dict(dicts)
# 更新全局存储中的会话历史
store[session_id] = ChatMessageHistory(messages=messages)
def legacy_predict(input_text: str, session_id: str = "default") -> str:
"""模拟旧版predict方法的调用接口
Args:
input_text: 用户输入文本
session_id: 会话ID(默认"default")
Returns:
AI生成的回复文本
"""
return conversation.invoke(
{"input": input_text}, # 输入参数
# 配置参数(必须包含session_id来关联历史记录)
config={"configurable": {"session_id": session_id}}
).content
if __name__ == "__main__":
# 使用默认会话ID
SESSION_ID = "default"
# 模拟连续对话(4轮)
legacy_predict("你好", SESSION_ID) # 问候
legacy_predict("你是谁,我是黄朝忆", SESSION_ID) # 身份确认
legacy_predict("你的背后实现原理是什么", SESSION_ID) # 技术原理询问
# 查询对话历史(第4轮)
last_response = legacy_predict('截止到现在我们聊了什么?', SESSION_ID)
print("最后一次回答:", last_response)
# 持久化保存对话历史(JSON格式)
save_memory("./memory_new.json", SESSION_ID)
# 模拟重新加载历史记录(清空当前会话后重新加载)
load_memory("./memory_new.json", SESSION_ID)
# 验证历史恢复效果(第5轮)
reload_response = legacy_predict("我回来了,我们之前都聊了一些什么?", SESSION_ID)
print("\n恢复后的回答:", reload_response)
