【AI】使用Azure OpenAI创建自己的AI应用!
@TOC
推荐超级课程:
这里将以 Azure 提供的 Openai 端口为例,并使用 OpenAI 提供的Python SDK 进行模型的调用。
创建工作区
进入Azure首页,在搜索栏输入OpenAI,点击进入OpenAI页面。
点击创建。
选择订阅,资源组,工作区名称,工作区地区,点击创建。这里我们的地区选择“美东”,因为目前只有这个地区支持chatgpt对话模型。如果你需要对话模型,可以选择其他模型种类更多的“西欧”。
选择下一步,确认无误后点击创建。
等他创建完成后,点击“探索”进入工作区。
模型介绍
在使用模型之前,我们先来了解一下Azure提供了哪些OpenAI模型。Azure提供的模型从功能上可以分为三大类:补全(completion)、对话(chat)、嵌入(embeddings)。
补全模型可以根据输入的文本,补全剩余的文本。这类模型顾名思义,就是根据前文续写后续的部分。他可以用来续写文章,补全程序。不仅如此,你其实也可以通过固定的文字格式来实现对话的效果。
对话模型相信用过ChatGPT的同学应该很熟悉。对话模型可以根据输入的文本方面,生成对话的回复。这类模型可以用来实现聊天机器人,也可以用来实现对话式的问答系统。在调用,对话模型与补全模型列表的区别是:你需要一个列表来存储对话的历史记录。
没有接触过NLP(自然语言处理)的同类可能会对“嵌入”这个词感到怀疑。“嵌入”显然就是将文本转换为支持的操作,而这个支持可以用来表示文本的语义信息,这样就可以方便地比较相似度的相似度。而嵌入模型就是用来实现这个操作的。
大多数模型拥有多个能力等级,能力越强处理的文字也计算复杂度,但相对的处理速度和使用成本调谐。通常有 4 个等级:达芬奇 > 居里 > 巴贝奇 > 阿达,其中达芬奇最强而Ada是最快的(有兴趣的同学可以查一下这4位名人)。在使用模型时,你可以根据自己的需求选择合适的等级。
具体的模型介绍可以参考Azure OpenAI 服务模型 。
部署模型
在了解了模型的功能和等级之后,我们就可以开始使用模型了。在使用模型之前,我们需要先部署模型。在Azure OpenAI工作区中,进入“部署”页面。
选择模型,点击创建。这里我配置了一个补全模型和对话模型。
部署后你就可以用 API 调用模型了,当然你也可以现在 Playground 中测试一下。
API参数
在 Playground 中测试模型时,我们可以看到 API 的参数。这里我们来介绍一下这些参数。具体的参数细节可以参考API 参考 。
model
指定使用的模型。prompt
是输入给模型的文本。temperature
控制了生成文本的随机程度,值越大,生成的文本越随机,值越小,生成的文本越稳定。这个值的范围在 0.0 到 2.0 之间(虽然在 Playground 中最高设置为 1) 。top_p
与temperature
类似,也是控制生成文本的随机程度。但这个参数简单的说是控制候选词的范围,值越大,候选词的范围越大,值越小,候选词的范围越小。这个值的范围在 0.0 到 1.0 之间。通常来说,这两个参数只需要设置一个就可以了。max_tokens
是模型生成的文本的最大长度,这其中的“令牌”不是指字符长度,你可以理解为模型眼中的“词”。令牌与我们所使用的词不一定是一一对应的。stop
是生成文本的条件停止,当生成的文本中包含这个字符串时,生成过程就会停止,最终生成的文本中将不包含这个字符串。这个参数可以是一个字符串,也可以是一个长度至多为4的字符串列表。presence_penalty
控制生成文本的多样性。他会惩罚那些在生成文本中已经出现过的令牌,以减少未来生成这些令牌的概率。这个值的范围在 -2.0 到 2.0 之间。如果设为负值,那么惩罚就会奖励,这样就会增加生成这些Token的概率。frequency_penalty
与presence_penalty
类似,也是控制生成文本的多样性。但不同,presence_penalty
是瞬时惩罚,而frequency_penalty
累计惩罚。如果一个词在生成文本中出现了多次,那么这个词在未来生成的概率就会越来越多小。这个值的范围同样在 -2.0 到 2.0 之间。
计算Token
GPT 模型使用Token来表示文本,而不是使用字符。模型能处理的文本长度是有限的,而这个长度指的是Token的数量,而不是字符的数量,而 OpenAI 使用模型的乐器方式也按照生成方式token的数量计算。因此为了能够更好地使用模型,我们需要知道生成的文本到底有多少token。
OpenAI 提供了一个 Python 库tiktoken 来计算Token。
pip install tiktoken
导入tiktoken库。
import tiktoken
不同的模型使用不同的编码来将转换为令牌。
编码名称 | OpenAI 模型 |
---|---|
cl100k_base | gpt-4 , gpt-3.5-turbo ,text-embedding-ada-002 |
p50k_base | 法典模型text-davinci-002 ,,text-davinci-003 |
r50k_base (或者gpt2 ) | GPT-3 模型如davinci |
我们可以使用tiktoken.get_encoding()
来获取编码对象。也可以使用tiktoken.encoding_for_model()
通过模型名自动获取编码对象。
encoding = tiktoken.get_encoding("cl100k_base")
encoding = tiktoken.encoding_for_model("gpt-3.5-turbo")
然后用.encode()
方法将文本Token化。返回的Token列表的长度,就是文本的Token数量。
encoding.encode("tiktoken is great!")
[83, 1609, 5963, 374, 2294, 0]
我们还可以使用.decode()
将令牌列表转换为文本。
encoding.decode([83, 1609, 5963, 374, 2294, 0])
'tiktoken is great!'
使用Python SDK
我们首先需要到Azure的“钥匙”页面获取钥匙和结束点,两个钥匙只要其中一个即可。
然后安装openai库。注意,Python版本需要大于等于3.7。我们这里使用官方提供的Python SDK
,其他语言的SDK可以在OpenAI Libraries
找到。
另外,因为这个库没有专门的文档参考,所以我们需要查看库的
和API源码
参考
。
pip3 install openai
以上获取的密钥和终止点初始化SDK:
import openai
openai.api_key = "REPLACE_WITH_YOUR_API_KEY_HERE" # Azure 的密鑰
openai.api_base = "REPLACE_WITH_YOUR_ENDPOINT_HERE" # Azure 的終結點
openai.api_type = "azure"
openai.api_version = "2023-03-15-preview" # API 版本,未來可能會變
model = "" # 模型的部署名
调用补全模型
补全模型的使用openai.Completion.create
方法,使用的参数在上面已经介绍过了,但是因为我使用的是 Azure 的 API,所以指定模型的参数名是engine
。下面是一个简单的例子:
prompt = "1, 2, 3, 4, "
response = openai.Completion.create(
engine=model, prompt=prompt, max_tokens=50, temperature=0.0
)
print(response)
它打印出的内容就是API返回的json结果。其中text
就是模型生成的文本,可以看到这数列续写下去。但他只停在21,因为是我可以设置了max_tokens=50
,看到usage
中的completion_tokens
是50,那么模型生成了50个令牌,达到了最大,finish_reason
那么length
,表示是因为达到了最大长度而停止的。如果是因为写完了而停止,那么finish_reason
的值就会是stop
。
{
"choices": [
{
"finish_reason": "length",
"index": 0,
"logprobs": null,
"text": "5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,"
}
],
"created": 1680628906,
"id": "cmpl-71edm7DMgHrynaiXONWi4ufSrXiL0",
"model": "gpt-35-turbo",
"object": "text_completion",
"usage": {
"completion_tokens": 50,
"prompt_tokens": 12,
"total_tokens": 62
}
}
另外你可能已经注意到,我使用的模型是“gpt-35-turbo”。他虽然是对话模型,但他实际上也可以通过补全API使用。但同样是对话模型的“gpt-4”就只能通过对话API使用。
接下来我们在其中加个停止条件,让他数到11就停止:
prompt = "1, 2, 3, 4, "
response = openai.Completion.create(
engine=model, prompt=prompt, temperature=0.0, stop=["11"]
)
print(response["choices"][0])
可以看到他确实是停在11之前,而且finish_reason
的值是stop
。
{
"finish_reason": "stop",
"index": 0,
"logprobs": null,
"text": "5, 6, 7, 8, 9, 10, "
}
但如果我们将temperature
值调高,它可能会生成意料之外的文本:
prompt = "1, 2, 3, 4, "
response = openai.Completion.create(
engine=model, prompt=prompt, temperature=0.8, stop=["11"]
)
print(response["choices"][0])
{
"finish_reason": "length",
"index": 0,
"logprobs": null,
"text": "5, 6, 7, 8, 9, 10])) # 55\nprint(sum_list([0, 0, 0, 0, 0])) # 0\nprint(sum_list([1, -"
}
调用对话模型
对话模型使用的是openai.ChatCompletion.create
方法,它的参数和补全模型的参数类似,但有一些不同:对话模型输入的是一个对话历史,而不是单个的文本。并且其参数名称是,而messages
不是prompt
。
对话历史简单来说是一个列表,列表中的每个元素都是一个对话,每个对话又是一个字典,字典中有两个键:和。是对话role
的content
角色role
,content
则是对话的内容。下面是个例子:
messages=[
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "Knock knock."},
{"role": "assistant", "content": "Who's there?"},
{"role": "user", "content": "Orange."},
]
可以看到role
的值有system
、user
和assistant
。user
就是指用户,assistant
代表的就是和你对话的AI模型,而system
可以理解为assistant
的设置、人设等等。当然system
也可以简洁。
接下来我们用这个对话历史来调用对话模型:
response = openai.ChatCompletion.create(
engine=model, messages=messages
)
response
{
"choices": [
{
"finish_reason": "stop",
"index": 0,
"message": {
"content": "Yes, many other Azure Cognitive Services do support the use of customer managed keys for encrypting and protecting data. In fact, most Azure services that handle sensitive data can be configured to use customer managed keys for enhanced security and compliance. Some examples of Azure Cognitive Services that support customer managed keys include Azure Cognitive Services Speech Services, Azure Cognitive Services Text Analytics, and Azure Cognitive Services Translator Text.",
"role": "assistant"
}
}
],
"created": 1680633366,
"id": "chatcmpl-71fnivs89GKEMKpzwuCCxbH0MNP98",
"model": "gpt-35-turbo",
"object": "chat.completion",
"usage": {
"completion_tokens": 79,
"prompt_tokens": 58,
"total_tokens": 137
}
}
我们可以维护一个对话历史,然后每次调用对话模型时,将新的对话加入到对话历史中,从而循环来实现一个对话系统:
conversation = [{"role": "system", "content": "You are a helpful assistant."}]
while True:
user_input = input()
conversation.append({"role": "user", "content": user_input})
response = openai.ChatCompletion.create(
engine=model,
messages=conversation,
)
conversation.append(
{"role": "assistant", "content": response["choices"][0]["message"]["content"]}
)
print("\n" + response["choices"][0]["message"]["content"] + "\n")
流式调用
所谓“流式”,就是服务器生成什么内容就立即返回给客户端,而不是等全部完成后再统一返回。最典型的例子就是各种视频网站,你在播放视频时,视频的内容是随着时间的瞬间而不断加载的,而不是等到视频加载结束后再一次性播放。这样做的好处是可以让用户更快的看到内容。ChatGPT 这样像一个字一个字写出来的效果就是通过流形成式的。
但他的缺点是,服务器不会帮你统计生成的token,需要你自己统计。
以下是一个一般调用的例子:
response = openai.ChatCompletion.create(
engine=model,
messages=[
{'role': 'user', 'content': "Count to 10. E.g. 1, 2, 3, 4, ..."},
],
temperature=0,
)
print(response)
{
"choices": [
{
"finish_reason": "stop",
"index": 0,
"message": {
"content": "\n\n1, 2, 3, 4, 5, 6, 7, 8, 9, 10.",
"role": "assistant"
}}
],
"created": 1680709601,
"id": "chatcmpl-71zdJtUaOIhfnVsAKLyD88RtGzgQb",
"model": "gpt-35-turbo",
"object": "chat.completion",
"usage": {
"completion_tokens": 31,
"prompt_tokens": 28,
"total_tokens": 59
}
}
但我们只要将stream
参数设置为True
,就可以使用流式调用了:
response = openai.ChatCompletion.create(
engine=model,
messages=[
{'role': 'user', 'content': "Count to 10. E.g. 1, 2, 3, 4, ..."},
],
temperature=0,
stream=True,
)
print(next(response))
for chunk in response:
print(chunk['choices'])
{
"choices": [
{
"delta": {
"role": "assistant"
},
"finish_reason": null,
"index": 0
}
],
"created": 1680710012,
"id": "chatcmpl-71zjwZk3EB5fLgVup9S1BPZo73JUk",
"model": "gpt-35-turbo",
"object": "chat.completion.chunk",
"usage": null
}
[<OpenAIObject at 0x1e2ba8fdc10> JSON: {
"delta": {
"content": "\n\n"
},
"finish_reason": null,
"index": 0
}]
[<OpenAIObject at 0x1e2ba8fe870> JSON: {
"delta": {
...
"delta": {},
"finish_reason": "stop",
"index": 0
}]
因为长度原因,除了第一个块,其他都只打印了choices
内容,但其余的部分都是一样的,甚至是id
。
可以看到,第一个块delta
里面只有role
,而后面的块delta
里面有content
,这就是服务器生成的内容。我们只要把这些内容拼接起来就可以了:
response = openai.ChatCompletion.create(
engine=model,
messages=[
{"role": "user", "content": "Count to 10. E.g. 1, 2, 3, 4, ..."},
],
temperature=0,
stream=True,
)
message = next(response)["choices"][0]["delta"]
content = "".join(
[chunk["choices"][0]["delta"].get("content", "") for chunk in response]
)
message["content"] = content
print(message)
{
"content": "\n\n1, 2, 3, 4, 5, 6, 7, 8, 9, 10.",
"role": "assistant"
}
异步调用
在我们写类似网站、爬虫之类的程序时,会经常遇到类似调用数据库、API的情况,这些操作都需要退出的等待,在等待的过程中,程序会遇到阻塞状态,无法继续执行后面可这些操作的瓶颈通常不在于程序本身,而是在于网络或者硬盘的速度,这个时候我们就可以使用异步来解决这个问题。
异步可以让我们等待结果的同时,继续执行其他的代码,这样就不会阻塞程序的执行了。这里不多介绍Python的异步及其语法,这超出了论文的范围,有兴趣的同学可以自行学习。论文将行为与SDK的异步调用。
OpenAI SDK的异步调用和同步调用的区别,异步调用需要使用acreate
方法,而不是create
方法。这里的a
表示异步。acreate
方法的返回值是一个coroutine
对象,而不是一个OpenAIObject
对象,所以我们不能直接使用print
来打印它,而是需要使用await
来等待它的结果。
async def async_completion():
response = await openai.ChatCompletion.acreate(
engine=model,
messages=[
{"role": "user", "content": "Count to 10. E.g. 1, 2, 3, 4, ..."},
],
temperature=0,
)
return response["choices"][0]["message"]
print(await async_completion())
{
"content": "\n\n1, 2, 3, 4, 5, 6, 7, 8, 9, 10.",
"role": "assistant"
}
OpenAI SDK 的异步请求是默认使用aiohttp
第三方异步请求库的会话。 SDK 在每次请求时都会创建一个会话,这会造成一部分的开销,因此如果你的程序需要间隔的异步请求,那么你可以自己设置另外aiohttp.ClientSession
要注意的是,如果你使用了自己的session,那么你需要在程序结束时手动关闭它,从而造成其他问题:
from aiohttp import ClientSession
openai.aiosession.set(ClientSession())
res = await asyncio.gather(*[async_completion() for _ in range(10)])
await openai.aiosession.get().close()
提示技巧
OpenAI的模型基于你输入的文本,清晰的提示(Prompt)来生成后续的文字,它可以做很多事情,正因如此,你需要明确的表达你想要什么。在一些时候,是不够的的,你还要给模型展示出你想要的结果,比如列举几个例子。下面三点是提示创建的基本准则:
- **展示和讲述。 **通过指示、示例或两者结合,清楚地表明您的需求。
- **提供优质数据。**如果您尝试构建分类器或让模型遵循某种模式,请确保有足够的结果。。
- *检查设置。 *
Temperature
和top_p
控制模型在生成响应时的确定性程度。如果你想要模型输出一些确定性的答案,将值设置低一些;如果你想要模型输出更多的选择,将值设置高一些通常这两个值只需要同时调整一个就可以了。在生成文字的时候,你可能需要经常调整这些设置,以保证输出的正确性。
另外,虽然以下示例都是使用完成模型,但这些原则提示同样适用于聊天模型。
生成
产生文本和想法几乎就是 OpenAI 的沟通功能,它可以用来生成代码、文章、故事、歌词、对话等等。在这里,我们将使用下面的类来完成这个任务。这是一个很简单的例子Completion
:
这虽然只是个非常简单的例子,但其中还是有一些值得注意的细节:
- 我们首先描述了我们的理解,即生成了一段关于 Python 优势的列表。
- 我们提供了一个例子,这样的模型就知道我们想要一个列表。并且只是说明优势,而不是用具体阐述。
- 我们留下了“2。 ”,以此引导模型继续生成。
对话
生成模型在经过引导后也可以用于对话。当然,只是简单的提示可能会让模型理解你的意思,你可以给他提供一些示例对话,这样他就可以跟着对话继续写下去:
为了避免让他生成过头,把我们的话也一起生成了,我们可以使用stop
参数来进行限制。比如在这里,我把stop
参数设置的是“博士:”,这样当他生成到这个字符之前,就会停止。
分类
下面的例子中,我在《巫师3》的评论区中选了5条,并让他分类为后悔或差评: