当前位置:   article > 正文

# OpenAI开发系列(十):Chat Completion Models API详解与构建本地知识库问答系统实践_openai.chatcompletion.create

openai.chatcompletion.create

授权声明: 本文基于九天Hector的原创课程资料创作,已获得其正式授权。
原课程出处:九天Hector的B站主页,感谢九天Hector为学习者带来的宝贵知识。
请尊重原创,转载或引用时,请标明来源。

全文共8000余字,预计阅读时间约18~28分钟 | 满满干货(附代码案例),建议收藏!

本文目标:详解Chat Completion Models的参数及应用实例,并基于该API实践如何构建本地知识库的问答系统

image-20230821163850240

代码&文件下载点这里

一、介绍

在OpenAI大模型生态中的文本模型包括了Completion模型和Chat模型,如果您还不清楚相关内容的话,强烈建议先阅读这两篇文章再学习本文

OpenAI开发系列(三):OpenAI的大模型生态介绍

OpenAI开发系列(六):Completions模型的工作原理及应用实例(开发多轮对话机器人)

本文介绍Chat类模型及其API使用方法,在OpenAI官网中可以看到模型列表如下:

image-20230722105356566

从模型的发展顺序上来看,Chat模型是Completion模型的升级版。在官网中是这样介绍的:

image-20230821085447728

各模型的优化关系是这样的:

1

二、Chat Completion Models与Completions Models的关系

Completion模型核心功能是根据提示(prompt)进⾏提示语句的补全(即继续进行后续⽂本创作),它本质上是文本补全模型。而Chat模型升级的核心功能是对话, 它基于大量高质量对话文本进行微调,能够更好的理解用户对话意图,所以它能更顺利的完成与用户的对话。

大语言模型本质上都是概率模型,根据前文提示进行补全是⼤语⾔模型的原始功能,而对话类的功能则是加⼊额外数据集之后训练的结果

当Completions Models和Chat Completions 接收不完整信息时,推理差异也是比较明显的,它们调用的方式分别如下:

# 获取ChatCompletion模型的返回结果
chat_completions_res = openai.ChatCompletion.create(
    model = "gpt-3.5-turbo-16k-0613",
    messages = [
     {"role": "user", "content":"你好呀,请问我"}   
    ]
)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
# 获取Completion模型的返回结果
completions_res = openai.Completion.create(
    model = "text-davinci-003",
    prompt = "你好呀,请问我",
    max_tokens = 1000
)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

看一下输入相同的一句话:“你好呀,请问我”,两个模型返回的结果对比:

# 打印两个模型的返回结果对比
print("ChatCompletion Model Response:\n", chat_completions_res.choices[0].message["content"])
print("\nCompletion Model Response:\n", completions_res["choices"][0]["text"].strip())
  • 1
  • 2
  • 3

image-20230821093340787

其实可以能看出来,Chat模型能够比较好的理解对话意图,跟接近于人类对话的常规方式,而Completions模型则是根据概率进行对话补全。

Chat模型的核心优势也就体现出来了: 它能更好的理解人类意图。与大语⾔模型(LLMs)交互最低⻔槛的形式就是对话,如果模型能够非常好的理解人类对话意图,才会更有利于模型的发展和社会价值的认同,这就是为什么gpt-3.5和davinci模型同属于⼀代模型,但基于gpt-3.5的 ChatGPT⼀炮而红,而早两年推出的davinci模型却远不及gpt-3.5影响力大。

据OpenAI官网数据,自gpt-3.5 API发布以来,约97%的开发者更偏向于使用Chat模型API进行开发

三、Chat Completion Models发展历程

Chat Completion Models 发展是非常迅速的,截止到目前2023年7月22日,OpenAI的官方数据如下:

  • 2023年3月1日,gpt-3.5-turbo API正式发布
  • 2023年3月14日,gpt-4 API正式发布,但是需要申请使用

在这两个里程碑后,也在不断的更新迭代,可用性不断的提升,经费也在不断的下降,如下图:

