LangChain Model IO模块

1. Model I/O

LangChain的Model I/O模块提供了标准的、可扩展的接口实现与大语言模型的外部集成,包括模型输入(Prompts)、模型输出(OutPuts)和模型本身(Models),通过该模块可以快速与任意大模型进行对话。

在这个模块中,LangChain抽象出一个chain,用于进一步简化和增强交互流程。包含三个核心部分,FormatPredictParse

Format:Prompts Template,通过模板化来管理大模型的输入
Predict:Models,使用通用接口调用不同的大语言模型
Parse:Output,用来从模型的推理中提取信息,并按照预先设定好的模版来规范化输出

2. Format

Prompt Template利用各种提示工程技巧,如Few-Shot、链式推理(CoT)等方法,以提高大模型的推理性能。
在实际应用开发中,提示词是动态变化的。
LangChain通过Prompt Template支持变量和动态内容的插入,使同一个应用可以根据不同的输入动态调整提示词,从而更好地响应用户的具体需求。

3. Predict

Predict处理模型从接收输入到执行推理的整个过程,LangChain在Model I/O模块中对Base类模型和Chat类模型都进行了抽象,分别归类为LLMs(Large Language Models)和Chat Models
LLMs是简化的大语言模型抽象,基于给定的Prompt提供内容生成的功能。而Chat Models专注于聊天API的抽象,需要维护上下文的记忆(聊天记录),呈现出更接近对话或聊天形式的交互。

4. Parse

大模型的输出是不稳定的,同样的输入Prompt往往会得到不同形式的输出,在应用开发中,大模型的输出可能是下一步逻辑处理的关键输入。因此,在这种情况下,规范化输出是必须要做的任务,以确保应用能够顺利进行后续的逻辑处理
输出解析器 Output Parser就是一个帮助结构化语言模型响应的抽象,可以获取格式指令或者进行更深层次的解析

5. 大模型接入

Langchain对大模型的支持:
https://python.langchain.com/docs/integrations/llms/

为了使开发者可以轻松地创建自定义链,整体采用Runnable协议。Runnable 协议是编程中一种常见的设计模式,用于定义可以执行的任务或行为。在LangChain中通过构建标准接口,可以用户轻松定义自定义链并以标准方式调用它们,目前在LangChain已经集成的LLMs中,均实现了Runnable接口,目前支持包括invokestreambatchastream 等方法的调用。

invoke:处理单条输入
batch:处理批量输入
stream:流式响应
ainvoke:异步处理单条输入
abatch:异步处理批量输入
astream:异步流式响应

5.1 通义千问

在阿里云开通在线模型API:https://dashscope.console.aliyun.com/overview

pip install dashscope

在环境变量中添加通义千问的api-key,变量名:DASHSCOPE_API_KEY

1
2
3
4
5
from langchain_community.llms import Tongyi  

llm = Tongyi()
result = llm.invoke("你是谁")
print(result)

[!Success] Print
我是来自阿里云的大规模语言模型,我叫通义千问。

stream流式响应

1
2
3
4
5
from langchain_community.llms import Tongyi  

llm = Tongyi()
for chunk in llm.stream("你是谁"):
print(chunk, end="", flush=True)

batch批量处理

1
2
3
4
5
from langchain_community.llms import Tongyi  

llm = Tongyi()
result = llm.batch(["你是谁", "什么是大模型"])
print(result)

[!Success] Print
[‘我是来自阿里云的大规模语言模型,我叫通义千问。’, ‘大模型是指一类参数量非常庞大的机器学习模型,通常是指预训练的深度学习模型。这些模型拥有数十亿甚至上千亿个参数,能够学习到更为复杂和泛化的语言结构和模式。大模型在自然语言处理(NLP)、计算机视觉(CV)等领域有广泛的应用,如对话系统、文本生成、翻译、问答等。\n\n例如,我就是阿里云推出的一种超大规模语言模型“通义千问”,拥有非常强大的语言理解和生成能力,可以回答各种领域的问题,提供各种帮助。其他知名的大模型还包括Google的BERT、T5,Facebook的XLM-R,以及中国的悟空、文心一言等。这些模型通过在大量无标注的文本数据上进行预训练,然后可以通过微调来适应特定的任务,从而达到优秀的性能。’]

ainvoke异步请求
pip install asyncio

1
2
3
4
5
6
7
8
9
10
11
12
13
import asyncio  

from langchain_community.llms import Tongyi

llm = Tongyi()


async def ainvoke_llm():
result = await llm.ainvoke("你是谁")
print(result)


asyncio.run(ainvoke_llm())

