LangChain实战课

月伴飞鱼 2025-05-30 11:25:10
学习专栏 > AI相关
支付宝打赏 微信打赏

如果文章对你有帮助,欢迎点击上方按钮打赏作者!

LangChain本质上就是对各种大模型提供的API的套壳,是为了方便我们使用这些API,搭建起来的一些框架、模块和接口。

欧式距离

欧氏距离度量的是绝对距离,它能很好地反映出向量的绝对差异。

当我们关心数据的绝对大小,例如在物品推荐系统中,用户的购买量可能反映他们的偏好强度。

  • 此时可以考虑使用欧氏距离。

同样,在数据集中各个向量的大小相似,且数据分布大致均匀时,使用欧氏距离也比较适合。

余弦相似度

余弦相似度度量的是方向的相似性,它更关心的是两个向量的⻆度差异,而不是它们的大小差异。

  • 在处理文本数据或者其他高维稀疏数据的时候,余弦相似度特别有用。

比如在信息检索和文本分类等任务中,文本数据往往被表示为高维的词向量。

  • 词向量的方向更能反映其语义相似性,此时可以使用余弦相似度。

安装入门

安装LangChain

pip install langchain

安装LangChain时包括常用的开源LLM(大语言模型)库:

pip install langchain[llms]

安装完成之后,还需要更新到 LangChain的昀新版本,这样才能使用较新的工具。

pip install --upgrade langchain

如果你想从源代码安装,可以克隆存储库并运行:

pip install -e

GitHub:https://github.com/langchain-ai/langchain

文档:https://python.langchain.com/docs/introduction/

当我遇到问题,我通常会在LangChain的GitHub开一个Issue,很快就可以得到解答。

大模型的两种类型:Chat Model 和 Text Model:

类型 用途 代表模型 特点/说明
Chat Model 用于产生人类和AI之间的对话 gpt-3.5-turbo (ChatGPT), GPT-4 - gpt-3.5-turbo: 基础版 - gpt-3.5-turbo-0613: 2023年6月13日的快照版本 - gpt-3.5-turbo-16k: 支持更长上下文(16K tokens),未公开可用且成本更高
Text Model 在ChatGPT之前被广泛使用的API调用方式 text-davinci-003 (基于GPT-3) - text-similarity-curie-001: 专门用于文本相似度比较 - text-embedding-ada-002: 专为文本嵌入设计

上面这两种模型,提供的功能类似,都是接收对话输入(input,也叫prompt),返回回答文本(output,也叫response)。

但是,它们的调用方式和要求的输入格式是有区别的。

调用Text模型

第1步,先注册好你的API Key。

第2步,用pip install openai命令来安装OpenAI库。

第3步,导入 OpenAI API Key。

导入API Key有多种方式,其中之一是通过下面的代码。

import os
os.environ["OPENAI_API_KEY"] = '你的Open API Key'

OpenAI库就会查看名为OPENAI_API_KEY的环境变量,并使用它的值作为API密钥。

也可以像下面这样先导入OpenAI库,然后指定api_key的值。

import openai
openai.api_key = '你的Open API Key'

这种把Key直接放在代码里面的方法昀不可取,因为你一不小心共享了代码,密钥就被别人看到了。

  • 他就可以使用你的GPT-4资源。

所以,建议你给自己的OpenAI账户设个上限,比如每月10美元啥的。

更好的方法是在操作系统中定义环境变量,比如在Linux系统的命令行中使用:

export OPENAI_API_KEY='你的Open API Key'

或者,你也可以考虑把环境变量保存在.env文件中,使用python-dotenv库从文件中读取它。

  • 这样也可以降低API密钥暴露在代码中的⻛险。

第4步,导入OpenAI库,并创建一个Client。

from openai import OpenAI
client = OpenAI()

第5步,指定gpt-3.5-turbo-instruct(也就是Text模型)并调用completions方法,返回结果。

response = client.completions.create(
  model="gpt-3.5-turbo-instruct",
  temperature=0.5,
  max_tokens=100,
  prompt="请给我的花店起个名")

第6步,打印输出大模型返回的文字。

print(response.choices[0].text.strip())

调用Chat模型

整体流程上,Chat模型和Text模型的调用是类似的,只是前面加了一个chat。

  • 然后输入(prompt)和输出(response)的数据格式有所不同。
response = client.chat.completions.create(  
  model="gpt-4",
  messages=[
        {"role": "system", "content": "You are a creative AI."},
        {"role": "user", "content": "请给我的花店起个名"},
    ],
  temperature=0.8,
  max_tokens=60
)

有两个专属于Chat模型的概念,一个是消息,一个是⻆色。

消息:

就是传入模型的提示,此处的messages参数是一个列表,包含了多个消息。

每个消息都有一个role(可以是system、user或assistant)和content(消息的内容)。

  • 系统消息设定了对话的背景,然后用户消息提出了具体请求。

模型的任务是基于这些消息来生成回复。

⻆色:

在OpenAI的Chat模型中,system、user和assistant都是消息的⻆色。

  • 每一种⻆色都有不同的含义和作用。

system:系统消息主要用于设定对话的背景或上下文。

  • 这可以帮助模型理解它在对话中的⻆色和任务。
  • 例如,你可以通过系统消息来设定一个场景,让模型知道它是在扮演一个医生、律师或者一个知识丰富的AI助手。
  • 系统消息通常在对话开始时给出。

user:用户消息是从用户或人类⻆色发出的。

  • 它们通常包含了用户想要模型回答或完成的请求。

  • 用户消息可以是一个问题、一段话,或者任何其他用户希望模型响应的内容。

assistant:助手消息是模型的回复。

  • 例如,在你使用API发送多轮对话中新的对话请求时,可以通过助手消息提供先前对话的上下文。

  • 然而,请注意在对话的最后一条消息应始终为用户消息,因为模型总是要回应最后这条用户消息。

响应:

在使用Chat模型生成内容后,返回的响应,也就是response会包含一个或多个choices,每个choices都包含一个message。

  • 每个message也都包含一个role和content。

role可以是system、user或assistant,表示该消息的发送者,content则包含了消息的实际内容。

一个典型的response对象可能如下所示:

response = 
{
 'id': 'chatcmpl-2nZI6v1cW9E3Jg4w2Xtoql0M3XHfH',
 'object': 'chat.completion',
 'created': 1677649420,
 'model': 'gpt-4',
 'usage': {'prompt_tokens': 56, 'completion_tokens': 31, 'total_tokens': 87},
 'choices': [
   {
    'message': {
      'role': 'assistant',
      'content': '你的花店可以叫做"花香四溢"。'
     },
    'finish_reason': 'stop',
    'index': 0
   }
  ]
}

Chat模型和Text模型都有各自的优点,其适用性取决于具体的应用场景。

相较于Text模型,Chat模型的设计更适合处理对话或者多轮次交互的情况。

  • 这是因为它可以接受一个消息列表作为输入,而不仅仅是一个字符串。

这个消息列表可以包含system、user和assistant的历史信息,从而在处理交互式对话时提供更多的上下文信息。

对于简单的单轮文本生成任务,使用Text模型可能会更简单、更直接。

  • 例如,如果你只需要模型根据一个简单的提示生成一段文本,那么Text模型可能更适合。

从上面的结果看,Chat模型给我们输出的文本更完善,是一句完整的话,而Text模型输出的是几个名字。

  • 这是因为ChatGPT经过了对⻬(基于人类反馈的强化学习),输出的答案更像是真实聊天场景。

这种设计的主要优点包括:

对话历史的管理:

通过使用Chat模型,你可以更方便地管理对话的历史,并在需要时向模型提供这些历史信息。

例如,你可以将过去的用户输入和模型的回复都包含在消息列表中,这样模型在生成新的回复时就可以考虑到这些历史信息。

⻆色模拟:

通过system⻆色,你可以设定对话的背景,给模型提供额外的指导信息,从而更好地控制输出的结果。

当然在Text模型中,你在提示中也可以为AI设定⻆色,作为输入的一部分。

通过 LangChain 调用 Text 和 Chat 模型