3

  • 2023年6月13日:OpenAI宣布在Chat Completion模型中加入函数调用(Function calling)功能,全面开放16K对话长度的模型、降低模型调用资费等,这代表着Chat模型不再需要借助LangChain框架就可以直接在模型内部调用外部工具API,可以更加便捷的构建以LLM为核心的AI应用程序。

  • 2023年7月6日:真正的王炸更新,全面开放gpt-4 API,同时重点强调了原先的编程模型(code)模型的功能将合并入chat模型,同时,未来将更新Chat模型的fine - tunes API。

四、 Chat Completions Model API基本情况

截至目前,OpenAI发布的Chat Completions模型主要包括gpt-3.5和gpt-4两类模型,这两个模型也是目前ChatGPT应用程序背后的对话大模型。根据官网给出的说明,gpt-3.5模型是基于text-davinci-003微调的模型,由code-davinci-002这一基座模型经过几轮微调后训练得到,对应的模型微调关系如下所示:

1

gpt-4则是完全重新训练的最新一代的对话类大模型,在诸多国内外大模型评测榜单上,gpt-4也是目前(多语种)对话效果最好的一类对话类大模型。

OpenAI发布的Chat Completions模型如下:

image-20230722121114581

但目前为止,Chat Completions模型并未开放全部的API,大多数模型的长文本对话模型(即标注为32k的模型),也需要填写申请方可使用,填写地址:申请地址

4.1 调用示例

与调用Completion模型需要使用Completion.create函数类似,若要调用Chat类大模型,需要使用ChatCompletion.create函数。调用代码如下:

response = openai.ChatCompletion.create(
  model="gpt-3.5-turbo-16k-0613",
  messages=[
    {"role": "user", "content": "什么是机器学习?"}
  ]
)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

和Completion.create非常明显的一个区别在于,ChatCompletion.create函数的调用不再需要prompt参数,而是换成了messages参数,并且不同于prompt参数对象是以简单的字符串形式呈现,messages参数是一个基本构成元素为字典的列表,其内每个字典都代表一条独立的消息,其中每个字典都包含两个键值(Key-value)对,其中第一个Key都是字符串role(角色)表示某条消息的作者,第二个key为content(内容)表示消息具体内容。

看下大模型返回的数据结构:

image-20230821094919689

messages参数是ChatCompletion.create函数最重要的参数之一,它可以简单理解为输入给模型的信息,模型接收到message之后也会输出对应的回答信息,代码如下:

# 查看结果
response.choices[0].message['content']
  • 1
  • 2

模型推理结果如下:

image-20230821095103783

需要关注的一点是每次对话占用的Token,毕竟这玩意是花钱的。和Completion模型一样,Chat模型同样也可以在返回结果的usage中查看本次对话所占用的token数量,各参数如下:

image-20230821095640190

4.2 参数详解

ChatCompletion.create函数的详细参数解释可以从官网 清楚的了解到。和Completion.create函数相比,ChatCompletion.create函数的参数结构发生了以下变化:

  • 用messages参数代替了prompt参数,使之更适合能够执行对话类任务
  • 新增functions和function_call参数,使之能够在函数内部调用其他工具的API
  • 其他核心参数完全一致,例如temperature、top_p、max_tokens、n、presence_penalty等参数的解释和使用方法都完全一致,且这些参数具体的调整策略也完全一致.
  • 剔除了best_of参数,即Chat模型不再支持从多个答案中选择一个最好的答案这一功能

各参数具体情况如下图:

5

五、messages参数详解

5.1 参数结构

messages 是一个专门为描述ChatCompletion模型与用户之间互动信息所设计的参数。它是一个列表,每个元素代表一次交互的消息。具体来说,每个元素都是一个字典,这个字典里有两个关键字段。第一个字段是 'role',这个字段的值是一个字符串,用于标明这条消息的发送者身份,比如是模型还是用户。第二个字段是 'content',其值也是一个字符串,表示这条消息的实际文本内容。所以,通过这个messages参数,能清晰地追踪模型与用户之间的每一次交互,包括谁发送的消息以及消息的内容是什么。

例如下面的代码指代:一名角色定位为”user”的用户,向大模型发送了一条消息,用来提问:“什么是大语言模型?”

