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
146 changes: 146 additions & 0 deletions src/main/java/com/crowdin/client/ai/AIApi.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@
import com.crowdin.client.applications.installations.model.ApplicationInstallationResponseObject;
import com.crowdin.client.core.CrowdinApi;
import com.crowdin.client.core.http.HttpRequestConfig;
import com.crowdin.client.core.http.JsonTransformer;
import com.crowdin.client.core.http.exceptions.HttpBadRequestException;
import com.crowdin.client.core.http.exceptions.HttpException;
import com.crowdin.client.core.http.impl.json.JacksonJsonTransformer;
import com.crowdin.client.core.model.ClientConfig;
import com.crowdin.client.core.model.Credentials;
import com.crowdin.client.core.model.DownloadLink;
Expand All @@ -22,6 +24,8 @@

public class AIApi extends CrowdinApi {

private static final JsonTransformer AI_GATEWAY_JSON_TRANSFORMER = new JacksonJsonTransformer();

public AIApi(Credentials credentials) {
super(credentials);
}
Expand Down Expand Up @@ -927,6 +931,8 @@ public ResponseObject<Map<String, Object>> aiGatewayPatch(final Long userId, fin

/**
* Sends a GET request through the AI gateway and returns the raw provider response body.
* The body must be a JSON object; for other response formats (arrays, scalars, empty bodies) use
* {@link #aiGatewayGetJson}.
*
* @param userId user identifier; should be {@code null} for Crowdin Enterprise
* @param aiProviderId id of AiProvider
Expand All @@ -945,6 +951,8 @@ public Map<String, Object> aiGatewayGetRaw(final Long userId, final Long aiProvi

/**
* Sends a POST request through the AI gateway and returns the raw provider response body.
* The body must be a JSON object; for other response formats (arrays, scalars, empty bodies) use
* {@link #aiGatewayPostJson}.
*
* @param userId user identifier; should be {@code null} for Crowdin Enterprise
* @param aiProviderId id of AiProvider
Expand All @@ -964,6 +972,8 @@ public Map<String, Object> aiGatewayPostRaw(final Long userId, final Long aiProv

/**
* Sends a PUT request through the AI gateway and returns the raw provider response body.
* The body must be a JSON object; for other response formats (arrays, scalars, empty bodies) use
* {@link #aiGatewayPutJson}.
*
* @param userId user identifier; should be {@code null} for Crowdin Enterprise
* @param aiProviderId id of AiProvider
Expand All @@ -983,6 +993,8 @@ public Map<String, Object> aiGatewayPutRaw(final Long userId, final Long aiProvi

/**
* Sends a PATCH request through the AI gateway and returns the raw provider response body.
* The body must be a JSON object; for other response formats (arrays, scalars, empty bodies) use
* {@link #aiGatewayPatchJson}.
*
* @param userId user identifier; should be {@code null} for Crowdin Enterprise
* @param aiProviderId id of AiProvider
Expand All @@ -1002,6 +1014,8 @@ public Map<String, Object> aiGatewayPatchRaw(final Long userId, final Long aiPro

/**
* Sends a DELETE request through the AI gateway and returns the raw provider response body.
* The body must be a JSON object; for other response formats (arrays, scalars, empty bodies) use
* {@link #aiGatewayDeleteJson}.
*
* @param userId user identifier; should be {@code null} for Crowdin Enterprise
* @param aiProviderId id of AiProvider
Expand All @@ -1018,6 +1032,138 @@ public Map<String, Object> aiGatewayDeleteRaw(final Long userId, final Long aiPr
return this.httpClient.delete(url, new HttpRequestConfig(), Map.class);
}

/**
* Sends a GET request through the AI gateway and returns the provider response body as any JSON value.
* <p>
* The AI gateway is a transparent proxy, so the response format depends on the provider and endpoint. The whole
* body is read into memory: streamed responses (e.g. server-sent events) and non-JSON bodies are not supported, and
* an HTTP 204 (No Content) response cannot be read by the default HTTP client.
*
* @param userId user identifier; should be {@code null} for Crowdin Enterprise
* @param aiProviderId id of AiProvider
* @param path downstream endpoint path (e.g. models)
* @return the provider response body parsed as JSON: {@link Map} for an object, {@link List} for an array,
* {@link String}, {@link Number} or {@link Boolean} for a scalar, {@code null} for JSON {@code null} or an empty body
* @throws HttpException if the request fails or the response body is not valid JSON
* @see <ul>
* <li><a href="https://developer.crowdin.com/api/v2/#operation/api.ai.providers.gateway.crowdin.get" target="_blank"><b>API Documentation</b></a></li>
* <li><a href="https://developer.crowdin.com/enterprise/api/v2/#operation/api.ai.providers.gateway.enterprise.get" target="_blank"><b>Enterprise API Documentation</b></a></li>
* </ul>
*/
public Object aiGatewayGetJson(final Long userId, final Long aiProviderId, final String path) throws HttpException, HttpBadRequestException {
String url = getAIPath(userId, "ai/providers/" + aiProviderId + "/gateway/" + path);
return this.parseAiGatewayResponse(this.httpClient.get(url, new HttpRequestConfig(), String.class));
}

/**
* Sends a POST request through the AI gateway and returns the provider response body as any JSON value.
* <p>
* The AI gateway is a transparent proxy, so the response format depends on the provider and endpoint. The whole
* body is read into memory: streamed responses (e.g. server-sent events) and non-JSON bodies are not supported, and
* an HTTP 204 (No Content) response cannot be read by the default HTTP client.
*
* @param userId user identifier; should be {@code null} for Crowdin Enterprise
* @param aiProviderId id of AiProvider
* @param path downstream endpoint path (e.g. models)
* @param request request body sent to the provider
* @return the provider response body parsed as JSON: {@link Map} for an object, {@link List} for an array,
* {@link String}, {@link Number} or {@link Boolean} for a scalar, {@code null} for JSON {@code null} or an empty body
* @throws HttpException if the request fails or the response body is not valid JSON
* @see <ul>
* <li><a href="https://developer.crowdin.com/api/v2/#operation/api.ai.providers.gateway.crowdin.post" target="_blank"><b>API Documentation</b></a></li>
* <li><a href="https://developer.crowdin.com/enterprise/api/v2/#operation/api.ai.providers.gateway.enterprise.post" target="_blank"><b>Enterprise API Documentation</b></a></li>
* </ul>
*/
public Object aiGatewayPostJson(final Long userId, final Long aiProviderId, final String path, final Map<String, Object> request) throws HttpException, HttpBadRequestException {
String url = getAIPath(userId, "ai/providers/" + aiProviderId + "/gateway/" + path);
return this.parseAiGatewayResponse(this.httpClient.post(url, request, new HttpRequestConfig(), String.class));
}

/**
* Sends a PUT request through the AI gateway and returns the provider response body as any JSON value.
* <p>
* The AI gateway is a transparent proxy, so the response format depends on the provider and endpoint. The whole
* body is read into memory: streamed responses (e.g. server-sent events) and non-JSON bodies are not supported, and
* an HTTP 204 (No Content) response cannot be read by the default HTTP client.
*
* @param userId user identifier; should be {@code null} for Crowdin Enterprise
* @param aiProviderId id of AiProvider
* @param path downstream endpoint path (e.g. models)
* @param request request body sent to the provider
* @return the provider response body parsed as JSON: {@link Map} for an object, {@link List} for an array,
* {@link String}, {@link Number} or {@link Boolean} for a scalar, {@code null} for JSON {@code null} or an empty body
* @throws HttpException if the request fails or the response body is not valid JSON
* @see <ul>
* <li><a href="https://developer.crowdin.com/api/v2/#operation/api.ai.providers.gateway.crowdin.put" target="_blank"><b>API Documentation</b></a></li>
* <li><a href="https://developer.crowdin.com/enterprise/api/v2/#operation/api.ai.providers.gateway.enterprise.put" target="_blank"><b>Enterprise API Documentation</b></a></li>
* </ul>
*/
public Object aiGatewayPutJson(final Long userId, final Long aiProviderId, final String path, final Map<String, Object> request) throws HttpException, HttpBadRequestException {
String url = getAIPath(userId, "ai/providers/" + aiProviderId + "/gateway/" + path);
return this.parseAiGatewayResponse(this.httpClient.put(url, request, new HttpRequestConfig(), String.class));
}

/**
* Sends a PATCH request through the AI gateway and returns the provider response body as any JSON value.
* <p>
* The AI gateway is a transparent proxy, so the response format depends on the provider and endpoint. The whole
* body is read into memory: streamed responses (e.g. server-sent events) and non-JSON bodies are not supported, and
* an HTTP 204 (No Content) response cannot be read by the default HTTP client.
*
* @param userId user identifier; should be {@code null} for Crowdin Enterprise
* @param aiProviderId id of AiProvider
* @param path downstream endpoint path (e.g. models)
* @param request request body sent to the provider
* @return the provider response body parsed as JSON: {@link Map} for an object, {@link List} for an array,
* {@link String}, {@link Number} or {@link Boolean} for a scalar, {@code null} for JSON {@code null} or an empty body
* @throws HttpException if the request fails or the response body is not valid JSON
* @see <ul>
* <li><a href="https://developer.crowdin.com/api/v2/#operation/api.ai.providers.gateway.crowdin.patch" target="_blank"><b>API Documentation</b></a></li>
* <li><a href="https://developer.crowdin.com/enterprise/api/v2/#operation/api.ai.providers.gateway.enterprise.patch" target="_blank"><b>Enterprise API Documentation</b></a></li>
* </ul>
*/
public Object aiGatewayPatchJson(final Long userId, final Long aiProviderId, final String path, final Map<String, Object> request) throws HttpException, HttpBadRequestException {
String url = getAIPath(userId, "ai/providers/" + aiProviderId + "/gateway/" + path);
return this.parseAiGatewayResponse(this.httpClient.patch(url, request, new HttpRequestConfig(), String.class));
}

/**
* Sends a DELETE request through the AI gateway and returns the provider response body as any JSON value.
* <p>
* The AI gateway is a transparent proxy, so the response format depends on the provider and endpoint. The whole
* body is read into memory: streamed responses (e.g. server-sent events) and non-JSON bodies are not supported, and
* an HTTP 204 (No Content) response cannot be read by the default HTTP client.
*
* @param userId user identifier; should be {@code null} for Crowdin Enterprise
* @param aiProviderId id of AiProvider
* @param path downstream endpoint path (e.g. models)
* @return the provider response body parsed as JSON: {@link Map} for an object, {@link List} for an array,
* {@link String}, {@link Number} or {@link Boolean} for a scalar, {@code null} for JSON {@code null} or an empty body
* @throws HttpException if the request fails or the response body is not valid JSON
* @see <ul>
* <li><a href="https://developer.crowdin.com/api/v2/#operation/api.ai.providers.gateway.crowdin.delete" target="_blank"><b>API Documentation</b></a></li>
* <li><a href="https://developer.crowdin.com/enterprise/api/v2/#operation/api.ai.providers.gateway.enterprise.delete" target="_blank"><b>Enterprise API Documentation</b></a></li>
* </ul>
*/
public Object aiGatewayDeleteJson(final Long userId, final Long aiProviderId, final String path) throws HttpException, HttpBadRequestException {
String url = getAIPath(userId, "ai/providers/" + aiProviderId + "/gateway/" + path);
return this.parseAiGatewayResponse(this.httpClient.delete(url, new HttpRequestConfig(), String.class));
}

private Object parseAiGatewayResponse(String body) {
if (body == null || body.trim().isEmpty()) {
return null;
}
JsonTransformer jsonTransformer = this.clientConfig != null && this.clientConfig.getJsonTransformer() != null
? this.clientConfig.getJsonTransformer()
: AI_GATEWAY_JSON_TRANSFORMER;
try {
return jsonTransformer.parse(body, Object.class);
} catch (Exception e) {
throw HttpException.fromMessage("AI gateway response is not valid JSON: " + e.getMessage(), body);
}
}

/**
* @param userId user identifier; should be {@code null} for Crowdin Enterprise
* @param request export filters
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,19 @@ public class AiPromptCompletionRequest {
private AiPromptResource resources;

/**
* @deprecated the API expects an array of tools; use {@link #aiTools} instead. This field is not sent to the API.
* Legacy tools map. Values set through {@code setTools(Map)} are only stored and returned by {@code getTools()};
* they are <b>not sent to the API</b>.
*
* @deprecated the API expects an array of tools; use {@code setAiTools(List)} ({@link #aiTools}) instead
*/
@Deprecated
@JsonIgnore
private Map<String, Object> tools;

/**
* Tools available to the model (JSON key {@code tools}); each entry is
* {@code {"tool": {"type": "function", "function": {"name": ..., "description": ..., "parameters": {...}}}}}
*/
@JsonProperty("tools")
private List<AiTool> aiTools;

Expand All @@ -35,7 +42,8 @@ public class AiPromptCompletionRequest {
private Object toolChoice;

/**
* @deprecated use {@link #toolChoice} instead
* @param tool_choice tool choice object
* @deprecated use {@code setToolChoice(Object)} ({@link #toolChoice}) instead
*/
@Deprecated
public void setTool_choice(Map<String, Object> tool_choice) {
Expand All @@ -44,7 +52,8 @@ public void setTool_choice(Map<String, Object> tool_choice) {
}

/**
* @deprecated use {@link #toolChoice} instead
* @return {@link #toolChoice} if it is an object, otherwise {@code null}
* @deprecated use {@code getToolChoice()} ({@link #toolChoice}) instead
*/
@Deprecated
@SuppressWarnings("unchecked")
Expand Down
39 changes: 37 additions & 2 deletions src/main/java/com/crowdin/client/ai/model/Credentials.java
Original file line number Diff line number Diff line change
@@ -1,18 +1,44 @@
package com.crowdin.client.ai.model;

import lombok.Data;
import lombok.ToString;

import java.util.List;
import java.util.Map;

/**
* AI provider credentials. Superset of the credential fields of all provider types;
* set only the fields required by the chosen provider type.
* AI provider credentials. Superset of the credential fields of all provider types; set only the fields of the
* chosen provider type (the SDK does not validate the combination, the API does).
* <p>
* Supported provider types and their credential fields (required fields in bold):
* <ul>
* <li>{@code open_ai}, {@code google_gemini_ai_studio}, {@code mistral_ai}, {@code anthropic}, {@code x_ai},
* {@code deepseek}: <b>apiKey</b>, baseUrl, headers, sendCustomHeadersOnly</li>
* <li>{@code azure_open_ai}: <b>resourceName</b>, <b>apiKey</b>, <b>deploymentName</b>, <b>apiVersion</b>, baseUrl,
* headers, sendCustomHeadersOnly</li>
* <li>{@code google_gemini} (Vertex AI): <b>project</b>, <b>region</b>, serviceAccountKey or
* workloadIdentityFederationAudience (one of them is required), serviceAccountEmail, baseUrl, headers,
* sendCustomHeadersOnly</li>
* <li>{@code watsonx}: <b>apiKey</b>, <b>projectId</b>, <b>region</b>, baseUrl, headers, sendCustomHeadersOnly</li>
* <li>{@code microsoft_foundry}: <b>deployments</b> (each with <b>deploymentName</b>, <b>targetUri</b>,
* <b>apiKey</b>), headers, sendCustomHeadersOnly</li>
* <li>{@code custom_ai}: <b>identifier</b>, <b>key</b></li>
* </ul>
* Secret values ({@code apiKey}, {@code headers}, {@code serviceAccountKey}, {@code key} and the deployments' API keys)
* are sent to the API as usual but are excluded from {@code toString()}.
*/
@Data
public class Credentials {
@ToString.Exclude
private String apiKey;
/**
* Override the provider API base URL (e.g. a self-hosted proxy or third-party gateway)
*/
private String baseUrl;
/**
* Custom HTTP headers sent with every request to the provider; may contain authorization values
*/
@ToString.Exclude
private Map<String, String> headers;
private Boolean sendCustomHeadersOnly;

Expand All @@ -28,6 +54,10 @@ public class Credentials {
*/
private String project;
private String region;
/**
* Google Gemini (Vertex AI) service account key (contains a private key)
*/
@ToString.Exclude
private Map<String, Object> serviceAccountKey;
private String workloadIdentityFederationAudience;
private String serviceAccountEmail;
Expand All @@ -46,12 +76,17 @@ public class Credentials {
* Custom AI
*/
private String identifier;
@ToString.Exclude
private String key;

/**
* Microsoft Foundry deployment
*/
@Data
public static class Deployment {
private String deploymentName;
private String targetUri;
@ToString.Exclude
private String apiKey;
}
}
16 changes: 13 additions & 3 deletions src/main/java/com/crowdin/client/ai/model/FineTuningJob.java
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ public static class Hyperparameters {
private Long nEpochs;

/**
* @deprecated use {@link #learningRateMultiplierValue} instead
* @param learningRateMultiplier learning rate multiplier as a whole number
* @deprecated use {@code setLearningRateMultiplierValue(Double)} ({@link #learningRateMultiplierValue}) instead
*/
@Deprecated
public void setLearningRateMultiplier(Long learningRateMultiplier) {
Expand All @@ -55,11 +56,20 @@ public void setLearningRateMultiplier(Long learningRateMultiplier) {
}

/**
* @deprecated use {@link #learningRateMultiplierValue} instead
* Legacy whole-number view of {@link #learningRateMultiplierValue}. A finite value within the {@link Long}
* range is truncated towards zero (e.g. {@code 1.8} &rarr; {@code 1}, {@code -0.5} &rarr; {@code 0}); a value
* outside the {@link Long} range, {@code NaN} or an infinite value is returned as {@code null}.
*
* @return learning rate multiplier truncated to a whole number, or {@code null}
* @deprecated use {@code getLearningRateMultiplierValue()} ({@link #learningRateMultiplierValue}) instead
*/
@Deprecated
public Long getLearningRateMultiplier() {
return this.learningRateMultiplierValue != null ? this.learningRateMultiplierValue.longValue() : null;
Double value = this.learningRateMultiplierValue;
if (value == null || value.isNaN() || value.isInfinite() || value < -0x1p63 || value >= 0x1p63) {
return null;
}
return value.longValue();
}
}

Expand Down
Loading
Loading