chore: 添加虚拟环境到仓库
- 添加 backend_service/venv 虚拟环境 - 包含所有Python依赖包 - 注意:虚拟环境约393MB,包含12655个文件
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,245 @@
|
||||
# Copyright (c) Alibaba, Inc. and its affiliates.
|
||||
|
||||
import json
|
||||
from http import HTTPStatus
|
||||
|
||||
import aiohttp
|
||||
|
||||
from dashscope.api_entities.base_request import AioBaseRequest
|
||||
from dashscope.api_entities.dashscope_response import DashScopeAPIResponse
|
||||
from dashscope.common.constants import (DEFAULT_REQUEST_TIMEOUT_SECONDS,
|
||||
SSE_CONTENT_TYPE, HTTPMethod)
|
||||
from dashscope.common.error import UnsupportedHTTPMethod
|
||||
from dashscope.common.logging import logger
|
||||
from dashscope.common.utils import async_to_sync
|
||||
|
||||
|
||||
class AioHttpRequest(AioBaseRequest):
|
||||
def __init__(self,
|
||||
url: str,
|
||||
api_key: str,
|
||||
http_method: str,
|
||||
stream: bool = True,
|
||||
async_request: bool = False,
|
||||
query: bool = False,
|
||||
timeout: int = DEFAULT_REQUEST_TIMEOUT_SECONDS,
|
||||
task_id: str = None,
|
||||
user_agent: str = '') -> None:
|
||||
"""HttpSSERequest, processing http server sent event stream.
|
||||
|
||||
Args:
|
||||
url (str): The request url.
|
||||
api_key (str): The api key.
|
||||
method (str): The http method(GET|POST).
|
||||
stream (bool, optional): Is stream request. Defaults to True.
|
||||
timeout (int, optional): Total request timeout.
|
||||
Defaults to DEFAULT_REQUEST_TIMEOUT_SECONDS.
|
||||
user_agent (str, optional): Additional user agent string to
|
||||
append. Defaults to ''.
|
||||
"""
|
||||
|
||||
super().__init__(user_agent=user_agent)
|
||||
self.url = url
|
||||
self.async_request = async_request
|
||||
self.headers = {
|
||||
'Accept': 'application/json',
|
||||
'Authorization': 'Bearer %s' % api_key,
|
||||
'Cache-Control': 'no-cache',
|
||||
**self.headers,
|
||||
}
|
||||
self.query = query
|
||||
if self.async_request and self.query is False:
|
||||
self.headers = {
|
||||
'X-DashScope-Async': 'enable',
|
||||
**self.headers,
|
||||
}
|
||||
self.method = http_method
|
||||
if self.method == HTTPMethod.POST:
|
||||
self.headers['Content-Type'] = 'application/json'
|
||||
|
||||
self.stream = stream
|
||||
if self.stream:
|
||||
self.headers['Accept'] = SSE_CONTENT_TYPE
|
||||
self.headers['X-Accel-Buffering'] = 'no'
|
||||
self.headers['X-DashScope-SSE'] = 'enable'
|
||||
if self.query:
|
||||
self.url = self.url.replace('api', 'api-task')
|
||||
self.url += '%s' % task_id
|
||||
if timeout is None:
|
||||
self.timeout = DEFAULT_REQUEST_TIMEOUT_SECONDS
|
||||
else:
|
||||
self.timeout = timeout
|
||||
|
||||
def add_header(self, key, value):
|
||||
self.headers[key] = value
|
||||
|
||||
def add_headers(self, headers):
|
||||
self.headers = {**self.headers, **headers}
|
||||
|
||||
def call(self):
|
||||
response = async_to_sync(self._handle_request())
|
||||
if self.stream:
|
||||
return (item for item in response)
|
||||
else:
|
||||
output = next(response)
|
||||
try:
|
||||
next(response)
|
||||
except StopIteration:
|
||||
pass
|
||||
return output
|
||||
|
||||
async def aio_call(self):
|
||||
response = self._handle_request()
|
||||
if self.stream:
|
||||
return (item async for item in response)
|
||||
else:
|
||||
result = await response.__anext__()
|
||||
try:
|
||||
await response.__anext__()
|
||||
except StopAsyncIteration:
|
||||
pass
|
||||
return result
|
||||
|
||||
async def _handle_stream(self, response):
|
||||
# TODO define done message.
|
||||
is_error = False
|
||||
status_code = HTTPStatus.BAD_REQUEST
|
||||
async for line in response.content:
|
||||
if line:
|
||||
line = line.decode('utf8')
|
||||
line = line.rstrip('\n').rstrip('\r')
|
||||
if line.startswith('event:error'):
|
||||
is_error = True
|
||||
elif line.startswith('status:'):
|
||||
status_code = line[len('status:'):]
|
||||
status_code = int(status_code.strip())
|
||||
elif line.startswith('data:'):
|
||||
line = line[len('data:'):]
|
||||
yield (is_error, status_code, line)
|
||||
if is_error:
|
||||
break
|
||||
else:
|
||||
continue # ignore heartbeat...
|
||||
|
||||
async def _handle_response(self, response: aiohttp.ClientResponse):
|
||||
request_id = ''
|
||||
if (response.status == HTTPStatus.OK and self.stream
|
||||
and SSE_CONTENT_TYPE in response.content_type):
|
||||
async for is_error, status_code, data in self._handle_stream(
|
||||
response):
|
||||
try:
|
||||
output = None
|
||||
usage = None
|
||||
msg = json.loads(data)
|
||||
if not is_error:
|
||||
if 'output' in msg:
|
||||
output = msg['output']
|
||||
if 'usage' in msg:
|
||||
usage = msg['usage']
|
||||
if 'request_id' in msg:
|
||||
request_id = msg['request_id']
|
||||
except json.JSONDecodeError:
|
||||
yield DashScopeAPIResponse(
|
||||
request_id=request_id,
|
||||
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
|
||||
code='Unknown',
|
||||
message=data)
|
||||
continue
|
||||
if is_error:
|
||||
yield DashScopeAPIResponse(request_id=request_id,
|
||||
status_code=status_code,
|
||||
code=msg['code'],
|
||||
message=msg['message'])
|
||||
else:
|
||||
yield DashScopeAPIResponse(request_id=request_id,
|
||||
status_code=HTTPStatus.OK,
|
||||
output=output,
|
||||
usage=usage)
|
||||
elif (response.status == HTTPStatus.OK
|
||||
and 'multipart' in response.content_type):
|
||||
reader = aiohttp.MultipartReader.from_response(response)
|
||||
output = {}
|
||||
while True:
|
||||
part = await reader.next()
|
||||
if part is None:
|
||||
break
|
||||
output[part.name] = await part.read()
|
||||
if 'request_id' in output:
|
||||
request_id = output['request_id']
|
||||
yield DashScopeAPIResponse(request_id=request_id,
|
||||
status_code=HTTPStatus.OK,
|
||||
output=output)
|
||||
elif response.status == HTTPStatus.OK:
|
||||
json_content = await response.json()
|
||||
output = None
|
||||
usage = None
|
||||
if 'output' in json_content and json_content['output'] is not None:
|
||||
output = json_content['output']
|
||||
if 'usage' in json_content:
|
||||
usage = json_content['usage']
|
||||
if 'request_id' in json_content:
|
||||
request_id = json_content['request_id']
|
||||
yield DashScopeAPIResponse(request_id=request_id,
|
||||
status_code=HTTPStatus.OK,
|
||||
output=output,
|
||||
usage=usage)
|
||||
else:
|
||||
if 'application/json' in response.content_type:
|
||||
error = await response.json()
|
||||
if 'request_id' in error:
|
||||
request_id = error['request_id']
|
||||
if 'message' not in error:
|
||||
message = ''
|
||||
logger.error('Request: %s failed, status: %s' %
|
||||
(self.url, response.status))
|
||||
else:
|
||||
message = error['message']
|
||||
logger.error(
|
||||
'Request: %s failed, status: %s, message: %s' %
|
||||
(self.url, response.status, error['message']))
|
||||
yield DashScopeAPIResponse(request_id=request_id,
|
||||
status_code=response.status,
|
||||
code=error['code'],
|
||||
message=message)
|
||||
else:
|
||||
msg = await response.read()
|
||||
yield DashScopeAPIResponse(request_id=request_id,
|
||||
status_code=response.status,
|
||||
code='Unknown',
|
||||
message=msg.decode('utf-8'))
|
||||
|
||||
async def _handle_request(self):
|
||||
try:
|
||||
async with aiohttp.ClientSession(
|
||||
timeout=aiohttp.ClientTimeout(total=self.timeout),
|
||||
headers=self.headers) as session:
|
||||
logger.debug('Starting request: %s' % self.url)
|
||||
if self.method == HTTPMethod.POST:
|
||||
is_form, obj = self.data.get_aiohttp_payload()
|
||||
if is_form:
|
||||
headers = {**self.headers, **obj.headers}
|
||||
response = await session.post(url=self.url,
|
||||
data=obj,
|
||||
headers=headers)
|
||||
else:
|
||||
response = await session.request('POST',
|
||||
url=self.url,
|
||||
json=obj,
|
||||
headers=self.headers)
|
||||
elif self.method == HTTPMethod.GET:
|
||||
response = await session.get(url=self.url,
|
||||
params=self.data.parameters,
|
||||
headers=self.headers)
|
||||
else:
|
||||
raise UnsupportedHTTPMethod('Unsupported http method: %s' %
|
||||
self.method)
|
||||
logger.debug('Response returned: %s' % self.url)
|
||||
async with response:
|
||||
async for rsp in self._handle_response(response):
|
||||
yield rsp
|
||||
except aiohttp.ClientConnectorError as e:
|
||||
logger.error(e)
|
||||
raise e
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
raise e
|
||||
@@ -0,0 +1,169 @@
|
||||
# Copyright (c) Alibaba, Inc. and its affiliates.
|
||||
|
||||
import json
|
||||
from urllib.parse import urlencode
|
||||
|
||||
import aiohttp
|
||||
|
||||
from dashscope.common.constants import ApiProtocol
|
||||
from dashscope.io.input_output import InputResolver
|
||||
|
||||
|
||||
class ApiRequestData():
|
||||
def __init__(
|
||||
self,
|
||||
model,
|
||||
task_group,
|
||||
task,
|
||||
function,
|
||||
input,
|
||||
form,
|
||||
is_binary_input,
|
||||
api_protocol,
|
||||
) -> None:
|
||||
self.model = model
|
||||
self.task = task
|
||||
self.task_group = task_group
|
||||
self.function = function
|
||||
self._input = input
|
||||
self._input_type = {}
|
||||
self._input_generators = {}
|
||||
self.parameters = {}
|
||||
self._form = form
|
||||
self._api_protocol = api_protocol
|
||||
self._is_binary_input = is_binary_input
|
||||
self.resources = None
|
||||
|
||||
if api_protocol in [ApiProtocol.HTTP, ApiProtocol.HTTPS]:
|
||||
self._input_resolver = InputResolver(input_instance=self._input)
|
||||
else:
|
||||
self._input_resolver = InputResolver(input_instance=self._input,
|
||||
is_encode_binary=False)
|
||||
|
||||
def add_parameters(self, **params):
|
||||
for key, value in params.items():
|
||||
self.parameters[key] = value
|
||||
|
||||
def add_resources(self, resources):
|
||||
self.resources = resources
|
||||
|
||||
def to_request_object(self) -> str:
|
||||
"""Convert data to json, called from http request.
|
||||
Returns:
|
||||
str: Json string.
|
||||
"""
|
||||
self.input = next(self._input_resolver)
|
||||
o = {
|
||||
k: v
|
||||
for k, v in self.__dict__.items()
|
||||
if not (k.startswith('_') or k.startswith('task')
|
||||
or k.startswith('function') or v is None)
|
||||
}
|
||||
return o
|
||||
|
||||
def get_aiohttp_payload(self):
|
||||
"""Get http payload.
|
||||
If there are form, return form data, otherwise
|
||||
return input and parameter body.
|
||||
|
||||
Returns:
|
||||
is_form, data: if there are are form, is_form is true.
|
||||
"""
|
||||
data = self.to_request_object()
|
||||
if self._form is not None:
|
||||
form = aiohttp.FormData()
|
||||
for key, value in self._form.items():
|
||||
form.add_field(key, value)
|
||||
form.add_field('model', data['model'])
|
||||
if 'input' in data:
|
||||
form.add_field('input', json.dumps(data['input']))
|
||||
form.add_field('parameters', json.dumps(data['parameters']))
|
||||
return True, form()
|
||||
"""
|
||||
mp_writer = aiohttp.MultipartWriter('mixed')
|
||||
mp_writer.append('model=%s'%self.model)
|
||||
mp_writer.append('input=%s' % json.dumps(self._input))
|
||||
mp_writer.append('parameters=%s'%json.dumps(self.parameters))
|
||||
mp_writer.append(form())
|
||||
return True, mp_writer
|
||||
"""
|
||||
else:
|
||||
return False, data
|
||||
|
||||
def get_http_payload(self):
|
||||
"""Get http payload.
|
||||
If there are form, return form data, otherwise
|
||||
return input and parameter body.
|
||||
|
||||
Returns:
|
||||
is_form, data: if there are are form, is_form is true.
|
||||
"""
|
||||
data = self.to_request_object()
|
||||
if self._form is not None:
|
||||
return True, self._form, data
|
||||
else:
|
||||
return False, None, data
|
||||
|
||||
def get_websocket_start_data(self):
|
||||
"""Process websocket start data.
|
||||
If the input data is str, can carry the data in start action package,
|
||||
otherwise only parameters.
|
||||
Current, only one binary input is supported.
|
||||
Return: is_binary, start_package
|
||||
"""
|
||||
if self._is_binary_input:
|
||||
return self._only_parameters()
|
||||
else:
|
||||
for content in self._input_resolver:
|
||||
self.input = content
|
||||
break
|
||||
|
||||
data = {
|
||||
k: v
|
||||
for k, v in self.__dict__.items()
|
||||
if not (k.startswith('_') or v is None)
|
||||
}
|
||||
return data
|
||||
|
||||
def get_websocket_continue_data(self):
|
||||
for content in self._input_resolver:
|
||||
yield content
|
||||
|
||||
def _to_json_only_data(self) -> str:
|
||||
o = {
|
||||
k: v
|
||||
for k, v in self.__dict__.items()
|
||||
if not (k.startswith('_') or k.startswith('param'))
|
||||
}
|
||||
return json.dumps(o, default=lambda o: o.__dict__)
|
||||
|
||||
def get_batch_binary_data(self) -> bytes:
|
||||
"""Get binary data. used in streaming mode none and
|
||||
out (input is not streaming), we send data in one package.
|
||||
In this case only has one field input.
|
||||
|
||||
Returns:
|
||||
bytes: The binary content, such as audio,image,video file content.
|
||||
"""
|
||||
for content in self._input_resolver:
|
||||
return content
|
||||
|
||||
def _only_parameters(self) -> str:
|
||||
obj = {'model': self.model, 'parameters': self.parameters, 'input': {}}
|
||||
if self.task is not None:
|
||||
obj['task'] = self.task
|
||||
if self.task_group is not None:
|
||||
obj['task_group'] = self.task_group
|
||||
if self.function is not None:
|
||||
obj['function'] = self.function
|
||||
if self.resources is not None:
|
||||
obj['resources'] = self.resources
|
||||
return obj
|
||||
|
||||
def to_query_parameters(self) -> str:
|
||||
query_string = '?'
|
||||
for key, value in self.parameters.items:
|
||||
param = '%s/%s&' % (key, value)
|
||||
query_string += param
|
||||
query_string = query_string[0:-1] # remove last #
|
||||
return urlencode(query_string)
|
||||
@@ -0,0 +1,138 @@
|
||||
# Copyright (c) Alibaba, Inc. and its affiliates.
|
||||
from urllib.parse import urlencode
|
||||
|
||||
import dashscope
|
||||
from dashscope.api_entities.api_request_data import ApiRequestData
|
||||
from dashscope.api_entities.http_request import HttpRequest
|
||||
from dashscope.api_entities.websocket_request import WebSocketRequest
|
||||
from dashscope.common.constants import (REQUEST_TIMEOUT_KEYWORD,
|
||||
SERVICE_API_PATH, ApiProtocol,
|
||||
HTTPMethod)
|
||||
from dashscope.common.error import InputDataRequired, UnsupportedApiProtocol
|
||||
from dashscope.common.logging import logger
|
||||
from dashscope.protocol.websocket import WebsocketStreamingMode
|
||||
from dashscope.api_entities.encryption import Encryption
|
||||
|
||||
def _get_protocol_params(kwargs):
|
||||
api_protocol = kwargs.pop('api_protocol', ApiProtocol.HTTPS)
|
||||
ws_stream_mode = kwargs.pop('ws_stream_mode', WebsocketStreamingMode.OUT)
|
||||
is_binary_input = kwargs.pop('is_binary_input', False)
|
||||
http_method = kwargs.pop('http_method', HTTPMethod.POST)
|
||||
stream = kwargs.pop('stream', False)
|
||||
if not stream and ws_stream_mode == WebsocketStreamingMode.OUT:
|
||||
ws_stream_mode = WebsocketStreamingMode.NONE
|
||||
|
||||
async_request = kwargs.pop('async_request', False)
|
||||
query = kwargs.pop('query', False)
|
||||
headers = kwargs.pop('headers', None)
|
||||
request_timeout = kwargs.pop(REQUEST_TIMEOUT_KEYWORD, None)
|
||||
form = kwargs.pop('form', None)
|
||||
resources = kwargs.pop('resources', None)
|
||||
base_address = kwargs.pop('base_address', None)
|
||||
flattened_output = kwargs.pop('flattened_output', False)
|
||||
extra_url_parameters = kwargs.pop('extra_url_parameters', None)
|
||||
|
||||
# Extract user-agent from headers if present
|
||||
user_agent = ''
|
||||
if headers and 'user-agent' in headers:
|
||||
user_agent = headers.pop('user-agent')
|
||||
|
||||
return (api_protocol, ws_stream_mode, is_binary_input, http_method, stream,
|
||||
async_request, query, headers, request_timeout, form, resources,
|
||||
base_address, flattened_output, extra_url_parameters, user_agent)
|
||||
|
||||
|
||||
def _build_api_request(model: str,
|
||||
input: object,
|
||||
task_group: str,
|
||||
task: str,
|
||||
function: str,
|
||||
api_key: str,
|
||||
is_service=True,
|
||||
**kwargs):
|
||||
(api_protocol, ws_stream_mode, is_binary_input, http_method, stream,
|
||||
async_request, query, headers, request_timeout, form, resources,
|
||||
base_address, flattened_output, extra_url_parameters,
|
||||
user_agent) = _get_protocol_params(kwargs)
|
||||
task_id = kwargs.pop('task_id', None)
|
||||
enable_encryption = kwargs.pop('enable_encryption', False)
|
||||
encryption = None
|
||||
|
||||
if api_protocol in [ApiProtocol.HTTP, ApiProtocol.HTTPS]:
|
||||
if base_address is None:
|
||||
base_address = dashscope.base_http_api_url
|
||||
if not base_address.endswith('/'):
|
||||
http_url = base_address + '/'
|
||||
else:
|
||||
http_url = base_address
|
||||
|
||||
if is_service:
|
||||
http_url = http_url + SERVICE_API_PATH + '/'
|
||||
|
||||
if task_group:
|
||||
http_url += '%s/' % task_group
|
||||
if task:
|
||||
http_url += '%s/' % task
|
||||
if function:
|
||||
http_url += function
|
||||
if extra_url_parameters is not None and extra_url_parameters:
|
||||
http_url += '?' + urlencode(extra_url_parameters)
|
||||
|
||||
if enable_encryption is True:
|
||||
encryption = Encryption()
|
||||
encryption.initialize()
|
||||
if encryption.is_valid():
|
||||
logger.debug('encryption enabled')
|
||||
|
||||
request = HttpRequest(url=http_url,
|
||||
api_key=api_key,
|
||||
http_method=http_method,
|
||||
stream=stream,
|
||||
async_request=async_request,
|
||||
query=query,
|
||||
timeout=request_timeout,
|
||||
task_id=task_id,
|
||||
flattened_output=flattened_output,
|
||||
encryption=encryption,
|
||||
user_agent=user_agent)
|
||||
elif api_protocol == ApiProtocol.WEBSOCKET:
|
||||
if base_address is not None:
|
||||
websocket_url = base_address
|
||||
else:
|
||||
websocket_url = dashscope.base_websocket_api_url
|
||||
pre_task_id = kwargs.pop('pre_task_id', None)
|
||||
request = WebSocketRequest(url=websocket_url,
|
||||
api_key=api_key,
|
||||
stream=stream,
|
||||
ws_stream_mode=ws_stream_mode,
|
||||
is_binary_input=is_binary_input,
|
||||
timeout=request_timeout,
|
||||
flattened_output=flattened_output,
|
||||
pre_task_id=pre_task_id,
|
||||
user_agent=user_agent)
|
||||
else:
|
||||
raise UnsupportedApiProtocol(
|
||||
'Unsupported protocol: %s, support [http, https, websocket]' %
|
||||
api_protocol)
|
||||
|
||||
if headers is not None:
|
||||
request.add_headers(headers=headers)
|
||||
|
||||
if input is None and form is None:
|
||||
raise InputDataRequired('There is no input data and form data')
|
||||
|
||||
if encryption and encryption.is_valid():
|
||||
input = encryption.encrypt(input)
|
||||
|
||||
request_data = ApiRequestData(model,
|
||||
task_group=task_group,
|
||||
task=task,
|
||||
function=function,
|
||||
input=input,
|
||||
form=form,
|
||||
is_binary_input=is_binary_input,
|
||||
api_protocol=api_protocol)
|
||||
request_data.add_resources(resources)
|
||||
request_data.add_parameters(**kwargs)
|
||||
request.data = request_data
|
||||
return request
|
||||
@@ -0,0 +1,49 @@
|
||||
# Copyright (c) Alibaba, Inc. and its affiliates.
|
||||
|
||||
import os
|
||||
import platform
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
from dashscope.common.constants import DASHSCOPE_DISABLE_DATA_INSPECTION_ENV
|
||||
from dashscope.version import __version__
|
||||
|
||||
|
||||
class BaseRequest(ABC):
|
||||
def __init__(self, user_agent: str = '') -> None:
|
||||
try:
|
||||
platform_info = platform.platform()
|
||||
except Exception:
|
||||
platform_info = "unknown"
|
||||
|
||||
try:
|
||||
processor_info = platform.processor()
|
||||
except Exception:
|
||||
processor_info = "unknown"
|
||||
|
||||
ua = 'dashscope/%s; python/%s; platform/%s; processor/%s' % (
|
||||
__version__,
|
||||
platform.python_version(),
|
||||
platform_info,
|
||||
processor_info,
|
||||
)
|
||||
|
||||
# Append user_agent if provided and not empty
|
||||
if user_agent:
|
||||
ua += '; ' + user_agent
|
||||
|
||||
self.headers = {'user-agent': ua}
|
||||
disable_data_inspection = os.environ.get(
|
||||
DASHSCOPE_DISABLE_DATA_INSPECTION_ENV, 'true')
|
||||
|
||||
if (disable_data_inspection.lower() == 'false'):
|
||||
self.headers['X-DashScope-DataInspection'] = 'enable'
|
||||
|
||||
@abstractmethod
|
||||
def call(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
class AioBaseRequest(BaseRequest):
|
||||
@abstractmethod
|
||||
async def aio_call(self):
|
||||
raise NotImplementedError()
|
||||
@@ -0,0 +1,344 @@
|
||||
# Copyright (c) Alibaba, Inc. and its affiliates.
|
||||
|
||||
# adapter from openai sdk
|
||||
from dataclasses import dataclass
|
||||
from typing import List, Literal, Optional
|
||||
|
||||
from dashscope.common.base_type import BaseObjectMixin
|
||||
|
||||
|
||||
@dataclass(init=False)
|
||||
class CompletionUsage(BaseObjectMixin):
|
||||
completion_tokens: int
|
||||
"""Number of tokens in the generated completion."""
|
||||
|
||||
prompt_tokens: int
|
||||
"""Number of tokens in the prompt."""
|
||||
|
||||
total_tokens: int
|
||||
"""Total number of tokens used in the request (prompt + completion)."""
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
|
||||
@dataclass(init=False)
|
||||
class TopLogprob(BaseObjectMixin):
|
||||
token: str
|
||||
"""The token."""
|
||||
|
||||
bytes: Optional[List[int]] = None
|
||||
"""A list of integers representing the UTF-8 bytes representation of the token.
|
||||
|
||||
Useful in instances where characters are represented by multiple tokens and
|
||||
their byte representations must be combined to generate the correct text
|
||||
representation. Can be `null` if there is no bytes representation for the token.
|
||||
"""
|
||||
|
||||
logprob: float
|
||||
"""The log probability of this token, if it is within the top 20 most likely
|
||||
tokens.
|
||||
|
||||
Otherwise, the value `-9999.0` is used to signify that the token is very
|
||||
unlikely.
|
||||
"""
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
|
||||
@dataclass(init=False)
|
||||
class ChatCompletionTokenLogprob(BaseObjectMixin):
|
||||
token: str
|
||||
"""The token."""
|
||||
|
||||
bytes: Optional[List[int]] = None
|
||||
"""A list of integers representing the UTF-8 bytes representation of the token.
|
||||
|
||||
Useful in instances where characters are represented by multiple tokens and
|
||||
their byte representations must be combined to generate the correct text
|
||||
representation. Can be `null` if there is no bytes representation for the token.
|
||||
"""
|
||||
|
||||
logprob: float
|
||||
"""The log probability of this token, if it is within the top 20 most likely
|
||||
tokens.
|
||||
|
||||
Otherwise, the value `-9999.0` is used to signify that the token is very
|
||||
unlikely.
|
||||
"""
|
||||
|
||||
top_logprobs: List[TopLogprob]
|
||||
"""List of the most likely tokens and their log probability, at this token
|
||||
position.
|
||||
|
||||
In rare cases, there may be fewer than the number of requested `top_logprobs`
|
||||
returned.
|
||||
"""
|
||||
def __init__(self, **kwargs):
|
||||
if 'top_logprobs' in kwargs and kwargs[
|
||||
'top_logprobs'] is not None and kwargs['top_logprobs']:
|
||||
top_logprobs = []
|
||||
for logprob in kwargs['top_logprobs']:
|
||||
top_logprobs.append(ChatCompletionTokenLogprob(**logprob))
|
||||
self.top_logprobs = top_logprobs
|
||||
else:
|
||||
self.top_logprobs = None
|
||||
|
||||
super().__init__(**kwargs)
|
||||
|
||||
|
||||
@dataclass(init=False)
|
||||
class ChoiceLogprobs(BaseObjectMixin):
|
||||
content: Optional[List[ChatCompletionTokenLogprob]] = None
|
||||
"""A list of message content tokens with log probability information."""
|
||||
def __init__(self, **kwargs):
|
||||
if 'content' in kwargs and kwargs['content'] is not None and kwargs[
|
||||
'content']:
|
||||
logprobs = []
|
||||
for logprob in kwargs['content']:
|
||||
logprobs.append(ChatCompletionTokenLogprob(**logprob))
|
||||
self.content = logprobs
|
||||
else:
|
||||
self.content = None
|
||||
|
||||
super().__init__(**kwargs)
|
||||
|
||||
|
||||
@dataclass(init=False)
|
||||
class FunctionCall(BaseObjectMixin):
|
||||
arguments: str
|
||||
"""
|
||||
The arguments to call the function with, as generated by the model in JSON
|
||||
format. Note that the model does not always generate valid JSON, and may
|
||||
hallucinate parameters not defined by your function schema. Validate the
|
||||
arguments in your code before calling your function.
|
||||
"""
|
||||
|
||||
name: str
|
||||
"""The name of the function to call."""
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
|
||||
@dataclass(init=False)
|
||||
class Function(BaseObjectMixin):
|
||||
arguments: str
|
||||
"""
|
||||
The arguments to call the function with, as generated by the model in JSON
|
||||
format. Note that the model does not always generate valid JSON, and may
|
||||
hallucinate parameters not defined by your function schema. Validate the
|
||||
arguments in your code before calling your function.
|
||||
"""
|
||||
|
||||
name: str
|
||||
"""The name of the function to call."""
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
|
||||
@dataclass(init=False)
|
||||
class ChatCompletionMessageToolCall(BaseObjectMixin):
|
||||
id: str
|
||||
"""The ID of the tool call."""
|
||||
|
||||
function: Function
|
||||
"""The function that the model called."""
|
||||
|
||||
type: Literal['function']
|
||||
"""The type of the tool. Currently, only `function` is supported."""
|
||||
def __init__(self, **kwargs):
|
||||
if 'function' in kwargs and kwargs['function'] is not None and kwargs[
|
||||
'function']:
|
||||
self.function = Function(**kwargs.pop('function', {}))
|
||||
else:
|
||||
self.function = None
|
||||
|
||||
super().__init__(**kwargs)
|
||||
|
||||
|
||||
@dataclass(init=False)
|
||||
class ChatCompletionMessage(BaseObjectMixin):
|
||||
content: Optional[str] = None
|
||||
"""The contents of the message."""
|
||||
|
||||
role: Literal['assistant']
|
||||
"""The role of the author of this message."""
|
||||
|
||||
function_call: Optional[FunctionCall] = None
|
||||
"""Deprecated and replaced by `tool_calls`.
|
||||
|
||||
The name and arguments of a function that should be called, as generated by the
|
||||
model.
|
||||
"""
|
||||
|
||||
tool_calls: Optional[List[ChatCompletionMessageToolCall]] = None
|
||||
"""The tool calls generated by the model, such as function calls."""
|
||||
def __init__(self, **kwargs):
|
||||
if 'function_call' in kwargs and kwargs[
|
||||
'function_call'] is not None and kwargs['function_call']:
|
||||
self.function_call = FunctionCall(
|
||||
**kwargs.pop('function_call', {}))
|
||||
|
||||
if 'tool_calls' in kwargs and kwargs[
|
||||
'tool_calls'] is not None and kwargs['tool_calls']:
|
||||
tool_calls = []
|
||||
for tool_call in kwargs['tool_calls']:
|
||||
tool_calls.append(ChatCompletionMessageToolCall(**tool_call))
|
||||
self.tool_calls = tool_calls
|
||||
|
||||
super().__init__(**kwargs)
|
||||
|
||||
|
||||
@dataclass(init=False)
|
||||
class Choice(BaseObjectMixin):
|
||||
finish_reason: Literal['stop', 'length', 'tool_calls', 'content_filter',
|
||||
'function_call']
|
||||
"""The reason the model stopped generating tokens.
|
||||
|
||||
This will be `stop` if the model hit a natural stop point or a provided stop
|
||||
sequence, `length` if the maximum number of tokens specified in the request was
|
||||
reached, `content_filter` if content was omitted due to a flag from our content
|
||||
filters, `tool_calls` if the model called a tool, or `function_call`
|
||||
(deprecated) if the model called a function.
|
||||
"""
|
||||
|
||||
index: int
|
||||
"""The index of the choice in the list of choices."""
|
||||
|
||||
logprobs: Optional[ChoiceLogprobs] = None
|
||||
"""Log probability information for the choice."""
|
||||
|
||||
message: ChatCompletionMessage
|
||||
"""A chat completion message generated by the model."""
|
||||
def __init__(self, **kwargs):
|
||||
if 'message' in kwargs and kwargs['message'] is not None and kwargs[
|
||||
'message']:
|
||||
self.message = ChatCompletionMessage(**kwargs.pop('message', {}))
|
||||
else:
|
||||
self.message = None
|
||||
|
||||
if 'logprobs' in kwargs and kwargs['logprobs'] is not None and kwargs[
|
||||
'logprobs']:
|
||||
self.logprobs = ChoiceLogprobs(**kwargs.pop('logprobs', {}))
|
||||
|
||||
super().__init__(**kwargs)
|
||||
|
||||
|
||||
@dataclass(init=False)
|
||||
class ChatCompletion(BaseObjectMixin):
|
||||
status_code: int
|
||||
"""The call response status_code, 200 indicate create success.
|
||||
"""
|
||||
code: str
|
||||
"""The request failed, this is the error code.
|
||||
"""
|
||||
message: str
|
||||
"""The request failed, this is the error message.
|
||||
"""
|
||||
id: str
|
||||
"""A unique identifier for the chat completion.
|
||||
"""
|
||||
choices: List[Choice]
|
||||
"""A list of chat completion choices.
|
||||
|
||||
Can be more than one if `n` is greater than 1.
|
||||
"""
|
||||
|
||||
created: int
|
||||
"""The Unix timestamp (in seconds) of when the chat completion was created."""
|
||||
|
||||
model: str
|
||||
"""The model used for the chat completion."""
|
||||
|
||||
object: Literal['chat.completion']
|
||||
"""The object type, which is always `chat.completion`."""
|
||||
|
||||
system_fingerprint: Optional[str] = None
|
||||
"""This fingerprint represents the backend configuration that the model runs with.
|
||||
|
||||
Can be used in conjunction with the `seed` request parameter to understand when
|
||||
backend changes have been made that might impact determinism.
|
||||
"""
|
||||
|
||||
usage: Optional[CompletionUsage] = None
|
||||
"""Usage statistics for the completion request."""
|
||||
def __init__(self, **kwargs):
|
||||
if 'usage' in kwargs and kwargs['usage'] is not None and kwargs[
|
||||
'usage']:
|
||||
self.usage = CompletionUsage(**kwargs.pop('usage', {}))
|
||||
else:
|
||||
self.usage = None
|
||||
|
||||
if 'choices' in kwargs and kwargs['choices'] is not None and kwargs[
|
||||
'choices']:
|
||||
choices = []
|
||||
for choice in kwargs.pop('choices', []):
|
||||
choices.append(Choice(**choice))
|
||||
self.choices = choices
|
||||
else:
|
||||
self.choices = None
|
||||
super().__init__(**kwargs)
|
||||
|
||||
|
||||
@dataclass(init=False)
|
||||
class ChatCompletionChunk(BaseObjectMixin):
|
||||
status_code: int
|
||||
"""The call response status_code, 200 indicate create success.
|
||||
"""
|
||||
code: str
|
||||
"""The request failed, this is the error code.
|
||||
"""
|
||||
message: str
|
||||
"""The request failed, this is the error message.
|
||||
"""
|
||||
id: str
|
||||
"""A unique identifier for the chat completion. Each chunk has the same ID."""
|
||||
|
||||
choices: List[Choice]
|
||||
"""A list of chat completion choices.
|
||||
|
||||
Can contain more than one elements if `n` is greater than 1. Can also be empty
|
||||
for the last chunk if you set `stream_options: {"include_usage": true}`.
|
||||
"""
|
||||
|
||||
created: int
|
||||
"""The Unix timestamp (in seconds) of when the chat completion was created.
|
||||
|
||||
Each chunk has the same timestamp.
|
||||
"""
|
||||
|
||||
model: str
|
||||
"""The model to generate the completion."""
|
||||
|
||||
object: Literal['chat.completion.chunk']
|
||||
"""The object type, which is always `chat.completion.chunk`."""
|
||||
|
||||
system_fingerprint: Optional[str] = None
|
||||
"""
|
||||
This fingerprint represents the backend configuration that the model runs with.
|
||||
Can be used in conjunction with the `seed` request parameter to understand when
|
||||
backend changes have been made that might impact determinism.
|
||||
"""
|
||||
|
||||
usage: Optional[CompletionUsage] = None
|
||||
"""
|
||||
An optional field that will only be present when you set
|
||||
`stream_options: {"include_usage": true}` in your request. When present, it
|
||||
contains a null value except for the last chunk which contains the token usage
|
||||
statistics for the entire request.
|
||||
"""
|
||||
def __init__(self, **kwargs):
|
||||
if 'usage' in kwargs and kwargs['usage'] is not None and kwargs[
|
||||
'usage']:
|
||||
self.usage = CompletionUsage(**kwargs.pop('usage', {}))
|
||||
else:
|
||||
self.usage = None
|
||||
|
||||
if 'choices' in kwargs and kwargs['choices'] is not None and kwargs[
|
||||
'choices']:
|
||||
choices = []
|
||||
for choice in kwargs.pop('choices', []):
|
||||
choices.append(Choice(**choice))
|
||||
self.choices = choices
|
||||
else:
|
||||
self.choices = None
|
||||
super().__init__(**kwargs)
|
||||
@@ -0,0 +1,713 @@
|
||||
# Copyright (c) Alibaba, Inc. and its affiliates.
|
||||
|
||||
import json
|
||||
from dataclasses import dataclass
|
||||
from http import HTTPStatus
|
||||
from typing import Any, Dict, List, Union
|
||||
|
||||
|
||||
@dataclass(init=False)
|
||||
class DictMixin(dict):
|
||||
__slots__ = ()
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def __getitem__(self, key):
|
||||
return super().__getitem__(key)
|
||||
|
||||
def __copy__(self):
|
||||
return type(self)(**self)
|
||||
|
||||
def __deepcopy__(self, memo):
|
||||
id_self = id(self)
|
||||
_copy = memo.get(id_self)
|
||||
if _copy is None:
|
||||
_copy = type(self)(**self)
|
||||
memo[id_self] = _copy
|
||||
return _copy
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
return super().__setitem__(key, value)
|
||||
|
||||
def __delitem__(self, key):
|
||||
return super().__delitem__(key)
|
||||
|
||||
def get(self, key, default=None):
|
||||
return super().get(key, default)
|
||||
|
||||
def setdefault(self, key, default=None):
|
||||
return super().setdefault(key, default)
|
||||
|
||||
def pop(self, key, default: Any):
|
||||
return super().pop(key, default)
|
||||
|
||||
def update(self, **kwargs):
|
||||
super().update(**kwargs)
|
||||
|
||||
def __contains__(self, key):
|
||||
return super().__contains__(key)
|
||||
|
||||
def copy(self):
|
||||
return type(self)(self)
|
||||
|
||||
def getattr(self, attr):
|
||||
return super().get(attr)
|
||||
|
||||
def setattr(self, attr, value):
|
||||
return super().__setitem__(attr, value)
|
||||
|
||||
def __getattr__(self, attr):
|
||||
return self[attr]
|
||||
|
||||
def __setattr__(self, attr, value):
|
||||
self[attr] = value
|
||||
|
||||
def __repr__(self):
|
||||
return '{0}({1})'.format(type(self).__name__, super().__repr__())
|
||||
|
||||
def __str__(self):
|
||||
return json.dumps(self, ensure_ascii=False)
|
||||
|
||||
|
||||
@dataclass(init=False)
|
||||
class DashScopeAPIResponse(DictMixin):
|
||||
"""The response content
|
||||
|
||||
Args:
|
||||
request_id (str): The request id.
|
||||
status_code (int): HTTP status code, 200 indicates that the
|
||||
request was successful, and others indicate an error。
|
||||
code (str): Error code if error occurs, otherwise empty str.
|
||||
message (str): Set to error message on error.
|
||||
output (Any): The request output.
|
||||
usage (Any): The request usage information.
|
||||
"""
|
||||
status_code: int
|
||||
request_id: str
|
||||
code: str
|
||||
message: str
|
||||
output: Any
|
||||
usage: Any
|
||||
|
||||
def __init__(self,
|
||||
status_code: int,
|
||||
request_id: str = '',
|
||||
code: str = '',
|
||||
message: str = '',
|
||||
output: Any = None,
|
||||
usage: Any = None,
|
||||
**kwargs):
|
||||
super().__init__(status_code=status_code,
|
||||
request_id=request_id,
|
||||
code=code,
|
||||
message=message,
|
||||
output=output,
|
||||
usage=usage,
|
||||
**kwargs)
|
||||
|
||||
|
||||
class Role:
|
||||
USER = 'user'
|
||||
SYSTEM = 'system'
|
||||
BOT = 'bot'
|
||||
ASSISTANT = 'assistant'
|
||||
ATTACHMENT = 'attachment'
|
||||
|
||||
|
||||
class Message(DictMixin):
|
||||
role: str
|
||||
content: Union[str, List]
|
||||
|
||||
def __init__(self, role: str, content: str = None, **kwargs):
|
||||
super().__init__(role=role, content=content, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def from_generation_response(cls, response: DictMixin):
|
||||
if 'text' in response.output and response.output['text'] is not None:
|
||||
content = response.output['text']
|
||||
return Message(role=Role.ASSISTANT, content=content)
|
||||
else:
|
||||
return response.output.choices[0]['message']
|
||||
|
||||
@classmethod
|
||||
def from_conversation_response(cls, response: DictMixin):
|
||||
return cls.from_generation_response(response)
|
||||
|
||||
|
||||
@dataclass(init=False)
|
||||
class Choice(DictMixin):
|
||||
finish_reason: str
|
||||
message: Message
|
||||
|
||||
def __init__(self,
|
||||
finish_reason: str = None,
|
||||
message: Message = None,
|
||||
**kwargs):
|
||||
msgObject = None
|
||||
if message is not None and message:
|
||||
msgObject = Message(**message)
|
||||
super().__init__(finish_reason=finish_reason,
|
||||
message=msgObject,
|
||||
**kwargs)
|
||||
|
||||
|
||||
@dataclass(init=False)
|
||||
class Audio(DictMixin):
|
||||
data: str
|
||||
url: str
|
||||
id: str
|
||||
expires_at: int
|
||||
|
||||
def __init__(self,
|
||||
data: str = None,
|
||||
url: str = None,
|
||||
id: str = None,
|
||||
expires_at: int = None,
|
||||
**kwargs):
|
||||
super().__init__(data=data,
|
||||
url=url,
|
||||
id=id,
|
||||
expires_at=expires_at,
|
||||
**kwargs)
|
||||
|
||||
|
||||
@dataclass(init=False)
|
||||
class GenerationOutput(DictMixin):
|
||||
text: str
|
||||
choices: List[Choice]
|
||||
finish_reason: str
|
||||
|
||||
def __init__(self,
|
||||
text: str = None,
|
||||
finish_reason: str = None,
|
||||
choices: List[Choice] = None,
|
||||
**kwargs):
|
||||
chs = None
|
||||
if choices is not None:
|
||||
chs = []
|
||||
for choice in choices:
|
||||
chs.append(Choice(**choice))
|
||||
super().__init__(text=text,
|
||||
finish_reason=finish_reason,
|
||||
choices=chs,
|
||||
**kwargs)
|
||||
|
||||
|
||||
@dataclass(init=False)
|
||||
class GenerationUsage(DictMixin):
|
||||
input_tokens: int
|
||||
output_tokens: int
|
||||
|
||||
def __init__(self,
|
||||
input_tokens: int = 0,
|
||||
output_tokens: int = 0,
|
||||
**kwargs):
|
||||
super().__init__(input_tokens=input_tokens,
|
||||
output_tokens=output_tokens,
|
||||
**kwargs)
|
||||
|
||||
|
||||
@dataclass(init=False)
|
||||
class GenerationResponse(DashScopeAPIResponse):
|
||||
output: GenerationOutput
|
||||
usage: GenerationUsage
|
||||
|
||||
@staticmethod
|
||||
def from_api_response(api_response: DashScopeAPIResponse):
|
||||
if api_response.status_code == HTTPStatus.OK:
|
||||
usage = {}
|
||||
if api_response.usage:
|
||||
usage = api_response.usage
|
||||
|
||||
return GenerationResponse(
|
||||
status_code=api_response.status_code,
|
||||
request_id=api_response.request_id,
|
||||
code=api_response.code,
|
||||
message=api_response.message,
|
||||
output=GenerationOutput(**api_response.output),
|
||||
usage=GenerationUsage(**usage))
|
||||
else:
|
||||
return GenerationResponse(status_code=api_response.status_code,
|
||||
request_id=api_response.request_id,
|
||||
code=api_response.code,
|
||||
message=api_response.message)
|
||||
|
||||
|
||||
@dataclass(init=False)
|
||||
class MultiModalConversationOutput(DictMixin):
|
||||
choices: List[Choice]
|
||||
audio: Audio
|
||||
|
||||
def __init__(self,
|
||||
text: str = None,
|
||||
finish_reason: str = None,
|
||||
choices: List[Choice] = None,
|
||||
audio: Audio = None,
|
||||
**kwargs):
|
||||
chs = None
|
||||
if choices is not None:
|
||||
chs = []
|
||||
for choice in choices:
|
||||
chs.append(Choice(**choice))
|
||||
if audio is not None:
|
||||
audio = Audio(**audio)
|
||||
super().__init__(text=text,
|
||||
finish_reason=finish_reason,
|
||||
choices=chs,
|
||||
audio=audio,
|
||||
**kwargs)
|
||||
|
||||
|
||||
@dataclass(init=False)
|
||||
class MultiModalConversationUsage(DictMixin):
|
||||
input_tokens: int
|
||||
output_tokens: int
|
||||
characters: int
|
||||
|
||||
# TODO add image usage info.
|
||||
|
||||
def __init__(self,
|
||||
input_tokens: int = 0,
|
||||
output_tokens: int = 0,
|
||||
characters: int = 0,
|
||||
**kwargs):
|
||||
super().__init__(input_tokens=input_tokens,
|
||||
output_tokens=output_tokens,
|
||||
characters=characters,
|
||||
**kwargs)
|
||||
|
||||
|
||||
@dataclass(init=False)
|
||||
class MultiModalConversationResponse(DashScopeAPIResponse):
|
||||
output: MultiModalConversationOutput
|
||||
usage: MultiModalConversationUsage
|
||||
|
||||
@staticmethod
|
||||
def from_api_response(api_response: DashScopeAPIResponse):
|
||||
if api_response.status_code == HTTPStatus.OK:
|
||||
usage = {}
|
||||
if api_response.usage:
|
||||
usage = api_response.usage
|
||||
|
||||
return MultiModalConversationResponse(
|
||||
status_code=api_response.status_code,
|
||||
request_id=api_response.request_id,
|
||||
code=api_response.code,
|
||||
message=api_response.message,
|
||||
output=MultiModalConversationOutput(**api_response.output),
|
||||
usage=MultiModalConversationUsage(**usage))
|
||||
else:
|
||||
return MultiModalConversationResponse(
|
||||
status_code=api_response.status_code,
|
||||
request_id=api_response.request_id,
|
||||
code=api_response.code,
|
||||
message=api_response.message)
|
||||
|
||||
|
||||
@dataclass(init=False)
|
||||
class ConversationResponse(GenerationResponse):
|
||||
pass
|
||||
|
||||
|
||||
@dataclass(init=False)
|
||||
class TranscriptionOutput(DictMixin):
|
||||
task_id: str
|
||||
task_status: str
|
||||
|
||||
def __init__(self, task_id: str, task_status: str, **kwargs):
|
||||
super().__init__(task_id=task_id, task_status=task_status, **kwargs)
|
||||
|
||||
|
||||
@dataclass(init=False)
|
||||
class TranscriptionUsage(DictMixin):
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
|
||||
@dataclass(init=False)
|
||||
class TranscriptionResponse(DashScopeAPIResponse):
|
||||
output: TranscriptionOutput
|
||||
usage: TranscriptionUsage
|
||||
|
||||
@staticmethod
|
||||
def from_api_response(api_response: DashScopeAPIResponse):
|
||||
if api_response.status_code == HTTPStatus.OK:
|
||||
output = None
|
||||
usage = None
|
||||
if api_response.output is not None:
|
||||
output = TranscriptionOutput(**api_response.output)
|
||||
if api_response.usage is not None:
|
||||
usage = TranscriptionUsage(**api_response.usage)
|
||||
|
||||
return TranscriptionResponse(status_code=api_response.status_code,
|
||||
request_id=api_response.request_id,
|
||||
code=api_response.code,
|
||||
message=api_response.message,
|
||||
output=output,
|
||||
usage=usage)
|
||||
|
||||
else:
|
||||
return TranscriptionResponse(status_code=api_response.status_code,
|
||||
request_id=api_response.request_id,
|
||||
code=api_response.code,
|
||||
message=api_response.message)
|
||||
|
||||
|
||||
@dataclass(init=False)
|
||||
class RecognitionOutput(DictMixin):
|
||||
sentence: Union[Dict[str, Any], List[Any]]
|
||||
|
||||
def __init__(self, sentence: Union[Dict[str, Any], List[Any]], **kwargs):
|
||||
super().__init__(sentence=sentence, **kwargs)
|
||||
|
||||
|
||||
@dataclass(init=False)
|
||||
class RecognitionUsage(DictMixin):
|
||||
duration: int
|
||||
|
||||
def __init__(self, duration: int = 0, **kwargs):
|
||||
super().__init__(duration=duration, **kwargs)
|
||||
|
||||
|
||||
@dataclass(init=False)
|
||||
class RecognitionResponse(DashScopeAPIResponse):
|
||||
output: RecognitionOutput
|
||||
usage: RecognitionUsage
|
||||
|
||||
@staticmethod
|
||||
def from_api_response(api_response: DashScopeAPIResponse):
|
||||
if api_response.status_code == HTTPStatus.OK:
|
||||
output = None
|
||||
usage = None
|
||||
if api_response.output is not None:
|
||||
if 'sentence' in api_response.output:
|
||||
output = RecognitionOutput(**api_response.output)
|
||||
if api_response.usage is not None:
|
||||
usage = RecognitionUsage(**api_response.usage)
|
||||
|
||||
return RecognitionResponse(status_code=api_response.status_code,
|
||||
request_id=api_response.request_id,
|
||||
code=api_response.code,
|
||||
message=api_response.message,
|
||||
output=output,
|
||||
usage=usage)
|
||||
|
||||
else:
|
||||
return RecognitionResponse(status_code=api_response.status_code,
|
||||
request_id=api_response.request_id,
|
||||
code=api_response.code,
|
||||
message=api_response.message)
|
||||
|
||||
@staticmethod
|
||||
def is_sentence_end(sentence: Dict[str, Any]) -> bool:
|
||||
"""Determine whether the speech recognition result is the end of a sentence.
|
||||
This is a static method.
|
||||
"""
|
||||
result = False
|
||||
if sentence is not None and 'end_time' in sentence and sentence[
|
||||
'end_time'] is not None:
|
||||
result = True
|
||||
return result
|
||||
|
||||
|
||||
@dataclass(init=False)
|
||||
class SpeechSynthesisOutput(DictMixin):
|
||||
sentence: Dict[str, Any]
|
||||
|
||||
def __init__(self, sentence: Dict[str, Any], **kwargs):
|
||||
super().__init__(sentence=sentence, **kwargs)
|
||||
|
||||
|
||||
@dataclass(init=False)
|
||||
class SpeechSynthesisUsage(DictMixin):
|
||||
characters: int
|
||||
|
||||
def __init__(self, characters: int = 0, **kwargs):
|
||||
super().__init__(characters=characters, **kwargs)
|
||||
|
||||
|
||||
@dataclass(init=False)
|
||||
class SpeechSynthesisResponse(DashScopeAPIResponse):
|
||||
output: SpeechSynthesisOutput
|
||||
usage: SpeechSynthesisUsage
|
||||
|
||||
@staticmethod
|
||||
def from_api_response(api_response: DashScopeAPIResponse):
|
||||
if api_response.status_code == HTTPStatus.OK:
|
||||
output = None
|
||||
usage = None
|
||||
if api_response.output is not None:
|
||||
output = SpeechSynthesisOutput(**api_response.output)
|
||||
if api_response.usage is not None:
|
||||
usage = SpeechSynthesisUsage(**api_response.usage)
|
||||
|
||||
return SpeechSynthesisResponse(
|
||||
status_code=api_response.status_code,
|
||||
request_id=api_response.request_id,
|
||||
code=api_response.code,
|
||||
message=api_response.message,
|
||||
output=output,
|
||||
usage=usage)
|
||||
|
||||
else:
|
||||
return SpeechSynthesisResponse(
|
||||
status_code=api_response.status_code,
|
||||
request_id=api_response.request_id,
|
||||
code=api_response.code,
|
||||
message=api_response.message)
|
||||
|
||||
|
||||
@dataclass(init=False)
|
||||
class ImageSynthesisResult(DictMixin):
|
||||
url: str
|
||||
|
||||
def __init__(self, url: str = '', **kwargs) -> None:
|
||||
super().__init__(url=url, **kwargs)
|
||||
|
||||
|
||||
@dataclass(init=False)
|
||||
class ImageSynthesisOutput(DictMixin):
|
||||
task_id: str
|
||||
task_status: str
|
||||
results: List[ImageSynthesisResult]
|
||||
|
||||
def __init__(self,
|
||||
task_id: str = None,
|
||||
task_status: str = None,
|
||||
results: List[ImageSynthesisResult] = [],
|
||||
**kwargs):
|
||||
res = []
|
||||
if len(results) > 0:
|
||||
for result in results:
|
||||
res.append(ImageSynthesisResult(**result))
|
||||
super().__init__(self,
|
||||
task_id=task_id,
|
||||
task_status=task_status,
|
||||
results=res,
|
||||
**kwargs)
|
||||
|
||||
|
||||
@dataclass(init=False)
|
||||
class VideoSynthesisOutput(DictMixin):
|
||||
task_id: str
|
||||
task_status: str
|
||||
video_url: str
|
||||
|
||||
def __init__(self,
|
||||
task_id: str,
|
||||
task_status: str,
|
||||
video_url: str = '',
|
||||
**kwargs):
|
||||
super().__init__(self,
|
||||
task_id=task_id,
|
||||
task_status=task_status,
|
||||
video_url=video_url,
|
||||
**kwargs)
|
||||
|
||||
|
||||
@dataclass(init=False)
|
||||
class ImageSynthesisUsage(DictMixin):
|
||||
image_count: int
|
||||
|
||||
def __init__(self, image_count: int = None, **kwargs):
|
||||
super().__init__(image_count=image_count, **kwargs)
|
||||
|
||||
|
||||
@dataclass(init=False)
|
||||
class VideoSynthesisUsage(DictMixin):
|
||||
video_count: int
|
||||
video_duration: int
|
||||
video_ratio: str
|
||||
|
||||
def __init__(self,
|
||||
video_count: int = 1,
|
||||
video_duration: int = 0,
|
||||
video_ratio: str = '',
|
||||
**kwargs):
|
||||
super().__init__(video_count=video_count,
|
||||
video_duration=video_duration,
|
||||
video_ratio=video_ratio,
|
||||
**kwargs)
|
||||
|
||||
|
||||
@dataclass(init=False)
|
||||
class ImageSynthesisResponse(DashScopeAPIResponse):
|
||||
output: ImageSynthesisOutput
|
||||
usage: ImageSynthesisUsage
|
||||
|
||||
@staticmethod
|
||||
def from_api_response(api_response: DashScopeAPIResponse):
|
||||
if api_response.status_code == HTTPStatus.OK:
|
||||
output = None
|
||||
usage = None
|
||||
if api_response.output is not None:
|
||||
output = ImageSynthesisOutput(**api_response.output)
|
||||
if api_response.usage is not None:
|
||||
usage = ImageSynthesisUsage(**api_response.usage)
|
||||
|
||||
return ImageSynthesisResponse(status_code=api_response.status_code,
|
||||
request_id=api_response.request_id,
|
||||
code=api_response.code,
|
||||
message=api_response.message,
|
||||
output=output,
|
||||
usage=usage)
|
||||
|
||||
else:
|
||||
return ImageSynthesisResponse(status_code=api_response.status_code,
|
||||
request_id=api_response.request_id,
|
||||
code=api_response.code,
|
||||
message=api_response.message)
|
||||
|
||||
|
||||
@dataclass(init=False)
|
||||
class VideoSynthesisResponse(DashScopeAPIResponse):
|
||||
output: VideoSynthesisOutput
|
||||
usage: VideoSynthesisUsage
|
||||
|
||||
@staticmethod
|
||||
def from_api_response(api_response: DashScopeAPIResponse):
|
||||
if api_response.status_code == HTTPStatus.OK:
|
||||
output = None
|
||||
usage = None
|
||||
if api_response.output is not None:
|
||||
output = VideoSynthesisOutput(**api_response.output)
|
||||
if api_response.usage is not None:
|
||||
usage = VideoSynthesisUsage(**api_response.usage)
|
||||
|
||||
return VideoSynthesisResponse(status_code=api_response.status_code,
|
||||
request_id=api_response.request_id,
|
||||
code=api_response.code,
|
||||
message=api_response.message,
|
||||
output=output,
|
||||
usage=usage)
|
||||
|
||||
else:
|
||||
return VideoSynthesisResponse(status_code=api_response.status_code,
|
||||
request_id=api_response.request_id,
|
||||
code=api_response.code,
|
||||
message=api_response.message)
|
||||
|
||||
|
||||
@dataclass(init=False)
|
||||
class ReRankResult(DictMixin):
|
||||
index: int
|
||||
relevance_score: float
|
||||
document: Dict = None
|
||||
|
||||
def __init__(self,
|
||||
index: int,
|
||||
relevance_score: float,
|
||||
document: Dict = None,
|
||||
**kwargs):
|
||||
super().__init__(index=index,
|
||||
relevance_score=relevance_score,
|
||||
document=document,
|
||||
**kwargs)
|
||||
|
||||
|
||||
@dataclass(init=False)
|
||||
class ReRankOutput(DictMixin):
|
||||
results: List[ReRankResult]
|
||||
|
||||
def __init__(self, results: List[ReRankResult] = None, **kwargs):
|
||||
ress = None
|
||||
if results is not None:
|
||||
ress = []
|
||||
for res in results:
|
||||
ress.append(ReRankResult(**res))
|
||||
super().__init__(results=ress, **kwargs)
|
||||
|
||||
|
||||
@dataclass(init=False)
|
||||
class ReRankUsage(DictMixin):
|
||||
total_tokens: int
|
||||
|
||||
def __init__(self, total_tokens=None, **kwargs):
|
||||
super().__init__(total_tokens=total_tokens, **kwargs)
|
||||
|
||||
|
||||
@dataclass(init=False)
|
||||
class ReRankResponse(DashScopeAPIResponse):
|
||||
output: ReRankOutput
|
||||
usage: GenerationUsage
|
||||
|
||||
@staticmethod
|
||||
def from_api_response(api_response: DashScopeAPIResponse):
|
||||
if api_response.status_code == HTTPStatus.OK:
|
||||
usage = {}
|
||||
if api_response.usage:
|
||||
usage = api_response.usage
|
||||
|
||||
return ReRankResponse(status_code=api_response.status_code,
|
||||
request_id=api_response.request_id,
|
||||
code=api_response.code,
|
||||
message=api_response.message,
|
||||
output=ReRankOutput(**api_response.output),
|
||||
usage=ReRankUsage(**usage))
|
||||
else:
|
||||
return ReRankResponse(status_code=api_response.status_code,
|
||||
request_id=api_response.request_id,
|
||||
code=api_response.code,
|
||||
message=api_response.message)
|
||||
|
||||
|
||||
@dataclass(init=False)
|
||||
class TextToSpeechAudio(DictMixin):
|
||||
expires_at: int
|
||||
id: str
|
||||
data: str
|
||||
url: str
|
||||
|
||||
def __init__(self,
|
||||
expires_at: int,
|
||||
id: str,
|
||||
data: str = None,
|
||||
url: str = None,
|
||||
**kwargs):
|
||||
super().__init__(expires_at=expires_at,
|
||||
id=id,
|
||||
data=data,
|
||||
url=url,
|
||||
**kwargs)
|
||||
|
||||
|
||||
@dataclass(init=False)
|
||||
class TextToSpeechOutput(DictMixin):
|
||||
finish_reason: str
|
||||
audio: TextToSpeechAudio
|
||||
|
||||
def __init__(self,
|
||||
finish_reason: str = None,
|
||||
audio: TextToSpeechAudio = None,
|
||||
**kwargs):
|
||||
super().__init__(finish_reason=finish_reason,
|
||||
audio=audio,
|
||||
**kwargs)
|
||||
|
||||
|
||||
@dataclass(init=False)
|
||||
class TextToSpeechResponse(DashScopeAPIResponse):
|
||||
output: TextToSpeechOutput
|
||||
usage: MultiModalConversationUsage
|
||||
|
||||
@staticmethod
|
||||
def from_api_response(api_response: DashScopeAPIResponse):
|
||||
if api_response.status_code == HTTPStatus.OK:
|
||||
usage = {}
|
||||
if api_response.usage:
|
||||
usage = api_response.usage
|
||||
|
||||
return MultiModalConversationResponse(
|
||||
status_code=api_response.status_code,
|
||||
request_id=api_response.request_id,
|
||||
code=api_response.code,
|
||||
message=api_response.message,
|
||||
output=TextToSpeechOutput(**api_response.output),
|
||||
usage=MultiModalConversationUsage(**usage))
|
||||
else:
|
||||
return TextToSpeechResponse(
|
||||
status_code=api_response.status_code,
|
||||
request_id=api_response.request_id,
|
||||
code=api_response.code,
|
||||
message=api_response.message)
|
||||
@@ -0,0 +1,179 @@
|
||||
# Copyright (c) Alibaba, Inc. and its affiliates.
|
||||
import base64
|
||||
import json
|
||||
from dataclasses import dataclass
|
||||
import os
|
||||
from typing import Optional
|
||||
|
||||
import requests
|
||||
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
|
||||
from cryptography.hazmat.primitives import serialization, hashes
|
||||
from cryptography.hazmat.primitives.asymmetric import padding
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
|
||||
import dashscope
|
||||
from dashscope.common.constants import ENCRYPTION_AES_SECRET_KEY_BYTES, ENCRYPTION_AES_IV_LENGTH
|
||||
from dashscope.common.logging import logger
|
||||
|
||||
|
||||
class Encryption:
|
||||
def __init__(self):
|
||||
self.pub_key_id: str = ''
|
||||
self.pub_key_str: str = ''
|
||||
self.aes_key_bytes: bytes = b''
|
||||
self.encrypted_aes_key_str: str = ''
|
||||
self.iv_bytes: bytes = b''
|
||||
self.base64_iv_str: str = ''
|
||||
self.valid: bool = False
|
||||
|
||||
def initialize(self):
|
||||
public_keys = self._get_public_keys()
|
||||
if not public_keys:
|
||||
return
|
||||
|
||||
public_key_str = public_keys.get('public_key')
|
||||
public_key_id = public_keys.get('public_key_id')
|
||||
if not public_key_str or not public_key_id:
|
||||
logger.error("public keys data not valid")
|
||||
return
|
||||
|
||||
aes_key_bytes = self._generate_aes_secret_key()
|
||||
iv_bytes = self._generate_iv()
|
||||
|
||||
encrypted_aes_key_str = self._encrypt_aes_key_with_rsa(aes_key_bytes, public_key_str)
|
||||
base64_iv_str = base64.b64encode(iv_bytes).decode('utf-8')
|
||||
|
||||
self.pub_key_id = public_key_id
|
||||
self.pub_key_str = public_key_str
|
||||
self.aes_key_bytes = aes_key_bytes
|
||||
self.encrypted_aes_key_str = encrypted_aes_key_str
|
||||
self.iv_bytes = iv_bytes
|
||||
self.base64_iv_str = base64_iv_str
|
||||
|
||||
self.valid = True
|
||||
|
||||
def encrypt(self, dict_plaintext):
|
||||
return self._encrypt_text_with_aes(json.dumps(dict_plaintext, ensure_ascii=False),
|
||||
self.aes_key_bytes, self.iv_bytes)
|
||||
|
||||
def decrypt(self, base64_ciphertext):
|
||||
return self._decrypt_text_with_aes(base64_ciphertext, self.aes_key_bytes, self.iv_bytes)
|
||||
|
||||
def is_valid(self):
|
||||
return self.valid
|
||||
|
||||
def get_pub_key_id(self):
|
||||
return self.pub_key_id
|
||||
|
||||
def get_encrypted_aes_key_str(self):
|
||||
return self.encrypted_aes_key_str
|
||||
|
||||
def get_base64_iv_str(self):
|
||||
return self.base64_iv_str
|
||||
|
||||
@staticmethod
|
||||
def _get_public_keys():
|
||||
url = dashscope.base_http_api_url + '/public-keys/latest'
|
||||
headers = {
|
||||
"Authorization": f"Bearer {dashscope.api_key}"
|
||||
}
|
||||
|
||||
response = requests.get(url, headers=headers)
|
||||
if response.status_code != 200:
|
||||
logger.error("exceptional public key response: %s" % response)
|
||||
return None
|
||||
|
||||
json_resp = response.json()
|
||||
response_data = json_resp.get('data')
|
||||
|
||||
if not response_data:
|
||||
logger.error("no valid data in public key response")
|
||||
return None
|
||||
|
||||
return response_data
|
||||
|
||||
@staticmethod
|
||||
def _generate_aes_secret_key():
|
||||
return os.urandom(ENCRYPTION_AES_SECRET_KEY_BYTES)
|
||||
|
||||
@staticmethod
|
||||
def _generate_iv():
|
||||
return os.urandom(ENCRYPTION_AES_IV_LENGTH)
|
||||
|
||||
@staticmethod
|
||||
def _encrypt_text_with_aes(plaintext, key, iv):
|
||||
"""使用AES-GCM加密数据"""
|
||||
|
||||
# 创建AES-GCM加密器
|
||||
aes_gcm = Cipher(
|
||||
algorithms.AES(key),
|
||||
modes.GCM(iv, tag=None),
|
||||
backend=default_backend()
|
||||
).encryptor()
|
||||
|
||||
# 关联数据设为空(根据需求可调整)
|
||||
aes_gcm.authenticate_additional_data(b'')
|
||||
|
||||
# 加密数据
|
||||
ciphertext = aes_gcm.update(plaintext.encode('utf-8')) + aes_gcm.finalize()
|
||||
|
||||
# 获取认证标签
|
||||
tag = aes_gcm.tag
|
||||
|
||||
# 组合密文和标签
|
||||
encrypted_data = ciphertext + tag
|
||||
|
||||
# 返回Base64编码结果
|
||||
return base64.b64encode(encrypted_data).decode('utf-8')
|
||||
|
||||
@staticmethod
|
||||
def _decrypt_text_with_aes(base64_ciphertext, aes_key, iv):
|
||||
"""使用AES-GCM解密响应"""
|
||||
|
||||
# 解码Base64数据
|
||||
encrypted_data = base64.b64decode(base64_ciphertext)
|
||||
|
||||
# 分离密文和标签(标签长度16字节)
|
||||
ciphertext = encrypted_data[:-16]
|
||||
tag = encrypted_data[-16:]
|
||||
|
||||
# 创建AES-GCM解密器
|
||||
aes_gcm = Cipher(
|
||||
algorithms.AES(aes_key),
|
||||
modes.GCM(iv, tag),
|
||||
backend=default_backend()
|
||||
).decryptor()
|
||||
|
||||
# 验证关联数据(与加密时一致)
|
||||
aes_gcm.authenticate_additional_data(b'')
|
||||
|
||||
# 解密数据
|
||||
decrypted_bytes = aes_gcm.update(ciphertext) + aes_gcm.finalize()
|
||||
|
||||
# 明文
|
||||
plaintext = decrypted_bytes.decode('utf-8')
|
||||
|
||||
return json.loads(plaintext)
|
||||
|
||||
@staticmethod
|
||||
def _encrypt_aes_key_with_rsa(aes_key, public_key_str):
|
||||
"""使用RSA公钥加密AES密钥"""
|
||||
|
||||
# 解码Base64格式的公钥
|
||||
public_key_bytes = base64.b64decode(public_key_str)
|
||||
|
||||
# 加载公钥
|
||||
public_key = serialization.load_der_public_key(
|
||||
public_key_bytes,
|
||||
backend=default_backend()
|
||||
)
|
||||
|
||||
base64_aes_key = base64.b64encode(aes_key).decode('utf-8')
|
||||
|
||||
# 使用RSA加密
|
||||
encrypted_bytes = public_key.encrypt(
|
||||
base64_aes_key.encode('utf-8'),
|
||||
padding.PKCS1v15()
|
||||
)
|
||||
|
||||
return base64.b64encode(encrypted_bytes).decode('utf-8')
|
||||
@@ -0,0 +1,379 @@
|
||||
# Copyright (c) Alibaba, Inc. and its affiliates.
|
||||
import datetime
|
||||
import json
|
||||
import ssl
|
||||
from http import HTTPStatus
|
||||
from typing import Optional
|
||||
|
||||
import aiohttp
|
||||
import certifi
|
||||
import requests
|
||||
|
||||
from dashscope.api_entities.base_request import AioBaseRequest
|
||||
from dashscope.api_entities.dashscope_response import DashScopeAPIResponse
|
||||
from dashscope.common.constants import (DEFAULT_REQUEST_TIMEOUT_SECONDS,
|
||||
SSE_CONTENT_TYPE, HTTPMethod)
|
||||
from dashscope.common.error import UnsupportedHTTPMethod
|
||||
from dashscope.common.logging import logger
|
||||
from dashscope.common.utils import (_handle_aio_stream,
|
||||
_handle_aiohttp_failed_response,
|
||||
_handle_http_failed_response,
|
||||
_handle_stream)
|
||||
from dashscope.api_entities.encryption import Encryption
|
||||
|
||||
|
||||
class HttpRequest(AioBaseRequest):
|
||||
def __init__(self,
|
||||
url: str,
|
||||
api_key: str,
|
||||
http_method: str,
|
||||
stream: bool = True,
|
||||
async_request: bool = False,
|
||||
query: bool = False,
|
||||
timeout: int = DEFAULT_REQUEST_TIMEOUT_SECONDS,
|
||||
task_id: str = None,
|
||||
flattened_output: bool = False,
|
||||
encryption: Optional[Encryption] = None,
|
||||
user_agent: str = '') -> None:
|
||||
"""HttpSSERequest, processing http server sent event stream.
|
||||
|
||||
Args:
|
||||
url (str): The request url.
|
||||
api_key (str): The api key.
|
||||
method (str): The http method(GET|POST).
|
||||
stream (bool, optional): Is stream request. Defaults to True.
|
||||
timeout (int, optional): Total request timeout.
|
||||
Defaults to DEFAULT_REQUEST_TIMEOUT_SECONDS.
|
||||
user_agent (str, optional): Additional user agent string to
|
||||
append. Defaults to ''.
|
||||
"""
|
||||
|
||||
super().__init__(user_agent=user_agent)
|
||||
self.url = url
|
||||
self.flattened_output = flattened_output
|
||||
self.async_request = async_request
|
||||
self.encryption = encryption
|
||||
self.headers = {
|
||||
'Accept': 'application/json',
|
||||
'Authorization': 'Bearer %s' % api_key,
|
||||
**self.headers,
|
||||
}
|
||||
|
||||
if encryption and encryption.is_valid():
|
||||
self.headers = {
|
||||
"X-DashScope-EncryptionKey": json.dumps({
|
||||
"public_key_id": encryption.get_pub_key_id(),
|
||||
"encrypt_key": encryption.get_encrypted_aes_key_str(),
|
||||
"iv": encryption.get_base64_iv_str()
|
||||
}),
|
||||
**self.headers,
|
||||
}
|
||||
|
||||
self.query = query
|
||||
if self.async_request and self.query is False:
|
||||
self.headers = {
|
||||
'X-DashScope-Async': 'enable',
|
||||
**self.headers,
|
||||
}
|
||||
self.method = http_method
|
||||
if self.method == HTTPMethod.POST:
|
||||
self.headers['Content-Type'] = 'application/json'
|
||||
|
||||
self.stream = stream
|
||||
if self.stream:
|
||||
self.headers['Accept'] = SSE_CONTENT_TYPE
|
||||
self.headers['X-Accel-Buffering'] = 'no'
|
||||
self.headers['X-DashScope-SSE'] = 'enable'
|
||||
if self.query:
|
||||
self.url = self.url.replace('api', 'api-task')
|
||||
self.url += '%s' % task_id
|
||||
if timeout is None:
|
||||
self.timeout = DEFAULT_REQUEST_TIMEOUT_SECONDS
|
||||
else:
|
||||
self.timeout = timeout
|
||||
|
||||
def add_header(self, key, value):
|
||||
self.headers[key] = value
|
||||
|
||||
def add_headers(self, headers):
|
||||
self.headers = {**self.headers, **headers}
|
||||
|
||||
def call(self):
|
||||
response = self._handle_request()
|
||||
if self.stream:
|
||||
return (item for item in response)
|
||||
else:
|
||||
output = next(response)
|
||||
try:
|
||||
next(response)
|
||||
except StopIteration:
|
||||
pass
|
||||
return output
|
||||
|
||||
async def aio_call(self):
|
||||
response = self._handle_aio_request()
|
||||
if self.stream:
|
||||
return (item async for item in response)
|
||||
else:
|
||||
result = await response.__anext__()
|
||||
try:
|
||||
await response.__anext__()
|
||||
except StopAsyncIteration:
|
||||
pass
|
||||
return result
|
||||
|
||||
async def _handle_aio_request(self):
|
||||
try:
|
||||
connector = aiohttp.TCPConnector(
|
||||
ssl=ssl.create_default_context(
|
||||
cafile=certifi.where()))
|
||||
async with aiohttp.ClientSession(
|
||||
connector=connector,
|
||||
timeout=aiohttp.ClientTimeout(total=self.timeout),
|
||||
headers=self.headers) as session:
|
||||
logger.debug('Starting request: %s' % self.url)
|
||||
if self.method == HTTPMethod.POST:
|
||||
is_form, obj = False, {}
|
||||
if hasattr(self, 'data') and self.data is not None:
|
||||
is_form, obj = self.data.get_aiohttp_payload()
|
||||
if is_form:
|
||||
headers = {**self.headers, **obj.headers}
|
||||
response = await session.post(url=self.url,
|
||||
data=obj,
|
||||
headers=headers)
|
||||
else:
|
||||
response = await session.request('POST',
|
||||
url=self.url,
|
||||
json=obj,
|
||||
headers=self.headers)
|
||||
elif self.method == HTTPMethod.GET:
|
||||
# 添加条件判断
|
||||
params = {}
|
||||
if hasattr(self, 'data') and self.data is not None:
|
||||
params = getattr(self.data, 'parameters', {})
|
||||
if params:
|
||||
params = self.__handle_parameters(params)
|
||||
response = await session.get(url=self.url,
|
||||
params=params,
|
||||
headers=self.headers)
|
||||
else:
|
||||
raise UnsupportedHTTPMethod('Unsupported http method: %s' %
|
||||
self.method)
|
||||
logger.debug('Response returned: %s' % self.url)
|
||||
async with response:
|
||||
async for rsp in self._handle_aio_response(response):
|
||||
yield rsp
|
||||
except aiohttp.ClientConnectorError as e:
|
||||
logger.error(e)
|
||||
raise e
|
||||
except BaseException as e:
|
||||
logger.error(e)
|
||||
raise e
|
||||
|
||||
@staticmethod
|
||||
def __handle_parameters(params: dict) -> dict:
|
||||
def __format(value):
|
||||
if isinstance(value, bool):
|
||||
return str(value).lower()
|
||||
elif isinstance(value, (str, int, float)):
|
||||
return value
|
||||
elif value is None:
|
||||
return ''
|
||||
elif isinstance(value, (datetime.datetime, datetime.date)):
|
||||
return value.isoformat()
|
||||
elif isinstance(value, (list, tuple)):
|
||||
return ','.join(str(__format(x)) for x in value)
|
||||
elif isinstance(value, dict):
|
||||
return json.dumps(value)
|
||||
else:
|
||||
try:
|
||||
return str(value)
|
||||
except Exception as e:
|
||||
raise ValueError(f"Unsupported type {type(value)} for param formatting: {e}")
|
||||
|
||||
formatted = {}
|
||||
for k, v in params.items():
|
||||
formatted[k] = __format(v)
|
||||
return formatted
|
||||
|
||||
async def _handle_aio_response(self, response: aiohttp.ClientResponse):
|
||||
request_id = ''
|
||||
if (response.status == HTTPStatus.OK and self.stream
|
||||
and SSE_CONTENT_TYPE in response.content_type):
|
||||
async for is_error, status_code, data in _handle_aio_stream(
|
||||
response):
|
||||
try:
|
||||
output = None
|
||||
usage = None
|
||||
msg = json.loads(data)
|
||||
if not is_error:
|
||||
if 'output' in msg:
|
||||
output = msg['output']
|
||||
if 'usage' in msg:
|
||||
usage = msg['usage']
|
||||
if 'request_id' in msg:
|
||||
request_id = msg['request_id']
|
||||
except json.JSONDecodeError:
|
||||
yield DashScopeAPIResponse(
|
||||
request_id=request_id,
|
||||
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
|
||||
code='Unknown',
|
||||
message=data)
|
||||
continue
|
||||
if is_error:
|
||||
yield DashScopeAPIResponse(request_id=request_id,
|
||||
status_code=status_code,
|
||||
code=msg['code'],
|
||||
message=msg['message'])
|
||||
else:
|
||||
if self.encryption and self.encryption.is_valid():
|
||||
output = self.encryption.decrypt(output)
|
||||
yield DashScopeAPIResponse(request_id=request_id,
|
||||
status_code=HTTPStatus.OK,
|
||||
output=output,
|
||||
usage=usage)
|
||||
elif (response.status == HTTPStatus.OK
|
||||
and 'multipart' in response.content_type):
|
||||
reader = aiohttp.MultipartReader.from_response(response)
|
||||
output = {}
|
||||
while True:
|
||||
part = await reader.next()
|
||||
if part is None:
|
||||
break
|
||||
output[part.name] = await part.read()
|
||||
if 'request_id' in output:
|
||||
request_id = output['request_id']
|
||||
if self.encryption and self.encryption.is_valid():
|
||||
output = self.encryption.decrypt(output)
|
||||
yield DashScopeAPIResponse(request_id=request_id,
|
||||
status_code=HTTPStatus.OK,
|
||||
output=output)
|
||||
elif response.status == HTTPStatus.OK:
|
||||
json_content = await response.json()
|
||||
output = None
|
||||
usage = None
|
||||
if 'output' in json_content and json_content['output'] is not None:
|
||||
output = json_content['output']
|
||||
# Compatible with wan
|
||||
elif 'data' in json_content and json_content['data'] is not None\
|
||||
and isinstance(json_content['data'], list)\
|
||||
and len(json_content['data']) > 0\
|
||||
and 'task_id' in json_content['data'][0]:
|
||||
output = json_content
|
||||
if 'usage' in json_content:
|
||||
usage = json_content['usage']
|
||||
if 'request_id' in json_content:
|
||||
request_id = json_content['request_id']
|
||||
if self.encryption and self.encryption.is_valid():
|
||||
output = self.encryption.decrypt(output)
|
||||
yield DashScopeAPIResponse(request_id=request_id,
|
||||
status_code=HTTPStatus.OK,
|
||||
output=output,
|
||||
usage=usage)
|
||||
else:
|
||||
yield await _handle_aiohttp_failed_response(response)
|
||||
|
||||
def _handle_response(self, response: requests.Response):
|
||||
request_id = ''
|
||||
if (response.status_code == HTTPStatus.OK and self.stream
|
||||
and SSE_CONTENT_TYPE in response.headers.get(
|
||||
'content-type', '')):
|
||||
for is_error, status_code, event in _handle_stream(response):
|
||||
try:
|
||||
data = event.data
|
||||
output = None
|
||||
usage = None
|
||||
msg = json.loads(data)
|
||||
logger.debug('Stream message: %s' % msg)
|
||||
if not is_error:
|
||||
if 'output' in msg:
|
||||
output = msg['output']
|
||||
if 'usage' in msg:
|
||||
usage = msg['usage']
|
||||
if 'request_id' in msg:
|
||||
request_id = msg['request_id']
|
||||
except json.JSONDecodeError:
|
||||
yield DashScopeAPIResponse(
|
||||
request_id=request_id,
|
||||
status_code=HTTPStatus.BAD_REQUEST,
|
||||
output=None,
|
||||
code='Unknown',
|
||||
message=data)
|
||||
continue
|
||||
if is_error:
|
||||
yield DashScopeAPIResponse(
|
||||
request_id=request_id,
|
||||
status_code=status_code,
|
||||
output=None,
|
||||
code=msg['code']
|
||||
if 'code' in msg else None, # noqa E501
|
||||
message=msg['message']
|
||||
if 'message' in msg else None) # noqa E501
|
||||
else:
|
||||
if self.flattened_output:
|
||||
yield msg
|
||||
else:
|
||||
if self.encryption and self.encryption.is_valid():
|
||||
output = self.encryption.decrypt(output)
|
||||
yield DashScopeAPIResponse(request_id=request_id,
|
||||
status_code=HTTPStatus.OK,
|
||||
output=output,
|
||||
usage=usage)
|
||||
elif response.status_code == HTTPStatus.OK:
|
||||
json_content = response.json()
|
||||
logger.debug('Response: %s' % json_content)
|
||||
output = None
|
||||
usage = None
|
||||
if 'task_id' in json_content:
|
||||
output = {'task_id': json_content['task_id']}
|
||||
if 'output' in json_content:
|
||||
output = json_content['output']
|
||||
if 'usage' in json_content:
|
||||
usage = json_content['usage']
|
||||
if 'request_id' in json_content:
|
||||
request_id = json_content['request_id']
|
||||
if self.flattened_output:
|
||||
yield json_content
|
||||
else:
|
||||
if self.encryption and self.encryption.is_valid():
|
||||
output = self.encryption.decrypt(output)
|
||||
yield DashScopeAPIResponse(request_id=request_id,
|
||||
status_code=HTTPStatus.OK,
|
||||
output=output,
|
||||
usage=usage)
|
||||
else:
|
||||
yield _handle_http_failed_response(response)
|
||||
|
||||
def _handle_request(self):
|
||||
try:
|
||||
with requests.Session() as session:
|
||||
if self.method == HTTPMethod.POST:
|
||||
is_form, form, obj = self.data.get_http_payload()
|
||||
if is_form:
|
||||
headers = {**self.headers}
|
||||
headers.pop('Content-Type')
|
||||
response = session.post(url=self.url,
|
||||
data=obj,
|
||||
files=form,
|
||||
headers=headers,
|
||||
timeout=self.timeout)
|
||||
else:
|
||||
logger.debug('Request body: %s' % obj)
|
||||
response = session.post(url=self.url,
|
||||
stream=self.stream,
|
||||
json=obj,
|
||||
headers={**self.headers},
|
||||
timeout=self.timeout)
|
||||
elif self.method == HTTPMethod.GET:
|
||||
response = session.get(url=self.url,
|
||||
params=self.data.parameters,
|
||||
headers=self.headers,
|
||||
timeout=self.timeout)
|
||||
else:
|
||||
raise UnsupportedHTTPMethod('Unsupported http method: %s' %
|
||||
self.method)
|
||||
for rsp in self._handle_response(response):
|
||||
yield rsp
|
||||
except BaseException as e:
|
||||
logger.error(e)
|
||||
raise e
|
||||
@@ -0,0 +1,362 @@
|
||||
# Copyright (c) Alibaba, Inc. and its affiliates.
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
import uuid
|
||||
from http import HTTPStatus
|
||||
from typing import Tuple, Union
|
||||
|
||||
import aiohttp
|
||||
|
||||
from dashscope.api_entities.base_request import AioBaseRequest
|
||||
from dashscope.api_entities.dashscope_response import DashScopeAPIResponse
|
||||
from dashscope.common.constants import (DEFAULT_REQUEST_TIMEOUT_SECONDS,
|
||||
SERVICE_503_MESSAGE,
|
||||
WEBSOCKET_ERROR_CODE)
|
||||
from dashscope.common.error import (RequestFailure, UnexpectedMessageReceived,
|
||||
UnknownMessageReceived)
|
||||
from dashscope.common.logging import logger
|
||||
from dashscope.common.utils import async_to_sync
|
||||
from dashscope.protocol.websocket import (ACTION_KEY, ERROR_MESSAGE,
|
||||
ERROR_NAME, EVENT_KEY, HEADER,
|
||||
TASK_ID, ActionType, EventType,
|
||||
WebsocketStreamingMode)
|
||||
|
||||
|
||||
class WebSocketRequest(AioBaseRequest):
|
||||
def __init__(
|
||||
self,
|
||||
url: str,
|
||||
api_key: str,
|
||||
stream: bool = True,
|
||||
ws_stream_mode: str = WebsocketStreamingMode.OUT,
|
||||
is_binary_input: bool = False,
|
||||
timeout: int = DEFAULT_REQUEST_TIMEOUT_SECONDS,
|
||||
flattened_output: bool = False,
|
||||
pre_task_id=None,
|
||||
user_agent: str = '',
|
||||
) -> None:
|
||||
super().__init__(user_agent=user_agent)
|
||||
"""HttpRequest.
|
||||
|
||||
Args:
|
||||
url (str): The request url.
|
||||
api_key (str): The api key.
|
||||
method (str): The http method(GET|POST).
|
||||
stream (bool, optional): Is stream request. Defaults to False.
|
||||
timeout (int, optional): Total request timeout.
|
||||
Defaults to DEFAULT_REQUEST_TIMEOUT_SECONDS.
|
||||
"""
|
||||
self.url = url
|
||||
self.stream = stream
|
||||
self.flattened_output = flattened_output
|
||||
if timeout is None:
|
||||
self.timeout = DEFAULT_REQUEST_TIMEOUT_SECONDS
|
||||
else:
|
||||
self.timeout = timeout
|
||||
self.ws_stream_mode = ws_stream_mode
|
||||
self.is_binary_input = is_binary_input
|
||||
|
||||
self.headers = {
|
||||
'Authorization': 'bearer %s' % api_key,
|
||||
**self.headers,
|
||||
}
|
||||
|
||||
self.task_headers = {
|
||||
'streaming': self.ws_stream_mode,
|
||||
}
|
||||
self.pre_task_id = pre_task_id
|
||||
|
||||
def add_headers(self, headers):
|
||||
self.headers = {**self.headers, **headers}
|
||||
|
||||
def call(self):
|
||||
response = async_to_sync(self.connection_handler())
|
||||
if self.stream:
|
||||
return (item for item in response)
|
||||
else:
|
||||
output = next(response)
|
||||
try:
|
||||
next(response)
|
||||
except StopIteration:
|
||||
pass
|
||||
return output
|
||||
|
||||
async def close(self):
|
||||
if self.ws is not None and not self.ws.closed:
|
||||
await self.ws.close()
|
||||
|
||||
async def aio_call(self):
|
||||
response = self.connection_handler()
|
||||
if self.stream:
|
||||
return (item async for item in response)
|
||||
else:
|
||||
result = await response.__anext__()
|
||||
try:
|
||||
await response.__anext__()
|
||||
except StopAsyncIteration:
|
||||
pass
|
||||
return result
|
||||
|
||||
async def connection_handler(self):
|
||||
try:
|
||||
task_id = None
|
||||
async with aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(
|
||||
total=self.timeout)) as session:
|
||||
async with session.ws_connect(self.url,
|
||||
headers=self.headers,
|
||||
heartbeat=6000) as ws:
|
||||
await self._start_task(ws) # send start task action.
|
||||
task_id = self.task_headers['task_id']
|
||||
await self._wait_for_task_started(
|
||||
ws) # wait for task started event. # noqa E501
|
||||
if self.ws_stream_mode == WebsocketStreamingMode.NONE:
|
||||
if self.is_binary_input: # send the binary package
|
||||
data = self.data.get_batch_binary_data()
|
||||
await ws.send_bytes(list(data.values())[0])
|
||||
is_binary, result = await self._receive_batch_data_task( # noqa E501
|
||||
ws)
|
||||
# do not need send finished task message.
|
||||
yield self._to_DashScopeAPIResponse(
|
||||
task_id, is_binary, result)
|
||||
elif self.ws_stream_mode == WebsocketStreamingMode.IN:
|
||||
# server is in, we send streaming out.
|
||||
await self._send_continue_task_data(ws)
|
||||
is_binary, result = await self._receive_batch_data_task( # noqa E501
|
||||
ws)
|
||||
# do not need send finished task message.
|
||||
yield self._to_DashScopeAPIResponse(
|
||||
task_id, is_binary, result)
|
||||
elif self.ws_stream_mode == WebsocketStreamingMode.OUT:
|
||||
# we send batch data, server streaming output data.
|
||||
if self.is_binary_input: # send only binary package.
|
||||
data = self.data.get_batch_binary_data()
|
||||
await ws.send_bytes(list(data.values())[0])
|
||||
async for is_binary, message in self._receive_streaming_data_task( # noqa E501
|
||||
ws):
|
||||
yield self._to_DashScopeAPIResponse(
|
||||
task_id, is_binary, message)
|
||||
else: # duplex mode
|
||||
asyncio.create_task(self._send_continue_task_data(ws))
|
||||
async for is_binary, message in self._receive_streaming_data_task( # noqa E501
|
||||
ws):
|
||||
yield self._to_DashScopeAPIResponse(
|
||||
task_id, is_binary, message)
|
||||
except RequestFailure as e:
|
||||
yield DashScopeAPIResponse(request_id=e.request_id,
|
||||
status_code=e.http_code,
|
||||
output=None,
|
||||
code=e.name,
|
||||
message=e.message)
|
||||
except aiohttp.ClientConnectorError as e:
|
||||
logger.exception(e)
|
||||
yield DashScopeAPIResponse(request_id='',
|
||||
status_code=-1,
|
||||
code='ClientConnectorError',
|
||||
message=str(e))
|
||||
except aiohttp.WSServerHandshakeError as e:
|
||||
code = e.status
|
||||
msg = e.message
|
||||
if e.status in [HTTPStatus.FORBIDDEN, HTTPStatus.UNAUTHORIZED]:
|
||||
msg = 'Unauthorized, your api-key is invalid!'
|
||||
elif e.status == HTTPStatus.SERVICE_UNAVAILABLE:
|
||||
msg = SERVICE_503_MESSAGE
|
||||
else:
|
||||
pass
|
||||
yield DashScopeAPIResponse(request_id=task_id,
|
||||
status_code=code,
|
||||
code=code,
|
||||
message=msg)
|
||||
except BaseException as e:
|
||||
logger.exception(e)
|
||||
yield DashScopeAPIResponse(request_id='',
|
||||
status_code=-1,
|
||||
code='Unknown',
|
||||
message='Error type: %s, message: %s' %
|
||||
(type(e), e))
|
||||
|
||||
def _to_DashScopeAPIResponse(self, task_id, is_binary, result):
|
||||
if is_binary:
|
||||
return DashScopeAPIResponse(request_id=task_id,
|
||||
status_code=HTTPStatus.OK,
|
||||
output=result)
|
||||
else:
|
||||
# get output and usage.
|
||||
output = {}
|
||||
usage = {}
|
||||
if 'output' in result:
|
||||
output = result['output']
|
||||
if 'usage' in result:
|
||||
usage = result['usage']
|
||||
return DashScopeAPIResponse(request_id=task_id,
|
||||
status_code=HTTPStatus.OK,
|
||||
output=output,
|
||||
usage=usage)
|
||||
|
||||
async def _receive_streaming_data_task(self, ws):
|
||||
# check if request stream data, re return an iterator,
|
||||
# otherwise we collect data and return user.
|
||||
# no matter what, the response is streaming
|
||||
is_binary_output = False
|
||||
while True:
|
||||
msg = await ws.receive()
|
||||
await self._check_websocket_unexpected_message(msg)
|
||||
if msg.type == aiohttp.WSMsgType.TEXT:
|
||||
msg_json = msg.json()
|
||||
logger.debug('Receive %s event' % msg_json[HEADER][EVENT_KEY])
|
||||
if msg_json[HEADER][EVENT_KEY] == EventType.GENERATED:
|
||||
payload = msg_json['payload']
|
||||
yield False, payload
|
||||
elif msg_json[HEADER][EVENT_KEY] == EventType.FINISHED:
|
||||
payload = None
|
||||
if 'payload' in msg_json:
|
||||
payload = msg_json['payload']
|
||||
logger.debug(payload)
|
||||
if payload:
|
||||
yield False, payload
|
||||
else:
|
||||
if not self.stream:
|
||||
if is_binary_output:
|
||||
yield True, payload
|
||||
else:
|
||||
yield False, payload
|
||||
break
|
||||
elif msg_json[HEADER][EVENT_KEY] == EventType.FAILED:
|
||||
self._on_failed(msg_json)
|
||||
else:
|
||||
error = 'Receive unknown message: %s' % msg_json
|
||||
logger.error(error)
|
||||
raise UnknownMessageReceived(error)
|
||||
elif msg.type == aiohttp.WSMsgType.BINARY:
|
||||
is_binary_output = True
|
||||
yield True, msg.data
|
||||
|
||||
def _on_failed(self, details):
|
||||
error = RequestFailure(request_id=details[HEADER][TASK_ID],
|
||||
http_code=WEBSOCKET_ERROR_CODE,
|
||||
name=details[HEADER][ERROR_NAME],
|
||||
message=details[HEADER][ERROR_MESSAGE])
|
||||
logger.error(error)
|
||||
raise error
|
||||
|
||||
async def _start_task(self, ws):
|
||||
if self.pre_task_id is not None:
|
||||
self.task_headers['task_id'] = self.pre_task_id
|
||||
else:
|
||||
self.task_headers['task_id'] = uuid.uuid4().hex # create task id.
|
||||
task_header = {**self.task_headers, ACTION_KEY: ActionType.START}
|
||||
# for binary data, the start action has no input, only parameters.
|
||||
start_data = self.data.get_websocket_start_data()
|
||||
message = self._build_up_message(task_header, start_data)
|
||||
logger.debug('Send start task: {}'.format(message))
|
||||
await ws.send_str(message)
|
||||
|
||||
async def _send_finished_task(self, ws):
|
||||
task_header = {**self.task_headers, ACTION_KEY: ActionType.FINISHED}
|
||||
payload = {'input': {}}
|
||||
message = self._build_up_message(task_header, payload)
|
||||
logger.debug('Send finish task: {}'.format(message))
|
||||
await ws.send_str(message)
|
||||
|
||||
async def _send_continue_task_data(self, ws):
|
||||
headers = {
|
||||
'task_id': self.task_headers['task_id'],
|
||||
'action': 'continue-task'
|
||||
}
|
||||
for input in self.data.get_websocket_continue_data():
|
||||
if self.is_binary_input:
|
||||
if len(input) > 0:
|
||||
if isinstance(input, bytes):
|
||||
await ws.send_bytes(input)
|
||||
logger.debug(
|
||||
'Send continue task with bytes: {}'.format(
|
||||
len(input)))
|
||||
else:
|
||||
await ws.send_bytes(list(input.values())[0])
|
||||
logger.debug(
|
||||
'Send continue task with list[byte]: {}'.format(
|
||||
len(input)))
|
||||
else:
|
||||
if len(input) > 0:
|
||||
message = self._build_up_message(headers=headers,
|
||||
payload=input)
|
||||
logger.debug('Send continue task: {}'.format(message))
|
||||
await ws.send_str(message)
|
||||
await asyncio.sleep(0.000001)
|
||||
|
||||
# data send completed, and send task completed.
|
||||
await self._send_finished_task(ws)
|
||||
|
||||
async def _receive_batch_data_task(self,
|
||||
ws) -> Tuple[bool, Union[str, bytes]]:
|
||||
"""_summary_
|
||||
|
||||
Args:
|
||||
ws (connection): The ws connection.
|
||||
|
||||
Raises:
|
||||
UnknownMessageReceived: The message is unexpected.
|
||||
|
||||
Returns:
|
||||
Tuple[bool, str]: is output is binary, output
|
||||
"""
|
||||
while True:
|
||||
msg = await ws.receive()
|
||||
await self._check_websocket_unexpected_message(msg)
|
||||
if msg.type == aiohttp.WSMsgType.TEXT:
|
||||
msg_json = msg.json()
|
||||
logger.debug('Receive %s event' % msg_json[HEADER][EVENT_KEY])
|
||||
if msg_json[HEADER][EVENT_KEY] == EventType.GENERATED:
|
||||
payload = msg_json['payload']
|
||||
return False, payload
|
||||
elif msg_json[HEADER][EVENT_KEY] == EventType.FINISHED:
|
||||
payload = msg_json['payload']
|
||||
return False, payload
|
||||
elif msg_json[HEADER][EVENT_KEY] == EventType.FAILED:
|
||||
self._on_failed(msg_json)
|
||||
else:
|
||||
error = 'Receive unknown message: %s' % msg_json
|
||||
logger.error(error)
|
||||
raise UnknownMessageReceived(error)
|
||||
elif msg.type == aiohttp.WSMsgType.BINARY:
|
||||
return True, msg.data # get binary result data.
|
||||
|
||||
async def _wait_for_task_started(self, ws):
|
||||
while True:
|
||||
msg = await ws.receive()
|
||||
await self._check_websocket_unexpected_message(msg)
|
||||
if msg.type == aiohttp.WSMsgType.TEXT:
|
||||
msg_json = msg.json()
|
||||
logger.debug('Receive %s event' % msg_json[HEADER][EVENT_KEY])
|
||||
if msg_json[HEADER][EVENT_KEY] == EventType.STARTED:
|
||||
return
|
||||
elif msg_json[HEADER][EVENT_KEY] == EventType.FAILED:
|
||||
self._on_failed(msg_json)
|
||||
else:
|
||||
raise UnexpectedMessageReceived(
|
||||
'Receive unexpected message, expect task-started, real: %s.' # noqa E501
|
||||
% msg_json[HEADER][EVENT_KEY])
|
||||
elif msg.type == aiohttp.WSMsgType.BINARY:
|
||||
raise UnexpectedMessageReceived(
|
||||
'Receive unexpected binary message when wait for task-started' # noqa E501
|
||||
)
|
||||
|
||||
async def _check_websocket_unexpected_message(self, msg):
|
||||
if msg.type == aiohttp.WSMsgType.CLOSED:
|
||||
details = 'WSMsgType.CLOSE, data: %s, extra: %s' % (msg.data,
|
||||
msg.extra)
|
||||
logger.error('Connection unexpected closed!')
|
||||
raise UnexpectedMessageReceived(
|
||||
'Receive unexpected websocket close message, details: %s' %
|
||||
details)
|
||||
elif msg.type == aiohttp.WSMsgType.ERROR:
|
||||
details = 'WSMsgType.ERROR, data: %s, extra: %s' % (msg.data,
|
||||
msg.extra)
|
||||
logger.error('Connection error: %s' % details)
|
||||
raise UnexpectedMessageReceived(
|
||||
'Receive unexpected websocket error message details: %s.' %
|
||||
details)
|
||||
|
||||
def _build_up_message(self, headers, payload):
|
||||
message = {'header': headers, 'payload': payload}
|
||||
return json.dumps(message)
|
||||
Reference in New Issue
Block a user