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
21 changes: 21 additions & 0 deletions crowdin_api/api_resources/ai/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,3 +87,24 @@ class AiReportFormat(Enum):
class ListSupportedAiModelsOrderBy(Enum):
KNOWLEDGE_CUTOFF = "knowledgeCutoff"
RELEASE_DATE = "releaseDate"


class AiRequestLogStatus(Enum):
PENDING = "pending"
SUCCESS = "success"
ERROR = "error"
TIMEOUT = "timeout"


class AiRequestLogSourceAction(Enum):
AI_PROXY = "ai_proxy"
AI_GATEWAY = "ai_gateway"
AI_TRANSLATE_STRINGS = "ai_translate_strings"
AI_FILE_TRANSLATE = "ai_file_translate"
AI_PROMPT_COMPLETION = "ai_prompt_completion"
PRE_TRANSLATE_MANUAL = "pre_translate:manual"
PRE_TRANSLATE_WORKFLOW = "pre_translate:workflow"
AI_ALIGNMENT = "ai_alignment"
QA_CHECK = "qa_check"
AI_SUGGESTION = "ai_suggestion"
ADVISOR = "advisor"
110 changes: 110 additions & 0 deletions crowdin_api/api_resources/ai/resource.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
from datetime import datetime
from typing import Dict, Iterable, Optional, Union

