在构建基于大语言模型(LLM)的应用时,LangChain 是一个不可或缺的框架。它通过简化语言模型调用、提供强大的模板管理和结构化输出解析工具,成为开发者进行 LLM 应用开发的重要选择。下面是本博客的内容摘要:
LangChain 的核心模型 I/O 流程
- 输入提示(Format) :通过提示模板构建输入信息。
- 调用模型(Predict) :通过 API 调用语言模型。
- 输出解析(Parse) :将模型的非结构化输出解析为结构化数据。
提示工程的重要性
- 提示工程(Prompt Engineering)通过清晰的提示和示例设计,引导模型生成更优质的结果。
- 使用 PromptTemplate 可以动态替换变量,并管理提示的复用性。
- FewShotPromptTemplate 提供示例指导,提高模型推理能力。
提升模型推理能力的技术
-
Partial Variables 与格式化输出:
- LangChain 的输出解析器通过生成和注入
format_instructions,帮助模型生成符合需求的结构化输出。 - 提供明确的格式和逻辑指导,减少模型的输出不确定性。
- LangChain 的输出解析器通过生成和注入
-
思维链(Chain of Thought, CoT) :
- 提供逐步推理示例(Few-Shot CoT)或明确指令(Zero-Shot CoT),显著提升模型在复杂任务上的推理能力。
- CoT 使模型能够模拟人类逻辑推理,从问题理解到最终决策都更加清晰。
-
思维树(Tree of Thought, ToT) :
- ToT 扩展了 CoT 的思路,通过多步骤、多候选路径的搜索,解决需要深度推理的复杂问题。
- 结合搜索算法,ToT 为开发者提供了一种系统性探索最佳答案的框架。
模型 I/O:LangChain 的核心结构
模型 I/O 包括三个环节:
- 输入提示(Format):构建模型输入的提示信息。
- 调用模型(Predict):通过 API 调用大语言模型。
- 输出解析(Parse):将模型输出解析为结构化数据。
LangChain 提供了丰富的模板和工具,帮助我们快速构建这些流程。
1. 提示模板:高效组织输入信息
提示模板是模型调用的起点。它决定了输入提示的内容和格式。通过良好的提示工程(Prompt Engineering),可以显著提高模型生成的质量。
以下是一个简单的例子:我们希望为鲜花生成文案。
代码示例:创建一个提示模板
from langchain.prompts import PromptTemplate
template = """您是一位专业的鲜花店文案撰写员。
对于售价为 {price} 元的 {flower_name} ,您能提供一个吸引人的简短描述吗?
"""
prompt = PromptTemplate.from_template(template)
print(prompt)
这里的模板定义了两个占位符:{flower_name} 和 {price}。它们将在实际调用时被替换成具体的花名和价格。
提示模板的优势
- 变量动态替换:通过占位符灵活调整输入。
- 统一管理:在复杂项目中,模板的复用性极高。
例如,调用该模板为“玫瑰”生成文案的代码如下:
input = prompt.format(flower_name="玫瑰", price="50")
print(input)
生成的提示为:
您是一位专业的鲜花店文案撰写员。
对于售价为 50 元的 玫瑰 ,您能提供一个吸引人的简短描述吗?
2. 调用语言模型:灵活选择模型接口
LangChain 支持多种语言模型,包括 OpenAI 的 GPT 系列、Hugging Face Hub 的开源模型等。
代码示例:调用 OpenAI 的模型
from langchain import OpenAI
import os
os.environ["OPENAI_API_KEY"] = '你的OpenAI API Key'
model = OpenAI(model_name='gpt-3.5-turbo-instruct')
# 调用模型
output = model.invoke(input)
print(output)
接收上述提示后,模型可能返回类似的文案:
让你心动!50元就可以拥有这支充满浪漫气息的玫瑰花束,让TA感受你的真心爱意。
通过复用模板,可以快速生成多个文案:
flowers = ["玫瑰", "百合", "康乃馨"]
prices = ["50", "30", "20"]
for flower, price in zip(flowers, prices):
input_prompt = prompt.format(flower_name=flower, price=price)
output = model.invoke(input_prompt)
print(output)
3. 输出解析:从无结构到有结构
模型输出通常是无结构的自然语言文本,但在实际开发中,我们往往需要结构化的数据。LangChain 提供了输出解析器来实现这一功能。
场景示例
假设我们需要模型返回以下格式:
description:鲜花的文案。reason:撰写该文案的理由。
通过 LangChain 的 StructuredOutputParser,我们可以轻松实现。
代码示例:定义输出解析器
from langchain.output_parsers import StructuredOutputParser, ResponseSchema
response_schemas = [
ResponseSchema(name="description", description="鲜花的描述文案"),
ResponseSchema(name="reason", description="为何这样撰写文案")
]
output_parser = StructuredOutputParser.from_response_schemas(response_schemas)
format_instructions = output_parser.get_format_instructions()
将解析器的格式指示加入模板:
prompt_template = """您是一位专业的鲜花店文案撰写员。
对于售价为 {price} 元的 {flower_name} ,您能提供一个吸引人的简短描述吗?
{format_instructions}
"""
prompt = PromptTemplate.from_template(prompt_template, partial_variables={"format_instructions": format_instructions})
整合:生成结构化输出并保存到文件
最终代码如下:
import pandas as pd
flowers = ["玫瑰", "百合", "康乃馨"]
prices = ["50", "30", "20"]
df = pd.DataFrame(columns=["flower", "price", "description", "reason"])
for flower, price in zip(flowers, prices):
input_prompt = prompt.format(flower_name=flower, price=price)
output = model.invoke(input_prompt)
parsed_output = output_parser.parse(output)
parsed_output["flower"] = flower
parsed_output["price"] = price
df.loc[len(df)] = parsed_output
df.to_csv("flowers_with_descriptions.csv", index=False)
print(df)
结果:
| flower | price | description | reason |
|----------|-------|-----------------------------------------------|-------------------------------------|
| 玫瑰 | 50 | 让你心动的红色玫瑰,传递浓浓爱意! | 红色象征热情与浪漫,适合表白场景。 |
| 百合 | 30 | 百合,温暖情感的象征,代表纯洁无暇的爱。 | 百合象征纯洁,让人联想到温馨氛围。 |
| 康乃馨 | 20 | 康乃馨,献给母爱的花,温柔且充满感激。 | 康乃馨适合感恩场景,凸显感情深厚。 |
在刚刚的代码中,用到了提示词来帮助大模型输出人类需要的内容,但是还有下面的问题:
- 为什么在提示模板中引入 partial_variables 能够让模型生成结构化输出?
- 如何通过 FewShotPromptTemplate 提高模型的输出质量?
- 使用示例选择器优化 FewShot 提示的效率。
Partial Variables 的秘密:让提示更智能
我们在构建提示模板时,LangChain 的输出解析器会自动生成 format_instructions 并注入到提示模板中,这个过程让模型能够输出结构化结果。
让我们通过一个具体的提示模板看看这一机制:
您是一位专业的鲜花店文案撰写员。
对于售价为 50 元的 玫瑰 ,您能提供一个吸引人的简短描述吗?
The output should be a markdown code snippet formatted in the following schema, including the leading and trailing "```json" and "```":
```json
{
"description": string // 鲜花的描述文案
"reason": string // 为什么要这样写这个文案
}
上面的 {format_instructions} 是 LangChain 的输出解析器生成并自动注入到提示中的内容。通过这样的清晰指示,模型知道需要按照 JSON 的 schema 输出结构化数据。
这一机制的核心优势在于:
- 明确的输出格式:减少了模型输出模糊或冗余信息的可能性。
- 结构化解析:方便开发者将结果直接用于代码逻辑或数据存储。
提示工程的基本原则
在 LangChain 的提示工程中,有两条核心原则:
- 清晰具体的指示:让模型明确任务目标。
- 给模型时间思考:通过逐步拆解任务,让模型推理更准确。
这些原则对应了 OpenAI 的 GPT 最佳实践,包括:
- 写清晰的指示。
- 提供参考(示例)。
- 将复杂任务拆解成子任务。
- 使用外部工具辅助任务。
- 反复迭代问题。
这些看似“老生常谈”的原则,其实是指导大语言模型生成高质量输出的基石。
LangChain 提示模板的类型
LangChain 提供了多种提示模板,包括:
- PromptTemplate:基础模板,用于简单任务。
- ChatPromptTemplate:为 ChatGPT 这样的聊天模型设计。
- FewShotPromptTemplate:支持 FewShot 学习,通过示例引导模型输出。
接下来,我们重点探讨 FewShotPromptTemplate 的使用。
FewShotPromptTemplate:少样本学习的精髓
FewShot 提示通过提供示例,让模型理解任务需求并生成更高质量的输出。下面我们通过鲜花文案生成的例子,来演示如何使用 FewShotPromptTemplate。
1. 创建示例样本
示例样本是 FewShot 提示的核心。每个样本包含输入变量和目标输出。
samples = [
{"flower_type": "玫瑰", "occasion": "爱情", "ad_copy": "玫瑰,浪漫的象征,是你向心爱的人表达爱意的最佳选择。"},
{"flower_type": "康乃馨", "occasion": "母亲节", "ad_copy": "康乃馨代表着母爱的纯洁与伟大,是母亲节的完美礼物。"},
{"flower_type": "百合", "occasion": "庆祝", "ad_copy": "百合象征纯洁与高雅,是你庆祝特殊时刻的理想选择。"}
]
2. 创建样本模板
将每个样本格式化为结构化的提示模板。
from langchain.prompts import PromptTemplate
template = """鲜花类型: {flower_type}
场合: {occasion}
文案: {ad_copy}"""
prompt_sample = PromptTemplate(
input_variables=["flower_type", "occasion", "ad_copy"],
template=template
)
打印其中一个样本的格式化结果:
print(prompt_sample.format(**samples[0]))
输出:
鲜花类型: 玫瑰
场合: 爱情
文案: 玫瑰,浪漫的象征,是你向心爱的人表达爱意的最佳选择。
3. 构建 FewShotPromptTemplate
将示例样本整合到 FewShotPromptTemplate 中,形成完整的提示模板。
from langchain.prompts.few_shot import FewShotPromptTemplate
prompt = FewShotPromptTemplate(
examples=samples,
example_prompt=prompt_sample,
suffix="鲜花类型: {flower_type}\n场合: {occasion}",
input_variables=["flower_type", "occasion"]
)
4. 调用大模型生成新文案
将新的花类型和场合传入 FewShotPromptTemplate,并调用模型生成文案。
import os
os.environ["OPENAI_API_KEY"] = '你的Open AI Key'
from langchain.llms import OpenAI
model = OpenAI(model_name='gpt-3.5-turbo-instruct')
result = model(prompt.format(flower_type="向日葵", occasion="鼓励"))
print(result)
输出:
文案: 向日葵象征着坚韧与乐观,是鼓励亲朋好友的最佳选择。
5. 使用示例选择器优化效率
当示例数量较多时,可以使用示例选择器,只选择最相关的示例。
from langchain.prompts.example_selector import SemanticSimilarityExampleSelector
from langchain.vectorstores import Chroma
from langchain.embeddings import OpenAIEmbeddings
example_selector = SemanticSimilarityExampleSelector.from_examples(
samples,
OpenAIEmbeddings(),
Chroma,
k=1
)
prompt = FewShotPromptTemplate(
example_selector=example_selector,
example_prompt=prompt_sample,
suffix="鲜花类型: {flower_type}\n场合: {occasion}",
input_variables=["flower_type", "occasion"]
)
通过选择器,可以避免传递过多的示例,节省 Token 使用量。
什么是思维链(Chain of Thought, CoT)
思维链(CoT) 是一种提示工程技术,首次由 Google Brain 的 Jason Wei 等人在 2022 年的论文《Chain-of-Thought Prompting Elicits Reasoning in Large Language Models》中提出。论文指出:
如果模型能够生成一系列中间推理步骤,其复杂推理能力将显著增强。
核心思想:通过在提示中提供清晰的逻辑步骤,指导模型逐步思考并推理复杂问题。
论文中的实验表明,在数学、常识推理等任务中,CoT 极大地提升了大语言模型的性能。尤其在 GSM8K 数学问题基准测试中,CoT 提示帮助模型达到了当时最先进的准确性。
Few-Shot CoT 与 Zero-Shot CoT
Few-Shot CoT
Few-Shot CoT 的思路是:在提示中提供一个或多个带有详细推理步骤的示例(是不是有点像上面的### **FewShotPromptTemplate**)。通过参考这些示例,模型可以学会逐步推理。
论文中展示了一个数学题的实验:
- 左图仅提供答案,模型错误推理。
- 右图提供了解题思路,模型生成了正确答案。
通过 Few-Shot CoT,即使是复杂问题,模型也能通过示例指导生成正确结果。
Zero-Shot CoT
Zero-Shot CoT 的实现更加直接——在提示中添加一句简单的引导,例如:
"Let's think step by step."
仅此一句话,就能让模型自动生成更详细、准确的推理过程!
这两种方法的成功,充分证明了清晰的逻辑引导对于大模型的重要性。
CoT 应用实例:AI 花店助手
让我们通过一个鲜花电商助手的示例,看看如何利用 CoT 技术开发智能应用。
项目需求
开发一个 AI 花店助手,帮助用户选择鲜花并解释推荐理由。
CoT 提示模板设计
我们设计了如下的 CoT 模板:
作为一个为花店电商公司工作的AI助手,我的目标是帮助客户根据他们的喜好做出明智的决定。
我会按部就班的思考,先理解客户的需求,然后考虑各种鲜花的涵义,最后根据这个需求,给出我的推荐。
同时,我也会向客户解释我这样推荐的原因。
示例 1:
人类:我想找一种象征爱情的花。
AI:首先,我理解你正在寻找一种可以象征爱情的花。在许多文化中,红玫瑰被视为爱情的象征,这是因为它们的红色通常与热情和浓烈的感情联系在一起。因此,考虑到这一点,我会推荐红玫瑰。红玫瑰不仅能够象征爱情,同时也可以传达出强烈的感情,这是你在寻找的。
示例 2:
人类:我想要一些独特和奇特的花。
AI:从你的需求中,我理解你想要的是独一无二和引人注目的花朵。兰花是一种非常独特并且颜色鲜艳的花,它们在世界上的许多地方都被视为奢侈品和美的象征。因此,我建议你考虑兰花。选择兰花可以满足你对独特和奇特的要求,而且,兰花的美丽和它们所代表的力量和奢侈也可能会吸引你。
完整实现代码
import os
from langchain.chat_models import ChatOpenAI
from langchain.prompts import ChatPromptTemplate, HumanMessagePromptTemplate, SystemMessagePromptTemplate
# 设置API密钥
os.environ["OPENAI_API_KEY"] = '你的OpenAI API Key'
# 定义角色和CoT模板
role_template = "你是一个为花店电商公司工作的AI助手, 你的目标是帮助客户根据他们的喜好做出明智的决定。"
cot_template = """
作为一个为花店电商公司工作的AI助手,我的目标是帮助客户根据他们的喜好做出明智的决定。
我会按部就班的思考,先理解客户的需求,然后考虑各种鲜花的涵义,最后根据这个需求,给出我的推荐。
同时,我也会向客户解释我这样推荐的原因。
示例 1:
人类:我想找一种象征爱情的花。
AI:首先,我理解你正在寻找一种可以象征爱情的花。在许多文化中,红玫瑰被视为爱情的象征,因此我推荐红玫瑰。
示例 2:
人类:我想要一些独特和奇特的花。
AI:从你的需求中,我理解你想要的是独一无二的花朵。兰花非常符合要求。
"""
# 创建聊天模型
llm = ChatOpenAI(temperature=0)
system_prompt_role = SystemMessagePromptTemplate.from_template(role_template)
system_prompt_cot = SystemMessagePromptTemplate.from_template(cot_template)
human_template = "{human_input}"
human_prompt = HumanMessagePromptTemplate.from_template(human_template)
# 整合模板
chat_prompt = ChatPromptTemplate.from_messages([system_prompt_role, system_prompt_cot, human_prompt])
prompt = chat_prompt.format_prompt(human_input="我想为我的女朋友购买一些花。她喜欢粉色和紫色。你有什么建议吗?").to_messages()
# 调用模型
response = llm(prompt)
print(response)
输出示例
考虑到您的需求,我建议您选择粉色玫瑰和紫色兰花的组合。这些花朵不仅美丽,还能传达深情与浪漫。希望您喜欢这些建议!
思维树(Tree of Thought, ToT)
虽然 CoT 已经显著提升了模型的推理能力,但对于需要多步骤推理的复杂任务来说,单一的线性推理可能不足。思维树(ToT) 是 CoT 的扩展版本,进一步增强了模型的思维能力。
ToT 的核心思想
- 将问题分解为多个步骤,每个步骤包含多个候选答案。
- 使用搜索算法(如广度优先搜索、深度优先搜索)评估每个思维路径的合理性。
- 最终选择最佳路径,生成答案。
ToT 的应用实例:鲜花电商助手
假设用户提出以下问题:
"我想为妻子购买一束花,她喜欢淡雅的颜色和花香。"
ToT 的推理步骤
- 理解需求
- 妻子喜欢淡雅的颜色和花香。
- 候选选项
- 百合:淡雅,花香浓郁。
- 紫罗兰:淡雅,有花香。
- 康乃馨:颜色淡雅,但花香较弱。
- 筛选最佳选项
- 百合和紫罗兰符合需求。
- 给出最终建议
"我建议选择百合或紫罗兰,它们不仅颜色淡雅,还散发迷人的花香,非常适合您的需求。"
通过 ToT,模型不仅能够逐步推理,还能在复杂场景中探索多种解决方案,最终找到最佳答案。