fix: mask apiKey in OvhAiEmbeddingModel builder toString (#5608)

## Issue
Closes #5607

## Change
`OvhAiEmbeddingModel.OvhAiEmbeddingModelBuilder.toString()` printed the
API key in plain text via `", apiKey=" + this.apiKey`, so any log or
exception that renders the builder leaked the OVHcloud key. It now masks
the key, matching the merged sibling builders JinaEmbeddingModel
(#3680), JinaScoringModel/JinaClient (#5539), and NomicEmbeddingModel
(#5559):
- `", apiKey=" + (this.apiKey == null ? null : "********")`

A null key still renders as `apiKey=null` (no NPE); no other field is a
secret.

Added unit test `OvhAiEmbeddingModelTest` (JUnit 5 + AssertJ, no new
dependencies), mirroring `JinaScoringModelBuilderTest` from #5539:
- with a key set, `toString()` contains `apiKey=********` and not the
raw value
- with no key, `toString()` contains `apiKey=null`

Note: unlike Jina (constructors only), the whole `OvhAiEmbeddingModel`
class is `@Deprecated(forRemoval = true, since = "1.14.0")`. Masking is
still worthwhile to stop leaking the key while the class is in use. The
functional change is one line in `toString()`; the remaining file diff
is Spotless reformatting a previously unformatted file (`ratchetFrom
origin/main`), not manual edits. Module unit tests and `spotless:check`
pass; the `*IT` needs an API key and was not run.

## General checklist
- [X] There are no breaking changes (API, behaviour)
- [X] I have added unit and/or integration tests for my change
- [X] The tests cover both positive and negative cases
- [X] I have manually run all the unit and integration tests in the
module I have added/changed, and they are all green
- [ ] I have manually run all the unit and integration tests in the core
and main modules, and they are all green
- [ ] I have added/updated the documentation
- [ ] I have added an example in the examples repo (only for "big"
features)
- [ ] I have added/updated Spring Boot starter(s) (if applicable)

<!-- "Checklist for adding new maven module" and both embedding-store
checklists omitted: not applicable (this is a one-line fix to an
existing module, no new module and no embedding-store integration
change). -->
This commit is contained in:
Eunbin Son 2026-06-29 17:50:48 +09:00 committed by GitHub
parent af67c71917
commit 166d202876
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 43 additions and 25 deletions

View File

@ -1,5 +1,9 @@
package dev.langchain4j.model.ovhai;
import static dev.langchain4j.internal.RetryUtils.withRetryMappingExceptions;
import static dev.langchain4j.internal.Utils.getOrDefault;
import static java.util.stream.Collectors.toList;
import dev.langchain4j.data.embedding.Embedding;
import dev.langchain4j.data.segment.TextSegment;
import dev.langchain4j.model.embedding.EmbeddingModel;
@ -7,14 +11,9 @@ import dev.langchain4j.model.output.Response;
import dev.langchain4j.model.ovhai.internal.api.EmbeddingRequest;
import dev.langchain4j.model.ovhai.internal.api.EmbeddingResponse;
import dev.langchain4j.model.ovhai.internal.client.DefaultOvhAiClient;
import org.slf4j.Logger;
import java.time.Duration;
import java.util.List;
import static dev.langchain4j.internal.RetryUtils.withRetryMappingExceptions;
import static dev.langchain4j.internal.Utils.getOrDefault;
import static java.util.stream.Collectors.toList;
import org.slf4j.Logger;
/**
* @deprecated use {@code OpenAiEmbeddingModel} from {@code langchain4j-open-ai} module instead
@ -26,16 +25,14 @@ public class OvhAiEmbeddingModel implements EmbeddingModel {
private final int maxRetries;
private OvhAiEmbeddingModel(OvhAiEmbeddingModelBuilder builder) {
this.client =
DefaultOvhAiClient
.builder()
.baseUrl(builder.baseUrl)
.apiKey(builder.apiKey)
.timeout(getOrDefault(builder.timeout, Duration.ofSeconds(60)))
.logRequests(getOrDefault(builder.logRequests, false))
.logResponses(getOrDefault(builder.logResponses, false))
.logger(builder.logger)
.build();
this.client = DefaultOvhAiClient.builder()
.baseUrl(builder.baseUrl)
.apiKey(builder.apiKey)
.timeout(getOrDefault(builder.timeout, Duration.ofSeconds(60)))
.logRequests(getOrDefault(builder.logRequests, false))
.logResponses(getOrDefault(builder.logResponses, false))
.logger(builder.logger)
.build();
this.maxRetries = getOrDefault(builder.maxRetries, 2);
}
@ -56,17 +53,14 @@ public class OvhAiEmbeddingModel implements EmbeddingModel {
@Override
public Response<List<Embedding>> embedAll(List<TextSegment> textSegments) {
EmbeddingRequest request = EmbeddingRequest
.builder()
EmbeddingRequest request = EmbeddingRequest.builder()
.input(textSegments.stream().map(TextSegment::text).collect(toList()))
.build();
EmbeddingResponse response = withRetryMappingExceptions(() -> client.embed((request)), maxRetries);
List<Embedding> embeddings = response.getEmbeddings()
.stream()
.map(Embedding::from)
.collect(toList());
List<Embedding> embeddings =
response.getEmbeddings().stream().map(Embedding::from).collect(toList());
return Response.from(embeddings);
}
@ -80,8 +74,7 @@ public class OvhAiEmbeddingModel implements EmbeddingModel {
private Boolean logResponses;
private Logger logger;
OvhAiEmbeddingModelBuilder() {
}
OvhAiEmbeddingModelBuilder() {}
public OvhAiEmbeddingModelBuilder baseUrl(String baseUrl) {
this.baseUrl = baseUrl;
@ -127,7 +120,10 @@ public class OvhAiEmbeddingModel implements EmbeddingModel {
}
public String toString() {
return "OvhAiEmbeddingModel.OvhAiEmbeddingModelBuilder(baseUrl=" + this.baseUrl + ", apiKey=" + this.apiKey + ", timeout=" + this.timeout + ", maxRetries=" + this.maxRetries + ", logRequests=" + this.logRequests + ", logResponses=" + this.logResponses + ")";
return "OvhAiEmbeddingModel.OvhAiEmbeddingModelBuilder(baseUrl=" + this.baseUrl + ", apiKey="
+ (this.apiKey == null ? null : "********") + ", timeout=" + this.timeout + ", maxRetries="
+ this.maxRetries + ", logRequests=" + this.logRequests + ", logResponses=" + this.logResponses
+ ")";
}
}
}

View File

@ -0,0 +1,22 @@
package dev.langchain4j.model.ovhai;
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.jupiter.api.Test;
class OvhAiEmbeddingModelTest {
@Test
void toString_should_mask_api_key() {
String toString = OvhAiEmbeddingModel.builder().apiKey("secret-api-key").toString();
assertThat(toString).doesNotContain("secret-api-key").contains("apiKey=********");
}
@Test
void toString_should_render_null_api_key_as_null() {
String toString = new OvhAiEmbeddingModel.OvhAiEmbeddingModelBuilder().toString();
assertThat(toString).contains("apiKey=null");
}
}