from crowdin_api.api_resources.abstract.resources import BaseResource
from crowdin_api.api_resources.ai.enums import (
AIPromptAction,
AiPromptFineTuningJobStatus,
AiRequestLogSourceAction,
AiRequestLogStatus,
AIProviderType,
)
from crowdin_api.api_resources.ai.types import (
Expand Down Expand Up @@ -626,6 +629,60 @@ def download_ai_report(
path=self.get_ai_reports_path(user_id, ai_report_id) + "/download",
)

def get_ai_request_logs_path(self, user_id: int):
return f"users/{user_id}/ai/request-logs"

def list_ai_request_logs(
self,
user_id: int,
request_id: Optional[str] = None,
project_id: Optional[int] = None,
request_user_id: Optional[int] = None,
ai_provider_id: Optional[int] = None,
model: Optional[str] = None,
source_action: Optional[AiRequestLogSourceAction] = None,
prompt_action: Optional[str] = None,
statuses: Optional[Iterable[AiRequestLogStatus]] = None,
system_credentials: Optional[bool] = None,
is_auto_triggered: Optional[bool] = None,
token_name: Optional[str] = None,
oauth_client_id: Optional[str] = None,
created_after: Optional[datetime] = None,
created_before: Optional[datetime] = None,
limit: Optional[int] = None,
offset: Optional[int] = None,
):
"""
List AI Request Logs

Link to documentation:
https://developer.crowdin.com/api/v2/#operation/api.ai.requestLogs.getMany
"""

params = {
"requestId": request_id,
"projectId": project_id,
"userId": request_user_id,
"aiProviderId": ai_provider_id,
"model": model,
"sourceAction": source_action,
"promptAction": prompt_action,
"statuses": convert_enum_collection_to_string_if_exists(statuses),
"systemCredentials": system_credentials,
"isAutoTriggered": is_auto_triggered,
"tokenName": token_name,
"oauthClientId": oauth_client_id,
"createdAfter": created_after,
"createdBefore": created_before,
}
params.update(self.get_page_params(limit=limit, offset=offset))

return self._get_entire_data(
method="get",
path=self.get_ai_request_logs_path(user_id),
params=params,
)

def get_ai_settings_path(self, user_id: int):
return f"users/{user_id}/ai/settings"

Expand Down Expand Up @@ -1467,6 +1524,59 @@ def download_ai_report(
path=self.get_ai_reports_path(ai_report_id) + "/download",
)

def get_ai_request_logs_path(self):
return "ai/request-logs"

def list_ai_request_logs(
self,
request_id: Optional[str] = None,
project_id: Optional[int] = None,
request_user_id: Optional[int] = None,
ai_provider_id: Optional[int] = None,
model: Optional[str] = None,
source_action: Optional[AiRequestLogSourceAction] = None,
prompt_action: Optional[str] = None,
statuses: Optional[Iterable[AiRequestLogStatus]] = None,
system_credentials: Optional[bool] = None,
is_auto_triggered: Optional[bool] = None,
token_name: Optional[str] = None,
oauth_client_id: Optional[str] = None,
created_after: Optional[datetime] = None,
created_before: Optional[datetime] = None,
limit: Optional[int] = None,
offset: Optional[int] = None,
):
"""
List AI Request Logs

Link to documentation:
https://developer.crowdin.com/enterprise/api/v2/#operation/api.ai.requestLogs.getMany
"""

params = {
"requestId": request_id,
"projectId": project_id,
"userId": request_user_id,
"aiProviderId": ai_provider_id,
"model": model,
"sourceAction": source_action,
"promptAction": prompt_action,
"statuses": convert_enum_collection_to_string_if_exists(statuses),
"systemCredentials": system_credentials,
"isAutoTriggered": is_auto_triggered,
"tokenName": token_name,
"oauthClientId": oauth_client_id,
"createdAfter": created_after,
"createdBefore": created_before,
}
params.update(self.get_page_params(limit=limit, offset=offset))

return self._get_entire_data(
method="get",
path=self.get_ai_request_logs_path(),
params=params,
)

def get_ai_settings(self):
"""
Get AI Settings
Expand Down
169 changes: 169 additions & 0 deletions crowdin_api/api_resources/ai/tests/test_ai_resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
from crowdin_api.api_resources.ai.enums import (
AIPromptAction,
AiPromptFineTuningJobStatus,
AiRequestLogSourceAction,
AiRequestLogStatus,
AIProviderType,
AiReportFormat,
AiToolType,
Expand Down Expand Up @@ -1132,6 +1134,90 @@ def test_download_ai_report(self, m_request, base_absolut_url):
path=f"users/{user_id}/ai/reports/{ai_report_id}/download",
)

def test_get_ai_request_logs_path(self, base_absolut_url):
resource = self.get_resource(base_absolut_url)
assert resource.get_ai_request_logs_path(1) == "users/1/ai/request-logs"

@pytest.mark.parametrize(
"incoming_data, request_params",
(
(
{},
{
"requestId": None,
"projectId": None,
"userId": None,
"aiProviderId": None,
"model": None,
"sourceAction": None,
"promptAction": None,
"statuses": None,
"systemCredentials": None,
"isAutoTriggered": None,
"tokenName": None,
"oauthClientId": None,
"createdAfter": None,
"createdBefore": None,
"limit": 25,
"offset": 0,
},
),
(
{
"request_id": "9d3b1c4e-2f3a-4b5c-8d6e-7f8a9b0c1d2e",
"project_id": 8,
"request_user_id": 42,
"ai_provider_id": 3,
"model": "gpt-5.6-sol",
"source_action": AiRequestLogSourceAction.AI_GATEWAY,
"prompt_action": "pre_translate",
"statuses": [AiRequestLogStatus.PENDING, AiRequestLogStatus.SUCCESS],
"system_credentials": True,
"is_auto_triggered": False,
"token_name": "CI token",
"oauth_client_id": "gpbccUFxAKZDrLm5Nq8t",
"created_after": datetime(2026, 1, 1, tzinfo=timezone.utc),
"created_before": datetime(2026, 1, 2, tzinfo=timezone.utc),
"limit": 10,
"offset": 20,
},
{
"requestId": "9d3b1c4e-2f3a-4b5c-8d6e-7f8a9b0c1d2e",
"projectId": 8,
"userId": 42,
"aiProviderId": 3,
"model": "gpt-5.6-sol",
"sourceAction": AiRequestLogSourceAction.AI_GATEWAY,
"promptAction": "pre_translate",
"statuses": "pending,success",
"systemCredentials": True,
"isAutoTriggered": False,
"tokenName": "CI token",
"oauthClientId": "gpbccUFxAKZDrLm5Nq8t",
"createdAfter": datetime(2026, 1, 1, tzinfo=timezone.utc),
"createdBefore": datetime(2026, 1, 2, tzinfo=timezone.utc),
"limit": 10,
"offset": 20,
},
),
),
)
@mock.patch("crowdin_api.requester.APIRequester.request")
def test_list_ai_request_logs(
self, m_request, incoming_data, request_params, base_absolut_url
):
m_request.return_value = "response"

user_id = 1
resource = self.get_resource(base_absolut_url)
assert resource.list_ai_request_logs(user_id, **incoming_data) == "response"

m_request.assert_called_once_with(
method="get",
path=resource.get_ai_request_logs_path(user_id),
params=request_params,
)

@mock.patch("crowdin_api.requester.APIRequester.request")
def test_get_ai_settings(self, m_request, base_absolut_url):
m_request.return_value = "response"
Expand Down Expand Up @@ -2594,6 +2680,89 @@ def test_download_ai_report(self, m_request, base_absolut_url):
path=f"ai/reports/{ai_report_id}/download",
)

def test_get_ai_request_logs_path(self, base_absolut_url):
resource = self.get_resource(base_absolut_url)
assert resource.get_ai_request_logs_path() == "ai/request-logs"

@pytest.mark.parametrize(
"incoming_data, request_params",
(
(
{},
{
"requestId": None,
"projectId": None,
"userId": None,
"aiProviderId": None,
"model": None,
"sourceAction": None,
"promptAction": None,
"statuses": None,
"systemCredentials": None,
"isAutoTriggered": None,
"tokenName": None,
"oauthClientId": None,
"createdAfter": None,
"createdBefore": None,
"limit": 25,
"offset": 0,
},
),
(
{
"request_id": "9d3b1c4e-2f3a-4b5c-8d6e-7f8a9b0c1d2e",
"project_id": 8,
"request_user_id": 42,
"ai_provider_id": 3,
"model": "gpt-5.6-sol",
"source_action": AiRequestLogSourceAction.AI_GATEWAY,
"prompt_action": "pre_translate",
"statuses": [AiRequestLogStatus.PENDING, AiRequestLogStatus.SUCCESS],
"system_credentials": True,
"is_auto_triggered": False,
"token_name": "CI token",
"oauth_client_id": "gpbccUFxAKZDrLm5Nq8t",
"created_after": datetime(2026, 1, 1, tzinfo=timezone.utc),
"created_before": datetime(2026, 1, 2, tzinfo=timezone.utc),
"limit": 10,
"offset": 20,
},
{
"requestId": "9d3b1c4e-2f3a-4b5c-8d6e-7f8a9b0c1d2e",
"projectId": 8,
"userId": 42,
"aiProviderId": 3,
"model": "gpt-5.6-sol",
"sourceAction": AiRequestLogSourceAction.AI_GATEWAY,
"promptAction": "pre_translate",
"statuses": "pending,success",
"systemCredentials": True,
"isAutoTriggered": False,
"tokenName": "CI token",
"oauthClientId": "gpbccUFxAKZDrLm5Nq8t",
"createdAfter": datetime(2026, 1, 1, tzinfo=timezone.utc),
"createdBefore": datetime(2026, 1, 2, tzinfo=timezone.utc),
"limit": 10,
"offset": 20,
},
),
),
)
@mock.patch("crowdin_api.requester.APIRequester.request")
def test_list_ai_request_logs(
self, m_request, incoming_data, request_params, base_absolut_url
):
m_request.return_value = "response"

resource = self.get_resource(base_absolut_url)
assert resource.list_ai_request_logs(**incoming_data) == "response"

m_request.assert_called_once_with(
method="get",
path=resource.get_ai_request_logs_path(),
params=request_params,
)

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