Files
wehub-resource-sync a203934033
Lint test / lint (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:34:58 +08:00

159 lines
6.6 KiB
Python

# Copyright (c) ModelScope Contributors. All rights reserved.
import aiohttp
import json
from copy import deepcopy
from dacite import from_dict
from dataclasses import asdict
from requests.exceptions import HTTPError
from typing import Any, AsyncIterator, Dict, Iterator, List, Optional, Union
from swift.metrics import Metric
from .infer_engine import InferEngine
from .protocol import (ChatCompletionRequest, ChatCompletionResponse, ChatCompletionStreamResponse, InferRequest,
ModelList, RequestConfig)
class InferClient(InferEngine):
def __init__(self,
host: str = '127.0.0.1',
port: int = 8000,
api_key: str = 'EMPTY',
*,
base_url: Optional[str] = None,
timeout: Optional[int] = 86400) -> None:
"""
Initialize the InferClient.
Args:
host (str): The hostname of the inference server. Defaults to '127.0.0.1'.
port (str): The port of the inference server. Defaults to '8000'.
api_key (str): The API key for authentication. Defaults to 'EMPTY'.
timeout (Optional[int]): The timeout for requests in seconds. Defaults to None.
"""
self.api_key = api_key
self.host = host
self.port = port
self.timeout = timeout
if base_url is None:
base_url = f'http://{self.host}:{self.port}/v1'
self.base_url = base_url
self._models = None
@property
def models(self):
if self._models is None:
models = []
for model in self.get_model_list().data:
models.append(model.id)
assert len(models) > 0, f'models: {models}'
self._models = models
return self._models
def get_model_list(self) -> ModelList:
"""Get model list from the inference server.
"""
coro = self.get_model_list_async()
return self.safe_asyncio_run(coro)
def _get_request_kwargs(self) -> Dict[str, Any]:
request_kwargs = {}
if isinstance(self.timeout, (int, float)) and self.timeout > 0:
request_kwargs['timeout'] = self.timeout
if self.api_key is not None:
request_kwargs['headers'] = {'Authorization': f'Bearer {self.api_key}'}
return request_kwargs
async def get_model_list_async(self) -> ModelList:
url = f"{self.base_url.rstrip('/')}/models"
async with aiohttp.ClientSession() as session:
async with session.get(url, **self._get_request_kwargs()) as resp:
resp_obj = await resp.json()
return from_dict(ModelList, resp_obj)
def infer(
self,
infer_requests: List[InferRequest],
request_config: Optional[RequestConfig] = None,
metrics: Optional[List[Metric]] = None,
*,
model: Optional[str] = None,
use_tqdm: Optional[bool] = None
) -> List[Union[ChatCompletionResponse, Iterator[ChatCompletionStreamResponse]]]:
"""
Perform inference using the specified model.
Args:
infer_requests (List[InferRequest]): A list of inference requests.
request_config (Optional[RequestConfig]): Configuration for the request. Defaults to None.
metrics (Optional[List[Metric]]): The usage information to return. Defaults to None.
model (Optional[str]): The model name to be used for inference. Defaults to None.
use_tqdm (Optional[bool]): Whether to use tqdm for progress tracking. Defaults to None.
Returns:
List[Union[ChatCompletionResponse, Iterator[ChatCompletionStreamResponse]]]:
The inference responses or an iterator of streaming responses.
"""
return super().infer(infer_requests, request_config, metrics, model=model, use_tqdm=use_tqdm)
@staticmethod
def _prepare_request_data(model: str, infer_request: InferRequest, request_config: RequestConfig) -> Dict[str, Any]:
if not isinstance(infer_request, dict):
infer_request = asdict(infer_request)
res = asdict(ChatCompletionRequest(model, **infer_request, **asdict(request_config)))
# ignore empty
empty_request = ChatCompletionRequest('', [])
for k in list(res.keys()):
if res[k] == getattr(empty_request, k):
res.pop(k)
return res
@staticmethod
def _parse_stream_data(data: bytes) -> Optional[str]:
data = data.decode(encoding='utf-8')
data = data.strip()
if len(data) == 0:
return
assert data.startswith('data:'), f'data: {data}'
return data[5:].strip()
async def infer_async(
self,
infer_request: InferRequest,
request_config: Optional[RequestConfig] = None,
*,
model: Optional[str] = None,
) -> Union[ChatCompletionResponse, AsyncIterator[ChatCompletionStreamResponse]]:
request_config = deepcopy(request_config or RequestConfig())
if model is None:
if len(self.models) == 1:
model = self.models[0]
else:
raise ValueError(f'Please explicitly specify the model. Available models: {self.models}.')
url = f"{self.base_url.rstrip('/')}/chat/completions"
request_data = self._prepare_request_data(model, infer_request, request_config)
if request_config.stream:
async def _gen_stream() -> AsyncIterator[ChatCompletionStreamResponse]:
async with aiohttp.ClientSession() as session:
async with session.post(url, json=request_data, **self._get_request_kwargs()) as resp:
async for data in resp.content:
data = self._parse_stream_data(data)
if data == '[DONE]':
break
if data is not None:
resp_obj = json.loads(data)
if resp_obj['object'] == 'error':
raise HTTPError(resp_obj['message'])
yield from_dict(ChatCompletionStreamResponse, resp_obj)
return _gen_stream()
else:
async with aiohttp.ClientSession() as session:
async with session.post(url, json=request_data, **self._get_request_kwargs()) as resp:
resp_obj = await resp.json()
if resp_obj['object'] == 'error':
raise HTTPError(resp_obj['message'])
return from_dict(ChatCompletionResponse, resp_obj)