Browse Source

[ML] Remove Voyageai request manager classes (#124512)

* Removing voyage request managers

* Fixing tests
Jonathan Buttner 7 months ago
parent
commit
1bee2cc3cc
15 changed files with 183 additions and 434 deletions
  1. 36 6
      x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/action/voyageai/VoyageAIActionCreator.java
  2. 0 57
      x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/http/sender/VoyageAIEmbeddingsRequestManager.java
  3. 0 54
      x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/http/sender/VoyageAIRequestManager.java
  4. 0 56
      x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/http/sender/VoyageAIRerankRequestManager.java
  5. 4 0
      x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/request/RequestUtils.java
  6. 15 25
      x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/request/voyageai/VoyageAIEmbeddingsRequest.java
  7. 3 3
      x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/request/voyageai/VoyageAIRequest.java
  8. 8 16
      x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/request/voyageai/VoyageAIRerankRequest.java
  9. 0 35
      x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/voyageai/VoyageAIAccount.java
  10. 41 28
      x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/voyageai/VoyageAIModel.java
  11. 21 21
      x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/voyageai/embeddings/VoyageAIEmbeddingsModel.java
  12. 21 20
      x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/voyageai/rerank/VoyageAIRerankModel.java
  13. 12 37
      x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/external/action/voyageai/VoyageAIEmbeddingsActionTests.java
  14. 4 9
      x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/external/request/voyageai/VoyageAIRequestTests.java
  15. 18 67
      x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/voyageai/VoyageAIServiceTests.java

+ 36 - 6
x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/action/voyageai/VoyageAIActionCreator.java

@@ -10,9 +10,16 @@ package org.elasticsearch.xpack.inference.external.action.voyageai;
 import org.elasticsearch.inference.InputType;
 import org.elasticsearch.xpack.inference.external.action.ExecutableAction;
 import org.elasticsearch.xpack.inference.external.action.SenderExecutableAction;
+import org.elasticsearch.xpack.inference.external.http.retry.ResponseHandler;
+import org.elasticsearch.xpack.inference.external.http.sender.DocumentsOnlyInput;
+import org.elasticsearch.xpack.inference.external.http.sender.GenericRequestManager;
+import org.elasticsearch.xpack.inference.external.http.sender.QueryAndDocsInputs;
 import org.elasticsearch.xpack.inference.external.http.sender.Sender;
-import org.elasticsearch.xpack.inference.external.http.sender.VoyageAIEmbeddingsRequestManager;
-import org.elasticsearch.xpack.inference.external.http.sender.VoyageAIRerankRequestManager;
+import org.elasticsearch.xpack.inference.external.request.voyageai.VoyageAIEmbeddingsRequest;
+import org.elasticsearch.xpack.inference.external.request.voyageai.VoyageAIRerankRequest;
+import org.elasticsearch.xpack.inference.external.response.voyageai.VoyageAIEmbeddingsResponseEntity;
+import org.elasticsearch.xpack.inference.external.response.voyageai.VoyageAIRerankResponseEntity;
+import org.elasticsearch.xpack.inference.external.voyageai.VoyageAIResponseHandler;
 import org.elasticsearch.xpack.inference.services.ServiceComponents;
 import org.elasticsearch.xpack.inference.services.voyageai.embeddings.VoyageAIEmbeddingsModel;
 import org.elasticsearch.xpack.inference.services.voyageai.rerank.VoyageAIRerankModel;
@@ -26,6 +33,15 @@ import static org.elasticsearch.xpack.inference.external.action.ActionUtils.cons
  * Provides a way to construct an {@link ExecutableAction} using the visitor pattern based on the voyageai model type.
  */
 public class VoyageAIActionCreator implements VoyageAIActionVisitor {
+    public static final ResponseHandler EMBEDDINGS_HANDLER = new VoyageAIResponseHandler(
+        "voyageai text embedding",
+        VoyageAIEmbeddingsResponseEntity::fromResponse
+    );
+    static final ResponseHandler RERANK_HANDLER = new VoyageAIResponseHandler(
+        "voyageai rerank",
+        (request, response) -> VoyageAIRerankResponseEntity.fromResponse(response)
+    );
+
     private final Sender sender;
     private final ServiceComponents serviceComponents;
 
@@ -37,16 +53,30 @@ public class VoyageAIActionCreator implements VoyageAIActionVisitor {
     @Override
     public ExecutableAction create(VoyageAIEmbeddingsModel model, Map<String, Object> taskSettings, InputType inputType) {
         var overriddenModel = VoyageAIEmbeddingsModel.of(model, taskSettings, inputType);
+        var manager = new GenericRequestManager<>(
+            serviceComponents.threadPool(),
+            overriddenModel,
+            EMBEDDINGS_HANDLER,
+            (documentsOnlyInput) -> new VoyageAIEmbeddingsRequest(documentsOnlyInput.getInputs(), overriddenModel),
+            DocumentsOnlyInput.class
+        );
+
         var failedToSendRequestErrorMessage = constructFailedToSendRequestMessage("VoyageAI embeddings");
-        var requestCreator = VoyageAIEmbeddingsRequestManager.of(overriddenModel, serviceComponents.threadPool());
-        return new SenderExecutableAction(sender, requestCreator, failedToSendRequestErrorMessage);
+        return new SenderExecutableAction(sender, manager, failedToSendRequestErrorMessage);
     }
 
     @Override
     public ExecutableAction create(VoyageAIRerankModel model, Map<String, Object> taskSettings) {
         var overriddenModel = VoyageAIRerankModel.of(model, taskSettings);
+        var manager = new GenericRequestManager<>(
+            serviceComponents.threadPool(),
+            overriddenModel,
+            RERANK_HANDLER,
+            (rerankInput) -> new VoyageAIRerankRequest(rerankInput.getQuery(), rerankInput.getChunks(), model),
+            QueryAndDocsInputs.class
+        );
+
         var failedToSendRequestErrorMessage = constructFailedToSendRequestMessage("VoyageAI rerank");
-        var requestCreator = VoyageAIRerankRequestManager.of(overriddenModel, serviceComponents.threadPool());
-        return new SenderExecutableAction(sender, requestCreator, failedToSendRequestErrorMessage);
+        return new SenderExecutableAction(sender, manager, failedToSendRequestErrorMessage);
     }
 }

+ 0 - 57
x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/http/sender/VoyageAIEmbeddingsRequestManager.java

@@ -1,57 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-package org.elasticsearch.xpack.inference.external.http.sender;
-
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
-import org.elasticsearch.action.ActionListener;
-import org.elasticsearch.inference.InferenceServiceResults;
-import org.elasticsearch.threadpool.ThreadPool;
-import org.elasticsearch.xpack.inference.external.http.retry.RequestSender;
-import org.elasticsearch.xpack.inference.external.http.retry.ResponseHandler;
-import org.elasticsearch.xpack.inference.external.request.voyageai.VoyageAIEmbeddingsRequest;
-import org.elasticsearch.xpack.inference.external.response.voyageai.VoyageAIEmbeddingsResponseEntity;
-import org.elasticsearch.xpack.inference.external.voyageai.VoyageAIResponseHandler;
-import org.elasticsearch.xpack.inference.services.voyageai.embeddings.VoyageAIEmbeddingsModel;
-
-import java.util.List;
-import java.util.Objects;
-import java.util.function.Supplier;
-
-public class VoyageAIEmbeddingsRequestManager extends VoyageAIRequestManager {
-    private static final Logger logger = LogManager.getLogger(VoyageAIEmbeddingsRequestManager.class);
-    private static final ResponseHandler HANDLER = createEmbeddingsHandler();
-
-    private static ResponseHandler createEmbeddingsHandler() {
-        return new VoyageAIResponseHandler("voyageai text embedding", VoyageAIEmbeddingsResponseEntity::fromResponse);
-    }
-
-    public static VoyageAIEmbeddingsRequestManager of(VoyageAIEmbeddingsModel model, ThreadPool threadPool) {
-        return new VoyageAIEmbeddingsRequestManager(Objects.requireNonNull(model), Objects.requireNonNull(threadPool));
-    }
-
-    private final VoyageAIEmbeddingsModel model;
-
-    private VoyageAIEmbeddingsRequestManager(VoyageAIEmbeddingsModel model, ThreadPool threadPool) {
-        super(threadPool, model);
-        this.model = Objects.requireNonNull(model);
-    }
-
-    @Override
-    public void execute(
-        InferenceInputs inferenceInputs,
-        RequestSender requestSender,
-        Supplier<Boolean> hasRequestCompletedFunction,
-        ActionListener<InferenceServiceResults> listener
-    ) {
-        List<String> docsInput = DocumentsOnlyInput.of(inferenceInputs).getInputs();
-        VoyageAIEmbeddingsRequest request = new VoyageAIEmbeddingsRequest(docsInput, model);
-
-        execute(new ExecutableInferenceRequest(requestSender, logger, request, HANDLER, hasRequestCompletedFunction, listener));
-    }
-}

+ 0 - 54
x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/http/sender/VoyageAIRequestManager.java

@@ -1,54 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-package org.elasticsearch.xpack.inference.external.http.sender;
-
-import org.elasticsearch.threadpool.ThreadPool;
-import org.elasticsearch.xpack.inference.services.voyageai.VoyageAIModel;
-
-import java.util.Map;
-import java.util.Objects;
-
-abstract class VoyageAIRequestManager extends BaseRequestManager {
-    private static final String DEFAULT_MODEL_FAMILY = "default_model_family";
-    private static final Map<String, String> MODEL_TO_MODEL_FAMILY = Map.of(
-        "voyage-multimodal-3",
-        "embed_multimodal",
-        "voyage-3-large",
-        "embed_large",
-        "voyage-code-3",
-        "embed_large",
-        "voyage-3",
-        "embed_medium",
-        "voyage-3-lite",
-        "embed_small",
-        "voyage-finance-2",
-        "embed_large",
-        "voyage-law-2",
-        "embed_large",
-        "voyage-code-2",
-        "embed_large",
-        "rerank-2",
-        "rerank_large",
-        "rerank-2-lite",
-        "rerank_small"
-    );
-
-    protected VoyageAIRequestManager(ThreadPool threadPool, VoyageAIModel model) {
-        super(threadPool, model.getInferenceEntityId(), RateLimitGrouping.of(model), model.rateLimitServiceSettings().rateLimitSettings());
-    }
-
-    record RateLimitGrouping(int apiKeyHash) {
-        public static RateLimitGrouping of(VoyageAIModel model) {
-            Objects.requireNonNull(model);
-            String modelId = model.getServiceSettings().modelId();
-            String modelFamily = MODEL_TO_MODEL_FAMILY.getOrDefault(modelId, DEFAULT_MODEL_FAMILY);
-
-            return new RateLimitGrouping(modelFamily.hashCode());
-        }
-    }
-}

+ 0 - 56
x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/http/sender/VoyageAIRerankRequestManager.java

@@ -1,56 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-package org.elasticsearch.xpack.inference.external.http.sender;
-
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
-import org.elasticsearch.action.ActionListener;
-import org.elasticsearch.inference.InferenceServiceResults;
-import org.elasticsearch.threadpool.ThreadPool;
-import org.elasticsearch.xpack.inference.external.http.retry.RequestSender;
-import org.elasticsearch.xpack.inference.external.http.retry.ResponseHandler;
-import org.elasticsearch.xpack.inference.external.request.voyageai.VoyageAIRerankRequest;
-import org.elasticsearch.xpack.inference.external.response.voyageai.VoyageAIRerankResponseEntity;
-import org.elasticsearch.xpack.inference.external.voyageai.VoyageAIResponseHandler;
-import org.elasticsearch.xpack.inference.services.voyageai.rerank.VoyageAIRerankModel;
-
-import java.util.Objects;
-import java.util.function.Supplier;
-
-public class VoyageAIRerankRequestManager extends VoyageAIRequestManager {
-    private static final Logger logger = LogManager.getLogger(VoyageAIRerankRequestManager.class);
-    private static final ResponseHandler HANDLER = createVoyageAIResponseHandler();
-
-    private static ResponseHandler createVoyageAIResponseHandler() {
-        return new VoyageAIResponseHandler("voyageai rerank", (request, response) -> VoyageAIRerankResponseEntity.fromResponse(response));
-    }
-
-    public static VoyageAIRerankRequestManager of(VoyageAIRerankModel model, ThreadPool threadPool) {
-        return new VoyageAIRerankRequestManager(Objects.requireNonNull(model), Objects.requireNonNull(threadPool));
-    }
-
-    private final VoyageAIRerankModel model;
-
-    private VoyageAIRerankRequestManager(VoyageAIRerankModel model, ThreadPool threadPool) {
-        super(threadPool, model);
-        this.model = model;
-    }
-
-    @Override
-    public void execute(
-        InferenceInputs inferenceInputs,
-        RequestSender requestSender,
-        Supplier<Boolean> hasRequestCompletedFunction,
-        ActionListener<InferenceServiceResults> listener
-    ) {
-        var rerankInput = QueryAndDocsInputs.of(inferenceInputs);
-        VoyageAIRerankRequest request = new VoyageAIRerankRequest(rerankInput.getQuery(), rerankInput.getChunks(), model);
-
-        execute(new ExecutableInferenceRequest(requestSender, logger, request, HANDLER, hasRequestCompletedFunction, listener));
-    }
-}

+ 4 - 0
x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/request/RequestUtils.java

@@ -34,5 +34,9 @@ public class RequestUtils {
         }
     }
 
+    public static URI buildUri(String service, CheckedSupplier<URI, URISyntaxException> uriBuilder) {
+        return buildUri(null, service, uriBuilder);
+    }
+
     private RequestUtils() {}
 }

