From f4aded1f92453deec9bc94dd675a0bad0b073106 Mon Sep 17 00:00:00 2001 From: mikelarg Date: Mon, 3 Mar 2025 00:40:33 +0300 Subject: [PATCH] =?UTF-8?q?feat:=20=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D0=BB=20=D0=BD=D0=BE=D0=B2=D1=8B=D0=B5=20=D0=BC=D0=B5=D1=82?= =?UTF-8?q?=D0=BE=D0=B4=D1=8B=20FilesAPI,=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2?= =?UTF-8?q?=D0=B8=D0=BB=20=D0=BF=D0=BE=D0=B4=D0=B4=D0=B5=D1=80=D0=B6=D0=BA?= =?UTF-8?q?=D1=83=20=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D1=83=20=D0=B0=D1=81?= =?UTF-8?q?=D1=81=D0=B8=D1=81=D1=82=D0=B5=D0=BD=D1=82=D0=BE=D0=B2=20=D0=B8?= =?UTF-8?q?=D0=B7=20chat/completions,=20=D0=BF=D0=BE=D0=BF=D1=80=D0=B0?= =?UTF-8?q?=D0=B2=D0=B8=D0=BB=20=D0=B2=D0=B5=D1=80=D1=81=D0=B8=D1=8E=20htt?= =?UTF-8?q?px?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyproject.toml | 4 +- src/gigachat/api/get_file.py | 44 ++++++++++++++ src/gigachat/api/get_files.py | 41 +++++++++++++ src/gigachat/api/post_files_delete.py | 42 ++++++++++++++ src/gigachat/client.py | 50 +++++++++++++++- src/gigachat/models/__init__.py | 6 ++ src/gigachat/models/chat.py | 3 + src/gigachat/models/chat_completion.py | 6 +- src/gigachat/models/chat_completion_chunk.py | 7 ++- src/gigachat/models/deleted_file.py | 11 ++++ src/gigachat/models/storage.py | 31 ++++++++++ src/gigachat/models/uploaded_file.py | 4 ++ src/gigachat/models/uploaded_files.py | 9 +++ src/gigachat/models/usage.py | 4 ++ src/gigachat/threads.py | 7 +++ tests/data/get_file.json | 9 +++ tests/data/get_files.json | 22 +++++++ tests/data/post_files_delete.json | 5 ++ tests/unit_tests/gigachat/test_client.py | 61 ++++++++++++++++++++ 19 files changed, 360 insertions(+), 6 deletions(-) create mode 100644 src/gigachat/api/get_file.py create mode 100644 src/gigachat/api/get_files.py create mode 100644 src/gigachat/api/post_files_delete.py create mode 100644 src/gigachat/models/deleted_file.py create mode 100644 src/gigachat/models/storage.py create mode 100644 src/gigachat/models/uploaded_files.py create mode 100644 tests/data/get_file.json create mode 100644 tests/data/get_files.json create mode 100644 tests/data/post_files_delete.json diff --git a/pyproject.toml b/pyproject.toml index 25fbf48..069e904 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "gigachat" -version = "0.1.38" +version = "0.1.39" description = "GigaChat. Python-library for GigaChain and LangChain" authors = ["Konstantin Krestnikov ", "Sergey Malyshev "] license = "MIT" @@ -11,7 +11,7 @@ packages = [{include = "gigachat", from = "src"}] [tool.poetry.dependencies] python = "^3.8" pydantic = ">=1" -httpx = "<=0.27.2" +httpx = "<1" [tool.poetry.group.dev.dependencies] black = "^23.12.1" diff --git a/src/gigachat/api/get_file.py b/src/gigachat/api/get_file.py new file mode 100644 index 0000000..957ff97 --- /dev/null +++ b/src/gigachat/api/get_file.py @@ -0,0 +1,44 @@ +from typing import Any, Dict, Optional + +import httpx + +from gigachat.api.utils import build_headers, build_response +from gigachat.models import UploadedFile + + +def _get_kwargs( + *, + file: str, + access_token: Optional[str] = None, +) -> Dict[str, Any]: + headers = build_headers(access_token) + + return { + "method": "GET", + "url": f"/files/{file}", + "headers": headers, + } + + +def sync( + client: httpx.Client, + *, + file: str, + access_token: Optional[str] = None, +) -> UploadedFile: + """Возвращает объект с описанием указанного файла.""" + kwargs = _get_kwargs(file=file, access_token=access_token) + response = client.request(**kwargs) + return build_response(response, UploadedFile) + + +async def asyncio( + client: httpx.AsyncClient, + *, + file: str, + access_token: Optional[str] = None, +) -> UploadedFile: + """Возвращает объект с описанием указанного файла.""" + kwargs = _get_kwargs(file=file, access_token=access_token) + response = await client.request(**kwargs) + return build_response(response, UploadedFile) diff --git a/src/gigachat/api/get_files.py b/src/gigachat/api/get_files.py new file mode 100644 index 0000000..8cbb15d --- /dev/null +++ b/src/gigachat/api/get_files.py @@ -0,0 +1,41 @@ +from typing import Any, Dict, Optional + +import httpx + +from gigachat.api.utils import build_headers, build_response +from gigachat.models import UploadedFiles + + +def _get_kwargs( + *, + access_token: Optional[str] = None, +) -> Dict[str, Any]: + headers = build_headers(access_token) + + return { + "method": "GET", + "url": "/files", + "headers": headers, + } + + +def sync( + client: httpx.Client, + *, + access_token: Optional[str] = None, +) -> UploadedFiles: + """Возвращает загруженные файлы""" + kwargs = _get_kwargs(access_token=access_token) + response = client.request(**kwargs) + return build_response(response, UploadedFiles) + + +async def asyncio( + client: httpx.AsyncClient, + *, + access_token: Optional[str] = None, +) -> UploadedFiles: + """Возвращает загруженные файлы""" + kwargs = _get_kwargs(access_token=access_token) + response = await client.request(**kwargs) + return build_response(response, UploadedFiles) diff --git a/src/gigachat/api/post_files_delete.py b/src/gigachat/api/post_files_delete.py new file mode 100644 index 0000000..b2e8357 --- /dev/null +++ b/src/gigachat/api/post_files_delete.py @@ -0,0 +1,42 @@ +from typing import Any, Dict, Optional + +import httpx + +from gigachat.api.utils import build_headers, build_response +from gigachat.models import DeletedFile + + +def _get_kwargs( + *, + file: str, + access_token: Optional[str] = None, +) -> Dict[str, Any]: + return { + "method": "POST", + "url": f"/files/{file}/delete", + "files": {"file": file}, + "data": {}, + "headers": build_headers(access_token), + } + + +def sync( + client: httpx.Client, + *, + file: str, + access_token: Optional[str] = None, +) -> DeletedFile: + kwargs = _get_kwargs(file=file, access_token=access_token) + response = client.request(**kwargs) + return build_response(response, DeletedFile) + + +async def asyncio( + client: httpx.AsyncClient, + *, + file: str, + access_token: Optional[str] = None, +) -> DeletedFile: + kwargs = _get_kwargs(file=file, access_token=access_token) + response = await client.request(**kwargs) + return build_response(response, DeletedFile) diff --git a/src/gigachat/client.py b/src/gigachat/client.py index 85d707f..408b817 100644 --- a/src/gigachat/client.py +++ b/src/gigachat/client.py @@ -21,6 +21,8 @@ from gigachat._types import FileTypes from gigachat.api import ( get_balance, + get_file, + get_files, get_image, get_model, get_models, @@ -28,6 +30,7 @@ post_chat, post_embeddings, post_files, + post_files_delete, post_functions_convert, post_token, post_tokens_count, @@ -42,6 +45,7 @@ Chat, ChatCompletion, ChatCompletionChunk, + DeletedFile, Embeddings, Image, Messages, @@ -52,6 +56,7 @@ Token, TokensCount, UploadedFile, + UploadedFiles, ) from gigachat.settings import Settings from gigachat.threads import ThreadsAsyncClient, ThreadsSyncClient @@ -101,7 +106,8 @@ def _parse_chat(payload: Union[Chat, Dict[str, Any], str], settings: Settings) - chat = Chat(messages=[Messages(role=MessagesRole.USER, content=payload)]) else: chat = Chat.parse_obj(payload) - if chat.model is None: + using_assistant = chat.storage is not None and (chat.storage.assistant_id or chat.storage.thread_id) + if not using_assistant and chat.model is None: chat.model = settings.model or GIGACHAT_MODEL if chat.profanity_check is None: chat.profanity_check = settings.profanity_check @@ -289,6 +295,21 @@ def upload_file( lambda: post_files.sync(self._client, file=file, purpose=purpose, access_token=self.token) ) + def get_file(self, file: str) -> UploadedFile: + """Получает информацию о файле""" + return self._decorator(lambda: get_file.sync(self._client, file=file, access_token=self.token)) + + def get_files(self) -> UploadedFiles: + """Получает загруженные файлы""" + return self._decorator(lambda: get_files.sync(self._client, access_token=self.token)) + + def delete_file( + self, + file: str, + ) -> DeletedFile: + """Удаляет файл""" + return self._decorator(lambda: post_files_delete.sync(self._client, file=file, access_token=self.token)) + def chat(self, payload: Union[Chat, Dict[str, Any], str]) -> ChatCompletion: """Возвращает ответ модели с учетом переданных сообщений""" chat = _parse_chat(payload, self._settings) @@ -451,6 +472,33 @@ async def _acall() -> UploadedFile: return await self._adecorator(_acall) + async def aget_file(self, file: str) -> UploadedFile: + """Получает информацию о файле""" + + async def _acall() -> UploadedFile: + return await get_file.asyncio(self._aclient, file=file, access_token=self.token) + + return await self._adecorator(_acall) + + async def aget_files(self) -> UploadedFiles: + """Получает загруженные файлы""" + + async def _acall() -> UploadedFiles: + return await get_files.asyncio(self._aclient, access_token=self.token) + + return await self._adecorator(_acall) + + async def adelete_file( + self, + file: str, + ) -> DeletedFile: + """Удаляет файл""" + + async def _acall() -> DeletedFile: + return await post_files_delete.asyncio(self._aclient, file=file, access_token=self.token) + + return await self._adecorator(_acall) + async def aget_balance(self) -> Balance: """Метод для получения баланса доступных для использования токенов. Только для клиентов с предоплатой иначе http 403""" diff --git a/src/gigachat/models/__init__.py b/src/gigachat/models/__init__.py index 7551629..694e6b6 100644 --- a/src/gigachat/models/__init__.py +++ b/src/gigachat/models/__init__.py @@ -5,6 +5,7 @@ from gigachat.models.chat_completion_chunk import ChatCompletionChunk from gigachat.models.choices import Choices from gigachat.models.choices_chunk import ChoicesChunk +from gigachat.models.deleted_file import DeletedFile from gigachat.models.embedding import Embedding from gigachat.models.embeddings import Embeddings from gigachat.models.embeddings_usage import EmbeddingsUsage @@ -18,9 +19,11 @@ from gigachat.models.model import Model from gigachat.models.models import Models from gigachat.models.open_api_functions import OpenApiFunctions +from gigachat.models.storage import Storage from gigachat.models.token import Token from gigachat.models.tokens_count import TokensCount from gigachat.models.uploaded_file import UploadedFile +from gigachat.models.uploaded_files import UploadedFiles from gigachat.models.usage import Usage from gigachat.models.with_x_headers import WithXHeaders @@ -32,6 +35,7 @@ "ChatCompletionChunk", "Choices", "ChoicesChunk", + "DeletedFile", "Embedding", "Embeddings", "EmbeddingsUsage", @@ -44,10 +48,12 @@ "Model", "Models", "OpenApiFunctions", + "Storage", "Token", "TokensCount", "Usage", "UploadedFile", + "UploadedFiles", "Image", "threads", "assistants", diff --git a/src/gigachat/models/chat.py b/src/gigachat/models/chat.py index fd154ba..d93eea7 100644 --- a/src/gigachat/models/chat.py +++ b/src/gigachat/models/chat.py @@ -3,6 +3,7 @@ from gigachat.models.chat_function_call import ChatFunctionCall from gigachat.models.function import Function from gigachat.models.messages import Messages +from gigachat.models.storage import Storage from gigachat.pydantic_v1 import BaseModel @@ -35,3 +36,5 @@ class Chat(BaseModel): """Набор функций, которые могут быть вызваны моделью""" flags: Optional[List[str]] = None """Флаги, включающие особенные фичи""" + storage: Optional[Storage] = None + """Данные для хранения контекста на стороне GigaChat""" diff --git a/src/gigachat/models/chat_completion.py b/src/gigachat/models/chat_completion.py index 702a439..8cf0175 100644 --- a/src/gigachat/models/chat_completion.py +++ b/src/gigachat/models/chat_completion.py @@ -1,4 +1,4 @@ -from typing import List +from typing import List, Optional from gigachat.models.choices import Choices from gigachat.models.usage import Usage @@ -15,6 +15,10 @@ class ChatCompletion(WithXHeaders): """Дата и время создания ответа в формате Unix time""" model: str """Название модели, которая вернула ответ""" + thread_id: Optional[str] = None + """Идентификатор треда""" + message_id: Optional[str] = None + """Идентификатор сообщения. Для storage_mode - true""" usage: Usage """Данные об использовании модели""" object_: str = Field(alias="object") diff --git a/src/gigachat/models/chat_completion_chunk.py b/src/gigachat/models/chat_completion_chunk.py index 2d3577d..1e77a27 100644 --- a/src/gigachat/models/chat_completion_chunk.py +++ b/src/gigachat/models/chat_completion_chunk.py @@ -1,6 +1,7 @@ -from typing import List +from typing import List, Optional from gigachat.models.choices_chunk import ChoicesChunk +from gigachat.models.usage import Usage from gigachat.models.with_x_headers import WithXHeaders from gigachat.pydantic_v1 import Field @@ -15,4 +16,6 @@ class ChatCompletionChunk(WithXHeaders): model: str """Название модели, которая вернула ответ""" object_: str = Field(alias="object") - """Название вызываемого метода""" + """Наименование вызываемого метода""" + usage: Optional[Usage] = None + """Данные о потребленных токенах""" diff --git a/src/gigachat/models/deleted_file.py b/src/gigachat/models/deleted_file.py new file mode 100644 index 0000000..a270925 --- /dev/null +++ b/src/gigachat/models/deleted_file.py @@ -0,0 +1,11 @@ +from gigachat.models.with_x_headers import WithXHeaders +from gigachat.pydantic_v1 import Field + + +class DeletedFile(WithXHeaders): + """Информация об удаленном файле""" + + id_: str = Field(alias="id") + """Идентификатор файла """ + deleted: bool + """Признак удаления файла""" diff --git a/src/gigachat/models/storage.py b/src/gigachat/models/storage.py new file mode 100644 index 0000000..985b6c5 --- /dev/null +++ b/src/gigachat/models/storage.py @@ -0,0 +1,31 @@ +from typing import Dict, Optional, Any + +from gigachat.pydantic_v1 import BaseModel + + +class Storage(BaseModel): + """Данные для хранения контекста на стороне GigaChat""" + + is_stateful: bool + """ + Режим сохранения сообщений контекста. + При работе с данным режимом не требуется передавать сообщения контекста каждый раз. + Достаточно передать только новое сообщение + """ + limit: Optional[int] = None + """ + Максимальное количество сообщений исторического контекста, которые посылаются в модель в запросе. + Если пользователь передает limit на генерацию ответа от модели и у него есть system в истории или + инструкция у ассистента, то кол-во сообщений отправленных в модель = limit + 1, + т.е. к лимиту добавляется систем промпт. + Если параметр не передан, считаем что необходимо отправить весь контекст. + """ + assistant_id: Optional[str] = None + """ + Идентификатор ассистента при создании треда (только в первом сообщении). + При передаче идентификатора нельзя передавать поле model. + При передаче id треда этот идентификатор не передается + """ + thread_id: Optional[str] = None + """Идентификатор треда. Не заполняется для первого сообщения""" + metadata: Optional[Dict[Any, Any]] = None diff --git a/src/gigachat/models/uploaded_file.py b/src/gigachat/models/uploaded_file.py index aae7c8c..0589e76 100644 --- a/src/gigachat/models/uploaded_file.py +++ b/src/gigachat/models/uploaded_file.py @@ -1,3 +1,5 @@ +from typing import Optional + from gigachat.models.with_x_headers import WithXHeaders from gigachat.pydantic_v1 import Field @@ -17,3 +19,5 @@ class UploadedFile(WithXHeaders): """Имя файла""" purpose: str """Предполагаемое назначение файла.""" + access_policy: Optional[str] = None + """Доступность файла""" diff --git a/src/gigachat/models/uploaded_files.py b/src/gigachat/models/uploaded_files.py new file mode 100644 index 0000000..b04563d --- /dev/null +++ b/src/gigachat/models/uploaded_files.py @@ -0,0 +1,9 @@ +from typing import List + +from gigachat.models.uploaded_file import UploadedFile +from gigachat.models.with_x_headers import WithXHeaders + + +class UploadedFiles(WithXHeaders): + data: List[UploadedFile] + """Список загруженных файлов""" diff --git a/src/gigachat/models/usage.py b/src/gigachat/models/usage.py index a4dc1f4..1284e11 100644 --- a/src/gigachat/models/usage.py +++ b/src/gigachat/models/usage.py @@ -1,3 +1,5 @@ +from typing import Optional + from gigachat.pydantic_v1 import BaseModel @@ -10,3 +12,5 @@ class Usage(BaseModel): """Количество токенов, сгенерированных моделью""" total_tokens: int """Общее количество токенов""" + precached_prompt_tokens: Optional[int] + """Количество токенов попавших в кэш""" diff --git a/src/gigachat/threads.py b/src/gigachat/threads.py index 526b9c2..2f5fa60 100644 --- a/src/gigachat/threads.py +++ b/src/gigachat/threads.py @@ -1,4 +1,5 @@ import logging +import warnings from typing import ( TYPE_CHECKING, Any, @@ -100,6 +101,7 @@ def delete(self, thread_id: str) -> bool: def get_run(self, thread_id: str) -> ThreadRunResult: """Получить результат run треда""" + warnings.warn("get_run is deprecated. Use get_messages instead", stacklevel=2) return self.base_client._decorator( lambda: get_threads_run.sync( @@ -168,6 +170,7 @@ def run_messages( thread_options: Optional[Union[ThreadRunOptions, Dict[str, Any]]] = None, ) -> ThreadCompletion: """Добавление сообщений к треду с запуском""" + warnings.warn("run_messages is deprecated. Use GigaChat.chat instead", stacklevel=2) parsed_messages = [_parse_message(message) for message in messages] if thread_options is not None: thread_options = ThreadRunOptions.parse_obj(thread_options) @@ -212,6 +215,7 @@ def run_messages_stream( update_interval: Optional[int] = None, ) -> Iterator[ThreadCompletionChunk]: """Добавление сообщений к треду с запуском""" + warnings.warn("run_messages is deprecated. Use GigaChat.chat instead", stacklevel=2) parsed_messages = [_parse_message(message) for message in messages] if thread_options is not None: thread_options = ThreadRunOptions.parse_obj(thread_options) @@ -337,6 +341,7 @@ async def _acall() -> bool: async def get_run(self, thread_id: str) -> ThreadRunResult: """Получить результат run треда""" + warnings.warn("get_run is deprecated. Use get_messages instead", stacklevel=2) async def _acall() -> ThreadRunResult: return await get_threads_run.asyncio( @@ -413,6 +418,7 @@ async def run_messages( thread_options: Optional[Union[ThreadRunOptions, Dict[str, Any]]] = None, ) -> ThreadCompletion: """Добавление сообщений к треду с запуском""" + warnings.warn("run_messages is deprecated. Use GigaChat.chat instead", stacklevel=2) parsed_messages = [_parse_message(message) for message in messages] if thread_options is not None: thread_options = ThreadRunOptions.parse_obj(thread_options) @@ -459,6 +465,7 @@ async def run_messages_stream( update_interval: Optional[int] = None, ) -> AsyncIterator[ThreadCompletionChunk]: """Добавление сообщений к треду с запуском""" + warnings.warn("run_messages is deprecated. Use GigaChat.chat instead", stacklevel=2) parsed_messages = [_parse_message(message) for message in messages] if thread_options is not None: thread_options = ThreadRunOptions.parse_obj(thread_options) diff --git a/tests/data/get_file.json b/tests/data/get_file.json new file mode 100644 index 0000000..32f26b6 --- /dev/null +++ b/tests/data/get_file.json @@ -0,0 +1,9 @@ +{ + "id": "123", + "object": "file", + "bytes": 202725, + "access_policy": "private", + "created_at": 1740658568, + "filename": "invoicesample.pdf", + "purpose": "general" +} \ No newline at end of file diff --git a/tests/data/get_files.json b/tests/data/get_files.json new file mode 100644 index 0000000..0e40a31 --- /dev/null +++ b/tests/data/get_files.json @@ -0,0 +1,22 @@ +{ + "data": [ + { + "id": "123", + "object": "file", + "bytes": 202725, + "access_policy": "private", + "created_at": 1740658568, + "filename": "invoicesample.pdf", + "purpose": "general" + }, + { + "id": "123", + "object": "file", + "bytes": 1063892, + "access_policy": "private", + "created_at": 1739225612, + "filename": "123.jpg", + "purpose": "general" + } + ] +} \ No newline at end of file diff --git a/tests/data/post_files_delete.json b/tests/data/post_files_delete.json new file mode 100644 index 0000000..27e9b3e --- /dev/null +++ b/tests/data/post_files_delete.json @@ -0,0 +1,5 @@ +{ + "id": "123", + "object": "file", + "deleted": true +} \ No newline at end of file diff --git a/tests/unit_tests/gigachat/test_client.py b/tests/unit_tests/gigachat/test_client.py index 3fab0d8..e75ce7e 100644 --- a/tests/unit_tests/gigachat/test_client.py +++ b/tests/unit_tests/gigachat/test_client.py @@ -21,6 +21,7 @@ Chat, ChatCompletion, ChatCompletionChunk, + DeletedFile, Embedding, Embeddings, Function, @@ -29,6 +30,7 @@ OpenApiFunctions, TokensCount, UploadedFile, + UploadedFiles, ) from gigachat.models.balance import BalanceValue from gigachat.settings import Settings @@ -46,6 +48,9 @@ FILES_URL = f"{BASE_URL}/files" BALANCE_URL = f"{BASE_URL}/balance" CONVERT_FUNCTIONS_URL = f"{BASE_URL}/functions/convert" +GET_FILE_URL = f"{BASE_URL}/files/1" +GET_FILES_URL = f"{BASE_URL}/files" +FILE_DELETE_URL = f"{BASE_URL}/files/1/delete" ACCESS_TOKEN = get_json("access_token.json") TOKEN = get_json("token.json") @@ -61,6 +66,9 @@ FILES = get_json("post_files.json") BALANCE = get_json("balance.json") CONVERT_FUNCTIONS = get_json("convert_functions.json") +GET_FILE = get_json("get_file.json") +GET_FILES = get_json("get_files.json") +FILE_DELETE = get_json("post_files_delete.json") FILE = get_bytes("image.jpg") @@ -428,6 +436,31 @@ def test_convert_functions(httpx_mock: HTTPXMock) -> None: assert isinstance(row, Function) +def test_get_file(httpx_mock: HTTPXMock) -> None: + httpx_mock.add_response(url=GET_FILE_URL, json=GET_FILE) + + with GigaChatSyncClient(base_url=BASE_URL) as client: + response = client.get_file(file="1") + assert isinstance(response, UploadedFile) + + +def test_get_files(httpx_mock: HTTPXMock) -> None: + httpx_mock.add_response(url=GET_FILES_URL, json=GET_FILES) + + with GigaChatSyncClient(base_url=BASE_URL) as client: + response = client.get_files() + assert isinstance(response, UploadedFiles) + assert len(response.data) == 2 + + +def test_delete_file(httpx_mock: HTTPXMock) -> None: + httpx_mock.add_response(url=FILE_DELETE_URL, json=FILE_DELETE) + + with GigaChatSyncClient(base_url=BASE_URL) as client: + response = client.delete_file(file="1") + assert isinstance(response, DeletedFile) + + @pytest.mark.asyncio() async def test_aget_models(httpx_mock: HTTPXMock) -> None: httpx_mock.add_response(url=MODELS_URL, json=MODELS) @@ -683,3 +716,31 @@ async def test_aget_token_credentials(httpx_mock: HTTPXMock) -> None: assert model._access_token.expires_at == ACCESS_TOKEN["expires_at"] assert access_token.access_token == ACCESS_TOKEN["access_token"] assert access_token.expires_at == ACCESS_TOKEN["expires_at"] + + +@pytest.mark.asyncio() +async def test_aget_file(httpx_mock: HTTPXMock) -> None: + httpx_mock.add_response(url=GET_FILE_URL, json=GET_FILE) + + async with GigaChatAsyncClient(base_url=BASE_URL) as client: + response = await client.aget_file(file="1") + assert isinstance(response, UploadedFile) + + +@pytest.mark.asyncio() +async def test_aget_files(httpx_mock: HTTPXMock) -> None: + httpx_mock.add_response(url=GET_FILES_URL, json=GET_FILES) + + async with GigaChatAsyncClient(base_url=BASE_URL) as client: + response = await client.aget_files() + assert isinstance(response, UploadedFiles) + assert len(response.data) == 2 + + +@pytest.mark.asyncio() +async def test_adelete_file(httpx_mock: HTTPXMock) -> None: + httpx_mock.add_response(url=FILE_DELETE_URL, json=FILE_DELETE) + + async with GigaChatAsyncClient(base_url=BASE_URL) as client: + response = await client.adelete_file(file="1") + assert isinstance(response, DeletedFile)