image-20230821100408059

返回的message结果也是一个“字典”,包含了信息的发送方和具体信息内容

image-20230821100607971

即回复方的角色定位为’assistant’,具体内容则是一段关于什么是大语言模型的描述。

image-20230821100707142

可以这么理解:Chat模型的每个对话任务都是通过输入和输出message来完成的。

5.2 message中的角色划分

5.2.1 user role和assistant role

一个最简单的对话就是扮演user(用户)这个角色(‘role’:‘user’),然后在content中输入问题并等待模型回答。当输入的用户是“user”时,模型在推理过程中,会扮演assistant(助手)这个角色(‘role’:‘assistant’)完成回复,即如果一条信息的role是user,则表明这是用户向模型发送的聊天信息,相当于是Completion模型中的prompt,而如果一条信息的role是assistant,则表示这是当前模型围绕某条用户信息做出的回应,相当于是Completion模型中的text

基于这样的一个定义的规则,最简单的Chat模型的调用方法就是在messages参数中设置一条role为user的参数,也就是5.1节中的那个示例。

# 发送提问
response = openai.ChatCompletion.create(
  model="gpt-3.5-turbo-16k-0613",
  messages=[
    {"role": "user", "content": "什么是大语言模型?"}
  ]
)

# 获得模型推理结果
response.choices[0].message['content']
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

其返回的推理内容如下:

image-20230821101150852

有一点需要注意:Messages可以包含多条信息,但模型只会对于最后一条用户信息进行回答,代码如下:

response = openai.ChatCompletion.create(
  model="gpt-3.5-turbo-16k-0613",
  messages=[
    {"role": "user", "content": "什么是大语言模型?"},
    {"role": "user", "content": "什么是深度学习?"}
  ]
)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

看下模型的推理结果:

response.choices[0].message['content']
  • 1

image-20230821101414314

所以,assistant消息和role消息是一一对应的,在一般情况下,assistant消息只会围绕messages参数中的最后一个role信息进行回答。

5.2.2 system role

直接提问虽然能获得明确答案,但方式较为单调。很多研究者进行相关实验得出结论:如果为大语言模型赋予特定的身份设定,是一个帮助模型更准确地回应预期输出非常有效的策略。这一点可以通过参数system role来调整。

比如,如果你希望对“什么是大语言模型?”这个问题得到更具深度和专业性的回答“,一个非常有效的方法是:让模型扮演一个“大语言模型专家”的角色。对比代码如下:

  • 使用role = user,同时在content中添加专家背景信息
response = openai.ChatCompletion.create(
  model="gpt-3.5-turbo-16k-0613",
  messages=[
    {"role": "user", "content": "你是一名资深的大语言模型领域的专家,精通模型架构原理和落地应用实践,请你基于你丰富的经验,帮我解答一下:什么是大语言模型?"}
  ]
)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

看下模型给出的推理:

response.choices[0].message['content']
  • 1

image-20230821102933904

  • 使用system role 身份设定
response = openai.ChatCompletion.create(
  model="gpt-3.5-turbo-16k-0613",
  messages=[
    {"role": "system", "content": "你是一名资深的大语言模型领域的专家,精通模型架构原理和落地应用实践"},
    {"role": "user", "content": "什么是大语言模型?"}
  ]
)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

看下大模型的推理结果:

response.choices[0].message['content']
  • 1

image-20230821104752918

在输入提问内容之前,新增消息{“role”: “system”, “content”: “你是一名资深的大语言模型领域的专家,精通模型架构原理和落地应用实践”},能起到设定模型身份的作用。system是messages参数的role可以选取的第三个字符串,意为该消息为一条系统消息。

相比直接在用户消息中添加角色背景信息,其实从两个示例的回复来看,都能对模型的推理效果达到比较高效的提升,但我建议使用system设置角色背景,原因如下:

  1. 系统消息的实际作用是给整个对话系统进行背景设置,不同的背景设置会极大程度影响后续对话过程中模型的输出结果,它会奠定整个对话的基调,在每一个用户消息中都添加背景信息,一定不是一种比较好的编写思路
  2. 当进行对轮的对话任务,开头设置System role 也会让整体代码具备很强的逻辑感和层次感