调用 Text 模型:

import os
os.environ["OPENAI_API_KEY"] = '你的Open API Key'
from langchain.llms import OpenAI
llm = OpenAI(  
    model="gpt-3.5-turbo-instruct",
    temperature=0.8,
    max_tokens=60,)
response = llm.predict("请给我的花店起个名")
print(response)

调用 Chat 模型:

import os
os.environ["OPENAI_API_KEY"] = '你的Open API Key'
from langchain.chat_models import ChatOpenAI
chat = ChatOpenAI(model="gpt-4",
                    temperature=0.8,
                    max_tokens=60)
from langchain.schema import (
    HumanMessage,
    SystemMessage
)
messages = [
    SystemMessage(content="你是一个很棒的智能助手"),
    HumanMessage(content="请给我的花店起个名")
]
response = chat(messages)
print(response)

模型IO

Model I/O

可以把对模型的使用过程拆解成三块:

  • 分别是输入提示(对应图中的Format)、调用模型(对应图中的Predict)和输出解析(对应图中的Parse)。

这三块形成了一个整体,因此在LangChain中这个过程被统称为 Model I/O。

image-20250530141540409

在模型 I/O的每个环节,LangChain都提供了模板和工具,快捷地形成调用各种语言模型的接口。

提示模板:

使用模型的第一个环节是把提示信息输入到模型中,可以创建LangChain模板。

  • 根据实际需求动态选择不同的输入,针对特定的任务和应用调整输入。

语言模型:

LangChain允许你通过通用接口来调用语言模型。

  • 这意味着无论你要使用的是哪种语言模型,都可以通过同一种方式进行调用,这样就提高了灵活性和便利性。

输出解析:

LangChain还提供了从模型输出中提取信息的功能。

通过输出解析器,你可以精确地从模型的输出中获取需要的信息,而不需要处理冗余或不相关的数据。

  • 更重要的是还可以把大模型给回的非结构化文本,转换成程序可以处理的结构化数据。

提示模板

吴恩达老师在他的提示工程课程中所说的:

给予模型清晰明确的指示。

让模型慢慢地思考。

# 导入LangChain中的提示模板
from langchain import PromptTemplate
# 创建原始模板
template = """ 
您是一位专业的鲜花店文案撰写员。\n
对于售价为 {price} 元的 {flower_name} ,您能提供一个吸引人的简短描述吗?
"""
# 根据原始模板创建LangChain提示模板
prompt = PromptTemplate.from_template(template) 
# 打印LangChain提示模板的内容
print(prompt)

提示模板的具体内容如下:

input_variables=['flower_name', 'price'] 
output_parser=None partial_variables={} 
template='/\n您是一位专业的鲜花店文案撰写员。
\n对于售价为 {price} 元的 {flower_name} ,您能提供一个吸引人的简短描述吗?\n'
template_format='f-string' 
validate_template=True

在这里,所谓模板就是一段描述某种鲜花的文本格式,它是一个f-string。

其中有两个变量{flower_name}和{price}表示花的名称和价格,这两个值是模板里面的占位符。

  • 在实际使用模板生成提示时会被具体的值替换。

代码中的from_template是一个类方法,它允许我们直接从一个字符串模板中创建一个PromptTemplate对象。

打印出这个PromptTemplate对象,你可以看到这个对象中的信息包括输入的变量:

  • (在这个例子中就是flower_name和price)、输出解析器(这个例子中没有指定)
  • 模板的格式(这个例子中为’f-string’)、是否验证模板(这个例子中设置为True)

因此PromptTemplate的from_template方法就是将一个原始的模板字符串转化为一个更丰富、更方便操作的PromptTemplate对象。

这个对象就是LangChain中的提示模板。

语言模型

LangChain中支持的模型有三大类:

大语言模型(LLM),也叫Text Model,这些模型将文本字符串作为输入,并返回文本字符串作为输出。

  • Open AI的text-davinci-003、Facebook的LLaMA、ANTHROPIC的Claude,都是典型的LLM。

聊天模型(Chat Model),主要代表Open AI的ChatGPT系列模型。

  • 这些模型通常由语言模型支持,但它们的 API 更加结构化。

  • 具体来说,这些模型将聊天消息列表作为输入,并返回聊天消息。

文本嵌入模型(Embedding Model),这些模型将文本作为输入并返回浮点数列表,也就是Embedding。

  • 而文本嵌入模型如OpenAI的text-embedding-ada-002,

然后,我们将调用语言模型,让模型帮我们写文案,并且返回文案的结果。

# 导入LangChain中的OpenAI模型接口
from langchain import OpenAI
# 创建模型实例
model = OpenAI(model_name='text-davinci-003')
# 输入提示
input = prompt.format(flower_name=["玫瑰"], price='50')
# 得到模型的输出
output = model(input)
# 打印输出内容
print(output) 

input = prompt.format(flower_name=["玫瑰"], price='50')这行代码的作用是将模板实例化。

此时将{flower_name}替换为”玫瑰”,{price}替换为’50’,形成了具体的提示:

  • 您是一位专业的鲜花店文案撰写员。对于售价为50元的玫瑰,您能提供一个吸引人的简短描述吗?

输出解析

在开发具体应用的过程中,很明显不仅仅需要文字,更多情况下需要的是程序能够直接处理的、结构化的数据。

# 通过LangChain调用模型
from langchain import PromptTemplate, OpenAI

# 导入OpenAI Key
import os
os.environ["OPENAI_API_KEY"] = '你的OpenAI API Key'

# 创建原始提示模板
prompt_template = """您是一位专业的鲜花店文案撰写员。
对于售价为 {price} 元的 {flower_name} ,您能提供一个吸引人的简短描述吗?
{format_instructions}"""

# 创建模型实例
model = OpenAI(model_name='text-davinci-003')

# 导入结构化输出解析器和ResponseSchema
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 = PromptTemplate.from_template(prompt_template, 
                partial_variables={"format_instructions": format_instructions}) 

# 数据准备
flowers = ["玫瑰", "百合", "康乃馨"]
prices = ["50", "30", "20"]

# 创建一个空的DataFrame用于存储结果
import pandas as pd
df = pd.DataFrame(columns=["flower", "price", "description", "reason"]) # 先声明列名

for flower, price in zip(flowers, prices):
    # 根据提示准备模型的输入
    input = prompt.format(flower_name=flower, price=price)

    # 获取模型的输出
    output = model(input)
    
    # 解析模型的输出(这是一个字典结构)
    parsed_output = output_parser.parse(output)

    # 在解析后的输出中添加“flower”和“price”
    parsed_output['flower'] = flower
    parsed_output['price'] = price

    # 将解析后的输出添加到DataFrame中
    df.loc[len(df)] = parsed_output  

# 打印字典
print(df.to_dict(orient='records'))

# 保存DataFrame到CSV文件
df.to_csv("flowers_with_descriptions.csv", index=False)

输出
[{'flower': '玫瑰', 'price': '50', 'description': 'Luxuriate in the beauty of this 50 yuan rose, with its deep red petals and delicate aroma.', 'reason': 'This description emphasizes the elegance and beauty of the rose, which will be sure to draw attention.'}, 
{'flower': '百合', 'price': '30', 'description': '30元的百合,象征着坚定的爱情,带给你的是温暖而持久的情感!', 'reason': '百合是象征爱情的花,写出这样的描述能让顾客更容易感受到百合所带来的爱意。'}, 
{'flower': '康乃馨', 'price': '20', 'description': 'This beautiful carnation is the perfect way to show your love and appreciation. Its vibrant pink color is sure to brighten up any room!', 'reason': 'The description is short, clear and appealing, emphasizing the beauty and color of the carnation while also invoking a sense of love and appreciation.'}]

这段代码中,首先定义输出结构,我们希望模型生成的答案包含两部分:

  • 鲜花的描述文案(description)和撰写这个文案的原因(reason)。

所以定义了一个名为response_schemas的列表,其中包含两个ResponseSchema对象,分别对应这两部分的输出。

根据这个列表,通过StructuredOutputParser.from_response_schemas方法创建了一个输出解析器。

