openai_chat.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. from openai import OpenAI
  2. from openai.types import Completion as CompletionMessage
  3. from openai._types import NotGiven, NOT_GIVEN
  4. from openai.types.chat import ChatCompletion, ChatCompletionChunk, ChatCompletionMessageParam, \
  5. ChatCompletionToolChoiceOptionParam, ChatCompletionToolParam, ChatCompletionMessageToolCall
  6. from openai.types.chat.chat_completion_chunk import ChoiceDeltaToolCall, ChoiceDeltaFunctionCall,\
  7. Choice, ChoiceDelta, ChoiceDeltaToolCallFunction
  8. from openai.types.chat.chat_completion import Choice as _ChatCompletionChoice, ChatCompletion as _ChatCompletion
  9. from openai.types.chat.chat_completion_message import FunctionCall, ChatCompletionMessage
  10. from openai.types.chat.chat_completion_message_tool_call import Function
  11. from openai.types.completion_usage import CompletionUsage
  12. from openai.resources.chat.completions import Completions
  13. from openai import AzureOpenAI
  14. import openai.types.chat.completion_create_params as completion_create_params
  15. # import monkeypatch
  16. from typing import List, Any, Generator, Union, Optional, Literal
  17. from time import time, sleep
  18. from json import dumps, loads
  19. from core.model_runtime.errors.invoke import InvokeAuthorizationError
  20. import re
  21. class MockChatClass(object):
  22. @staticmethod
  23. def generate_function_call(
  24. functions: List[completion_create_params.Function] | NotGiven = NOT_GIVEN,
  25. ) -> Optional[FunctionCall]:
  26. if not functions or len(functions) == 0:
  27. return None
  28. function: completion_create_params.Function = functions[0]
  29. function_name = function['name']
  30. function_description = function['description']
  31. function_parameters = function['parameters']
  32. function_parameters_type = function_parameters['type']
  33. if function_parameters_type != 'object':
  34. return None
  35. function_parameters_properties = function_parameters['properties']
  36. function_parameters_required = function_parameters['required']
  37. parameters = {}
  38. for parameter_name, parameter in function_parameters_properties.items():
  39. if parameter_name not in function_parameters_required:
  40. continue
  41. parameter_type = parameter['type']
  42. if parameter_type == 'string':
  43. if 'enum' in parameter:
  44. if len(parameter['enum']) == 0:
  45. continue
  46. parameters[parameter_name] = parameter['enum'][0]
  47. else:
  48. parameters[parameter_name] = 'kawaii'
  49. elif parameter_type == 'integer':
  50. parameters[parameter_name] = 114514
  51. elif parameter_type == 'number':
  52. parameters[parameter_name] = 1919810.0
  53. elif parameter_type == 'boolean':
  54. parameters[parameter_name] = True
  55. return FunctionCall(name=function_name, arguments=dumps(parameters))
  56. @staticmethod
  57. def generate_tool_calls(
  58. tools: List[ChatCompletionToolParam] | NotGiven = NOT_GIVEN,
  59. ) -> Optional[List[ChatCompletionMessageToolCall]]:
  60. list_tool_calls = []
  61. if not tools or len(tools) == 0:
  62. return None
  63. tool: ChatCompletionToolParam = tools[0]
  64. if tools['type'] != 'function':
  65. return None
  66. function = tool['function']
  67. function_call = MockChatClass.generate_function_call(functions=[function])
  68. if function_call is None:
  69. return None
  70. list_tool_calls.append(ChatCompletionMessageToolCall(
  71. id='sakurajima-mai',
  72. function=Function(
  73. name=function_call.name,
  74. arguments=function_call.arguments,
  75. ),
  76. type='function'
  77. ))
  78. return list_tool_calls
  79. @staticmethod
  80. def mocked_openai_chat_create_sync(
  81. model: str,
  82. functions: List[completion_create_params.Function] | NotGiven = NOT_GIVEN,
  83. tools: List[ChatCompletionToolParam] | NotGiven = NOT_GIVEN,
  84. ) -> CompletionMessage:
  85. tool_calls = []
  86. function_call = MockChatClass.generate_function_call(functions=functions)
  87. if not function_call:
  88. tool_calls = MockChatClass.generate_tool_calls(tools=tools)
  89. sleep(1)
  90. return _ChatCompletion(
  91. id='cmpl-3QJQa5jXJ5Z5X',
  92. choices=[
  93. _ChatCompletionChoice(
  94. finish_reason='content_filter',
  95. index=0,
  96. message=ChatCompletionMessage(
  97. content='elaina',
  98. role='assistant',
  99. function_call=function_call,
  100. tool_calls=tool_calls
  101. )
  102. )
  103. ],
  104. created=int(time()),
  105. model=model,
  106. object='chat.completion',
  107. system_fingerprint='',
  108. usage=CompletionUsage(
  109. prompt_tokens=2,
  110. completion_tokens=1,
  111. total_tokens=3,
  112. )
  113. )
  114. @staticmethod
  115. def mocked_openai_chat_create_stream(
  116. model: str,
  117. functions: List[completion_create_params.Function] | NotGiven = NOT_GIVEN,
  118. tools: List[ChatCompletionToolParam] | NotGiven = NOT_GIVEN,
  119. ) -> Generator[ChatCompletionChunk, None, None]:
  120. tool_calls = []
  121. function_call = MockChatClass.generate_function_call(functions=functions)
  122. if not function_call:
  123. tool_calls = MockChatClass.generate_tool_calls(tools=tools)
  124. full_text = "Hello, world!\n\n```python\nprint('Hello, world!')\n```"
  125. for i in range(0, len(full_text) + 1):
  126. sleep(0.1)
  127. if i == len(full_text):
  128. yield ChatCompletionChunk(
  129. id='cmpl-3QJQa5jXJ5Z5X',
  130. choices=[
  131. Choice(
  132. delta=ChoiceDelta(
  133. content='',
  134. function_call=ChoiceDeltaFunctionCall(
  135. name=function_call.name,
  136. arguments=function_call.arguments,
  137. ) if function_call else None,
  138. role='assistant',
  139. tool_calls=[
  140. ChoiceDeltaToolCall(
  141. index=0,
  142. id='misaka-mikoto',
  143. function=ChoiceDeltaToolCallFunction(
  144. name=tool_calls[0].function.name,
  145. arguments=tool_calls[0].function.arguments,
  146. ),
  147. type='function'
  148. )
  149. ] if tool_calls and len(tool_calls) > 0 else None
  150. ),
  151. finish_reason='function_call',
  152. index=0,
  153. )
  154. ],
  155. created=int(time()),
  156. model=model,
  157. object='chat.completion.chunk',
  158. system_fingerprint='',
  159. usage=CompletionUsage(
  160. prompt_tokens=2,
  161. completion_tokens=17,
  162. total_tokens=19,
  163. ),
  164. )
  165. else:
  166. yield ChatCompletionChunk(
  167. id='cmpl-3QJQa5jXJ5Z5X',
  168. choices=[
  169. Choice(
  170. delta=ChoiceDelta(
  171. content=full_text[i],
  172. role='assistant',
  173. ),
  174. finish_reason='content_filter',
  175. index=0,
  176. )
  177. ],
  178. created=int(time()),
  179. model=model,
  180. object='chat.completion.chunk',
  181. system_fingerprint='',
  182. )
  183. def chat_create(self: Completions, *,
  184. messages: List[ChatCompletionMessageParam],
  185. model: Union[str,Literal[
  186. "gpt-4-1106-preview", "gpt-4-vision-preview", "gpt-4", "gpt-4-0314", "gpt-4-0613",
  187. "gpt-4-32k", "gpt-4-32k-0314", "gpt-4-32k-0613",
  188. "gpt-3.5-turbo-1106", "gpt-3.5-turbo", "gpt-3.5-turbo-16k", "gpt-3.5-turbo-0301",
  189. "gpt-3.5-turbo-0613", "gpt-3.5-turbo-16k-0613"],
  190. ],
  191. functions: List[completion_create_params.Function] | NotGiven = NOT_GIVEN,
  192. response_format: completion_create_params.ResponseFormat | NotGiven = NOT_GIVEN,
  193. stream: Optional[Literal[False]] | NotGiven = NOT_GIVEN,
  194. tools: List[ChatCompletionToolParam] | NotGiven = NOT_GIVEN,
  195. **kwargs: Any,
  196. ):
  197. openai_models = [
  198. "gpt-4-1106-preview", "gpt-4-vision-preview", "gpt-4", "gpt-4-0314", "gpt-4-0613",
  199. "gpt-4-32k", "gpt-4-32k-0314", "gpt-4-32k-0613",
  200. "gpt-3.5-turbo-1106", "gpt-3.5-turbo", "gpt-3.5-turbo-16k", "gpt-3.5-turbo-0301",
  201. "gpt-3.5-turbo-0613", "gpt-3.5-turbo-16k-0613",
  202. ]
  203. azure_openai_models = [
  204. "gpt35", "gpt-4v", "gpt-35-turbo"
  205. ]
  206. if not re.match(r'^(https?):\/\/[^\s\/$.?#].[^\s]*$', self._client.base_url.__str__()):
  207. raise InvokeAuthorizationError('Invalid base url')
  208. if model in openai_models + azure_openai_models:
  209. if not re.match(r'sk-[a-zA-Z0-9]{24,}$', self._client.api_key) and type(self._client) == OpenAI:
  210. # sometime, provider use OpenAI compatible API will not have api key or have different api key format
  211. # so we only check if model is in openai_models
  212. raise InvokeAuthorizationError('Invalid api key')
  213. if len(self._client.api_key) < 18 and type(self._client) == AzureOpenAI:
  214. raise InvokeAuthorizationError('Invalid api key')
  215. if stream:
  216. return MockChatClass.mocked_openai_chat_create_stream(model=model, functions=functions, tools=tools)
  217. return MockChatClass.mocked_openai_chat_create_sync(model=model, functions=functions, tools=tools)