+ 15 - 25
x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/request/voyageai/VoyageAIEmbeddingsRequest.java

@@ -12,10 +12,8 @@ import org.apache.http.entity.ByteArrayEntity;
 import org.elasticsearch.common.Strings;
 import org.elasticsearch.xpack.inference.external.request.HttpRequest;
 import org.elasticsearch.xpack.inference.external.request.Request;
-import org.elasticsearch.xpack.inference.external.voyageai.VoyageAIAccount;
 import org.elasticsearch.xpack.inference.services.voyageai.embeddings.VoyageAIEmbeddingsModel;
 import org.elasticsearch.xpack.inference.services.voyageai.embeddings.VoyageAIEmbeddingsServiceSettings;
-import org.elasticsearch.xpack.inference.services.voyageai.embeddings.VoyageAIEmbeddingsTaskSettings;
 
 import java.net.URI;
 import java.nio.charset.StandardCharsets;
@@ -24,47 +22,43 @@ import java.util.Objects;
 
 public class VoyageAIEmbeddingsRequest extends VoyageAIRequest {
 
-    private final VoyageAIAccount account;
     private final List<String> input;
-    private final VoyageAIEmbeddingsServiceSettings serviceSettings;
-    private final VoyageAIEmbeddingsTaskSettings taskSettings;
-    private final String model;
-    private final String inferenceEntityId;
+    private final VoyageAIEmbeddingsModel embeddingsModel;
 
     public VoyageAIEmbeddingsRequest(List<String> input, VoyageAIEmbeddingsModel embeddingsModel) {
-        Objects.requireNonNull(embeddingsModel);
-
-        account = VoyageAIAccount.of(embeddingsModel);
+        this.embeddingsModel = Objects.requireNonNull(embeddingsModel);
         this.input = Objects.requireNonNull(input);
-        serviceSettings = embeddingsModel.getServiceSettings();
-        taskSettings = embeddingsModel.getTaskSettings();
-        model = embeddingsModel.getServiceSettings().getCommonSettings().modelId();
-        inferenceEntityId = embeddingsModel.getInferenceEntityId();
     }
 
     @Override
     public HttpRequest createHttpRequest() {
-        HttpPost httpPost = new HttpPost(account.uri());
+        HttpPost httpPost = new HttpPost(embeddingsModel.uri());
 
         ByteArrayEntity byteEntity = new ByteArrayEntity(
-            Strings.toString(new VoyageAIEmbeddingsRequestEntity(input, serviceSettings, taskSettings, model))
-                .getBytes(StandardCharsets.UTF_8)
+            Strings.toString(
+                new VoyageAIEmbeddingsRequestEntity(
+                    input,
+                    embeddingsModel.getServiceSettings(),
+                    embeddingsModel.getTaskSettings(),
+                    embeddingsModel.getServiceSettings().modelId()
+                )
+            ).getBytes(StandardCharsets.UTF_8)
         );
         httpPost.setEntity(byteEntity);
 
-        decorateWithHeaders(httpPost, account);
+        decorateWithHeaders(httpPost, embeddingsModel);
 
         return new HttpRequest(httpPost, getInferenceEntityId());
     }
 
     @Override
     public String getInferenceEntityId() {
-        return inferenceEntityId;
+        return embeddingsModel.getInferenceEntityId();
     }
 
     @Override
     public URI getURI() {
-        return account.uri();
+        return embeddingsModel.uri();
     }
 
     @Override
@@ -77,11 +71,7 @@ public class VoyageAIEmbeddingsRequest extends VoyageAIRequest {
         return null;
     }
 
-    public VoyageAIEmbeddingsTaskSettings getTaskSettings() {
-        return taskSettings;
-    }
-
     public VoyageAIEmbeddingsServiceSettings getServiceSettings() {
-        return serviceSettings;
+        return embeddingsModel.getServiceSettings();
     }
 }