然后,通过输出解析器对象的get_format_instructions()方法获取输出的格式说明(format_instructions)。

  • 再根据原始的字符串模板和输出解析器格式说明创建新的提示模板(这个模板就整合了输出解析结构信息)。

再通过新的模板生成模型的输入,得到模型的输出。

此时模型的输出结构将尽最大可能遵循我们的指示,以便于输出解析器进行解析。

提示工程

提示的结构

指令(Instuction)

告诉模型这个任务大概要做什么、怎么做,比如如何使用提供的外部信息、如何处理查询以及如何构造输出。

这通常是一个提示模板中比较固定的部分。

  • 一个常见用例是告诉模型你是一个有用的XX助手,这会让他更认真地对待自己的角色。

上下文(Context)

则充当模型的额外知识来源。

这些信息可以手动插入到提示中,通过矢量数据库检索得来,或通过其他方式(如调用API、计算器等工具)拉入。

一个常见的用例时是把从向量数据库查询到的知识作为上下文传递给模型。

提示输入(Prompt Input)

通常就是具体的问题或者需要大模型做的具体事情,这个部分和指令部分其实也可以合二为一。

  • 但是拆分出来成为一个独立的组件,就更加结构化,便于复用模板。

这通常是作为变量,在调用模型之前传递给提示模板,以形成具体的提示。

输出指示器

标记要生成的文本的开始,这就像我们小时候的数学考卷,先写一个解,就代表你要开始答题了。

提示模板的类型

image-20250530144636383
from langchain.prompts.prompt import PromptTemplate
from langchain.prompts import FewShotPromptTemplate
from langchain.prompts.pipeline import PipelinePromptTemplate
from langchain.prompts import ChatPromptTemplate
from langchain.prompts import (
    ChatMessagePromptTemplate,
    SystemMessagePromptTemplate,
    AIMessagePromptTemplate,
    HumanMessagePromptTemplate,
)

使用 PromptTemplate

from langchain import PromptTemplate

template = """\
你是业务咨询顾问。
你给一个销售{product}的电商公司,起一个好的名字?
"""
prompt = PromptTemplate.from_template(template)

print(prompt.format(product="鲜花"))

也可以通过提示模板类的构造函数,在创建模板时手工指定input_variables

prompt = PromptTemplate(
    input_variables=["product", "market"], 
    template="你是业务咨询顾问。对于一个面向{market}市场的,专注于销售{product}的公司,你会推荐哪个名字?"
)
print(prompt.format(product="鲜花", market="高端"))

使用 ChatPromptTemplate

对于OpenAI推出的ChatGPT这一类的聊天模型,LangChain也提供了一系列的模板,这些模板的不同之处是它们有对应的角色。

import openai
openai.ChatCompletion.create(
  model="gpt-3.5-turbo",
  messages=[
        {"role": "system", "content": "You are a helpful assistant."},
        {"role": "user", "content": "Who won the world series in 2020?"},
        {"role": "assistant", "content": "The Los Angeles Dodgers won the World Series in 2020."},
        {"role": "user", "content": "Where was it played?"}
    ]
)

消息必须是消息对象的数组,其中每个对象都有一个角色(系统、用户或助理)和内容。

  • 对话可以短至一条消息,也可以来回多次。

通常,对话首先由系统消息格式化,然后是交替的用户消息和助理消息。

# 导入聊天消息类模板
from langchain.prompts import (
    ChatPromptTemplate,
    SystemMessagePromptTemplate,
    HumanMessagePromptTemplate,
)
# 模板的构建
template="你是一位专业顾问,负责为专注于{product}的公司起名。"
system_message_prompt = SystemMessagePromptTemplate.from_template(template)
human_template="公司主打产品是{product_detail}。"
human_message_prompt = HumanMessagePromptTemplate.from_template(human_template)
prompt_template = ChatPromptTemplate.from_messages([system_message_prompt, human_message_prompt])

# 格式化提示消息生成提示
prompt = prompt_template.format_prompt(product="鲜花装饰", product_detail="创新的鲜花设计。").to_messages()

# 下面调用模型,把提示传入模型,生成结果
import os
os.environ["OPENAI_API_KEY"] = '你的OpenAI Key'
from langchain.chat_models import ChatOpenAI
chat = ChatOpenAI()
result = chat(prompt)
print(result)

FewShot的思想起源

Few-Shot(少样本)、One-Shot(单样本)和与之对应的 Zero-Shot(零样本)的概念都起源于机器学习。

在提示工程(Prompt Engineering)中,Few-Shot 和 Zero-Shot 学习的概念也被广泛应用。

  • 在Few-Shot学习设置中,模型会被给予几个示例,以帮助模型理解任务,并生成正确的响应。
  • 在Zero-Shot学习设置中,模型只根据任务的描述生成响应,不需要任何示例。

使用 FewShotPromptTemplate

创建示例样本:

# 1. 创建一些示例
samples = [
  {
    "flower_type": "玫瑰",
    "occasion": "爱情",
    "ad_copy": "玫瑰,浪漫的象征,是你向心爱的人表达爱意的最佳选择。"
  },
  {
    "flower_type": "康乃馨",
    "occasion": "母亲节",
    "ad_copy": "康乃馨代表着母爱的纯洁与伟大,是母亲节赠送给母亲的完美礼物。"
  },
  {
    "flower_type": "百合",
    "occasion": "庆祝",
    "ad_copy": "百合象征着纯洁与高雅,是你庆祝特殊时刻的理想选择。"
  },
  {
    "flower_type": "向日葵",
    "occasion": "鼓励",
    "ad_copy": "向日葵象征着坚韧和乐观,是你鼓励亲朋好友的最好方式。"
  }
]

创建提示模板:

# 2. 创建一个提示模板
from langchain.prompts.prompt import PromptTemplate
template="鲜花类型: {flower_type}\n场合: {occasion}\n文案: {ad_copy}"
prompt_sample = PromptTemplate(input_variables=["flower_type", "occasion", "ad_copy"], 
                               template=template)
print(prompt_sample.format(**samples[0]))

在这个步骤中,创建了一个PromptTemplate对象,这个对象是根据指定的输入变量和模板字符串来生成提示的。

在这里,输入变量包括 flower_type、occasion、ad_copy,模板是一个字符串。

  • 其中包含了用大括号包围的变量名,它们会被对应的变量值替换。

创建 FewShotPromptTemplate 对象:

# 3. 创建一个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"]
)
print(prompt.format(flower_type="野玫瑰", occasion="爱情"))

# 输出
鲜花类型: 玫瑰
场合: 爱情
文案: 玫瑰,浪漫的象征,是你向心爱的人表达爱意的最佳选择。

鲜花类型: 康乃馨
场合: 母亲节
文案: 康乃馨代表着母爱的纯洁与伟大,是母亲节赠送给母亲的完美礼物。

鲜花类型: 百合
场合: 庆祝
文案: 百合象征着纯洁与高雅,是你庆祝特殊时刻的理想选择。

鲜花类型: 向日葵
场合: 鼓励
文案: 向日葵象征着坚韧和乐观,是你鼓励亲朋好友的最好方式。

鲜花类型: 野玫瑰
场合: 爱情

可以看到,FewShotPromptTemplate是一个更复杂的提示模板,它包含了多个示例和一个提示。

这种模板可以使用多个示例来指导模型生成对应的输出。

  • 目前我们创建一个新提示,其中包含了根据指定的花的类型野玫瑰和场合爱情。

调用大模型创建新文案:

# 4. 把提示传递给大模型
import os
os.environ["OPENAI_API_KEY"] = '你的Open AI Key'
from langchain.llms import OpenAI
model = OpenAI(model_name='text-davinci-003')
result = model(prompt.format(flower_type="野玫瑰", occasion="爱情"))

使用示例选择器

如果我们的示例很多,那么一次性把所有示例发送给模型是不现实而且低效的。

  • 另外,每次都包含太多的Token也会浪费流量。
# 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
)

