Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions crowdin_api/api_resources/ai/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,8 @@ class EditAIProviderPath(Enum):
CONFIG = "/config"
IS_ENABLED = "/isEnabled"
USE_SYSTEM_CREDENTIALS = "/useSystemCredentials"


class DatasetPurpose(Enum):
TRAINING = "training"
VALIDATION = "validation"
114 changes: 114 additions & 0 deletions crowdin_api/api_resources/ai/resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
EditAIProviderRequestScheme,
GoogleGeminiChatProxy,
OtherChatProxy,
GenerateAIPromptFineTuningDatasetRequest,
CreateAIPromptFineTuningJobRequest,
)


Expand Down Expand Up @@ -211,6 +213,118 @@ def create_ai_proxy_chat_completion(
request_data=request_data,
)

def get_ai_prompt_fine_tuning_datasets_path(
self,
user_id: int,
ai_prompt_id: Optional[int] = None,
job_identifier: Optional[str] = None
):
if job_identifier is not None:
return f"users/{user_id}/ai/prompts/{ai_prompt_id}/fine-tuning/datasets/{job_identifier}"
return f"users/{user_id}/ai/prompts/{ai_prompt_id}/fine-tuning/datasets"

def get_ai_prompt_fine_tuning_jobs_path(
self,
user_id: int,
ai_prompt_id: Optional[int] = None,
job_identifier: Optional[str] = None
):
if job_identifier is not None:
return f"users/{user_id}/ai/prompts/{ai_prompt_id}/fine-tuning/jobs/{job_identifier}"
return f"users/{user_id}/ai/prompts/{ai_prompt_id}/fine-tuning/jobs"

def generate_ai_prompt_fine_tuning_dataset(
self,
user_id: int,
ai_prompt_id: int,
request_data: GenerateAIPromptFineTuningDatasetRequest,
):
"""
Generate AI Prompt Fine-Tuning Dataset

Link to documentation:
https://support.crowdin.com/developer/api/v2/#tag/AI/operation/api.ai.prompts.fine-tuning.datasets.post
"""

return self.requester.request(
method="post",
path=self.get_ai_prompt_fine_tuning_datasets_path(user_id, ai_prompt_id),
request_data=request_data,
)

def get_ai_prompt_fine_tuning_dataset_generation_status(
self,
user_id: int,
ai_prompt_id: int,
job_identifier: str
):
"""
Get AI Prompt Fine-Tuning Dataset Generation Status

Link to documentation:
https://support.crowdin.com/developer/api/v2/#tag/AI/operation/api.users.ai.prompts.fine-tuning.datasets.get
"""

return self.requester.request(
method="get",
path=self.get_ai_prompt_fine_tuning_datasets_path(user_id, ai_prompt_id, job_identifier),
)

def create_ai_prompt_fine_tuning_job(
self,
user_id: int,
ai_prompt_id: int,
request_data: CreateAIPromptFineTuningJobRequest
):
"""
Create AI Prompt Fine-Tuning Job

Link to documentation:
https://support.crowdin.com/developer/api/v2/#tag/AI/operation/api.ai.prompts.fine-tuning.jobs.post
"""

return self.requester.request(
method="post",
path=self.get_ai_prompt_fine_tuning_jobs_path(user_id, ai_prompt_id),
request_data=request_data,
)

def get_ai_prompt_fine_tuning_job_status(
self,
user_id: int,
ai_prompt_id: int,
job_identifier: str
):
"""
Get AI Prompt Fine-Tuning Job Status

Link to documentation:
https://support.crowdin.com/developer/api/v2/#tag/AI/operation/api.users.ai.prompts.fine-tuning.jobs.get
"""

return self.requester.request(
method="get",
path=self.get_ai_prompt_fine_tuning_jobs_path(user_id, ai_prompt_id, job_identifier),
)

def download_ai_prompt_fine_tuning_dataset(
self,
user_id: int,
ai_prompt_id: int,
job_identifier: str
):
"""
Download AI Prompt Fine-Tuning Dataset

Link to documentation:
https://support.crowdin.com/developer/api/v2/#tag/AI/operation/api.users.ai.prompts.fine-tuning.datasets.download.get
"""