当设置不同system role时,不同的角色背景会对输出产生非常大的影响,比如让一个脱口秀演员来解释大语言模型,代码如下:

response = openai.ChatCompletion.create(
  model="gpt-3.5-turbo-16k-0613",
  messages=[
    {"role": "system", "content": "你是一个搞笑的脱口秀演员"},
    {"role": "user", "content": "什么是大语言模型?"}
  ]
)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

看下推理结果:

response.choices[0].message['content']
  • 1

image-20230821105951319

很明显能看出来风格的转变是非常大的。

当messages中只包含一条system消息时,系统会围绕system进行回答,此时大模型的回复类似于completion的过程,即围绕system的prompt进行进一步的文本补全:

response = openai.ChatCompletion.create(
  model="gpt-3.5-turbo-16k-0613",
  messages=[
    {"role": "system", "content": "你是一名资深的大语言模型领域的专家,精通模型架构原理和落地应用实践"},
  ]
)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

看下推理结果:

response.choices[0].message['content']
  • 1

image-20230821110258013

  • 角色定义的顺序

需要根据system系统信息对系统进行设置,然后再提问,那么先system消息再user消息的顺序是非常重要的,例如还是上面的例子,如果希望以脱口秀演员的身份介绍大语言模型的相关概念,但如果调换了system消息和user消息的顺序,system消息的作用就会失效,代码如下:

response = openai.ChatCompletion.create(
  model="gpt-3.5-turbo-16k-0613",
  messages=[
    {"role": "user", "content": "什么是大语言模型?"},
    {"role": "system", "content": "你是一个搞笑的脱口秀演员"}
  ]
)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

看下推理结果:

response.choices[0].message['content']
  • 1

image-20230821110701659

模型能解答“什么是大语言模型?”这个问题,但却没有正确接受“你是一个搞笑的脱口秀演员”这个设定。

六、message参数应用实例

对于ChatCompletion.create函数,利用灵活的messages参数可以轻松满足多种对话需求,如基于提示词的提问、Few-shot提问方式,以及依据特定背景知识的提问。在OpenAI开发系列(八):基于思维链(CoT)的进阶提示工程文章中提出了四个经典的推理题,本文就以这四个问题作为示例,看一下如何用用在Chat模型下如何进行推理。

先提前定义好四组问题的问题和答案:

Q1 = '罗杰有五个网球,他又买了两盒网球,每盒有3个网球,请问他现在总共有多少个网球?'
A1 = '现在罗杰总共有11个网球。'

Q2 = '食堂总共有23个苹果,如果用掉20个苹果,然后又买了6个苹果,请问现在食堂总共有多少个苹果?'
A2 = '现在食堂总共有9个苹果。'

Q3 = '杂耍者可以杂耍16个球。一半的球是高尔夫球,一半的高尔夫球是蓝色的。请问总共有多少个蓝色高尔夫球?'
A3 = '现在总共有4个蓝色高尔夫球。'

Q4 = '艾米需要4分钟能爬到滑梯顶部,然后需要花费1分钟滑下来,现在水滑梯将在15分钟后关闭,请问在关闭之前她能滑多少次?'
A4 = '水滑梯关闭之前艾米能滑3次。'
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

以下结论已经过本地测试:

  1. gpt-3.5是基于对话语料进行的微调,它具有比text-davinci-003更强大的推理能力,四个推理问题对于gpt-3.5来说,除了最后一个问题不一定能得出正确答案外,其他问题均能在不进行额外提示的情况下进行很好的回答。

  2. gpt-4模型能够非常好的回答第四个推理问题。但这并不代表此前的CoT和LtM技术就不再重要,面对超出模型原生能力的更加复杂的推理问题(如SCAN数据集的命令解释问题),仍然还是需要使用这些提示工程技术。

6.1 借助多轮user-assistant消息进行few-shot

  • 使用Zero-shot提示法

Zero-shot简单理解就是:不给大模型任何示例,直接进行提问,测试一下对Q3直接进行提问是否能得到正确答案(Q1、Q2相对来说比较简单),代码如下:

