Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: 增加Azure OpenAI服务的支持 #418

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
- [x] [GPT-3.0](https://github.com/zhayujie/bot-on-anything#2gpt-30)
- [x] [New Bing](https://github.com/zhayujie/bot-on-anything#4newbing)
- [x] [Google Bard](https://github.com/zhayujie/bot-on-anything#5bard)
- [x] [Azure OpenAI](#azure)

**应用:**

Expand Down Expand Up @@ -209,6 +210,44 @@ cookie示例:
}
```

### 6. Azure OpenAI

这个选项主要是为了解决OpenAI的api免费额度到期后,无法使用国内信用卡继续使用的问题。
众所周知微软和OpenAI的关系,微软在自家的Azure上也提供了OpenAI的服务,而Azure是可以绑定国内信用卡进行付费的。

#### (0) 申请开通 Azure OpenAI 服务 <a id="azure"></a>

参考这个视频:
[![](https://res.cloudinary.com/marcomontalbano/image/upload/v1686642690/video_to_markdown/images/youtube---RI2pXNfOKQ-c05b58ac6eb4c4700831b2b3070cd403.jpg)](https://www.youtube.com/watch?v=-RI2pXNfOKQ "")

Azure OpenAI 服务的快速入门文档:https://learn.microsoft.com/zh-cn/azure/cognitive-services/openai/chatgpt-quickstart?tabs=command-line&pivots=programming-language-python

#### (1) 安装依赖

``` shell
pip install openai
```

#### (2) 配置说明

基本与ChatGPT的配置相同,从依赖项就能看出来,实际使用的还是openai的python库,只是调用时部分参数有所不同。

``` json
{
"model": {
"type" : "azure_openai",
"azure_openai": {
"api_key": "你在Azure上创建的OpenAI服务实例的Key",
"api_base": "你在Azure上创建的OpenAI服务实例的Endpoint",
"api_version": "2023-05-15", # 目前是这个版本,如果有更新,可以在Azure上查看
"engine": "你在OpenAI服务实例中部署模型时设置的名称",
}
}
}
```

以上4个配置项都是必须的,其中engine相当于ChatGPT配置中的model。ChatGPT中的其他配置亦可在此使用。

## 三、选择应用

### 1.命令行终端
Expand Down
3 changes: 2 additions & 1 deletion common/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@

# model
OPEN_AI = "openai"
AZURE_OPEN_AI = "azure_openai"
CHATGPT = "chatgpt"
BAIDU = "baidu"
BING = "bing"
BARD = "bard"
BARD = "bard"
4 changes: 4 additions & 0 deletions model/model_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ def create_bot(model_type):
from model.openai.chatgpt_model import ChatGPTModel
return ChatGPTModel()

elif model_type == const.AZURE_OPEN_AI:
from model.openai.azure_open_ai_model import AzureOpenAIModel
return AzureOpenAIModel()

elif model_type == const.BAIDU:
from model.baidu.yiyan_model import YiyanModel
return YiyanModel()
Expand Down
220 changes: 220 additions & 0 deletions model/openai/azure_open_ai_model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
# encoding:utf-8

from model.model import Model
from config import model_conf, common_conf_val
from common import const
from common import log
import openai
import time

user_session = dict()

# OpenAI对话模型API (可用)
class AzureOpenAIModel(Model):
def __init__(self):
openai.api_type = 'azure'
openai.api_key = model_conf(const.AZURE_OPEN_AI).get('api_key')
api_base = model_conf(const.AZURE_OPEN_AI).get('api_base')
if api_base is None or api_base.strip() == '':
raise Exception('api_base is required, use your Azure OpenAI endpoint')
openai.api_base = api_base
openai.api_version = model_conf(const.AZURE_OPEN_AI).get('api_version')
engine = model_conf(const.AZURE_OPEN_AI).get('engine')
if engine is None or engine.strip() == '':
raise Exception('engine is required, it should be your Azure OpenAI deployment_name')
self.engine = engine
log.info(f"[AZURE_OPEN_AI] api_base={api_base} engine={engine}")

def reply(self, query, context=None):
# acquire reply content
if not context or not context.get('type') or context.get('type') == 'TEXT':
log.info("[AZURE_OPEN_AI] query={}".format(query))
from_user_id = context['from_user_id']
clear_memory_commands = common_conf_val('clear_memory_commands', ['#清除记忆'])
if query in clear_memory_commands:
Session.clear_session(from_user_id)
return '记忆已清除'

new_query = Session.build_session_query(query, from_user_id)
log.debug("[AZURE_OPEN_AI] session query={}".format(new_query))

# if context.get('stream'):
# # reply in stream
# return self.reply_text_stream(query, new_query, from_user_id)

reply_content = self.reply_text(new_query, from_user_id, 0)
#log.debug("[AZURE_OPEN_AI] new_query={}, user={}, reply_cont={}".format(new_query, from_user_id, reply_content))
return reply_content

elif context.get('type', None) == 'IMAGE_CREATE':
return self.create_img(query, 0)

def reply_text(self, query, user_id, retry_count=0):
try:
response = openai.ChatCompletion.create(
engine=self.engine,
messages=query,
temperature=model_conf(const.AZURE_OPEN_AI).get("temperature", 0.75), # 熵值,在[0,1]之间,越大表示选取的候选词越随机,回复越具有不确定性,建议和top_p参数二选一使用,创意性任务越大越好,精确性任务越小越好
#max_tokens=4096, # 回复最大的字符数,为输入和输出的总数
#top_p=model_conf(const.AZURE_OPEN_AI).get("top_p", 0.7),, #候选词列表。0.7 意味着只考虑前70%候选词的标记,建议和temperature参数二选一使用
frequency_penalty=model_conf(const.AZURE_OPEN_AI).get("frequency_penalty", 0.0), # [-2,2]之间,该值越大则越降低模型一行中的重复用词,更倾向于产生不同的内容
presence_penalty=model_conf(const.AZURE_OPEN_AI).get("presence_penalty", 1.0) # [-2,2]之间,该值越大则越不受输入限制,将鼓励模型生成输入中不存在的新词,更倾向于产生不同的内容
)
reply_content = response.choices[0]['message']['content']
used_token = response['usage']['total_tokens']
log.debug(response)
log.info("[AZURE_OPEN_AI] reply={}", reply_content)
if reply_content:
# save conversation
Session.save_session(query, reply_content, user_id, used_token)
return response.choices[0]['message']['content']
except openai.error.RateLimitError as e:
# rate limit exception
log.warn(e)
if retry_count < 1:
time.sleep(5)
log.warn("[AZURE_OPEN_AI] RateLimit exceed, 第{}次重试".format(retry_count+1))
return self.reply_text(query, user_id, retry_count+1)
else:
return "提问太快啦,请休息一下再问我吧"
except openai.error.APIConnectionError as e:
log.warn(e)
log.warn("[AZURE_OPEN_AI] APIConnection failed")
return "我连接不到网络,请稍后重试"
except openai.error.Timeout as e:
log.warn(e)
log.warn("[AZURE_OPEN_AI] Timeout")
return "我没有收到消息,请稍后重试"
except Exception as e:
# unknown exception
log.exception(e)
Session.clear_session(user_id)
return "请再问我一次吧"


async def reply_text_stream(self, query, context, retry_count=0):
try:
user_id=context['from_user_id']
new_query = Session.build_session_query(query, user_id)
res = openai.ChatCompletion.create(
model= model_conf(const.AZURE_OPEN_AI).get("model") or "gpt-3.5-turbo", # 对话模型的名称
messages=new_query,
temperature=model_conf(const.AZURE_OPEN_AI).get("temperature", 0.75), # 熵值,在[0,1]之间,越大表示选取的候选词越随机,回复越具有不确定性,建议和top_p参数二选一使用,创意性任务越大越好,精确性任务越小越好
#max_tokens=4096, # 回复最大的字符数,为输入和输出的总数
#top_p=model_conf(const.AZURE_OPEN_AI).get("top_p", 0.7),, #候选词列表。0.7 意味着只考虑前70%候选词的标记,建议和temperature参数二选一使用
frequency_penalty=model_conf(const.AZURE_OPEN_AI).get("frequency_penalty", 0.0), # [-2,2]之间,该值越大则越降低模型一行中的重复用词,更倾向于产生不同的内容
presence_penalty=model_conf(const.AZURE_OPEN_AI).get("presence_penalty", 1.0), # [-2,2]之间,该值越大则越不受输入限制,将鼓励模型生成输入中不存在的新词,更倾向于产生不同的内容
stream=True
)
full_response = ""
for chunk in res:
log.debug(chunk)
if (chunk["choices"][0]["finish_reason"]=="stop"):
break
chunk_message = chunk['choices'][0]['delta'].get("content")
if(chunk_message):
full_response+=chunk_message
yield False,full_response
Session.save_session(query, full_response, user_id)
log.info("[chatgpt]: reply={}", full_response)
yield True,full_response

except openai.error.RateLimitError as e:
# rate limit exception
log.warn(e)
if retry_count < 1:
time.sleep(5)
log.warn("[AZURE_OPEN_AI] RateLimit exceed, 第{}次重试".format(retry_count+1))
yield True, self.reply_text_stream(query, user_id, retry_count+1)
else:
yield True, "提问太快啦,请休息一下再问我吧"
except openai.error.APIConnectionError as e:
log.warn(e)
log.warn("[AZURE_OPEN_AI] APIConnection failed")
yield True, "我连接不到网络,请稍后重试"
except openai.error.Timeout as e:
log.warn(e)
log.warn("[AZURE_OPEN_AI] Timeout")
yield True, "我没有收到消息,请稍后重试"
except Exception as e:
# unknown exception
log.exception(e)
Session.clear_session(user_id)
yield True, "请再问我一次吧"

def create_img(self, query, retry_count=0):
try:
log.info("[OPEN_AI] image_query={}".format(query))
response = openai.Image.create(
prompt=query, #图片描述
n=1, #每次生成图片的数量
size="256x256" #图片大小,可选有 256x256, 512x512, 1024x1024
)
image_url = response['data'][0]['url']
log.info("[OPEN_AI] image_url={}".format(image_url))
return [image_url]
except openai.error.RateLimitError as e:
log.warn(e)
if retry_count < 1:
time.sleep(5)
log.warn("[OPEN_AI] ImgCreate RateLimit exceed, 第{}次重试".format(retry_count+1))
return self.reply_text(query, retry_count+1)
else:
return "提问太快啦,请休息一下再问我吧"
except Exception as e:
log.exception(e)
return None


class Session(object):
@staticmethod
def build_session_query(query, user_id):
'''
build query with conversation history
e.g. [
{"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?"}
]
:param query: query content
:param user_id: from user id
:return: query content with conversaction
'''
session = user_session.get(user_id, [])
if len(session) == 0:
system_prompt = model_conf(const.AZURE_OPEN_AI).get("character_desc", "")
system_item = {'role': 'system', 'content': system_prompt}
session.append(system_item)
user_session[user_id] = session
user_item = {'role': 'user', 'content': query}
session.append(user_item)
return session

@staticmethod
def save_session(query, answer, user_id, used_tokens=0):
max_tokens = model_conf(const.AZURE_OPEN_AI).get('conversation_max_tokens')
max_history_num = model_conf(const.AZURE_OPEN_AI).get('max_history_num', None)
if not max_tokens or max_tokens > 4000:
# default value
max_tokens = 1000
session = user_session.get(user_id)
if session:
# append conversation
gpt_item = {'role': 'assistant', 'content': answer}
session.append(gpt_item)

if used_tokens > max_tokens and len(session) >= 3:
# pop first conversation (TODO: more accurate calculation)
session.pop(1)
session.pop(1)

if max_history_num is not None:
while len(session) > max_history_num * 2 + 1:
session.pop(1)
session.pop(1)

@staticmethod
def clear_session(user_id):
user_session[user_id] = []