# 创建一个使用示例选择器的FewShotPromptTemplate对象
prompt = FewShotPromptTemplate(
    example_selector=example_selector, 
    example_prompt=prompt_sample, 
    suffix="鲜花类型: {flower_type}\n场合: {occasion}", 
    input_variables=["flower_type", "occasion"]
)
print(prompt.format(flower_type="红玫瑰", occasion="爱情"))

在这个步骤中,它首先创建了一个SemanticSimilarityExampleSelector对象,这个对象可以根据语义相似性选择最相关的示例。

然后,它创建了一个新的FewShotPromptTemplate对象,这个对象使用了上一步创建的选择器来选择最相关的示例生成提示。

然后,又用这个模板生成了一个新的提示,因为我们的提示中需要创建的是红玫瑰的文案。

所以,示例选择器example_selector会根据语义的相似度(余弦相似度)找到最相似的示例。

  • 也就是玫瑰,并用这个示例构建了FewShot模板。

这样,我们就避免了把过多的无关模板传递给大模型,以节省Token的用量。

什么是 Chain of Thought

如果生成一系列的中间推理步骤,就能够显著提高大型语言模型进行复杂推理的能力。

Few-Shot CoT

Few-Shot CoT 简单的在提示中提供了一些链式思考示例(Chain-of-Thought Prompting)。

  • 足够大的语言模型的推理能力就能够被增强。
image.png

Zero-Shot CoT

在Zero-Shot CoT中,你只要简单地告诉模型让我们一步步的思考(Let’s think step by step)

模型就能够给出更好的答案。

image.png

Chain of Thought 实战

项目需求

在这个示例中,你正在开发一个AI运营助手,我们要展示AI如何根据用户的需求推理和生成答案。

然后,AI根据当前的用户请求进行推理,提供了具体的花卉建议并解释了为什么选择这些建议。

CoT的模板设计

针对这个聊天机器人的需求,我设计了下面这样的思维链模板:

  • 作为一个为花店电商公司工作的AI助手,我的目标是帮助客户根据他们的喜好做出明智的决定。
  • 我会按部就班的思考,先理解客户的需求,然后考虑各种鲜花的涵义,最后根据这个需求,给出我的推荐。
  • 同时,我也会向客户解释我这样推荐的原因。
# 设置环境变量和API密钥
import os
os.environ["OPENAI_API_KEY"] = '你的OpenAI API Key'

# 创建聊天模型
from langchain.chat_models import ChatOpenAI
llm = ChatOpenAI(temperature=0)

# 设定 AI 的角色和目标
role_template = "你是一个为花店电商公司工作的AI助手, 你的目标是帮助客户根据他们的喜好做出明智的决定"

# CoT 的关键部分,AI 解释推理过程,并加入一些先前的对话示例(Few-Shot Learning)
cot_template = """
作为一个为花店电商公司工作的AI助手,我的目标是帮助客户根据他们的喜好做出明智的决定。 

我会按部就班的思考,先理解客户的需求,然后考虑各种鲜花的涵义,最后根据这个需求,给出我的推荐。
同时,我也会向客户解释我这样推荐的原因。

示例 1:
  人类:我想找一种象征爱情的花。
  AI:首先,我理解你正在寻找一种可以象征爱情的花。在许多文化中,红玫瑰被视为爱情的象征,这是因为它们的红色通常与热情和浓烈的感情联系在一起。因此,考虑到这一点,我会推荐红玫瑰。红玫瑰不仅能够象征爱情,同时也可以传达出强烈的感情,这是你在寻找的。

示例 2:
  人类:我想要一些独特和奇特的花。
  AI:从你的需求中,我理解你想要的是独一无二和引人注目的花朵。兰花是一种非常独特并且颜色鲜艳的花,它们在世界上的许多地方都被视为奢侈品和美的象征。因此,我建议你考虑兰花。选择兰花可以满足你对独特和奇特的要求,而且,兰花的美丽和它们所代表的力量和奢侈也可能会吸引你。
"""
from langchain.prompts import ChatPromptTemplate, HumanMessagePromptTemplate, SystemMessagePromptTemplate
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是一种解决复杂问题的框架,它在需要多步骤推理的任务中。

  • 引导语言模型搜索一棵由连贯的语言序列(解决问题的中间步骤)组成的思维树,而不是简单地生成一个答案。

ToT框架的核心思想是:

让模型生成和评估其思维的能力,并将其与搜索算法(如广度优先搜索和深度优先搜索)结合起来,进行系统性地探索和验证。

image.png

应用ToT的思想,给出一个鲜花运营方面的示例:

假设一个顾客在鲜花网站上询问:

  • 我想为我的妻子购买一束鲜花,但我不确定应该选择哪种鲜花。她喜欢淡雅的颜色和花香。

AI(使用ToT框架):

思维步骤1:理解顾客的需求。

顾客想为妻子购买鲜花。

顾客的妻子喜欢淡雅的颜色和花香。

思维步骤2:考虑可能的鲜花选择。

候选1:百合,因为它有淡雅的颜色和花香。

候选2:玫瑰,选择淡粉色或白色,它们通常有花香。

候选3:紫罗兰,它有淡雅的颜色和花香。

候选4:桔梗,它的颜色淡雅但不一定有花香。

候选5:康乃馨,选择淡色系列,它们有淡雅的花香。

思维步骤3:根据顾客的需求筛选最佳选择。

百合和紫罗兰都符合顾客的需求,因为它们都有淡雅的颜色和花香。

淡粉色或白色的玫瑰也是一个不错的选择。

桔梗可能不是最佳选择,因为它可能没有花香。

康乃馨是一个可考虑的选择。

思维步骤4:给出建议。

考虑到您妻子喜欢淡雅的颜色和花香,我建议您可以选择百合或紫罗兰。

淡粉色或白色的玫瑰也是一个很好的选择。希望这些建议能帮助您做出决策!

这个例子,可以作为FewShot示例之一,传递给模型,让他学着实现ToT。

CoT的核心思想是通过生成一系列中间推理步骤来增强模型的推理能力。

在Few-Shot CoT和Zero-Shot CoT两种应用方法中:

  • 前者通过提供链式思考示例传递给模型,后者则直接告诉模型进行要按部就班的推理。

ToT进一步扩展了CoT的思想,通过搜索由连贯的语言序列组成的思维树来解决复杂问题。

调用模型

Transformer是几乎所有预训练模型的核心底层架构。

  • 基于Transformer预训练所得的大规模语言模型也被叫做基础模型(Foundation Model 或Base Model)。

在预训练模型出现的早期,BERT毫无疑问是最具代表性的,也是影响力最大的模型。

  • BERT通过同时学习文本的前向和后向上下文信息,实现对句子结构的深入理解。

BERT之后,各种大型预训练模型如雨后春笋般地涌现,自然语言处理(NLP)领域进入了一个新时代。

这些模型推动了NLP技术的快速发展,解决了许多以前难以应对的问题。

  • 比如翻译、文本总结、聊天对话等等,提供了强大的工具。

预训练+微调的模式

大型预训练模型的确是工程师的福音。

因为,经过预训练的大模型中所习得的语义信息和所蕴含的语言知识,能够非常容易地向下游任务迁移。

NLP应用人员可以对模型的头部或者部分参数根据自己的需要进行适应性的调整。

  • 这通常涉及在相对较小的有标注数据集上进行有监督学习,让模型适应特定任务的需求。

预训练模型的微调(Fine-tuning)。

微调过程相比于从头训练一个模型要快得多,且需要的数据量也要少得多。

  • 这使得作为工程师的我们能够更高效地开发和部署各种NLP解决方案。
image-20250530152906411

预训练:

在大规模无标注文本数据上进行模型的训练,目标是让模型学习自然语言的基础表达、上下文信息和语义知识。

  • 为后续任务提供一个通用的、丰富的语言表示基础。

微调:在预训练模型的基础上,可以根据特定的下游任务对模型进行微调。

输出解析

LangChain 中的输出解析器

输出解析器是一种专用于处理和构建语言模型响应的类

一个基本的输出解析器类通常需要实现两个核心方法:

get_format_instructions:

  • 这个方法需要返回一个字符串,用于指导如何格式化语言模型的输出,告诉它应该如何组织并构建它的回答。

parse:

  • 这个方法接收一个字符串(也就是语言模型的输出)并将其解析为特定的数据结构或格式。
  • 这一步通常用于确保模型的输出符合我们的预期,并且能够以我们需要的形式进行后续处理。

还有一个可选的方法。

parse_with_prompt

这个方法接收一个字符串(也就是语言模型的输出)和一个提示(用于生成这个输出的提示),并将其解析为特定的数据结构。

这样,你可以根据原始提示来修正或重新解析模型的输出,确保输出的信息更加准确和贴合要求。

class OutputParser:
    def __init__(self):
        pass

    def get_format_instructions(self):
        # 返回一个字符串,指导如何格式化模型的输出
        pass

    def parse(self, model_output):
        # 解析模型的输出,转换为某种数据结构或格式
        pass

    def parse_with_prompt(self, model_output, prompt):
        # 基于原始提示解析模型的输出,转换为某种数据结构或格式
        pass

LangChain通过这个三个方法,实现了各种解析器:

列表解析器(List Parser):

这个解析器用于处理模型生成的输出,当需要模型的输出是一个列表的时候使用。

例如,如果你询问模型列出所有鲜花的库存,模型的回答应该是一个列表。

日期时间解析器(Datetime Parser):

这个解析器用于处理日期和时间相关的输出,确保模型的输出是正确的日期或时间格式。

枚举解析器(Enum Parser):

这个解析器用于处理预定义的一组值,当模型的输出应该是这组预定义值之一时使用。

例如,如果你定义了一个问题的答案只能是是或否,那么枚举解析器可以确保模型的回答是这两个选项之一。

结构化输出解析器(Structured Output Parser):

这个解析器用于处理复杂的、结构化的输出。

如果你的应用需要模型生成具有特定结构的复杂回答(例如一份报告、一篇文章等),那么可以使用结构化输出解析器来实现。

Pydantic(JSON)解析器:

这个解析器用于处理模型的输出,当模型的输出应该是一个符合特定格式的JSON对象时使用。

它使用Pydantic库,这是一个数据验证库,可以用于构建复杂的数据模型,并确保模型的输出符合预期的数据模型。

自动修复解析器(Auto-Fixing Parser):

这个解析器可以自动修复某些常见的模型输出错误。

例如,如果模型的输出应该是一段文本,但是模型返回了一段包含语法或拼写错误的文本,自动修复解析器可以自动纠正这些错误。

重试解析器(RetryWithErrorOutputParser):

这个解析器用于在模型的初次输出不符合预期时,尝试修复或重新生成新的输出。

例如,如果模型的输出应该是一个日期,但是模型返回了一个字符串,那么重试解析器可以重新提示模型生成正确的日期格式。

Pydantic(JSON)解析器实战

创建模型实例:

# 设置OpenAI API密钥
import os
os.environ["OPENAI_API_KEY"] = '你的OpenAI API Key'

# 创建模型实例
from langchain import OpenAI
model = OpenAI(model_name='text-davinci-003')

定义输出数据的格式:

先创建了一个空的DataFrame,用于存储从模型生成的描述。

接下来,通过一个名为FlowerDescription的Pydantic BaseModel类,定义了期望的数据格式(也就是数据的结构)。

# ------Part 2
# 创建一个空的DataFrame用于存储结果
import pandas as pd
df = pd.DataFrame(columns=["flower_type", "price", "description", "reason"])

# 数据准备
flowers = ["玫瑰", "百合", "康乃馨"]
prices = ["50", "30", "20"]

# 定义我们想要接收的数据格式
from pydantic import BaseModel, Field
class FlowerDescription(BaseModel):
    flower_type: str = Field(description="鲜花的种类")
    price: int = Field(description="鲜花的价格")
    description: str = Field(description="鲜花的描述文案")
    reason: str = Field(description="为什么要这样写这个文案")

创建输出解析器:

# ------Part 3
# 创建输出解析器
from langchain.output_parsers import PydanticOutputParser
output_parser = PydanticOutputParser(pydantic_object=FlowerDescription)

# 获取输出格式指示
format_instructions = output_parser.get_format_instructions()
# 打印提示
print("输出格式:",format_instructions)

让输入模型的提示和输出解析器的要求相互吻合,前后就呼应得上

创建提示模板:

我们定义了一个提示模板,该模板将用于为模型生成输入提示。

模板中包含了你需要模型填充的变量(如价格和花的种类),以及之前获取的输出格式指示。

# ------Part 4
# 创建提示模板
from langchain import PromptTemplate
prompt_template = """您是一位专业的鲜花店文案撰写员。
对于售价为 {price} 元的 {flower} ,您能提供一个吸引人的简短中文描述吗?
{format_instructions}"""

# 根据模板创建提示,同时在提示中加入输出解析器的说明
prompt = PromptTemplate.from_template(prompt_template, 
       partial_variables={"format_instructions": format_instructions}) 

# 打印提示
print("提示:", prompt)

生成提示,传入模型并解析输出:

这部分是程序的主体,我们循环来处理所有的花和它们的价格。

  • 对于每种花,都根据提示模板创建了输入,然后获取模型的输出。

然后使用之前创建的解析器来解析这个输出,并将解析后的输出添加到DataFrame中。

最后,你打印出了所有的结果,并且可以选择将其保存到CSV文件中。

# ------Part 5
for flower, price in zip(flowers, prices):
    # 根据提示准备模型的输入
    input = prompt.format(flower=flower, price=price)
    # 打印提示
    print("提示:", input)

    # 获取模型的输出
    output = model(input)

    # 解析模型的输出
    parsed_output = output_parser.parse(output)
    parsed_output_dict = parsed_output.dict()  # 将Pydantic格式转换为字典

    # 将解析后的输出添加到DataFrame中
    df.loc[len(df)] = parsed_output.dict()

# 打印字典
print("输出的数据:", df.to_dict(orient='records'))

自动修复解析器(OutputFixingParser)实战

设计一个解析时出现的错误:

# 导入所需要的库和模块
from langchain.output_parsers import PydanticOutputParser
from pydantic import BaseModel, Field
from typing import List

# 使用Pydantic创建一个数据格式,表示花
class Flower(BaseModel):
    name: str = Field(description="name of a flower")
    colors: List[str] = Field(description="the colors of this flower")
# 定义一个用于获取某种花的颜色列表的查询
flower_query = "Generate the charaters for a random flower."

# 定义一个格式不正确的输出
misformatted = "{'name': '康乃馨', 'colors': ['粉红色','白色','红色','紫色','黄色']}"

# 创建一个用于解析输出的Pydantic解析器,此处希望解析为Flower格式
parser = PydanticOutputParser(pydantic_object=Flower)
# 使用Pydantic解析器解析不正确的输出
parser.parse(misformatted)

这段代码如果运行,会出现错误,因为json数据格式是不正确的。

可以使用使用OutputFixingParser来帮助咱们自动解决类似的格式错误,不用人力去修改。

# 从langchain库导入所需的模块
from langchain.chat_models import ChatOpenAI
from langchain.output_parsers import OutputFixingParser

# 设置OpenAI API密钥
import os
os.environ["OPENAI_API_KEY"] = '你的OpenAI API Key'

# 使用OutputFixingParser创建一个新的解析器,该解析器能够纠正格式不正确的输出
new_parser = OutputFixingParser.from_llm(parser=parser, llm=ChatOpenAI())

# 使用新的解析器解析不正确的输出
result = new_parser.parse(misformatted) # 错误被自动修正
print(result) # 打印解析后的输出结果

在于,在OutputFixingParser内部,调用了原有的PydanticOutputParser,如果成功,就返回。

如果失败,它会将格式错误的输出以及格式化的指令传递给大模型,并要求LLM进行相关的修复。

重试解析器(RetryWithErrorOutputParser)实战

OutputFixingParser不错,但它只能做简单的格式修复。

如果出错的不只是格式,比如,输出根本不完整,有缺失内容,那么仅仅根据输出和格式本身,是无法修复它的。

