LangChain——AI应用开发框架(核心组件1)

目录

核心组件(Components)

1.消息(Messages)

[LLM 的消息结构](#LLM 的消息结构)

LangChain的消息

缓存历史消息

管理历史消息

[2.提示词模板(Prompt Template)](#2.提示词模板(Prompt Template))

文本提示词模板

聊天提示词模板

消息占位符

[使用LangChain Hub的提示词模板](#使用LangChain Hub的提示词模板)

3.少样本提示(few-shotting)

使用案例1:推理引导

使用案例2:增强LanggChain信息提取能力

[示例选择器(Example selectors)](#示例选择器(Example selectors))

按长度选择示例 (Length)

按语义相似性选择示例(Similarity)

按最大边际相关性选择示例(MMR)

[通过语义 ngram 重叠选择示例(Ngram)](#通过语义 ngram 重叠选择示例(Ngram))

[4.输出解析器(Output parsers)](#4.输出解析器(Output parsers))

解析文本输出

解析结构化对象输出


核心组件(Components)

1.消息(Messages)

消息是聊天模型中的通信单位,用于表示聊天模型的输入和输出,以及可能与对话关联的任何其他上下文或元数据。

LLM 的消息结构

每条消息都有一个角色和内容,以及因 LLM 的不同而不同的附加元数据。

**消息角色 (Role):**用来 区分对话中不同类型的消息,并帮助聊天模型了解如何响应给定的消息序列。

**消息内容 (Content):**表示多模态数据 (例如,图像、音频、视频)的消息文本或字典列表的内容。内容的具体格式可能因底层不同的 LLM 而异。目前 ,大多数模型都支持文本作为主要内容类型,对多模态数据的支持仍然有限。

下面展示一个OpenAI 的格式消息列表:

复制代码
[
    {
        "role": "user",
        "content": "Hello, how are you?",
    },
    {
        "role": "assistant",
        "content": "I'm doing well, thank you for asking.",
    },
    {
        "role": "user",
        "content": "Can you tell me a joke?",
    }
]

LangChain 接受下面的格式作为聊天模型的输入:

复制代码
chat_model.invoke([
    {
        "role": "user",
        "content": "Hello, how are you?",
    },
    {
        "role": "assistant",
        "content": "I'm doing well, thank you for asking.",
    },
    {
        "role": "user",
        "content": "Can you tell me a joke?",
    }
])
LangChain的消息

LangChain 提供了一种统一的消息格式,可以跨聊天模型使用,允许用户使用不同的聊天模型,而无需担心每个模型提供商使用的消息格式的具体细节。例如:

复制代码
openai_model = init_chat_model("gpt-4o-mini", model_provider="openai")

anthropic_model = init_chat_model("claude-3-5-sonnet-latest",model_provider="anthropic")

deepseek_model = init_chat_model("deepseek-chat", model_provider="deepseek")

google_genai_model = init_chat_model("gemini-2.5-flash",model_provider="google_genai")

model = init_chat_model(...)

这些模型提供商不同,但对于其输入和输出,统一使用 LangChain 的消息格式。LangChain 消息格式主要分为五种,分别是:

缓存历史消息

在与大型语言模型交互的过程中,我们常常体验到与智能助手进行连贯多轮对话的便利性。但目前 我们的系统还不支持此功能,代码如下:

复制代码
from langchain_openai import ChatOpenAI

#定义模型
model=ChatOpenAI(model="gpt-4o-mini")

#这种调用方式不具备记忆功能
model.invoke("我叫小明").pretty_print()
model.invoke("你知道我是谁吗").pretty_print()

可以发现,聊天模型并不认识我们,更别说支持更多轮的对话了~

稍作修改,让我们将 AI 回复给我们的响应跟着新的用户消息一起发给聊天模型试试。代码如下:

复制代码
from langchain_core.messages import HumanMessage, SystemMessage, AIMessage
from langchain_openai import ChatOpenAI

#定义模型
model=ChatOpenAI(model="gpt-4o-mini")

#具备记忆功能
#可以看出,当我们把LLM回复给我们的消息(AIMessage)
#和用户消息(HumanMessage)同时发给LLM,此时LLM就具备记忆功能了
message=[
    HumanMessage("我叫小明"),
    AIMessage("你好,小明!很高兴认识你。有什么我可以帮你的吗?"),
    HumanMessage("你知道我是谁吗")
]

model.invoke(message).pretty_print()

从结果可知,只要将历史消息,重新发送给聊天模型,那么就可以实现多轮对话的功能。

那么对于历史消息的管理就显得尤为重要。在 LangChain 老版本中,可以使用RunnableWithMessageHistory 消息历史类来包装另一个Runnable 并为其管理聊天消息历史记录。它将跟踪模型的输入和输出,并将其存储在某个数据存储中。未来的交互将加载这些消息,并将其作为输入的一部分传递给链。

代码如下:

复制代码
#让大模型具有记忆功能
from langchain_core.chat_history import BaseChatMessageHistory, InMemoryChatMessageHistory
from langchain_core.messages import HumanMessage, SystemMessage, AIMessage
from langchain_core.runnables import RunnableWithMessageHistory
from langchain_openai import ChatOpenAI

#定义模型
model=ChatOpenAI(model="gpt-4o-mini")

#将消息存储在内存中
#定义消息的存储结构(key:会话,val:历史消息)
store={}
def get_session_history(session_id:str)->BaseChatMessageHistory:
    if session_id not in store:
        #InMemoryChatMessageHistory:自动将AIMessage和HumanMessage.......添加进来
        store[session_id]=InMemoryChatMessageHistory()
    return store[session_id]

#包装model,让model具备存储历史消息功能
model_with_history_message=RunnableWithMessageHistory(model,get_session_history)

#model为Runnable实例
#invoke可以通过config来配置 Runnable实例
config={"configurable":{"session_id":"1"}}

#每次在调用的时候,都会去内存中查找会话ID为1的历史消息
model_with_history_message.invoke("我叫小明",config=config).pretty_print()
model_with_history_message.invoke("你知道我是谁吗?",config=config).pretty_print()

参数说明

  • runnable :被包装 Runnable 实例,这里就是我们定义的聊天模型。

  • get_session_history :返回类型为 BaseChatMessageHistory 的函数,传入后作为回调函数。此函数接受一个 session_id 字符串类型,并返回相应的聊天消息历史记录实例。

  • invoke() 方法:此方法与其他 Runnable 实例的 .invoke() 方法相同。只不过注意其config配置,需要配置成 config={"configurable": {"session_id": ""}} ,让RunnableWithMessageHistory 可以读取到会话id。

管理历史消息

管理历史消息,无非就是理解如何"管理","管理"无非也就是一些"CRUD"。那么在了解如何管理消息之前,需要先了解下多轮对话的核心概念:上下文窗口。上下文窗口可以理解为模型的"短期工作记忆区",即 LLM 在一次处理请求时,所能查看和处理的最大Token 数量,它包含了:

  • 用户的输入

  • 大模型的输出

  • 有时还包括系统指令(SystemMessage)和对话历史。

不同大模型支持的上下文窗口大小不同,例如:

  • OpenAI 下 GPT-5 模型上下文窗⼝为 400000 (最大 Token 数量)

  • GPT-4.1 模型上下文窗口为 1047576 (最大 Token 数量)

++消息裁剪++

下面演示一个通过 trim_messages 裁剪消息的示例(基于输入Token 数的修剪)。

复制代码
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage, SystemMessage, AIMessage,trim_messages
# 定义⼤模型
model = ChatOpenAI(model="gpt-4o-mini")

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?"),
]
print(model.invoke(messages))

# 使⽤ trim_messages 减少发送给模型的消息数量(按照token数量)
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
)
chain = trimmer | model
print(chain.invoke(messages))

除了基于 token 的修剪,还可以通过设置 token_counter=len 根据消息数修剪聊天记录。在这种情况下, max_tokens 将控制最大消息数。示例如下:

复制代码
#上下文长度
#进行消息裁剪

from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage, SystemMessage, AIMessage,trim_messages
# 定义⼤模型
model = ChatOpenAI(model="gpt-4o-mini")

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?"),
]
print(model.invoke(messages))

# 使⽤ trim_messages 减少发送给模型的消息数量(按照消息的数量)
# 使⽤ trim_messages 减少发送给模型的消息数量
trimmer = trim_messages(
    max_tokens=11,      # 最⼤消息数
    strategy="last",    # 修剪策略:"last"(默认):保留最后的消息。可获取消息列表中的最后⼀个 max_tokens
                        # "first":保留最早的消息。
    token_counter=len,  # 根据消息数裁剪
    include_system=True,# 如果想始终保留初始系统消息,可以指定include_system=True
    allow_partial=False,# 是否允许拆分消息的内容
    start_on="human",# 如果需要确保我们的第⼀条消息(不包括系统消息)始终是特定类型,可以指定 start_on
)

chain = trimmer | model
print(chain.invoke(messages))

++消息过滤++

在更复杂的场景下,我们可能会使用消息列表来跟踪状态,例如我们可能只想将这个完整消息列表的子集传递模型调用,而不是所有的历史记录。filter_messages 方法则可以轻松地按类型、ID 或名称过滤 message。

下面演示相关过滤示例,首先准备消息列表:

复制代码
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"),
]

按照类型进行筛选

复制代码
#按照类型进行筛选
print(filter_messages(messages, include_types="human"))
filter_messages(include_types="human").invoke(messages)

按类型+ID进行筛选:

复制代码
#按照消息类型+ID进行筛选
print(filter_messages(messages, include_types=[HumanMessage, AIMessage],exclude_ids=["3"]))

++消息合并++

若我们的消息列表存在连续某种类型相同的消息,但实际上某些模型不支持传递相同类型的连续消息。因此对于这种情况,我们可以使用 merge_message_runs 方法轻松合并相同类型的连续消息。

复制代码
from langchain_core.messages import SystemMessage, HumanMessage, AIMessage, merge_message_runs
from langchain_openai import ChatOpenAI

model=ChatOpenAI(model="gpt-4o-mini")

messages = [
    SystemMessage("你是⼀个聊天助⼿。"),
    SystemMessage("你总是以笑话回应。"),
    HumanMessage("为什么要使⽤ LangChain?"),
    HumanMessage("为什么要使⽤ LangGraph?"),
    AIMessage("因为当你试图让你的代码更有条理时,LangGraph 会让你感到"节点"是个好主意!"),
]

merged=merge_message_runs(messages)

#打印合并后的消息
print("\n".join([repr(x) for x in merged]))

#merged=merge_message_runs()
#chain = merged | model
#chain.invoke(messages).pretty_print()

2.提示词模板(Prompt Template)

提示词模板(Prompt Template)是 LangChain 的核心抽象之一,它被广泛应用于构建大语言模型(LLM)应用的各个环节。

简单来说,只要是需要动态、批量、或有结构地向大语言模型【发送请求】的地方,几乎都会用到提示词模板。

一个简单的例子,假设我们想根据一个城市名询问 LLM 其历史,按照之前的做法,我们可以定义HumanMessage("请介绍上海的历史") 、 HumanMessage("请介绍西安的历史") 消息等等。可以发现每次询问都会

描写重复的消息内容: 请介绍xxx的历史 。

在 LangChain 中,针对这种情况,可以定义一个模板:

  • 固定文本(模板): "请介绍{city}的历史。"

  • 输入变量: ["city"]

定义好后,可以使⽤该模板:

  • 当我们需要查询北京时,就将 city 变量赋值为 "北京"。模板引擎会⽣成: "请介绍北京的历史。"

  • 当我们需要查询上海时,就将 city 变量赋值为 "上海"。模板引擎会⽣成: "请介绍上海的历史。"

文本提示词模板

LangChain 提供了 PromptTemplate 类来轻松实现这一功能。 PromptTemplate 实现了标准的Runnable 接口。

示例如下:

复制代码
from langchain_core.prompts import PromptTemplate, ChatPromptTemplate, MessagesPlaceholder

#定义文本提示词模板
#方式一
#prompt_template = PromptTemplate(
#    input_variables=["language"],
#   template="Translate the following into {language}",
#)
#方式二
prompt_template = PromptTemplate.from_template("将文本从[language_from]翻译成[language_to]")

print(prompt_template.invoke({
    "language_from": "英文",
    "language_to": "中文"
}))
#输出:  text='将文本从[language_from]翻译成[language_to]'

class langchain_core.prompts.prompt.PromptTemplate 类,其参数如下:

  • template :提示模板

  • input_variables :需要其值作为提示输入的变量的名称列表。

内置方法:

  • from_template() :从模板定义提示模板。方法返回了一个 PromptTemplate 实例。
聊天提示词模板

ChatPromptTemplate 模板:专为 LangChain 聊天模型设计。可以方便地构建包含SystemMessage 、 HumanMessage 、 AIMessage 的消息模板。

如下代码所示:

复制代码
from langchain_core.messages import HumanMessage, AIMessage
from langchain_core.prompts import PromptTemplate, ChatPromptTemplate, MessagesPlaceholder
from langchain_openai import ChatOpenAI
model=ChatOpenAI(model="gpt-4o-mini")

#定义聊天提示词模板
chat_prompt_template = ChatPromptTemplate(
    [
        ("system","将文本从[language_from]翻译成[language_to]"),
        ("user","{text}")
    ]
)

#实例化模板
messages=chat_prompt_template.invoke(
    {
        "language_from": "英文",
        "language_to": "中文",
        "text": "how are you?"
    }
)
print(messages)
model.invoke(messages).pretty_print()

# chain = chat_prompt_template | model
# print(chain.invoke({
#     "language_from": "英文",
#     "language_to": "中文",
#     "text": "how are you?"
# }
# ))
消息占位符

在上⾯的 ChatPromptTemplate 中,我们看到了如何格式化两条消息,每条消息都是⼀个字符串。但如果我们希望将消息插入特定位置怎么办?使用MessagesPlaceholder 。MessagesPlaceholder 负责在特定位置添加消息列表。

代码如下:

复制代码
from langchain_core.messages import HumanMessage, AIMessage
from langchain_core.prompts import PromptTemplate, ChatPromptTemplate, MessagesPlaceholder
from langchain_openai import ChatOpenAI

#消息占位符
prompt_template = ChatPromptTemplate([
    ("system","你是一个聊天助手"),
    MessagesPlaceholder("msgs"),   #消息占位
])

messages=[
    HumanMessage(content="中国⾸都是哪⾥?"),
    AIMessage(content="中国⾸都是北京。"),
    HumanMessage(content="那法国呢?")
]

formatted_ptompt_template = prompt_template.invoke({"msgs":messages})
print(formatted_ptompt_template)
使用LangChain Hub的提示词模板

LangChain Hub 是一个用于上传、浏览、拉取和管理提示词(prompts)的地方。随着 LLM 的发展,提示词变得越来越重要。LangChain 正在打造一个与像 GitHub 这样的传统平台,GitHub长期以来一直是共享和协作代码的首选平台。于是推出了 LangChain Hub 平台。

LangChain Hub 官网地址:https://smith.langchain.com/hub/

目前收藏最高的提示词模板是: hardkothari/prompt-maker ,我们就以它为示例,演示一下如何使用LangChain Hub 上的提示词。

要想使用该能力,需要先申请并配置 LangSmith 环境变量: LANGSMITH_API_KEY="你的LangSmith API Key" 。接着,需要从 hub 拉取相应的提示,并使用。

代码如下:

复制代码
from langchain_openai import ChatOpenAI
from langsmith import Client

client = Client()
#拉取提示词(这个提示词模板,会对我们输入的提示词进行优化)
prompt = client.pull_prompt("hardkothari/prompt-maker")

model=ChatOpenAI(model="gpt-4o-mini")

#定义链
chain = prompt | model

while True:
    task=input("你的任务是什么?(输入quit退出)\n")
    if task=="quit":
        break
    lazy_prompt=input("你当前的提示词是什么?(输入quit退出)\n")
    if lazy_prompt=="quit":
        break

    chain.invoke({"task":task,"lazy_prompt":lazy_prompt}).pretty_print()

3.少样本提示(few-shotting)

少样本提示是一种通过向 LLM 提供少量具体示例或样本,来教会它如何执行某项特定任务的技术。提高模型性能的最有效方法之一是给出一个【模型示例】指导大模型你想做什么、怎么做。下面用一个例子解释少样本提示的作用。

对于 LangChain 就需要创建⼀个FewShotChatMessagePromptTemplate 对象来实例化示例集。FewShotChatMessagePromptTemplate 是一个提示词模板,专门用来将示例集实例化为聊天消息,用法如下所示:

复制代码
#少样本提示
from langchain_core.prompts import ChatPromptTemplate, FewShotChatMessagePromptTemplate
from langchain_openai import ChatOpenAI

model=ChatOpenAI(model="gpt-4o-mini")

#定义案例
examples = [
    {"text":"2 😍 3 等于多少?","output":"2 😍 3 等于6"},
    {"text":"3 😍 5 等于多少?","output":"3 😍 5 等于15"}
]

#要想将案例加入到聊天提示词模板中,需要先将案例转化成消息列表,然后再插入到消息列表中
#要想转化为消息列表,通过提示词模板+案例(参数)
#定义与案例有关的提示词模板
examples_prompt_template = ChatPromptTemplate([
    ("user","{text}"),      #用户输入
    ("ai","{output}")       #ai回复
])

#定义少量提示词模板
few_prompt_template=FewShotChatMessagePromptTemplate(
    examples=examples,
    example_prompt=examples_prompt_template
)

#定义聊天提示词模板
chat_prompt_template = ChatPromptTemplate([
    ("system","计算{number1} 😍 {number2} 等于多少?"),
    few_prompt_template,
    ("user","{number1}😍{number2}等于多少?")
])

#定义链
chain = chat_prompt_template | model

print(chain.invoke({
    "number1":10,"number2":3,
}))
使用案例1:推理引导
复制代码
#少量提示词模板1: 推理引导
from langchain_core.prompts import PromptTemplate, FewShotPromptTemplate
from langchain_openai import ChatOpenAI

#定义模型
model = ChatOpenAI(model="gpt-4o-mini")

#定义样本提示词模板
examples_prompt_template = 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_template = FewShotPromptTemplate(
    examples=examples,
    example_prompt=examples_prompt_template,
    suffix="Question : {input}",    #将输入的变量放入到示例之后
    input_variables=["input"],      #输入变量列表
)

# print(few_shot_prompt_template.invoke({"input":"<教父>和<星球大战>的导演是同一个国家的吗?"}).to_string())

# #定义链
chain = few_shot_prompt_template | model

chain.invoke({"input": "<教父>和<星球大战>的导演是同一个国家的吗?"}).pretty_print()
使用案例2:增强LanggChain信息提取能力
复制代码
#样本提示词模板2:增强信息推理能力
from typing import List, Optional

from langchain_core.messages import SystemMessage
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.utils.function_calling import tool_example_to_messages
from langchain_openai import ChatOpenAI
from pydantic import BaseModel, Field

#1.定义模型
model= ChatOpenAI(model="gpt-4o-mini")
#2.定义结构化输出
class Person(BaseModel):
    """⼀个⼈的信息。"""
    name: Optional[str] = Field(default=None, description="这个人的名字")
    hair_color: Optional[str] = Field(default=None, description="如果知道这个人头发的颜⾊")
    skin_color: Optional[str] = Field(default=None, description="如果知道这个人的肤色")
    height_in_meters: Optional[str] = Field(default=None, description="以米为单位的高度")

class Data(BaseModel):
    """提取关于⼈的数据。"""
    people: List[Person]

#3.定义示例
examples = [
    (
        "海洋是⼴阔而蓝色的。它有两万多英尺深。",
        Data(people=[]),       # 没有⼈物信息的情况
    ),
    (
        "小强从中国远行到美国。",
        Data(people=[Person(name="⼩强", height_in_meters=None, skin_color=None,hair_color=None),])
    ),
]

#4.将示例转化为消息列表(AIMessage/HumanMessage/...)
messages=[]
for txt,tool_call in examples:
    if tool_call.people:
        ai_response="检测到人"
    else:
        ai_response="未检测到人"
    messages.extend(tool_example_to_messages(
        txt,    #输入的文本
        [tool_call],    #工具(使用pydantic对象作为工具)
        ai_response=ai_response,  #让LLM强制生成ai_response
    ))
# print(messages)
#输出
# [
# HumanMessage(content='海洋是⼴阔⽽蓝⾊的。它有两万多英尺深。', additional_kwargs={}, response_metadata={}),
# AIMessage(content='', additional_kwargs={'tool_calls': [{'id': '9402077d-db91-4d81-ba0d-6f374d2617ac', 'type': 'function', 'function': {'name': 'Data', 'arguments': '{"people":[]}'}}]}, response_metadata={}, tool_calls=[{'name': 'Data', 'args': {'people': []}, 'id': '9402077d-db91-4d81-ba0d-6f374d2617ac', 'type': 'tool_call'}]),
# ToolMessage(content='You have correctly called this tool.', tool_call_id='9402077d-db91-4d81-ba0d-6f374d2617ac'),
# AIMessage(content='未检测到人', additional_kwargs={}, response_metadata={}),
# HumanMessage(content='⼩强从中国远⾏到美国。', additional_kwargs={}, response_metadata={}),
# AIMessage(content='', additional_kwargs={'tool_calls': [{'id': '4d65ef45-0d2b-4ec6-b4ae-2c947ffa7ba3', 'type': 'function', 'function': {'name': 'Data', 'arguments': '{"people":[{"name":"⼩强","hair_color":null,"skin_color":null,"height_in_meters":null}]}'}}]}, response_metadata={}, tool_calls=[{'name': 'Data', 'args': {'people': [{'name': '⼩强', 'hair_color': None, 'skin_color': None, 'height_in_meters': None}]}, 'id': '4d65ef45-0d2b-4ec6-b4ae-2c947ffa7ba3', 'type': 'tool_call'}]),
# ToolMessage(content='You have correctly called this tool.', tool_call_id='4d65ef45-0d2b-4ec6-b4ae-2c947ffa7ba3'),
# AIMessage(content='检测到人', additional_kwargs={}, response_metadata={})
# ]


#5.定义提示词模板
prompt_template = ChatPromptTemplate([
    SystemMessage(content="你是⼀个提取信息的专家,只从⽂本中提取相关信息。""如果您不知道要提取的属性的值,属性值返回null"),
    MessagesPlaceholder("example_messages"),      #消息占位符(传入示例)
    ("user", "{new_message}"),                    #用户输入的信息
])

#将返回结构绑定到模型
model_with_struct=model.with_structured_output(Data)
#定义链
chain = prompt_template | model_with_struct
print(chain.invoke(
    {
        "example_messages": messages,
        "new_message": "篮球场上,⾝⾼两⽶的中锋王伟默契地将球传给⼀⽶七的后卫挚友李明,完成⼀记绝杀。这对⽼友⽤⼗年配合弥补了⾝⾼的差距。"
    }
))

输出结果:

复制代码
people=[
     Person(name='王伟', hair_color=None, skin_color=None, height_in_meters='2'),
     Person(name='李明', hair_color=None, skin_color=None, height_in_meters='1.7')
]
示例选择器(Example selectors)

一旦我们有了示例数据集,就需要考虑提示中应该有多少个示例。关键的权衡是,更多的示例通常会提高性能,但更大的提示会增加成本和延迟。超过某个阈值,太多示例可能会开始混淆模型。

找到正确数量的示例在很大程度上取决于模型、任务、示例的质量以及成本和延迟限制。有趣的是,模型越好,它需要精准的示例就越少。但其实,最佳的方法是使用不同数量的示例进行一些实验。

若此时我们有【大量】的示例数据集。对于大模型来说,就没必要全部使用与参考。我们需要有一种方法可以根据给定的输入,从数据集中选择示例。

在 LangChain 中,示例选择器就可以帮我们从一组【示例的集合】中根据具体策略选择正确的【示例子集】构建少样本提示。

选择策略有:

  • Length :根据特定【长度】内可以容纳的数量选择示例。

  • Similarity :使用输入和示例之间的【语义相似性】来决定选择哪些示例。

  • MMR :使用输入和示例之间的【最大边际相关性】来决定要选择哪些示例。

  • Ngram :使用输入和示例之间的【ngram 重叠】来决定要选择哪些示例。

这些其实都是自然语言处理(NLP)里的相似性衡量问题。

按长度选择示例 (Length)

当我们担心构造提示时,将超过上下文窗口长度,根据特定长度内可以容纳的数量选择示例。对于较长的输⼊,它将选择更少的示例来包含;而对于较短的输入,它将选择更多示例。

实现按长度选择示例的示例选择器是:

Class langchain_core.example_selectors.length_based.LengthBasedExampleSelector 类,其参数如下:

  • example_prompt : PromptTemplate,用于格式化示例的提示模板。

  • Examples : 模板所需的示例列表。

  • max_length : 提示的最大程度,超过该长度将剪切示例。

  • get_text_length : 测量提示长度的方法。默认为字数统计。

    #示例选择器------按长度选择示例
    from sys import prefix

    from attr.validators import max_len
    from langchain_core.example_selectors import LengthBasedExampleSelector
    from langchain_core.prompts import PromptTemplate, FewShotPromptTemplate

    反义词示例集合

    examples = [
    {"input": "happy", "output": "sad"},
    {"input": "tall", "output": "short"},
    {"input": "energetic", "output": "lethargic"},
    {"input": "sunny", "output": "gloomy"},
    {"input": "windy", "output": "calm"},
    ]

    #定义示例模板(将示例格式化为文本)
    example_template = PromptTemplate.from_template("Input:{input}\nOutput:{output}")

    #定义示例选择器(按照长度)
    example_selector = LengthBasedExampleSelector(
    examples=examples, #传入示例
    example_prompt=example_template, #传入示例模板,将示例格式化为文本
    max_length=25, #格式化文本的最大长度
    # ⽤于获取字符串⻓度的函数,⽤于确定包含哪些⽰例。
    # 如果没有指定,它是作为默认值提供的。
    # 该函数返回⼀个整数,表⽰字符串中由换⾏符或空格分隔的"单词"数量
    # get_text_length: Callable[[str], int] = lambda x: len(re.split("\n| ",x))
    )

    #定义少样本提示模板
    few_prompt_template = FewShotPromptTemplate(
    example_selector = example_selector, #示例选择器
    example_prompt=example_template, #示例模板
    prefix="给出每个输入的反义词", #将prefix添加到示例输入之前
    suffix = "Input:{adjective}\noutput:",#将suffix添加到输入之后
    input_variables=["adjective"],
    )

    print(few_prompt_template.invoke({"adjective": "good"}).to_messages()[0].content)

按语义相似性选择示例(Similarity)

什么是语义相似?它是衡量文本在【含义上】的接近程度。例如下述两段文本:

text1 = "我喜欢猫"

text2 = "我讨厌狗"

这两段文本表面相似度低,但语义上都是表达对动物的态度。

再例如:

text1 = "苹果很甜"

text2 = "苹果市值创新高"

"苹果"可以指水果或公司,语义相似可以解决一词多义问题,因此这两段文本语义上不相似。

LangChain 能根据输入和示例之间的语义相似性来决定选择哪些示例,它通过查找与输入具有最大余弦相似性的嵌入式示例来实现这一点。

实现按语义相似性选择示例的示例选择器是:

class langchain_core.example_selectors.semantic_similarity.SemanticSimilarityExampleSelector 类,

内置方法:

  • from_examples() :根据示例集生成语义相似示例选择器

输入:

  • examples :示例列表

  • embeddings :初始化的嵌入 API 接口,如 OpenAIEmbeddings()

  • vectorstore_cls :向量存储数据库接口类。

  • k :最终要选择的示例的数量。默认值为 4。

输出:语义相似性示例选择器

复制代码
#示例选择器------按语义相似性
from sys import prefix

from attr.validators import max_len
from langchain_chroma import Chroma
from langchain_core.example_selectors import  SemanticSimilarityExampleSelector
from langchain_core.prompts import PromptTemplate, FewShotPromptTemplate
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_template = PromptTemplate.from_template("Input:{input}\nOutput:{output}")

#定义示例选择器(按照语义相似性)
example_selector = SemanticSimilarityExampleSelector.from_examples(
    examples,                                               #示例
    OpenAIEmbeddings(model="text-embedding-3-large"),       #嵌入式类(用于生成向量)
    Chroma,                                                 #存储向量:向量数据库
    k=2,                                                    #生成示例的数量
)

#定义少样本提示模板
few_prompt_template = FewShotPromptTemplate(
    example_selector = example_selector,    #示例选择器
    example_prompt=example_template,      #示例模板
    suffix = "Input:{adjective}\noutput:",#将suffix添加到输入之后
    input_variables=["adjective"],
)

print(few_prompt_template.invoke({"adjective": "good"}).to_messages()[0].content)
按最大边际相关性选择示例(MMR)

什么是最大边际相关性?它是一种重新排序算法,它使用语义相似性作为基础工具,从一个候选集中挑选出一组既能代表查询主题又彼此多样化的结果。

MMR(Maximum Marginal Relevance,最大边际相关性) 是 LangChain 中用于「优化相似性检索结果」的核心算法 ------ 它解决了纯语义相似度检索的痛点(比如返回结果高度重复),能在「相关性」和「多样性」之间做平衡。

听起来好像和语义相似性类似,用一个例子看下两者的区别:

【语义相似性】就像面试官衡量每个应聘者与职位要求的匹配度。他会给每个应聘者打一个分数。

【最大边际相关性】就像团队经理(MMR算法)要组建一个团队。目标是选出一组"精华"结果,

而不是一个单一结果:

每个成员都要满足基本职位要求(满足相关性)。

但经理不希望团队里全是只会一种技能的程序员。他需要前端、后端、算法、测试等不同专长的人,以确保团队能力全面、减少冗余(新颖性/多样性)。

经理的策略是:先招一个最匹配的技术大牛(第⼀步),然后接下来招的人,既要技术达标,又要和已招的人技能互补(迭代过程)。

了解下使用最大边际相关性的场景,更能让我们理解其概念:

语义相似性使用场景:搜索引擎的基础排序、重复检测、聚类、语义搜索。

MMR 使用场景:

  • 推荐系统:推荐与用户兴趣相关但又不同类型的物品,避免"信息茧房"。

  • 文档摘要:从长文档中选择能代表主旨又包含不同信息的句子,避免摘要内容重复。

  • RAG (检索增强生成):在从知识库检索完一堆相关文档后,使用 MMR 进行去重和多样化筛选,再交给LLM生成答案,能有效提升答案质量和减少幻觉。

实现:

LangChain 提供了按最大边际相关性选择示例的能力,该示例选择器是:

Class langchain_core.example_selectors.semantic_similarity.MaxMarginalRelevanceExampleSelector 类,

内置方法:

from_examples() :根据示例集生成 MMR 示例选择器

输入:

  • examples :示例列表

  • embeddings :初始化的嵌入 API 接口,如 OpenAIEmbeddings()

  • vectorstore_cls :向量存储数据库接口类。

  • k :最终要选择的示例的数量。默认值为 4。

输出:MMR 示例选择器

复制代码
#示例选择器------MMR
from sys import prefix

from attr.validators import max_len
from langchain_chroma import Chroma
from langchain_core.example_selectors import  MaxMarginalRelevanceExampleSelector
from langchain_core.prompts import PromptTemplate, FewShotPromptTemplate
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_template = PromptTemplate.from_template("Input:{input}\nOutput:{output}")

#定义示例选择器(MMR)
example_selector = MaxMarginalRelevanceExampleSelector.from_examples(
    examples,                                               #示例
    OpenAIEmbeddings(model="text-embedding-3-large"),       #嵌入式类(用于生成向量)
    Chroma,                                                 #存储向量:向量数据库
    k=2,                                                    #生成示例的数量
)

#定义少样本提示模板
few_prompt_template = FewShotPromptTemplate(
    example_selector = example_selector,    #示例选择器
    example_prompt=example_template,      #示例模板
    suffix = "Input:{adjective}\noutput:",#将suffix添加到输入之后
    input_variables=["adjective"],
)

print(few_prompt_template.invoke({"adjective": "good"}).to_messages()[0].content)
通过语义 ngram 重叠选择示例(Ngram)

什么是**【** ngram ?ngram 指一个文本序列中连续的 n 个词(word) 或字符(character)。

什么是**【** ngram 重叠】?通过计算它们之间共同拥有的 ngram 数量来一种衡量两段文本相似度的方法。

例如下述两段文本:

text1 = "苹果手机很好用" (分词后: 苹果 手机 很 好用 )

text2 = "这款手机很好用" (分词后: 这款 手机 很 好用 )

这两段文本单词重复度很高,连续三个词的相同的情况也存在,因此 ngram 重叠高。

再看个例子:

text1 = 苹果手机很好用" (分词后: 苹果 手机 很 好用 )

text2 = "iPhone 非常不错" (分词后: iPhone 非常 不错 )

这两段文本在含义上非常相似,但它们的 ngram 重叠度为 0。

因此,传统 ngram 重叠是一种表面形式的匹配。它只关心词是否完全一样,但对于同义词却无法处理。

什么是【语义 ngram 重叠】?不再比较词本身,而是比较词背后的语义向量(Embedding)。也就是说,它不是看两个词 [苹果] 和 [iPhone] 的字面是否相同,而是计算它们在语义空间中的向量是否相似。如果相似度超过某个阈值,就认为它们"重叠"了。还是看这个例子:

text1 = "苹果手机很好用" (分词后: 苹果 手机 很 好用 )

text2 = "iPhone 非常不错" (分词后: iPhone 非常 不错 )

计算 苹果 和 iPhone 的向量相似度 → 得分 0.95 (很高,视为重叠)

计算 手机 和 iPhone 的向量相似度 → 得分 0.88 (很高,但可能不会同时计分,取决于算法设计,避免重复计算)

计算 很 和 非常 的向量相似度 → 得分 0.90 (很高,视为重叠)

计算 好用 和 不错 的向量相似度 → 得分 0.82 (很高,视为重叠)

最终,语义上的 nigram 重叠度可能为 3 或 4(非常相似!)。

那么语义 ngram 重叠的使用场景是什么?语义 ngram 重叠常用于需要更精准语义评估的场景,例如剽窃检测 , 能够发现那些改换了词汇但保留了核心思想的"智能"剽窃。

LangChain 实现按语义 ngram 重叠选择示例的示例选择器是:

Class langchain_community.example_selectors.ngram_overlap.NGramOverlapExampleSelector 类,

其参数如下:

  • example_prompt :PromptTemplate,用于格式化示例的提示模板。

  • examples :模板所需的示例列表。

  • threshold :算法亭子的阈值。默认设置为 -1.0。

对于负阈值:按 [重叠分数] 对示例进行排序,但不排除任何示例。

对于等于 0.0 的阈值:按 [重叠分数] 进行排序,并排除与输入没有 ngram 重叠的示例。

对于大于 1.0 的阈值:排除所有示例,并返回一个空列表。

复制代码
#示例选择器------ngram重叠度检测
from sys import prefix

from attr.validators import max_len
from langchain_chroma import Chroma
from langchain_community.example_selectors import NGramOverlapExampleSelector
from langchain_core.prompts import PromptTemplate, FewShotPromptTemplate
from langchain_openai import OpenAIEmbeddings

# 翻译⽰例
examples = [
    {"input": "See Spot run.", "output": "看⻅Spot跑。"},
    {"input": "My dog barks.", "output": "我的狗叫。"},
    {"input": "Spot can run.", "output": "Spot可以跑。"},
]

#定义示例模板(将示例格式化为文本)
example_template = PromptTemplate.from_template("Input:{input}\nOutput:{output}")

#定义示例选择器(ngram)
example_selector = NGramOverlapExampleSelector(
    examples=examples,                  #示例
    example_prompt=example_template,    #示例模板
    threshold=0,                        #阈值
    #阈值如果为负数:表示将不相干的示例(相似度为0的示例)也筛选出来
    #阈值=0.0:表示输出的示例是与输入有关的
    #阈值>=1.0:表示排除所有示例,返回空列表
)

#定义少样本提示模板
few_prompt_template = FewShotPromptTemplate(
    example_selector = example_selector,    #示例选择器
    example_prompt=example_template,      #示例模板
    suffix = "Input:{sentence}\noutput:",#将suffix添加到输入之后
    input_variables=["sentence"],
)

#示例输出的顺序是按照相似度进行排序的
print(few_prompt_template.invoke({"sentence": "See Spot run fast."}).to_messages()[0].content)

4.输出解析器(Output parsers)

负责获取模型的输出,并将输出转换为更结构化的格式。当使用 LLM 生成结构化数据或规范化聊天模型和 LLM 的输出时,这很有用。

大型语言模型(LLM)的输出本质上是非结构化的文本。但在构建应用程序时,我们通常希望得到结构化的、机器可读的数据,这样可以将其转换为更适合下游任务的格式,比如:

  • JSON 对象

  • Python 字典或列表

  • 一个特定的 Pydantic 模型实例

  • 一个简单的布尔值或字符串枚举

输出解析器的作用就是架起这座桥梁:它们将 LLM 的非结构化文本输出转换为结构化格式。这使得与LLM 的交互从"模糊的文本对话"变成了"精确的数据 API 调用",是构建可靠、高效 LLM 应用不可或缺的组件。

解析文本输出

其实对于使用 StrOutputParser 输出解析器输出文本,它也实现了标准的 Runnable 接口。示例如下:

复制代码
from langchain_openai import ChatOpenAI
from langchain_core.output_parsers import StrOutputParser
model = ChatOpenAI(model="gpt-4o-mini")
chain = model | StrOutputParser()
for chunk in chain.stream("写⼀⾸夏天的诗词,50字以内。"):
    print(chunk, end="|")

输出如下:

复制代码
|炎|夏|骄|阳|照|,|绿|树|映|蓝|天|。|
|蝉|鸣|声|声|烈|,|荷|塘|映|清|鲜|。|
|微|风|拂|面|过|,|凉|意|透|心|间|。|
|烦|忧|随|汗|去|,|畅|享|此|夏|欢|。||

若是不使用输出解析器,而是直接得到聊天模型返回的 AIMessage,文本内容则需要从消息中的content 字段获取。

解析结构化对象输出

要输出结构化对象,需要用到的输出解析器是 PydanticOutputParser 。

class langchain_core.output_parsers.pydantic.PydanticOutputParser 类,其参数如下:

  • pydantic_object :要解析的 pydantic 模型。

内置方法:

  • invoke() :将单个输入转换为输出。

  • get_format_instructions() → str :重要!!

  • 作用:生成一个指令字符串,这个字符串会被添加到发送给 LLM 的提示(Prompt)的末尾。

  • 目的:告诉 LLM 应该以什么样的格式返回它的响应。例如,"请将你的回复封装在 XML 标签中"或"请以 JSON 格式输出,包含 'name' 和 'age' 两个字段"。

代码示例如下:

复制代码
#自定义输出解析器
from typing import Optional, List

from langchain_core.output_parsers import PydanticOutputParser
from langchain_core.prompts import PromptTemplate
from langchain_openai import ChatOpenAI
from pydantic import BaseModel, Field

#定义模型
model=ChatOpenAI(model="gpt-4o-mini")

#定义结构化对象
class Joke(BaseModel):
    setup: str=Field(description="这个笑话的主题")                                   #setup:场景构建/前置条件
    punchline: str=Field(description="这个笑话的妙处")                               #punchline:点睛之笔
    rating: Optional[int]=Field(default=None,description="从1-10分,给这个笑话的评分") #rating:效果评分


#自定义输出解析器
parser = PydanticOutputParser(pydantic_object=Joke)
# print(parser.get_format_instructions())

#定义提示词模板
prompt = PromptTemplate(
    template="回复用户的问题。\n返回结构说明:{format_instructions}。\n用户输入的问题:{query}",
    partial_variables={"format_instructions":parser.get_format_instructions()},
    input_variables=["query"],
)

#定义链
chain = prompt | model | parser

print(chain.invoke("请结合篮球和鸡,给我讲一个笑话"))

除了上面讲的文本、对象、JSON解析器,其实 LangChain 官方还提供了更多类型的解析器,如:

  • XML 解析器: XMLOutputParser

  • Yaml 解析器: YamlOutputParser

  • CSV 解析器: CommaSeparatedListOutputParser

  • 枚举解析器: EnumOutputParser

  • 日期解析器: DatetimeOutputParser 等等

除此之外,LangChain 还支持我们自定义输出解析器,以将模型输出结构化为自定义格式。

官方手册:https://reference.langchain.com/python/langchain_core/output_parsers/

相关推荐
庄小焱2 小时前
【机器学习】——房屋销售价格预测实战
人工智能·算法·机器学习·预测模型
txzrxz2 小时前
单调栈详解(含题目)
数据结构·c++·算法·前缀和·单调栈
pas1362 小时前
35-mini-vue 实现组件更新功能
前端·javascript·vue.js
人工智能AI技术2 小时前
【Agent从入门到实践】29 开发第一个Agent——需求定义
人工智能·python
爱喝可乐的老王2 小时前
深度学习初认识
人工智能·深度学习
前端达人2 小时前
为什么聪明的工程师都在用TypeScript写AI辅助代码?
前端·javascript·人工智能·typescript·ecmascript
快乐点吧2 小时前
使用 data-属性和 CSS 属性选择器实现状态样式控制
前端·css
AI科技星2 小时前
张祥前统一场论的数学表述与概念梳理:从几何公设到统一场方程
人工智能·线性代数·算法·机器学习·矩阵·数据挖掘
丝斯20112 小时前
AI学习笔记整理(55)——大模型训练流程
人工智能·笔记·学习