Skip to content

Commit 76e58d8

Browse files
committed
feat(ai): add Fine Tuning endpoints support
1 parent addff72 commit 76e58d8

File tree

4 files changed

+343
-2
lines changed

4 files changed

+343
-2
lines changed

crowdin_api/api_resources/ai/enums.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,8 @@ class EditAIProviderPath(Enum):
3737
CONFIG = "/config"
3838
IS_ENABLED = "/isEnabled"
3939
USE_SYSTEM_CREDENTIALS = "/useSystemCredentials"
40+
41+
42+
class DatasetPurpose(Enum):
43+
TRAINING = "training"
44+
VALIDATION = "validation"

crowdin_api/api_resources/ai/resource.py

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
EditAIProviderRequestScheme,
1010
GoogleGeminiChatProxy,
1111
OtherChatProxy,
12+
GenerateAIPromptFineTuningDatasetRequest,
13+
CreateAIPromptFineTuningJobRequest,
1214
)
1315

1416

@@ -211,6 +213,118 @@ def create_ai_proxy_chat_completion(
211213
request_data=request_data,
212214
)
213215

216+
def get_ai_prompt_fine_tuning_datasets_path(
217+
self,
218+
user_id: int,
219+
ai_prompt_id: Optional[int] = None,
220+
job_identifier: Optional[str] = None
221+
):
222+
if job_identifier is not None:
223+
return f"users/{user_id}/ai/prompts/{ai_prompt_id}/fine-tuning/datasets/{job_identifier}"
224+
return f"users/{user_id}/ai/prompts/{ai_prompt_id}/fine-tuning/datasets"
225+
226+
def get_ai_prompt_fine_tuning_jobs_path(
227+
self,
228+
user_id: int,
229+
ai_prompt_id: Optional[int] = None,
230+
job_identifier: Optional[str] = None
231+
):
232+
if job_identifier is not None:
233+
return f"users/{user_id}/ai/prompts/{ai_prompt_id}/fine-tuning/jobs/{job_identifier}"
234+
return f"users/{user_id}/ai/prompts/{ai_prompt_id}/fine-tuning/jobs"
235+
236+
def generate_ai_prompt_fine_tuning_dataset(
237+
self,
238+
user_id: int,
239+
ai_prompt_id: int,
240+
request_data: GenerateAIPromptFineTuningDatasetRequest,
241+
):
242+
"""
243+
Generate AI Prompt Fine-Tuning Dataset
244+
245+
Link to documentation:
246+
https://support.crowdin.com/developer/api/v2/#tag/AI/operation/api.ai.prompts.fine-tuning.datasets.post
247+
"""
248+
249+
return self.requester.request(
250+
method="post",
251+
path=self.get_ai_prompt_fine_tuning_datasets_path(user_id, ai_prompt_id),
252+
request_data=request_data,
253+
)
254+
255+
def get_ai_prompt_fine_tuning_dataset_generation_status(
256+
self,
257+
user_id: int,
258+
ai_prompt_id: int,
259+
job_identifier: str
260+
):
261+
"""
262+
Get AI Prompt Fine-Tuning Dataset Generation Status
263+
264+
Link to documentation:
265+
https://support.crowdin.com/developer/api/v2/#tag/AI/operation/api.users.ai.prompts.fine-tuning.datasets.get
266+
"""
267+
268+
return self.requester.request(
269+
method="get",
270+
path=self.get_ai_prompt_fine_tuning_datasets_path(user_id, ai_prompt_id, job_identifier),
271+
)
272+
273+
def create_ai_prompt_fine_tuning_job(
274+
self,
275+
user_id: int,
276+
ai_prompt_id: int,
277+
request_data: CreateAIPromptFineTuningJobRequest
278+
):
279+
"""
280+
Create AI Prompt Fine-Tuning Job
281+
282+
Link to documentation:
283+
https://support.crowdin.com/developer/api/v2/#tag/AI/operation/api.ai.prompts.fine-tuning.jobs.post
284+
"""
285+
286+
return self.requester.request(
287+
method="post",
288+
path=self.get_ai_prompt_fine_tuning_jobs_path(user_id, ai_prompt_id),
289+
request_data=request_data,
290+
)
291+
292+
def get_ai_prompt_fine_tuning_job_status(
293+
self,
294+
user_id: int,
295+
ai_prompt_id: int,
296+
job_identifier: str
297+
):
298+
"""
299+
Get AI Prompt Fine-Tuning Job Status
300+
301+
Link to documentation:
302+
https://support.crowdin.com/developer/api/v2/#tag/AI/operation/api.users.ai.prompts.fine-tuning.jobs.get
303+
"""
304+
305+
return self.requester.request(
306+
method="get",
307+
path=self.get_ai_prompt_fine_tuning_jobs_path(user_id, ai_prompt_id, job_identifier),
308+
)
309+
310+
def download_ai_prompt_fine_tuning_dataset(
311+
self,
312+
user_id: int,
313+
ai_prompt_id: int,
314+
job_identifier: str
315+
):
316+
"""
317+
Download AI Prompt Fine-Tuning Dataset
318+
319+
Link to documentation:
320+
https://support.crowdin.com/developer/api/v2/#tag/AI/operation/api.users.ai.prompts.fine-tuning.datasets.download.get
321+
"""
322+
323+
return self.requester.request(
324+
method="get",
325+
path=self.get_ai_prompt_fine_tuning_datasets_path(user_id, ai_prompt_id, job_identifier) + "/download",
326+
)
327+
214328