还是设计一个解析过程中的错误。

# 定义一个模板字符串,这个模板将用于生成提问
template = """Based on the user question, provide an Action and Action Input for what step should be taken.
{format_instructions}
Question: {query}
Response:"""

# 定义一个Pydantic数据格式,它描述了一个"行动"类及其属性
from pydantic import BaseModel, Field
class Action(BaseModel):
    action: str = Field(description="action to take")
    action_input: str = Field(description="input to the action")

# 使用Pydantic格式Action来初始化一个输出解析器
from langchain.output_parsers import PydanticOutputParser
parser = PydanticOutputParser(pydantic_object=Action)

# 定义一个提示模板,它将用于向模型提问
from langchain.prompts import PromptTemplate
prompt = PromptTemplate(
    template="Answer the user query.\n{format_instructions}\n{query}\n",
    input_variables=["query"],
    partial_variables={"format_instructions": parser.get_format_instructions()},
)
prompt_value = prompt.format_prompt(query="What are the colors of Orchid?")

# 定义一个错误格式的字符串
bad_response = '{"action": "search"}'
parser.parse(bad_response) # 如果直接解析,它会引发一个错误

由于bad_response只提供了action字段,而没有提供action_input字段,这与Action数据格式的预期不符,所以解析会失败。

首先尝试用OutputFixingParser来解决这个错误。

from langchain.output_parsers import OutputFixingParser
from langchain.chat_models import ChatOpenAI
fix_parser = OutputFixingParser.from_llm(parser=parser, llm=ChatOpenAI())
parse_result = fix_parser.parse(bad_response)
print('OutputFixingParser的parse结果:',parse_result)

OutputFixingParser的parse结果:action='search' action_input='query'

解决的问题有:

  • 不完整的数据:原始的bad_response只提供了action字段而没有action_input字段。
  • OutputFixingParser已经填补了这个缺失,为action_input字段提供了值 query。

没解决的问题有:

  • 具体性:尽管OutputFixingParser为action_input字段提供了默认值 query,但这并不具有描述性。

  • 真正的查询是 Orchid(兰花)的颜色是什么?所以,这个修复只是提供了一个通用的值,并没有真正地回答用户的问题。

可能的误导:query 可能被误解为一个指示,要求进一步查询某些内容,而不是作为实际的查询输入。

有更鲁棒的选择,最后尝试一下RetryWithErrorOutputParser这个解析器

# 初始化RetryWithErrorOutputParser,它会尝试再次提问来得到一个正确的输出
from langchain.output_parsers import RetryWithErrorOutputParser
from langchain.llms import OpenAI
retry_parser = RetryWithErrorOutputParser.from_llm(
    parser=parser, llm=OpenAI(temperature=0)
)
parse_result = retry_parser.parse_with_prompt(bad_response, prompt_value)
print('RetryWithErrorOutputParser的parse结果:',parse_result)

RetryWithErrorOutputParser的parse结果:action='search' action_input='colors of Orchid'

这个解析器没有让我们失望,成功地还原了格式,甚至也根据传入的原始提示,还原了action_input字段的内容。

RetryWithErrorOutputParser的parse结果:action=’search’,action_input=’colors of Orchid’

Chain链

什么是 Chain

如果你想开发更复杂的应用程序,那么就需要通过 Chain 来链接LangChain的各个组件和功能。

模型之间彼此链接,或模型与其他组件链接。

首先LangChain通过设计好的接口,实现一个具体的链的功能。

例如,LLM链(LLMChain)能够接受用户输入,使用 PromptTemplate 对其进行格式化,然后将格式化的响应传递给 LLM。

  • 这就相当于把整个Model I/O的流程封装到链里面。

实现了链的具体功能之后,我们可以通过将多个链组合在一起,或者将链与其他组件组合来构建更复杂的链。

链在内部把一系列的功能进行封装,而链的外部则又可以组合串联。

链其实可以被视为LangChain中的一种基本功能单元。

image-20250530161444003

LLMChain:最简单的链

#----第一步 创建提示
# 导入LangChain中的提示模板
from langchain import PromptTemplate
# 原始字符串模板
template = "{flower}的花语是?"
# 创建LangChain模板
prompt_temp = PromptTemplate.from_template(template) 
# 根据模板创建提示
prompt = prompt_temp.format(flower='玫瑰')
# 打印提示的内容
print(prompt)

#----第二步 创建并调用模型 
# 导入LangChain中的OpenAI模型接口
from langchain import OpenAI
# 创建模型实例
model = OpenAI(temperature=0)
# 传入提示,调用模型,返回结果
result = model(prompt)
print(result)

此时Model I/O的实现分为两个部分,提示模板的构建和模型的调用独立处理。

如果使用链,代码结构则显得更简洁。

# 导入所需的库
from langchain import PromptTemplate, OpenAI, LLMChain
# 原始字符串模板
template = "{flower}的花语是?"
# 创建模型实例
llm = OpenAI(temperature=0)
# 创建LLMChain
llm_chain = LLMChain(
    llm=llm,
    prompt=PromptTemplate.from_template(template))
# 调用LLMChain,返回结果
result = llm_chain("玫瑰")
print(result)

链的调用方式

直接调用:

如果你的提示模板中包含多个变量,在调用链的时候,可以使用字典一次性输入它们。

prompt = PromptTemplate(
    input_variables=["flower", "season"],
    template="{flower}在{season}的花语是?",
)
chain = LLMChain(llm=llm, prompt=prompt)
print(chain({
    'flower': "玫瑰",
    'season': "夏季" }))

通过run方法:

通过run方法,也等价于直接调用_call_函数。

llm_chain("玫瑰")

等价于:

llm_chain.run("玫瑰")

Sequential Chain:顺序链

第一步,我们假设大模型是一个植物学家,让他给出某种特定鲜花的知识和介绍。

第二步,我们假设大模型是一个鲜花评论者,让他参考上面植物学家的文字输出,对鲜花进行评论。

第三步,我们假设大模型是易速鲜花的社交媒体运营经理。

  • 让他参考上面植物学家和鲜花评论者的文字输出,来写一篇鲜花运营文案。

导入所有需要的库:

# 设置OpenAI API密钥
import os
os.environ["OPENAI_API_KEY"] = '你的OpenAI API Key'

from langchain.llms import OpenAI
from langchain.chains import LLMChain
from langchain.prompts import PromptTemplate
from langchain.chains import SequentialChain

添加第一个LLMChain,生成鲜花的知识性说明。

# 这是第一个LLMChain,用于生成鲜花的介绍,输入为花的名称和种类
llm = OpenAI(temperature=.7)
template = """
你是一个植物学家。给定花的名称和类型,你需要为这种花写一个200字左右的介绍。

花名: {name}
颜色: {color}
植物学家: 这是关于上述花的介绍:"""
prompt_template = PromptTemplate(input_variables=["name", "color"], template=template)
introduction_chain = LLMChain(llm=llm, prompt=prompt_template, output_key="introduction")

添加第二个LLMChain,根据鲜花的知识性说明生成评论。

# 这是第二个LLMChain,用于根据鲜花的介绍写出鲜花的评论
llm = OpenAI(temperature=.7)
template = """
你是一位鲜花评论家。给定一种花的介绍,你需要为这种花写一篇200字左右的评论。

鲜花介绍:
{introduction}
花评人对上述花的评论:"""
prompt_template = PromptTemplate(input_variables=["introduction"], template=template)
review_chain = LLMChain(llm=llm, prompt=prompt_template, output_key="review")

添加第三个LLMChain,根据鲜花的介绍和评论写出一篇自媒体的文案。

# 这是第三个LLMChain,用于根据鲜花的介绍和评论写出一篇自媒体的文案
template = """
你是一家花店的社交媒体经理。给定一种花的介绍和评论,你需要为这种花写一篇社交媒体的帖子,300字左右。

鲜花介绍:
{introduction}
花评人对上述花的评论:
{review}

社交媒体帖子:
"""
prompt_template = PromptTemplate(input_variables=["introduction", "review"], template=template)
social_post_chain = LLMChain(llm=llm, prompt=prompt_template, output_key="social_post_text")