参数说明

  • model:必选参数,具体调用的Completions模型名称,可以调用的模型包括text-davinci-003、text-davinci-002、text-curie-001、text-babbage-001、text-ada-001等,不同模型参数规模不同;这里需要注意,大模型领域不同于机器学习领域,后者哪怕是简单模型在某些场景下可能也会拥有比复杂模型更好的表现。在大模型领域,(就OpenAI提供的A、B、C、D四大模型来看)参数规模越大、越新版本的模型效果更好(当然费用也更高),因此课程中主要以text-davinci-003使用为例进行讲解;
  • prompt:必选参数,提示词;
  • suffix:可选参数,默认为空,具体指模型返回结果的后缀;
  • max_tokens:可选参数,默认为16,代表返回结果的token数量;
  • temperature:可选参数,取值范围为0-2,默认值为1。参数代表采样温度,数值越小,则模型会倾向于选择概率较高的词汇,生成的文本会更加保守;而当temperature值较高时,模型会更多地选择概率较低的词汇,生成的文本会更加多样;
  • top_p:可选参数,取值范围为0-1,默认值为1,和temperature作用类似,用于控制输出文本的随机性,数值越趋近与1,输出文本随机性越强,越趋近于0文本随机性越弱;通常来说若要调节文本随机性,top_p和temperature两个参数选择一个进行调整即可;这里更推荐使用temperature参数进行文本随机性调整;
  • n:可选参数,默认值为1,表示一个提示返回几个Completion;
  • stream:可选参数,默认值为False,表示回复响应的方式,当为False时,模型会等待返回结果全部生成后一次性返回全部结果,而为True时,则会逐个字进行返回;
  • logprobs:可选参数,默认为null,该参数用于指定模型返回前N个概率最高的token及其对数概率。例如,如果logprobs设为10,那么对于生成的每个token,API会返回模型预测的前10个token及其对数概率;
  • echo:可选参数,默认为False,该参数用于控制模型是否应该简单地复述用户的输入。如果设为True,模型的响应会尽可能地复述用户的输入;
  • stop:可选参数,默认为null,该参数接受一个或多个字符串,用于指定生成文本的停止信号。当模型生成的文本遇到这些字符串中的任何一个时,会立即停止生成。这可以用来控制模型的输出长度或格式;
  • presence_penalty:可选参数,默认为0,取值范围为[-2, 2],该参数用于调整模型生成新内容(例如新的概念或主题)的倾向性。较高的值会使模型更倾向于生成新内容,而较低的值则会使模型更倾向于坚持已有的内容,当返回结果篇幅较大并且存在前后主题重复时,可以提高该参数的取值;
  • frequency_penalty:可选参数,默认为0,取值范围为[-2, 2],该参数用于调整模型重复自身的倾向性。较高的值会使模型更倾向于避免重复,而较低的值则会使模型更可能重复自身;当返回结果篇幅较大并且存在前后语言重复时,可以提高该参数的取值;
  • best_of:该参数用于控制模型的生成过程。它会让模型进行多次尝试(例如,生成5个不同的响应),然后选择这些响应中得分最高的一个;
  • logit_bias:该参数接受一个字典,用于调整特定token的概率。字典的键是token的ID,值是应用于该token的对数概率的偏置;在GPT中我们可以使用tokenizer tool查看文本Token的标记。一般不建议修改;
  • user:可选参数,使用用户的身份标记,可以通过人为设置标记,来注明当前使用者身份。需要注意的是,Completion.create函数中的user和后续介绍的对话类模型的user参数含义并不相同,需要注意区分;

6. Chat Models

LangChain抽象出来的消息类型有 AIMessage 、 HumanMessage 、 SystemMessage 、 FunctionMessage 和 ChatMessage

  • SystemMessage :用于启动 AI 行为,作为输入消息序列中的第一个传入。
  • HumanMessage :表示来自与聊天模型交互的用户消息。
  • AIMessage :表示来自聊天模型的消息。这可以是文本,也可以是调用工具的请求
    1
    2
    3
    4
    5
    6
    7
    from langchain_community.llms import Tongyi  
    from langchain_core.messages import SystemMessage, HumanMessage

    message = [SystemMessage("是你是一个企业内的智能小助手"), HumanMessage("你是谁")]
    llm = Tongyi()
    result = llm.invoke(message)
    print(result)

    [!Success] Print
    我是您的智能小助手,可以在企业内部提供各种信息查询、问题解答、流程引导等服务。我可以帮助员工更高效地工作,解答他们关于公司政策、项目进度、内部系统使用等问题。请告诉我您需要什么帮助,我会尽力协助您。

