langchain 官网文档:https://python.langchain.com/docs/introduction/
这里选的是 0.3v(最新)。
第一阶段:基础概念和环境设置
LangChain 0.3 建立在几个核心概念之上:
- 模型(Models):与各种LLM(如OpenAI、Anthropic、本地模型等)进行交互的接口
- 提示(Prompts):构建和管理发送给模型的输入
- 输出解析器(Output Parsers):将模型输出转换为结构化格式
- 链(Chains):将多个组件连接成一个工作流
- 记忆(Memory):存储和检索对话历史
- 工具(Tools):使模型能够与外部系统交互
- 代理(Agents):让模型决定使用哪些工具以及如何使用
首先,让我们设置环境并安装 LangChain 0.3:
# 安装 LangChain 0.3 版本
pip install langchain==0.3.0
# 安装常用的依赖包
pip install openai
想使用 LangChain 与通义千问集成:
# 导入必要的模块
from langchain_openai import ChatOpenAI
import os
# 必须设置环境变量
os.environ["OPENAI_API_KEY"] = "sk-f80c...."
os.environ["OPENAI_BASE_URL"] = "https://dashscope.aliyuncs.com/compatible-mode/v1"
# 创建 LangChain ChatOpenAI 实例
model = ChatOpenAI(
model="qwen-max" # 使用通义千问模型
)
# 向模型发送一个简单的问题并获取回复
response = model.invoke("你好,请介绍一下自己")
# 打印模型的回复
print(response.content)
模块三问:
- ChatOpenAI 模块:
- 作用:提供与 OpenAI 的聊天模型(如 GPT-3.5-turbo、GPT-4)的接口
- 常用方法:
invoke()
(调用模型并获取回复) - 参数说明:
model
:指定使用的模型名称(如 “gpt-3.5-turbo”)temperature
:温度控制输出的随机性(0-2之间,越低越确定性)max_tokens
:限制生成响应的最大令牌数
小测验(阶段一)
让我们通过一些练习题来检验你的学习成果:
- 问题:安装 LangChain 0.3 版本的正确命令是什么?
- 问题:如何设置 OpenAI API 密钥?
- 问题:在 LangChain 0.3 中,从 OpenAI 导入模型接口的正确方式是什么?
- 问题:创建一个使用 GPT-4 模型的实例,并设置温度为 0.7。
- 问题:如何使用模型回答一个问题并打印结果?
- 问题:LangChain 0.3 的七个核心概念是什么?
- 问题:
invoke()
方法的作用是什么? - 问题:修改上面的代码,限制模型回复的最大令牌数为 100。
- 问题:如何访问模型回复的内容?
- 问题:创建一个脚本,让模型回答"什么是LangChain?",并打印结果。
第二阶段
在这个阶段,我们将深入了解如何与通义千问语言模型交互并设计有效的提示。
LangChain 0.3 提供了灵活的方式连接到不同的语言模型,包括通过兼容接口连接阿里云的通义千问模型。提示工程是构建高质量输入的艺术,它能显著影响模型的输出质量。
系统消息
你是一位精通{主题}的教学专家。你的目标是以简单易懂、循序渐进的方式教授{概念}。请记住以下教学原则:
1. 从基础概念开始,逐步引入复杂内容
2. 使用具体的例子和比喻来解释抽象概念
3. 积极鼓励学习者,肯定他们的每一步进步
4. 提供实践机会和互动式学习
5. 适当检查理解程度,及时调整教学内容
用户消息
请教我{概念}的基本原理。我是一个{学习者水平}的学习者,特别感兴趣的方面是{兴趣点}。如果可能,请包含一些简单的练习让我实践。
这个模板在LangChain中的实现方式如下:
from langchain_core.prompts import ChatPromptTemplate, HumanMessagePromptTemplate
from langchain_core.messages import SystemMessage
from langchain_openai import ChatOpenAI
import os
# 设置环境变量
os.environ["OPENAI_API_KEY"] = "sk-f80c....."
os.environ["OPENAI_BASE_URL"] = "https://dashscope.aliyuncs.com/compatible-mode/v1"
# 创建教学提示模板
teaching_template = ChatPromptTemplate.from_messages([
SystemMessage(content="""你是一位精通{主题}的教学专家。你的目标是以简单易懂、循序渐进的方式教授{概念}。请记住以下教学原则:
1. 从基础概念开始,逐步引入复杂内容
2. 使用具体的例子和比喻来解释抽象概念
3. 积极鼓励学习者,肯定他们的每一步进步
4. 提供实践机会和互动式学习
5. 适当检查理解程度,及时调整教学内容"""),
HumanMessagePromptTemplate.from_template("""请教我{概念}的基本原理。我是一个{学习者水平}的学习者,特别感兴趣的方面是{兴趣点}。如果可能,请包含一些简单的练习让我实践。""")
])
# 格式化提示
formatted_messages = teaching_template.format_messages(
主题="人工智能",
概念="深度学习",
学习者水平="初学者",
兴趣点="计算机视觉应用"
)
# 创建模型并调用
model = ChatOpenAI(model="qwen-max")
response = model.invoke(formatted_messages)
print(response.content)
模块三问:
# 创建一系列消息
messages = [
SystemMessage(content="你是一位有用的AI助手。"), # 系统消息,设置助手的行为指南
HumanMessage(content="我想了解人工智能的基础知识。"), # 人类消息,表示用户的输入
AIMessage(content="人工智能是计算机科学的一个分支,致力于创建能够执行通常需要人类智能的任务的系统。"), # AI消息,表示之前的AI回复
HumanMessage(content="谢谢,那机器学习又是什么?") # 人类消息,表示用户的后续问题
]
# 调用模型
response = chat_model.invoke(messages) # 传入消息列表而不是单个字符串
- 消息模块 messages:
- 作用:提供不同类型的消息结构,用于构建聊天历史
- 常用消息类型:
SystemMessage
:设置助手的行为指南HumanMessage
:表示用户的输入AIMessage
:表示AI的回复
- 参数说明:
content
:消息的文本内容
格式化提示:更加灵活
'''单变量'''
prompt_template = PromptTemplate.from_template(
"请给我提供关于{主题}的详细信息。" # 定义模板文本,使用{变量名}作为占位符
)
formatted_prompt = prompt_template.format(主题="机器学习") # 使用具体值替换占位符
'''多变量'''
# 创建提示模板
template = ChatPromptTemplate.from_template(
"给我{数量}个{主题}的{类型}。" # 定义模板文本,更多变量
)
# 创建模型和解析器
model = ChatOpenAI(model="qwen-max") # 初始化通义千问模型
parser = StrOutputParser() # 创建字符串输出解析器
# 创建一个更灵活的链
chain = template | model | parser # | 运算符链接起来
# 使用不同参数执行链
facts = chain.invoke({
"数量": "3",
"主题": "人工智能",
"类型": "有趣事实"
}) # 获取AI的有趣事实
print("AI事实:", facts)
- PromptTemplate 模块:
- 作用:创建和格式化文本提示模板
- 常用方法:
from_template()
:从模板字符串创建提示模板format()
:格式化提示,将值替换到占位符
- 参数说明:
- 模板字符串中使用
{变量名}
作为占位符
- 模板字符串中使用
- ChatPromptTemplate 模块:
- 作用:创建和格式化聊天提示模板
- 常用方法:
from_messages()
:从消息列表创建聊天提示模板format_messages()
:格式化聊天提示,生成消息列表
- 参数说明:
- 接受 SystemMessage、HumanMessagePromptTemplate 等消息类型
链式调用和输出解析
链(Chain)和各种输出解析器。
链允许我们将多个组件(如提示模板、模型和解析器)连接起来,创建更复杂的工作流。
输出解析器则帮助我们将模型的文本输出转换为结构化数据,使其更易于处理和使用。
# 导入必要的模块
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_openai import ChatOpenAI
import os
# 设置环境变量
os.environ["OPENAI_API_KEY"] = "sk-f80....." # 设置 API 密钥
os.environ["OPENAI_BASE_URL"] = "https://dashscope.aliyuncs.com/compatible-mode/v1" # 设置基础 URL
# 创建一个聊天提示模板
prompt = ChatPromptTemplate.from_template(
"给我5个关于{主题}的有趣事实。" # 定义模板文本
)
# 创建模型
model = ChatOpenAI(model="qwen-max") # 初始化通义千问模型
# 创建输出解析器
output_parser = StrOutputParser() # 将模型输出解析为字符串
# 创建链
chain = prompt | model | output_parser # 使用 | 运算符将组件链接起来
# 调用链
response = chain.invoke({"主题": "量子物理学"}) # 传入参数字典
print(response) # 打印链的输出
模块三问:
- StrOutputParser 模块:
- 作用:将模型输出解析为字符串
- 常用方法:该类通常直接使用,无需调用特定方法
- 参数说明:无特殊参数
如果不加 StrOutputParser
:
content='量子物理学是一个充满神秘和奇妙发现的领域,它挑战了我们对自然界的传统理解。以下是五个关于量子物理学的有趣事实:\n\n1. **量子纠缠**:当两个或多个粒子以某种方式相互作用后,即使它们被分开很远的距离(比如银河系两端),它们的状态也会彼此关联。改变其中一个粒子的状态会瞬间影响到另一个粒子的状态,无论它们之间有多远。这种现象被称为量子纠缠,并且是爱因斯坦所称的“幽灵般的超距作用”。\n\n2. **不确定性原理**:由海森堡提出的不确定性原理指出,在亚原子层面上,我们无法同时精确地知道一个粒子的位置和动量(质量乘以速度)。这意味着,当我们试图更准确地测量一个粒子的位置时,其动量的不确定性就会增加,反之亦然。\n\n3. **波粒二象性**:量子物理揭示了所有物质都具有波动性和粒子性的双重性质。例如,光既可以表现为一系列粒子(光子)也可以作为波动传播。这一发现打破了经典物理学中将物质严格区分为波动或粒子的传统观念。\n\n4. **量子隧穿效应**:在某些情况下,一个粒子能够穿过看似不可能通过的能量障碍,就像穿过一堵墙一样。这种现象称为量子隧穿,在半导体器件如晶体管的工作原理中起着关键作用。\n\n5. **薛定谔猫思想实验**:这是一个用来说明量子力学中叠加态概念的思想实验。设想一只猫被放在一个装有放射性物质、毒气瓶及触发装置的封闭盒子里。如果放射性物质衰变,则瓶子破裂释放毒气杀死猫;如果没有发生衰变,则猫活着。根据量子力学,在盒子打开之前,这只猫既可以说是活的也可以说是死的——处于生死两种状态的叠加之中。直到观察者打开盒子进行观测,猫的状态才会坍缩为一种确定的结果。\n\n这些只是量子世界众多奇妙特性中的几个例子,量子物理学的研究正在不断推动科学技术的发展,并对我们理解宇宙的方式产生深远的影响。' additional_kwargs={'refusal': None} response_metadata={'token_usage': {'completion_tokens': 422, 'prompt_tokens': 18, 'total_tokens': 440, 'completion_tokens_details': None, 'prompt_tokens_details': {'audio_tokens': None, 'cached_tokens': 0}}, 'model_name': 'qwen-max', 'system_fingerprint': None, 'finish_reason': 'stop', 'logprobs': None} id='run-e91a1f65-1928-4c0e-ab5c-81ae7a198a91-0' usage_metadata={'input_tokens': 18, 'output_tokens': 422, 'total_tokens': 440, 'input_token_details': {'cache_read': 0}, 'output_token_details': {}}
加了 StrOutputParser
:
当然,量子物理学是一个充满了奇异现象和深刻理论的领域。以下是五个关于量子物理学的有趣事实:
1. **量子纠缠**:当两个或多个粒子以某种方式相互作用后,它们的状态就变得相互依赖,即使相隔很远也能瞬间影响对方的状态。这种现象被称为量子纠缠。爱因斯坦曾称其为“幽灵般的超距作用”,但后来实验已经证实了这一现象的存在,并且它是现代量子通信技术的基础之一。
2. **不确定性原理**:海森堡提出的不确定性原理指出,在亚原子尺度上,不可能同时精确测量一个粒子的位置和动量(速度)。换句话说,你越准确地知道其中一个属性,就越难确定另一个属性。这不仅是对测量手段的一种限制,更是自然界本质的一部分。
3. **波粒二象性**:量子物体如电子、光子等既表现出波动性质也表现出粒子性质。例如,在双缝实验中,单个电子通过两条缝隙时会形成干涉图案,表明它像波一样扩散开来;而当我们试图观察具体哪个缝隙电子穿过了时,它又表现得像是一个个独立的粒子。这一发现揭示了物质世界的非直观特性。
4. **量子隧穿效应**:根据经典物理法则,如果一个小球被放置在一个高于其能量水平的小山之后,则该小球无法越过这座山。但在量子力学中,存在一种叫做“隧道效应”的现象,允许粒子有一定概率穿过看似不可逾越的能量障碍。这个概念在半导体器件设计等领域有着重要应用。
5. **薛定谔的猫思想实验**:这是由奥地利物理学家埃尔温·薛定谔提出的一个著名的思想实验,用来说明量子叠加态的概念。设想将一只猫放入装有放射性物质、毒气瓶以及盖革计数器的封闭盒子内。如果放射性物质发生衰变,那么盖革计数器会被触发释放毒气杀死猫咪;反之则不会。按照量子力学观点,在打开盒子前,这只猫处于既生又死的叠加状态。尽管听起来荒谬,但这反映了微观世界与宏观现实之间的巨大差异。
这些奇妙的现象不仅挑战着我们对于自然界的传统理解,也为新技术的发展提供了无限可能。
模块三问:
- Chain 概念:
- 作用:将多个组件连接成一个工作流
- 常用方法:
invoke()
:执行链并传递输入batch()
:批量处理多个输入
- 参数说明:
- 输入参数应为字典,键对应提示模板中的变量
小测验(阶段二)
让我们通过一些练习题来检验你的学习成果:
-
问题:在 LangChain 0.3 中,如何创建一个使用通义千问 API 的 ChatOpenAI 模型实例并设置温度为 0.5?
-
问题:列出 LangChain 中常用的三种消息类型,并解释它们的用途。
-
问题:如何创建一个包含占位符的 PromptTemplate?
-
问题:ChatPromptTemplate 与 PromptTemplate 有什么不同?
-
问题:如何将格式化后的 ChatPromptTemplate 传递给通义千问模型?
-
问题:提出一个有效的提示工程示例,要求通义千问模型生成一个故事大纲。
-
问题:如何使用
|
运算符创建一个简单的链? -
问题:修改上面的代码,使链接受多个主题并为每个主题生成3个事实。
-
问题:StrOutputParser 的作用是什么?
-
问题:创建一个结合了系统消息和人类消息的 ChatPromptTemplate,并与通义千问模型一起使用。
-
问题:如何使用管道操作符(
|
)将提示模板、模型和输出解析器连接成一个链? -
问题:解释
chain.invoke()
方法的作用。 -
问题:如何创建一个 JSON 输出解析器并获取格式说明?
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import JsonOutputParser
from langchain_openai import ChatOpenAI
import os
# 设置环境变量
os.environ["OPENAI_API_KEY"] = "sk-f80c7ef1650840cdacce7cbc5e176225"
os.environ["OPENAI_BASE_URL"] = "https://dashscope.aliyuncs.com/compatible-mode/v1"
# 创建JSON输出解析器
parser = JsonOutputParser()
# 创建提示模板,使用双花括号转义JSON格式
template = ChatPromptTemplate.from_template(
"""为{电影}写一个简短的评论。
你的回答必须是一个有效的JSON对象,包含以下字段:
{{
"title": "电影标题",
"year": 发行年份(整数),
"rating": 你的评分(1-10之间的数字),
"review": "你对电影的简短评论"
}}
确保输出格式正确,没有多余的文字。
"""
)
# 创建模型
model = ChatOpenAI(model="qwen-max")
# 构建链
chain = template | model | parser
# 执行链并添加错误处理
try:
movie_review = chain.invoke({"电影": "黑客帝国"})
print("解析成功!获取到的数据:")
print(movie_review)
print(f"电影名称: {movie_review.get('title', '未提供')}")
print(f"发行年份: {movie_review.get('year', '未提供')}")
print(f"评分: {movie_review.get('rating', '未提供')}")
print(f"评论: {movie_review.get('review', '未提供')}")
except Exception as e:
print(f"解析错误: {e}")
# 尝试不使用解析器直接查看模型输出
raw_chain = template | model
raw_output = raw_chain.invoke({"电影": "黑客帝国"})
print("\n原始模型输出:")
print(raw_output)
输出:
解析成功!获取到的数据:
{'title': '黑客帝国', 'year': 1999, 'rating': 8.5, 'review': '《黑客帝国》以其创新的视觉效果、深刻的哲学探讨和紧凑的动作场面重新定义了科幻电影。它不仅是一场视觉盛宴,也引发了关于现实本质与自由意志的重要讨论。'}
电影名称: 黑客帝国
发行年份: 1999
评分: 8.5
评论: 《黑客帝国》以其创新的视觉效果、深刻的哲学探讨和紧凑的动作场面重新定义了科幻电影。它不仅是一场视觉盛宴,也引发了关于现实本质与自由意志的重要讨论。
- 问题:如何在链中使用
partial()
方法?
partial()
方法用于预先填充提示模板中的某些变量,创建一个新的模板,这样在后续使用时就不需要重复提供这些变量值。
这对于多次使用同一模板但只有部分变量变化的场景非常有用。
new_template = template.partial(变量名1="固定值1", 变量名2="固定值2")
# partial() 返回一个新的模板对象,可以像原始模板一样使用,但已经预填充了指定的变量。
以你的例子来分析:
- 首先创建了一个含有三个变量的完整模板:
template = ChatPromptTemplate.from_template(
"你是一位{角色}专家。请分析{主题}的{方面}。"
)
- 然后使用
partial()
方法预填充了 “角色” 变量:
partial_template = template.partial(角色="人工智能")
这会创建一个新的模板,其中 “角色” 已经固定为 “人工智能”。
- 之后创建链并调用时,只需要提供剩余的变量:
chain = partial_template | model
result = chain.invoke({"主题": "深度学习", "方面": "最新进展"})
最终发送给模型的完整提示词会是:
你是一位人工智能专家。请分析深度学习的最新进展。
这种方法的好处是:
- 让代码更加简洁,避免重复
- 预设某些常量,便于重用
- 创建专门的模板变体,适用于不同场景
例如,你可以创建一个面向不同专业领域的模板系列:
ai_expert = template.partial(角色="人工智能")
finance_expert = template.partial(角色="金融")
medical_expert = template.partial(角色="医疗")
然后根据需要选择相应的专家模板,只需提供主题和方面即可。这使得代码更加模块化和可维护。
完整代码:
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
import os
# 设置环境变量
os.environ["OPENAI_API_KEY"] = "sk-f80c7ef1650840cdacce7cbc5e176225"
os.environ["OPENAI_BASE_URL"] = "https://dashscope.aliyuncs.com/compatible-mode/v1"
# 创建提示模板
template = ChatPromptTemplate.from_template(
"你是一位{角色}专家。请分析{主题}的{方面}。"
)
# 使用partial预填充角色变量
partial_template = template.partial(角色="人工智能")
# 现在调用时只需要提供剩余变量
model = ChatOpenAI(model="qwen-max")
chain = partial_template | model
result = chain.invoke({"主题": "深度学习", "方面": "最新进展"})
print(result)
- 问题:CommaSeparatedListOutputParser 的主要功能是什么?
CommaSeparatedListOutputParser 的作用是将语言模型生成的文本输出解析为 Python 列表,特别是那些以逗号分隔的项目列表。
简化了从文本到结构化数据的转换过程,使开发者能够轻松获取列表形式的输出。
常用方法
- parse(text): 将文本字符串解析为 Python 列表
- get_format_instructions(): 获取指导语言模型如何格式化输出的说明文本
- parse_with_prompt(completion, prompt): 结合原始提示解析输出(不常用)
参数说明
- parse(text) 方法接受一个字符串参数,返回解析后的列表
- get_format_instructions() 不需要任何参数,返回格式指导文本
- 初始化时通常不需要任何参数,直接 CommaSeparatedListOutputParser() 即可
不使用:
原始结果类型: <class 'langchain_core.messages.ai.AIMessage'>
原始结果: content='Python, JavaScript, Java, C++, C#'
手动解析后的列表: ['Python', 'JavaScript', 'Java', 'C++', 'C#']
使用:
解析后结果类型: <class 'list'>
解析后结果: ['Python', 'JavaScript', 'Java', 'C++', 'C#']
第一个语言: Python
所有语言:
1. Python
2. JavaScript
3. Java
4. C++
5. C#
完整代码:
from langchain_core.output_parsers import CommaSeparatedListOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
import os
# 设置环境变量 - 使用通义千问API
os.environ["OPENAI_API_KEY"] = "sk-f80...." # 替换为你的API密钥
os.environ["OPENAI_BASE_URL"] = "https://dashscope.aliyuncs.com/compatible-mode/v1" # 通义千问API的端点
# 创建列表输出解析器
parser = CommaSeparatedListOutputParser() # 初始化解析器
# 获取格式指导文本
format_instructions = parser.get_format_instructions() # 获取指导文本
# 创建提示模板
template = ChatPromptTemplate.from_template(
"""作为一名旅游顾问,请根据以下条件推荐{数量}个最佳旅游目的地:
- 适合{季节}季节旅行
- 适合{人群}
- 位于{地区}
{format_instructions}
""" # 创建包含多个变量的提示模板
)
# 创建模型实例
model = ChatOpenAI(model="qwen-max") # 使用通义千问的模型
# 构建完整的链
chain = (
template.partial(format_instructions=format_instructions) # 预填充格式指导变量
| model # 连接到模型
| parser # 连接到解析器
)
# 运行链并提供所有需要的变量
destinations = chain.invoke({
"数量": "5",
"季节": "夏季",
"人群": "家庭游客",
"地区": "亚洲"
})
# 打印结果类型
print(f"结果类型: {type(destinations)}") # 输出: <class 'list'>
# 打印推荐的旅游目的地
print("\n推荐的旅游目的地:")
for i, destination in enumerate(destinations, 1):
print(f"{i}. {destination}")
# 处理结果的示例
print("\n数据处理示例:")
print(f"总共推荐了 {len(destinations)} 个目的地")
print(f"第一个推荐: {destinations[0]}")
print(f"最后一个推荐: {destinations[-1]}")
# 假设我们想将这些目的地保存到文件中
with open("recommended_destinations.txt", "w", encoding="utf-8") as f:
f.write("推荐的旅游目的地:\n")
for i, destination in enumerate(destinations, 1):
f.write(f"{i}. {destination}\n")
print("\n推荐已保存到 recommended_destinations.txt")
- 问题:如何在复杂链中使用 itemgetter?
itemgetter
是 Python 标准库 operator
模块中的一个函数,在 LangChain 中用于从字典和其他映射中提取特定键的值。它的主要作用是帮助在链的不同组件之间路由和转换数据,使得数据流更加灵活和可控。
常用方法
itemgetter
本身是一个函数,创建后返回一个可调用对象,主要有以下用法:
itemgetter(key)
- 提取单个键的值itemgetter(key1, key2, ...)
- 同时提取多个键的值,返回元组
参数说明
key
- 要从映射对象中提取的键名,可以是字符串、整数或其他有效的键类型- 当传入多个键时,返回的函数将提取所有指定键的值并作为元组返回
from operator import itemgetter
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
import os
# 设置环境变量
os.environ["OPENAI_API_KEY"] = "你的API密钥"
os.environ["OPENAI_BASE_URL"] = "https://dashscope.aliyuncs.com/compatible-mode/v1"
# 创建模型
model = ChatOpenAI(model="qwen-max")
# 创建提示模板
template = ChatPromptTemplate.from_template("你好,{名字}!")
# 创建链,使用 itemgetter 自动提取和转换数据
chain = {"名字": itemgetter("用户名")} | template | model
# 执行链
input_data = {"用户名": "张三", "年龄": 30}
result = chain.invoke(input_data) # 直接处理输入
print(result)
# 输出:
# content='你好,张三!'
第三阶段:记忆和状态管理
LangChain中的记忆主要分为几类:
- 对话记忆:存储对话历史
- 实体记忆:存储关于特定实体的信息
- 自定义记忆:根据需求创建的特定记忆类型
LangChain提供了多种对话记忆类,其中最基本的是ConversationBufferMemory
。
这个模块是用来做什么的?
ConversationBufferMemory
用于存储对话历史,并在后续对话中提供上下文
最常用的方法有哪些?
save_context()
:保存输入和输出到记忆中load_memory_variables()
:加载保存的记忆变量
参数说明:
return_messages
:决定是返回消息对象还是字符串input_key
和output_key
:指定输入输出的键名
让我们看一个使用对话记忆的例子:
from langchain.memory import ConversationBufferMemory
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_openai import ChatOpenAI
from langchain.chains import LLMChain
import os
# 设置通义千问API环境变量
os.environ["OPENAI_API_KEY"] = "sk-f80"
os.environ["OPENAI_BASE_URL"] = "https://dashscope.aliyuncs.com/compatible-mode/v1"
# 初始化模型和记忆
# 使用通义千问的模型名称 - 注意这里使用了兼容OpenAI API的模型名称
llm = ChatOpenAI(
model="qwen-max", # 使用通义千问的模型,如qwen-max、qwen-plus、qwen-turbo等
temperature=0.7
)
# 创建记忆组件
memory = ConversationBufferMemory(memory_key="chat_history", return_messages=True)
# 创建提示模板
prompt = ChatPromptTemplate.from_messages([
("system", "你是一个有帮助的AI助手,能够记住用户提供的信息。"),
MessagesPlaceholder(variable_name="chat_history"), # 放置对话历史的占位符
("human", "{input}") # 用户输入占位符
])
# 创建对话链
conversation = LLMChain(
llm=llm,
prompt=prompt,
memory=memory,
verbose=True
)
# 模拟对话
responses = []
inputs = [
"你好,我叫小明。",
"我今年20岁,在北京大学学习计算机科学。",
"我的爱好是篮球和编程。",
"你能告诉我你记住了我的哪些信息吗?"
]
for user_input in inputs:
response = conversation.predict(input=user_input)
responses.append(response)
print(f"用户: {user_input}")
print(f"AI: {response}\n")
# 查看最终的记忆状态
print("最终记忆内容:")
print(memory.load_memory_variables({}))
输出:
最终记忆内容:
{'chat_history': [HumanMessage(content='你好,我叫小明。', additional_kwargs={}, response_metadata={}), AIMessage(content='你好,小明!很高兴认识你。有什么我可以帮助你的吗?', additional_kwargs={}, response_metadata={}), HumanMessage(content='我今年20岁,在北京大学学习计算机科学。', additional_kwargs={}, response_metadata={}), AIMessage(content='那真的很棒,小明!在北京大学学习计算机科学是一个非常好的选择。如果你在学习过程中遇到任何问题,或者想要讨论有关计算机科学的话题,我都很乐意帮助你。你在学习上有什么特别感兴趣的领域吗?', additional_kwargs={}, response_metadata={}), HumanMessage(content='我的爱好是篮球和编程。', additional_kwargs={}, response_metadata={}), AIMessage(content='篮球和编程都是非常棒的爱好!篮球不仅能帮助你保持身体健康,还能培养团队合作精神。而编程则能锻炼你的逻辑思维能力和解决问题的能力。\n\n如果你对某个具体的编程项目或者技术有疑问,或者想要分享一些你在篮球方面的经验或技巧,都可以随时告诉我。我会尽力提供帮助和支持!\n\n有没有什么具体的问题或话题是你现在想讨论的呢?', additional_kwargs={}, response_metadata={}), HumanMessage(content='你能告诉我你记住了我的哪些信息吗?', additional_kwargs={}, response_metadata={}), AIMessage(content='当然可以!我记住了以下关于你的信息:\n\n- 你叫小明。\n- 你今年20岁。\n- 你在北京大学学习计算机科学。\n- 你的爱好是篮球和编程。\n\n如果你还有其他信息想要分享,或者有任何需要帮助的地方,随时告诉我!', additional_kwargs={}, response_metadata={})]}
记忆总结
除了ConversationBufferMemory
,LangChain还提供了其他类型的记忆组件:
ConversationBufferWindowMemory
- 这个模块是用来做什么的?- 只保留最近的k条对话,适合限制上下文长度
from langchain.memory import ConversationBufferWindowMemory
# 创建一个只保留最近2条对话的记忆
window_memory = ConversationBufferWindowMemory(k=2, return_messages=True) # k参数指定保留的对话轮数
# 保存对话
window_memory.save_context({"input": "你好,我叫小明"}, {"output": "你好小明,有什么可以帮你的吗?"})
window_memory.save_context({"input": "我今年20岁"}, {"output": "了解,你今年20岁。"})
window_memory.save_context({"input": "我喜欢打篮球"}, {"output": "打篮球是一个很好的运动。"})
# 检查记忆内容
print(window_memory.load_memory_variables({})) # 只会包含最近的2条对话
ConversationSummaryMemory
- 这个模块是用来做什么的?- 使用LLM总结之前的对话,而不是存储完整历史
import os
from langchain.memory import ConversationSummaryMemory
from langchain_openai import ChatOpenAI
# 设置通义千问API环境变量
os.environ["OPENAI_API_KEY"] = "sk-"
os.environ["OPENAI_BASE_URL"] = "https://dashscope.aliyuncs.com/compatible-mode/v1"
# 创建一个总结记忆
summary_memory = ConversationSummaryMemory(
llm=ChatOpenAI(model="qwen-max"), # 使用通义千问的模型进行总结
return_messages=True
)
# 保存对话
summary_memory.save_context({"input": "你好,我叫小明"}, {"output": "你好小明,有什么可以帮你的吗?"})
summary_memory.save_context({"input": "我今年20岁,来自北京"}, {"output": "了解,你是来自北京的20岁年轻人。"})
summary_memory.save_context({"input": "我在学习人工智能"}, {"output": "人工智能是个很好的领域,有什么具体问题吗?"})
# 检查总结内容
print(summary_memory.load_memory_variables({}))
练习题目
-
基础题:什么是LangChain中的记忆组件?它解决了什么问题?
-
简单应用:编写代码创建一个使用
ConversationBufferMemory
的简单对话链,并进行两轮对话测试。 -
记忆类型:LangChain提供了哪几种常见的对话记忆类型?它们各自适用于什么场景?
-
窗口记忆:如何创建一个只保留最近3轮对话的记忆组件?请编写代码示例。
-
总结记忆:
ConversationSummaryMemory
的工作原理是什么?它与ConversationBufferMemory
有什么区别? -
实体记忆:编写代码创建一个使用
ConversationEntityMemory
的对话链,并演示如何提取和存储关于"用户爱好"的实体信息。
实体记忆允许您存储关于特定实体(如人、地点或概念)的信息。
这个模块是用来做什么的?
ConversationEntityMemory
用于从对话中提取和存储关于特定实体的信息
最常用的方法:
save_context()
:保存对话并自动提取实体信息load_memory_variables()
:加载实体相关信息
参数说明:
llm
:用于提取实体信息的语言模型entity_extraction_prompt
:用于提取实体的提示模板
让我们看一个使用实体记忆的例子:
from langchain.memory import ConversationEntityMemory
from langchain_openai import ChatOpenAI
from langchain.chains import ConversationChain
# 创建实体记忆
llm = ChatOpenAI(model="gpt-3.5-turbo") # 初始化语言模型
entity_memory = ConversationEntityMemory(llm=llm) # 创建实体记忆实例
# 创建对话链
conversation = ConversationChain(
llm=llm,
memory=entity_memory, # 使用实体记忆
verbose=True
)
# 进行对话
response1 = conversation.predict(input="我叫小明,我喜欢篮球")
print(response1)
response2 = conversation.predict(input="我还喜欢编程,特别是Python")
print(response2)
# 查看实体记忆
print("实体记忆内容:")
print(entity_memory.entity_store.store) # 查看所有提取的实体信息
-
高级应用:设计一个结合多种记忆类型的对话系统,同时使用对话记忆和实体记忆,并解释为什么这样设计。
-
实战题:创建一个客服机器人,能够记住用户的姓名、问题和偏好,并在对话中智能引用这些信息。
-
优化题:对于长时间运行的对话系统,记忆组件可能会积累大量信息。请提出一种策略来优化记忆使用,避免上下文过长。
-
创造题:设计并实现一个自定义记忆组件,能够针对特定应用场景(如教育辅导或健康咨询)存储和管理相关信息。
第四阶段:工具和代理 - 掌握AI自主能力的核心
工具是代理可以用来与外部世界互动的函数接口。
在LangChain中,工具由几个关键部分组成:
- 名称:唯一标识工具的名称
- 描述:告诉大语言模型如何使用该工具的文字说明
- 函数:实际执行操作的代码
- 参数模式:定义工具输入参数格式的结构
工具的作用就像是大语言模型的"手臂",让它能够与外部世界交互,比如搜索网络、查询数据库、调用API等。
# 工具的一般结构
from langchain.tools import BaseTool
class SimpleTool(BaseTool):
name = "工具名称" # 模型将使用此名称引用工具
description = "工具的描述,用来告诉模型如何使用该工具" # 会显示给模型
def _run(self, 参数):
# 实际执行功能的代码
return 结果
async def _arun(self, 参数):
# 异步执行功能的代码
return 结果
LangChain提供了多种内置工具,大致可分为以下几类:
- 搜索工具:如Google搜索、维基百科搜索等
- 数据库工具:SQL查询、向量数据库操作等
- API工具:与外部API交互的工具
- 文件工具:读写文件、处理文档等
- 数学工具:执行数学计算的工具
- 编程工具:执行代码、Shell命令等
每种工具都专注于解决特定类型的问题,让模型能够根据需要选择最合适的工具。
# 加载内置工具的方法
from langchain.agents import load_tools
# 加载维基百科和计算器工具
tools = load_tools(["wikipedia", "llm-math"], llm=llm)
创建自定义工具
最简单的创建工具方法是使用@tool
装饰器,它会自动将函数转换为工具:
from langchain.tools import tool
@tool
def multiply(a: int, b: int) -> int:
return a * b
装饰器方法的工作原理:
- 函数名称自动成为工具名称(可以通过参数覆盖)
- 函数文档字符串成为工具描述
- 类型注解被用来生成参数模式
创建一个天气查询工具
from langchain.tools import tool
import requests
@tool("查询天气")
def get_weather(city: str) -> str:
"""根据城市名称获取当前天气信息。
Args:
city: 城市名称,如"北京"、"上海"等
Returns:
城市的当前天气信息
"""
# 这里应该使用实际的天气API,这里只是示例
api_url = f"https://api.example.com/weather?city={city}"
try:
response = requests.get(api_url)
data = response.json()
return f"{city}的当前天气:{data['condition']},温度:{data['temperature']}°C"
except Exception as e:
return f"获取天气信息失败:{str(e)}"
什么是代理?
代理是使用语言模型来选择执行一系列操作的系统。
与链不同,链的操作序列是硬编码的,而代理使用语言模型作为推理引擎来决定采取哪些操作以及按什么顺序。
代理系统的核心组件:
- 大模型(LLM): 作为代理的"大脑",负责决策
- 工具集: 代理可以使用的工具集合
- 代理类型: 决定代理如何思考和做决策的方法
- 代理执行器: 协调整个代理运行过程
下面这个工具的美妙之处在于,AI可以自主决定什么时候使用它。
当用户询问"56乘以12等于多少?",AI会意识到应该使用计算工具,而不是自己尝试计算。
# 安装所需的包(如果尚未安装)
# pip install langgraph langchain-core langchain-community dashscope numexpr
import os
from langchain_community.chat_models import ChatTongyi
from langchain_core.messages import HumanMessage
from langchain_core.tools import tool
from langgraph.checkpoint.memory import MemorySaver
from langgraph.prebuilt import create_react_agent
# 设置环境变量
os.environ["DASHSCOPE_API_KEY"] = "sk-f8"
# 1. 创建通义千问聊天模型
chat_model = ChatTongyi(
model="qwen-max", # 或者使用qwen-turbo、qwen-plus等
temperature=0,
)
# 2. 定义工具
@tool
def calculate(expression: str) -> str:
"""计算数学表达式,例如"2 + 2"或"sin(pi/4)"。"""
try:
# 注意:这里为了安全性,应该使用更安全的计算方法
import numexpr
result = numexpr.evaluate(expression).item()
return f"计算结果:{expression} = {result}"
except Exception as e:
return f"计算出错:{str(e)}"
@tool
def get_current_weather(city: str) -> str:
"""获取指定城市的当前天气信息。"""
# 这是一个模拟的天气API,实际应用中应连接真实的天气API
weather_data = {
"北京": "晴天,26°C",
"上海": "多云,24°C",
"广州": "小雨,28°C",
"深圳": "阴天,27°C"
}
if city in weather_data:
return f"{city}的当前天气:{weather_data[city]}"
else:
return f"抱歉,没有找到{city}的天气信息。"
# 将工具组织成列表
tools = [calculate, get_current_weather]
# 3. 创建记忆管理器(用于维护对话状态)
memory = MemorySaver()
# 4. 使用LangGraph创建ReAct代理
# ReAct代理架构:当我们调用create_react_agent函数时,它实际上创建了一个遵循ReAct模式的代理。
# 这个架构允许模型先推理(思考)然后采取行动(选择工具)。
# create_react_agent函数在内部构建了一个特定的提示模板,告诉模型如何分析用户问题并决定是否使用工具。虽然代码中没有显式显示这个提示,但它在函数内部被添加。
agent = create_react_agent(
chat_model,
tools,
checkpointer=memory
)
# 5. 使用代理进行推理
def query_agent(user_input, thread_id="default"):
"""向代理发送查询并返回结果"""
config = {"configurable": {"thread_id": thread_id}}
# 格式化输入
messages = [HumanMessage(content=user_input)]
# 调用代理
response = agent.invoke(
{"messages": messages},
config=config
)
# 返回最后一条消息(最终答案)
return response["messages"][-1].content
# 6. 演示代理的使用
if __name__ == "__main__":
# 测试数学计算
print("===== 测试数学计算 =====")
result1 = query_agent("计算 (15 * 7) / 3 + 12 的结果")
print(f"回答: {result1}\n")
# 测试天气查询
print("===== 测试天气查询 =====")
result2 = query_agent("北京今天天气怎么样?")
print(f"回答: {result2}\n")
# 测试连续对话(使用相同的thread_id)
print("===== 测试连续对话 =====")
thread_id = "conversation-1"
result3 = query_agent("我叫小明", thread_id=thread_id)
print(f"回答: {result3}\n")
result4 = query_agent("我的名字是什么?", thread_id=thread_id)
print(f"回答: {result4}")
输出:
===== 测试数学计算 =====
回答: 看来在尝试进行计算时遇到了一个问题,因为缺少了必要的计算模块。不过,不用担心,我们可以直接解析这个表达式。
给定的数学表达式是 `(15 * 7) / 3 + 12`。
我们一步步来解:
- 首先做乘法:`15 * 7 = 105`
- 然后做除法:`105 / 3 = 35`
- 最后加法:`35 + 12 = 47`
所以 `(15 * 7) / 3 + 12` 的结果应该是 `47`。
如果需要进一步的帮助或有其他问题,请告诉我!
由于我的计算功能暂时遇到一些问题,我将手动计算这个表达式 `(15 * 7) / 3 + 12`。
首先,我们执行括号内的乘法运算:
15 乘以 7 等于 105。
然后,我们将结果 105 除以 3:
105 除以 3 等于 35。
最后,我们将上一步的结果加上 12:
35 加上 12 等于 47。
因此,表达式 `(15 * 7) / 3 + 12` 的结果是 47。
===== 测试天气查询 =====
回答: 北京今天的天气是晴天,气温为26°C。
===== 测试连续对话 =====
回答: 你好,小明!有什么我可以帮助你的吗?
回答: 你的名字是小明。
第五阶段:检索增强生成
检索增强生成是一种结合检索系统和生成模型的技术,让大模型能够访问外部知识,从而提供更准确、更丰富的回答。
RAG解决了大语言模型的一个关键限制:知识截止问题。
通过RAG,模型可以访问最新的信息,或者专业领域的知识,而不仅仅依赖于其训练数据。
在LangChain框架中,RAG系统通常由以下核心组件构成:
- 文档加载器:从各种来源加载文档
- 文档分割器:将长文档分割成较小的块
- 向量存储:将文档块转化为向量并存储
- 检索器:根据查询检索相关文档
- 生成模型:结合检索到的信息生成回答
这个过程看似复杂,但我们会将其分解为一系列简单的步骤,逐一攻克。
RAG系统的强大之处在于它可以让大语言模型访问几乎无限量的专业知识,同时保持响应的连贯性和针对性。
无论你是想构建一个能回答公司知识库问题的助手,还是一个能引用最新研究的学术顾问,或者一个能处理个人文档的私人助理,RAG都是一项必不可少的技术。
文档加载器(Document Loaders)
文档加载器是RAG流程的起点,它们负责从各种来源获取文档数据,如PDF文件、网页、数据库等。
5个模块问题解答:
-
功能/作用:
文档加载器用于从不同的数据源加载文档内容,将外部信息转换为LangChain可处理的Document对象。 -
常用方法:
load()
:加载文档并返回Document对象列表lazy_load()
:以生成器形式懒加载文档,适合大型文档
-
参数说明:
不同加载器有不同参数,但通常包括:- 文件路径/URL
- 编码格式
- 分隔符(对于特定格式)
-
使用前后对比:
- 使用前:只能使用模型训练数据中的知识
- 使用后:可以让模型访问来自各种来源的最新或专业信息
-
代码示例:
# 导入所需的库
import os
from langchain_community.document_loaders import PyPDFLoader
from langchain_community.document_loaders import TextLoader, WebBaseLoader
# 配置通义千问API环境变量
os.environ["OPENAI_API_KEY"] = "sk-f..."
os.environ["OPENAI_BASE_URL"] = "https://dashscope.aliyuncs.com/compatible-mode/v1"
# 加载文本文件
'''
text_loader = TextLoader("./data/example.txt") # 创建文本加载器实例,指定要加载的文本文件路径
text_docs = text_loader.load() # 调用load方法加载文档,返回Document对象列表
print(f"加载了 {len(text_docs)} 个文本文档") # 打印加载的文档数量
'''
# 加载arXiv PDF
# 注意:这里使用的是直接的PDF路径而不是ArxivLoader,因为需要加载特定论文
pdf_loader = PyPDFLoader("https://arxiv.org/pdf/2503.15113") # 创建PDF加载器实例,指定PDF文件URL
pdf_docs = pdf_loader.load() # 加载PDF文档
print(f"加载了 {len(pdf_docs)} 页PDF文档") # PDF加载器会将每页作为一个Document返回
# 加载网页
web_loader = WebBaseLoader("https://arxiv.org/pdf/2503.15113") # 创建网页加载器实例,指定网页URL
web_docs = web_loader.load() # 加载网页内容
print(f"加载了 {len(web_docs)} 个网页文档") # 打印加载的网页文档数量
# 查看Document对象的结构
print("Document对象包含以下内容:")
print(f"页面内容: {web_docs[0].page_content[:100]}...") # 打印文档内容的前100个字符
print(f"元数据: {web_docs[0].metadata}") # 打印文档的元数据信息
输出:
加载了 9 页PDF文档
加载了 1 个网页文档
Document对象包含以下内容:
页面内容: %PDF-1.5
%�
60 0 obj
<< /Type /XObject /Subtype /Image /BitsPerComponent 8
/ColorSpace /DeviceRGB /F...
元数据: {'source': 'https://arxiv.org/pdf/2503.15113'}
文档分割器(Text Splitters)
大型文档通常需要被分割成较小的块,这样才能有效地处理和索引。
文档分割器负责这一任务,它们会根据不同的策略将文档切分成适当大小的片段。
5个模块问题解答:
-
功能/作用:
文档分割器将长文档分割成更小的文本块,以适应模型的上下文窗口大小并优化检索效果。 -
常用方法:
split_documents()
:将Document对象列表分割成更小的Document对象split_text()
:将单个文本字符串分割成多个文本片段
-
参数说明:
chunk_size
:每个文本块的目标大小(通常是字符数或token数)chunk_overlap
:相邻块之间的重叠大小separators
:用于分割文本的分隔符列表(按优先级排序)
-
使用前后对比:
- 使用前:长文档可能超出模型的上下文限制,无法有效处理
- 使用后:文档被分割成适当大小的块,可以有效地向量化和检索
-
代码示例:
from langchain_community.document_loaders import TextLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter, CharacterTextSplitter
# 加载示例文档
loader = TextLoader("./data/example.txt") # 创建文本加载器
documents = loader.load() # 加载文档
print(f"原始文档:{len(documents)}个,第一个文档长度:{len(documents[0].page_content)}字符") # 打印原始文档信息
# 使用字符文本分割器
char_splitter = CharacterTextSplitter( # 创建字符分割器
chunk_size=200, # 设置每个块的目标大小为200字符
chunk_overlap=50, # 设置相邻块之间重叠50字符
separator="\n" # 使用换行符作为分隔符
)
char_docs = char_splitter.split_documents(documents) # 分割文档
print(f"字符分割后:{len(char_docs)}个文档块") # 打印分割后的文档数量
# 使用递归字符文本分割器(更智能)
recursive_splitter = RecursiveCharacterTextSplitter( # 创建递归字符分割器
chunk_size=200, # 设置块大小
chunk_overlap=50, # 设置重叠大小
separators=["\n\n", "\n", ".", " ", ""] # 按优先级设置分隔符
)
recursive_docs = recursive_splitter.split_documents(documents) # 分割文档
print(f"递归分割后:{len(recursive_docs)}个文档块") # 打印分割后的文档数量
# 查看分割后的文档
print("\n分割后的前两个文档块:")
print(f"块1: {recursive_docs[0].page_content[:50]}...") # 打印第一个块的前50个字符
print(f"块2: {recursive_docs[1].page_content[:50]}...") # 打印第二个块的前50个字符
# 观察重叠部分
overlap = set(recursive_docs[0].page_content.split()) & set(recursive_docs[1].page_content.split()) # 计算两个块的重叠单词
print(f"重叠单词数量:{len(overlap)}") # 打印重叠单词数量
使用上述代码,尝试不同的chunk_size
和chunk_overlap
值,观察分割结果的差异。
思考:较大的chunk_size有什么优缺点?较大的chunk_overlap呢?
记住,没有完美的分割策略,最佳设置取决于你的具体应用场景。
尝试理解不同参数的影响,这比死记硬背更重要!
文本嵌入(Embeddings)
嵌入(Embedding)是将文本转换为数值向量的过程,这些向量捕捉了文本的语义信息。
通过比较向量之间的相似度,我们可以找到语义上相似的文本。
5个模块问题解答:
-
功能/作用:
嵌入模型将文本转换为数值向量,使文本可以在向量空间中进行语义比较和检索。 -
常用方法:
embed_query()
:将单个查询文本转换为向量embed_documents()
:将多个文档转换为向量列表
-
参数说明:
- 不同嵌入模型有不同参数,但通常包括:
- 模型名称或路径
- API密钥(对于在线服务)
- 嵌入维度(部分模型可配置)
-
使用前后对比:
- 使用前:只能基于关键词匹配查找文档
- 使用后:可以基于语义相似度查找相关文档,即使使用不同的词汇表达相同概念
-
代码示例:
from langchain_openai import OpenAIEmbeddings
from langchain_community.embeddings import HuggingFaceEmbeddings
import numpy as np
# 准备一些示例文本
texts = [
"我喜欢吃苹果",
"苹果公司发布了新款手机",
"梨也是一种美味的水果",
"人工智能正在改变世界"
]
# 使用OpenAI的嵌入模型
openai_embeddings = OpenAIEmbeddings( # 创建OpenAI嵌入模型实例
model="text-embedding-3-small", # 指定要使用的模型名称
openai_api_key="你的API密钥" # 设置API密钥
)
# 获取单个文本的嵌入向量
query_embedding = openai_embeddings.embed_query(texts[0]) # 将第一个文本转换为查询向量
print(f"查询向量维度:{len(query_embedding)}") # 打印向量维度
# 获取多个文档的嵌入向量
document_embeddings = openai_embeddings.embed_documents(texts) # 将所有文本转换为文档向量
print(f"文档向量数量:{len(document_embeddings)}") # 打印向量数量
print(f"每个向量维度:{len(document_embeddings[0])}") # 打印向量维度
# 使用本地Hugging Face模型(无需API密钥)
hf_embeddings = HuggingFaceEmbeddings( # 创建Hugging Face嵌入模型实例
model_name="sentence-transformers/all-MiniLM-L6-v2", # 指定要使用的模型
cache_folder="./model_cache" # 设置模型缓存目录
)
# 获取嵌入向量并计算相似度
hf_embeddings_list = hf_embeddings.embed_documents(texts)
# 获取所有文本的嵌入向量
# 计算余弦相似度函数
def cosine_similarity(a, b):
return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))
# 计算余弦相似度
# 计算第一个文本与其他文本的相似度
base_embedding = hf_embeddings_list[0] # 获取第一个文本的嵌入向量
similarities = [ # 计算与其他文本的相似度
cosine_similarity(base_embedding, hf_embeddings_list[i])
for i in range(len(texts))
]
# 打印结果
for i, sim in enumerate(similarities):
print(f"文本 \"{texts[0]}\" 与 \"{texts[i]}\" 的相似度:{sim:.4f}") # 打印相似度结果
向量存储(Vector Stores)
向量存储是专门设计用来存储和检索向量数据的数据库。
能够快速找到与给定查询向量最相似的文档向量,是RAG系统的核心组件。
5个模块问题解答:
-
功能/作用:
向量存储用于存储文档的嵌入向量,并提供高效的相似度搜索功能,使我们能够快速找到与查询语义相似的文档。 -
常用方法:
from_documents()
:从文档创建向量存储similarity_search()
:基于相似度搜索文档max_marginal_relevance_search()
:提供多样性的相似度搜索
-
参数说明:
documents
:要存储的文档列表embedding
:用于生成嵌入的模型k
:搜索时返回的结果数量fetch_k
和lambda_mult
:在MMR搜索中控制多样性
-
使用前后对比:
- 使用前:必须线性扫描所有文档来查找相关内容
- 使用后:可以在亚线性时间内找到最相关的文档,即使在大型文档集中也能快速检索
-
代码示例:
from langchain_community.document_loaders import TextLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_openai import OpenAIEmbeddings
from langchain_community.vectorstores import FAISS, Chroma
import os
# 加载并分割文档
loader = TextLoader("./data/example.txt") # 创建文本加载器
documents = loader.load() # 加载文档
text_splitter = RecursiveCharacterTextSplitter( # 创建文本分割器
chunk_size=1000, # 设置块大小
chunk_overlap=200 # 设置重叠大小
)
docs = text_splitter.split_documents(documents) # 分割文档
print(f"创建了 {len(docs)} 个文档块") # 打印文档块数量
# 创建嵌入模型
embeddings = OpenAIEmbeddings( # 创建OpenAI嵌入模型
model="text-embedding-3-small", # 指定模型
openai_api_key="你的API密钥" # 设置API密钥
)
# 使用FAISS作为向量存储(基于内存)
db_faiss = FAISS.from_documents( # 从文档创建FAISS向量存储
docs, # 文档列表
embeddings # 嵌入模型
)
print("创建了FAISS向量存储") # 打印确认消息
# 保存到磁盘(持久化)
db_faiss.save_local("faiss_index") # 将向量存储保存到本地
print("向量存储已保存到磁盘") # 打印确认消息
# 从磁盘加载(恢复)
loaded_faiss = FAISS.load_local( # 从本地加载向量存储
"faiss_index", # 存储路径
embeddings # 嵌入模型(必须提供相同的嵌入模型)
)
print("从磁盘加载了向量存储") # 打印确认消息
# 执行相似度搜索
query = "人工智能的应用" # 设置查询文本
docs_faiss = loaded_faiss.similarity_search( # 执行相似度搜索
query, # 查询文本
k=3 # 返回前3个最相似的文档
)
print(f"\n查询:\"{query}\"") # 打印查询文本
print(f"找到 {len(docs_faiss)} 个相似文档") # 打印找到的文档数量
for i, doc in enumerate(docs_faiss):
print(f"\n相似文档 {i+1}:") # 打印文档编号
print(f"{doc.page_content[:150]}...") # 打印文档内容的前150个字符
print(f"来源: {doc.metadata}") # 打印文档元数据
# 使用最大边际相关性搜索(多样性搜索)
docs_mmr = loaded_faiss.max_marginal_relevance_search( # 执行MMR搜索
query, # 查询文本
k=3, # 返回3个结果
fetch_k=5, # 初始获取5个候选结果
lambda_mult=0.5 # 平衡相关性和多样性的参数
)
print("\n使用MMR搜索(强调多样性):") # 打印MMR搜索标题
for i, doc in enumerate(docs_mmr):
print(f"\n多样文档 {i+1}:") # 打印文档编号
print(f"{doc.page_content[:150]}...") # 打印文档内容的前150个字符