最后,添加SequentialChain,把前面三个链串起来。

# 这是总的链,我们按顺序运行这三个链
overall_chain = SequentialChain(
    chains=[introduction_chain, review_chain, social_post_chain],
    input_variables=["name", "color"],
    output_variables=["introduction","review","social_post_text"],
    verbose=True)

# 运行链,并打印结果
result = overall_chain({"name":"玫瑰", "color": "黑色"})
print(result)

RouterChain

RouterChain,也叫路由链,能动态选择用于给定输入的下一个链。

我们会根据用户的问题内容,首先使用路由器链确定问题更适合哪个处理模板。

然后将问题发送到该处理模板进行回答。

  • 如果问题不适合任何已定义的处理模板,它会被发送到默认链。

具体步骤如下:

构建处理模板:

  • 为鲜花护理和鲜花装饰分别定义两个字符串模板。

提示信息:

  • 使用一个列表来组织和存储这两个处理模板的关键信息,如模板的键、描述和实际内容。

初始化语言模型:

  • 导入并实例化语言模型。

构建目标链:

  • 根据提示信息中的每个模板构建了对应的LLMChain,并存储在一个字典中。

构建LLM路由链:

  • 这是决策的核心部分。
  • 首先,它根据提示信息构建了一个路由模板,然后使用这个模板创建了一个LLMRouterChain。

构建默认链:

  • 如果输入不适合任何已定义的处理模板,这个默认链会被触发。

构建多提示链:

  • 使用MultiPromptChain将LLM路由链、目标链和默认链组合在一起,形成一个完整的决策系统。

构建提示信息的模板:

# 构建两个场景的模板
flower_care_template = """你是一个经验丰富的园丁,擅长解答关于养花育花的问题。
                        下面是需要你来回答的问题:
                        {input}"""

flower_deco_template = """你是一位网红插花大师,擅长解答关于鲜花装饰的问题。
                        下面是需要你来回答的问题:
                        {input}"""

# 构建提示信息
prompt_infos = [
    {
        "key": "flower_care",
        "description": "适合回答关于鲜花护理的问题",
        "template": flower_care_template,
    },
    {
        "key": "flower_decoration",
        "description": "适合回答关于鲜花装饰的问题",
        "template": flower_deco_template,
    }]

初始化语言模型:

# 初始化语言模型
from langchain.llms import OpenAI
import os
os.environ["OPENAI_API_KEY"] = '你的OpenAI Key'
llm = OpenAI()

构建目标链:

# 构建目标链
from langchain.chains.llm import LLMChain
from langchain.prompts import PromptTemplate
chain_map = {}
for info in prompt_infos:
    prompt = PromptTemplate(template=info['template'], 
                            input_variables=["input"])
    print("目标提示:\n",prompt)
    chain = LLMChain(llm=llm, prompt=prompt,verbose=True)
    chain_map[info["key"]] = chain

目标链提示是这样的:

目标提示:
input_variables=['input'] 
output_parser=None partial_variables={} 
template='你是一个经验丰富的园丁,擅长解答关于养花育花的问题。\n下面是需要你来回答的问题:\n  
{input}' template_format='f-string' 
validate_template=True

目标提示:
input_variables=['input'] 
output_parser=None partial_variables={} 
template='你是一位网红插花大师,擅长解答关于鲜花装饰的问题。\n 下面是需要你来回答的问题:\n 
{input}' template_format='f-string' 
validate_template=True

构建路由链:

# 构建路由链
from langchain.chains.router.llm_router import LLMRouterChain, RouterOutputParser
from langchain.chains.router.multi_prompt_prompt import MULTI_PROMPT_ROUTER_TEMPLATE as RounterTemplate
destinations = [f"{p['key']}: {p['description']}" for p in prompt_infos]
router_template = RounterTemplate.format(destinations="\n".join(destinations))
print("路由模板:\n",router_template)
router_prompt = PromptTemplate(
    template=router_template,
    input_variables=["input"],
    output_parser=RouterOutputParser(),)
print("路由提示:\n",router_prompt)
router_chain = LLMRouterChain.from_llm(llm, 
                                       router_prompt,
                                       verbose=True)

Memory记忆

无论是LLM还是代理都是无状态的,每次模型的调用都是独立于其他交互的。

也就是说,我们每次通过API开始和大语言模型展开一次新的对话,它都不知道你其实昨天或者前天曾经和它聊过天了。

使用ConversationChain

from langchain import OpenAI
from langchain.chains import ConversationChain

# 初始化大语言模型
llm = OpenAI(
    temperature=0.5,
    model_name="text-davinci-003"
)

# 初始化对话链
conv_chain = ConversationChain(llm=llm)

# 打印对话的模板
print(conv_chain.prompt.template)

The following is a friendly conversation between a human and an AI. The AI is talkative and provides lots of specific details from its context. If the AI does not know the answer to a question, it truthfully says it does not know.

Current conversation:
{history}
Human: {input}
AI:

{history} 是存储会话记忆的地方,也就是人类和人工智能之间对话历史的信息。

{input} 是新输入的地方,你可以把它看成是和ChatGPT对话时,文本框中的输入。

使用ConversationBufferMemory

在LangChain中,通过ConversationBufferMemory(缓冲记忆)可以实现最简单的记忆机制。

from langchain import OpenAI
from langchain.chains import ConversationChain
from langchain.chains.conversation.memory import ConversationBufferMemory

# 初始化大语言模型
llm = OpenAI(
    temperature=0.5,
    model_name="text-davinci-003")

# 初始化对话链
conversation = ConversationChain(
    llm=llm,
    memory=ConversationBufferMemory()
)

# 第一天的对话
# 回合1
conversation("我姐姐明天要过生日,我需要一束生日花束。")
print("第一次对话后的记忆:", conversation.memory.buffer)

#输出:
第一次对话后的记忆: 
Human: 我姐姐明天要过生日,我需要一束生日花束。
AI:  哦,你姐姐明天要过生日,那太棒了!我可以帮你推荐一些生日花束,你想要什么样的?我知道有很多种,比如玫瑰、康乃馨、郁金香等等。
# 回合2
conversation("她喜欢粉色玫瑰,颜色是粉色的。")
print("第二次对话后的记忆:", conversation.memory.buffer)

#输出
第二次对话后的记忆: 
Human: 我姐姐明天要过生日,我需要一束生日花束。
AI:  哦,你姐姐明天要过生日,那太棒了!我可以帮你推荐一些生日花束,你想要什么样的?我知道有很多种,比如玫瑰、康乃馨、郁金香等等。
Human: 她喜欢粉色玫瑰,颜色是粉色的。
AI:  好的,那我可以推荐一束粉色玫瑰的生日花束给你。你想要多少朵?我可以帮你定制一束,比如说十朵、二十朵或者更多?

实际上,这些聊天历史信息,都被传入了ConversationChain的提示模板中的 {history} 参数。

  • 构建出了包含聊天记录的新的提示输入。

有了记忆机制,LLM能够了解之前的对话内容,这样简单直接地存储所有内容为LLM提供了最大量的信息。

  • 但是新输入中也包含了更多的Token(所有的聊天历史记录),这意味着响应时间变慢和更高的成本。

但是Token太长,也是需要解决。

使用ConversationBufferWindowMemory

ConversationBufferWindowMemory 是缓冲窗口记忆,它的思路就是只保存最新最近的几次人类和AI的互动。

from langchain import OpenAI
from langchain.chains import ConversationChain
from langchain.chains.conversation.memory import ConversationBufferWindowMemory

# 创建大语言模型实例
llm = OpenAI(
    temperature=0.5,
    model_name="text-davinci-003")

# 初始化对话链
conversation = ConversationChain(
    llm=llm,
    memory=ConversationBufferWindowMemory(k=1)
)

# 第一天的对话
# 回合1
result = conversation("我姐姐明天要过生日,我需要一束生日花束。")
print(result)
# 回合2
result = conversation("她喜欢粉色玫瑰,颜色是粉色的。")
# print("\n第二次对话后的记忆:\n", conversation.memory.buffer)
print(result)