7. Prompt Template

提示工程(Prompt Engineering)是指在与大语言模型进行交互时,精心设计输入文本(即提示)的过程,以获得更精准、相关或有创造性的输出,通过采用Few-Shot、Chain of Thought (CoT)等高级提示技巧,可以显著提高大模型在推理任务上的表现
LangChain 提供了创建和使用提示模板的工具,使用PromptTemplate 方法创建字符串提示模板,目前语言模型接收的提示基本都是字符串或聊天消息列表。

1
2
3
4
5
6
7
8
from langchain_community.llms.tongyi import Tongyi  
from langchain_core.prompts import PromptTemplate

prompt_template = PromptTemplate.from_template("请给我一个关于{topic}的{type}解释。")
prompt = prompt_template.format(type="通俗易懂的", topic="薛定谔的猫")
llm = Tongyi()
result = llm.invoke(prompt)
print(result)

[!Success] Print
薛定谔的猫是一个著名的量子力学思想实验,由奥地利物理学家埃尔温·薛定谔在1935年提出,用于揭示量子世界和宏观世界之间的矛盾。这个实验是用来说明量子力学中的超位置态和观测问题的 ……

聊天模板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from langchain_community.llms.tongyi import Tongyi  

from langchain_core.prompts import ChatPromptTemplate

chat_template = ChatPromptTemplate.from_messages(
[("system", "你是公司智能小秘书,你的名字是{name}。"),
("human", "你好,最近怎么样?"),
("ai", "我很好,谢谢!"),
("human", "{user_input}")])
print(chat_template)
messages = chat_template.format_messages(name="小蜜", user_input="你叫什么名字?")
print(messages)
llm = Tongyi()
result = llm.invoke(messages)
print(result)

[!Success] Print
input_variables=[‘name’, ‘user_input’] messages=[SystemMessagePromptTemplate(prompt=PromptTemplate(input_variables=[‘name’], template=’你是公司智能小秘书,你的名字是{name}。’)), HumanMessagePromptTemplate(prompt=PromptTemplate(input_variables=[], template=’你好,最近怎么样?’)), AIMessagePromptTemplate(prompt=PromptTemplate(input_variables=[], template=’我很好,谢谢!’)), HumanMessagePromptTemplate(prompt=PromptTemplate(input_variables=[‘user_input’], template=’{user_input}’))]

[SystemMessage(content=’你是公司智能小秘书,你的名字是小蜜。’), HumanMessage(content=’你好,最近怎么样?’), AIMessage(content=’我很好,谢谢!’), HumanMessage(content=’你叫什么名字?’)]

我叫小蜜,是您的智能小秘书。有什么可以帮到您的吗?

input_variables:这是一个列表,包含模板中需要动态填充的变量名。这些变量名在模板字符串中以花括号(如{name})标记。通过指定这些变量,可以在后续过程中动态地替换这些占位符。
template:这是定义具体提示文本的模板字符串。它可以包含静态文本和input_variables列表中指定的变量占位符。当调用format方法时,这些占位符会被实际的变量值替换,生成最终的提示文本。

8. 构造Few-Shot模版

通过输入一些类似问题和答案,让模型参考学习,并在同一个prompt的末尾提出新的问题,以此来提升模型的推理能力
在LangChain中,需要使用 PromptTemplate 创建字符串提示模板。模板可以包括说明、少量示例以及适合给定任务的特定上下文和问题。因此,需要创建一个少量示例的列表。每个示例都是一个字典,其中键是输入变量,值是这些输入变量的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
from langchain_community.llms.tongyi import Tongyi  
from langchain_core.prompts import PromptTemplate, FewShotPromptTemplate

examples = [
{
"question": "罗杰有五个网球,他又买了两盒网球,每盒有3个网球,请问他现在总共有多少个网球?",
"answer": "罗杰一开始有五个网球,又购买了两盒网球,每盒3个,共购买了6个网球,因此现在总共由5+6=11个网球。因此答案是11。"
},
{
"question": "食堂总共有23个苹果,如果他们用掉20个苹果,然后又买了6个苹果,请问现在食堂总共有多少个苹果?",
"answer": "食堂最初有23个苹果,用掉20个,然后又买了6个,总共有23-20+6=9个苹果,答案是9。"
},
{
"question": "杂耍者可以杂耍16个球。其中一半的球是高尔夫球,其中一半的高尔夫球是蓝色的。请问总共有多少个蓝色高尔夫球?",
"answer": "总共有16个球,其中一半是高尔夫球,也就是8个,其中一半是蓝色的,也就是4个,答案是4个。"
},
]
example_prompt = PromptTemplate(
input_variables=["question", "answer"], template="Question: {question}\n{answer}"
)
few_shot_prompt = FewShotPromptTemplate(
examples=examples,
example_prompt=example_prompt,
suffix="Question: {input}", # 后缀模板,其中 {input} 会被替换为实际输入
input_variables=["input"] # 定义输入变量的列表
)
question = '艾米需要4分钟才能爬到滑梯顶部,她花了1分钟才滑下来,水滑梯将在15分钟后关闭,请问在关闭之前她能滑多少次?'
prompt_format = few_shot_prompt.format(input=question)
llm = Tongyi()
result = llm.invoke(prompt_format)
print(result)

[!Success] Print
艾米每次玩滑梯(包括爬上滑梯和滑下来)需要4 + 1 = 5分钟。如果水滑梯在15分钟后关闭,那么在关闭前她有15分钟的时间。所以她能滑的次数是15分钟除以5分钟每次,即15 / 5 = 3次。所以答案是3次。

9. Output Parsers

输出解析器,负责获取大模型的输出并将其转换为更合适的格式
LangChain采取的策略是通过在Prompt上增加输出解析,直接引导大模型输出的格式化方式。
LangChain构造的输出解释器必须实现两个主要方法:

日期解析器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from langchain.output_parsers import DatetimeOutputParser  
from langchain_community.llms.tongyi import Tongyi

from langchain_core.prompts import PromptTemplate

output_parser = DatetimeOutputParser()
template = """用户发起的提问:
{question}
{format_instructions}"""

prompt = PromptTemplate.from_template(template, # 预定义的变量,这里我们传入格式化指令
partial_variables={
"format_instructions": output_parser.get_format_instructions()})

chain = prompt | Tongyi() | output_parser

output = chain.invoke({"question": "长征一号是哪天发射的"})
print(output)

[!Success] Print
1970-04-24 00:00:00

10. 示例选择器

Prompt Template存在的问题:不同类型的问题的提示模板不一样,应该如何在众多的提示词模板中选择适合当前输入问题的提示模板?
LangChain解决方案:抽象出一个Example selectors模块,通过技术手段灵活的选择合适的提示模版,LangChain内置了多个预定义的示例选择器,每种选择器都有其特定的功能和适用场景

10.1 SemanticSimilarityExampleSelector

这个选择器的目的是在给定的示例集合中选出与输入在语义上最接近的示例。主要的实现步骤如下:

  1. 向量化表示:首先,输入文本和示例集中的每个示例都会被转换成向量化的表示。通过Embedding模型将文本转换成高维空间中的点,其中语义上相似的文本会被映射到空间中相近的位置。
  2. 计算语义相似度:一旦得到了输入和示例的向量化表示,下一步是计算输入与每个示例之间的语义相似度。通过计算向量之间的距离来实现,常见的度量方式包括余弦相似度、欧氏距离等。
  3. 选择最相似的示例:基于计算出的相似度,选择一个或多个与输入最相似的示例。这个选择过程可以是简单地选取相似度最高的示例,或者根据相似度分布采取更复杂的策略,例如选择相似度高于某个阈值的所有示例。

SemanticSimilarityExampleSelector内部实现是使用chromadb向量数据库进行存储和检索向量数据
pip install chromadb

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
from langchain_community.embeddings.dashscope import DashScopeEmbeddings  
from langchain_community.llms.tongyi import Tongyi
from langchain_community.vectorstores.chroma import Chroma
from langchain_core.example_selectors import SemanticSimilarityExampleSelector
from langchain_core.prompts import FewShotChatMessagePromptTemplate, ChatPromptTemplate

examples = [
# 数学推理
{
"question": "小明的妈妈给了他10块钱去买文具,如果一支笔3块钱,小明最多能买几支笔?",
"answer": "小明有10块钱,每支笔3块钱,所以他最多能买3支笔,因为3*3=9,剩下1块钱不够再买一支笔。因此答案是3支。"
},
{
"question": "一个篮球队有12名球员,如果教练想分成两个小组进行训练,每组需要有多少人?",
"answer": "篮球队总共有12名球员,分成两个小组,每组有12/2=6名球员。因此每组需要有6人。"
},
# 逻辑推理
{
"question": "如果所有的猫都怕水,而Tom是一只猫,请问Tom怕水吗?",
"answer": "根据题意,所有的猫都怕水,因此作为一只猫的Tom也会怕水。所以答案是肯定的,Tom怕水。"
},
{
"question": "在夏天,如果白天温度高于30度,夜晚就会很凉爽。今天白天温度是32度,请问今晚会凉爽吗?",
"answer": "根据题意,只要白天温度高于30度,夜晚就会很凉爽。今天白天的温度是32度,超过了30度,因此今晚会凉爽。"
},
# 常识问题
{
"question": "地球绕太阳转一圈需要多久?",
"answer": "地球绕太阳转一圈大约需要365天,也就是一年的时间。"
},
{
"question": "水的沸点是多少摄氏度?",
"answer": "水的沸点是100摄氏度。"
},
# 文化常识
{
"question": "中国的首都是哪里?",
"answer": "中国的首都是北京。"
},
{
"question": "世界上最长的河流是哪一条?",
"answer": "世界上最长的河流是尼罗河。"
},
]