response = openai.ChatCompletion.create(
  model="gpt-3.5-turbo-16k-0613",
  messages=[
    {"role": "user", "content": Q3}
  ]
)

response.choices[0].message['content']
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

看下大模型推理结果:

image-20230821112234830

  • 技巧:使用Few-Shot提示法来格式化输出

虽然Zero-shot提示法能够进行正确的推理,但在实际应用中,常常需要的是标准化或格式化的输出。大语言模型作为概率生成模型,若不进行干预,其每次的回复很可能各不相同。因此,Few-shot提示法常被用来规范输出。

要在Chat模型中实现Few-shot提示,一个有效的策略是在messages中配置多轮user-assistant交互。具体来说,可以使用第一个、第二个问题及其对应答案作为提示示例,以引导模型回答随后的问题。按此方法,可以如下配置messages

response = openai.ChatCompletion.create(
  model="gpt-3.5-turbo-16k-0613",
  messages=[
    {"role": "user", "content": Q1},
    {"role": "assistant", "content": A1},
    {"role": "user", "content": Q2},
    {"role": "assistant", "content": A2},
    {"role": "user", "content": Q3}
  ]
)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

看下大模型推理结果:

response.choices[0].message['content']
  • 1

image-20230821135236597

可以从回复上看出:Q1的答案A1 = ‘现在罗杰总共有11个网球’,Q2的答案A2 = ‘现在食堂总共有9个苹果’,通过Few-Shot提示,模型的推理回复基本上符合标准化的输出格式。

6.2 借助system role进行Few-shot

一个比较关键的点就是:到底模型能不能稳定的输出我们想要的格式,因为对于一个自动化开发的AI程序来说,当前过程的输出会作为下一过程的输入,所以不仅要保证模型推理的准确性,也要限制住模型回复的标准性。

根据OpenAI官方给出的提示:assistant消息是可以自定义的,用来给模型提供回答的示例,所以两种比较好的解决思路是:

  1. 按照user-assistant-user-assistant…形式来进行Few-Shot
  2. 按照system-user的形式规定回答风格

先尝试使用第一种方式:把提示示例写进一条system信息中,作为当前问答的背景信息。

  • Step 1:拼接System role的content
print('Q: ' + Q1 + 'A: ' + A1, 'Q: ' + Q2 + 'A: ' + A2)
  • 1

看一下拼接结果:

image-20230821140557195

  • Step 2:作为提示示例,送入大模型进行推理
response = openai.ChatCompletion.create(
  model="gpt-3.5-turbo",
  messages=[
    {"role": "system", "content": 'Q: ' + Q1 + 'A: ' + A1 + 'Q: ' + Q2 + 'A: ' + A2},
    {"role": "user", "content": 'Q: ' + Q3 }
  ]
)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

看下大模型的推理结果:

response.choices[0].message['content']
  • 1

image-20230821141458452

6.3 借助system role输入提示模板

在Completion模型的推理测试中,想让其正确的推理出第四个问题:

Q4 = '艾米需要4分钟能爬到滑梯顶部,然后需要花费1分钟滑下来,现在水滑梯将在15分钟后关闭,请问在关闭之前她能滑多少次?'
A4 = '水滑梯关闭之前艾米能滑3次。'
  • 1
  • 2

是需要用到LtM提示法才能做到较高准确率的推理,在Chat模型下,我用Few-Shot做了测试:

response = openai.ChatCompletion.create(
  model="gpt-3.5-turbo",
  messages=[
    {"role": "system", "content": 'Q: ' + Q1 + 'A: ' + A1 + 'Q: ' + Q2 + 'A: ' + A2 + 'Q: ' + Q3 + 'A: ' + A3},
    {"role": "user", "content": 'Q: ' + Q4 }
  ]
)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

测试了5次,全错。所以针对相对复杂的推理,Chat模型 + 定义角色这种形式也并不能直接提升模型的推理能力,并不是说你告诉大模型你是一个什么什么专家,它就能马上答对推理问题,但一种比较好的思路是:因为system role 可以作为背景设定,并对后续问答产生影响,那么是不是可以利用system角色来输入提示模板?

