diff --git a/src/gigachat/api/assistants/__init__.py b/src/gigachat/api/assistants/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/gigachat/api/assistants/get_assistants.py b/src/gigachat/api/assistants/get_assistants.py new file mode 100644 index 0000000..b04c903 --- /dev/null +++ b/src/gigachat/api/assistants/get_assistants.py @@ -0,0 +1,57 @@ +from http import HTTPStatus +from typing import Any, Dict, Optional + +import httpx + +from gigachat.api.utils import build_headers +from gigachat.exceptions import AuthenticationError, ResponseError +from gigachat.models import Assistants + + +def _get_kwargs( + *, + assistant_id: Optional[str] = None, + access_token: Optional[str] = None, +) -> Dict[str, Any]: + headers = build_headers(access_token) + params = { + "method": "GET", + "url": "/assistants", + "headers": headers, + } + if assistant_id: + params["params"] = {"assistant_id": assistant_id} + return params + + +def _build_response(response: httpx.Response) -> Assistants: + if response.status_code == HTTPStatus.OK: + return Assistants(**response.json()) + elif response.status_code == HTTPStatus.UNAUTHORIZED: + raise AuthenticationError(response.url, response.status_code, response.content, response.headers) + else: + raise ResponseError(response.url, response.status_code, response.content, response.headers) + + +def sync( + client: httpx.Client, + *, + assistant_id: Optional[str] = None, + access_token: Optional[str] = None, +) -> Assistants: + """Возвращает массив объектов с данными доступных ассистентов""" + kwargs = _get_kwargs(assistant_id=assistant_id, access_token=access_token) + response = client.request(**kwargs) + return _build_response(response) + + +async def asyncio( + client: httpx.AsyncClient, + *, + assistant_id: Optional[str] = None, + access_token: Optional[str] = None, +) -> Assistants: + """Возвращает массив объектов с данными доступных ассистентов""" + kwargs = _get_kwargs(assistant_id=assistant_id, access_token=access_token) + response = await client.request(**kwargs) + return _build_response(response) diff --git a/src/gigachat/api/assistants/post_assistant_files_delete.py b/src/gigachat/api/assistants/post_assistant_files_delete.py new file mode 100644 index 0000000..01e5924 --- /dev/null +++ b/src/gigachat/api/assistants/post_assistant_files_delete.py @@ -0,0 +1,60 @@ +from http import HTTPStatus +from typing import Any, Dict, Optional + +import httpx + +from gigachat.api.utils import build_headers +from gigachat.exceptions import AuthenticationError, ResponseError +from gigachat.models import AssistantFileDelete + + +def _get_kwargs( + *, + assistant_id: str, + file_id: str, + access_token: Optional[str] = None, +) -> Dict[str, Any]: + headers = build_headers(access_token) + + return { + "method": "POST", + "url": "/assistants/files/delete", + "json": { + "assistant_id": assistant_id, + "file_id": file_id, + }, + "headers": headers, + } + + +def _build_response(response: httpx.Response) -> AssistantFileDelete: + if response.status_code == HTTPStatus.OK: + return AssistantFileDelete(**response.json()) + elif response.status_code == HTTPStatus.UNAUTHORIZED: + raise AuthenticationError(response.url, response.status_code, response.content, response.headers) + else: + raise ResponseError(response.url, response.status_code, response.content, response.headers) + + +def sync( + client: httpx.Client, + *, + assistant_id: str, + file_id: str, + access_token: Optional[str] = None, +) -> AssistantFileDelete: + kwargs = _get_kwargs(assistant_id=assistant_id, file_id=file_id, access_token=access_token) + response = client.request(**kwargs) + return _build_response(response) + + +async def asyncio( + client: httpx.AsyncClient, + *, + assistant_id: str, + file_id: str, + access_token: Optional[str] = None, +) -> AssistantFileDelete: + kwargs = _get_kwargs(assistant_id=assistant_id, file_id=file_id, access_token=access_token) + response = await client.request(**kwargs) + return _build_response(response) diff --git a/src/gigachat/api/assistants/post_assistant_modify.py b/src/gigachat/api/assistants/post_assistant_modify.py new file mode 100644 index 0000000..4cdf96f --- /dev/null +++ b/src/gigachat/api/assistants/post_assistant_modify.py @@ -0,0 +1,98 @@ +from http import HTTPStatus +from typing import Any, Dict, List, Optional + +import httpx + +from gigachat.api.utils import build_headers +from gigachat.exceptions import AuthenticationError, ResponseError +from gigachat.models import Assistant + + +def _get_kwargs( + *, + assistant_id: str, + model: Optional[str] = None, + name: Optional[str] = None, + description: Optional[str] = None, + instructions: Optional[str] = None, + file_ids: Optional[List[str]] = None, + metadata: Optional[Dict[str, Any]] = None, + access_token: Optional[str] = None, +) -> Dict[str, Any]: + headers = build_headers(access_token) + + return { + "method": "POST", + "url": "/assistants/modify", + "json": { + "assistant_id": assistant_id, + "model": model, + "name": name, + "description": description, + "instructions": instructions, + "file_ids": file_ids, + "metadata": metadata, + }, + "headers": headers, + } + + +def _build_response(response: httpx.Response) -> Assistant: + if response.status_code == HTTPStatus.OK: + return Assistant(**response.json()) + elif response.status_code == HTTPStatus.UNAUTHORIZED: + raise AuthenticationError(response.url, response.status_code, response.content, response.headers) + else: + raise ResponseError(response.url, response.status_code, response.content, response.headers) + + +def sync( + client: httpx.Client, + *, + assistant_id: str, + model: Optional[str] = None, + name: Optional[str] = None, + description: Optional[str] = None, + instructions: Optional[str] = None, + file_ids: Optional[List[str]] = None, + metadata: Optional[Dict[str, Any]] = None, + access_token: Optional[str] = None, +) -> Assistant: + kwargs = _get_kwargs( + assistant_id=assistant_id, + model=model, + name=name, + description=description, + instructions=instructions, + file_ids=file_ids, + metadata=metadata, + access_token=access_token, + ) + response = client.request(**kwargs) + return _build_response(response) + + +async def asyncio( + client: httpx.AsyncClient, + *, + assistant_id: str, + model: Optional[str] = None, + name: Optional[str] = None, + description: Optional[str] = None, + instructions: Optional[str] = None, + file_ids: Optional[List[str]] = None, + metadata: Optional[Dict[str, Any]] = None, + access_token: Optional[str] = None, +) -> Assistant: + kwargs = _get_kwargs( + assistant_id=assistant_id, + model=model, + name=name, + description=description, + instructions=instructions, + file_ids=file_ids, + metadata=metadata, + access_token=access_token, + ) + response = await client.request(**kwargs) + return _build_response(response) diff --git a/src/gigachat/api/assistants/post_assistants.py b/src/gigachat/api/assistants/post_assistants.py new file mode 100644 index 0000000..f82da0a --- /dev/null +++ b/src/gigachat/api/assistants/post_assistants.py @@ -0,0 +1,94 @@ +from http import HTTPStatus +from typing import Any, Dict, List, Optional + +import httpx + +from gigachat.api.utils import build_headers +from gigachat.exceptions import AuthenticationError, ResponseError +from gigachat.models import CreateAssistant + + +def _get_kwargs( + *, + model: str, + name: str, + description: Optional[str] = None, + instructions: str, + file_ids: Optional[List[str]] = None, + metadata: Optional[Dict[str, Any]] = None, + access_token: Optional[str] = None, +) -> Dict[str, Any]: + headers = build_headers(access_token) + + return { + "method": "POST", + "url": "/assistants", + "json": { + "model": model, + "name": name, + "description": description, + "instructions": instructions, + "file_ids": file_ids, + "metadata": metadata, + }, + "headers": headers, + } + + +def _build_response(response: httpx.Response) -> CreateAssistant: + if response.status_code == HTTPStatus.OK: + return CreateAssistant(**response.json()) + elif response.status_code == HTTPStatus.UNAUTHORIZED: + raise AuthenticationError(response.url, response.status_code, response.content, response.headers) + else: + raise ResponseError(response.url, response.status_code, response.content, response.headers) + + +def sync( + client: httpx.Client, + *, + model: str, + name: str, + description: Optional[str] = None, + instructions: str, + file_ids: Optional[List[str]] = None, + metadata: Optional[Dict[str, Any]] = None, + access_token: Optional[str] = None, +) -> CreateAssistant: + """Создание ассистента""" + kwargs = _get_kwargs( + model=model, + name=name, + description=description, + instructions=instructions, + file_ids=file_ids, + metadata=metadata, + access_token=access_token, + ) + response = client.request(**kwargs) + return _build_response(response) + + +async def asyncio( + client: httpx.AsyncClient, + *, + model: str, + name: str, + description: Optional[str] = None, + instructions: str, + file_ids: Optional[List[str]] = None, + metadata: Optional[Dict[str, Any]] = None, + access_token: Optional[str] = None, +) -> CreateAssistant: + """Создание ассистента""" + kwargs = _get_kwargs( + model=model, + name=name, + description=description, + instructions=instructions, + file_ids=file_ids, + metadata=metadata, + access_token=access_token, + ) + response = await client.request(**kwargs) + return _build_response(response) diff --git a/src/gigachat/api/threads/__init__.py b/src/gigachat/api/threads/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/gigachat/api/threads/get_threads.py b/src/gigachat/api/threads/get_threads.py new file mode 100644 index 0000000..6b94a60 --- /dev/null +++ b/src/gigachat/api/threads/get_threads.py @@ -0,0 +1,52 @@ +from http import HTTPStatus +from typing import Any, Dict, Optional + +import httpx + +from gigachat.api.utils import build_headers +from gigachat.exceptions import AuthenticationError, ResponseError +from gigachat.models import Threads + + +def _get_kwargs( + *, + access_token: Optional[str] = None, +) -> Dict[str, Any]: + headers = build_headers(access_token) + params = { + "method": "GET", + "url": "/threads", + "headers": headers, + } + return params + + +def _build_response(response: httpx.Response) -> Threads: + if response.status_code == HTTPStatus.OK: + return Threads(**response.json()) + elif response.status_code == HTTPStatus.UNAUTHORIZED: + raise AuthenticationError(response.url, response.status_code, response.content, response.headers) + else: + raise ResponseError(response.url, response.status_code, response.content, response.headers) + + +def sync( + client: httpx.Client, + *, + access_token: Optional[str] = None, +) -> Threads: + """Получение перечня тредов""" + kwargs = _get_kwargs(access_token=access_token) + response = client.request(**kwargs) + return _build_response(response) + + +async def asyncio( + client: httpx.AsyncClient, + *, + access_token: Optional[str] = None, +) -> Threads: + """Получение перечня тредов""" + kwargs = _get_kwargs(access_token=access_token) + response = await client.request(**kwargs) + return _build_response(response) diff --git a/src/gigachat/api/threads/get_threads_messages.py b/src/gigachat/api/threads/get_threads_messages.py new file mode 100644 index 0000000..4548914 --- /dev/null +++ b/src/gigachat/api/threads/get_threads_messages.py @@ -0,0 +1,62 @@ +from http import HTTPStatus +from typing import Any, Dict, Optional + +import httpx + +from gigachat.api.utils import build_headers +from gigachat.exceptions import AuthenticationError, ResponseError +from gigachat.models import ThreadMessages + + +def _get_kwargs( + *, + thread_id: str, + limit: Optional[int] = None, + access_token: Optional[str] = None, +) -> Dict[str, Any]: + headers = build_headers(access_token) + params: Dict[str, Any] = {"thread_id": thread_id} + if limit: + params["limit"] = limit + params = { + "method": "GET", + "url": "/threads/messages", + "headers": headers, + "params": params, + } + return params + + +def _build_response(response: httpx.Response) -> ThreadMessages: + if response.status_code == HTTPStatus.OK: + return ThreadMessages(**response.json()) + elif response.status_code == HTTPStatus.UNAUTHORIZED: + raise AuthenticationError(response.url, response.status_code, response.content, response.headers) + else: + raise ResponseError(response.url, response.status_code, response.content, response.headers) + + +def sync( + client: httpx.Client, + *, + thread_id: str, + limit: Optional[int] = None, + access_token: Optional[str] = None, +) -> ThreadMessages: + """Получение сообщений треда""" + kwargs = _get_kwargs(thread_id=thread_id, limit=limit, access_token=access_token) + response = client.request(**kwargs) + return _build_response(response) + + +async def asyncio( + client: httpx.AsyncClient, + *, + thread_id: str, + limit: Optional[int] = None, + access_token: Optional[str] = None, +) -> ThreadMessages: + """Получение сообщений треда""" + kwargs = _get_kwargs(thread_id=thread_id, limit=limit, access_token=access_token) + response = await client.request(**kwargs) + return _build_response(response) diff --git a/src/gigachat/api/threads/get_threads_run.py b/src/gigachat/api/threads/get_threads_run.py new file mode 100644 index 0000000..543aff3 --- /dev/null +++ b/src/gigachat/api/threads/get_threads_run.py @@ -0,0 +1,56 @@ +from http import HTTPStatus +from typing import Any, Dict, Optional + +import httpx + +from gigachat.api.utils import build_headers +from gigachat.exceptions import AuthenticationError, ResponseError +from gigachat.models import ThreadRunResult + + +def _get_kwargs( + *, + thread_id: str, + access_token: Optional[str] = None, +) -> Dict[str, Any]: + headers = build_headers(access_token) + params = { + "method": "GET", + "url": "/threads/run", + "headers": headers, + "params": {"thread_id": thread_id}, + } + return params + + +def _build_response(response: httpx.Response) -> ThreadRunResult: + if response.status_code == HTTPStatus.OK: + return ThreadRunResult(**response.json()) + elif response.status_code == HTTPStatus.UNAUTHORIZED: + raise AuthenticationError(response.url, response.status_code, response.content, response.headers) + else: + raise ResponseError(response.url, response.status_code, response.content, response.headers) + + +def sync( + client: httpx.Client, + *, + thread_id: str, + access_token: Optional[str] = None, +) -> ThreadRunResult: + """Получить результат run треда""" + kwargs = _get_kwargs(thread_id=thread_id, access_token=access_token) + response = client.request(**kwargs) + return _build_response(response) + + +async def asyncio( + client: httpx.AsyncClient, + *, + thread_id: str, + access_token: Optional[str] = None, +) -> ThreadRunResult: + """Получить результат run треда""" + kwargs = _get_kwargs(thread_id=thread_id, access_token=access_token) + response = await client.request(**kwargs) + return _build_response(response) diff --git a/src/gigachat/api/threads/post_thread_messages_rerun.py b/src/gigachat/api/threads/post_thread_messages_rerun.py new file mode 100644 index 0000000..40da1c0 --- /dev/null +++ b/src/gigachat/api/threads/post_thread_messages_rerun.py @@ -0,0 +1,75 @@ +from http import HTTPStatus +from typing import Any, Dict, Optional + +import httpx + +from gigachat.api.utils import build_headers +from gigachat.exceptions import AuthenticationError, ResponseError +from gigachat.models.threads import ThreadCompletion, ThreadRunOptions + + +def _get_kwargs( + *, + thread_id: str, + thread_options: Optional[ThreadRunOptions] = None, + access_token: Optional[str] = None, +) -> Dict[str, Any]: + headers = build_headers(access_token) + thread_options_dict = {} + if thread_options: + thread_options_dict = thread_options.dict(exclude_none=True, by_alias=True, exclude={"stream"}) + params = { + "method": "POST", + "url": "/threads/messages/rerun", + "headers": headers, + "json": { + **thread_options_dict, + **{ + "thread_id": thread_id, + }, + }, + } + return params + + +def _build_response(response: httpx.Response) -> ThreadCompletion: + if response.status_code == HTTPStatus.OK: + return ThreadCompletion(**response.json()) + elif response.status_code == HTTPStatus.UNAUTHORIZED: + raise AuthenticationError(response.url, response.status_code, response.content, response.headers) + else: + raise ResponseError(response.url, response.status_code, response.content, response.headers) + + +def sync( + client: httpx.Client, + *, + thread_id: str, + thread_options: Optional[ThreadRunOptions] = None, + access_token: Optional[str] = None, +) -> ThreadCompletion: + """Перегенерация ответа модели""" + kwargs = _get_kwargs( + thread_id=thread_id, + thread_options=thread_options, + access_token=access_token, + ) + response = client.request(**kwargs) + return _build_response(response) + + +async def asyncio( + client: httpx.AsyncClient, + *, + thread_id: str, + thread_options: Optional[ThreadRunOptions] = None, + access_token: Optional[str] = None, +) -> ThreadCompletion: + """Перегенерация ответа модели""" + kwargs = _get_kwargs( + thread_id=thread_id, + thread_options=thread_options, + access_token=access_token, + ) + response = await client.request(**kwargs) + return _build_response(response) diff --git a/src/gigachat/api/threads/post_thread_messages_rerun_stream.py b/src/gigachat/api/threads/post_thread_messages_rerun_stream.py new file mode 100644 index 0000000..be90d63 --- /dev/null +++ b/src/gigachat/api/threads/post_thread_messages_rerun_stream.py @@ -0,0 +1,116 @@ +from http import HTTPStatus +from typing import Any, AsyncIterator, Dict, Iterator, Optional + +import httpx + +from gigachat.api.utils import build_headers +from gigachat.exceptions import AuthenticationError, ResponseError +from gigachat.models.threads import ThreadCompletionChunk, ThreadRunOptions + +EVENT_STREAM = "text/event-stream" + + +def _get_kwargs( + *, + thread_id: str, + thread_options: Optional[ThreadRunOptions] = None, + update_interval: Optional[int] = None, + access_token: Optional[str] = None, +) -> Dict[str, Any]: + headers = build_headers(access_token) + thread_options_dict = {} + if thread_options: + thread_options_dict = thread_options.dict(exclude_none=True) + params = { + "method": "POST", + "url": "/threads/messages/rerun", + "headers": headers, + "json": { + **thread_options_dict, + **{ + "thread_id": thread_id, + "update_interval": update_interval, + "stream": True, + }, + }, + } + return params + + +def _parse_chunk(line: str) -> Optional[ThreadCompletionChunk]: + name, _, value = line.partition(": ") + if name == "data": + if value == "[DONE]": + return None + else: + return ThreadCompletionChunk.parse_raw(value) + else: + return None + + +def _check_content_type(response: httpx.Response) -> None: + content_type, _, _ = response.headers.get("content-type", "").partition(";") + if content_type != EVENT_STREAM: + raise httpx.TransportError(f"Expected response Content-Type to be '{EVENT_STREAM}', got {content_type!r}") + + +def _check_response(response: httpx.Response) -> None: + if response.status_code == HTTPStatus.OK: + _check_content_type(response) + elif response.status_code == HTTPStatus.UNAUTHORIZED: + raise AuthenticationError(response.url, response.status_code, response.read(), response.headers) + else: + raise ResponseError(response.url, response.status_code, response.read(), response.headers) + + +async def _acheck_response(response: httpx.Response) -> None: + if response.status_code == HTTPStatus.OK: + _check_content_type(response) + elif response.status_code == HTTPStatus.UNAUTHORIZED: + raise AuthenticationError(response.url, response.status_code, await response.aread(), response.headers) + else: + raise ResponseError(response.url, response.status_code, await response.aread(), response.headers) + + +def sync( + client: httpx.Client, + *, + thread_id: str, + thread_options: Optional[ThreadRunOptions] = None, + update_interval: Optional[int] = None, + access_token: Optional[str] = None, +) -> Iterator[ThreadCompletionChunk]: + """Перегенерация ответа модели""" + kwargs = _get_kwargs( + thread_id=thread_id, + thread_options=thread_options, + update_interval=update_interval, + access_token=access_token, + ) + with client.stream(**kwargs) as response: + _check_response(response) + for line in response.iter_lines(): + if chunk := _parse_chunk(line): + yield chunk + + +async def asyncio( + client: httpx.AsyncClient, + *, + thread_id: str, + thread_options: Optional[ThreadRunOptions] = None, + update_interval: Optional[int] = None, + access_token: Optional[str] = None, +) -> AsyncIterator[ThreadCompletionChunk]: + """Перегенерация ответа модели""" + kwargs = _get_kwargs( + thread_id=thread_id, + thread_options=thread_options, + update_interval=update_interval, + access_token=access_token, + ) + async with client.stream(**kwargs) as response: + await _acheck_response(response) + async for line in response.aiter_lines(): + if chunk := _parse_chunk(line): + yield chunk diff --git a/src/gigachat/api/threads/post_thread_messages_run.py b/src/gigachat/api/threads/post_thread_messages_run.py new file mode 100644 index 0000000..81c032a --- /dev/null +++ b/src/gigachat/api/threads/post_thread_messages_run.py @@ -0,0 +1,96 @@ +from http import HTTPStatus +from typing import Any, Dict, List, Optional + +import httpx + +from gigachat.api.utils import build_headers +from gigachat.exceptions import AuthenticationError, ResponseError +from gigachat.models import Messages +from gigachat.models.threads import ThreadCompletion, ThreadRunOptions + + +def _get_kwargs( + *, + messages: List[Messages], + thread_id: Optional[str] = None, + assistant_id: Optional[str] = None, + model: Optional[str] = None, + thread_options: Optional[ThreadRunOptions] = None, + access_token: Optional[str] = None, +) -> Dict[str, Any]: + headers = build_headers(access_token) + thread_options_dict = {} + if thread_options: + thread_options_dict = thread_options.dict(exclude_none=True, by_alias=True, exclude={"stream"}) + if thread_id is not None or assistant_id is not None: + model = None + params = { + "method": "POST", + "url": "/threads/messages/run", + "headers": headers, + "json": { + **thread_options_dict, + **{ + "thread_id": thread_id, + "assistant_id": assistant_id, + "model": model, + "messages": [message.dict(exclude_none=True) for message in messages], + }, + }, + } + return params + + +def _build_response(response: httpx.Response) -> ThreadCompletion: + if response.status_code == HTTPStatus.OK: + return ThreadCompletion(**response.json()) + elif response.status_code == HTTPStatus.UNAUTHORIZED: + raise AuthenticationError(response.url, response.status_code, response.content, response.headers) + else: + raise ResponseError(response.url, response.status_code, response.content, response.headers) + + +def sync( + client: httpx.Client, + *, + messages: List[Messages], + thread_id: Optional[str] = None, + assistant_id: Optional[str] = None, + model: Optional[str] = None, + thread_options: Optional[ThreadRunOptions] = None, + access_token: Optional[str] = None, +) -> ThreadCompletion: + """Добавление сообщений к треду с запуском""" + kwargs = _get_kwargs( + messages=messages, + thread_id=thread_id, + assistant_id=assistant_id, + model=model, + thread_options=thread_options, + access_token=access_token, + ) + response = client.request(**kwargs) + return _build_response(response) + + +async def asyncio( + client: httpx.AsyncClient, + *, + messages: List[Messages], + thread_id: Optional[str] = None, + assistant_id: Optional[str] = None, + model: Optional[str] = None, + thread_options: Optional[ThreadRunOptions] = None, + access_token: Optional[str] = None, +) -> ThreadCompletion: + """Добавление сообщений к треду с запуском""" + kwargs = _get_kwargs( + messages=messages, + thread_id=thread_id, + assistant_id=assistant_id, + model=model, + thread_options=thread_options, + access_token=access_token, + ) + response = await client.request(**kwargs) + return _build_response(response) diff --git a/src/gigachat/api/threads/post_thread_messages_run_stream.py b/src/gigachat/api/threads/post_thread_messages_run_stream.py new file mode 100644 index 0000000..54ca753 --- /dev/null +++ b/src/gigachat/api/threads/post_thread_messages_run_stream.py @@ -0,0 +1,135 @@ +from http import HTTPStatus +from typing import Any, AsyncIterator, Dict, Iterator, List, Optional + +import httpx + +from gigachat.api.utils import build_headers +from gigachat.exceptions import AuthenticationError, ResponseError +from gigachat.models import Messages +from gigachat.models.threads import ThreadCompletionChunk, ThreadRunOptions + +EVENT_STREAM = "text/event-stream" + + +def _get_kwargs( + *, + messages: List[Messages], + thread_id: Optional[str] = None, + assistant_id: Optional[str] = None, + model: Optional[str] = None, + thread_options: Optional[ThreadRunOptions] = None, + update_interval: Optional[int] = None, + access_token: Optional[str] = None, +) -> Dict[str, Any]: + headers = build_headers(access_token) + thread_options_dict = {} + if assistant_id is not None or thread_id is not None: + model = None + if thread_options: + thread_options_dict = thread_options.dict(exclude_none=True) + params = { + "method": "POST", + "url": "/threads/messages/run", + "headers": headers, + "json": { + **thread_options_dict, + **{ + "thread_id": thread_id, + "assistant_id": assistant_id, + "model": model, + "messages": [message.dict(exclude_none=True) for message in messages], + "update_interval": update_interval, + "stream": True, + }, + }, + } + return params + + +def _parse_chunk(line: str) -> Optional[ThreadCompletionChunk]: + name, _, value = line.partition(": ") + if name == "data": + if value == "[DONE]": + return None + else: + return ThreadCompletionChunk.parse_raw(value) + else: + return None + + +def _check_content_type(response: httpx.Response) -> None: + content_type, _, _ = response.headers.get("content-type", "").partition(";") + if content_type != EVENT_STREAM: + raise httpx.TransportError(f"Expected response Content-Type to be '{EVENT_STREAM}', got {content_type!r}") + + +def _check_response(response: httpx.Response) -> None: + if response.status_code == HTTPStatus.OK: + _check_content_type(response) + elif response.status_code == HTTPStatus.UNAUTHORIZED: + raise AuthenticationError(response.url, response.status_code, response.read(), response.headers) + else: + raise ResponseError(response.url, response.status_code, response.read(), response.headers) + + +async def _acheck_response(response: httpx.Response) -> None: + if response.status_code == HTTPStatus.OK: + _check_content_type(response) + elif response.status_code == HTTPStatus.UNAUTHORIZED: + raise AuthenticationError(response.url, response.status_code, await response.aread(), response.headers) + else: + raise ResponseError(response.url, response.status_code, await response.aread(), response.headers) + + +def sync( + client: httpx.Client, + *, + messages: List[Messages], + thread_id: Optional[str] = None, + assistant_id: Optional[str] = None, + model: Optional[str] = None, + thread_options: Optional[ThreadRunOptions] = None, + update_interval: Optional[int] = None, + access_token: Optional[str] = None, +) -> Iterator[ThreadCompletionChunk]: + kwargs = _get_kwargs( + messages=messages, + thread_id=thread_id, + assistant_id=assistant_id, + model=model, + thread_options=thread_options, + update_interval=update_interval, + access_token=access_token, + ) + with client.stream(**kwargs) as response: + _check_response(response) + for line in response.iter_lines(): + if chunk := _parse_chunk(line): + yield chunk + + +async def asyncio( + client: httpx.AsyncClient, + *, + messages: List[Messages], + thread_id: Optional[str] = None, + assistant_id: Optional[str] = None, + model: Optional[str] = None, + thread_options: Optional[ThreadRunOptions] = None, + update_interval: Optional[int] = None, + access_token: Optional[str] = None, +) -> AsyncIterator[ThreadCompletionChunk]: + kwargs = _get_kwargs( + messages=messages, + thread_id=thread_id, + assistant_id=assistant_id, + model=model, + thread_options=thread_options, + update_interval=update_interval, + access_token=access_token, + ) + async with client.stream(**kwargs) as response: + await _acheck_response(response) + async for line in response.aiter_lines(): + if chunk := _parse_chunk(line): + yield chunk diff --git a/src/gigachat/api/threads/post_threads_delete.py b/src/gigachat/api/threads/post_threads_delete.py new file mode 100644 index 0000000..c917a2c --- /dev/null +++ b/src/gigachat/api/threads/post_threads_delete.py @@ -0,0 +1,57 @@ +from http import HTTPStatus +from typing import Any, Dict, Optional + +import httpx + +from gigachat.api.utils import build_headers +from gigachat.exceptions import AuthenticationError, ResponseError + + +def _get_kwargs( + *, + thread_id: str, + access_token: Optional[str] = None, +) -> Dict[str, Any]: + headers = build_headers(access_token) + params = { + "method": "POST", + "url": "/threads/delete", + "json": { + "thread_id": thread_id, + }, + "headers": headers, + } + return params + + +def _build_response(response: httpx.Response) -> bool: + if response.status_code == HTTPStatus.OK: + return True + elif response.status_code == HTTPStatus.UNAUTHORIZED: + raise AuthenticationError(response.url, response.status_code, response.content, response.headers) + else: + raise ResponseError(response.url, response.status_code, response.content, response.headers) + + +def sync( + client: httpx.Client, + *, + thread_id: str, + access_token: Optional[str] = None, +) -> bool: + """Удаляет тред""" + kwargs = _get_kwargs(thread_id=thread_id, access_token=access_token) + response = client.request(**kwargs) + return _build_response(response) + + +async def asyncio( + client: httpx.AsyncClient, + *, + thread_id: str, + access_token: Optional[str] = None, +) -> bool: + """Удаляет тред""" + kwargs = _get_kwargs(thread_id=thread_id, access_token=access_token) + response = await client.request(**kwargs) + return _build_response(response) diff --git a/src/gigachat/api/threads/post_threads_messages.py b/src/gigachat/api/threads/post_threads_messages.py new file mode 100644 index 0000000..176ab2c --- /dev/null +++ b/src/gigachat/api/threads/post_threads_messages.py @@ -0,0 +1,85 @@ +from http import HTTPStatus +from typing import Any, Dict, List, Optional + +import httpx + +from gigachat.api.utils import build_headers +from gigachat.exceptions import AuthenticationError, ResponseError +from gigachat.models import Messages +from gigachat.models.threads.thread_messages_response import ThreadMessagesResponse + + +def _get_kwargs( + *, + messages: List[Messages], + model: Optional[str] = None, + thread_id: Optional[str] = None, + assistant_id: Optional[str] = None, + access_token: Optional[str] = None, +) -> Dict[str, Any]: + headers = build_headers(access_token) + if thread_id is not None or assistant_id is not None: + model = None + params = { + "method": "POST", + "url": "/threads/messages", + "json": { + "thread_id": thread_id, + "model": model, + "assistant_id": assistant_id, + "messages": [message.dict() for message in messages], + }, + "headers": headers, + } + return params + + +def _build_response(response: httpx.Response) -> ThreadMessagesResponse: + if response.status_code == HTTPStatus.OK: + return ThreadMessagesResponse(**response.json()) + elif response.status_code == HTTPStatus.UNAUTHORIZED: + raise AuthenticationError(response.url, response.status_code, response.content, response.headers) + else: + raise ResponseError(response.url, response.status_code, response.content, response.headers) + + +def sync( + client: httpx.Client, + *, + messages: List[Messages], + model: Optional[str] = None, + thread_id: Optional[str] = None, + assistant_id: Optional[str] = None, + access_token: Optional[str] = None, +) -> ThreadMessagesResponse: + """Добавление сообщений к треду без запуска""" + kwargs = _get_kwargs( + messages=messages, + model=model, + thread_id=thread_id, + assistant_id=assistant_id, + access_token=access_token, + ) + response = client.request(**kwargs) + return _build_response(response) + + +async def asyncio( + client: httpx.AsyncClient, + *, + messages: List[Messages], + model: Optional[str] = None, + thread_id: Optional[str] = None, + assistant_id: Optional[str] = None, + access_token: Optional[str] = None, +) -> ThreadMessagesResponse: + """Добавление сообщений к треду без запуска""" + kwargs = _get_kwargs( + messages=messages, + model=model, + thread_id=thread_id, + assistant_id=assistant_id, + access_token=access_token, + ) + response = await client.request(**kwargs) + return _build_response(response) diff --git a/src/gigachat/api/threads/post_threads_run.py b/src/gigachat/api/threads/post_threads_run.py new file mode 100644 index 0000000..44bf1cc --- /dev/null +++ b/src/gigachat/api/threads/post_threads_run.py @@ -0,0 +1,62 @@ +from http import HTTPStatus +from typing import Any, Dict, Optional + +import httpx + +from gigachat.api.utils import build_headers +from gigachat.exceptions import AuthenticationError, ResponseError +from gigachat.models.threads import ThreadRunOptions, ThreadRunResponse + + +def _get_kwargs( + *, + thread_id: str, + thread_options: Optional[ThreadRunOptions] = None, + access_token: Optional[str] = None, +) -> Dict[str, Any]: + headers = build_headers(access_token) + thread_options_dict = {} + if thread_options: + thread_options_dict = thread_options.dict(exclude_none=True) + params = { + "method": "POST", + "url": "/threads/run", + "headers": headers, + "json": {**thread_options_dict, **{"thread_id": thread_id}}, + } + return params + + +def _build_response(response: httpx.Response) -> ThreadRunResponse: + if response.status_code == HTTPStatus.OK: + return ThreadRunResponse(**response.json()) + elif response.status_code == HTTPStatus.UNAUTHORIZED: + raise AuthenticationError(response.url, response.status_code, response.content, response.headers) + else: + raise ResponseError(response.url, response.status_code, response.content, response.headers) + + +def sync( + client: httpx.Client, + *, + thread_id: str, + thread_options: Optional[ThreadRunOptions] = None, + access_token: Optional[str] = None, +) -> ThreadRunResponse: + """Получить результат run треда""" + kwargs = _get_kwargs(thread_id=thread_id, thread_options=thread_options, access_token=access_token) + response = client.request(**kwargs) + return _build_response(response) + + +async def asyncio( + client: httpx.AsyncClient, + *, + thread_id: str, + thread_options: Optional[ThreadRunOptions] = None, + access_token: Optional[str] = None, +) -> ThreadRunResponse: + """Получить результат run треда""" + kwargs = _get_kwargs(thread_id=thread_id, thread_options=thread_options, access_token=access_token) + response = await client.request(**kwargs) + return _build_response(response) diff --git a/src/gigachat/api/utils.py b/src/gigachat/api/utils.py index 5a5c59e..f867e60 100644 --- a/src/gigachat/api/utils.py +++ b/src/gigachat/api/utils.py @@ -2,6 +2,7 @@ from gigachat.context import ( authorization_cvar, + client_id_cvar, operation_id_cvar, request_id_cvar, service_id_cvar, @@ -24,6 +25,7 @@ def build_headers(access_token: Optional[str] = None) -> Dict[str, str]: request_id = request_id_cvar.get() service_id = service_id_cvar.get() operation_id = operation_id_cvar.get() + client_id = client_id_cvar.get() if authorization: headers["Authorization"] = authorization @@ -35,4 +37,6 @@ def build_headers(access_token: Optional[str] = None) -> Dict[str, str]: headers["X-Service-ID"] = service_id if operation_id: headers["X-Operation-ID"] = operation_id + if client_id: + headers["X-Client-ID"] = client_id return headers diff --git a/src/gigachat/assistants.py b/src/gigachat/assistants.py new file mode 100644 index 0000000..0d1cd58 --- /dev/null +++ b/src/gigachat/assistants.py @@ -0,0 +1,169 @@ +from typing import TYPE_CHECKING, Any, Dict, List, Optional + +from gigachat.api.assistants import ( + get_assistants, + post_assistant_files_delete, + post_assistant_modify, + post_assistants, +) +from gigachat.models import Assistant, AssistantFileDelete, Assistants, CreateAssistant + +if TYPE_CHECKING: + from gigachat.client import GigaChatAsyncClient, GigaChatSyncClient + + +class AssistantsSyncClient: + def __init__(self, base_client: "GigaChatSyncClient"): + self.base_client = base_client + + def get(self, assistant_id: Optional[str] = None) -> Assistants: + """Возвращает список доступных ассистентов""" + return self.base_client._decorator( + lambda: get_assistants.sync( + self.base_client._client, + assistant_id=assistant_id, + access_token=self.base_client.token, + ) + ) + + def create( + self, + model: str, + name: str, + instructions: str, + description: Optional[str] = None, + file_ids: Optional[List[str]] = None, + metadata: Optional[Dict[str, Any]] = None, + ) -> CreateAssistant: + """Создает ассистента""" + return self.base_client._decorator( + lambda: post_assistants.sync( + self.base_client._client, + model=model, + name=name, + description=description, + instructions=instructions, + file_ids=file_ids, + metadata=metadata, + access_token=self.base_client.token, + ) + ) + + def update( + self, + assistant_id: str, + model: Optional[str] = None, + name: Optional[str] = None, + description: Optional[str] = None, + instructions: Optional[str] = None, + file_ids: Optional[List[str]] = None, + metadata: Optional[Dict[str, Any]] = None, + ) -> Assistant: + """Обновляет ассистента""" + + return self.base_client._decorator( + lambda: post_assistant_modify.sync( + self.base_client._client, + assistant_id=assistant_id, + model=model, + name=name, + description=description, + instructions=instructions, + file_ids=file_ids, + metadata=metadata, + access_token=self.base_client.token, + ) + ) + + def delete_file(self, assistant_id: str, file_id: str) -> AssistantFileDelete: + """Удаляет файл ассистента""" + return self.base_client._decorator( + lambda: post_assistant_files_delete.sync( + self.base_client._client, + assistant_id=assistant_id, + file_id=file_id, + access_token=self.base_client.token, + ) + ) + + +class AssistantsAsyncClient: + def __init__(self, base_client: "GigaChatAsyncClient"): + self.base_client = base_client + + async def get(self, assistant_id: Optional[str] = None) -> Assistants: + """Возвращает список доступных ассистентов""" + + async def _acall() -> Assistants: + return await get_assistants.asyncio( + self.base_client._aclient, + assistant_id=assistant_id, + access_token=self.base_client.token, + ) + + return await self.base_client._adecorator(_acall) + + async def create( + self, + model: str, + name: str, + instructions: str, + description: Optional[str] = None, + file_ids: Optional[List[str]] = None, + metadata: Optional[Dict[str, Any]] = None, + ) -> CreateAssistant: + """Создает ассистента""" + + async def _acall() -> CreateAssistant: + return await post_assistants.asyncio( + self.base_client._aclient, + model=model, + name=name, + description=description, + instructions=instructions, + file_ids=file_ids, + metadata=metadata, + access_token=self.base_client.token, + ) + + return await self.base_client._adecorator(_acall) + + async def update( + self, + assistant_id: str, + model: Optional[str] = None, + name: Optional[str] = None, + description: Optional[str] = None, + instructions: Optional[str] = None, + file_ids: Optional[List[str]] = None, + metadata: Optional[Dict[str, Any]] = None, + ) -> Assistant: + """Обновляет ассистента""" + + async def _acall() -> Assistant: + return await post_assistant_modify.asyncio( + self.base_client._aclient, + assistant_id=assistant_id, + model=model, + name=name, + description=description, + instructions=instructions, + file_ids=file_ids, + metadata=metadata, + access_token=self.base_client.token, + ) + + return await self.base_client._adecorator(_acall) + + async def delete_file(self, assistant_id: str, file_id: str) -> AssistantFileDelete: + """Удаляет файл ассистента""" + + async def _acall() -> AssistantFileDelete: + return await post_assistant_files_delete.asyncio( + self.base_client._aclient, + assistant_id=assistant_id, + file_id=file_id, + access_token=self.base_client.token, + ) + + return await self.base_client._adecorator(_acall) diff --git a/src/gigachat/client.py b/src/gigachat/client.py index 6b6da95..c5d1c4a 100644 --- a/src/gigachat/client.py +++ b/src/gigachat/client.py @@ -1,6 +1,17 @@ import logging from functools import cached_property -from typing import Any, AsyncIterator, Awaitable, Callable, Dict, Iterator, List, Optional, TypeVar, Union +from typing import ( + Any, + AsyncIterator, + Awaitable, + Callable, + Dict, + Iterator, + List, + Optional, + TypeVar, + Union, +) import httpx @@ -15,6 +26,7 @@ post_tokens_count, stream_chat, ) +from gigachat.assistants import AssistantsAsyncClient, AssistantsSyncClient from gigachat.context import authorization_cvar from gigachat.exceptions import AuthenticationError from gigachat.models import ( @@ -32,6 +44,7 @@ TokensCount, ) from gigachat.settings import Settings +from gigachat.threads import ThreadsAsyncClient, ThreadsSyncClient T = TypeVar("T") @@ -50,7 +63,11 @@ def _get_kwargs(settings: Settings) -> Dict[str, Any]: if settings.ca_bundle_file: kwargs["verify"] = settings.ca_bundle_file if settings.cert_file: - kwargs["cert"] = (settings.cert_file, settings.key_file, settings.key_file_password) + kwargs["cert"] = ( + settings.cert_file, + settings.key_file, + settings.key_file_password, + ) return kwargs @@ -157,6 +174,11 @@ def _reset_token(self) -> None: class GigaChatSyncClient(_BaseClient): """Синхронный клиент GigaChat""" + def __init__(self, **kwargs: Any) -> None: + super().__init__(**kwargs) + self.assistants = AssistantsSyncClient(self) + self.threads = ThreadsSyncClient(self) + @cached_property def _client(self) -> httpx.Client: return httpx.Client(**_get_kwargs(self._settings)) @@ -188,7 +210,11 @@ def _update_token(self) -> None: _logger.info("OAUTH UPDATE TOKEN") elif self._settings.user and self._settings.password: self._access_token = _build_access_token( - post_token.sync(self._client, user=self._settings.user, password=self._settings.password) + post_token.sync( + self._client, + user=self._settings.user, + password=self._settings.password, + ) ) _logger.info("UPDATE TOKEN") @@ -256,6 +282,11 @@ def stream(self, payload: Union[Chat, Dict[str, Any], str]) -> Iterator[ChatComp class GigaChatAsyncClient(_BaseClient): """Асинхронный клиент GigaChat""" + def __init__(self, **kwargs: Any) -> None: + super().__init__(**kwargs) + self.a_assistants = AssistantsAsyncClient(self) + self.a_threads = ThreadsAsyncClient(self) + @cached_property def _aclient(self) -> httpx.AsyncClient: return httpx.AsyncClient(**_get_kwargs(self._settings)) @@ -287,7 +318,11 @@ async def _aupdate_token(self) -> None: _logger.info("OAUTH UPDATE TOKEN") elif self._settings.user and self._settings.password: self._access_token = _build_access_token( - await post_token.asyncio(self._aclient, user=self._settings.user, password=self._settings.password) + await post_token.asyncio( + self._aclient, + user=self._settings.user, + password=self._settings.password, + ) ) _logger.info("UPDATE TOKEN") diff --git a/src/gigachat/context.py b/src/gigachat/context.py index 5ae429f..bb36ad6 100644 --- a/src/gigachat/context.py +++ b/src/gigachat/context.py @@ -4,7 +4,7 @@ authorization_cvar: ContextVar[Optional[str]] = ContextVar("authorization_cvar", default=None) """Информация об авторизации с помощью JWE""" client_id_cvar: ContextVar[Optional[str]] = ContextVar("client_id_cvar", default=None) -"""[DEPRECATED] Уникальный ID клиента""" +"""Уникальный ID клиента""" request_id_cvar: ContextVar[Optional[str]] = ContextVar("request_id_cvar", default=None) """Уникальный ID запроса""" session_id_cvar: ContextVar[Optional[str]] = ContextVar("session_id_cvar", default=None) diff --git a/src/gigachat/models/__init__.py b/src/gigachat/models/__init__.py index 473cd12..746f54c 100644 --- a/src/gigachat/models/__init__.py +++ b/src/gigachat/models/__init__.py @@ -1,4 +1,10 @@ from gigachat.models.access_token import AccessToken +from gigachat.models.assistants import ( + Assistant, + AssistantFileDelete, + Assistants, + CreateAssistant, +) from gigachat.models.chat import Chat from gigachat.models.chat_completion import ChatCompletion from gigachat.models.chat_completion_chunk import ChatCompletionChunk @@ -16,12 +22,20 @@ from gigachat.models.messages_role import MessagesRole from gigachat.models.model import Model from gigachat.models.models import Models +from gigachat.models.threads import ThreadMessages, ThreadRunResult, Threads from gigachat.models.token import Token from gigachat.models.tokens_count import TokensCount from gigachat.models.usage import Usage __all__ = ( + "Assistants", + "Assistant", + "AssistantFileDelete", "AccessToken", + "ThreadRunResult", + "ThreadMessages", + "Threads", + "CreateAssistant", "Chat", "ChatCompletion", "ChatCompletionChunk", diff --git a/src/gigachat/models/assistants/__init__.py b/src/gigachat/models/assistants/__init__.py new file mode 100644 index 0000000..fc80ac2 --- /dev/null +++ b/src/gigachat/models/assistants/__init__.py @@ -0,0 +1,6 @@ +from gigachat.models.assistants.assistant import Assistant +from gigachat.models.assistants.assistant_file_delete import AssistantFileDelete +from gigachat.models.assistants.assistants import Assistants +from gigachat.models.assistants.create_assistant import CreateAssistant + +__all__ = ("Assistant", "AssistantFileDelete", "Assistants", "CreateAssistant") diff --git a/src/gigachat/models/assistants/assistant.py b/src/gigachat/models/assistants/assistant.py new file mode 100644 index 0000000..551d318 --- /dev/null +++ b/src/gigachat/models/assistants/assistant.py @@ -0,0 +1,26 @@ +from typing import Any, Dict, List, Optional + +from gigachat.pydantic_v1 import BaseModel + + +class Assistant(BaseModel): + """Ассистент""" + + model: str + """Идентификатор модели, которую необходимо использовать.""" + assistant_id: str + """Идентификатор созданного ассистента. UUIDv4""" + name: Optional[str] + """Имя ассистента, которое было передано в запросе""" + description: Optional[str] + """Описание ассистента, которое было передано в запросе""" + instructions: Optional[str] + """Инструкция для ассистента, которое было передано в запросе""" + created_at: int + """Время создания ассистента в Unix-time формате""" + updated_at: int + """Время изменения ассистента в Unix-time формате""" + file_ids: Optional[List[str]] + """Идентификаторы прикрепленных к ассистенту файлов """ + metadata: Optional[Dict[str, Any]] + """Дополнительная информация""" diff --git a/src/gigachat/models/assistants/assistant_file_delete.py b/src/gigachat/models/assistants/assistant_file_delete.py new file mode 100644 index 0000000..056f411 --- /dev/null +++ b/src/gigachat/models/assistants/assistant_file_delete.py @@ -0,0 +1,10 @@ +from gigachat.pydantic_v1 import BaseModel + + +class AssistantFileDelete(BaseModel): + """Информация об удаленном файле""" + + file_id: str + """Идентификатор прикрепленного к ассистенту файла""" + deleted: bool + """Признак удаления. Если true - файл удален из ассистента""" diff --git a/src/gigachat/models/assistants/assistants.py b/src/gigachat/models/assistants/assistants.py new file mode 100644 index 0000000..ef468e4 --- /dev/null +++ b/src/gigachat/models/assistants/assistants.py @@ -0,0 +1,11 @@ +from typing import List + +from gigachat.models.assistants.assistant import Assistant +from gigachat.pydantic_v1 import BaseModel + + +class Assistants(BaseModel): + """Доступные ассистенты""" + + data: List[Assistant] + """Массив объектов с данными доступных ассистентов""" diff --git a/src/gigachat/models/assistants/create_assistant.py b/src/gigachat/models/assistants/create_assistant.py new file mode 100644 index 0000000..bcc32f0 --- /dev/null +++ b/src/gigachat/models/assistants/create_assistant.py @@ -0,0 +1,10 @@ +from gigachat.pydantic_v1 import BaseModel + + +class CreateAssistant(BaseModel): + """Информация о созданном ассистенте""" + + assistant_id: str + """Идентификатор созданного ассистента. UUIDv4""" + created_at: int + """Время создания ассистента в Unix-time формате""" diff --git a/src/gigachat/models/chat_function_call.py b/src/gigachat/models/chat_function_call.py index e7b5d71..ca7dcab 100644 --- a/src/gigachat/models/chat_function_call.py +++ b/src/gigachat/models/chat_function_call.py @@ -1,3 +1,5 @@ +from typing import Any, Dict, Optional + from gigachat.pydantic_v1 import BaseModel @@ -6,3 +8,5 @@ class ChatFunctionCall(BaseModel): name: str """Название функции""" + partial_arguments: Optional[Dict[str, Any]] = None + """Часть аргументов функции""" diff --git a/src/gigachat/models/few_shot_example.py b/src/gigachat/models/few_shot_example.py new file mode 100644 index 0000000..8491b50 --- /dev/null +++ b/src/gigachat/models/few_shot_example.py @@ -0,0 +1,9 @@ +from typing import Any, Dict + +from gigachat.pydantic_v1 import BaseModel + + +class FewShotExample(BaseModel): + request: str + """Запрос пользователя""" + params: Dict[str, Any] diff --git a/src/gigachat/models/function.py b/src/gigachat/models/function.py index 64b38c2..63169df 100644 --- a/src/gigachat/models/function.py +++ b/src/gigachat/models/function.py @@ -1,5 +1,6 @@ -from typing import Any, Dict, Optional +from typing import Any, Dict, List, Optional +from gigachat.models.few_shot_example import FewShotExample from gigachat.models.function_parameters import FunctionParameters from gigachat.pydantic_v1 import BaseModel @@ -13,5 +14,6 @@ class Function(BaseModel): """Описание функции""" parameters: Optional[FunctionParameters] = None """Список параметров функции""" + few_shot_examples: Optional[List[FewShotExample]] = None return_parameters: Optional[Dict[Any, Any]] = None """Список возвращаемых параметров функции""" diff --git a/src/gigachat/models/messages.py b/src/gigachat/models/messages.py index 842bf75..d19e463 100644 --- a/src/gigachat/models/messages.py +++ b/src/gigachat/models/messages.py @@ -1,4 +1,4 @@ -from typing import Any, Optional +from typing import Any, List, Optional from gigachat.models.function_call import FunctionCall from gigachat.models.messages_role import MessagesRole @@ -14,6 +14,10 @@ class Messages(BaseModel): """Текст сообщения""" function_call: Optional[FunctionCall] = None """Вызов функции""" + name: Optional[str] = None + """Наименование функции. Заполняется, если role = "function" """ + attachments: Optional[List[str]] = None + """Идентификаторы предзагруженных ранее файлов """ id: Optional[Any] = None # noqa: A003 class Config: diff --git a/src/gigachat/models/messages_role.py b/src/gigachat/models/messages_role.py index 2e3b704..52ee417 100644 --- a/src/gigachat/models/messages_role.py +++ b/src/gigachat/models/messages_role.py @@ -8,3 +8,4 @@ class MessagesRole(str, Enum): SYSTEM = "system" USER = "user" FUNCTION = "function" + SEARCH_RESULT = "search_result" diff --git a/src/gigachat/models/threads/__init__.py b/src/gigachat/models/threads/__init__.py new file mode 100644 index 0000000..bf78667 --- /dev/null +++ b/src/gigachat/models/threads/__init__.py @@ -0,0 +1,29 @@ +from gigachat.models.threads.thread import Thread +from gigachat.models.threads.thread_completion import ThreadCompletion +from gigachat.models.threads.thread_completion_chunk import ThreadCompletionChunk +from gigachat.models.threads.thread_message import ThreadMessage +from gigachat.models.threads.thread_message_attachment import ThreadMessageAttachment +from gigachat.models.threads.thread_message_response import ThreadMessageResponse +from gigachat.models.threads.thread_messages import ThreadMessages +from gigachat.models.threads.thread_messages_response import ThreadMessagesResponse +from gigachat.models.threads.thread_run_options import ThreadRunOptions +from gigachat.models.threads.thread_run_response import ThreadRunResponse +from gigachat.models.threads.thread_run_result import ThreadRunResult +from gigachat.models.threads.thread_status import ThreadStatus +from gigachat.models.threads.threads import Threads + +__all__ = ( + "Thread", + "ThreadCompletion", + "ThreadCompletionChunk", + "ThreadMessage", + "ThreadMessageAttachment", + "ThreadMessageResponse", + "ThreadMessages", + "ThreadMessagesResponse", + "ThreadRunOptions", + "ThreadRunResponse", + "ThreadRunResult", + "ThreadStatus", + "Threads", +) diff --git a/src/gigachat/models/threads/thread.py b/src/gigachat/models/threads/thread.py new file mode 100644 index 0000000..2bdf4bb --- /dev/null +++ b/src/gigachat/models/threads/thread.py @@ -0,0 +1,25 @@ +from typing import Optional + +from gigachat.models.threads.thread_status import ThreadStatus +from gigachat.pydantic_v1 import BaseModel + + +class Thread(BaseModel): + """Тред""" + + id: str # noqa: A003 + """Идентификатор треда""" + assistant_id: Optional[str] + """Идентификатор ассистента. Передается при первом сообщении в сессию""" + model: str + """Алиас модели из Table.threads или из Table.assistants, + если прикреплен assistant_id""" + created_at: int + """Дата создания сессии в Unix-time формате""" + updated_at: int + """Дата последней активности в сессии в Unix-time формате. + Активностью считается добавление в сессию сообщения, run сессии""" + run_lock: bool + """Текущий статус запуска сессии""" + status: ThreadStatus + """Статус запуска""" diff --git a/src/gigachat/models/threads/thread_completion.py b/src/gigachat/models/threads/thread_completion.py new file mode 100644 index 0000000..de34585 --- /dev/null +++ b/src/gigachat/models/threads/thread_completion.py @@ -0,0 +1,24 @@ +from gigachat.models import Messages +from gigachat.models.usage import Usage +from gigachat.pydantic_v1 import BaseModel, Field + + +class ThreadCompletion(BaseModel): + """Ответ модели""" + + object_: str = Field(alias="object") + """Название вызываемого метода""" + model: str + """Название модели, которая вернула ответ""" + thread_id: str + """Идентификатор треда""" + message_id: str + """Идентификатор сообщения ответа модели""" + created: int + """Дата и время создания ответа в формате Unix time""" + usage: Usage + """Данные об использовании модели""" + message: Messages + """Массив ответов модели""" + finish_reason: str + """Причина завершения гипотезы""" diff --git a/src/gigachat/models/threads/thread_completion_chunk.py b/src/gigachat/models/threads/thread_completion_chunk.py new file mode 100644 index 0000000..d9f9c27 --- /dev/null +++ b/src/gigachat/models/threads/thread_completion_chunk.py @@ -0,0 +1,24 @@ +from typing import List + +from gigachat.models import ChoicesChunk +from gigachat.models.usage import Usage +from gigachat.pydantic_v1 import BaseModel, Field + + +class ThreadCompletionChunk(BaseModel): + """Ответ модели""" + + object_: str = Field(alias="object") + """Название вызываемого метода""" + model: str + """Название модели, которая вернула ответ""" + thread_id: str + """Идентификатор треда""" + message_id: str + """Идентификатор сообщения ответа модели""" + created: int + """Дата и время создания ответа в формате Unix time""" + usage: Usage + """Данные об использовании модели""" + choices: List[ChoicesChunk] + """Массив ответов модели в потоке""" diff --git a/src/gigachat/models/threads/thread_message.py b/src/gigachat/models/threads/thread_message.py new file mode 100644 index 0000000..a8ab109 --- /dev/null +++ b/src/gigachat/models/threads/thread_message.py @@ -0,0 +1,28 @@ +from typing import List, Optional + +from gigachat.models.function_call import FunctionCall +from gigachat.models.messages_role import MessagesRole +from gigachat.models.threads.thread_message_attachment import ThreadMessageAttachment +from gigachat.pydantic_v1 import BaseModel + + +class ThreadMessage(BaseModel): + """Сообщение""" + + message_id: str + """Идентификатор сообщения""" + role: MessagesRole + """Роль автора сообщения""" + content: str = "" + """Текст сообщения""" + attachments: Optional[List[ThreadMessageAttachment]] = [] + """Идентификаторы предзагруженных ранее файлов""" + created_at: int + """Дата создания сообщения в Unix-time формате""" + function_call: Optional[FunctionCall] = None + """Вызов функции""" + finish_reason: Optional[str] = None + """Причина завершения гипотезы""" + + class Config: + use_enum_values = True diff --git a/src/gigachat/models/threads/thread_message_attachment.py b/src/gigachat/models/threads/thread_message_attachment.py new file mode 100644 index 0000000..6279e0e --- /dev/null +++ b/src/gigachat/models/threads/thread_message_attachment.py @@ -0,0 +1,10 @@ +from gigachat.pydantic_v1 import BaseModel + + +class ThreadMessageAttachment(BaseModel): + """Файл""" + + file_id: str + """Индентификатор предзагруженного ранее файла""" + name: str + """Наименование файла""" diff --git a/src/gigachat/models/threads/thread_message_response.py b/src/gigachat/models/threads/thread_message_response.py new file mode 100644 index 0000000..263b4f3 --- /dev/null +++ b/src/gigachat/models/threads/thread_message_response.py @@ -0,0 +1,8 @@ +from gigachat.pydantic_v1 import BaseModel + + +class ThreadMessageResponse(BaseModel): + created_at: int + """Время создания сообщения в Unix-time формате""" + message_id: str + """Идентификатор созданного сообщения""" diff --git a/src/gigachat/models/threads/thread_messages.py b/src/gigachat/models/threads/thread_messages.py new file mode 100644 index 0000000..0403443 --- /dev/null +++ b/src/gigachat/models/threads/thread_messages.py @@ -0,0 +1,13 @@ +from typing import List + +from gigachat.models.threads.thread_message import ThreadMessage +from gigachat.pydantic_v1 import BaseModel + + +class ThreadMessages(BaseModel): + """Сообщения треда""" + + thread_id: str + """Идентификатор треда""" + messages: List[ThreadMessage] + """Сообщения""" diff --git a/src/gigachat/models/threads/thread_messages_response.py b/src/gigachat/models/threads/thread_messages_response.py new file mode 100644 index 0000000..fd7fd6a --- /dev/null +++ b/src/gigachat/models/threads/thread_messages_response.py @@ -0,0 +1,11 @@ +from typing import List + +from gigachat.models.threads.thread_message_response import ThreadMessageResponse +from gigachat.pydantic_v1 import BaseModel + + +class ThreadMessagesResponse(BaseModel): + thread_id: str + """Идентификатор треда""" + messages: List[ThreadMessageResponse] + """Созданные сообщения""" diff --git a/src/gigachat/models/threads/thread_run_options.py b/src/gigachat/models/threads/thread_run_options.py new file mode 100644 index 0000000..d8c80b9 --- /dev/null +++ b/src/gigachat/models/threads/thread_run_options.py @@ -0,0 +1,32 @@ +from typing import List, Literal, Optional, Union + +from gigachat.models.chat_function_call import ChatFunctionCall +from gigachat.models.function import Function +from gigachat.pydantic_v1 import BaseModel + + +class ThreadRunOptions(BaseModel): + """Параметры запроса""" + + temperature: Optional[float] = None + """Температура выборки в диапазоне от ноля до двух""" + top_p: Optional[float] = None + """Альтернатива параметру температуры""" + limit: Optional[int] = None + """Максимальное количество сообщений исторического контекста, которые посылаются в модель в запросе + Если параметр не передан, считаем что необходимо отправить весь контекст""" + max_tokens: Optional[int] = None + """Максимальное количество токенов, которые будут использованы для создания ответов""" + repetition_penalty: Optional[float] = None + """Количество повторений слов. + Значение 1.0 - ничего не менять (нейтральное значение), + от 0 до 1 - повторять уже сказанные слова, + от 1 и далее стараться не использовать сказанные слова. Допустимое значение > 0""" + profanity_check: Optional[bool] = None + """Параметр цензуры""" + flags: Optional[List[str]] = None + """Флаги, включающие особенные фичи""" + function_call: Optional[Union[Literal["auto", "none"], ChatFunctionCall]] = None + """Правила вызова функций""" + functions: Optional[List[Function]] = None + """Набор функций, которые могут быть вызваны моделью""" diff --git a/src/gigachat/models/threads/thread_run_response.py b/src/gigachat/models/threads/thread_run_response.py new file mode 100644 index 0000000..6c33bd1 --- /dev/null +++ b/src/gigachat/models/threads/thread_run_response.py @@ -0,0 +1,11 @@ +from gigachat.models.threads.thread_status import ThreadStatus +from gigachat.pydantic_v1 import BaseModel + + +class ThreadRunResponse(BaseModel): + status: ThreadStatus + """Статус запуска""" + thread_id: str + """Идентификатор запущенного треда""" + created_at: int + """Время запуска сессии в Unix-time формате""" diff --git a/src/gigachat/models/threads/thread_run_result.py b/src/gigachat/models/threads/thread_run_result.py new file mode 100644 index 0000000..e3beb30 --- /dev/null +++ b/src/gigachat/models/threads/thread_run_result.py @@ -0,0 +1,20 @@ +from typing import List, Optional + +from gigachat.models.threads.thread_message import ThreadMessage +from gigachat.models.threads.thread_status import ThreadStatus +from gigachat.pydantic_v1 import BaseModel + + +class ThreadRunResult(BaseModel): + """Run треда""" + + status: ThreadStatus + """Статус запуска""" + thread_id: str + """Идентификатор треда""" + updated_at: int + """Время обновления статуса run-a в Unix-time формате""" + model: str + """Модель""" + messages: Optional[List[ThreadMessage]] = None + """Сообщения""" diff --git a/src/gigachat/models/threads/thread_status.py b/src/gigachat/models/threads/thread_status.py new file mode 100644 index 0000000..e32374d --- /dev/null +++ b/src/gigachat/models/threads/thread_status.py @@ -0,0 +1,10 @@ +from enum import Enum + + +class ThreadStatus(str, Enum): + """Статус треда""" + + IN_PROGRESS = "in_progress" + READY = "ready" + FAILED = "failed" + DELETED = "deleted" diff --git a/src/gigachat/models/threads/threads.py b/src/gigachat/models/threads/threads.py new file mode 100644 index 0000000..05cabc2 --- /dev/null +++ b/src/gigachat/models/threads/threads.py @@ -0,0 +1,11 @@ +from typing import List + +from gigachat.models.threads.thread import Thread +from gigachat.pydantic_v1 import BaseModel + + +class Threads(BaseModel): + """Треды""" + + threads: List[Thread] + """Массив тредов клиента""" diff --git a/src/gigachat/threads.py b/src/gigachat/threads.py new file mode 100644 index 0000000..d7831ed --- /dev/null +++ b/src/gigachat/threads.py @@ -0,0 +1,480 @@ +import logging +from typing import ( + TYPE_CHECKING, + Any, + AsyncIterator, + Dict, + Iterator, + List, + Optional, + Union, +) + +from gigachat.api.threads import ( + get_threads, + get_threads_messages, + get_threads_run, + post_thread_messages_rerun, + post_thread_messages_rerun_stream, + post_thread_messages_run, + post_thread_messages_run_stream, + post_threads_delete, + post_threads_messages, + post_threads_run, +) +from gigachat.exceptions import AuthenticationError +from gigachat.models import ( + Messages, + MessagesRole, + ThreadMessages, + ThreadRunResult, +) +from gigachat.models.threads import ( + ThreadCompletion, + ThreadCompletionChunk, + ThreadMessagesResponse, + ThreadRunOptions, + ThreadRunResponse, + Threads, +) + +if TYPE_CHECKING: + from gigachat.client import GigaChatAsyncClient, GigaChatSyncClient + + +def _parse_message(message: Union[Messages, str, Dict[str, Any]]) -> Messages: + if isinstance(message, str): + return Messages(role=MessagesRole.USER, content=message) + else: + return Messages.parse_obj(message) + + +_logger = logging.getLogger(__name__) + + +class ThreadsSyncClient: + def __init__(self, base_client: "GigaChatSyncClient"): + self.base_client = base_client + + def list(self) -> Threads: # noqa: A003 + """Получение перечня тредов""" + return self.base_client._decorator( + lambda: get_threads.sync(self.base_client._client, access_token=self.base_client.token) + ) + + def delete(self, thread_id: str) -> bool: + """Удаляет тред""" + + return self.base_client._decorator( + lambda: post_threads_delete.sync( + self.base_client._client, + thread_id=thread_id, + access_token=self.base_client.token, + ) + ) + + def get_run(self, thread_id: str) -> ThreadRunResult: + """Получить результат run треда""" + + return self.base_client._decorator( + lambda: get_threads_run.sync( + self.base_client._client, + thread_id=thread_id, + access_token=self.base_client.token, + ) + ) + + def get_messages(self, thread_id: str, limit: Optional[int] = None) -> ThreadMessages: + """Получение сообщений треда""" + + return self.base_client._decorator( + lambda: get_threads_messages.sync( + self.base_client._client, + thread_id=thread_id, + limit=limit, + access_token=self.base_client.token, + ) + ) + + def run( + self, + thread_id: str, + thread_options: Optional[Union[ThreadRunOptions, Dict[str, Any]]] = None, + ) -> ThreadRunResponse: + """Запуск генерации ответа на контекст треда""" + if thread_options is not None: + thread_options = ThreadRunOptions.parse_obj(thread_options) + return self.base_client._decorator( + lambda: post_threads_run.sync( + self.base_client._client, + thread_id=thread_id, + thread_options=thread_options, + access_token=self.base_client.token, + ) + ) + + def add_messages( + self, + messages: Union[List[Messages], List[str], List[Dict[str, Any]]], + model: Optional[str] = None, + thread_id: Optional[str] = None, + assistant_id: Optional[str] = None, + ) -> ThreadMessagesResponse: + """Добавление сообщений к треду без запуска""" + parsed_messages = [_parse_message(message) for message in messages] + return self.base_client._decorator( + lambda: post_threads_messages.sync( + self.base_client._client, + messages=parsed_messages, + model=model, + thread_id=thread_id, + assistant_id=assistant_id, + access_token=self.base_client.token, + ) + ) + + def run_messages( + self, + messages: Union[List[Messages], List[str], List[Dict[str, Any]]], + thread_id: Optional[str] = None, + assistant_id: Optional[str] = None, + model: Optional[str] = None, + thread_options: Optional[Union[ThreadRunOptions, Dict[str, Any]]] = None, + ) -> ThreadCompletion: + """Добавление сообщений к треду с запуском""" + parsed_messages = [_parse_message(message) for message in messages] + if thread_options is not None: + thread_options = ThreadRunOptions.parse_obj(thread_options) + + return self.base_client._decorator( + lambda: post_thread_messages_run.sync( + self.base_client._client, + messages=parsed_messages, + thread_id=thread_id, + assistant_id=assistant_id, + model=model, + thread_options=thread_options, + access_token=self.base_client.token, + ) + ) + + def rerun_messages( + self, + thread_id: str, + thread_options: Optional[Union[ThreadRunOptions, Dict[str, Any]]] = None, + ) -> ThreadCompletion: + """Перегенерация ответа модели""" + if thread_options is not None: + thread_options = ThreadRunOptions.parse_obj(thread_options) + + return self.base_client._decorator( + lambda: post_thread_messages_rerun.sync( + self.base_client._client, + thread_id=thread_id, + thread_options=thread_options, + access_token=self.base_client.token, + ) + ) + + def run_messages_stream( + self, + messages: Union[List[Messages], List[str], List[Dict[str, Any]]], + thread_id: Optional[str] = None, + assistant_id: Optional[str] = None, + model: Optional[str] = None, + thread_options: Optional[Union[ThreadRunOptions, Dict[str, Any]]] = None, + update_interval: Optional[int] = None, + ) -> Iterator[ThreadCompletionChunk]: + """Добавление сообщений к треду с запуском""" + parsed_messages = [_parse_message(message) for message in messages] + if thread_options is not None: + thread_options = ThreadRunOptions.parse_obj(thread_options) + + if self.base_client._use_auth: + if self.base_client._check_validity_token(): + try: + for chunk in post_thread_messages_run_stream.sync( + self.base_client._client, + messages=parsed_messages, + thread_id=thread_id, + assistant_id=assistant_id, + model=model, + thread_options=thread_options, + update_interval=update_interval, + access_token=self.base_client.token, + ): + yield chunk + return + except AuthenticationError: + _logger.warning("AUTHENTICATION ERROR") + self.base_client._reset_token() + self.base_client._update_token() + + for chunk in post_thread_messages_run_stream.sync( + self.base_client._client, + messages=parsed_messages, + thread_id=thread_id, + assistant_id=assistant_id, + model=model, + thread_options=thread_options, + update_interval=update_interval, + access_token=self.base_client.token, + ): + yield chunk + + def rerun_messages_stream( + self, + thread_id: str, + thread_options: Optional[Union[ThreadRunOptions, Dict[str, Any]]] = None, + update_interval: Optional[int] = None, + ) -> Iterator[ThreadCompletionChunk]: + """Перегенерация ответа модели""" + if thread_options is not None: + thread_options = ThreadRunOptions.parse_obj(thread_options) + + if self.base_client._use_auth: + if self.base_client._check_validity_token(): + try: + for chunk in post_thread_messages_rerun_stream.sync( + self.base_client._client, + thread_id=thread_id, + thread_options=thread_options, + update_interval=update_interval, + access_token=self.base_client.token, + ): + yield chunk + return + except AuthenticationError: + _logger.warning("AUTHENTICATION ERROR") + self.base_client._reset_token() + self.base_client._update_token() + + for chunk in post_thread_messages_rerun_stream.sync( + self.base_client._client, + thread_id=thread_id, + thread_options=thread_options, + update_interval=update_interval, + access_token=self.base_client.token, + ): + yield chunk + + +class ThreadsAsyncClient: + def __init__(self, base_client: "GigaChatAsyncClient"): + self.base_client = base_client + + async def list(self) -> Threads: # noqa: A003 + """Получение перечня тредов""" + + async def _acall() -> Threads: + return await get_threads.asyncio( + self.base_client._aclient, + access_token=self.base_client.token, + ) + + return await self.base_client._adecorator(_acall) + + async def delete(self, thread_id: str) -> bool: + """Удаляет тред""" + + async def _acall() -> bool: + return await post_threads_delete.asyncio( + self.base_client._aclient, + thread_id=thread_id, + access_token=self.base_client.token, + ) + + return await self.base_client._adecorator(_acall) + + async def get_run(self, thread_id: str) -> ThreadRunResult: + """Получить результат run треда""" + + async def _acall() -> ThreadRunResult: + return await get_threads_run.asyncio( + self.base_client._aclient, + thread_id=thread_id, + access_token=self.base_client.token, + ) + + return await self.base_client._adecorator(_acall) + + async def get_messages(self, thread_id: str, limit: Optional[int] = None) -> ThreadMessages: + """Получение сообщений треда""" + + async def _acall() -> ThreadMessages: + return await get_threads_messages.asyncio( + self.base_client._aclient, + thread_id=thread_id, + limit=limit, + access_token=self.base_client.token, + ) + + return await self.base_client._adecorator(_acall) + + async def run( + self, + thread_id: str, + thread_options: Optional[Union[ThreadRunOptions, Dict[str, Any]]] = None, + ) -> ThreadRunResponse: + """Запуск генерации ответа на контекст треда""" + if thread_options is not None: + thread_options = ThreadRunOptions.parse_obj(thread_options) + + async def _acall() -> ThreadRunResponse: + return await post_threads_run.asyncio( + self.base_client._aclient, + thread_id=thread_id, + thread_options=thread_options, + access_token=self.base_client.token, + ) + + return await self.base_client._adecorator(_acall) + + async def add_messages( + self, + messages: Union[List[Messages], List[str], List[Dict[str, Any]]], + model: Optional[str] = None, + thread_id: Optional[str] = None, + assistant_id: Optional[str] = None, + ) -> ThreadMessagesResponse: + """Добавление сообщений к треду без запуска""" + parsed_messages = [_parse_message(message) for message in messages] + + async def _acall() -> ThreadMessagesResponse: + return await post_threads_messages.asyncio( + self.base_client._aclient, + messages=parsed_messages, + model=model, + thread_id=thread_id, + assistant_id=assistant_id, + access_token=self.base_client.token, + ) + + return await self.base_client._adecorator(_acall) + + async def run_messages( + self, + messages: Union[List[Messages], List[str], List[Dict[str, Any]]], + thread_id: Optional[str] = None, + assistant_id: Optional[str] = None, + model: Optional[str] = None, + thread_options: Optional[Union[ThreadRunOptions, Dict[str, Any]]] = None, + ) -> ThreadCompletion: + """Добавление сообщений к треду с запуском""" + parsed_messages = [_parse_message(message) for message in messages] + if thread_options is not None: + thread_options = ThreadRunOptions.parse_obj(thread_options) + + async def _acall() -> ThreadCompletion: + return await post_thread_messages_run.asyncio( + self.base_client._aclient, + messages=parsed_messages, + thread_id=thread_id, + assistant_id=assistant_id, + model=model, + thread_options=thread_options, + access_token=self.base_client.token, + ) + + return await self.base_client._adecorator(_acall) + + async def rerun_messages( + self, + thread_id: str, + thread_options: Optional[Union[ThreadRunOptions, Dict[str, Any]]] = None, + ) -> ThreadCompletion: + """Перегенерация ответа модели""" + if thread_options is not None: + thread_options = ThreadRunOptions.parse_obj(thread_options) + + async def _acall() -> ThreadCompletion: + return await post_thread_messages_rerun.asyncio( + self.base_client._aclient, + thread_id=thread_id, + thread_options=thread_options, + access_token=self.base_client.token, + ) + + return await self.base_client._adecorator(_acall) + + async def run_messages_stream( + self, + messages: Union[List[Messages], List[str], List[Dict[str, Any]]], + thread_id: Optional[str] = None, + assistant_id: Optional[str] = None, + model: Optional[str] = None, + thread_options: Optional[Union[ThreadRunOptions, Dict[str, Any]]] = None, + update_interval: Optional[int] = None, + ) -> AsyncIterator[ThreadCompletionChunk]: + """Добавление сообщений к треду с запуском""" + parsed_messages = [_parse_message(message) for message in messages] + if thread_options is not None: + thread_options = ThreadRunOptions.parse_obj(thread_options) + + if self.base_client._use_auth: + if self.base_client._check_validity_token(): + try: + async for chunk in post_thread_messages_run_stream.asyncio( + self.base_client._aclient, + messages=parsed_messages, + thread_id=thread_id, + assistant_id=assistant_id, + model=model, + thread_options=thread_options, + update_interval=update_interval, + access_token=self.base_client.token, + ): + yield chunk + return + except AuthenticationError: + _logger.warning("AUTHENTICATION ERROR") + self.base_client._reset_token() + await self.base_client._aupdate_token() + + async for chunk in post_thread_messages_run_stream.asyncio( + self.base_client._aclient, + messages=parsed_messages, + thread_id=thread_id, + assistant_id=assistant_id, + model=model, + thread_options=thread_options, + update_interval=update_interval, + access_token=self.base_client.token, + ): + yield chunk + + async def rerun_messages_stream( + self, + thread_id: str, + thread_options: Optional[Union[ThreadRunOptions, Dict[str, Any]]] = None, + update_interval: Optional[int] = None, + ) -> AsyncIterator[ThreadCompletionChunk]: + """Перегенерация ответа модели""" + if thread_options is not None: + thread_options = ThreadRunOptions.parse_obj(thread_options) + + if self.base_client._use_auth: + if self.base_client._check_validity_token(): + try: + async for chunk in post_thread_messages_rerun_stream.asyncio( + self.base_client._aclient, + thread_id=thread_id, + thread_options=thread_options, + update_interval=update_interval, + access_token=self.base_client.token, + ): + yield chunk + return + except AuthenticationError: + _logger.warning("AUTHENTICATION ERROR") + self.base_client._reset_token() + await self.base_client._aupdate_token() + + async for chunk in post_thread_messages_rerun_stream.asyncio( + self.base_client._aclient, + thread_id=thread_id, + thread_options=thread_options, + update_interval=update_interval, + access_token=self.base_client.token, + ): + yield chunk diff --git a/tests/unit_tests/gigachat/api/assistants/__init__.py b/tests/unit_tests/gigachat/api/assistants/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/unit_tests/gigachat/api/assistants/test_get_assistants.py b/tests/unit_tests/gigachat/api/assistants/test_get_assistants.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/unit_tests/gigachat/api/assistants/test_post_assistant_files_delete.py b/tests/unit_tests/gigachat/api/assistants/test_post_assistant_files_delete.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/unit_tests/gigachat/api/assistants/test_post_assistant_modify.py b/tests/unit_tests/gigachat/api/assistants/test_post_assistant_modify.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/unit_tests/gigachat/api/assistants/test_post_assistants.py b/tests/unit_tests/gigachat/api/assistants/test_post_assistants.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/unit_tests/gigachat/api/threads/__init__.py b/tests/unit_tests/gigachat/api/threads/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/unit_tests/gigachat/api/threads/test_get_threads.py b/tests/unit_tests/gigachat/api/threads/test_get_threads.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/unit_tests/gigachat/api/threads/test_get_threads_messages.py b/tests/unit_tests/gigachat/api/threads/test_get_threads_messages.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/unit_tests/gigachat/api/threads/test_get_threads_run.py b/tests/unit_tests/gigachat/api/threads/test_get_threads_run.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/unit_tests/gigachat/api/threads/test_post_thread_messages_rerun.py b/tests/unit_tests/gigachat/api/threads/test_post_thread_messages_rerun.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/unit_tests/gigachat/api/threads/test_post_thread_messages_rerun_stream.py b/tests/unit_tests/gigachat/api/threads/test_post_thread_messages_rerun_stream.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/unit_tests/gigachat/api/threads/test_post_thread_messages_run.py b/tests/unit_tests/gigachat/api/threads/test_post_thread_messages_run.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/unit_tests/gigachat/api/threads/test_post_thread_messages_run_stream.py b/tests/unit_tests/gigachat/api/threads/test_post_thread_messages_run_stream.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/unit_tests/gigachat/api/threads/test_post_threads_delete.py b/tests/unit_tests/gigachat/api/threads/test_post_threads_delete.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/unit_tests/gigachat/api/threads/test_post_threads_messages.py b/tests/unit_tests/gigachat/api/threads/test_post_threads_messages.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/unit_tests/gigachat/api/threads/test_post_threads_run.py b/tests/unit_tests/gigachat/api/threads/test_post_threads_run.py new file mode 100644 index 0000000..e69de29