Compare commits
2 Commits
55434466d4
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| bb37d9d708 | |||
| 3bd346ea77 |
@@ -21,6 +21,11 @@
|
||||
<groupId>com.easyagents</groupId>
|
||||
<artifactId>easy-agents-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
|
||||
@@ -1,20 +1,7 @@
|
||||
/*
|
||||
* Copyright (c) 2023-2026, Easy-Agents (fuhai999@gmail.com).
|
||||
* <p>
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.easyagents.embedding.openai;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.easyagents.core.document.Document;
|
||||
import com.easyagents.core.model.client.HttpClient;
|
||||
import com.easyagents.core.model.embedding.BaseEmbeddingModel;
|
||||
@@ -24,10 +11,9 @@ import com.easyagents.core.store.VectorData;
|
||||
import com.easyagents.core.util.JSONUtil;
|
||||
import com.easyagents.core.util.Maps;
|
||||
import com.easyagents.core.util.StringUtil;
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
public class OpenAIEmbeddingModel extends BaseEmbeddingModel<OpenAIEmbeddingConfig> {
|
||||
@@ -69,19 +55,63 @@ public class OpenAIEmbeddingModel extends BaseEmbeddingModel<OpenAIEmbeddingConf
|
||||
|
||||
VectorData vectorData = new VectorData();
|
||||
double[] embedding = JSONUtil.readDoubleArray(jsonObject, "$.data[0].embedding");
|
||||
if (embedding == null || embedding.length == 0) {
|
||||
throw new ModelException(buildMissingEmbeddingMessage());
|
||||
}
|
||||
vectorData.setVector(embedding);
|
||||
|
||||
return vectorData;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Builds the embeddings request payload for OpenAI-compatible providers.
|
||||
*
|
||||
* @param text document to embed
|
||||
* @param options embedding request options
|
||||
* @param config model configuration
|
||||
* @return JSON payload for the embeddings endpoint
|
||||
*/
|
||||
public static String promptToEmbeddingsPayload(Document text, EmbeddingOptions options, OpenAIEmbeddingConfig config) {
|
||||
// https://platform.openai.com/docs/api-reference/making-requests
|
||||
return Maps.of("model", options.getModelOrDefault(config.getModel()))
|
||||
String model = options.getModelOrDefault(config.getModel());
|
||||
return Maps.of("model", model)
|
||||
.set("encoding_format", options.getEncodingFormatOrDefault("float"))
|
||||
.set("input", text.getContent())
|
||||
.setIfNotEmpty("user", options.getUser())
|
||||
.setIfNotEmpty("dimensions", options.getDimensions())
|
||||
.setIf(
|
||||
supportsDimensionsParameter(model) && options.getDimensions() != null,
|
||||
"dimensions",
|
||||
options.getDimensions()
|
||||
)
|
||||
.toJSON();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the upstream embeddings endpoint supports dynamic dimensions.
|
||||
*
|
||||
* @param model model name sent to the provider
|
||||
* @return true when dimensions should be sent
|
||||
*/
|
||||
static boolean supportsDimensionsParameter(String model) {
|
||||
if (StringUtil.noText(model)) {
|
||||
return false;
|
||||
}
|
||||
String normalizedModel = model.toLowerCase(Locale.ROOT);
|
||||
return normalizedModel.contains("qwen3-embedding")
|
||||
|| normalizedModel.contains("qwen");
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a safe diagnostic message for malformed embeddings responses.
|
||||
*
|
||||
* @return diagnostic message without secrets or raw response content
|
||||
*/
|
||||
private String buildMissingEmbeddingMessage() {
|
||||
return "Embedding response does not contain data[0].embedding."
|
||||
+ " Please check provider, model, request path, dimensions, or response format."
|
||||
+ " provider=" + config.getProvider()
|
||||
+ ", model=" + config.getModel()
|
||||
+ ", endpoint=" + config.getEndpoint()
|
||||
+ ", requestPath=" + config.getRequestPath();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
package com.easyagents.embedding.openai;
|
||||
|
||||
import com.easyagents.core.document.Document;
|
||||
import com.easyagents.core.model.client.HttpClient;
|
||||
import com.easyagents.core.model.embedding.EmbeddingOptions;
|
||||
import com.easyagents.core.model.exception.ModelException;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Tests for OpenAI-compatible embeddings request payload generation.
|
||||
*/
|
||||
public class OpenAIEmbeddingModelTest {
|
||||
|
||||
/**
|
||||
* Verifies that fixed-dimension embeddings models do not receive dimensions.
|
||||
*/
|
||||
@Test
|
||||
public void shouldNotSendDimensionsForBgeModel() {
|
||||
OpenAIEmbeddingConfig config = new OpenAIEmbeddingConfig();
|
||||
config.setModel("BAAI/bge-m3");
|
||||
EmbeddingOptions options = new EmbeddingOptions();
|
||||
options.setDimensions(1024);
|
||||
|
||||
String payload = OpenAIEmbeddingModel.promptToEmbeddingsPayload(Document.of("hello"), options, config);
|
||||
|
||||
Assert.assertFalse(payload.contains("\"dimensions\""));
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that Qwen3 embedding models keep the dynamic dimensions parameter.
|
||||
*/
|
||||
@Test
|
||||
public void shouldSendDimensionsForQwen3EmbeddingModel() {
|
||||
OpenAIEmbeddingConfig config = new OpenAIEmbeddingConfig();
|
||||
config.setModel("Qwen/Qwen3-Embedding-8B");
|
||||
EmbeddingOptions options = new EmbeddingOptions();
|
||||
options.setDimensions(1024);
|
||||
|
||||
String payload = OpenAIEmbeddingModel.promptToEmbeddingsPayload(Document.of("hello"), options, config);
|
||||
|
||||
Assert.assertTrue(payload.contains("\"dimensions\":1024"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that malformed embeddings responses fail with a clear model exception.
|
||||
*/
|
||||
@Test
|
||||
public void shouldThrowModelExceptionWhenEmbeddingMissing() {
|
||||
OpenAIEmbeddingConfig config = new OpenAIEmbeddingConfig();
|
||||
config.setProvider("test-provider");
|
||||
config.setModel("BAAI/bge-m3");
|
||||
config.setApiKey("test-key");
|
||||
OpenAIEmbeddingModel model = new OpenAIEmbeddingModel(config);
|
||||
model.setHttpClient(new HttpClient() {
|
||||
@Override
|
||||
public String post(String url, Map<String, String> headers, String payload) {
|
||||
return "{\"data\":[{}]}";
|
||||
}
|
||||
});
|
||||
|
||||
ModelException exception = Assert.assertThrows(
|
||||
ModelException.class,
|
||||
() -> model.embed(Document.of("hello"))
|
||||
);
|
||||
|
||||
Assert.assertTrue(exception.getMessage().contains("data[0].embedding"));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user