215329
class EnterpriseAIResource(BaseResource):
216330
"""

crowdin_api/api_resources/ai/tests/test_ai_resources.py

Lines changed: 179 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,16 @@
1+
from datetime import datetime, timezone
12
from unittest import mock
23

34
import pytest
4-
from crowdin_api.api_resources.ai.enums import AIPromptAction, AIProviderType
5+
from crowdin_api.api_resources.ai.enums import AIPromptAction, AIProviderType, DatasetPurpose
56
from crowdin_api.api_resources.ai.resource import AIResource, EnterpriseAIResource
6-
from crowdin_api.api_resources.ai.types import AIPromptOperation, EditAIPromptPath
7+
from crowdin_api.api_resources.ai.types import (
8+
AIPromptOperation,
9+
EditAIPromptPath,
10+
CreateAIPromptFineTuningJobRequest,
11+
HyperParameters,
12+
TrainingOptions, GenerateAIPromptFineTuningDatasetRequest
13+
)
714
from crowdin_api.requester import APIRequester
815

916

@@ -405,6 +412,176 @@ def test_create_ai_proxy_chat_completion(self, m_request, base_absolut_url):
405412
request_data=request_data,
406413
)
407414

415+
@pytest.mark.parametrize(
416+
"incoming_data, request_data",
417+
(
418+
(
419+
GenerateAIPromptFineTuningDatasetRequest(
420+
projectIds=[1],
421+
tmIds=[2, 3],
422+
purpose=DatasetPurpose.TRAINING.value,
423+
dateFrom=datetime(2019, 9, 23, 11, 26, 54,
424+
tzinfo=timezone.utc).isoformat(),
425+
dateTo=datetime(2019, 9, 23, 11, 26, 54,
426+
tzinfo=timezone.utc).isoformat(),
427+
maxFileSize=20,
428+
minExamplesCount=2,
429+
maxExamplesCount=10
430+
),
431+
{
432+
"projectIds": [
433+
1
434+
],
435+
"tmIds": [
436+
2, 3
437+
],
438+
"purpose": "training",
439+
"dateFrom": "2019-09-23T11:26:54+00:00",
440+
"dateTo": "2019-09-23T11:26:54+00:00",
441+
"maxFileSize": 20,
442+
"minExamplesCount": 2,
443+
"maxExamplesCount": 10
444+
}
445+
),
446+
),
447+
)
448+
@mock.patch("crowdin_api.requester.APIRequester.request")
449+
def test_generate_ai_prompt_fine_tuning_dataset(self, m_request, incoming_data, request_data, base_absolut_url):
450+
m_request.return_value = "response"
451+
452+
user_id = 1
453+
ai_prompt_id = 2
454+
455+
resource = self.get_resource(base_absolut_url)
456+
assert (
457+
resource.generate_ai_prompt_fine_tuning_dataset(user_id, ai_prompt_id, request_data=incoming_data)
458+
== "response"
459+
)
460+
m_request.assert_called_once_with(
461+
method="post",
462+
path=f"users/{user_id}/ai/prompts/{ai_prompt_id}/fine-tuning/datasets",
463+
request_data=request_data,
464+
)
465+
466+
@mock.patch("crowdin_api.requester.APIRequester.request")
467+
def test_get_ai_prompt_fine_tuning_dataset_generation_status(self, m_request, base_absolut_url):
468+
m_request.return_value = "response"
469+
470+
user_id = 1
471+
ai_prompt_id = 2
472+
job_identifier = "id"
473+
474+
resource = self.get_resource(base_absolut_url)
475+
assert (
476+
resource.get_ai_prompt_fine_tuning_dataset_generation_status(user_id, ai_prompt_id, job_identifier)
477+
== "response"
478+
)
479+
m_request.assert_called_once_with(
480+
method="get",
481+
path=f"users/{user_id}/ai/prompts/{ai_prompt_id}/fine-tuning/datasets/{job_identifier}",
482+
)
483+
484+
@pytest.mark.parametrize(
485+
"incoming_data, request_data",
486+
(
487+
(
488+
CreateAIPromptFineTuningJobRequest(
489+
dryRun=False,
490+
hyperparameters=HyperParameters(
491+
batchSize=1,
492+
learningRateMultiplier=2.0,
493+
nEpochs=100,
494+
),
495+
trainingOptions=TrainingOptions(
496+
projectIds=[1],
497+
tmIds=[2],
498+
dateFrom=datetime(2019, 9, 23, 11, 26, 54,
499+
tzinfo=timezone.utc).isoformat(),
500+
dateTo=datetime(2019, 9, 23, 11, 26, 54,
501+
tzinfo=timezone.utc).isoformat(),
502+
maxFileSize=10,
503+
minExamplesCount=200,
504+
maxExamplesCount=300
505+
)
506+
),
507+
{
508+
"dryRun": False,
509+
"hyperparameters": {
510+
"batchSize": 1,
511+
"learningRateMultiplier": 2.0,
512+
"nEpochs": 100,
513+
},
514+
"trainingOptions": {
515+
"projectIds": [1],
516+
"tmIds": [2],
517+
"dateFrom": "2019-09-23T11:26:54+00:00",
518+
"dateTo": "2019-09-23T11:26:54+00:00",
519+
"maxFileSize": 10,
520+
"minExamplesCount": 200,
521+
"maxExamplesCount": 300
522+
}
523+
}
524+
),
525+
),
526+
)
527+
@mock.patch("crowdin_api.requester.APIRequester.request")
528+
def test_create_ai_prompt_fine_tuning_job(self, m_request, incoming_data, request_data, base_absolut_url):
529+
m_request.return_value = "response"
530+
531+
user_id = 1
532+
ai_prompt_id = 2
533+
534+
resource = self.get_resource(base_absolut_url)
535+
assert (
536+
resource.create_ai_prompt_fine_tuning_job(user_id, ai_prompt_id, request_data=incoming_data)
537+
== "response"
538+
)
539+
m_request.assert_called_once_with(
540+
method="post",
541+
path=f"users/{user_id}/ai/prompts/{ai_prompt_id}/fine-tuning/jobs",
542+
request_data=request_data,
543+
)
544+
545+
@mock.patch("crowdin_api.requester.APIRequester.request")
546+
def test_get_ai_prompt_fine_tuning_job_status(self, m_request, base_absolut_url):
547+
m_request.return_value = "response"
548+
549+
user_id = 1
550+
ai_prompt_id = 2
551+
job_identifier = "id"
552+
553+
resource = self.get_resource(base_absolut_url)
554+
assert (
555+
resource.get_ai_prompt_fine_tuning_job_status(user_id, ai_prompt_id, job_identifier)
556+
== "response"
557+
)
558+
m_request.assert_called_once_with(
559+
method="get",
560+
path=f"users/{user_id}/ai/prompts/{ai_prompt_id}/fine-tuning/jobs/{job_identifier}",
561+
)
562+
563+
@mock.patch("crowdin_api.requester.APIRequester.request")
564+
def test_download_ai_prompt_fine_tuning_dataset(
565+
self,
566+
m_request,
567+
base_absolut_url
568+
):
569+
m_request.return_value = "response"
570+
571+
user_id = 1
572+
ai_prompt_id = 2
573+
job_identifier = "id"
574+
575+
resource = self.get_resource(base_absolut_url)
576+
assert (
577+
resource.download_ai_prompt_fine_tuning_dataset(user_id, ai_prompt_id, job_identifier)
578+
== "response"
579+
)
580+
m_request.assert_called_once_with(
581+
method="get",
582+
path=f"users/{user_id}/ai/prompts/{ai_prompt_id}/fine-tuning/datasets/{job_identifier}/download",
583+
)
584+
408585

409586
class TestEnterpriseAIResources:
410587
resource_class = EnterpriseAIResource

crowdin_api/api_resources/ai/types.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,3 +148,48 @@ class GoogleGeminiChatProxy(TypedDict):
148148

149149
class OtherChatProxy(TypedDict):
150150
stream: Optional[bool]
151+
152+
153+
class GenerateAIPromptFineTuningDatasetRequest(TypedDict):
154+
projectIds: Optional[Iterable[int]]
155+
tmIds: Optional[Iterable[int]]
156+
purpose: Optional[str]
157+
dateFrom: str
158+
dateTo: str
159+
maxFileSize: Optional[int]
160+
minExamplesCount: Optional[int]
161+
maxExamplesCount: Optional[int]
162+
163+
164+
class HyperParameters(TypedDict):
165+
batchSize: int
166+
learningRateMultiplier: float
167+
nEpochs: int
168+
169+
170+
class TrainingOptions(TypedDict):
171+
projectIds: Optional[Iterable[int]]
172+
tmIds: Optional[Iterable[int]]
173+
dateFrom: Optional[str]
174+
dateTo: Optional[str]
175+
maxFileSize: Optional[int]
176+
minExamplesCount: Optional[int]
177+
maxExamplesCount: Optional[int]
178+
179+
180+
class ValidationOptions(TypedDict):
181+
projectIds: Optional[Iterable[int]]
182+
tmIds: Optional[Iterable[int]]
183+
dateFrom: Optional[str]
184+
dateTo: Optional[str]
185+
maxFileSize: Optional[int]
186+
minExamplesCount: Optional[int]
187+
maxExamplesCount: Optional[int]
188+
189+
190+
191+
class CreateAIPromptFineTuningJobRequest(TypedDict):
192+
dryRun: Optional[bool]
193+
hyperparameters: Optional[HyperParameters]
194+
trainingOptions: TrainingOptions
195+
validationOptions: Optional[ValidationOptions]

0 commit comments

Comments
 (0)