比如围绕第四个推理题,使用Zero-Shot-CoT提示法,可以这样设定:

response = openai.ChatCompletion.create(
  model="gpt-3.5-turbo-16k-0613",
  messages=[
    {"role": "system", "content": '请一步步思考并解决问题'},
    {"role": "user", "content": Q4}
  ]
)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

看下大模型的推理结果:

response.choices[0].message['content']
  • 1

image-20230821142741388

我测试了5次,模型回答对了3次。再更近一步使用LtM提示法,代码如下:

response = openai.ChatCompletion.create(
  model="gpt-3.5-turbo-16k-0613",
  messages=[
    {"role": "system", "content": '为了解决当前这个问题,请列举先要解决的问题,并逐步解决原问题。'},
    {"role": "user", "content": Q4}
  ]
)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

看下大模型返回的推理结果:

image-20230821143232753

**在多次尝试中也发现,gpt-3.5的推导过程复杂且不稳定,使用LtM提示法回答Q4这个最难的推理题时,10次能回答正确3次,直接提问更是一次都回答不对,gpt-4模型在相同提示策略下,会表现出更强大的推理性能。**同样是围绕第四个问题,对gpt-4模型进行CoT和LtM提示结果如下:

  • Zero-Shot-CoT推理代码
response = openai.ChatCompletion.create(
  model="gpt-4-0613",
  messages=[
    {"role": "system", "content": '请一步步思考并解决问题'},
    {"role": "user", "content": Q4}
  ]
)

response.choices[0].message['content']
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

Zero-Shot-CoT推理结果:

image-20230821143514476

  • Zero-Shot-LtM推理代码
response = openai.ChatCompletion.create(
  model="gpt-4-0613",
  messages=[
    {"role": "system", "content": '为了解决当前这个问题,请列举先要解决的问题,并逐步解决原问题。'},
    {"role": "user", "content": Q4}
  ]
)

response.choices[0].message['content']
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

Zero-Shot-LtM推理结果:

image-20230821143758324

除了推理能力外,gpt-4各方面性能都属于目前大模型领域的顶流。

6.4 借助system role实现本地知识库问答

使用system role设定聊天背景信息是一种经常被采用的策略。通过在system消息中提供长文本背景,可以在聊天开始之前为系统提供一个知识基础。随后,在整个聊天过程中,assistant可以根据这段文本为基础提供回答。这实际上是一种便捷地使大语言模型依据特定知识库回答问题的方式

例如,可以在system消息中提供一个虚拟人物“李三多”的简介。接着,在随后的对话中,user和assistant都可以围绕这段特定的背景信息展开互动。

character_background = '算法小陈,男,1995123日出生于内蒙古赤峰市。 \
        2017年从北京某大学计算机系毕业,并获得硕士学位。 \
        毕业后,他在北京的一家国有企业任职了六年,主要负责某垂直领域的算法研究与应用落地'

response = openai.ChatCompletion.create(
  model="gpt-3.5-turbo-16k-0613",
  messages=[
    {"role": "system", "content": character_background},
    {"role": "user", "content": '请问你知道算法小陈吗?'}
  ]
)

response.choices[0].message['content']
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

看下大模型的回复:

response.choices[0].message['content']
  • 1

image-20230821145337044

从之前的回复可见,大语言模型可以学习背景信息并基于此信息进行针对性的回答。这实际上是一种简洁的基于本地知识的问答实现方式。然而,这种方法并不能完全实现高度个性化或处理超大规模的文本知识库,因为使用system role输入的文本仍然受到模型最大输入长度的限制。

一个可行的解决方案思路是:首先,对超大规模的本地文本知识库采用滑动窗口法进行切分,确保每段小文本都不超过模型的Max tokens限制。接着,在Embedding过程中,实时匹配用户的问题与这些短文本段落。将与用户问题相关性最高的文本片段以system消息形式输入模型,然后再获取回答。这样,我们可以高效且准确地实现本地知识库问答。在整个过程中,利用system role来提供背景信息是关键的技术手段。

6.5 借助.append方法进行多轮对话