+ 3 - 3
x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/request/voyageai/VoyageAIRequest.java

@@ -11,15 +11,15 @@ import org.apache.http.HttpHeaders;
 import org.apache.http.client.methods.HttpPost;
 import org.elasticsearch.xcontent.XContentType;
 import org.elasticsearch.xpack.inference.external.request.Request;
-import org.elasticsearch.xpack.inference.external.voyageai.VoyageAIAccount;
+import org.elasticsearch.xpack.inference.services.voyageai.VoyageAIModel;
 
 import static org.elasticsearch.xpack.inference.external.request.RequestUtils.createAuthBearerHeader;
 
 public abstract class VoyageAIRequest implements Request {
 
-    public static void decorateWithHeaders(HttpPost request, VoyageAIAccount account) {
+    public static void decorateWithHeaders(HttpPost request, VoyageAIModel model) {
         request.setHeader(HttpHeaders.CONTENT_TYPE, XContentType.JSON.mediaType());
-        request.setHeader(createAuthBearerHeader(account.apiKey()));
+        request.setHeader(createAuthBearerHeader(model.apiKey()));
         request.setHeader(VoyageAIUtils.createRequestSourceHeader());
     }
 

+ 8 - 16
x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/request/voyageai/VoyageAIRerankRequest.java

@@ -12,9 +12,7 @@ import org.apache.http.entity.ByteArrayEntity;
 import org.elasticsearch.common.Strings;
 import org.elasticsearch.xpack.inference.external.request.HttpRequest;
 import org.elasticsearch.xpack.inference.external.request.Request;
-import org.elasticsearch.xpack.inference.external.voyageai.VoyageAIAccount;
 import org.elasticsearch.xpack.inference.services.voyageai.rerank.VoyageAIRerankModel;
-import org.elasticsearch.xpack.inference.services.voyageai.rerank.VoyageAIRerankTaskSettings;
 
 import java.net.URI;
 import java.nio.charset.StandardCharsets;
@@ -23,46 +21,40 @@ import java.util.Objects;
 
 public class VoyageAIRerankRequest extends VoyageAIRequest {
 
-    private final VoyageAIAccount account;
     private final String query;
     private final List<String> input;
-    private final VoyageAIRerankTaskSettings taskSettings;
-    private final String model;
-    private final String inferenceEntityId;
+    private final VoyageAIRerankModel model;
 
     public VoyageAIRerankRequest(String query, List<String> input, VoyageAIRerankModel model) {
-        Objects.requireNonNull(model);
+        this.model = Objects.requireNonNull(model);
 
-        this.account = VoyageAIAccount.of(model);
         this.input = Objects.requireNonNull(input);
         this.query = Objects.requireNonNull(query);
-        taskSettings = model.getTaskSettings();
-        this.model = model.getServiceSettings().modelId();
-        inferenceEntityId = model.getInferenceEntityId();
     }
 
     @Override
     public HttpRequest createHttpRequest() {
-        HttpPost httpPost = new HttpPost(account.uri());
+        HttpPost httpPost = new HttpPost(model.uri());
 
         ByteArrayEntity byteEntity = new ByteArrayEntity(
-            Strings.toString(new VoyageAIRerankRequestEntity(query, input, taskSettings, model)).getBytes(StandardCharsets.UTF_8)
+            Strings.toString(new VoyageAIRerankRequestEntity(query, input, model.getTaskSettings(), model.getServiceSettings().modelId()))
+                .getBytes(StandardCharsets.UTF_8)
         );
         httpPost.setEntity(byteEntity);
 
-        decorateWithHeaders(httpPost, account);
+        decorateWithHeaders(httpPost, model);
 
         return new HttpRequest(httpPost, getInferenceEntityId());
     }
 
     @Override
     public String getInferenceEntityId() {
-        return inferenceEntityId;
+        return model.getInferenceEntityId();
     }
 
     @Override
     public URI getURI() {
-        return account.uri();
+        return model.uri();
     }
 
     @Override

+ 0 - 35
x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/voyageai/VoyageAIAccount.java

@@ -1,35 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-package org.elasticsearch.xpack.inference.external.voyageai;
-
-import org.elasticsearch.ElasticsearchStatusException;
-import org.elasticsearch.common.settings.SecureString;
-import org.elasticsearch.rest.RestStatus;
-import org.elasticsearch.xpack.inference.services.voyageai.VoyageAIModel;
-
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.util.Objects;
-
-public record VoyageAIAccount(URI uri, SecureString apiKey) {
-
-    public static VoyageAIAccount of(VoyageAIModel model) {
-        try {
-            var uri = model.buildUri();
-            return new VoyageAIAccount(uri, model.apiKey());
-        } catch (URISyntaxException e) {
-            // using bad request here so that potentially sensitive URL information does not get logged
-            throw new ElasticsearchStatusException("Failed to construct VoyageAI URL", RestStatus.BAD_REQUEST, e);
-        }
-    }
-
-    public VoyageAIAccount {
-        Objects.requireNonNull(uri);
-        Objects.requireNonNull(apiKey);
-    }
-}

+ 41 - 28
x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/voyageai/VoyageAIModel.java

@@ -10,53 +10,68 @@ package org.elasticsearch.xpack.inference.services.voyageai;
 import org.elasticsearch.common.settings.SecureString;
 import org.elasticsearch.core.Nullable;
 import org.elasticsearch.inference.InputType;
-import org.elasticsearch.inference.Model;
 import org.elasticsearch.inference.ModelConfigurations;
 import org.elasticsearch.inference.ModelSecrets;
 import org.elasticsearch.inference.ServiceSettings;
 import org.elasticsearch.inference.TaskSettings;
 import org.elasticsearch.xpack.inference.external.action.ExecutableAction;
 import org.elasticsearch.xpack.inference.external.action.voyageai.VoyageAIActionVisitor;
+import org.elasticsearch.xpack.inference.services.RateLimitGroupingModel;
 import org.elasticsearch.xpack.inference.services.ServiceUtils;
 import org.elasticsearch.xpack.inference.services.settings.ApiKeySecrets;
+import org.elasticsearch.xpack.inference.services.settings.RateLimitSettings;
 
 import java.net.URI;
-import java.net.URISyntaxException;
 import java.util.Map;
 import java.util.Objects;
 
-public abstract class VoyageAIModel extends Model {
+public abstract class VoyageAIModel extends RateLimitGroupingModel {
+    private static final String DEFAULT_MODEL_FAMILY = "default_model_family";
+    private static final Map<String, String> MODEL_TO_MODEL_FAMILY = Map.of(
+        "voyage-multimodal-3",
+        "embed_multimodal",
+        "voyage-3-large",
+        "embed_large",
+        "voyage-code-3",
+        "embed_large",
+        "voyage-3",
+        "embed_medium",
+        "voyage-3-lite",
+        "embed_small",
+        "voyage-finance-2",
+        "embed_large",
+        "voyage-law-2",
+        "embed_large",
+        "voyage-code-2",
+        "embed_large",
+        "rerank-2",
+        "rerank_large",
+        "rerank-2-lite",
+        "rerank_small"
+    );
+
     private final SecureString apiKey;
     private final VoyageAIRateLimitServiceSettings rateLimitServiceSettings;
-    protected final URI uri;
-
-    public VoyageAIModel(
-        ModelConfigurations configurations,
-        ModelSecrets secrets,
-        @Nullable ApiKeySecrets apiKeySecrets,
-        VoyageAIRateLimitServiceSettings rateLimitServiceSettings
-    ) {
-        this(configurations, secrets, apiKeySecrets, rateLimitServiceSettings, null);
-    }
+    private final URI uri;
 
     public VoyageAIModel(
         ModelConfigurations configurations,
         ModelSecrets secrets,
         @Nullable ApiKeySecrets apiKeySecrets,
         VoyageAIRateLimitServiceSettings rateLimitServiceSettings,
-        String url
+        URI uri
     ) {
         super(configurations, secrets);
 
         this.rateLimitServiceSettings = Objects.requireNonNull(rateLimitServiceSettings);
         this.apiKey = ServiceUtils.apiKey(apiKeySecrets);
-        this.uri = url == null ? null : URI.create(url);
+        this.uri = Objects.requireNonNull(uri);
     }
 
     protected VoyageAIModel(VoyageAIModel model, TaskSettings taskSettings) {
         super(model, taskSettings);
 
-        this.rateLimitServiceSettings = model.rateLimitServiceSettings();
+        this.rateLimitServiceSettings = model.rateLimitServiceSettings;
         this.apiKey = model.apiKey();
         this.uri = model.uri;
     }
@@ -64,7 +79,7 @@ public abstract class VoyageAIModel extends Model {
     protected VoyageAIModel(VoyageAIModel model, ServiceSettings serviceSettings) {
         super(model, serviceSettings);
 
-        this.rateLimitServiceSettings = model.rateLimitServiceSettings();
+        this.rateLimitServiceSettings = model.rateLimitServiceSettings;
         this.apiKey = model.apiKey();
         this.uri = model.uri;
     }
@@ -73,22 +88,20 @@ public abstract class VoyageAIModel extends Model {
         return apiKey;
     }
 
-    public VoyageAIRateLimitServiceSettings rateLimitServiceSettings() {
-        return rateLimitServiceSettings;
-    }
+    public int rateLimitGroupingHash() {
+        String modelId = getServiceSettings().modelId();
+        String modelFamily = MODEL_TO_MODEL_FAMILY.getOrDefault(modelId, DEFAULT_MODEL_FAMILY);
 
-    public abstract ExecutableAction accept(VoyageAIActionVisitor creator, Map<String, Object> taskSettings, InputType inputType);
+        return Objects.hash(modelFamily, apiKey);
+    }
 
-    public URI uri() {
-        return uri;
+    public RateLimitSettings rateLimitSettings() {
+        return rateLimitServiceSettings.rateLimitSettings();
     }
 
-    public URI buildUri() throws URISyntaxException {
-        if (uri == null) {
-            return buildRequestUri();
-        }
+    public URI uri() {
         return uri;
     }
 
-    protected abstract URI buildRequestUri() throws URISyntaxException;
+    public abstract ExecutableAction accept(VoyageAIActionVisitor creator, Map<String, Object> taskSettings, InputType inputType);
 }

+ 21 - 21
x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/voyageai/embeddings/VoyageAIEmbeddingsModel.java

@@ -18,13 +18,16 @@ import org.elasticsearch.xpack.inference.external.action.ExecutableAction;
 import org.elasticsearch.xpack.inference.external.action.voyageai.VoyageAIActionVisitor;
 import org.elasticsearch.xpack.inference.external.request.voyageai.VoyageAIUtils;
 import org.elasticsearch.xpack.inference.services.ConfigurationParseContext;
+import org.elasticsearch.xpack.inference.services.ServiceUtils;
 import org.elasticsearch.xpack.inference.services.settings.DefaultSecretSettings;
 import org.elasticsearch.xpack.inference.services.voyageai.VoyageAIModel;
+import org.elasticsearch.xpack.inference.services.voyageai.VoyageAIService;
 
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.util.Map;
 
+import static org.elasticsearch.xpack.inference.external.request.RequestUtils.buildUri;
 import static org.elasticsearch.xpack.inference.external.request.voyageai.VoyageAIUtils.HOST;
 
 public class VoyageAIEmbeddingsModel extends VoyageAIModel {
@@ -51,42 +54,46 @@ public class VoyageAIEmbeddingsModel extends VoyageAIModel {
             VoyageAIEmbeddingsServiceSettings.fromMap(serviceSettings, context),
             VoyageAIEmbeddingsTaskSettings.fromMap(taskSettings),
             chunkingSettings,
-            DefaultSecretSettings.fromMap(secrets)
+            DefaultSecretSettings.fromMap(secrets),
+            buildUri(VoyageAIService.NAME, VoyageAIEmbeddingsModel::buildRequestUri)
         );
     }
 
+    public static URI buildRequestUri() throws URISyntaxException {
+        return new URIBuilder().setScheme("https")
+            .setHost(HOST)
+            .setPathSegments(VoyageAIUtils.VERSION_1, VoyageAIUtils.EMBEDDINGS_PATH)
+            .build();
+    }
+
     // should only be used for testing
     VoyageAIEmbeddingsModel(
-        String modelId,
+        String inferenceId,
         String service,
+        String url,
         VoyageAIEmbeddingsServiceSettings serviceSettings,
         VoyageAIEmbeddingsTaskSettings taskSettings,
         ChunkingSettings chunkingSettings,
         @Nullable DefaultSecretSettings secretSettings
     ) {
-        super(
-            new ModelConfigurations(modelId, TaskType.TEXT_EMBEDDING, service, serviceSettings, taskSettings, chunkingSettings),
-            new ModelSecrets(secretSettings),
-            secretSettings,
-            serviceSettings.getCommonSettings()
-        );
+        this(inferenceId, service, serviceSettings, taskSettings, chunkingSettings, secretSettings, ServiceUtils.createUri(url));
     }
 
-    VoyageAIEmbeddingsModel(
-        String modelId,
+    private VoyageAIEmbeddingsModel(
+        String inferenceId,
         String service,
-        String url,
         VoyageAIEmbeddingsServiceSettings serviceSettings,
         VoyageAIEmbeddingsTaskSettings taskSettings,
         ChunkingSettings chunkingSettings,
-        @Nullable DefaultSecretSettings secretSettings
+        @Nullable DefaultSecretSettings secretSettings,
+        URI uri
     ) {
         super(
-            new ModelConfigurations(modelId, TaskType.TEXT_EMBEDDING, service, serviceSettings, taskSettings, chunkingSettings),
+            new ModelConfigurations(inferenceId, TaskType.TEXT_EMBEDDING, service, serviceSettings, taskSettings, chunkingSettings),
             new ModelSecrets(secretSettings),
             secretSettings,
             serviceSettings.getCommonSettings(),
-            url
+            uri
         );
     }
 
@@ -117,11 +124,4 @@ public class VoyageAIEmbeddingsModel extends VoyageAIModel {
     public ExecutableAction accept(VoyageAIActionVisitor visitor, Map<String, Object> taskSettings, InputType inputType) {
         return visitor.create(this, taskSettings, inputType);
     }
-
-    protected URI buildRequestUri() throws URISyntaxException {
-        return new URIBuilder().setScheme("https")
-            .setHost(HOST)
-            .setPathSegments(VoyageAIUtils.VERSION_1, VoyageAIUtils.EMBEDDINGS_PATH)
-            .build();
-    }
 }

+ 21 - 20
x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/voyageai/rerank/VoyageAIRerankModel.java

@@ -17,13 +17,16 @@ import org.elasticsearch.xpack.inference.external.action.ExecutableAction;
 import org.elasticsearch.xpack.inference.external.action.voyageai.VoyageAIActionVisitor;
 import org.elasticsearch.xpack.inference.external.request.voyageai.VoyageAIUtils;
 import org.elasticsearch.xpack.inference.services.ConfigurationParseContext;
+import org.elasticsearch.xpack.inference.services.ServiceUtils;
 import org.elasticsearch.xpack.inference.services.settings.DefaultSecretSettings;
 import org.elasticsearch.xpack.inference.services.voyageai.VoyageAIModel;
+import org.elasticsearch.xpack.inference.services.voyageai.VoyageAIService;
 
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.util.Map;
 
+import static org.elasticsearch.xpack.inference.external.request.RequestUtils.buildUri;
 import static org.elasticsearch.xpack.inference.external.request.voyageai.VoyageAIUtils.HOST;
 
 public class VoyageAIRerankModel extends VoyageAIModel {
@@ -45,35 +48,44 @@ public class VoyageAIRerankModel extends VoyageAIModel {
             service,
             VoyageAIRerankServiceSettings.fromMap(serviceSettings, context),
             VoyageAIRerankTaskSettings.fromMap(taskSettings),
-            DefaultSecretSettings.fromMap(secrets)
+            DefaultSecretSettings.fromMap(secrets),
+            buildUri(VoyageAIService.NAME, VoyageAIRerankModel::buildRequestUri)
         );
     }
 
+    public static URI buildRequestUri() throws URISyntaxException {
+        return new URIBuilder().setScheme("https")
+            .setHost(HOST)
+            .setPathSegments(VoyageAIUtils.VERSION_1, VoyageAIUtils.RERANK_PATH)
+            .build();
+    }
+
     // should only be used for testing
     VoyageAIRerankModel(
-        String modelId,
+        String inferenceId,
         String service,
+        String url,
         VoyageAIRerankServiceSettings serviceSettings,
         VoyageAIRerankTaskSettings taskSettings,
         @Nullable DefaultSecretSettings secretSettings
     ) {
-        this(modelId, service, null, serviceSettings, taskSettings, secretSettings);
+        this(inferenceId, service, serviceSettings, taskSettings, secretSettings, ServiceUtils.createUri(url));
     }
 
-    VoyageAIRerankModel(
-        String modelId,
+    private VoyageAIRerankModel(
+        String inferenceId,
         String service,
-        String url,
         VoyageAIRerankServiceSettings serviceSettings,
         VoyageAIRerankTaskSettings taskSettings,
-        @Nullable DefaultSecretSettings secretSettings
+        @Nullable DefaultSecretSettings secretSettings,
+        URI uri
     ) {
         super(
-            new ModelConfigurations(modelId, TaskType.RERANK, service, serviceSettings, taskSettings),
+            new ModelConfigurations(inferenceId, TaskType.RERANK, service, serviceSettings, taskSettings),
             new ModelSecrets(secretSettings),
             secretSettings,
             serviceSettings.getCommonSettings(),
-            url
+            uri
         );
     }
 
@@ -81,10 +93,6 @@ public class VoyageAIRerankModel extends VoyageAIModel {
         super(model, taskSettings);
     }
 
-    public VoyageAIRerankModel(VoyageAIRerankModel model, VoyageAIRerankServiceSettings serviceSettings) {
-        super(model, serviceSettings);
-    }
-
     @Override
     public VoyageAIRerankServiceSettings getServiceSettings() {
         return (VoyageAIRerankServiceSettings) super.getServiceSettings();
@@ -112,11 +120,4 @@ public class VoyageAIRerankModel extends VoyageAIModel {
         return visitor.create(this, taskSettings);
     }
 
-    @Override
-    protected URI buildRequestUri() throws URISyntaxException {
-        return new URIBuilder().setScheme("https")
-            .setHost(HOST)
-            .setPathSegments(VoyageAIUtils.VERSION_1, VoyageAIUtils.RERANK_PATH)
-            .build();
-    }
 }

+ 12 - 37
x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/external/action/voyageai/VoyageAIEmbeddingsActionTests.java

@@ -27,9 +27,10 @@ import org.elasticsearch.xpack.inference.external.action.SenderExecutableAction;
 import org.elasticsearch.xpack.inference.external.http.HttpClientManager;
 import org.elasticsearch.xpack.inference.external.http.HttpResult;
 import org.elasticsearch.xpack.inference.external.http.sender.DocumentsOnlyInput;
+import org.elasticsearch.xpack.inference.external.http.sender.GenericRequestManager;
 import org.elasticsearch.xpack.inference.external.http.sender.HttpRequestSenderTests;
 import org.elasticsearch.xpack.inference.external.http.sender.Sender;
-import org.elasticsearch.xpack.inference.external.http.sender.VoyageAIEmbeddingsRequestManager;
+import org.elasticsearch.xpack.inference.external.request.voyageai.VoyageAIEmbeddingsRequest;
 import org.elasticsearch.xpack.inference.external.request.voyageai.VoyageAIUtils;
 import org.elasticsearch.xpack.inference.logging.ThrottlerManager;
 import org.elasticsearch.xpack.inference.services.voyageai.embeddings.VoyageAIEmbeddingType;
@@ -50,6 +51,7 @@ import static org.elasticsearch.xpack.core.inference.results.TextEmbeddingFloatR
 import static org.elasticsearch.xpack.inference.Utils.inferenceUtilityPool;
 import static org.elasticsearch.xpack.inference.Utils.mockClusterServiceEmpty;
 import static org.elasticsearch.xpack.inference.external.action.ActionUtils.constructFailedToSendRequestMessage;
+import static org.elasticsearch.xpack.inference.external.action.voyageai.VoyageAIActionCreator.EMBEDDINGS_HANDLER;
 import static org.elasticsearch.xpack.inference.external.http.Utils.entityAsMap;
 import static org.elasticsearch.xpack.inference.external.http.Utils.getUrl;
 import static org.hamcrest.Matchers.equalTo;
@@ -339,26 +341,6 @@ public class VoyageAIEmbeddingsActionTests extends ESTestCase {
         MatcherAssert.assertThat(thrownException.getMessage(), is("Failed to send VoyageAI embeddings request. Cause: failed"));
     }
 
-    public void testExecute_ThrowsElasticsearchException_WhenSenderOnFailureIsCalled_WhenUrlIsNull() {
-        var sender = mock(Sender.class);
-
-        doAnswer(invocation -> {
-            ActionListener<HttpResult> listener = invocation.getArgument(3);
-            listener.onFailure(new IllegalStateException("failed"));
-
-            return Void.TYPE;
-        }).when(sender).send(any(), any(), any(), any());
-
-        var action = createAction(null, "secret", VoyageAIEmbeddingsTaskSettings.EMPTY_SETTINGS, "model", null, sender);
-
-        PlainActionFuture<InferenceServiceResults> listener = new PlainActionFuture<>();
-        action.execute(new DocumentsOnlyInput(List.of("abc")), InferenceAction.Request.DEFAULT_TIMEOUT, listener);
-
-        var thrownException = expectThrows(ElasticsearchException.class, () -> listener.actionGet(TIMEOUT));
-
-        MatcherAssert.assertThat(thrownException.getMessage(), is("Failed to send VoyageAI embeddings request. Cause: failed"));
-    }
-
     public void testExecute_ThrowsException() {
         var sender = mock(Sender.class);
         doThrow(new IllegalArgumentException("failed")).when(sender).send(any(), any(), any(), any());
@@ -373,20 +355,6 @@ public class VoyageAIEmbeddingsActionTests extends ESTestCase {
         MatcherAssert.assertThat(thrownException.getMessage(), is("Failed to send VoyageAI embeddings request. Cause: failed"));
     }
 
-    public void testExecute_ThrowsExceptionWithNullUrl() {
-        var sender = mock(Sender.class);
-        doThrow(new IllegalArgumentException("failed")).when(sender).send(any(), any(), any(), any());
-
-        var action = createAction(null, "secret", VoyageAIEmbeddingsTaskSettings.EMPTY_SETTINGS, "model", null, sender);
-
-        PlainActionFuture<InferenceServiceResults> listener = new PlainActionFuture<>();
-        action.execute(new DocumentsOnlyInput(List.of("abc")), InferenceAction.Request.DEFAULT_TIMEOUT, listener);
-
-        var thrownException = expectThrows(ElasticsearchException.class, () -> listener.actionGet(TIMEOUT));
-
-        MatcherAssert.assertThat(thrownException.getMessage(), is("Failed to send VoyageAI embeddings request. Cause: failed"));
-    }
-
     private ExecutableAction createAction(
         String url,
         String apiKey,
@@ -396,9 +364,16 @@ public class VoyageAIEmbeddingsActionTests extends ESTestCase {
         Sender sender
     ) {
         var model = VoyageAIEmbeddingsModelTests.createModel(url, apiKey, taskSettings, 1024, 1024, modelName, embeddingType);
+        var manager = new GenericRequestManager<>(
+            threadPool,
+            model,
+            EMBEDDINGS_HANDLER,
+            (documentsOnlyInput) -> new VoyageAIEmbeddingsRequest(documentsOnlyInput.getInputs(), model),
+            DocumentsOnlyInput.class
+        );
+
         var failedToSendRequestErrorMessage = constructFailedToSendRequestMessage("VoyageAI embeddings");
-        var requestCreator = VoyageAIEmbeddingsRequestManager.of(model, threadPool);
-        return new SenderExecutableAction(sender, requestCreator, failedToSendRequestErrorMessage);
+        return new SenderExecutableAction(sender, manager, failedToSendRequestErrorMessage);
     }
 
 }

+ 4 - 9
x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/external/request/voyageai/VoyageAIRequestTests.java

@@ -9,12 +9,9 @@ package org.elasticsearch.xpack.inference.external.request.voyageai;
 
 import org.apache.http.HttpHeaders;
 import org.apache.http.client.methods.HttpPost;
-import org.elasticsearch.common.settings.SecureString;
 import org.elasticsearch.test.ESTestCase;
 import org.elasticsearch.xcontent.XContentType;
-import org.elasticsearch.xpack.inference.external.voyageai.VoyageAIAccount;
-
-import java.net.URI;
+import org.elasticsearch.xpack.inference.services.voyageai.embeddings.VoyageAIEmbeddingsModelTests;
 
 import static org.hamcrest.Matchers.is;
 
@@ -22,14 +19,12 @@ public class VoyageAIRequestTests extends ESTestCase {
 
     public void testDecorateWithHeaders() {
         var request = new HttpPost("http://www.abc.com");
+        var model = VoyageAIEmbeddingsModelTests.createModel("abc", "key", null, "model_id");
 
-        VoyageAIRequest.decorateWithHeaders(
-            request,
-            new VoyageAIAccount(URI.create("http://www.abc.com"), new SecureString(new char[] { 'a', 'b', 'c' }))
-        );
+        VoyageAIRequest.decorateWithHeaders(request, model);
 
         assertThat(request.getFirstHeader(HttpHeaders.CONTENT_TYPE).getValue(), is(XContentType.JSON.mediaType()));
-        assertThat(request.getFirstHeader(HttpHeaders.AUTHORIZATION).getValue(), is("Bearer abc"));
+        assertThat(request.getFirstHeader(HttpHeaders.AUTHORIZATION).getValue(), is("Bearer key"));
         assertThat(request.getFirstHeader(VoyageAIUtils.REQUEST_SOURCE_HEADER).getValue(), is(VoyageAIUtils.ELASTIC_REQUEST_SOURCE));
     }
 

+ 18 - 67
x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/voyageai/VoyageAIServiceTests.java

@@ -53,7 +53,6 @@ import org.junit.After;
 import org.junit.Before;
 
 import java.io.IOException;
-import java.net.URISyntaxException;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -109,8 +108,7 @@ public class VoyageAIServiceTests extends ESTestCase {
                 MatcherAssert.assertThat(model, instanceOf(VoyageAIEmbeddingsModel.class));
 
                 var embeddingsModel = (VoyageAIEmbeddingsModel) model;
-                assertNull(embeddingsModel.uri());
-                MatcherAssert.assertThat(embeddingsModel.buildUri().toString(), is("https://api.voyageai.com/v1/embeddings"));
+                MatcherAssert.assertThat(embeddingsModel.uri().toString(), is("https://api.voyageai.com/v1/embeddings"));
                 MatcherAssert.assertThat(embeddingsModel.getServiceSettings().getCommonSettings().modelId(), is("model"));
                 MatcherAssert.assertThat(embeddingsModel.getTaskSettings(), is(new VoyageAIEmbeddingsTaskSettings(InputType.INGEST, null)));
                 MatcherAssert.assertThat(embeddingsModel.getSecretSettings().apiKey().toString(), is("secret"));
@@ -126,7 +124,6 @@ public class VoyageAIServiceTests extends ESTestCase {
                 ),
                 modelListener
             );
-
         }
     }
 
@@ -136,8 +133,7 @@ public class VoyageAIServiceTests extends ESTestCase {
                 MatcherAssert.assertThat(model, instanceOf(VoyageAIEmbeddingsModel.class));
 
                 var embeddingsModel = (VoyageAIEmbeddingsModel) model;
-                assertNull(embeddingsModel.uri());
-                MatcherAssert.assertThat(embeddingsModel.buildUri().toString(), is("https://api.voyageai.com/v1/embeddings"));
+                MatcherAssert.assertThat(embeddingsModel.uri().toString(), is("https://api.voyageai.com/v1/embeddings"));
                 MatcherAssert.assertThat(embeddingsModel.getServiceSettings().getCommonSettings().modelId(), is("model"));
                 MatcherAssert.assertThat(embeddingsModel.getTaskSettings(), is(new VoyageAIEmbeddingsTaskSettings(InputType.INGEST, null)));
                 MatcherAssert.assertThat(embeddingsModel.getConfigurations().getChunkingSettings(), instanceOf(ChunkingSettings.class));
@@ -166,8 +162,7 @@ public class VoyageAIServiceTests extends ESTestCase {
                 MatcherAssert.assertThat(model, instanceOf(VoyageAIEmbeddingsModel.class));
 
                 var embeddingsModel = (VoyageAIEmbeddingsModel) model;
-                assertNull(embeddingsModel.uri());
-                MatcherAssert.assertThat(embeddingsModel.buildUri().toString(), is("https://api.voyageai.com/v1/embeddings"));
+                MatcherAssert.assertThat(embeddingsModel.uri().toString(), is("https://api.voyageai.com/v1/embeddings"));
                 MatcherAssert.assertThat(embeddingsModel.getServiceSettings().getCommonSettings().modelId(), is("model"));
                 MatcherAssert.assertThat(embeddingsModel.getTaskSettings(), is(new VoyageAIEmbeddingsTaskSettings(InputType.INGEST, null)));
                 MatcherAssert.assertThat(embeddingsModel.getConfigurations().getChunkingSettings(), instanceOf(ChunkingSettings.class));
@@ -196,8 +191,7 @@ public class VoyageAIServiceTests extends ESTestCase {
                 MatcherAssert.assertThat(model, instanceOf(VoyageAIEmbeddingsModel.class));
 
                 var embeddingsModel = (VoyageAIEmbeddingsModel) model;
-                assertNull(embeddingsModel.uri());
-                MatcherAssert.assertThat(embeddingsModel.buildUri().toString(), is("https://api.voyageai.com/v1/embeddings"));
+                MatcherAssert.assertThat(embeddingsModel.uri().toString(), is("https://api.voyageai.com/v1/embeddings"));
                 MatcherAssert.assertThat(embeddingsModel.getServiceSettings().getCommonSettings().modelId(), is("model"));
                 MatcherAssert.assertThat(embeddingsModel.getTaskSettings(), equalTo(VoyageAIEmbeddingsTaskSettings.EMPTY_SETTINGS));
                 MatcherAssert.assertThat(embeddingsModel.getSecretSettings().apiKey().toString(), is("secret"));
@@ -333,13 +327,10 @@ public class VoyageAIServiceTests extends ESTestCase {
             MatcherAssert.assertThat(model, instanceOf(VoyageAIEmbeddingsModel.class));
 
             var embeddingsModel = (VoyageAIEmbeddingsModel) model;
-            assertNull(embeddingsModel.uri());
-            MatcherAssert.assertThat(embeddingsModel.buildUri().toString(), is("https://api.voyageai.com/v1/embeddings"));
+            MatcherAssert.assertThat(embeddingsModel.uri().toString(), is("https://api.voyageai.com/v1/embeddings"));
             MatcherAssert.assertThat(embeddingsModel.getServiceSettings().getCommonSettings().modelId(), is("model"));
             MatcherAssert.assertThat(embeddingsModel.getTaskSettings(), is(new VoyageAIEmbeddingsTaskSettings((InputType) null, null)));
             MatcherAssert.assertThat(embeddingsModel.getSecretSettings().apiKey().toString(), is("secret"));
-        } catch (URISyntaxException e) {
-            throw new RuntimeException(e);
         }
     }
 
@@ -362,14 +353,11 @@ public class VoyageAIServiceTests extends ESTestCase {
             MatcherAssert.assertThat(model, instanceOf(VoyageAIEmbeddingsModel.class));
 
             var embeddingsModel = (VoyageAIEmbeddingsModel) model;
-            assertNull(embeddingsModel.uri());
-            MatcherAssert.assertThat(embeddingsModel.buildUri().toString(), is("https://api.voyageai.com/v1/embeddings"));
+            MatcherAssert.assertThat(embeddingsModel.uri().toString(), is("https://api.voyageai.com/v1/embeddings"));
             MatcherAssert.assertThat(embeddingsModel.getServiceSettings().getCommonSettings().modelId(), is("model"));
             MatcherAssert.assertThat(embeddingsModel.getTaskSettings(), is(new VoyageAIEmbeddingsTaskSettings((InputType) null, null)));
             MatcherAssert.assertThat(embeddingsModel.getConfigurations().getChunkingSettings(), instanceOf(ChunkingSettings.class));
             MatcherAssert.assertThat(embeddingsModel.getSecretSettings().apiKey().toString(), is("secret"));
-        } catch (URISyntaxException e) {
-            throw new RuntimeException(e);
         }
     }
 
@@ -391,14 +379,11 @@ public class VoyageAIServiceTests extends ESTestCase {
             MatcherAssert.assertThat(model, instanceOf(VoyageAIEmbeddingsModel.class));
 
             var embeddingsModel = (VoyageAIEmbeddingsModel) model;
-            assertNull(embeddingsModel.uri());
-            MatcherAssert.assertThat(embeddingsModel.buildUri().toString(), is("https://api.voyageai.com/v1/embeddings"));
+            MatcherAssert.assertThat(embeddingsModel.uri().toString(), is("https://api.voyageai.com/v1/embeddings"));
             MatcherAssert.assertThat(embeddingsModel.getServiceSettings().getCommonSettings().modelId(), is("model"));
             MatcherAssert.assertThat(embeddingsModel.getTaskSettings(), is(new VoyageAIEmbeddingsTaskSettings((InputType) null, null)));
             MatcherAssert.assertThat(embeddingsModel.getConfigurations().getChunkingSettings(), instanceOf(ChunkingSettings.class));
             MatcherAssert.assertThat(embeddingsModel.getSecretSettings().apiKey().toString(), is("secret"));
-        } catch (URISyntaxException e) {
-            throw new RuntimeException(e);
         }
     }
 
@@ -446,13 +431,10 @@ public class VoyageAIServiceTests extends ESTestCase {
             MatcherAssert.assertThat(model, instanceOf(VoyageAIEmbeddingsModel.class));
 
             var embeddingsModel = (VoyageAIEmbeddingsModel) model;
-            assertNull(embeddingsModel.uri());
-            MatcherAssert.assertThat(embeddingsModel.buildUri().toString(), is("https://api.voyageai.com/v1/embeddings"));
+            MatcherAssert.assertThat(embeddingsModel.uri().toString(), is("https://api.voyageai.com/v1/embeddings"));
             MatcherAssert.assertThat(embeddingsModel.getServiceSettings().getCommonSettings().modelId(), is("model"));
             MatcherAssert.assertThat(embeddingsModel.getTaskSettings(), is(new VoyageAIEmbeddingsTaskSettings(InputType.SEARCH, null)));
             MatcherAssert.assertThat(embeddingsModel.getSecretSettings().apiKey().toString(), is("secret"));
-        } catch (URISyntaxException e) {
-            throw new RuntimeException(e);
         }
     }
 
@@ -477,13 +459,10 @@ public class VoyageAIServiceTests extends ESTestCase {
             MatcherAssert.assertThat(model, instanceOf(VoyageAIEmbeddingsModel.class));
 
             var embeddingsModel = (VoyageAIEmbeddingsModel) model;
-            assertNull(embeddingsModel.uri());
-            MatcherAssert.assertThat(embeddingsModel.buildUri().toString(), is("https://api.voyageai.com/v1/embeddings"));
+            MatcherAssert.assertThat(embeddingsModel.uri().toString(), is("https://api.voyageai.com/v1/embeddings"));
             MatcherAssert.assertThat(embeddingsModel.getServiceSettings().getCommonSettings().modelId(), is("model"));
             MatcherAssert.assertThat(embeddingsModel.getTaskSettings(), is(VoyageAIEmbeddingsTaskSettings.EMPTY_SETTINGS));
             MatcherAssert.assertThat(embeddingsModel.getSecretSettings().apiKey().toString(), is("secret"));
-        } catch (URISyntaxException e) {
-            throw new RuntimeException(e);
         }
     }
 
@@ -506,13 +485,10 @@ public class VoyageAIServiceTests extends ESTestCase {
             MatcherAssert.assertThat(model, instanceOf(VoyageAIEmbeddingsModel.class));
 
             var embeddingsModel = (VoyageAIEmbeddingsModel) model;
-            assertNull(embeddingsModel.uri());
-            MatcherAssert.assertThat(embeddingsModel.buildUri().toString(), is("https://api.voyageai.com/v1/embeddings"));
+            MatcherAssert.assertThat(embeddingsModel.uri().toString(), is("https://api.voyageai.com/v1/embeddings"));
             MatcherAssert.assertThat(embeddingsModel.getServiceSettings().getCommonSettings().modelId(), is("model"));
             MatcherAssert.assertThat(embeddingsModel.getTaskSettings(), is(new VoyageAIEmbeddingsTaskSettings((InputType) null, null)));
             MatcherAssert.assertThat(embeddingsModel.getSecretSettings().apiKey().toString(), is("secret"));
-        } catch (URISyntaxException e) {
-            throw new RuntimeException(e);
         }
     }
 
@@ -537,13 +513,10 @@ public class VoyageAIServiceTests extends ESTestCase {
             MatcherAssert.assertThat(model, instanceOf(VoyageAIEmbeddingsModel.class));
 
             var embeddingsModel = (VoyageAIEmbeddingsModel) model;
-            assertNull(embeddingsModel.uri());
-            MatcherAssert.assertThat(embeddingsModel.buildUri().toString(), is("https://api.voyageai.com/v1/embeddings"));
+            MatcherAssert.assertThat(embeddingsModel.uri().toString(), is("https://api.voyageai.com/v1/embeddings"));
             MatcherAssert.assertThat(embeddingsModel.getServiceSettings().getCommonSettings().modelId(), is("model"));
             MatcherAssert.assertThat(embeddingsModel.getTaskSettings(), is(VoyageAIEmbeddingsTaskSettings.EMPTY_SETTINGS));
             MatcherAssert.assertThat(embeddingsModel.getSecretSettings().apiKey().toString(), is("secret"));
-        } catch (URISyntaxException e) {
-            throw new RuntimeException(e);
         }
     }
 
@@ -568,13 +541,10 @@ public class VoyageAIServiceTests extends ESTestCase {
             MatcherAssert.assertThat(model, instanceOf(VoyageAIEmbeddingsModel.class));
 
             var embeddingsModel = (VoyageAIEmbeddingsModel) model;
-            assertNull(embeddingsModel.uri());
-            MatcherAssert.assertThat(embeddingsModel.buildUri().toString(), is("https://api.voyageai.com/v1/embeddings"));
+            MatcherAssert.assertThat(embeddingsModel.uri().toString(), is("https://api.voyageai.com/v1/embeddings"));
             MatcherAssert.assertThat(embeddingsModel.getServiceSettings().getCommonSettings().modelId(), is("model"));
             MatcherAssert.assertThat(embeddingsModel.getTaskSettings(), is(new VoyageAIEmbeddingsTaskSettings(InputType.SEARCH, null)));
             MatcherAssert.assertThat(embeddingsModel.getSecretSettings().apiKey().toString(), is("secret"));
-        } catch (URISyntaxException e) {
-            throw new RuntimeException(e);
         }
     }
 
@@ -590,13 +560,10 @@ public class VoyageAIServiceTests extends ESTestCase {
             MatcherAssert.assertThat(model, instanceOf(VoyageAIEmbeddingsModel.class));
 
             var embeddingsModel = (VoyageAIEmbeddingsModel) model;
-            assertNull(embeddingsModel.uri());
-            MatcherAssert.assertThat(embeddingsModel.buildUri().toString(), is("https://api.voyageai.com/v1/embeddings"));
+            MatcherAssert.assertThat(embeddingsModel.uri().toString(), is("https://api.voyageai.com/v1/embeddings"));
             MatcherAssert.assertThat(embeddingsModel.getServiceSettings().getCommonSettings().modelId(), is("model"));
             MatcherAssert.assertThat(embeddingsModel.getTaskSettings(), is(new VoyageAIEmbeddingsTaskSettings((InputType) null, null)));
             assertNull(embeddingsModel.getSecretSettings());
-        } catch (URISyntaxException e) {
-            throw new RuntimeException(e);
         }
     }
 
@@ -613,14 +580,11 @@ public class VoyageAIServiceTests extends ESTestCase {
             MatcherAssert.assertThat(model, instanceOf(VoyageAIEmbeddingsModel.class));
 
             var embeddingsModel = (VoyageAIEmbeddingsModel) model;
-            assertNull(embeddingsModel.uri());
-            MatcherAssert.assertThat(embeddingsModel.buildUri().toString(), is("https://api.voyageai.com/v1/embeddings"));
+            MatcherAssert.assertThat(embeddingsModel.uri().toString(), is("https://api.voyageai.com/v1/embeddings"));
             MatcherAssert.assertThat(embeddingsModel.getServiceSettings().getCommonSettings().modelId(), is("model"));
             MatcherAssert.assertThat(embeddingsModel.getTaskSettings(), is(new VoyageAIEmbeddingsTaskSettings((InputType) null, null)));
             MatcherAssert.assertThat(embeddingsModel.getConfigurations().getChunkingSettings(), instanceOf(ChunkingSettings.class));
             assertNull(embeddingsModel.getSecretSettings());
-        } catch (URISyntaxException e) {
-            throw new RuntimeException(e);
         }
     }
 
@@ -636,14 +600,11 @@ public class VoyageAIServiceTests extends ESTestCase {
             MatcherAssert.assertThat(model, instanceOf(VoyageAIEmbeddingsModel.class));
 
             var embeddingsModel = (VoyageAIEmbeddingsModel) model;
-            assertNull(embeddingsModel.uri());
-            MatcherAssert.assertThat(embeddingsModel.buildUri().toString(), is("https://api.voyageai.com/v1/embeddings"));
+            MatcherAssert.assertThat(embeddingsModel.uri().toString(), is("https://api.voyageai.com/v1/embeddings"));
             MatcherAssert.assertThat(embeddingsModel.getServiceSettings().getCommonSettings().modelId(), is("model"));
             MatcherAssert.assertThat(embeddingsModel.getTaskSettings(), is(new VoyageAIEmbeddingsTaskSettings((InputType) null, null)));
             MatcherAssert.assertThat(embeddingsModel.getConfigurations().getChunkingSettings(), instanceOf(ChunkingSettings.class));
             assertNull(embeddingsModel.getSecretSettings());
-        } catch (URISyntaxException e) {
-            throw new RuntimeException(e);
         }
     }
 
@@ -678,7 +639,6 @@ public class VoyageAIServiceTests extends ESTestCase {
             MatcherAssert.assertThat(model, instanceOf(VoyageAIEmbeddingsModel.class));
 
             var embeddingsModel = (VoyageAIEmbeddingsModel) model;
-            assertNull(embeddingsModel.uri());
             MatcherAssert.assertThat(embeddingsModel.getServiceSettings().getCommonSettings().modelId(), is("model"));
             MatcherAssert.assertThat(embeddingsModel.getTaskSettings(), is(new VoyageAIEmbeddingsTaskSettings((InputType) null, null)));
             assertNull(embeddingsModel.getSecretSettings());
@@ -698,13 +658,10 @@ public class VoyageAIServiceTests extends ESTestCase {
             MatcherAssert.assertThat(model, instanceOf(VoyageAIEmbeddingsModel.class));
 
             var embeddingsModel = (VoyageAIEmbeddingsModel) model;
-            assertNull(embeddingsModel.uri());
-            MatcherAssert.assertThat(embeddingsModel.buildUri().toString(), is("https://api.voyageai.com/v1/embeddings"));
+            MatcherAssert.assertThat(embeddingsModel.uri().toString(), is("https://api.voyageai.com/v1/embeddings"));
             MatcherAssert.assertThat(embeddingsModel.getServiceSettings().getCommonSettings().modelId(), is("model"));
             MatcherAssert.assertThat(embeddingsModel.getTaskSettings(), is(VoyageAIEmbeddingsTaskSettings.EMPTY_SETTINGS));
             assertNull(embeddingsModel.getSecretSettings());
-        } catch (URISyntaxException e) {
-            throw new RuntimeException(e);
         }
     }
 
@@ -723,13 +680,10 @@ public class VoyageAIServiceTests extends ESTestCase {
             MatcherAssert.assertThat(model, instanceOf(VoyageAIEmbeddingsModel.class));
 
             var embeddingsModel = (VoyageAIEmbeddingsModel) model;
-            assertNull(embeddingsModel.uri());
-            MatcherAssert.assertThat(embeddingsModel.buildUri().toString(), is("https://api.voyageai.com/v1/embeddings"));
+            MatcherAssert.assertThat(embeddingsModel.uri().toString(), is("https://api.voyageai.com/v1/embeddings"));
             MatcherAssert.assertThat(embeddingsModel.getServiceSettings().getCommonSettings().modelId(), is("model"));
             MatcherAssert.assertThat(embeddingsModel.getTaskSettings(), is(new VoyageAIEmbeddingsTaskSettings(InputType.SEARCH, null)));
             assertNull(embeddingsModel.getSecretSettings());
-        } catch (URISyntaxException e) {
-            throw new RuntimeException(e);
         }
     }
 
@@ -747,13 +701,10 @@ public class VoyageAIServiceTests extends ESTestCase {
             MatcherAssert.assertThat(model, instanceOf(VoyageAIEmbeddingsModel.class));
 
             var embeddingsModel = (VoyageAIEmbeddingsModel) model;
-            assertNull(embeddingsModel.uri());
-            MatcherAssert.assertThat(embeddingsModel.buildUri().toString(), is("https://api.voyageai.com/v1/embeddings"));
+            MatcherAssert.assertThat(embeddingsModel.uri().toString(), is("https://api.voyageai.com/v1/embeddings"));
             MatcherAssert.assertThat(embeddingsModel.getServiceSettings().getCommonSettings().modelId(), is("model"));
             MatcherAssert.assertThat(embeddingsModel.getTaskSettings(), is(new VoyageAIEmbeddingsTaskSettings(InputType.INGEST, null)));
             assertNull(embeddingsModel.getSecretSettings());
-        } catch (URISyntaxException e) {
-            throw new RuntimeException(e);
         }
     }