Skip to content

Commit f8a9cd0

Browse files
authored
feat: add support for List AI Request Logs (#281)
1 parent aa3e356 commit f8a9cd0

3 files changed

Lines changed: 300 additions & 0 deletions

File tree

‎crowdin_api/api_resources/ai/enums.py‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,3 +87,24 @@ class AiReportFormat(Enum):
8787
class ListSupportedAiModelsOrderBy(Enum):
8888
KNOWLEDGE_CUTOFF = "knowledgeCutoff"
8989
RELEASE_DATE = "releaseDate"
90+
91+
92+
class AiRequestLogStatus(Enum):
93+
PENDING = "pending"
94+
SUCCESS = "success"
95+
ERROR = "error"
96+
TIMEOUT = "timeout"
97+
98+
99+
class AiRequestLogSourceAction(Enum):
100+
AI_PROXY = "ai_proxy"
101+
AI_GATEWAY = "ai_gateway"
102+
AI_TRANSLATE_STRINGS = "ai_translate_strings"
103+
AI_FILE_TRANSLATE = "ai_file_translate"
104+
AI_PROMPT_COMPLETION = "ai_prompt_completion"
105+
PRE_TRANSLATE_MANUAL = "pre_translate:manual"
106+
PRE_TRANSLATE_WORKFLOW = "pre_translate:workflow"
107+
AI_ALIGNMENT = "ai_alignment"
108+
QA_CHECK = "qa_check"
109+
AI_SUGGESTION = "ai_suggestion"
110+
ADVISOR = "advisor"

‎crowdin_api/api_resources/ai/resource.py‎

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
1+
from datetime import datetime
12
from typing import Dict, Iterable, Optional, Union
23

34
from crowdin_api.api_resources.abstract.resources import BaseResource
45
from crowdin_api.api_resources.ai.enums import (
56
AIPromptAction,
67
AiPromptFineTuningJobStatus,
8+
AiRequestLogSourceAction,
9+
AiRequestLogStatus,
710
AIProviderType,
811
)
912
from crowdin_api.api_resources.ai.types import (
@@ -626,6 +629,60 @@ def download_ai_report(
626629
path=self.get_ai_reports_path(user_id, ai_report_id) + "/download",
627630
)
628631

632+
def get_ai_request_logs_path(self, user_id: int):
633+
return f"users/{user_id}/ai/request-logs"
634+
635+
def list_ai_request_logs(
636+
self,
637+
user_id: int,
638+
request_id: Optional[str] = None,
639+
project_id: Optional[int] = None,
640+
request_user_id: Optional[int] = None,
641+
ai_provider_id: Optional[int] = None,
642+
model: Optional[str] = None,
643+
source_action: Optional[AiRequestLogSourceAction] = None,
644+
prompt_action: Optional[str] = None,
645+
statuses: Optional[Iterable[AiRequestLogStatus]] = None,
646+
system_credentials: Optional[bool] = None,
647+
is_auto_triggered: Optional[bool] = None,
648+
token_name: Optional[str] = None,
649+
oauth_client_id: Optional[str] = None,
650+
created_after: Optional[datetime] = None,
651+
created_before: Optional[datetime] = None,
652+
limit: Optional[int] = None,
653+
offset: Optional[int] = None,
654+
):
655+
"""
656+
List AI Request Logs
657+
658+
Link to documentation:
659+
https://developer.crowdin.com/api/v2/#operation/api.ai.requestLogs.getMany
660+
"""
661+
662+
params = {
663+
"requestId": request_id,
664+
"projectId": project_id,
665+
"userId": request_user_id,
666+
"aiProviderId": ai_provider_id,
667+
"model": model,
668+
"sourceAction": source_action,
669+
"promptAction": prompt_action,
670+
"statuses": convert_enum_collection_to_string_if_exists(statuses),
671+
"systemCredentials": system_credentials,
672+
"isAutoTriggered": is_auto_triggered,
673+
"tokenName": token_name,
674+
"oauthClientId": oauth_client_id,
675+
"createdAfter": created_after,
676+
"createdBefore": created_before,
677+
}
678+
params.update(self.get_page_params(limit=limit, offset=offset))
679+
680+
return self._get_entire_data(
681+
method="get",
682+
path=self.get_ai_request_logs_path(user_id),
683+
params=params,
684+
)
685+
629686
def get_ai_settings_path(self, user_id: int):
630687
return f"users/{user_id}/ai/settings"
631688

@@ -1467,6 +1524,59 @@ def download_ai_report(
14671524
path=self.get_ai_reports_path(ai_report_id) + "/download",
14681525
)
14691526

1527+
def get_ai_request_logs_path(self):
1528+
return "ai/request-logs"
1529+
1530+
def list_ai_request_logs(
1531+
self,
1532+
request_id: Optional[str] = None,
1533+
project_id: Optional[int] = None,
1534+
request_user_id: Optional[int] = None,
1535+
ai_provider_id: Optional[int] = None,
1536+
model: Optional[str] = None,
1537+
source_action: Optional[AiRequestLogSourceAction] = None,
1538+
prompt_action: Optional[str] = None,
1539+
statuses: Optional[Iterable[AiRequestLogStatus]] = None,
1540+
system_credentials: Optional[bool] = None,
1541+
is_auto_triggered: Optional[bool] = None,
1542+
token_name: Optional[str] = None,
1543+
oauth_client_id: Optional[str] = None,
1544+
created_after: Optional[datetime] = None,
1545+
created_before: Optional[datetime] = None,
1546+
limit: Optional[int] = None,
1547+
offset: Optional[int] = None,
1548+
):
1549+
"""
1550+
List AI Request Logs
1551+
1552+
Link to documentation:
1553+
https://developer.crowdin.com/enterprise/api/v2/#operation/api.ai.requestLogs.getMany
1554+
"""
1555+
1556+
params = {
1557+
"requestId": request_id,
1558+
"projectId": project_id,
1559+
"userId": request_user_id,
1560+
"aiProviderId": ai_provider_id,
1561+
"model": model,
1562+
"sourceAction": source_action,
1563+
"promptAction": prompt_action,
1564+
"statuses": convert_enum_collection_to_string_if_exists(statuses),
1565+
"systemCredentials": system_credentials,
1566+
"isAutoTriggered": is_auto_triggered,
1567+
"tokenName": token_name,
1568+
"oauthClientId": oauth_client_id,
1569+
"createdAfter": created_after,
1570+
"createdBefore": created_before,
1571+
}
1572+
params.update(self.get_page_params(limit=limit, offset=offset))
1573+
1574+
return self._get_entire_data(
1575+
method="get",
1576+
path=self.get_ai_request_logs_path(),
1577+
params=params,
1578+
)
1579+
14701580
def get_ai_settings(self):
14711581
"""
14721582
Get AI Settings

‎crowdin_api/api_resources/ai/tests/test_ai_resources.py‎

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
from crowdin_api.api_resources.ai.enums import (
66
AIPromptAction,
77
AiPromptFineTuningJobStatus,
8+
AiRequestLogSourceAction,
9+
AiRequestLogStatus,
810
AIProviderType,
911
AiReportFormat,
1012
AiToolType,
@@ -1132,6 +1134,90 @@ def test_download_ai_report(self, m_request, base_absolut_url):
11321134
path=f"users/{user_id}/ai/reports/{ai_report_id}/download",
11331135
)
11341136

1137+
def test_get_ai_request_logs_path(self, base_absolut_url):
1138+
resource = self.get_resource(base_absolut_url)
1139+
assert resource.get_ai_request_logs_path(1) == "users/1/ai/request-logs"
1140+
1141+
@pytest.mark.parametrize(
1142+
"incoming_data, request_params",
1143+
(
1144+
(
1145+
{},
1146+
{
1147+
"requestId": None,
1148+
"projectId": None,
1149+
"userId": None,
1150+
"aiProviderId": None,
1151+
"model": None,
1152+
"sourceAction": None,
1153+
"promptAction": None,
1154+
"statuses": None,
1155+
"systemCredentials": None,
1156+
"isAutoTriggered": None,
1157+
"tokenName": None,
1158+
"oauthClientId": None,
1159+
"createdAfter": None,
1160+
"createdBefore": None,
1161+
"limit": 25,
1162+
"offset": 0,
1163+
},
1164+
),
1165+
(
1166+
{
1167+
"request_id": "9d3b1c4e-2f3a-4b5c-8d6e-7f8a9b0c1d2e",
1168+
"project_id": 8,
1169+
"request_user_id": 42,
1170+
"ai_provider_id": 3,
1171+
"model": "gpt-5.6-sol",
1172+
"source_action": AiRequestLogSourceAction.AI_GATEWAY,
1173+
"prompt_action": "pre_translate",
1174+
"statuses": [AiRequestLogStatus.PENDING, AiRequestLogStatus.SUCCESS],
1175+
"system_credentials": True,
1176+
"is_auto_triggered": False,
1177+
"token_name": "CI token",
1178+
"oauth_client_id": "gpbccUFxAKZDrLm5Nq8t",
1179+
"created_after": datetime(2026, 1, 1, tzinfo=timezone.utc),
1180+
"created_before": datetime(2026, 1, 2, tzinfo=timezone.utc),
1181+
"limit": 10,
1182+
"offset": 20,
1183+
},
1184+
{
1185+
"requestId": "9d3b1c4e-2f3a-4b5c-8d6e-7f8a9b0c1d2e",
1186+
"projectId": 8,
1187+
"userId": 42,
1188+
"aiProviderId": 3,
1189+
"model": "gpt-5.6-sol",
1190+
"sourceAction": AiRequestLogSourceAction.AI_GATEWAY,
1191+
"promptAction": "pre_translate",
1192+
"statuses": "pending,success",
1193+
"systemCredentials": True,
1194+
"isAutoTriggered": False,
1195+
"tokenName": "CI token",
1196+
"oauthClientId": "gpbccUFxAKZDrLm5Nq8t",
1197+
"createdAfter": datetime(2026, 1, 1, tzinfo=timezone.utc),
1198+
"createdBefore": datetime(2026, 1, 2, tzinfo=timezone.utc),
1199+
"limit": 10,
1200+
"offset": 20,
1201+
},
1202+
),
1203+
),
1204+
)
1205+
@mock.patch("crowdin_api.requester.APIRequester.request")
1206+
def test_list_ai_request_logs(
1207+
self, m_request, incoming_data, request_params, base_absolut_url
1208+
):
1209+
m_request.return_value = "response"
1210+
1211+
user_id = 1
1212+
resource = self.get_resource(base_absolut_url)
1213+
assert resource.list_ai_request_logs(user_id, **incoming_data) == "response"
1214+
1215+
m_request.assert_called_once_with(
1216+
method="get",
1217+
path=resource.get_ai_request_logs_path(user_id),
1218+
params=request_params,
1219+
)
1220+
11351221
@mock.patch("crowdin_api.requester.APIRequester.request")
11361222
def test_get_ai_settings(self, m_request, base_absolut_url):
11371223
m_request.return_value = "response"
@@ -2594,6 +2680,89 @@ def test_download_ai_report(self, m_request, base_absolut_url):
25942680
path=f"ai/reports/{ai_report_id}/download",
25952681
)
25962682

2683+
def test_get_ai_request_logs_path(self, base_absolut_url):
2684+
resource = self.get_resource(base_absolut_url)
2685+
assert resource.get_ai_request_logs_path() == "ai/request-logs"
2686+
2687+
@pytest.mark.parametrize(
2688+
"incoming_data, request_params",
2689+
(
2690+
(
2691+
{},
2692+
{
2693+
"requestId": None,
2694+
"projectId": None,
2695+
"userId": None,
2696+
"aiProviderId": None,
2697+
"model": None,
2698+
"sourceAction": None,
2699+
"promptAction": None,
2700+
"statuses": None,
2701+
"systemCredentials": None,
2702+
"isAutoTriggered": None,
2703+
"tokenName": None,
2704+
"oauthClientId": None,
2705+
"createdAfter": None,
2706+
"createdBefore": None,
2707+
"limit": 25,
2708+
"offset": 0,
2709+
},
2710+
),
2711+
(
2712+
{
2713+
"request_id": "9d3b1c4e-2f3a-4b5c-8d6e-7f8a9b0c1d2e",
2714+
"project_id": 8,
2715+
"request_user_id": 42,
2716+
"ai_provider_id": 3,
2717+
"model": "gpt-5.6-sol",
2718+
"source_action": AiRequestLogSourceAction.AI_GATEWAY,
2719+
"prompt_action": "pre_translate",
2720+
"statuses": [AiRequestLogStatus.PENDING, AiRequestLogStatus.SUCCESS],
2721+
"system_credentials": True,
2722+
"is_auto_triggered": False,
2723+
"token_name": "CI token",
2724+
"oauth_client_id": "gpbccUFxAKZDrLm5Nq8t",
2725+
"created_after": datetime(2026, 1, 1, tzinfo=timezone.utc),
2726+
"created_before": datetime(2026, 1, 2, tzinfo=timezone.utc),
2727+
"limit": 10,
2728+
"offset": 20,
2729+
},
2730+
{
2731+
"requestId": "9d3b1c4e-2f3a-4b5c-8d6e-7f8a9b0c1d2e",
2732+
"projectId": 8,
2733+
"userId": 42,
2734+
"aiProviderId": 3,
2735+
"model": "gpt-5.6-sol",
2736+
"sourceAction": AiRequestLogSourceAction.AI_GATEWAY,
2737+
"promptAction": "pre_translate",
2738+
"statuses": "pending,success",
2739+
"systemCredentials": True,
2740+
"isAutoTriggered": False,
2741+
"tokenName": "CI token",
2742+
"oauthClientId": "gpbccUFxAKZDrLm5Nq8t",
2743+
"createdAfter": datetime(2026, 1, 1, tzinfo=timezone.utc),
2744+
"createdBefore": datetime(2026, 1, 2, tzinfo=timezone.utc),
2745+
"limit": 10,
2746+
"offset": 20,
2747+
},
2748+
),
2749+
),
2750+
)
2751+
@mock.patch("crowdin_api.requester.APIRequester.request")
2752+
def test_list_ai_request_logs(
2753+
self, m_request, incoming_data, request_params, base_absolut_url
2754+
):
2755+
m_request.return_value = "response"
2756+
2757+
resource = self.get_resource(base_absolut_url)
2758+
assert resource.list_ai_request_logs(**incoming_data) == "response"
2759+
2760+
m_request.assert_called_once_with(
2761+
method="get",
2762+
path=resource.get_ai_request_logs_path(),
2763+
params=request_params,
2764+
)
2765+
25972766
@mock.patch("crowdin_api.requester.APIRequester.request")
25982767
def test_get_ai_settings(self, m_request, base_absolut_url):
25992768
m_request.return_value = "response"

0 commit comments

Comments
 (0)