Merge pull request #587 from RockChinQ/hotfix/openai-1.0-adaptation

Feat: 适配openai>=1.0.0
This commit is contained in:
Junyan Qin
2023-11-10 11:49:20 +08:00
committed by GitHub
12 changed files with 111 additions and 97 deletions
+1 -1
View File
@@ -248,7 +248,7 @@ image_api_params = {
trace_function_calls = False trace_function_calls = False
# 群内回复消息时是否引用原消息 # 群内回复消息时是否引用原消息
quote_origin = True quote_origin = False
# 群内回复消息时是否at发送者 # 群内回复消息时是否at发送者
at_sender = False at_sender = False
+6 -3
View File
@@ -191,13 +191,16 @@ def start(first_time_init=False):
# 配置OpenAI proxy # 配置OpenAI proxy
import openai import openai
openai.proxy = None # 先重置,因为重载后可能需要清除proxy openai.proxies = None # 先重置,因为重载后可能需要清除proxy
if "http_proxy" in config.openai_config and config.openai_config["http_proxy"] is not None: if "http_proxy" in config.openai_config and config.openai_config["http_proxy"] is not None:
openai.proxy = config.openai_config["http_proxy"] openai.proxies = {
"http": config.openai_config["http_proxy"],
"https": config.openai_config["http_proxy"]
}
# 配置openai api_base # 配置openai api_base
if "reverse_proxy" in config.openai_config and config.openai_config["reverse_proxy"] is not None: if "reverse_proxy" in config.openai_config and config.openai_config["reverse_proxy"] is not None:
openai.api_base = config.openai_config["reverse_proxy"] openai.base_url = config.openai_config["reverse_proxy"]
# 主启动流程 # 主启动流程
database = pkg.database.manager.DatabaseManager() database = pkg.database.manager.DatabaseManager()
+1 -1
View File
@@ -63,7 +63,7 @@
"size": "256x256" "size": "256x256"
}, },
"trace_function_calls": false, "trace_function_calls": false,
"quote_origin": true, "quote_origin": false,
"at_sender": false, "at_sender": false,
"include_image_description": true, "include_image_description": true,
"process_message_timeout": 120, "process_message_timeout": 120,
+39 -22
View File
@@ -1,4 +1,5 @@
import openai import openai
from openai.types.chat import chat_completion_message
import json import json
import logging import logging
@@ -13,13 +14,14 @@ class ChatCompletionRequest(RequestBase):
此类保证每一次返回的角色为assistant的信息的finish_reason一定为stop。 此类保证每一次返回的角色为assistant的信息的finish_reason一定为stop。
若有函数调用响应,本类的返回瀑布是:函数调用请求->函数调用结果->...->assistant的信息->stop。 若有函数调用响应,本类的返回瀑布是:函数调用请求->函数调用结果->...->assistant的信息->stop。
""" """
model: str model: str
messages: list[dict[str, str]] messages: list[dict[str, str]]
kwargs: dict kwargs: dict
stopped: bool = False stopped: bool = False
pending_func_call: dict = None pending_func_call: chat_completion_message.FunctionCall = None
pending_msg: str pending_msg: str
@@ -46,16 +48,18 @@ class ChatCompletionRequest(RequestBase):
def __init__( def __init__(
self, self,
client: openai.Client,
model: str, model: str,
messages: list[dict[str, str]], messages: list[dict[str, str]],
**kwargs **kwargs
): ):
self.client = client
self.model = model self.model = model
self.messages = messages.copy() self.messages = messages.copy()
self.kwargs = kwargs self.kwargs = kwargs
self.req_func = openai.ChatCompletion.acreate self.req_func = self.client.chat.completions.create
self.pending_func_call = None self.pending_func_call = None
@@ -84,39 +88,48 @@ class ChatCompletionRequest(RequestBase):
# 拼接kwargs # 拼接kwargs
args = {**args, **self.kwargs} args = {**args, **self.kwargs}
from openai.types.chat import chat_completion
resp = self._req(**args) resp: chat_completion.ChatCompletion = self._req(**args)
choice0 = resp["choices"][0] choice0 = resp.choices[0]
# 如果不是函数调用,且finish_reason为stop,则停止迭代 # 如果不是函数调用,且finish_reason为stop,则停止迭代
if choice0['finish_reason'] == 'stop': # and choice0["finish_reason"] == "stop" if choice0.finish_reason == 'stop': # and choice0["finish_reason"] == "stop"
self.stopped = True self.stopped = True
if 'function_call' in choice0['message']: if hasattr(choice0.message, 'function_call') and choice0.message.function_call is not None:
self.pending_func_call = choice0['message']['function_call'] self.pending_func_call = choice0.message.function_call
self.append_message( self.append_message(
role="assistant", role="assistant",
content=choice0['message']['content'], content=choice0.message.content,
function_call=choice0['message']['function_call'] function_call=choice0.message.function_call
) )
return { return {
"id": resp["id"], "id": resp.id,
"choices": [ "choices": [
{ {
"index": choice0["index"], "index": choice0.index,
"message": { "message": {
"role": "assistant", "role": "assistant",
"type": "function_call", "type": "function_call",
"content": choice0['message']['content'], "content": choice0.message.content,
"function_call": choice0['message']['function_call'] "function_call": {
"name": choice0.message.function_call.name,
"arguments": choice0.message.function_call.arguments
}
}, },
"finish_reason": "function_call" "finish_reason": "function_call"
} }
], ],
"usage": resp["usage"] "usage": {
"prompt_tokens": resp.usage.prompt_tokens,
"completion_tokens": resp.usage.completion_tokens,
"total_tokens": resp.usage.total_tokens
}
} }
else: else:
@@ -124,19 +137,23 @@ class ChatCompletionRequest(RequestBase):
# 普通回复一定处于最后方,故不用再追加进内部messages # 普通回复一定处于最后方,故不用再追加进内部messages
return { return {
"id": resp["id"], "id": resp.id,
"choices": [ "choices": [
{ {
"index": choice0["index"], "index": choice0.index,
"message": { "message": {
"role": "assistant", "role": "assistant",
"type": "text", "type": "text",
"content": choice0['message']['content'] "content": choice0.message.content
}, },
"finish_reason": choice0["finish_reason"] "finish_reason": choice0.finish_reason
} }
], ],
"usage": resp["usage"] "usage": {
"prompt_tokens": resp.usage.prompt_tokens,
"completion_tokens": resp.usage.completion_tokens,
"total_tokens": resp.usage.total_tokens
}
} }
else: # 处理函数调用请求 else: # 处理函数调用请求
@@ -144,20 +161,20 @@ class ChatCompletionRequest(RequestBase):
self.pending_func_call = None self.pending_func_call = None
func_name = cp_pending_func_call['name'] func_name = cp_pending_func_call.name
arguments = {} arguments = {}
try: try:
try: try:
arguments = json.loads(cp_pending_func_call['arguments']) arguments = json.loads(cp_pending_func_call.arguments)
# 若不是json格式的异常处理 # 若不是json格式的异常处理
except json.decoder.JSONDecodeError: except json.decoder.JSONDecodeError:
# 获取函数的参数列表 # 获取函数的参数列表
func_schema = get_func_schema(func_name) func_schema = get_func_schema(func_name)
arguments = { arguments = {
func_schema['parameters']['required'][0]: cp_pending_func_call['arguments'] func_schema['parameters']['required'][0]: cp_pending_func_call.arguments
} }
logging.info("执行函数调用: name={}, arguments={}".format(func_name, arguments)) logging.info("执行函数调用: name={}, arguments={}".format(func_name, arguments))
+17 -28
View File
@@ -1,4 +1,5 @@
import openai import openai
from openai.types import completion, completion_choice
from .model import RequestBase from .model import RequestBase
@@ -17,10 +18,12 @@ class CompletionRequest(RequestBase):
def __init__( def __init__(
self, self,
client: openai.Client,
model: str, model: str,
messages: list[dict[str, str]], messages: list[dict[str, str]],
**kwargs **kwargs
): ):
self.client = client
self.model = model self.model = model
self.prompt = "" self.prompt = ""
@@ -31,7 +34,7 @@ class CompletionRequest(RequestBase):
self.kwargs = kwargs self.kwargs = kwargs
self.req_func = openai.Completion.acreate self.req_func = self.client.completions.create
def __iter__(self): def __iter__(self):
return self return self
@@ -63,49 +66,35 @@ class CompletionRequest(RequestBase):
if self.stopped: if self.stopped:
raise StopIteration() raise StopIteration()
resp = self._req( resp: completion.Completion = self._req(
model=self.model, model=self.model,
prompt=self.prompt, prompt=self.prompt,
**self.kwargs **self.kwargs
) )
if resp["choices"][0]["finish_reason"] == "stop": if resp.choices[0].finish_reason == "stop":
self.stopped = True self.stopped = True
choice0 = resp["choices"][0] choice0: completion_choice.CompletionChoice = resp.choices[0]
self.prompt += choice0["text"] self.prompt += choice0.text
return { return {
"id": resp["id"], "id": resp.id,
"choices": [ "choices": [
{ {
"index": choice0["index"], "index": choice0.index,
"message": { "message": {
"role": "assistant", "role": "assistant",
"type": "text", "type": "text",
"content": choice0["text"] "content": choice0.text
}, },
"finish_reason": choice0["finish_reason"] "finish_reason": choice0.finish_reason
} }
], ],
"usage": resp["usage"] "usage": {
} "prompt_tokens": resp.usage.prompt_tokens,
"completion_tokens": resp.usage.completion_tokens,
if __name__ == "__main__": "total_tokens": resp.usage.total_tokens
import os
openai.api_key = os.environ["OPENAI_API_KEY"]
for resp in CompletionRequest(
model="text-davinci-003",
messages=[
{
"role": "user",
"content": "Hello, who are you?"
} }
] }
):
print(resp)
if resp["choices"][0]["finish_reason"] == "stop":
break
+7 -29
View File
@@ -8,6 +8,8 @@ import openai
class RequestBase: class RequestBase:
client: openai.Client
req_func: callable req_func: callable
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
@@ -17,41 +19,17 @@ class RequestBase:
import pkg.utils.context as context import pkg.utils.context as context
switched, name = context.get_openai_manager().key_mgr.auto_switch() switched, name = context.get_openai_manager().key_mgr.auto_switch()
logging.debug("切换api-key: switched={}, name={}".format(switched, name)) logging.debug("切换api-key: switched={}, name={}".format(switched, name))
openai.api_key = context.get_openai_manager().key_mgr.get_using_key() self.client.api_key = context.get_openai_manager().key_mgr.get_using_key()
def _req(self, **kwargs): def _req(self, **kwargs):
"""处理代理问题""" """处理代理问题"""
import config import config
ret: dict = {} ret = self.req_func(**kwargs)
exception: Exception = None logging.debug("接口请求返回:%s", str(ret))
async def awrapper(**kwargs): if config.switch_strategy == 'active':
nonlocal ret, exception self._next_key()
try:
ret = await self.req_func(**kwargs)
logging.debug("接口请求返回:%s", str(ret))
if config.switch_strategy == 'active':
self._next_key()
return ret
except Exception as e:
exception = e
loop = asyncio.new_event_loop()
thr = threading.Thread(
target=loop.run_until_complete,
args=(awrapper(**kwargs),)
)
thr.start()
thr.join()
if exception is not None:
raise exception
return ret return ret
+6 -2
View File
@@ -24,6 +24,8 @@ class OpenAIInteract:
"size": "256x256", "size": "256x256",
} }
client: openai.Client = None
def __init__(self, api_key: str): def __init__(self, api_key: str):
self.key_mgr = pkg.openai.keymgr.KeysManager(api_key) self.key_mgr = pkg.openai.keymgr.KeysManager(api_key)
@@ -31,7 +33,9 @@ class OpenAIInteract:
# logging.info("文字总使用量:%d", self.audit_mgr.get_total_text_length()) # logging.info("文字总使用量:%d", self.audit_mgr.get_total_text_length())
openai.api_key = self.key_mgr.get_using_key() self.client = openai.Client(
api_key=self.key_mgr.get_using_key()
)
pkg.utils.context.set_openai_manager(self) pkg.utils.context.set_openai_manager(self)
@@ -48,7 +52,7 @@ class OpenAIInteract:
cp_parmas = config.completion_api_params.copy() cp_parmas = config.completion_api_params.copy()
del cp_parmas['model'] del cp_parmas['model']
request = select_request_cls(model, messages, cp_parmas) request = select_request_cls(self.client, model, messages, cp_parmas)
# 请求接口 # 请求接口
for resp in request: for resp in request:
+4 -5
View File
@@ -5,9 +5,8 @@ ChatCompletion - gpt-3.5-turbo 等模型
Completion - text-davinci-003 等模型 Completion - text-davinci-003 等模型
此模块封装此两个接口的请求实现,为上层提供统一的调用方式 此模块封装此两个接口的请求实现,为上层提供统一的调用方式
""" """
import openai, logging, threading, asyncio
import openai.error as aiE
import tiktoken import tiktoken
import openai
from pkg.openai.api.model import RequestBase from pkg.openai.api.model import RequestBase
from pkg.openai.api.completion import CompletionRequest from pkg.openai.api.completion import CompletionRequest
@@ -53,11 +52,11 @@ IMAGE_MODELS = {
} }
def select_request_cls(model_name: str, messages: list, args: dict) -> RequestBase: def select_request_cls(client: openai.Client, model_name: str, messages: list, args: dict) -> RequestBase:
if model_name in CHAT_COMPLETION_MODELS: if model_name in CHAT_COMPLETION_MODELS:
return ChatCompletionRequest(model_name, messages, **args) return ChatCompletionRequest(client, model_name, messages, **args)
elif model_name in COMPLETION_MODELS: elif model_name in COMPLETION_MODELS:
return CompletionRequest(model_name, messages, **args) return CompletionRequest(client, model_name, messages, **args)
raise ValueError("不支持模型[{}],请检查配置文件".format(model_name)) raise ValueError("不支持模型[{}],请检查配置文件".format(model_name))
+1 -1
View File
@@ -278,7 +278,7 @@ class Session:
if resp['choices'][0]['message']['role'] == "assistant" and resp['choices'][0]['message']['content'] != None: # 包含纯文本响应 if resp['choices'][0]['message']['role'] == "assistant" and resp['choices'][0]['message']['content'] != None: # 包含纯文本响应
if not trace_func_calls: if not trace_func_calls:
res_text += resp['choices'][0]['message']['content'] + "\n" res_text += resp['choices'][0]['message']['content']
else: else:
res_text = resp['choices'][0]['message']['content'] res_text = resp['choices'][0]['message']['content']
pending_res_text = resp['choices'][0]['message']['content'] pending_res_text = resp['choices'][0]['message']['content']
+4 -4
View File
@@ -65,14 +65,14 @@ def process_normal_message(text_message: str, mgr, config, launcher_type: str,
if not event.is_prevented_default(): if not event.is_prevented_default():
reply = [prefix + text] reply = [prefix + text]
except openai.error.APIConnectionError as e: except openai.APIConnectionError as e:
err_msg = str(e) err_msg = str(e)
if err_msg.__contains__('Error communicating with OpenAI'): if err_msg.__contains__('Error communicating with OpenAI'):
reply = handle_exception("{}会话调用API失败:{}\n您的网络无法访问OpenAI接口或网络代理不正常".format(session_name, e), reply = handle_exception("{}会话调用API失败:{}\n您的网络无法访问OpenAI接口或网络代理不正常".format(session_name, e),
"[bot]err:调用API失败,请重试或联系管理员,或等待修复") "[bot]err:调用API失败,请重试或联系管理员,或等待修复")
else: else:
reply = handle_exception("{}会话调用API失败:{}".format(session_name, e), "[bot]err:调用API失败,请重试或联系管理员,或等待修复") reply = handle_exception("{}会话调用API失败:{}".format(session_name, e), "[bot]err:调用API失败,请重试或联系管理员,或等待修复")
except openai.error.RateLimitError as e: except openai.RateLimitError as e:
logging.debug(type(e)) logging.debug(type(e))
logging.debug(e.error['message']) logging.debug(e.error['message'])
@@ -116,14 +116,14 @@ def process_normal_message(text_message: str, mgr, config, launcher_type: str,
else: else:
reply = handle_exception("{}会话调用API失败:{}".format(session_name, e), reply = handle_exception("{}会话调用API失败:{}".format(session_name, e),
"[bot]err:RateLimitError,请重试或联系作者,或等待修复") "[bot]err:RateLimitError,请重试或联系作者,或等待修复")
except openai.error.InvalidRequestError as e: except openai.BadRequestError as e:
if config.auto_reset and "This model's maximum context length is" in str(e): if config.auto_reset and "This model's maximum context length is" in str(e):
session.reset(persist=True) session.reset(persist=True)
reply = [tips_custom.session_auto_reset_message] reply = [tips_custom.session_auto_reset_message]
else: else:
reply = handle_exception("{}API调用参数错误:{}\n".format( reply = handle_exception("{}API调用参数错误:{}\n".format(
session_name, e), "[bot]err:API调用参数错误,请联系管理员,或等待修复") session_name, e), "[bot]err:API调用参数错误,请联系管理员,或等待修复")
except openai.error.ServiceUnavailableError as e: except openai.APIStatusError as e:
reply = handle_exception("{}API调用服务不可用:{}".format(session_name, e), "[bot]err:API调用服务不可用,请重试或联系管理员,或等待修复") reply = handle_exception("{}API调用服务不可用:{}".format(session_name, e), "[bot]err:API调用服务不可用,请重试或联系管理员,或等待修复")
except Exception as e: except Exception as e:
logging.exception(e) logging.exception(e)
+1 -1
View File
@@ -1,5 +1,5 @@
requests requests
openai==0.28.1 openai
dulwich~=0.21.6 dulwich~=0.21.6
colorlog~=6.6.0 colorlog~=6.6.0
yiri-mirai yiri-mirai
+24
View File
@@ -0,0 +1,24 @@
import os
import openai
client = openai.Client(
api_key=os.environ["OPENAI_API_KEY"],
)
openai.proxies = {
'http': 'http://127.0.0.1:7890',
'https': 'http://127.0.0.1:7890',
}
resp = client.chat.completions.create(
model="gpt-3.5-turbo",
messages=[
{
"role": "user",
"content": "Hello, how are you?",
}
]
)
print(resp)