to_vectorize = [" ".join(example.values()) for example in examples]
embeddings = DashScopeEmbeddings()
vectorstore = Chroma.from_texts(to_vectorize, embeddings, metadatas=examples)
example_selector = SemanticSimilarityExampleSelector(
vectorstore=vectorstore,
k=2
)
select_examples = example_selector.select_examples({"input": "月亮每天什么时候出现"})
print(select_examples)

few_shot_prompt = FewShotChatMessagePromptTemplate(
input_variables=["input"], # 定义输入变量的列表
example_selector=example_selector, # 使用动态的示例选择器

# 定义每一轮对话的格式化文本
example_prompt=ChatPromptTemplate.from_messages(
[("human", "{question}"), ("ai", "{answer}")]
),
)

print(few_shot_prompt.format(input="月亮每天什么时候出现"))

final_prompt = ChatPromptTemplate.from_messages(
[
("system", "你是一个无所不能的人,无论什么问题都可以回答。"),
few_shot_prompt,
("human", "{input}"),
]
)

chain = final_prompt | Tongyi()
result = chain.invoke({"input": "月亮每天什么时候出现"})
print(result)

[!Success] Print
[{‘answer’: ‘根据题意,只要白天温度高于30度,夜晚就会很凉爽。今天白天的温度是32度,超过了30度,因此今晚会凉爽。’, ‘question’: ‘在夏天,如果白天温度高于30度,夜晚就会很凉爽。今天白天温度是32度,请问今晚会凉爽吗?’}, {‘answer’: ‘地球绕太阳转一圈大约需要365天,也就是一年的时间。’, ‘question’: ‘地球绕太阳转一圈需要多久?’}]

Human: 在夏天,如果白天温度高于30度,夜晚就会很凉爽。今天白天温度是32度,请问今晚会凉爽吗?
AI: 根据题意,只要白天温度高于30度,夜晚就会很凉爽。今天白天的温度是32度,超过了30度,因此今晚会凉爽。
Human: 地球绕太阳转一圈需要多久?
AI: 地球绕太阳转一圈大约需要365天,也就是一年的时间。

月亮每天出现的时间会有所不同,因为它在天空中的位置会随着地球的自转而改变。一般来说,在没有云层遮挡的情况下,月亮会在日落后升起,并在日出前落下。具体的升落时间会受到地理位置、季节和月相的影响。在满月时,月亮整夜可见;而在新月时,月亮则不会在天空中出现。如果你需要知道特定日期和地点的月亮升起和落下时间,可以使用天文软件或在线工具进行查询。

10.2 自定义示例选择器

在LangChain中,Example Selector的基本接口定义如下:

1
2
3
4
5
6
7
8
9
10
class BaseExampleSelector(ABC):  
"""用于选择要包含在提示中的示例的接口。"""

@abstractmethod
def add_example(self, example: Dict[str, str]) -> Any:
"""向存储中添加新的示例。"""

@abstractmethod
def select_examples(self, input_variables: Dict[str, str]) -> List[dict]:
"""根据输入选择使用哪些示例。"""

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
from langchain_core.example_selectors import BaseExampleSelector

class ChatbotExampleSelector(BaseExampleSelector):
def __init__(self, examples):
# examples是一个列表,包含多个字典,每个字典都有'input'和'output'键
self.examples = examples

def add_example(self, example):
# 向examples列表添加一个输入-输出对
self.examples.append(example)

def select_examples(self, input_variables):
# 此方法找到与用户输入长度最接近的示例,并返回相应的输出
new_word = input_variables["input"]
new_word_length = len(new_word)

best_match = None
smallest_diff = float("inf")

for example in self.examples:
current_diff = abs(len(example["input"]) - new_word_length)

if current_diff < smallest_diff:
smallest_diff = current_diff
best_match = example

# 如果找到了最佳匹配项,返回相应的输出;否则,返回None
return [best_match] if best_match else []