在这篇文章:OpenAI开发系列(六):Completions模型的工作原理及应用实例(开发多轮对话机器人)的最后提出了一种基于Completion模型实现了多轮对话机器人的思路,这类模型实现上下文记忆的能力是需要将历史问答都拼接为一个字符串并输入到新的prompt中来实现历史消息的输入,Chat模型作为对话类模型,提供了更加便捷的方式来赋予大模型上下文记忆的能力,只需要将模型返回的message消息+用户新的提问message拼接到模型的messages参数中,并再次向模型进行提问,即可非常便捷的实现多轮对话。

message的输入格式是列表,所以一个比较容易想到的方法就是:使用列表的append()方法,不断的加入上下文对话的内容,如:

  • 第一轮对话

单独设置messages参数,将此前的问题+答案进行拼接,代码如下:

character_background = '算法小陈,男,1995123日出生于内蒙古赤峰市。 \
        2017年从北京某大学计算机系毕业,并获得硕士学位。 \
        毕业后,他在北京的一家国有企业任职了六年,主要负责某垂直领域的算法研究与应用落地'

# 初始化messages信息
messages=[
    {"role": "system", "content": character_background},
    {"role": "user", "content": '请问你知道算法小陈吗?'}
  ]

response = openai.ChatCompletion.create(
  model="gpt-3.5-turbo-16k-0613",
  messages=messages
)

# 将第一轮对话,添加到messages中,更新上下文信息
messages.append(response.choices[0].message.to_dict())
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

此时的messages是这样的:

image-20230821151807414

此时messages参数包含了第一轮最开始的问题+问题答案

  • Step 2:第二轮对话

将第二轮对话的提问,添加的messages中,代码如下:

messages.append({'role': 'user', 'content': '请问我刚才的问题是?'})
  • 1

此时的messages是这样的:

image-20230821152052558

接下来再次调用模型,并输入messages作为参数,此时模型将同时结合此前的所有消息,并围绕最后一个user信息进行回答:

response = openai.ChatCompletion.create(
  model="gpt-3.5-turbo-16k-0613",
  messages=messages
)
  • 1
  • 2
  • 3
  • 4

此时推理结果:

image-20230821152132866

相比Completions模型,Chat类模型能够更加便捷的实现多轮对话,所以messages参数的一个重要的应用就是借助append方法来高效实现多轮对话。

七、实操:构建一个本地知识库问答系统

利用Chat模型进行多轮对话,若要构建一个本地知识库问答系统,一个直观且易于实施的策略是:首先让大模型浏览本地知识库内容,并将其设定为System role的知识背景。有了这个背景,模型就能进行基于此知识库的问答。可以这样做:

  • Step 1:选定本地知识库

本文详细讲解了Chat Completion模型的API参数及调用方法,作为测试,就可以把本文的内容全部传给大模型给它赋予这方面的知识

  • Step 2:测试大模型的知识库

gpt系列模型的知识只更新到2021年9月,所以它应该是不知道Chat Completion模型的相关内容的。

response = openai.ChatCompletion.create(
  model="gpt-3.5-turbo-16k-0613",
  messages=[
    {"role": "user", "content": '你知道ChatCompletion.create函数的使用方法吗?'}
  ]
)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

看下大模型的输出:

image-20230821153609377

从回复上来看,基本与本文的内容是毫不沾边的。

  • Step 3:读取本地知识库

可以将本文的内容存储在一个Markdown中,然后作为system message输入给模型

# 读取Markdown文件
with open('Chat_Completion_Models API使用方法介绍.md', 'r', encoding='utf-8') as f:
    chatCompletion_kg= f.read()
  • 1
  • 2
  • 3

看下读取内容:

image-20230821154310950

读取之后将其作为system message输入给模型。

  • Step 4:问答测试
response = openai.ChatCompletion.create(
  model="gpt-3.5-turbo-16k-0613",
  messages=[
    {"role": "system", "content": md_content},
    {"role": "user", "content": '你知道ChatCompletion.create函数的使用方法吗?'}
  ]
)

response.choices[0].message['content']
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

看下大模型的推理结果:

