LangChain常用组件学习

LangChain系列


文章目录

前言


提示:以下是本篇文章正文内容,下面案例可供参考

一、消息管理

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官网

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_examplesadd_exampleselect_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)
相关推荐
happymaker06261 小时前
SpringBoot学习日记——DAY04(整合junit,myBatis)
spring boot·学习·junit
Restart-AHTCM1 小时前
LangChain学习之模型 I/O 与输出解析器 (Output Parsers)(3/8)
前端·学习·langchain
windawdaysss1 小时前
离线学习SQL和数据库的工具及其部署
数据库·sql·学习
倾颜1 小时前
AI 应用里的第一个 Agent:我如何做一个可控的 Tasklist Agent
langchain·agent·next.js
吃好睡好便好10 小时前
在Matlab中绘制横直方图
开发语言·学习·算法·matlab
nashane10 小时前
HarmonyOS 6学习:CapsLock键失效诊断与长截图完整实现指南
学习·华为·harmonyos
Maimai1080811 小时前
React如何用 @microsoft/fetch-event-source 落地 SSE:比原生 EventSource 更灵活的实时推送方案
前端·javascript·react.js·microsoft·前端框架·reactjs·webassembly
xian_wwq12 小时前
【学习笔记】AGC协调控制系统概述
笔记·学习
憧憬成为java架构高手的小白13 小时前
docker学习笔记(基于b站多个视频学习)【未完结】
笔记·学习