LangChain 提示工程:少样本提示与输出解析器

📑 目录

  • 三、少样本提示(few-shotting)
    • [3.1 概念](#3.1 概念)
    • [3.2 少样本聊天提示模板](#3.2 少样本聊天提示模板)
    • [3.3 使用案例](#3.3 使用案例)
      • [3.3.1 案例一:基于少样本提示实现分步推理引导](#3.3.1 案例一:基于少样本提示实现分步推理引导)
      • [3.3.2 案例二:基于工具调用式少样本增强结构化信息提取](#3.3.2 案例二:基于工具调用式少样本增强结构化信息提取)
    • [3.4 示例选择器(Example selectors)](#3.4 示例选择器(Example selectors))
      • [3.4.1 概念](#3.4.1 概念)
      • [3.4.2 基于长度的示例选择器](#3.4.2 基于长度的示例选择器)
      • [3.4.3 基于语义相似性的示例选择器](#3.4.3 基于语义相似性的示例选择器)
      • [3.4.4 基于最大边际相关性 MMR 的示例选择器](#3.4.4 基于最大边际相关性 MMR 的示例选择器)
      • [3.4.5 基于 Ngram 重叠的示例选择器](#3.4.5 基于 Ngram 重叠的示例选择器)
  • [四、输出解析器(Ouput parsers)](#四、输出解析器(Ouput parsers))
    • [4.1 概念](#4.1 概念)
    • [4.2 解析文本输出](#4.2 解析文本输出)
    • [4.3 解析结构化对象输出](#4.3 解析结构化对象输出)
    • [4.4 解析 Json 输出](#4.4 解析 Json 输出)
    • 4.5 其他内置输出解析器概览

三、少样本提示(few-shotting)

3.1 概念

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

  • 参考之前学习的内容,我们可以将其想象为"零样本提示 ",直接给模型一道考题,不给任何例子。模型会凭借已有的知识直接回答。例如我们输入"What is 2 🐦9?",答案可能是:
python 复制代码
from langchain_openai import ChatOpenAI

model = ChatOpenAI(model="gpt-4o-mini")
model.invoke("What is 2 🐦9?").pretty_print()

运行输出:

python 复制代码
================================== Ai Message ==================================

表达式"2 🦜 9"不是⼀个标准的数学运算或⽅程。它似乎是数字2和鹦鹉🦜的组合,后⾯跟着数字9。

它没有特定的数学意义。
  • 少样本提示则是在给出考题前,先给它几道类似的、附有正确答案的例题。添加示例输入 和预期输出 的技术给到模型提示,让模型通过例题来理解任务应该怎么做。例如以下样例,模型通过样例后可以发现"🐦"与"+"含义相似,便可以按照此规则得出 "what is 2 🐦9? "的结果是11。
python 复制代码
examples = [
	{"input":"2 🐦 2","output":"4"},
	{"input":"2 🐦 3","output":"5"}
]

这能解决什么问题?LLM 虽然知识渊博,但有时我们需要它以非常特定的格式、风格或逻辑来回答问题。提供正确的示例可以减少模型 "胡说八道" 或犯低级错误的概率,将其输出约束在你提供的范例范围内。

  • 强制要求模型以特定的格式(如JSON、XML、特定的列表样式)输出结果。样例可以当作格式样板。
  • 有些任务很难用文字指令清晰描述(例如:"请用莎士比亚的风格写作")。提供几个例子比写长篇大论的指令更有效。
  • 对于需要多步推理的复杂任务,示例可以展示出思考链,引导模型遵循类似的推理路径。

3.2 少样本聊天提示模板

  FewShotChatMessagePromptTemplate 是 LangChain 中用于构建聊天格式少样本提示 的模板类。它可以批量把一组样本对(输入 - 期望输出)转换成一组HumanMessage与AIMessage交替的聊天消息,专门适配聊天大模型,并且实现了标准 Runnable 接口,可以嵌入到主提示模板、LCEL 链路中使用。

构造方式

  直接实例化构造,需要传入样本数据集,以及用于格式化单条样本的聊天模板

python 复制代码
from langchain_core.prompts import ChatPromptTemplate, FewShotChatMessagePromptTemplate

# 1. 准备少样本数据集
examples = [
    {"input": "2 + 2", "output": "4"},
    {"input": "2 + 3", "output": "5"},
]

# 2. 定义单个样本的格式化模板
example_prompt = ChatPromptTemplate([
    ("human", "{input}"),
    ("ai", "{output}"),
])

# 3. 构造少样本提示模板
few_shot_prompt = FewShotChatMessagePromptTemplate(
    example_prompt=example_prompt,  # 格式化单条示例的模板
    examples=examples               # 样本示例集合
)

内置参数

  • examples:样本列表,列表内每一项是字典,存放单条示例的输入输出字段。
  • example_prompt:ChatPromptTemplate 对象,定义如何把单条示例字典渲染成一对人机聊天消息。

内置方法

  • invoke():Runnable 标准调用方法,传入变量字典,渲染全部示例,返回ChatPromptValue对象。
  • to_messages():将渲染得到的ChatPromptValue转为模型可接收的消息实例列表list[BaseMessage]。

基础调用示例

python 复制代码
# 渲染少样本模板,生成消息列表
print(few_shot_prompt.invoke({}).to_messages())

运行输出:

python 复制代码
[
 HumanMessage(content='2 + 2', additional_kwargs={}, response_metadata={}),
 AIMessage(content='4', additional_kwargs={}, response_metadata={}),
 HumanMessage(content='2 + 3', additional_kwargs={}, response_metadata={}),
 AIMessage(content='5', additional_kwargs={}, response_metadata={})
]

嵌入主模板,构建完整 LCEL 调用链

  可以将FewShotChatMessagePromptTemplate直接嵌入到ChatPromptTemplate,和系统提示、用户当前提问组合,送入模型进行推理。

python 复制代码
# 组装完整提示模板
final_prompt = ChatPromptTemplate([
    ("system", "你是一个神奇的数学天才。"),
    few_shot_prompt,
    ("human", "{input}")
])

# 构建LCEL链路
chain = final_prompt | model
chain.invoke({"input": "what is 2 + 9?"}).pretty_print()

运行输出:

python 复制代码
================================ Ai Message =================================
11

3.3 使用案例

3.3.1 案例一:基于少样本提示实现分步推理引导

案例目标

  给定问题,让大模型先展示分步推理过程,再输出最终结论 ,而不是直接给出答案。

  测试输入问题:《教父》和《星球大战》的导演来自同一个国家吗?

本案例使用字符串版本少样本模板 FewShotPromptTemplate,构造带推理步骤的示例,引导模型模仿示例的思考格式输出。

构造思路

  1. 准备多组示例:每组包含【问题 + 完整分步推理过程 + 最终答案】
  2. 使用 PromptTemplate 定义单个样本的渲染模板
  3. 实例化 FewShotPromptTemplate,配置前缀、后缀,组装全部示例
  4. 调用模板渲染提示词,送入大模型得到推理结果

内置参数(FewShotPromptTemplate)

  • examples:样本示例列表,列表中每一项为字典,存放单条示例的 question 与 answer。
  • example_prompt:PromptTemplate 对象,用于格式化单个样本。
  • prefix:全部示例之前的提示文本。
  • suffix:全部示例之后的提示文本,一般放置待预测的新问题。
  • input_variables:模板占位变量名称列表。

内置方法

  • invoke():Runnable 标准调用方法,传入变量字典,渲染完整提示,返回PromptValue。
  • .to_string():将 PromptValue 转为纯字符串。
  • .to_messages():将 PromptValue 转为消息列表,可直接送入聊天模型。

完整代码示例:

python 复制代码
from langchain_core.prompts import PromptTemplate, FewShotPromptTemplate

# 1. 定义单条样本的格式化模板
example_prompt = PromptTemplate.from_template("Question: {question}\n{answer}")

# 2. 准备少样本数据集,每组包含问题 + 完整分步推理
examples = [
    {
        "question": "李白和杜甫,谁更长寿?",
        "answer": """
		是否需要后续问题:是的。
		后续问题:李白享年多少岁?
		中间答案:李白享年61岁。
		后续问题:杜甫享年多少岁?
		中间答案:杜甫享年58岁。
		所以最终答案是:李白
		"""
    },
    {
        "question": "腾讯的创始人什么时候出生?",
        "answer": """
		是否需要后续问题:是的。
		后续问题:腾讯的创始人是谁?
		中间答案:腾讯由马化腾创立。
		后续问题:马化腾什么时候出生?
		中间答案:马化腾出生于1971年10月29日。
		所以最终答案是:1971年10月29日
		"""
    },
    {
        "question": "孙中山的外祖父是谁?",
        "answer": """
		是否需要后续问题:是的。
		后续问题:孙中山的母亲是谁?
		中间答案:孙中山的母亲是杨太夫人。
		后续问题:杨太夫人的父亲是谁?
		中间答案:杨太夫人的父亲是杨胜辉。
		所以最终答案是:杨胜辉
		"""
    },
    {
        "question": "电影《红高粱》和《霸王别姬》的导演来自同一个国家吗?",
        "answer": """
		是否需要后续问题:是的。
		后续问题:《红高粱》的导演是谁?
		中间答案:《红高粱》的导演是张艺谋。
		后续问题:张艺谋来自哪里?
		中间答案:中国。
		后续问题:《霸王别姬》的导演是谁?
		中间答案:《霸王别姬》的导演是陈凯歌。
		后续问题:陈凯歌来自哪里?
		中间答案:中国。
		所以最终答案是:是
		"""
    }
]

# 测试:渲染单条示例
print(example_prompt.invoke(examples[0]).to_string())

运行输出:

python 复制代码
Question: 李白和杜甫,谁更长寿?

是否需要后续问题:是的。
后续问题:李白享年多少岁?
中间答案:李白享年61岁。
后续问题:杜甫享年多少岁?
中间答案:杜甫享年58岁。
所以最终答案是:李白
python 复制代码
# 3. 构建完整FewShotPromptTemplate
prompt = FewShotPromptTemplate(
    examples=examples,
    example_prompt=example_prompt,
    suffix="Question: {input}",
    input_variables=["input"],
)

# 4. 渲染模板,送入模型进行推理
prompt_messages = prompt.invoke({"input":"《教父》和《星球大战》的导演来自同一个国家吗?"}).to_messages()
model.invoke(prompt_messages).pretty_print()

模型输出结果:

python 复制代码
================================ Ai Message =================================
是否需要后续问题:是的。
后续问题:《教父》的导演是谁?
中间答案:《教父》的导演是弗朗西斯·福特·科波拉。
后续问题:弗朗西斯·福特·科波拉来自哪里?
中间答案:弗朗西斯·福特·科波拉来自美国。
后续问题:《星球大战》的导演是谁?
中间答案:《星球大战》的导演是乔治·卢卡斯。
后续问题:乔治·卢卡斯来自哪里?
中间答案:乔治·卢卡斯也来自美国。
所以最终答案是:是

📌 案例小结:

  通过少样本提示,给模型提供带推理步骤的示范样例,模型会模仿样本的格式,对新问题一步步拆解思考,而不是直接给出简短答案。适合需要展示推理过程的问答场景。

3.3.2 案例二:基于工具调用式少样本增强结构化信息提取

案例目标

  结合工具调用风格的少样本提示 与结构化输出能力,从自然语言文本中提取人物信息。输入一段描述文本,模型输出符合 Pydantic Schema 的结构化人物对象列表,未知字段返回None。

  测试输入文本:篮球场上,身高两米的中锋王伟默契地将球传给一米七的后卫挚友李明,完成一记绝杀。这对老友用十年配合弥补了身高的差距。

预期提取结果:

python 复制代码
people=[
    Person(name='王伟', height_in_meters="2",skin_color=None,hair_color=None),
    Person(name='李明',height_in_meters="1.7",skin_color=None,hair_color=None),
]

构造思路

  1. 定义 Pydantic 模型,声明需要提取的结构化数据 Schema;
  2. 准备两组少样本示例:无人名场景、仅人名缺少其余属性场景;
  3. 构建ChatPromptTemplate,使用MessagesPlaceholder预留少样本消息插槽;
  4. 使用tool_example_to_messages工具函数,把文本 + 预期结构化结果转换成工具调用格式的多消息组(HumanMessage + AIMessage(tool_call) + ToolMessage + AIMessage);
  5. 将组装好的示例消息填入模板,搭配with_structured_output绑定模型输出结构,构建 LCEL 链路完成信息提取。

核心工具 / 类参数说明

1. Pydantic BaseModel

  用于定义输出数据结构,约束字段名称、类型与描述,供with_structured_output使用。

2. tool_example_to_messages(LangChain 内置工具函数)

  专门用于工具调用类型的少样本示例 ,和普通FewShotChatMessagePromptTemplate不一样,一组样例会生成多条消息。

参数

  • input:str,示例里的原始用户输入文本
  • tool_calls:list[BaseModel],期望输出的 Pydantic 模型实例
  • tool_outputs:可选,工具返回内容;不传默认填充You have correctly called this tool.
  • ai_response:可选,整套示例末尾追加的 AI 文本回复,部分模型需要

返回值 :list[BaseMessage],转换完成的消息列表

3. ChatPromptTemplate + MessagesPlaceholder

  • MessagesPlaceholder("example_messages"):作为插槽,批量放入工具调用格式的少样本消息组。
  • ("user", "{new_message}"):接收待提取信息的新文本。

内置方法

  • invoke():Runnable 标准调用,传入变量字典,渲染完整提示消息
  • to_messages():将ChatPromptValue转为消息实例列表,用于预览消息结构
  • with_structured_output():模型绑定结构化输出,强制模型返回符合 Pydantic Schema 的数据对象

完整代码示例:

python 复制代码
from typing import List,Optional
from pydantic import BaseModel,Field
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.messages import HumanMessage,SystemMessage
from langchain_core.utils.function_calling import tool_example_to_messages
from langchain_openai import ChatOpenAI

# 第一步:定义结构化返回对象
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]

# 第二步:定义两组关键示例
examples = [
    (
        "海洋是广阔而蓝色的。它有两万多英尺深。",
        Data(people=[]) # 没有人物信息的情况
    ),
    (
        "小强从中国远行到美国。",
        Data(people=[
            Person(name="小强",height_in_meters=None,skin_color=None,hair_color=None),
        ])
    )
]

# 第三步:定义提示词模板
prompt_template = ChatPromptTemplate([
    SystemMessage(content="你是一个提取信息的专家,只从文本中提取相关信息。如果您不知道要提取的属性的值,属性值返回null"),
    MessagesPlaceholder("example_messages"),
    ("user","{new_message}"),
])

# 第四步:遍历示例,构造工具调用格式的消息列表
example_messages = []
for txt,expected_result in examples:
    # 根据提取结果生成AI响应文本
    if expected_result.people:
        ai_response = "检测到人"
    else:
        ai_response = "未检测到人"
    example_messages.extend(
        tool_example_to_messages(
            input=txt,
            tool_calls=[expected_result],
            ai_response=ai_response
        )
    )

# 预览:渲染模板,查看组装后的完整消息
formatted_prompt = prompt_template.invoke({
    "example_messages":example_messages,
    "new_message":"篮球场上,身高两米的中锋王伟默契地将球传给一米七的后卫挚友李明,完成一记绝杀。"
})
for message in formatted_prompt.to_messages():
    message.pretty_print()

预览输出(消息预览):

python 复制代码
================================ System Message ================================
你是一个提取信息的专家,只从文本中提取相关信息。如果您不知道要提取的属性的值,属性值返回null

================================ Human Message =================================
海洋是广阔而蓝色的。它有两万多英尺深。

================================== Ai Message ==================================
Tool Calls:
  Data(f81f0c81-108d-4c06-88dc-9e5f5289749e)
 Call ID: f81f0c81-108d-4c06-88dc-9e5f5289749e
  Args:
    people: []

================================= Tool Message =================================
You have correctly called this tool.

================================== Ai Message ==================================
未检测到人

================================ Human Message =================================
小强从中国远行到美国。

================================== Ai Message ==================================
Tool Calls:
  Data(ace46c82-07e0-455c-aaef-c13da7328e45)
 Call ID: ace46c82-07e0-455c-aaef-c13da7328e45
  Args:
    people: [{'name': '小强', 'hair_color': None, 'skin_color': None, 'height_in_meters': None}]

================================= Tool Message =================================
You have correctly called this tool.

================================== Ai Message ==================================
检测到人

================================ Human Message =================================
篮球场上,身高两米的中锋王伟默契地将球传给一米七的后卫挚友李明,完成一记绝杀。
python 复制代码
# 第五步:初始化模型,绑定结构化输出,构建LCEL链路调用
model = ChatOpenAI(model="gpt-4o-mini")
structured_model = model.with_structured_output(schema=Data)

chain = prompt_template | structured_model
result = chain.invoke({
    "example_messages": example_messages,
    "new_message": "篮球场上,身高两米的中锋王伟默契地将球传给一米七的后卫挚友李明,完成一记绝杀。"
})
print(result)

模型最终输出结果:

python 复制代码
people = [
    Person(name='王伟', hair_color=None, skin_color=None, height_in_meters='2.0'),
    Person(name='李明', hair_color=None, skin_color=None, height_in_meters='1.7')
]

📌 案例小结

  本案例使用工具调用格式的少样本 ,区别于普通文本型少样本提示。通过tool_example_to_messages把样例组装成工具调用交互消息,搭配with_structured_output,可以引导模型严格按照定义的 Schema,从原文提取人物信息,未知字段填充None,适合结构化信息抽取场景。

3.4 示例选择器(Example selectors)

3.4.1 概念

  准备好示例数据集后,我们需要确定提示词里放入多少个少样本示例。这里存在权衡:增加示例一般能提升模型效果,但更长的提示会带来更高的调用成本与推理延迟;当示例数量超过阈值,过多的样例反而会混淆模型。

  合适的示例数量,取决于模型能力、任务类型、样例质量,以及成本、延迟约束。通常能力越强的大模型,需要的示例数量越少。最稳妥的方式是,用不同数量的示例做对比实验,找到最优值。

  当示例数据集规模很大时,不需要把全部样例一次性塞进提示词。我们需要一种机制:根据当前用户输入,动态挑选匹配的示例 。

  在 LangChain 中,示例选择器(Example Selector) 就是用来解决这个问题的组件:按照指定策略,从全部示例集合里挑选出合适的示例子集,用来构建少样本提示。

选择策略有:

  • Length:根据特定【长度】内可以容纳的数量选择示例。
  • Similarity:使用输入和示例之间的【语义相似性】来决定选择哪些示例。
  • MMR:使用输入和示例之间的【最大边际相关性】来决定要选择哪些示例。
  • Ngram:使用输入和示例之间的【ngram 重叠】来决定要选择哪些示例。

3.4.2 基于长度的示例选择器

  LengthBasedExampleSelector 是 LangChain 提供的按文本总长度筛选少样本示例的选择器。它会在拼接提示词时,自动挑选示例,保证最终提示词文本长度不超过上限,避免超出模型上下文窗口。

构造参数

  • example_prompt:提示模板,用来格式化每一条示例。
  • examples:存放全部候选样例的列表。
  • max_length:提示词允许的最大总长度,超过该值就会删减示例。
  • get_text_length:自定义文本长度计算函数,默认按字符数量统计长度。

内置方法

  1. add_example(example: dict[str, str]):向候选示例池新增一条样例,入参为字典,字典的键对应提示变量,值为变量内容。
  2. select_examples(input_variables: dict[str, str]) -> list[dict]:结合当前输入,从候选示例里选出合适的样例。
    • 输入:字典,键为输入变量,值为变量内容
    • 返回:筛选后,放入提示词中的示例列表

基础调用示例:

python 复制代码
from langchain_core.example_selectors import LengthBasedExampleSelector
from langchain_core.prompts import FewShotPromptTemplate,PromptTemplate

# 反义词示例集合
examples = [
    {"input": "happy","output": "sad"},
    {"input": "tall", "output": "shot"},
    {"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 = LengthBasedExampleSelector(
    examples=examples,
    example_prompt=example_prompt,
    max_length=25,
)

# 用于实例化示例的模版
dynamic_prompt = FewShotPromptTemplate(
    # 提供一个示例选择器,动态选择示例
    example_selector=example_selector,
    example_prompt=example_prompt,
    prefix="给出每个输入的反义词",
    suffix="Input: {adjective}\nOutput:",
    input_variables=["adjective"]
)

print(dynamic_prompt.invoke({"adjective":"big"}).to_messages()[0].content)

运行输出:

python 复制代码
给出每个输入的反义词

Input: happy
Output: sad

Input: tall
Output: shot

Input: energetic
Output: lethargic

Input: sunny
Output: gloomy

Input: windy
Output: calm

Input: big
Output:

  可以看到,示例选择器将所有的示例都选择了,因为还没有超过设置的示例最大值。接下来让我们将输入变长,验证长度示例选择器筛选示例。

python 复制代码
long_str = "非常 非常 非常 非常 非常 非常 非常 非常 非常 非常 非常 非常 非常 非常 非常 非常大的事物"
print(dynamic_prompt.invoke({"adjective":long_str}).to_messages()[0].content)

运行输出:

python 复制代码
给出每个输入的反义词

Input: happy
Output: sad

Input: tall
Output: shot

Input: 非常 非常 非常 非常 非常 非常 非常 非常 非常 非常 非常 非常 非常 非常 非常 非常大的事物
Output:

3.4.3 基于语义相似性的示例选择器

  SemanticSimilarityExampleSelector 是 LangChain 中基于文本语义相似度挑选少样本示例的选择器。它会将用户输入和全部候选示例转为向量,检索出语义最接近当前提问的样例,放到提示词中,让模型拿到和当前任务更相关的参考案例。

概念
  什么是语义相似性?它是衡量两段文本含义层面的接近程度,不只看字面文字。

  • 例 1:
text 复制代码
text1 = "我喜欢猫"
text2 = "我讨厌狗"

两段文字用词差异大,但语义主题都是对动物的态度,语义相似度较高。

  • 例 2:
text 复制代码
text1 = "苹果很甜"
text2 = "苹果市值创新高"

两处 "苹果" 分别指代水果、公司,语义完全不同,因此两段文本语义不相似。

构造方式:from_examples () 类方法

用于根据示例集合快速生成语义相似示例选择器,参数如下:

  • examples:候选示例列表,存放所有可供挑选的样例。
  • embeddings:嵌入模型实例,用于把文本转为向量,例如OpenAIEmbeddings()。
  • vectorstore_cls:向量数据库接口类,用来存储所有示例的向量。
  • k:最终要挑选的示例数量,默认值为 4。

返回值:语义相似示例选择器实例

内置方法

  1. add_example(example: dict[str, str]):向候选示例池新增一条样例。

    • 输入:字典,字典的键对应提示变量,值为变量内容。
  2. select_examples(input_variables: dict[str, str]) -> list[dict]:根据当前输入,检索语义最匹配的示例。

    • 输入:字典,键为输入变量,值为变量内容
    • 返回:筛选后,放入提示词中的示例列表

基础调用示例:

python 复制代码
from langchain_core.example_selectors import SemanticSimilarityExampleSelector
from langchain_core.prompts import FewShotPromptTemplate,PromptTemplate
from langchain_chroma import Chroma
from langchain_openai import OpenAIEmbeddings

# 反义词示例集合
examples = [
    {"input": "happy","output": "sad"},
    {"input": "tall", "output": "shot"},
    {"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=examples,
    # 嵌入类用于生成用于度量语义相似度的嵌入
    embeddings=OpenAIEmbeddings(),
    # VectorStore类,用于存储嵌入并对其进行相似性搜索
    vectorstore_cls=Chroma,
    k=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)

运行输出:

python 复制代码
给出每个输⼊的反义词

Input: happy
Output: sad

Input: worried
Output:

  可以看到我们输入的内容和最后选择的示例都表示心情,在语义上最相似。

3.4.4 基于最大边际相关性 MMR 的示例选择器

  MaxMarginalRelevanceExampleSelector 是 LangChain 中基于 最大边际相关性(MMR) 挑选少样本示例的选择器。它使用语义相似性作为基础工具,将用户输入和全部候选示例转为向量,使用 MMR 算法,挑选一组既贴合查询主题、样例之间又具备多样性的结果,放到提示词中。

概念

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

MMR 和单纯语义相似性很容易混淆,用例子区分两者:

  • 语义相似性:就像面试官衡量每个应聘者与职位要求的匹配度,给每个应聘者单独打分,只看样例与查询的相似度。
  • 最大边际相关性(MMR) :就像团队经理组建团队,目标选出一组优质样例,而不是单个最优结果:
    • 每个成员都要满足基本职位要求(满足相关性)
    • 不希望团队成员技能完全重复,需要不同专长的人(保证样例新颖性、多样性,减少冗余)
    • 策略:先挑选最匹配的样例,后续挑选的样例,既要和查询相关,又要和已选中样例有差异。

📌 使用场景

  • 推荐系统:推荐和用户兴趣相关但类型不同的物品,避免 "信息茧房"
  • 文档摘要:从长文档挑选代表主旨、信息不重复的句子
  • RAG 检索增强生成:检索到相关文档后,用 MMR 做去重与多样化筛选,减少幻觉,提升答案质量

构造方式:from_examples() 类方法

用于根据示例集合快速生成 MMR 示例选择器,参数如下:

  • examples:候选示例列表,存放所有可供挑选的样例。
  • embeddings:嵌入模型实例,用于把文本转为向量,例如 OpenAIEmbeddings()。
  • vectorstore_cls:向量数据库接口类,用来存储所有示例的向量。
  • k:最终要挑选的示例数量,默认值为 4。

返回值:MMR 示例选择器实例

内置方法

  1. add_example(example: dict[str, str]):向候选示例池新增一条样例。
    • 输入:字典,字典的键对应提示变量,值为变量内容。
  2. select_examples(input_variables: dict[str, str]) -> list[dict]:根据当前输入,使用 MMR 算法筛选示例。
    • 输入:字典,键为输入变量,值为变量内容。
    • 返回:筛选后,放入提示词中的示例列表。

基础调用示例:

python 复制代码
from langchain_core.example_selectors import MaxMarginalRelevanceExampleSelector
from langchain_core.prompts import FewShotPromptTemplate,PromptTemplate
from langchain_chroma import Chroma
from langchain_openai import OpenAIEmbeddings

# 反义词示例集合
examples = [
    {"input": "happy","output": "sad"},
    {"input": "tall", "output": "shot"},
    {"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 = MaxMarginalRelevanceExampleSelector.from_examples(
    examples=examples,
    embeddings=OpenAIEmbeddings(),
    vectorstore_cls=Chroma,
    k=2,
)

# 用于实例化示例的模版
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)

运行输出:

python 复制代码
给出每个输⼊的反义词

Input: happy
Output: sad

Input: windy
Output: calm

Input: worried
Output:

3.4.5 基于 Ngram 重叠的示例选择器

  NGramOverlapExampleSelector 是 LangChain 中基于 Ngram 重叠度 挑选少样本示例的选择器。它通过计算输入文本与候选示例之间的 ngram 重叠分数,优先选取文本片段重合度高的样例放入提示词。

概念

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

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

例如下述两段文本:

text 复制代码
text1 = "苹果手机很好用" (分词后:苹果 手机 很 好用)
text2 = "这款手机很好用" (分词后:这款 手机 很 好用)

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

再看一组例子:

text 复制代码
text1 = "苹果手机很好用" (分词后:苹果 手机 很 好用)
text2 = "iPhone非常不错" (分词后:iPhone 非常 不错)

两段文本含义上非常相似,但字面词汇完全不一样,它们的 ngram 重叠度为 0。

📌 因此,传统 ngram 重叠属于表面形式匹配,只看文字是否完全一致,无法识别同义词、同义改写。

  什么是【语义 ngram 重叠】?不再比较词本身,而是比较词背后的语义向量(Embedding)。不再看 "苹果" 和 "iPhone" 字面是否相同,而是计算它们在语义空间向量相似度,相似度超过阈值就判定为 "重叠"。

使用场景:

  • 多用于需要精准语义评估的场景,例如剽窃检测,可以识别替换词汇,但保留核心思想的 "智能剽窃"。

构造参数

  • example_prompt:PromptTemplate,用于格式化示例的提示模板。
  • examples:模板所需的示例列表。
  • threshold:算法筛选停止阈值。默认设置为 -1.0。
    • 负阈值:按重叠分数对示例排序,但不会排除任何示例。
    • 阈值等于 0.0:按重叠分数排序,剔除与输入没有 ngram 重叠的示例。
    • 阈值大于 1.0:排除全部示例,返回空列表。

内置方法

  1. add_example(example: dict[str, str]):向候选示例池新增一条样例。
    • 输入:字典,字典的键对应提示变量,值为变量内容。
  2. select_examples(input_variables: dict[str, str]) -> list[dict]:根据输入文本,按 ngram 重叠分数降序筛选示例。
    • 输入:字典,键为输入变量,值为变量内容。
    • 返回:筛选后,放入提示词中的示例列表。

基础调用示例:

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)

运行输出:

python 复制代码
给出每个输入的中文翻译

Input: Spot can run.
Output: Spot可以跑

Input: See Spot run.
Output: 看见Spot跑。

Input: My dog barks.
Output: 我的狗叫。

Input: Spot can run fast.
Output:

  可以看到结果集是按照 ngram 重叠分数进行了排序。可以分别设置 threshold=0.0 和 threshold=1.0进行测试,输出结果如下:

python 复制代码
threshold=0.0(表示排除不相关的示例)
给出每个输入的中文翻译

Input: Spot can run.
Output: Spot可以跑。

Input: See Spot run.
Output:看见Spot跑。

Input: Spot can run fast.
Output: 

threshold=1.0(排除所有示例)
给出每个输入的中文翻译

Input: Spot can run fast.
Output:

四、输出解析器(Ouput parsers)

4.1 概念

  输出解析器 是 LangChain 里用于接收大模型输出、并把输出转为目标结构化格式的组件。在需要让 LLM 输出规范数据、对聊天模型返回结果做标准化处理的场景中,输出解析器是核心工具。

  大语言模型(LLM)原生输出是自由、无固定格式的非结构化文本。但开发 LLM 应用时,下游代码往往无法直接读取自由文本,我们期望模型返回机器可识别的结构化数据,常见目标格式包括:

  • JSON 对象
  • Python 字典、列表
  • 自定义 Pydantic 模型实例
  • 布尔值、字符串枚举等简单类型

  输出解析器就承担了转换桥梁的工作:将 LLM 返回的自然语言文本,解析、转换成程序可直接使用的结构化对象。借助输出解析器,我们和大模型的交互,从模糊的自然语言对话,转变为类似接口调用的、返回确定数据的模式,是搭建稳定、可自动化的 LLM 应用必不可少的组件。

与 with_structured_output 的区别

  二者都可以实现获取结构化模型输出的能力,但本质定位、使用方式存在明显差异:

  • 定位维度不同 :输出解析器是 LangChain 独立的功能性组件;with_structured_output 是聊天模型对象提供的实例方法。
  • 使用方式不同 :输出解析器可直接融入 LCEL 管道,把提示模板、大模型、解析器串联成链式执行链路;with_structured_output 调用后会返回一个新增结构化输出能力的全新 Runnable 对象,以模型包装器的形式使用。

4.2 解析文本输出

  其实对于使用 StrOutputParser 输出解析器输出文本,我们已经使用过很多次了。对于 StrOutputParser,它也实现了标准的 Runnable 接口。

python 复制代码
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="|")

运行输出:

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

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

4.3 解析结构化对象输出

  PydanticOutputParser 是用于解析模型输出、生成 Pydantic 结构化对象的输出解析器。

构造参数

  • pydantic_object:要解析对应的 Pydantic 模型。

内置方法

  1. invoke():将单个输入文本转换为 Pydantic 对象输出。
  2. get_format_instructions() -> str:
    • 作用:生成格式指令字符串,追加到送入 LLM 的 Prompt 末尾。
    • 目的:向 LLM 说明输出需要遵守的格式,例如,"请将你的回复封装在XML标签中" 或 "请以JSON格式输出,包含 'name' 和 'age' 两个字段"。

📌 小说明:get_format_instructions 是 PydanticOutputParser 最关键的方法,它会自动根据你定义的 Pydantic 模型,生成对应的格式提示,不用手写 JSON 格式要求。

基础调用示例:

python 复制代码
from langchain_openai import ChatOpenAI
from langchain_core.output_parsers import PydanticOutputParser
from langchain_core.prompts import PromptTemplate
from typing import Optional
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)

# 提示词模版
prompt = PromptTemplate(
    template="Answer the user query.\n{format_instructions}\n{query}\n",
    input_variables=["query"],
    # partial_variables:预设的固定变量字典。
    # 这类变量在模板创建阶段就提前赋值,后续调用模板时,不需要重复传入。
    # 类型:Mapping[str, Any],用于存放模板中预先固定的变量键值对。
    partial_variables={"format_instructions":parser.get_format_instructions()}
)

chain = prompt | model | parser
for chunk in chain.stream({"query": "给我讲一个关于唱歌的笑话"}):
    print(chunk,end="|")

运行输出:

python 复制代码
setup='为什么歌手总是带着铅笔去演出?' punchline='' rating=None|
setup='为什么歌手总是带着铅笔去演出?' punchline='因为' rating=None|
setup='为什么歌手总是带着铅笔去演出?' punchline='因为他们' rating=None|
setup='为什么歌手总是带着铅笔去演出?' punchline='因为他们想' rating=None|
setup='为什么歌手总是带着铅笔去演出?' punchline='因为他们想要' rating=None|
setup='为什么歌手总是带着铅笔去演出?' punchline='因为他们想要不断' rating=None|
setup='为什么歌手总是带着铅笔去演出?' punchline='因为他们想要不断调整' rating=None
setup='为什么歌手总是带着铅笔去演出?' punchline='因为他们想要不断调整音' rating=None|
setup='为什么歌手总是带着铅笔去演出?' punchline='因为他们想要不断调整音调' rating=None|
setup='为什么歌手总是带着铅笔去演出?' punchline='因为他们想要不断调整音调!' rating=None|

4.4 解析 Json 输出

  JsonOutputParser 专门用于解析大模型输出,将符合规范的 JSON 文本转为 Python 字典。

构造参数

  • pydantic_object:可选,用于校验 JSON 数据的 Pydantic 模型。为空时,仅校验 JSON 语法合法,不校验字段结构。

内置方法

  1. invoke():将模型返回的文本,解析转换为 Python 字典。
  2. get_format_instructions() -> str:
    • 作用:生成格式指令字符串,追加到送入 LLM 的 Prompt 末尾。
    • 目的:向 LLM 说明输出需要遵守的格式,例如,"请将你的回复封装在XML标签中" 或 "请以JSON格式输出,包含 'name' 和 'age' 两个字段"。

基础调用示例:

python 复制代码
from langchain_openai import ChatOpenAI
from langchain_core.output_parsers import JsonOutputParser
from langchain_core.prompts import PromptTemplate

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

# 设置解析器
parser = JsonOutputParser()

# 提示词模版
prompt = PromptTemplate(
    template="Answer the user query.\n{format_instructions}\n{query}\n",
    input_variables=["query"],
    partial_variables={"format_instructions":parser.get_format_instructions()}
)

chain = prompt | model | parser
for chunk in chain.stream({"query": "给我讲一个关于唱歌的笑话"}):
    print(chunk,end="|")

运行输出:

python 复制代码
{'joke': '为什么歌⼿总是带着梯⼦?\n因为他们想要在⾳乐会上达到更⾼的层次!'}

  也可以使用带 Pydantic 情况下的 JsonOutputParser:

python 复制代码
from langchain_openai import ChatOpenAI
from langchain_core.output_parsers import JsonOutputParser
from langchain_core.prompts import PromptTemplate
from typing import Optional
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 = JsonOutputParser(pydantic_object=Joke)

# 提示词模版
prompt = PromptTemplate(
    template="Answer the user query.\n{format_instructions}\n{query}\n",
    input_variables=["query"],
    partial_variables={"format_instructions":parser.get_format_instructions()}
)

chain = prompt | model | parser
for chunk in chain.stream({"query": "给我讲一个关于唱歌的笑话"}):
    print(chunk,end="|")

运行输出:

python 复制代码
{'setup': '为什么歌⼿从不在森林⾥唱歌?', 'punchline': '因为他们会被树⽊的'静'⽌住!', 'rating': None}

4.5 其他内置输出解析器概览

  除前文介绍的文本、结构化对象、JSON 解析器外,LangChain 官方还提供了更多专用内置输出解析器:

  • XML 解析器:XMLOutputParser
  • Yaml 解析器:YamlOutputParser
  • CSV 解析器:CommaSeparatedListOutputParser
  • 枚举解析器:EnumOutputParser
  • 日期解析器:DatetimeOutputParser

  LangChain 同时支持自定义输出解析器,我们可以根据业务需求,将模型输出解析为自定义格式。


相关推荐
朱包林1 小时前
iTerm2终端配置指南
linux·javascript·python·云原生·容器·kubernetes·云计算
棉猴1 小时前
玩游戏学Python5-函数
开发语言·python·函数·玩游戏·游戏编程
Rain的Java大神之路1 小时前
[特殊字符]8年Java老兵转型AI Agent:90%的人挂在同一个坑,根本不用学Python!(万字实战复盘,建议收藏)
java·数据库·人工智能·后端·python·spring·ai编程
高级程序源1 小时前
springboot本地生活服务平台33399-计算机课程设计、毕业设计
vue.js·spring boot·python·mysql·小程序·flask·课程设计
高级程序源1 小时前
springboot社区旧物回收管理系统25381-计算机课程设计、毕业设计
vue.js·spring boot·python·django·课程设计
高级程序源1 小时前
springboot大学生体测系统72170-计算机课程设计、毕业设计
java·vue.js·spring boot·后端·python·django·课程设计
打工仔折腾 AI1 小时前
极空间用 Docker 部署 Photopea:网页版 PS 的私有化实践
运维·后端·python·docker·容器·langchain·ai agent 实战
Eiceblue1 小时前
使用 Python 实现 Excel 行列转置(保留单元格样式)
前端·python·excel
WeiXin_DZbishe2 小时前
基于Node.js的马拉松赛事管理系统的设计与实现-计算机毕设 附源码【课程设计】90211
java·javascript·vue.js·python·php·课程设计