# 第二天的对话
# 回合3
result = conversation("我又来了,还记得我昨天为什么要来买花吗?")
print(result)

第三回合的输出:
{'input': '我又来了,还记得我昨天为什么要来买花吗?', 
'history': 'Human: 她喜欢粉色玫瑰,颜色是粉色的。\nAI:  好的,那粉色玫瑰花束怎么样?我可以帮你找到一束非常漂亮的粉色玫瑰花束,你觉得怎么样?', 
'response': '  当然记得,你昨天来买花是为了给你喜欢的人送一束粉色玫瑰花束,表达你对TA的爱意。'}

如果设置 k=1,这意味着窗口只会记住与AI之间的最新的互动,即只保留上一次的人类回应和AI的回应。

使用ConversationSummaryMemory

上面说了,如果模型在第二轮回答的时候,能够说出我可以帮你为你姐姐找到…,那么在第三轮回答时。

  • 即使窗口大小 k=1,还是能够回答出正确答案。

因为模型在回答新问题的时候,对之前的问题进行了总结性的重述。

ConversationSummaryMemory(对话总结记忆)的思路就是将对话历史进行汇总,然后再传递给 {history} 参数。

这种方法旨在通过对之前的对话进行汇总来避免过度使用 Token。

ConversationSummaryMemory有这么几个核心特点

汇总对话:此方法不是保存整个对话历史,而是每次新的互动发生时对其进行汇总。

  • 然后将其添加到之前所有互动的运行汇总中。

使用LLM进行汇总:该汇总功能由另一个LLM驱动,这意味着对话的汇总实际上是由AI自己进行的。

适合长对话:

对于长对话,此方法的优势尤为明显。虽然最初使用的 Token 数量较多,但随着对话的进展,汇总方法的增长速度会减慢。

  • 与此同时,常规的缓冲内存模型会继续线性增长。
from langchain.chains.conversation.memory import ConversationSummaryMemory

# 初始化对话链
conversation = ConversationChain(
    llm=llm,
    memory=ConversationSummaryMemory(llm=llm)
)

第一回合的输出:
{'input': '我姐姐明天要过生日,我需要一束生日花束。', 
'history': '', 
'response': ' 我明白,你需要一束生日花束。我可以为你提供一些建议吗?我可以推荐一些花束给你,比如玫瑰,康乃馨,百合,仙客来,郁金香,满天星等等。挑选一束最适合你姐姐的生日花束吧!'}
第二回合的输出:
{'input': '她喜欢粉色玫瑰,颜色是粉色的。', 
'history': "\nThe human asked what the AI thinks of artificial intelligence. The AI thinks artificial intelligence is a force for good because it will help humans reach their full potential. The human then asked the AI for advice on what type of flower bouquet to get for their sister's birthday, to which the AI provided a variety of suggestions.", 
'response': ' 为了为你的姐姐的生日准备一束花,我建议你搭配粉色玫瑰和白色康乃馨。你可以在玫瑰花束中添加一些紫色的满天星,或者添加一些绿叶以增加颜色对比。这将是一束可爱的花束,让你姐姐的生日更加特别。'}
第三回合的输出:
{'input': '我又来了,还记得我昨天为什么要来买花吗?', 
'history': "\n\nThe human asked what the AI thinks of artificial intelligence. The AI thinks artificial intelligence is a force for good because it will help humans reach their full potential. The human then asked the AI for advice on what type of flower bouquet to get for their sister's birthday, to which the AI suggested pink roses and white carnations with the addition of purple aster flowers and green leaves for contrast. This would make a lovely bouquet to make the sister's birthday extra special.",
'response': ' 确实,我记得你昨天想买一束花给你的姐姐作为生日礼物。我建议你买粉红色的玫瑰花和白色的康乃馨花,再加上紫色的雏菊花和绿叶,这样可以让你的姐姐的生日更加特别。'}

这里,我们不仅仅利用了LLM来回答每轮问题,还利用LLM来对之前的对话进行总结性的陈述,以节约Token数量。

通过对话历史的汇总来优化和管理 Token 的使用。

  • ConversationSummaryMemory 为那些预期会有多轮的、长时间对话的场景提供了一种很好的方法。

然而,这种方法仍然受到 Token 数量的限制,在一段时间后,我们仍然会超过大模型的上下文窗口限制。

使用ConversationSummaryBufferMemory

对话总结缓冲记忆,它是一种混合记忆模型。

  • 结合了上述各种记忆机制,包括ConversationSummaryMemory 和 ConversationBufferWindowMemory的特点。

这种模型旨在在对话中总结早期的互动,同时尽量保留最近互动中的原始内容。

  • 它是通过max_token_limit这个参数做到这一点的。

当最新的对话文字长度在300字之内的时候,LangChain会记忆原始对话内容。

当对话文字超出了这个参数的长度,那么模型就会把所有超过预设长度的内容进行总结,以节省Token数量。

from langchain.chains.conversation.memory import ConversationSummaryBufferMemory

# 初始化对话链
conversation = ConversationChain(
    llm=llm,
    memory=ConversationSummaryBufferMemory(
        llm=llm,
        max_token_limit=300))
image-20250530170029206

ReAct框架

应用思维链推理并不能解决大模型的固有问题:无法主动更新自己的知识,导致出现事实幻觉。

也就是说,因为缺乏和外部世界的接触,大模型只拥有训练时见过的知识,以及提示信息中作为上下文提供的附加知识。

如果你问的问题超出它的知识范围,要么大模型向你坦白:

  • 我的训练时间截至XXXX年XX月XX日,要么它就会开始一本正经地胡说。

代理的作用

每当你遇到这种需要模型做自主判断、自行调用工具、自行决定下一步行动的时候,Agent(也就是代理)就出场了。

  • 大模型:提供逻辑的引擎,负责生成预测和处理输入。
  • 与之交互的外部工具:可能包括数据清洗工具、搜索引擎、应用程序等。
  • 控制交互的代理:调用适当的外部工具,并管理整个交互过程的流程。

ReAct框架

如果你接到一个新任务,你将如何做出决策并完成下一步的行动?

通过代理实现ReAct框架

ZERO_SHOT_REACT_DESCRIPTION ——这种常用代理类型:

pip install google-search-results #先安装SerpAPI的包。
# 设置OpenAI和SERPAPI的API密钥
import os
os.environ["OPENAI_API_KEY"] = 'Your OpenAI API Key'
os.environ["SERPAPI_API_KEY"] = 'Your SerpAPI API Key'
from langchain.agents import load_tools
from langchain.agents import initialize_agent
from langchain.agents import AgentType
from langchain.llms import OpenAI
llm = OpenAI(temperature=0)
tools = load_tools(["serpapi", "llm-math"], llm=llm) # 加载一些要使用的工具,包括serpapi(这是调用Google搜索引擎的工具)以及llm-math(这是通过LLM进行数学计算的工具)。
agent = initialize_agent(tools, llm, agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION, verbose=True)
agent.run("目前市场上玫瑰花的平均价格是多少?如果我在此基础上加价15%卖出,应该如何定价?")

检索增强生成

什么是 RAG?

其全称为 Retrieval-Augmented Generation,即检索增强生成。

  • 它结合了检 索和生成的能力,为文本序列生成任务引入外部知识。

RAG 将传统的语言生成模型与大规模 的外部知识库相结合。

  • 使模型在生成响应或文本时可以动态地从这些知识库中检索相关信息。

RAG 的工作原理可以概括为几个步骤 :

检索:对于给定的输入(问题),模型首先使用检索系统从大型文档集合中查找相关的文档 或段落。

  • 这个检索系统通常基于密集向量搜索,例如 ChromaDB、Faiss 这样的向量数据库。

上下文编码:

  • 找到相关的文档或段落后,模型将它们与原始输入(问题)一起编码。

生成:使用编码的上下文信息,模型生成输出(答案),这通常当然是通过大模型完成的。

支付宝打赏 微信打赏

如果文章对你有帮助,欢迎点击上方按钮打赏作者!