LangChain系列
文章目录
- LangChain系列
- 前言
- 一、消息管理
- 二、提示词
-
- 2.1、提示词模板
- 2.2、少样本提示
- 2.3、示例选择器
-
- 2.3.1、按长度选择示例
- 2.3.2、按语义相似性选择示例
- [2.3.3、 按最大边际相关性选择示例(MMR)](#2.3.3、 按最大边际相关性选择示例(MMR))
- [2.3.4、通过 ngram 重叠选择示例(Ngram)](#2.3.4、通过 ngram 重叠选择示例(Ngram))
- 2.4、输出解析器
-
- 2.4.1、解析结构化对象输出
- [2.4.2、解析 JSON 输出](#2.4.2、解析 JSON 输出)
- 三、文档加载器
-
- [3.1、Document 文档类](#3.1、Document 文档类)
- 3.2、文本分割器
- 四、嵌入模型
- 五、向量数据库
前言
提示:以下是本篇文章正文内容,下面案例可供参考
一、消息管理
1.1、多轮对话
在使用大型语言模型时,多轮对话的连贯性为用户带来了便利体验。然而,当系统暂不支持此功能时,有效管理历史消息就显得尤为关键。
在LangChain旧版本中,可通过RunnableWithMessageHistory类来封装其他Runnable对象,实现聊天消息历史记录的管理。该功能会持续追踪模型的输入输出,并将这些数据存储至指定数据库。在后续交互中,系统会自动加载这些历史消息,并将其作为输入内容的一部分传递给处理链。
该功能通过回调函数实现,该函数接收一个字符串类型的session_id参数,并返回对应的聊天消息历史记录实例。
python
from langchain_core.chat_history import BaseChatMessageHistory, InMemoryChatMessageHistory
from langchain_core.messages import HumanMessage, AIMessage
from langchain_core.runnables import RunnableWithMessageHistory
from langchain_openai import ChatOpenAI
# 初始化模型
model = ChatOpenAI(model="deepseek-v4-flash")
# 演示无上下文记忆的调用
# model.invoke("你好,我是GGBong").pretty_print()
# model.invoke("你知道我是谁吗?").pretty_print()
# 手动传递上下文消息示例
# messages = [
# HumanMessage(content="你好,我是GGBong"),
# AIMessage(content="你好,GGBong!很高兴认识你~😊 有什么想聊的吗?"),
# HumanMessage(content="你知道我是谁吗?"),
# ]
# model.invoke(messages).pretty_print()
# 实现自动记忆功能
store = {}
def get_session_history(session_id: str) -> BaseChatMessageHistory:
"""获取或创建指定会话ID的历史记录"""
if session_id not in store:
store[session_id] = InMemoryChatMessageHistory()
return store[session_id]
# 创建带历史记录功能的模型实例
model_with_history = RunnableWithMessageHistory(model, get_session_history)
# 配置会话ID
config = {"configurable": {"session_id": "1"}}
# 带上下文的连续调用
model_with_history.invoke(input="你好,我是GGBong", config=config).pretty_print()
model_with_history.invoke(input="你知道我是谁吗?", config=config).pretty_print()
1.2、管理消息列表
大模型的上下文窗口容量由历史对话记录、当前提问和待生成回答共同占用。当内容超出窗口限制时,系统会优先遗忘最早的对话内容。为提升上下文处理能力,可通过优化消息列表管理来实现。
1.2.1、消息剪切
python
from langchain_core.messages import SystemMessage, HumanMessage, AIMessage, trim_messages
from langchain_openai import ChatOpenAI
model = ChatOpenAI(model="deepseek-v4-flash")
# 历史消息记录
messages = [
SystemMessage(content="you're a good assistant"),
HumanMessage(content="hi! I'm bob"),
AIMessage(content="hi!"),
HumanMessage(content="I like vanilla ice cream"),
AIMessage(content="nice"),
HumanMessage(content="whats 2 + 2"),
AIMessage(content="4"),
HumanMessage(content="thanks"),
AIMessage(content="no problem!"),
HumanMessage(content="having fun?"),
AIMessage(content="yes!"),
HumanMessage(content="What's my name?"),
]
# trim
# 使用 trim_messages 减少发送给模型的消息数量
# trimmer = trim_messages(
# max_tokens=65, # 修剪消息的最大令牌数,根据你想要的谈话长度来调整
# strategy="last", # 修剪策略:
# # "last"(默认):保留最后的消息。
# # "first":保留最早的消息。
# token_counter=model, # 传入一个函数或一个语言模型(因为语言模型有消息令牌计数方法)
# include_system=True, # 如果想始终保留初始系统消息,可以指定 include_system=True
# allow_partial=False, # 是否允许拆分消息的内容
# start_on="human", # 如果需要确保我们的第一条消息(不包括系统消息)始终是特定类型,可以指定 start_on
# )
trimmer = trim_messages(
max_tokens=11, # 修剪消息的最大令牌数,根据你想要的谈话长度来调整
strategy="last", # 修剪策略:
# "last"(默认):保留最后的消息。
# "first":保留最早的消息。
token_counter=len, # 传入一个函数或一个语言模型(因为语言模型有消息令牌计数方法)
include_system=True, # 如果想始终保留初始系统消息,可以指定 include_system=True
allow_partial=False, # 是否允许拆分消息的内容
start_on="human", # 如果需要确保我们的第一条消息(不包括系统消息)始终是特定类型,可以指定 start_on
)
chain = trimmer | model
# [SystemMessage(content="you're a good assistant", additional_kwargs={}, response_metadata={}),
# HumanMessage(content='I like vanilla ice cream', additional_kwargs={}, response_metadata={}),
# AIMessage(content='nice', additional_kwargs={}, response_metadata={}),
# HumanMessage(content='whats 2 + 2', additional_kwargs={}, response_metadata={}),
# AIMessage(content='4', additional_kwargs={}, response_metadata={}),
# HumanMessage(content='thanks', additional_kwargs={}, response_metadata={}),
# AIMessage(content='no problem!', additional_kwargs={}, response_metadata={}),
# HumanMessage(content='having fun?', additional_kwargs={}, response_metadata={}),
# AIMessage(content='yes!', additional_kwargs={}, response_metadata={}),
# HumanMessage(content="What's my name?", additional_kwargs={}, response_metadata={})]
# print(trimmer.invoke(messages))
print(chain.invoke(messages))
trim_messages 修剪器参数详解
-
max_tokens:设定允许保留的最大令牌(Token)/消息条数上限,当历史消息总长度超过该数值时,就会自动触发修剪,用来控制上下文窗口大小,防止超出大模型处理限制。
-
strategy:消息修剪策略,用来决定裁剪时保留哪部分对话:
last:保留最新的对话消息,丢弃最早的历史内容,适合日常多轮聊天;first:保留最早的消息,舍弃后面新增的对话内容。
-
token_counter:指定统计消息长度的计算依据:传入大模型实例时,会按照模型真实 Token 数量精准计数;传入
len时,简单按消息条目数量统计,仅适合测试调试。 -
include_system:设置是否强制保留系统消息
SystemMessage,设为True时,系统提示词永远不会被修剪掉,始终生效;设为False则系统消息也会参与裁剪。 -
allow_partial:控制是否允许拆分单条消息内容:设为
False时,每条消息只能完整保留或整体删除,不截断单条内容,保证语义完整;设为True允许截断单条消息文本,一般不推荐使用。 -
start_on:规范修剪后的对话格式,指定除去系统消息外,第一条消息的类型;配置为
human可保证修剪后对话以用户消息开头,规避对话格式非法导致的模型调用报错。
1.2.2、消息过滤
在复杂场景中,我们常借助消息列表来追踪状态。例如,有时只需将消息列表的子集而非全部历史记录传递给模型调用。此时,filter_messages 方法便能便捷地按消息类型、ID或名称进行筛选。
python
from langchain_core.messages import HumanMessage, SystemMessage, AIMessage,filter_messages
# 历史消息记录
messages = [
SystemMessage("你是⼀个聊天助⼿", id="1"),
HumanMessage("⽰例输⼊", id="2"),
AIMessage("⽰例输出", id="3"),
HumanMessage("真实输⼊", id="4"),
AIMessage("真实输出", id="5"),
]
# 过滤消息只保留human类型
print(filter_messages(messages, include_types=["human"]))
# 过滤消息只保留ai类型
print(filter_messages( include_types=["ai"]).invoke(messages))
# 过滤消息,保留human类型并且排除id="2"的消息
print(filter_messages(messages, exclude_ids=["2"], include_types=["human"]))
1.2.3、消息合并
当消息列表中出现连续的同类型消息时,部分模型可能无法有效处理这种情况。为解决这一问题,可采用 merge_message_runs 方法进行合并处理,该方法具有以下特性:
- 仅合并连续且类型相同的消息
- 不同类型消息保持分隔,不会跨段合并
- ToolMessage 类型消息始终不参与合并
python
from langchain_core.messages import HumanMessage, SystemMessage, AIMessage, merge_message_runs
from langchain_openai import ChatOpenAI
model=ChatOpenAI(model="deepseek-v4-flash")
messages = [
SystemMessage("你是⼀个聊天助⼿。"),
SystemMessage("你总是以笑话回应。"),
HumanMessage("为什么要使⽤ LangChain?"),
HumanMessage("为什么要使⽤ LangGraph?"),
AIMessage("因为当你试图让你的代码更有条理时,LangGraph 会让你感到"节点"是个好主意!"),
AIMessage("不过别担⼼,它不会"分散"你的注意⼒!"),
HumanMessage("选择LangChain还是LangGraph?"),
]
merge=merge_message_runs()
print(merge.invoke(messages))
# 1、合并消息:只合并连续、同类型的消息;不同类型隔开、不跨段合并;ToolMessage 永远不合并
# print(merge_message_runs(messages))
# 2、合并后的消息体
merger_message=merge_message_runs(messages)
# model.invoke(merger_message).pretty_print()
# 3、使用链进行调用
# chain=merge | model
# chain.invoke(messages).pretty_print()
二、提示词
2.1、提示词模板
提示词模板是 LangChain 核心抽象,广泛用于大模型应用开发,适合动态、批量、结构化 向大模型发请求的场景。
它相当于带占位符的提示词蓝图,类似字符串格式化,只需定义固定模板,运行时填充变量即可自动生成完整提示词。
同时它具备四大核心价值:可复用 ,一套模板适配多类查询;关注点分离 ,把提示词结构和业务数据拆分;输出一致 ,统一提示词格式,让大模型结果更稳定;易维护,修改提示词只需改动模板,无需多处改代码。
python
from langchain_core.messages import HumanMessage
from langchain_core.prompts import PromptTemplate, ChatPromptTemplate, MessagesPlaceholder
from langchain_openai import ChatOpenAI
model=ChatOpenAI(model="deepseek-v4-flash")
# 定义文本提示词模板, Runnable 实例
# 方式1:
# prompt=PromptTemplate(
# template="介绍{city}的历史",
# input_variables=["city"],
# )
# 调用:实例化模板
# print(prompt.invoke({"city": "北京"}))
# print(prompt.invoke({"city": "上海"}))
# # 方式2:
# prompt=PromptTemplate.from_template("将文本从{language_from}翻译为{language_to}")
# # 调用:实例化模板
# print(prompt.invoke({"language_from": "中文", "language_to": "英文"}))
# # 3、处理聊天消息的模板
# chat_prompt_template = ChatPromptTemplate(
# [
# ("system","你是一个专业的翻译助手,请帮我将将文本从{language_from}翻译为{language_to}"),
# ("user","{text}"),
# ]
# )
# 调用:实例化模板
# print(chat_prompt_template.invoke({
# "language_from": "中文",
# "language_to": "英文",
# "text": "你好,我叫GGBong"
# }))
# 获取消息模板实例
# messages=chat_prompt_template.invoke(
# {
# "language_from": "中文",
# "language_to": "英文",
# "text": "你好,我叫GGBong"
# }
# )
# chain =chat_prompt_template|model
# chain.invoke(
# {
# "language_from": "中文",
# "language_to": "英文",
# "text": "你好,我叫GGBong"
# }
# ).pretty_print()
# 4、添加用户消息
chat_prompt_template = ChatPromptTemplate(
[
("system","你是一个专业的翻译助手,请帮我将将文本从{language_from}翻译为{language_to}"),
("user","{text}"),
MessagesPlaceholder("msg") # 添加消息占位符
]
)
message_template =[
HumanMessage("你叫什么名字")
]
chain=chat_prompt_template|model
chain.invoke({
"language_from":"中文",
"language_to":"英文",
"text":"你好,我叫GGBong",
"msg":message_template
}).pretty_print()
LangChain Hub 是专门用于上传、浏览、拉取、统一管理提示词的共享平台,类比 GitHub 共享代码的定位,专为提示词工程打造。它为开发者提供了发现、分享、复用提示词的渠道,方便提示工程师协作开发、复用现有提示并按需微调,大幅加速对话代理及各类大模型应用的开发部署。平台早期支持 Prompt、Chain、Agent 资源,目前仅保留提示词(Prompt) 相关管理与共享能力。具体如何使用,这里不做介绍。
2.2、少样本提示
少样本提示是给大模型提供少量示例,引导其按规范完成任务、提升输出质量的技术。它能约束模型输出风格与格式,减少胡乱生成和低级错误;适用于固定格式输出、难以文字描述的风格仿写,以及需要多步推理的复杂任务场景。
python
from langchain_core.prompts import ChatPromptTemplate, few_shot, FewShotPromptTemplate, few_shot_with_templates, \
FewShotChatMessagePromptTemplate
from langchain_openai import ChatOpenAI
from test14 import chat_prompt_template
model=ChatOpenAI(model="deepseek-v4-flash")
# 样本参数,案例
examples=[
{"text": "你好,你叫什么名字?", "output": "hi, what is your name?"},
{"text": "你好,你多大了?", "output": "hi, what is your age?"},
]
# 与案例关联的聊天消息模板
examples_prompt_template = ChatPromptTemplate(
[
("user", "{text}"),
("ai", "{output}"),
]
)
# 将案例转换为 消息列表,插入到提示词模板中去
# 少样本提示词模板
shot_prompt_template=FewShotChatMessagePromptTemplate(
examples=examples,
example_prompt=examples_prompt_template
)
# print(shot_prompt_template.invoke({}).to_messages())
# 聊天提示词
chat_prompt_message_template=ChatPromptTemplate(
[
("system","你是一个翻译专家,请帮我将文本从{language_from}翻译为{language_to}"),
shot_prompt_template,
("user","{text}")
]
)
print(chat_prompt_message_template.invoke({
"language_from": "中文",
"language_to":"英文",
"text":"好久不见啊,胡小姐"
}))
langchain_core.prompts.few_shot.FewShotChatMessagePromptTemplate 类,它实现了标准的 Runnable 接口。
类初始化参数:
examples: 样本示例example_prompt: 用于格式化单个示例的 ChatPromptTemplate
类方法说明:
- 通过
to_string()方法可将提示值转为字符串格式 - 通过
to_messages()方法可将提示转为消息列表表
python
from langchain_core.prompts import FewShotChatMessagePromptTemplate, FewShotPromptTemplate, PromptTemplate
from langchain_openai import ChatOpenAI
model = ChatOpenAI(model="deepseek-v4-flash")
# 文本提示词模板
example_prompt = PromptTemplate.from_template("Question: {question}\n{answer}")
# 创建示例集
examples = [
{
"question": "李白和杜甫,谁更长寿?",
"answer": """
是否需要后续问题:是的。
后续问题:李白享年多少岁?
中间答案:李白享年61岁。
后续问题:杜甫享年多少岁?
中间答案:杜甫享年58岁。
所以最终答案是:李白
"""
},
{
"question": "腾讯的创始人什么时候出生?",
"answer": """
是否需要后续问题:是的。
后续问题:腾讯的创始人是谁?
中间答案:腾讯由马化腾创立。
后续问题:马化腾什么时候出生?
中间答案:马化腾出生于1971年10月29日。
所以最终答案是:1971年10月29日
""",
},
{
"question": "孙中山的外祖父是谁?",
"answer": """
是否需要后续问题:是的。
后续问题:孙中山的母亲是谁?
中间答案:孙中山的母亲是杨太夫人。
后续问题:杨太夫人的父亲是谁?
中间答案:杨太夫人的父亲是杨胜辉。
所以最终答案是:杨胜辉
""",
},
{
"question": "电影《红高粱》和《霸王别姬》的导演来自同一个国家吗?",
"answer": """
是否需要后续问题:是的。
后续问题:《红高粱》的导演是谁?
中间答案:《红高粱》的导演是张艺谋。
后续问题:张艺来自哪里?
中间答案:中国。
后续问题:《霸王别姬》的导演是谁?
中间答案:《霸王别姬》的导演是陈凯歌。
后续问题:陈凯歌来自哪里?
中间答案:中国。
所以最终答案是:是
""",
},
]
# 消息通过提示词模板构建出来
# FewShotPromptTemplate 针对文本、消息
few_shot_prompt = FewShotPromptTemplate(
examples=examples,
example_prompt=example_prompt, # PromptTemplate,用于格式化单个示例
suffix="Question: {input}", # suffix表示放在示例之后的模板字符串
input_variables=["input"], # 输入变量列表
)
print(few_shot_prompt.invoke({}).to_messages())
# # print(few_shot_prompt.invoke({"input": "《教父》和《星球大战》的导演是否来自一个国家?"}).to_string())
# # print(few_shot_prompt.invoke({"input": "《教父》和《星球大战》的导演是否来自一个国家?"}).to_messages())
#
#
# chain = few_shot_prompt | model
# chain.invoke({"input": "《教父》和《星球大战》的导演是否来自一个国家?"}).pretty_print()
这里的[tool_call]就是提前定义好的结构化标准答案 ,借助tool_example_to_messages函数,会把文本、文字回复、结构化数据,自动转换成用户消息、AI消息、工具消息 的标准对话格式;之所以要把结构化数据包装成工具消息形式,是因为LangChain里结构化输出底层依赖工具调用机制,用工具消息承载结构化样例,就是给大模型示范固定输出格式,让模型照着示例的工具调用结构生成结果,避免乱输出、格式跑偏。
2.3、示例选择器
大模型上下文窗口有限,少样本提示不必塞入全部示例;示例数量要过多反而干扰模型,LangChain 提供示例选择器,可按特定策略从示例集中筛选适配输入的子集构建提示,常用策略包括按长度容量、语义相似度、最大边际相关性及 Ngram 重叠度筛选,本质均属于NLP文本相似性衡量范畴。
2.3.1、按长度选择示例
python
from langchain_chroma import Chroma
from langchain_core.example_selectors import LengthBasedExampleSelector, SemanticSimilarityExampleSelector, \
MaxMarginalRelevanceExampleSelector
from langchain_core.prompts import PromptTemplate, FewShotPromptTemplate
from langchain_community.example_selectors import NGramOverlapExampleSelector
from langchain_openai import OpenAIEmbeddings
# 反义词示例集合
examples = [
{"input": "happy", "output": "sad"},
{"input": "tall", "output": "short"},
{"input": "energetic", "output": "lethargic"},
{"input": "sunny", "output": "gloomy"},
{"input": "windy", "output": "calm"},
]
# 示例模板(文本)
example_prompt = PromptTemplate.from_template("Input: {input}\nOutput: {output}")
# 示例选择器(长度)依据示例模板进行选择,选择完成后返回示例
example_selector = LengthBasedExampleSelector(
examples=examples,
example_prompt=example_prompt,
max_length=25, # 格式化示例的最大长度
# 用于获取字符串长度的函数,用于确定包含哪些示例。
# 如果没有指定,它是作为默认值提供的。
# 该函数返回一个整数,表示字符串中由换行符或空格分隔的"单词"数量
# get_text_length: Callable[[str], int] = lambda x: len(re.split("\n| ", x))
)
# 少样本模板
# 转换 Message
few_shot_prompt = FewShotPromptTemplate(
example_selector=example_selector, # 示例选择器
example_prompt=example_prompt,
prefix="给出每个输入的反义词:",
suffix="Input: {adjective}\nOutput: ",
input_variables=["adjective"],
)
print(
few_shot_prompt.invoke({"adjective": "焦虑"}).to_messages()[0].content
)
2.3.2、按语义相似性选择示例
语义相似是衡量文本在含义层面 的接近程度,LangChain通过SemanticSimilarityExampleSelector类实现按语义相似性选择示例,核心是计算输入与示例嵌入向量的余弦相似度筛选最相关示例;该选择器提供from_examples(基于示例集、嵌入模型、向量库和选取数量生成实例)、add_example(添加新示例)、select_examples(根据输入匹配相似示例)核心方法。
python
from langchain_chroma import Chroma
from langchain_core.example_selectors import SemanticSimilarityExampleSelector
from langchain_core.prompts import FewShotPromptTemplate, PromptTemplate
from langchain_openai import OpenAIEmbeddings
# 反义词示例集合
examples = [
{"input": "happy", "output": "sad"},
{"input": "tall", "output": "short"},
{"input": "energetic", "output": "lethargic"},
{"input": "sunny", "output": "gloomy"},
{"input": "windy", "output": "calm"},
]
# 单条示例的字符串模板
example_prompt = PromptTemplate(
input_variables=["input", "output"],
template="Input: {input}\nOutput: {output}",
)
# 语义相似示例选择器
example_selector = SemanticSimilarityExampleSelector.from_examples(
examples, # 示例库
OpenAIEmbeddings(), # 嵌入模型,计算语义向量
Chroma, # 向量数据库
k=1 # 只选择最相似的1个示例
)
# 少样本提示模板(自动按语义选择示例)
similar_prompt = FewShotPromptTemplate(
example_selector=example_selector,
example_prompt=example_prompt,
prefix="给出每个输入的反义词",
suffix="Input: {adjective}\nOutput:",
input_variables=["adjective"],
)
# 调用并打印最终提示词
print(
similar_prompt.invoke({"adjective": "worried"}).to_messages()[0].content
)
2.3.3、 按最大边际相关性选择示例(MMR)
最大边际相关性(MMR)是依托语义相似度实现结果重排的算法,筛选内容时既保证候选内容和查询需求高度相关,又兼顾内容之间的多样性、减少信息冗余;语义相似性仅单纯判定内容与目标的贴合程度,只看重匹配度不区分内容差异,二者应用场景不同,语义相似多用于基础语义检索、内容聚类等场景,而MMR常用于推荐系统破除信息茧房、文档摘要选取多元语句、RAG检索后文档去重提纯,以此优化输出效果。
LangChain 提供 MaxMarginalRelevanceExampleSelector 最大边际相关性示例选择器,用法与语义相似选择器一致,支持 from_examples、add_example、select_examples 核心方法;使用流程为准备示例集→定义占位符模板→用示例、嵌入模型、向量库初始化MMR选择器→构建少样本提示模板→传入输入自动筛选出既相关又多样化的示例,最终生成提示消息。
python
from langchain_chroma import Chroma
from langchain_core.example_selectors import MaxMarginalRelevanceExampleSelector
from langchain_core.prompts import FewShotPromptTemplate, PromptTemplate
from langchain_openai import OpenAIEmbeddings
# 反义词示例集合
examples = [
{"input": "happy", "output": "sad"},
{"input": "tall", "output": "short"},
{"input": "energetic", "output": "lethargic"},
{"input": "sunny", "output": "gloomy"},
{"input": "windy", "output": "calm"},
]
# 字符串模板
example_prompt = PromptTemplate(
input_variables=["input", "output"],
template="Input: {input}\nOutput: {output}",
)
# MMR 示例选择器
example_selector = MaxMarginalRelevanceExampleSelector.from_examples(
examples,
OpenAIEmbeddings(),
Chroma,
k=2,
)
# 用于实例化示例的模板
similar_prompt = FewShotPromptTemplate(
# 提供了一个ExampleSelector而不是examples
example_selector=example_selector,
example_prompt=example_prompt,
prefix="给出每个输入的反义词",
suffix="Input: {adjective}\nOutput:",
input_variables=["adjective"],
)
print(
similar_prompt.invoke({"adjective": "worried"}).to_messages()[0].content
)
2.3.4、通过 ngram 重叠选择示例(Ngram)
n-gram是文本里连续n个字词/字符,n-gram重叠依靠共有连续文本片段判定相似度,仅做字面匹配,无法识别同义词;语义n-gram重叠则对比字词语义向量相似度实现语义层面重叠判定,可识别换词不改意的内容,多用于精准剽窃检测等场景。LangChain通过NGramOverlapExampleSelector实现该筛选方式,可配置提示模板、示例集与重叠阈值,阈值不同筛选规则不同,还支持新增示例、按重叠分数降序筛选示例,使用时只需准备文本示例、定义占位符模板、初始化选择器并设定阈值,再搭配FewShotPromptTemplate构建提示模板,即可实现基于语义n-gram重叠规则筛选并输出排序后的提示内容。
python
from langchain_community.example_selectors import NGramOverlapExampleSelector
from langchain_core.prompts import FewShotPromptTemplate, PromptTemplate
# 翻译示例
examples = [
{"input": "See Spot run.", "output": "看见Spot跑。"},
{"input": "My dog barks.", "output": "我的狗叫。"},
{"input": "Spot can run.", "output": "Spot可以跑。"},
]
# 字符串模板
example_prompt = PromptTemplate(
input_variables=["input", "output"],
template="Input: {input}\nOutput: {output}",
)
# NGram 示例选择器
example_selector = NGramOverlapExampleSelector(
examples=examples,
example_prompt=example_prompt,
threshold=-1.0,
)
# 用于实例化示例的模板
dynamic_prompt = FewShotPromptTemplate(
example_selector=example_selector,
example_prompt=example_prompt,
prefix="给出每个输入的中文翻译",
suffix="Input: {sentence}\nOutput:",
input_variables=["sentence"],
)
# 按照重叠分数排序
print(
dynamic_prompt.invoke({"sentence": "Spot can run fast."}).to_messages()[0].content
)
2.4、输出解析器
输出解析器是LangChain中用于将LLM非结构化文本输出转换为JSON、字典、Pydantic模型等结构化格式的核心组件,能让模型输出适配下游机器可读任务,是构建可靠LLM应用的关键;它与聊天模型的with_structured_output()功能一致,但维度和用法不同,输出解析器是独立功能组件,支持Prompt、LLM、Parser链式组合使用,with_structured_output()是模型方法,仅能手动调用返回具备结构化输出能力的可运行对象。
2.4.1、解析结构化对象输出
PydanticOutputParser是LangChain中用于输出结构化对象的核心输出解析器,属于langchain_core.output_parsers.pydantic模块,需传入pydantic模型作为参数,核心包含invoke()方法(将单输入转换为结构化输出)和关键的get_format_instructions()方法,该方法可生成格式指令字符串,添加到提示词末尾,明确告知大模型以指定格式返回响应,确保输出符合定义的结构化规范。
python
from typing import Optional
from langchain_core.output_parsers import PydanticOutputParser, JsonOutputParser
from langchain_core.prompts import PromptTemplate
from langchain_openai import ChatOpenAI
from pydantic import BaseModel, Field
# 定义聊天模型
model = ChatOpenAI(model="gpt-4o-mini")
# Pydantic 对象
class Joke(BaseModel):
"""给用户讲的一个笑话"""
setup: str = Field(description="这个笑话的开头")
punchline: str = Field(description="这个笑话的妙语")
rating: Optional[int] = Field(default=None, description="从1-10分,给这个笑话评分")
# 定义解析器
parser = PydanticOutputParser(pydantic_object=Joke)
# print(parser.get_format_instructions())
# 提示模板
prompt = PromptTemplate(
template="回复用户问题。\n返回结构说明:{format_instructions}\n用户问题:{query}\n",
partial_variables={"format_instructions": parser.get_format_instructions()}, # 将返回的结构作为提示词发送给大模型
input_variables=["query"],
)
# print(prompt.invoke({"query": "讲一个关于跳舞的笑话"}))
# 定义链
chain = prompt | model | parser
result = chain.invoke({"query": "讲一个关于跳舞的笑话"})
print(result)
2.4.2、解析 JSON 输出
JsonOutputParser是LangChain中用于输出JSON格式结构化数据的输出解析器,隶属于langchain_core.output_parsers.json模块,可传入Pydantic对象(JSON schema不好写)对输出结果进行验证(无则不验证),用于将模型输入转换为规范JSON输出,以及关键的get_format_instructions()方法,该方法能生成格式指令字符串并添加到提示词末尾,明确告知大模型按照指定JSON格式返回响应,确保模型输出符合结构化要求。
python
from typing import Optional
from langchain_core.output_parsers import PydanticOutputParser, JsonOutputParser
from langchain_core.prompts import PromptTemplate
from langchain_openai import ChatOpenAI
from pydantic import BaseModel, Field
# 定义聊天模型
model = ChatOpenAI(model="gpt-4o-mini")
# Pydantic 对象
class Joke(BaseModel):
"""给用户讲的一个笑话"""
setup: str = Field(description="这个笑话的开头")
punchline: str = Field(description="这个笑话的妙语")
rating: Optional[int] = Field(default=None, description="从1-10分,给这个笑话评分")
# 定义解析器
# parser = PydanticOutputParser(pydantic_object=Joke)
# JSON Schema 不好写
parser = JsonOutputParser(pydantic_object=Joke)
# print(parser.get_format_instructions())
# 提示模板
prompt = PromptTemplate(
template="回复用户问题。\n返回结构说明:{format_instructions}\n用户问题:{query}\n",
partial_variables={"format_instructions": parser.get_format_instructions()}, # 将返回的结构作为提示词发送给大模型
input_variables=["query"],
)
# print(prompt.invoke({"query": "讲一个关于跳舞的笑话"}))
# 定义链
chain = prompt | model | parser
result = chain.invoke({"query": "讲一个关于跳舞的笑话"})
print(result)
三、文档加载器
RAG(检索增强生成)是当前大语言模型应用的核心模式,我们可通过AI搜索引出其核心逻辑------大模型擅长语义理解和文本总结但缺乏实时信息获取能力,搜索引擎擅长获取实时信息但需人为总结,二者结合可实现AI实时查阅信息,而RAG与AI搜索的本质区别的是将知识库从搜索引擎的公开网络数据,替换为本地或企业内部私有数据,其核心流程是用户向LLM提问时,系统先在私有/本地知识库中进行语义搜索,找到最相关内容后,将内容与问题一同交给LLM生成答案,解决了搜索引擎无法获取非公开数据的局限。
3.1、Document 文档类
要实现RAG,首先需通过LangChain的文档加载器从各类数据源加载数据,将其转换为一系列Document对象;langchain_core.documents.base.Document是用于存储文本内容及相关元数据的核心类,也支持直接定义LangChain文档列表,为后续的文本分割、向量存储与检索等RAG流程提供标准化的数据载体。
python
import os
from langchain_community.document_loaders import PyPDFLoader, UnstructuredMarkdownLoader
from langchain_core.documents import Document
# 手动定义的文档列表
documents = [
# 对于单个Document文档,它一般表示较大的文档的某个块或者某一页
Document(
# 内容
page_content="狗是忠实的伴侣",
# 元数据字典
# 元数据属性可以包含:文档源,与其他文档的关系以及其他属性信息
metadata={"source": "pets-doc"},
)
]
# # 文档加载器(PDF)
loader = PyPDFLoader(file_path="C:/Users/l3202/PyCharmMiscProject/LangChain/Docs/pdf/基于微服务的即时通讯系统 .pdf")
# # 加载:生成文档列表
docs = loader.load()
#
# PDF加载器默认将文档按分页进行拆分
print(f"PDF文档总页数:\n{len(docs)}\n")
print(f"第一页文本的内容(前200)是:\n{docs[0].page_content[:200]}\n")
print(f"第一页的元数据字典是:\n{docs[0].metadata}\n")
print(f"第二页文本的内容(前200)是:\n{docs[1].page_content[:200]}\n")
print(f"第二页的元数据字典是:\n{docs[1].metadata}\n")
3.2、文本分割器
完成数据源加载并转为Document文档对象后,下一步进行文档拆分,也就是把长文本切分成短小易处理的文本块,既能适配大模型有限的上下文窗口,也能提升检索精准度,细化搜索匹配粒度,LangChain内置的各类文本分割器即可高效实现大文档切块处理。
3.2.1、按字符进行文本分割
python
from langchain_community.document_loaders import UnstructuredMarkdownLoader
from langchain_text_splitters import CharacterTextSplitter
# 创建文档加载器对象,指定要读取的 Markdown 文件路径
# UnstructuredMarkdownLoader 专门用于加载 .md 文件,保留文本结构
loader = UnstructuredMarkdownLoader(file_path="./Docs/Markdown/C++开发方向.md")
# 执行加载操作:真正读取文件内容
# 返回值 data 是一个 List[Document] 列表,每个 Document 包含文本内容+元数据
data = loader.load()
# 创建文本分割器,用于把长文档切成小的文本块
text_splitter = CharacterTextSplitter(
separator="\n\n", # 分割符:按两个换行符分割(段落分割)
is_separator_regex=False, # 分割符不是正则表达式,就是普通字符串
chunk_size=500, # 每个文本块最大长度为500个字符
chunk_overlap=50, # 块与块之间重叠50个字符,保证语义完整
length_function=len, # 使用len()函数计算文本长度
)
# 调用分割方法,将长文档列表 data 切成小文本块
# 返回值 documents 是分割后的小文档块列表
documents = text_splitter.split_documents(data)
3.2.2、按token进行文本分割
大模型无法直接接收字符串,需先完成Token切分编码,可使用tiktoken分词器实现,其中cl100k_base编码适配主流模型,能将文本拆分为对应词汇单元并生成数字编码;在LangChain里可通过CharacterTextSplitter的from_tiktoken_encoder()方法,快速创建基于tiktoken分词规则的文本分割器完成文档拆分。
python
from langchain_community.document_loaders import UnstructuredMarkdownLoader
from langchain_text_splitters import CharacterTextSplitter
# 初始化加载器,指定读取的markdown文件路径
loader = UnstructuredMarkdownLoader(file_path="./Docs/Markdown/C++开发方向.md")
# 读取文件内容,转为标准Document文档对象列表
data = loader.load()
# 基于tiktoken编码器创建分割器
text_splitter = CharacterTextSplitter.from_tiktoken_encoder(
chunk_size=500,
chunk_overlap=50,
encoding_name="cl100k_base"
)
# 执行文档拆分,生成小块文档数据
documents = text_splitter.split_documents(data)
# 遍历输出前十条拆分后的文本块
for document in documents[:10]:
print("*"*30)
print(document)
3.2.3、按照设定长度强制分割
若想要严格限制所有文本块长度不超出指定数值,可使用RecursiveCharacterTextSplitter类,也能调用其from_tiktoken_encoder方法,二者均可对文本块大小设置硬性约束,精准把控分块尺寸。
python
from langchain_community.document_loaders import UnstructuredMarkdownLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
# 创建Markdown文档加载器,指定要读取的文件路径
loader = UnstructuredMarkdownLoader(file_path="./Docs/Markdown/C++开发方向.md")
# 加载文件内容,转换为LangChain标准的Document列表
data = loader.load()
# 创建基于tiktoken分词的递归文本分割器(最常用、效果最好)
text_splitter = RecursiveCharacterTextSplitter.from_tiktoken_encoder(
chunk_size=50, # 每个文本块最大长度
chunk_overlap=50, # 块之间的重叠长度
encoding_name="cl100k_base"# OpenAI模型专用编码格式
)
# 对文档进行分块切割
documents = text_splitter.split_documents(data)
# 打印前10个分割后的文本块
for document in documents[:10]:
print("*" * 30)
print(document)
递归字符分割器会依照设定顺序逐层拆分文本,先按段落划分,内容过长就再按行分割,实在超出尺寸就用空格拆分,能严格把控文本块大小
python
# 导入文档加载器与文本分割模块
from langchain_community.document_loaders import UnstructuredMarkdownLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
# 加载 Markdown 文档
loader = UnstructuredMarkdownLoader(file_path="./Docs/Markdown/C++开发方向.md")
# 读取文档内容,转为 LangChain 标准 Document 列表
data = loader.load()
# 创建递归文本分割器(按分隔符优先级逐层分割,语义保留最好)
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=50, # 每个文本块最大长度
chunk_overlap=50, # 块间重叠长度
is_separator_regex=False, # 分隔符不使用正则表达式
separators=["\n\n", "\n", " "], # 分割优先级:段落 → 换行 → 空格
length_function=len # 使用 len() 计算文本长度
)
# 执行文档分割
documents = text_splitter.split_documents(data)
# 输出前 10 个文本块
for document in documents[:10]:
print("*" * 30)
print(document)
四、嵌入模型
嵌入就是把文字、图像等人类符号转化为计算机可识别且保留语义关系的数字向量,实现自然语言到数学语言的转换;大语言模型偏向生成新文本,而嵌入模型专注生成富含语义的向量,向量有固定维度,维度越高语义捕捉能力越强,我们常借助欧氏距离、余弦相似度衡量向量语义相似度,其中余弦相似度更适配文本场景;依托嵌入技术可实现语义搜索、RAG检索增强生成、智能推荐、异常检测等多种实用场景,突破传统精准匹配局限。
LangChain 支持多种嵌入模型提供商,需分别安装对应依赖包,以 OpenAI 为例可通过 langchain_openai.embeddings.base.OpenAIEmbeddings 定义嵌入模型,该模型继承自基础 Embeddings 类,包含两个核心方法:embed_documents() 接收多个文本并返回二维列表,用于将知识库文档转为向量,embed_query() 接收单个文本并返回一维列表,用于将用户问题转为向量,部分提供商会对文档和查询采用不同优化方式,因此框架设计了两个独立方法以提升检索效果。
4.1、嵌入模型query表示
python
from langchain_openai import OpenAIEmbeddings
# 定义嵌入模型
embeddings = OpenAIEmbeddings(
model="text-embedding-3-large",
)
# 将query转换成向量表示
query_vector = embeddings.embed_query("你好")
print(f"text-embedding-3-large 向量维度:{len(query_vector)}")
print(f"向量前五个数值:{query_vector[:5]}")
4.2、嵌入模型document
python
from langchain_openai import OpenAIEmbeddings
from langchain_community.document_loaders import UnstructuredMarkdownLoader
from langchain_text_splitters import CharacterTextSplitter, RecursiveCharacterTextSplitter
# 定义嵌入模型
embeddings = OpenAIEmbeddings(
model="text-embedding-3-large",
)
# single 模式,只生成一个大文档
loader = UnstructuredMarkdownLoader("../Docs/markdown/脚手架级微服务租房平台Q&A.md",)
# Document 列表
data = loader.load()
# tiktoken 分词器
text_splitter = CharacterTextSplitter.from_tiktoken_encoder(
encoding_name="cl100k_base", # cl100k_base 是tiktoken 分词器中的一种编码方式
chunk_size=400, # 块token大小(参考标准,为了保证段落/句子完整,会超出此设定的大小)
chunk_overlap=50, # 块重叠大小
)
# 文档列表
docs = text_splitter.split_documents(data)
# 将文档列表表示为向量列表
# 参数:texts: list[str]
texts = [doc.page_content for doc in docs]
docs_vector = embeddings.embed_documents(texts)
print(f"文档数量:{len(docs)},转换的向量列表数量:{len(docs_vector)}")
print(f"第一个文档向量维度:{len(docs_vector[0])}")
print(f"第一个文档向量前五个值:{docs_vector[0][:5]}")
五、向量数据库
在LangChain开发RAG应用时,无需手动调用嵌入模型生成向量并比对,项目中的向量存储组件可统一管理向量数据,向量数据库专门用于存放高维向量,擅长实现内容相似度检索而非传统精准匹配,还能高效存储、统筹海量向量数据并完成快速检索,在前面我们介绍的功能,其实就是对向量数据库下层能力的展示。
向量数据库的核心是通过构建特殊索引结构实现高效检索,而非暴力搜索,最常用的近似最近邻(ANN)搜索会以极小的精度损失换取极致速度,借助聚类、分层、压缩等算法缩小检索范围,大幅提升检索效率,就像图书馆按分类找书而非逐架查找。
5.1、向量数据库CURD
python
from langchain_community.document_loaders import UnstructuredMarkdownLoader
from langchain_core.documents import Document
from langchain_core.vectorstores import InMemoryVectorStore
from langchain_openai import OpenAIEmbeddings
from langchain_text_splitters import CharacterTextSplitter
# 定义嵌入模型
embeddings = OpenAIEmbeddings(model="text-embedding-3-large")
# 内存向量存储
vector_store = InMemoryVectorStore(embedding=embeddings)
# 获取文档列表
# single 模式,只生成一个大文档
loader = UnstructuredMarkdownLoader("../Docs/markdown/脚手架级微服务租房平台Q&A.md",)
# Document 列表
data = loader.load()
# tiktoken 分词器
text_splitter = CharacterTextSplitter.from_tiktoken_encoder(
encoding_name="cl100k_base", # cl100k_base 是tiktoken 分词器中的一种编码方式
chunk_size=400, # 块token大小(参考标准,为了保证段落/句子完整,会超出此设定的大小)
chunk_overlap=50, # 块重叠大小
)
# 文档列表
docs = text_splitter.split_documents(data)
# 存储文档到内存向量存储中
# add_documents: 将要存储的文档列表进行编排索引。
ids = vector_store.add_documents(docs)
print(f"共有{len(docs)}个文档,编排了{len(ids)}个索引")
print(f"前三个文档的索引:{ids[:3]}")
#
# # 根据索引获取文档
# doc_2 = vector_store.get_by_ids(ids[:2])
# print(doc_2)
#
# # 删除文档
# vector_store.delete(ids=ids[:2])
#
# doc_3 = vector_store.get_by_ids(ids[:3])
# print(doc_3)