return self.requester.request(
method="get",
path=self.get_ai_prompt_fine_tuning_datasets_path(user_id, ai_prompt_id, job_identifier) + "/download",
)


class EnterpriseAIResource(BaseResource):
"""
Expand Down
181 changes: 179 additions & 2 deletions crowdin_api/api_resources/ai/tests/test_ai_resources.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
from datetime import datetime, timezone
from unittest import mock

import pytest
from crowdin_api.api_resources.ai.enums import AIPromptAction, AIProviderType
from crowdin_api.api_resources.ai.enums import AIPromptAction, AIProviderType, DatasetPurpose
from crowdin_api.api_resources.ai.resource import AIResource, EnterpriseAIResource
from crowdin_api.api_resources.ai.types import AIPromptOperation, EditAIPromptPath
from crowdin_api.api_resources.ai.types import (
AIPromptOperation,
EditAIPromptPath,
CreateAIPromptFineTuningJobRequest,
HyperParameters,
TrainingOptions, GenerateAIPromptFineTuningDatasetRequest
)
from crowdin_api.requester import APIRequester


Expand Down Expand Up @@ -405,6 +412,176 @@ def test_create_ai_proxy_chat_completion(self, m_request, base_absolut_url):
request_data=request_data,
)

@pytest.mark.parametrize(
"incoming_data, request_data",
(
(
GenerateAIPromptFineTuningDatasetRequest(
projectIds=[1],
tmIds=[2, 3],
purpose=DatasetPurpose.TRAINING.value,
dateFrom=datetime(2019, 9, 23, 11, 26, 54,
tzinfo=timezone.utc).isoformat(),
dateTo=datetime(2019, 9, 23, 11, 26, 54,
tzinfo=timezone.utc).isoformat(),
maxFileSize=20,
minExamplesCount=2,
maxExamplesCount=10
),
{
"projectIds": [
1
],
"tmIds": [
2, 3
],
"purpose": "training",
"dateFrom": "2019-09-23T11:26:54+00:00",
"dateTo": "2019-09-23T11:26:54+00:00",
"maxFileSize": 20,
"minExamplesCount": 2,
"maxExamplesCount": 10
}
),
),
)
@mock.patch("crowdin_api.requester.APIRequester.request")
def test_generate_ai_prompt_fine_tuning_dataset(self, m_request, incoming_data, request_data, base_absolut_url):
m_request.return_value = "response"

user_id = 1
ai_prompt_id = 2

resource = self.get_resource(base_absolut_url)
assert (
resource.generate_ai_prompt_fine_tuning_dataset(user_id, ai_prompt_id, request_data=incoming_data)
== "response"
)
m_request.assert_called_once_with(
method="post",
path=f"users/{user_id}/ai/prompts/{ai_prompt_id}/fine-tuning/datasets",
request_data=request_data,
)

@mock.patch("crowdin_api.requester.APIRequester.request")
def test_get_ai_prompt_fine_tuning_dataset_generation_status(self, m_request, base_absolut_url):
m_request.return_value = "response"

user_id = 1
ai_prompt_id = 2
job_identifier = "id"

resource = self.get_resource(base_absolut_url)
assert (
resource.get_ai_prompt_fine_tuning_dataset_generation_status(user_id, ai_prompt_id, job_identifier)
== "response"
)
m_request.assert_called_once_with(
method="get",
path=f"users/{user_id}/ai/prompts/{ai_prompt_id}/fine-tuning/datasets/{job_identifier}",
)

@pytest.mark.parametrize(
"incoming_data, request_data",
(
(
CreateAIPromptFineTuningJobRequest(
dryRun=False,
hyperparameters=HyperParameters(
batchSize=1,
learningRateMultiplier=2.0,
nEpochs=100,
),
trainingOptions=TrainingOptions(
projectIds=[1],
tmIds=[2],
dateFrom=datetime(2019, 9, 23, 11, 26, 54,
tzinfo=timezone.utc).isoformat(),
dateTo=datetime(2019, 9, 23, 11, 26, 54,
tzinfo=timezone.utc).isoformat(),
maxFileSize=10,
minExamplesCount=200,
maxExamplesCount=300
)
),
{
"dryRun": False,
"hyperparameters": {
"batchSize": 1,
"learningRateMultiplier": 2.0,
"nEpochs": 100,
},
"trainingOptions": {
"projectIds": [1],
"tmIds": [2],
"dateFrom": "2019-09-23T11:26:54+00:00",
"dateTo": "2019-09-23T11:26:54+00:00",
"maxFileSize": 10,
"minExamplesCount": 200,
"maxExamplesCount": 300
}
}
),
),
)
@mock.patch("crowdin_api.requester.APIRequester.request")
def test_create_ai_prompt_fine_tuning_job(self, m_request, incoming_data, request_data, base_absolut_url):
m_request.return_value = "response"

user_id = 1
ai_prompt_id = 2

resource = self.get_resource(base_absolut_url)
assert (
resource.create_ai_prompt_fine_tuning_job(user_id, ai_prompt_id, request_data=incoming_data)
== "response"
)
m_request.assert_called_once_with(
method="post",
path=f"users/{user_id}/ai/prompts/{ai_prompt_id}/fine-tuning/jobs",
request_data=request_data,
)

@mock.patch("crowdin_api.requester.APIRequester.request")
def test_get_ai_prompt_fine_tuning_job_status(self, m_request, base_absolut_url):
m_request.return_value = "response"

user_id = 1
ai_prompt_id = 2
job_identifier = "id"

resource = self.get_resource(base_absolut_url)
assert (
resource.get_ai_prompt_fine_tuning_job_status(user_id, ai_prompt_id, job_identifier)
== "response"
)
m_request.assert_called_once_with(
method="get",
path=f"users/{user_id}/ai/prompts/{ai_prompt_id}/fine-tuning/jobs/{job_identifier}",
)

@mock.patch("crowdin_api.requester.APIRequester.request")
def test_download_ai_prompt_fine_tuning_dataset(
self,
m_request,
base_absolut_url
):
m_request.return_value = "response"

user_id = 1
ai_prompt_id = 2
job_identifier = "id"

resource = self.get_resource(base_absolut_url)
assert (
resource.download_ai_prompt_fine_tuning_dataset(user_id, ai_prompt_id, job_identifier)
== "response"
)
m_request.assert_called_once_with(
method="get",
path=f"users/{user_id}/ai/prompts/{ai_prompt_id}/fine-tuning/datasets/{job_identifier}/download",
)


class TestEnterpriseAIResources:
resource_class = EnterpriseAIResource
Expand Down
44 changes: 44 additions & 0 deletions crowdin_api/api_resources/ai/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,3 +148,47 @@ class GoogleGeminiChatProxy(TypedDict):

class OtherChatProxy(TypedDict):
stream: Optional[bool]


class GenerateAIPromptFineTuningDatasetRequest(TypedDict):
projectIds: Optional[Iterable[int]]
tmIds: Optional[Iterable[int]]
purpose: Optional[str]
dateFrom: str
dateTo: str
maxFileSize: Optional[int]
minExamplesCount: Optional[int]
maxExamplesCount: Optional[int]


class HyperParameters(TypedDict):
batchSize: int
learningRateMultiplier: float
nEpochs: int


class TrainingOptions(TypedDict):
projectIds: Optional[Iterable[int]]
tmIds: Optional[Iterable[int]]
dateFrom: Optional[str]
dateTo: Optional[str]
maxFileSize: Optional[int]
minExamplesCount: Optional[int]
maxExamplesCount: Optional[int]


class ValidationOptions(TypedDict):
projectIds: Optional[Iterable[int]]
tmIds: Optional[Iterable[int]]
dateFrom: Optional[str]
dateTo: Optional[str]
maxFileSize: Optional[int]
minExamplesCount: Optional[int]
maxExamplesCount: Optional[int]


class CreateAIPromptFineTuningJobRequest(TypedDict):
dryRun: Optional[bool]
hyperparameters: Optional[HyperParameters]
trainingOptions: TrainingOptions
validationOptions: Optional[ValidationOptions]