Files
hsa/.venv/lib/python3.10/site-packages/dashscope/client/base_api.py
2025-09-11 13:29:12 +00:00

801 lines
31 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import time
from http import HTTPStatus
from typing import List, Union
import dashscope
import requests
from dashscope.api_entities.api_request_factory import _build_api_request
from dashscope.api_entities.dashscope_response import DashScopeAPIResponse
from dashscope.common.api_key import get_default_api_key
from dashscope.common.constants import (DEFAULT_REQUEST_TIMEOUT_SECONDS,
REPEATABLE_STATUS,
REQUEST_TIMEOUT_KEYWORD,
SSE_CONTENT_TYPE, TaskStatus)
from dashscope.common.error import InvalidParameter, InvalidTask, ModelRequired
from dashscope.common.logging import logger
from dashscope.common.utils import (_handle_http_failed_response,
_handle_http_response, default_headers,
join_url)
def _normalization_url(*args):
url = dashscope.base_http_api_url
return join_url(url, *args)
class BaseAioApi():
"""BaseApi, internal use only.
"""
@classmethod
def _validate_params(cls, api_key, model):
if api_key is None:
api_key = get_default_api_key()
if model is None or not model:
raise ModelRequired('Model is required!')
return api_key, model
@classmethod
def aio_call(cls,
model: str,
input: object,
task_group: str,
task: str = None,
function: str = None,
api_key: str = None,
**kwargs) -> DashScopeAPIResponse:
"""Call service and get result.
Args:
model (str): The requested model, such as gpt3-v2
input (object): The api input data, cannot be None.
task_group (str, optional): The api task group.
task (str, optional): The task name. Defaults to None.
function (str, optional): The function of the task.
Defaults to None.
api_key (str, optional): The api api_key, if not present,
will get by default rule(TODO: api key doc). Defaults to None.
api_protocol (str, optional): Api protocol websocket or http.
Defaults to None.
ws_stream_mode (str, optional): websocket stream mode,
[none, in, out, duplex]. Defaults to out.
is_binary_input (bool, optional): Is input data binary.
Defaults to False.
http_method (str, optional): If api protocol is http, specifies
method[GET, POST]. Defaults to POST.
Returns:
DashScopeAPIResponse: The service response.
"""
api_key, model = BaseAioApi._validate_params(api_key, model)
request = _build_api_request(model=model,
input=input,
task_group=task_group,
task=task,
function=function,
api_key=api_key,
**kwargs)
# call request service.
return request.aio_call()
class BaseApi():
"""BaseApi, internal use only.
"""
@classmethod
def _validate_params(cls, api_key, model):
if api_key is None:
api_key = get_default_api_key()
if model is None or not model:
raise ModelRequired('Model is required!')
return api_key, model
@classmethod
def call(cls,
model: str,
input: object,
task_group: str,
task: str = None,
function: str = None,
api_key: str = None,
**kwargs) -> DashScopeAPIResponse:
"""Call service and get result.
Args:
model (str): The requested model, such as gpt3-v2
input (object): The api input data, cannot be None.
task_group (str, optional): The api task group.
task (str, optional): The task name. Defaults to None.
function (str, optional): The function of the task.
Defaults to None.
api_key (str, optional): The api api_key, if not present,
will get by default rule(TODO: api key doc). Defaults to None.
api_protocol (str, optional): Api protocol websocket or http.
Defaults to None.
ws_stream_mode (str, optional): websocket stream mode,
[none, in, out, duplex]. Defaults to out.
is_binary_input (bool, optional): Is input data binary.
Defaults to False.
http_method (str, optional): If api protocol is http, specifies
method[GET, POST]. Defaults to POST.
Returns:
DashScopeAPIResponse: The service response.
"""
api_key, model = BaseApi._validate_params(api_key, model)
request = _build_api_request(model=model,
input=input,
task_group=task_group,
task=task,
function=function,
api_key=api_key,
**kwargs)
# call request service.
return request.call()
class AsyncTaskGetMixin():
@classmethod
def _get(cls, task_id: str, api_key: str = None) -> DashScopeAPIResponse:
status_url = _normalization_url('tasks', task_id)
with requests.Session() as session:
logger.debug('Starting request: %s' % status_url)
response = session.get(status_url,
headers={
**default_headers(api_key),
},
timeout=DEFAULT_REQUEST_TIMEOUT_SECONDS)
logger.debug('Starting processing response: %s' % status_url)
return _handle_http_response(response)
class BaseAsyncApi(AsyncTaskGetMixin):
"""BaseAsyncApi,for async task, internal use only.
"""
@classmethod
def _validate_params(cls, api_key, model):
if api_key is None:
api_key = get_default_api_key()
if model is None or not model:
raise ModelRequired('Model is required!')
return api_key, model
@classmethod
def call(cls, *args, api_key: None, **kwargs) -> DashScopeAPIResponse:
"""Call service and get result.
"""
task_response = cls.async_call(*args, api_key=api_key, **kwargs)
response = cls.wait(task_response, api_key=api_key)
return response
@classmethod
def _get_task_id(cls, task):
if isinstance(task, str):
task_id = task
elif isinstance(task, DashScopeAPIResponse):
if task.status_code == HTTPStatus.OK:
task_id = task.output['task_id']
else:
raise InvalidTask('Invalid task, task create failed: %s' %
task)
else:
raise InvalidParameter('Task invalid!')
if task_id is None or task_id == '':
raise InvalidParameter('Task id required!')
return task_id
@classmethod
def cancel(
cls,
task: Union[str, DashScopeAPIResponse],
api_key: str = None,
) -> DashScopeAPIResponse:
"""Cancel PENDING task.
Args:
task (Union[str, DashScopeAPIResponse]): The task_id, or
async_call response.
api_key (str, optional): The api-key. Defaults to None.
Returns:
DashScopeAPIResponse: The cancel result.
"""
task_id = cls._get_task_id(task)
url = _normalization_url('tasks', task_id, 'cancel')
with requests.Session() as session:
response = session.post(url,
headers={
**default_headers(api_key),
})
return _handle_http_response(response)
@classmethod
def list(cls,
start_time: str = None,
end_time: str = None,
model_name: str = None,
api_key_id: str = None,
region: str = None,
status: str = None,
page_no: int = 1,
page_size: int = 10,
api_key: str = None,
**kwargs) -> DashScopeAPIResponse:
"""List async tasks.
Args:
start_time (str, optional): The tasks start time,
for example: 20230420000000. Defaults to None.
end_time (str, optional): The tasks end time,
for example: 20230420000000. Defaults to None.
model_name (str, optional): The tasks model name.
Defaults to None.
api_key_id (str, optional): The tasks api-key-id.
Defaults to None.
region (str, optional): The service region,
for example: cn-beijing. Defaults to None.
status (str, optional): The status of tasks[PENDING,
RUNNING, SUCCEEDED, FAILED, CANCELED]. Defaults to None.
page_no (int, optional): The page number. Defaults to 1.
page_size (int, optional): The page size. Defaults to 10.
api_key (str, optional): The user api-key. Defaults to None.
Returns:
DashScopeAPIResponse: The response data.
"""
url = _normalization_url('tasks')
params = {'page_no': page_no, 'page_size': page_size}
if start_time is not None:
params['start_time'] = start_time
if end_time is not None:
params['end_time'] = end_time
if model_name is not None:
params['model_name'] = model_name
if api_key_id is not None:
params['api_key_id'] = api_key_id
if region is not None:
params['region'] = region
if status is not None:
params['status'] = status
with requests.Session() as session:
response = session.get(url,
params=params,
headers={
**default_headers(api_key),
})
if response.status_code == HTTPStatus.OK:
json_content = response.json()
request_id = ''
if 'request_id' in json_content:
request_id = json_content['request_id']
json_content.pop('request_id')
return DashScopeAPIResponse(request_id=request_id,
status_code=response.status_code,
code=None,
output=json_content,
usage=None,
message='')
else:
return _handle_http_failed_response(response)
@classmethod
def fetch(
cls,
task: Union[str, DashScopeAPIResponse],
api_key: str = None,
) -> DashScopeAPIResponse:
"""Query async task status.
Args:
task (Union[str, DashScopeAPIResponse]): The task_id, or
async_call response.
api_key (str, optional): The api_key. Defaults to None.
Returns:
DashScopeAPIResponse: The async task information.
"""
task_id = cls._get_task_id(task)
return cls._get(task_id, api_key)
@classmethod
def wait(cls,
task: Union[str, DashScopeAPIResponse],
api_key: str = None,
**kwargs) -> DashScopeAPIResponse:
"""Wait for async task completion and return task result.
Args:
task (Union[str, DashScopeAPIResponse]): The task_id, or
async_call response.
api_key (str, optional): The api_key. Defaults to None.
Returns:
DashScopeAPIResponse: The async task information.
"""
task_id = cls._get_task_id(task)
wait_seconds = 1
max_wait_seconds = 5
increment_steps = 3
step = 0
while True:
step += 1
# we start by querying once every second, and double
# the query interval after every 3(increment_steps)
# intervals, until we hit the max waiting interval
# of 5(seconds
# TODO: investigate if we can use long-poll
# (server side return immediately when ready)
if wait_seconds < max_wait_seconds and step % increment_steps == 0:
wait_seconds = min(wait_seconds * 2, max_wait_seconds)
rsp = cls._get(task_id, api_key)
if rsp.status_code == HTTPStatus.OK:
if rsp.output is None:
return rsp
task_status = rsp.output['task_status']
if task_status in [
TaskStatus.FAILED, TaskStatus.CANCELED,
TaskStatus.SUCCEEDED, TaskStatus.UNKNOWN
]:
return rsp
else:
logger.info('The task %s is %s' % (task_id, task_status))
time.sleep(wait_seconds)
elif rsp.status_code in REPEATABLE_STATUS:
logger.warn(
('Get task: %s temporary failure, \
status_code: %s, code: %s message: %s, will try again.'
) % (task_id, rsp.status_code, rsp.code, rsp.message))
time.sleep(wait_seconds)
else:
return rsp
@classmethod
def async_call(cls,
model: str,
input: object,
task_group: str,
task: str = None,
function: str = None,
api_key: str = None,
**kwargs) -> DashScopeAPIResponse:
"""Call async service return async task information.
Args:
model (str): The requested model, such as gpt3-v2
input (object): The api input data, cannot be None.
task_group (str, optional): The api task group.
task (str, optional): The task name. Defaults to None.
function (str, optional): The function of the task.
Defaults to None.
api_key (str, optional): The api api_key, if not present,
will get by default rule(TODO: api key doc). Defaults to None.
Returns:
DashScopeAPIResponse: The async task information,
which contains the task id, you can use the task id
to query the task status.
"""
is_stream = kwargs.pop('stream', None) # async api not support stream.
if is_stream:
logger.warn('async_call do not support stream argument')
api_key, model = BaseApi._validate_params(api_key, model)
request = _build_api_request(model=model,
input=input,
task_group=task_group,
task=task,
function=function,
api_key=api_key,
async_request=True,
query=False,
**kwargs)
return request.call()
def _get(url, params={}, api_key=None, **kwargs) -> DashScopeAPIResponse:
timeout = kwargs.pop(REQUEST_TIMEOUT_KEYWORD,
DEFAULT_REQUEST_TIMEOUT_SECONDS)
with requests.Session() as session:
logger.debug('Starting request: %s' % url)
response = session.get(url,
headers={
**default_headers(api_key),
**kwargs.pop('headers', {})
},
params=params,
timeout=timeout)
logger.debug('Starting processing response: %s' % url)
return _handle_http_response(response)
class ListMixin():
@classmethod
def list(cls,
page_no=1,
page_size=10,
api_key: str = None,
**kwargs) -> DashScopeAPIResponse:
"""list objects
Args:
api_key (str, optional): The api api_key, if not present,
will get by default rule(TODO: api key doc). Defaults to None.
page_no (int, optional): Page number. Defaults to 1.
page_size (int, optional): Items per page. Defaults to 10.
Returns:
DashScopeAPIResponse: The object list in output.
"""
url = join_url(dashscope.base_http_api_url, cls.SUB_PATH.lower())
params = {'page_no': page_no, 'page_size': page_size}
return _get(url, params=params, api_key=api_key, **kwargs)
class LogMixin():
@classmethod
def logs(cls,
job_id: str,
offset=1,
line=1000,
api_key: str = None,
**kwargs) -> DashScopeAPIResponse:
"""Get log of the job.
Args:
job_id (str): The job id(used for fine-tune)
offset (int, optional): start log line. Defaults to 1.
line (int, optional): total line return. Defaults to 1000.
api_key (str, optional): The api key. Defaults to None.
Returns:
DashScopeAPIResponse: The response
"""
url = join_url(dashscope.base_http_api_url, cls.SUB_PATH.lower(),
job_id, 'logs')
params = {'offset': offset, 'line': line}
return _get(url, params=params, api_key=api_key, **kwargs)
class GetMixin():
@classmethod
def get(cls,
target,
api_key: str = None,
params: dict = {},
**kwargs) -> DashScopeAPIResponse:
"""Get object information.
Args:
target (str): The target to get, such as model_id.
api_key (str, optional): The api api_key, if not present,
will get by default rule(TODO: api key doc). Defaults to None.
Returns:
DashScopeAPIResponse: The object information in output.
"""
url = join_url(dashscope.base_http_api_url, cls.SUB_PATH.lower(),
target)
return _get(url, api_key=api_key, params=params, **kwargs)
class GetStatusMixin():
@classmethod
def get(cls,
target,
api_key: str = None,
**kwargs) -> DashScopeAPIResponse:
"""Get object information.
Args:
target (str): The target to get, such as model_id.
api_key (str, optional): The api api_key, if not present,
will get by default rule(TODO: api key doc). Defaults to None.
Returns:
DashScopeAPIResponse: The object information in output.
"""
url = join_url(dashscope.base_http_api_url, cls.SUB_PATH.lower(),
target)
return _get(url, api_key, **kwargs)
class DeleteMixin():
@classmethod
def delete(cls,
target: str,
api_key: str = None,
**kwargs) -> DashScopeAPIResponse:
"""Delete object.
Args:
target (str): The object to delete, .
api_key (str, optional): The api api_key, if not present,
will get by default rule(TODO: api key doc). Defaults to None.
Returns:
DashScopeAPIResponse: The delete result.
"""
url = join_url(dashscope.base_http_api_url, cls.SUB_PATH.lower(),
target)
timeout = kwargs.pop(REQUEST_TIMEOUT_KEYWORD,
DEFAULT_REQUEST_TIMEOUT_SECONDS)
with requests.Session() as session:
logger.debug('Starting request: %s' % url)
response = session.delete(url,
headers={
**default_headers(api_key),
**kwargs.pop('headers', {})
},
timeout=timeout)
logger.debug('Starting processing response: %s' % url)
return _handle_http_response(response)
class CreateMixin():
@classmethod
def call(cls,
data: object,
api_key: str = None,
**kwargs) -> DashScopeAPIResponse:
"""Create a object
Args:
data (object): The create request json body.
api_key (str, optional): The api api_key, if not present,
will get by default rule(TODO: api key doc). Defaults to None.
Returns:
DashScopeAPIResponse: The created object in output.
"""
url = join_url(dashscope.base_http_api_url, cls.SUB_PATH.lower())
timeout = kwargs.pop(REQUEST_TIMEOUT_KEYWORD,
DEFAULT_REQUEST_TIMEOUT_SECONDS)
with requests.Session() as session:
logger.debug('Starting request: %s' % url)
response = session.post(url,
json=data,
headers={
**default_headers(api_key),
**kwargs.pop('headers', {})
},
timeout=timeout)
logger.debug('Starting processing response: %s' % url)
return _handle_http_response(response)
class UpdateMixin():
@classmethod
def update(cls,
target: str,
json: object,
api_key: str = None,
**kwargs) -> DashScopeAPIResponse:
"""Async update a object
Args:
target (str): The target to update.
json (object): The create request json body.
api_key (str, optional): The api api_key, if not present,
will get by default rule(TODO: api key doc). Defaults to None.
Returns:
DashScopeAPIResponse: The updated object information in output.
"""
url = join_url(dashscope.base_http_api_url, cls.SUB_PATH.lower(),
target)
timeout = kwargs.pop(REQUEST_TIMEOUT_KEYWORD,
DEFAULT_REQUEST_TIMEOUT_SECONDS)
with requests.Session() as session:
logger.debug('Starting request: %s' % url)
response = session.patch(url,
json=json,
headers={
**default_headers(api_key),
**kwargs.pop('headers', {})
},
timeout=timeout)
logger.debug('Starting processing response: %s' % url)
return _handle_http_response(response)
class PutMixin():
@classmethod
def put(cls,
target: str,
json: object,
extra_path: str = None,
api_key: str = None,
**kwargs) -> DashScopeAPIResponse:
"""Async update a object
Args:
target (str): The target to update.
json (object): The create request json body.
api_key (str, optional): The api api_key, if not present,
will get by default rule(TODO: api key doc). Defaults to None.
Returns:
DashScopeAPIResponse: The updated object information in output.
"""
if extra_path is None:
url = join_url(dashscope.base_http_api_url, cls.SUB_PATH.lower(),
target)
else:
url = join_url(dashscope.base_http_api_url, cls.SUB_PATH.lower(),
target, extra_path)
print(url)
timeout = kwargs.pop(REQUEST_TIMEOUT_KEYWORD,
DEFAULT_REQUEST_TIMEOUT_SECONDS)
with requests.Session() as session:
logger.debug('Starting request: %s' % url)
response = session.put(url,
json=json,
headers={
**default_headers(api_key),
**kwargs.pop('headers', {})
},
timeout=timeout)
logger.debug('Starting processing response: %s' % url)
return _handle_http_response(response)
class FileUploadMixin():
@classmethod
def upload(cls,
files: list,
descriptions: List[str] = None,
params: dict = None,
api_key: str = None,
**kwargs) -> DashScopeAPIResponse:
"""Upload files
Args:
files (list): List of (name, opened file, file_name).
descriptions (list[str]): The file description messages.
params (dict): The parameters
api_key (str, optional): The api api_key, if not present,
will get by default rule(TODO: api key doc). Defaults to None.
Returns:
DashScopeAPIResponse: The uploaded file information in the output.
"""
url = join_url(dashscope.base_http_api_url, cls.SUB_PATH.lower())
js = None
if descriptions:
js = {'descriptions': descriptions}
timeout = kwargs.pop(REQUEST_TIMEOUT_KEYWORD,
DEFAULT_REQUEST_TIMEOUT_SECONDS)
with requests.Session() as session:
logger.debug('Starting request: %s' % url)
response = session.post(url,
data=js,
headers={
**default_headers(api_key),
**kwargs.pop('headers', {})
},
files=files,
timeout=timeout)
logger.debug('Starting processing response: %s' % url)
return _handle_http_response(response)
class CancelMixin():
@classmethod
def cancel(cls,
target: str,
api_key: str = None,
**kwargs) -> DashScopeAPIResponse:
"""Cancel a job.
Args:
target (str): The request params, key/value map.
api_key (str, optional): The api api_key, if not present,
will get by default rule(TODO: api key doc). Defaults to None.
Returns:
DashScopeAPIResponse: The cancel result.
"""
url = join_url(dashscope.base_http_api_url, cls.SUB_PATH.lower(),
target, 'cancel')
timeout = kwargs.pop(REQUEST_TIMEOUT_KEYWORD,
DEFAULT_REQUEST_TIMEOUT_SECONDS)
with requests.Session() as session:
logger.debug('Starting request: %s' % url)
response = session.post(url,
headers={
**default_headers(api_key),
**kwargs.pop('headers', {})
},
timeout=timeout)
logger.debug('Starting processing response: %s' % url)
return _handle_http_response(response)
class StreamEventMixin():
@classmethod
def _handle_stream(cls, response: requests.Response):
# TODO define done message.
is_error = False
status_code = HTTPStatus.INTERNAL_SERVER_ERROR
for line in response.iter_lines():
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...
@classmethod
def _handle_response(cls, response: requests.Response):
request_id = ''
if (response.status_code == HTTPStatus.OK
and SSE_CONTENT_TYPE in response.headers.get(
'content-type', '')):
for is_error, status_code, data in cls._handle_stream(response):
if is_error:
yield DashScopeAPIResponse(request_id=request_id,
status_code=status_code,
output=None,
code='',
message='') # noqa E501
else:
yield DashScopeAPIResponse(request_id=request_id,
status_code=HTTPStatus.OK,
output=data,
usage=None)
elif response.status_code == HTTPStatus.OK:
json_content = response.json()
request_id = ''
if 'request_id' in json_content:
request_id = json_content['request_id']
yield DashScopeAPIResponse(request_id=request_id,
status_code=HTTPStatus.OK,
output=json_content,
usage=None)
else:
_handle_http_failed_response(response)
@classmethod
def stream_events(cls,
target,
api_key: str = None,
**kwargs) -> DashScopeAPIResponse:
"""Get job log.
Args:
target (str): The target to get, such as model_id.
api_key (str, optional): The api api_key, if not present,
will get by default rule(TODO: api key doc). Defaults to None.
Returns:
DashScopeAPIResponse: The target outputs.
"""
url = join_url(dashscope.base_http_api_url, cls.SUB_PATH.lower(),
target, 'stream')
timeout = kwargs.pop(REQUEST_TIMEOUT_KEYWORD,
DEFAULT_REQUEST_TIMEOUT_SECONDS)
with requests.Session() as session:
logger.debug('Starting request: %s' % url)
response = session.get(url,
headers={
**default_headers(api_key),
**kwargs.pop('headers', {})
},
stream=True,
timeout=timeout)
logger.debug('Starting processing response: %s' % url)
for rsp in cls._handle_response(response):
yield rsp