image-20230821155020982

此时模型已经能够了解openai.ChatCompletion.create函数使用方法与基本规则。

Step 5:创建多轮对话函数

当大模型根据提供的本地知识库:Chat_Completion_Models API使用方法介绍.md,它其实已经具备了基础的理论知识和代码示例,所以可以尝试让大模型来帮助实现一个多轮对话函数,代码如下:

response = openai.ChatCompletion.create(
  model="gpt-3.5-turbo-16k-0613",
  messages=[
    {"role": "system", "content": md_content},
    {"role": "user", "content": '请帮编写一个基于openai.ChatCompletion.create这个函数的能够实现多轮对话的函数'}
  ]
)

response.choices[0].message['content']
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

看下推理结果:

image-20230723130916142

  • Step 6:将编写结果转化为markdown格式,并写入本地
with open('chatCompletionsModelTest.md', 'a', encoding='utf-8') as f:
    f.write(response.choices[0].message['content'])
  • 1
  • 2
  • Step 7:查看生成函数

image-20230821160400612

  • Step 8:尝试运行函数
def chat_with_model(messages):
    response = openai.ChatCompletion.create(
        model="gpt-3.5-turbo-16k-0613",
        messages=messages
    )
    return response.choices[0].message['content']

# 测试函数
def test_chat_with_model():
    # 初始问候
    messages = [
        {"role": "user", "content": "你好!"},
        {"role": "assistant", "content": "你好!我是一个智能助理,有什么问题我可以帮助你?"}
    ]
    print(chat_with_model(messages))

    # 进行对话
    while True:
        user_input = input("用户:")
        messages.append({"role": "user", "content": user_input})
        assistant_response = chat_with_model(messages)
        messages.append({"role": "assistant", "content": assistant_response})
        print("助理:" + assistant_response)

        # 判断是否结束对话
        if user_input.lower() == 'bye':
            break

test_chat_with_model()
  • 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

测试一下函数运行情况:

image-20230821161354199

至此,借助大模型已经实现了基于openai.ChatCompletion.create()的多轮对话函数,但是从第二个问题发现,它的回复并不是本地知识库的内容,所以需要修改原有代码,让其加载本地知识库

  • Step 9:函数优化:加入本地知识

将Chat Completions Models的相关知识作为system message进行输入,即可完成围绕本地知识库的问答流程,只需要修改这里:

# 测试函数
def test_chat_with_model():
    # 初始问候
    messages = [
        # 增加这一行
        {"role": "system", "content": chatCompletion_kg},
        {"role": "user", "content": "你好!"},
        {"role": "assistant", "content": "你好!我是一个智能助理,有什么问题我可以帮助你?"}
    ]
    print(chat_with_model(messages))
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • Step 10:最终测试

image-20230821162252226

可以看出来,回答的问题都是本文上述说到内容和知识,其实这样验证了它是顺利加载了本地知识库。所以上述就是一个借助System Role来加载本地知识库的思路。

八、总结

文章从介绍Chat Completion Models开始,阐述了其与Completions Models的关系及其发展历程。深入解读了Chat Completions Model API,包括调用示例及参数详解。在此基础上,专门对messages参数进行了详解,包括参数结构,以及message中的角色划分。并且,提供了丰富的message参数应用实例,如借助多轮user-assistant消息进行few-shot,使用system role进行Few-shot,输入提示模板,设置聊天背景信息,以及使用.append方法进行多轮对话。文章的最后部分提供了实际操作示例,训练一个本地知识库的智能助理,为实际应用Chat Completion Models提供了参考。

最后,感谢您阅读这篇文章!如果您觉得有所收获,别忘了点赞、收藏并关注我,这是我持续创作的动力。您有任何问题或建议,都可以在评论区留言,我会尽力回答并接受您的反馈。如果您希望了解某个特定主题,也欢迎告诉我,我会乐于创作与之相关的文章。谢谢您的支持,期待与您共同成长!

本文内容由网友自发贡献,转载请注明出处:https://www.wpsshop.cn/w/很楠不爱3/article/detail/378911
推荐阅读
相关标签
  

闽ICP备14008679号