From 7a90c0c10b29af35307fd1fbb3931442c4aad06b Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Mon, 6 May 2024 15:45:27 +0200 Subject: [PATCH] feat: Support Box AI endpoints (box/box-openapi#416) (#137) --- .codegen.json | 2 +- box_sdk_gen/client.py | 3 + box_sdk_gen/managers/__init__.py | 2 + box_sdk_gen/managers/ai.py | 218 +++++++++++++++++++++++++++++++ box_sdk_gen/schemas.py | 150 +++++++++++++++++++++ docs/ai.md | 60 +++++++++ 6 files changed, 434 insertions(+), 1 deletion(-) create mode 100644 box_sdk_gen/managers/ai.py create mode 100644 docs/ai.md diff --git a/.codegen.json b/.codegen.json index a1c03b79..572b536c 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "cb5120c", "specHash": "1698c95", "version": "0.6.4" } +{ "engineHash": "afd7974", "specHash": "63d1af0", "version": "0.6.4" } diff --git a/box_sdk_gen/client.py b/box_sdk_gen/client.py index e8f9a61b..368d0957 100644 --- a/box_sdk_gen/client.py +++ b/box_sdk_gen/client.py @@ -160,6 +160,8 @@ from box_sdk_gen.managers.integration_mappings import IntegrationMappingsManager +from box_sdk_gen.managers.ai import AiManager + from box_sdk_gen.networking.auth import Authentication from box_sdk_gen.networking.network import NetworkSession @@ -381,6 +383,7 @@ def __init__(self, auth: Authentication, *, network_session: NetworkSession = No self.integration_mappings = IntegrationMappingsManager( auth=self.auth, network_session=self.network_session ) + self.ai = AiManager(auth=self.auth, network_session=self.network_session) def with_as_user_header(self, user_id: str) -> 'BoxClient': """ diff --git a/box_sdk_gen/managers/__init__.py b/box_sdk_gen/managers/__init__.py index 766347b4..ca5771c5 100644 --- a/box_sdk_gen/managers/__init__.py +++ b/box_sdk_gen/managers/__init__.py @@ -133,3 +133,5 @@ from box_sdk_gen.managers.sign_templates import * from box_sdk_gen.managers.integration_mappings import * + +from box_sdk_gen.managers.ai import * diff --git a/box_sdk_gen/managers/ai.py b/box_sdk_gen/managers/ai.py new file mode 100644 index 00000000..afbd9026 --- /dev/null +++ b/box_sdk_gen/managers/ai.py @@ -0,0 +1,218 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from typing import List + +from typing import Dict + +from box_sdk_gen.serialization.json.serializer import serialize + +from box_sdk_gen.serialization.json.serializer import deserialize + +from box_sdk_gen.internal.utils import DateTime + +from box_sdk_gen.schemas import AiResponse + +from box_sdk_gen.schemas import ClientError + +from box_sdk_gen.schemas import AiAsk + +from box_sdk_gen.schemas import AiTextGen + +from box_sdk_gen.networking.auth import Authentication + +from box_sdk_gen.networking.network import NetworkSession + +from box_sdk_gen.internal.utils import prepare_params + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.internal.utils import ByteStream + +from box_sdk_gen.networking.fetch import FetchOptions + +from box_sdk_gen.networking.fetch import FetchResponse + +from box_sdk_gen.networking.fetch import fetch + +from box_sdk_gen.serialization.json.json_data import SerializedData + + +class CreateAiAskMode(str, Enum): + MULTIPLE_ITEM_QA = 'multiple_item_qa' + SINGLE_ITEM_QA = 'single_item_qa' + + +class CreateAiAskItemsTypeField(str, Enum): + FILE = 'file' + + +class CreateAiAskItems(BaseObject): + _discriminator = 'type', {'file'} + + def __init__( + self, + id: str, + *, + type: CreateAiAskItemsTypeField = CreateAiAskItemsTypeField.FILE.value, + content: Optional[str] = None, + **kwargs + ): + """ + :param id: The id of the item + :type id: str + :param type: The type of the item, defaults to CreateAiAskItemsTypeField.FILE.value + :type type: CreateAiAskItemsTypeField, optional + :param content: The content of the item, often the text representation., defaults to None + :type content: Optional[str], optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type + self.content = content + + +class CreateAiTextGenItemsTypeField(str, Enum): + FILE = 'file' + + +class CreateAiTextGenItems(BaseObject): + _discriminator = 'type', {'file'} + + def __init__( + self, + *, + id: Optional[str] = None, + type: Optional[CreateAiTextGenItemsTypeField] = None, + content: Optional[str] = None, + **kwargs + ): + """ + :param id: The id of the item., defaults to None + :type id: Optional[str], optional + :param type: The type of the item., defaults to None + :type type: Optional[CreateAiTextGenItemsTypeField], optional + :param content: The content to use as context for generating new text or editing existing text., defaults to None + :type content: Optional[str], optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type + self.content = content + + +class CreateAiTextGenDialogueHistory(BaseObject): + def __init__( + self, + *, + prompt: Optional[str] = None, + answer: Optional[str] = None, + created_at: Optional[DateTime] = None, + **kwargs + ): + """ + :param prompt: The prompt previously provided by the client and answered by the LLM., defaults to None + :type prompt: Optional[str], optional + :param answer: The answer previously provided by the LLM., defaults to None + :type answer: Optional[str], optional + :param created_at: The ISO date formatted timestamp of when the previous answer to the prompt was created., defaults to None + :type created_at: Optional[DateTime], optional + """ + super().__init__(**kwargs) + self.prompt = prompt + self.answer = answer + self.created_at = created_at + + +class AiManager: + def __init__( + self, + *, + auth: Optional[Authentication] = None, + network_session: NetworkSession = None + ): + if network_session is None: + network_session = NetworkSession() + self.auth = auth + self.network_session = network_session + + def create_ai_ask( + self, + mode: CreateAiAskMode, + prompt: str, + items: List[CreateAiAskItems], + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> AiResponse: + """ + Sends an AI request to supported LLMs and returns an answer specifically focused on the user's question given the provided context. + :param mode: The mode specifies if this request is for a single or multiple items. + :type mode: CreateAiAskMode + :param prompt: The prompt provided by the client to be answered by the LLM. + :type prompt: str + :param items: The items to be processed by the LLM, often files. + :type items: List[CreateAiAskItems] + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = {'mode': mode, 'prompt': prompt, 'items': items} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = fetch( + ''.join([self.network_session.base_urls.base_url, '/v2/ai/ask']), + FetchOptions( + method='POST', + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format='json', + auth=self.auth, + network_session=self.network_session, + ), + ) + return deserialize(response.data, AiResponse) + + def create_ai_text_gen( + self, + prompt: str, + items: List[CreateAiTextGenItems], + *, + dialogue_history: Optional[List[CreateAiTextGenDialogueHistory]] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> AiResponse: + """ + Sends an AI request to supported LLMs and returns an answer specifically focused on the creation of new text. + :param prompt: The prompt provided by the client to be answered by the LLM. + :type prompt: str + :param items: The items to be processed by the LLM, often files. + :type items: List[CreateAiTextGenItems] + :param dialogue_history: The history of prompts and answers previously passed to the LLM. This provides additional context to the LLM in generating the response., defaults to None + :type dialogue_history: Optional[List[CreateAiTextGenDialogueHistory]], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = { + 'prompt': prompt, + 'items': items, + 'dialogue_history': dialogue_history, + } + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = fetch( + ''.join([self.network_session.base_urls.base_url, '/v2/ai/text_gen']), + FetchOptions( + method='POST', + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format='json', + auth=self.auth, + network_session=self.network_session, + ), + ) + return deserialize(response.data, AiResponse) diff --git a/box_sdk_gen/schemas.py b/box_sdk_gen/schemas.py index 25c1a427..3d14a583 100644 --- a/box_sdk_gen/schemas.py +++ b/box_sdk_gen/schemas.py @@ -15,6 +15,133 @@ from box_sdk_gen.internal.utils import Date +class AiAskModeField(str, Enum): + MULTIPLE_ITEM_QA = 'multiple_item_qa' + SINGLE_ITEM_QA = 'single_item_qa' + + +class AiAskItemsTypeField(str, Enum): + FILE = 'file' + + +class AiAskItemsField(BaseObject): + _discriminator = 'type', {'file'} + + def __init__( + self, + id: str, + *, + type: AiAskItemsTypeField = AiAskItemsTypeField.FILE.value, + content: Optional[str] = None, + **kwargs + ): + """ + :param id: The id of the item + :type id: str + :param type: The type of the item, defaults to AiAskItemsTypeField.FILE.value + :type type: AiAskItemsTypeField, optional + :param content: The content of the item, often the text representation., defaults to None + :type content: Optional[str], optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type + self.content = content + + +class AiAsk(BaseObject): + def __init__( + self, mode: AiAskModeField, prompt: str, items: List[AiAskItemsField], **kwargs + ): + """ + :param mode: The mode specifies if this request is for a single or multiple items. + :type mode: AiAskModeField + :param prompt: The prompt provided by the client to be answered by the LLM. + :type prompt: str + :param items: The items to be processed by the LLM, often files. + :type items: List[AiAskItemsField] + """ + super().__init__(**kwargs) + self.mode = mode + self.prompt = prompt + self.items = items + + +class AiTextGenItemsTypeField(str, Enum): + FILE = 'file' + + +class AiTextGenItemsField(BaseObject): + _discriminator = 'type', {'file'} + + def __init__( + self, + *, + id: Optional[str] = None, + type: Optional[AiTextGenItemsTypeField] = None, + content: Optional[str] = None, + **kwargs + ): + """ + :param id: The id of the item., defaults to None + :type id: Optional[str], optional + :param type: The type of the item., defaults to None + :type type: Optional[AiTextGenItemsTypeField], optional + :param content: The content to use as context for generating new text or editing existing text., defaults to None + :type content: Optional[str], optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type + self.content = content + + +class AiTextGenDialogueHistoryField(BaseObject): + def __init__( + self, + *, + prompt: Optional[str] = None, + answer: Optional[str] = None, + created_at: Optional[DateTime] = None, + **kwargs + ): + """ + :param prompt: The prompt previously provided by the client and answered by the LLM., defaults to None + :type prompt: Optional[str], optional + :param answer: The answer previously provided by the LLM., defaults to None + :type answer: Optional[str], optional + :param created_at: The ISO date formatted timestamp of when the previous answer to the prompt was created., defaults to None + :type created_at: Optional[DateTime], optional + """ + super().__init__(**kwargs) + self.prompt = prompt + self.answer = answer + self.created_at = created_at + + +class AiTextGen(BaseObject): + def __init__( + self, + prompt: str, + items: List[AiTextGenItemsField], + *, + dialogue_history: Optional[List[AiTextGenDialogueHistoryField]] = None, + **kwargs + ): + """ + :param prompt: The prompt provided by the client to be answered by the LLM. + :type prompt: str + :param items: The items to be processed by the LLM, often files. + :type items: List[AiTextGenItemsField] + :param dialogue_history: The history of prompts and answers previously passed to the LLM. This provides additional context to the LLM in generating the response., defaults to None + :type dialogue_history: Optional[List[AiTextGenDialogueHistoryField]], optional + """ + super().__init__(**kwargs) + self.prompt = prompt + self.items = items + self.dialogue_history = dialogue_history + + class PostOAuth2TokenGrantTypeField(str, Enum): AUTHORIZATION_CODE = 'authorization_code' REFRESH_TOKEN = 'refresh_token' @@ -636,6 +763,29 @@ def __init__( self.error_description = error_description +class AiResponse(BaseObject): + def __init__( + self, + answer: str, + created_at: DateTime, + *, + completion_reason: Optional[str] = None, + **kwargs + ): + """ + :param answer: The answer provided by the LLM. + :type answer: str + :param created_at: The ISO date formatted timestamp of when the answer to the prompt was created. + :type created_at: DateTime + :param completion_reason: The reason the response finishes., defaults to None + :type completion_reason: Optional[str], optional + """ + super().__init__(**kwargs) + self.answer = answer + self.created_at = created_at + self.completion_reason = completion_reason + + class ClassificationTemplateField(str, Enum): SECURITYCLASSIFICATION_6VMVOCHWUWO = 'securityClassification-6VMVochwUWo' diff --git a/docs/ai.md b/docs/ai.md new file mode 100644 index 00000000..6c319a1d --- /dev/null +++ b/docs/ai.md @@ -0,0 +1,60 @@ +# AiManager + +- [Send AI Ask request](#send-ai-ask-request) +- [Send AI Text Gen request](#send-ai-text-gen-request) + +## Send AI Ask request + +Sends an AI request to supported LLMs and returns an answer specifically focused on the user's question given the provided context. + +This operation is performed by calling function `create_ai_ask`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/post-ai-ask/). + +_Currently we don't have an example for calling `create_ai_ask` in integration tests_ + +### Arguments + +- mode `CreateAiAskMode` + - The mode specifies if this request is for a single or multiple items. +- prompt `str` + - The prompt provided by the client to be answered by the LLM. +- items `List[CreateAiAskItems]` + - The items to be processed by the LLM, often files. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `AiResponse`. + +A successful response including the answer from the LLM. + +## Send AI Text Gen request + +Sends an AI request to supported LLMs and returns an answer specifically focused on the creation of new text. + +This operation is performed by calling function `create_ai_text_gen`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/post-ai-text-gen/). + +_Currently we don't have an example for calling `create_ai_text_gen` in integration tests_ + +### Arguments + +- prompt `str` + - The prompt provided by the client to be answered by the LLM. +- items `List[CreateAiTextGenItems]` + - The items to be processed by the LLM, often files. +- dialogue_history `Optional[List[CreateAiTextGenDialogueHistory]]` + - The history of prompts and answers previously passed to the LLM. This provides additional context to the LLM in generating the response. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `AiResponse`. + +A successful response including the answer from the LLM.