Merge pull request 'fix: 修复增加 FAQ 报错的 bug' (#1) from hotfix/embedding_error into main
Reviewed-on: #1
This commit was merged in pull request #1.
This commit is contained in:
@@ -21,6 +21,11 @@
|
|||||||
<groupId>com.easyagents</groupId>
|
<groupId>com.easyagents</groupId>
|
||||||
<artifactId>easy-agents-core</artifactId>
|
<artifactId>easy-agents-core</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>junit</groupId>
|
||||||
|
<artifactId>junit</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
</project>
|
</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;
|
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.document.Document;
|
||||||
import com.easyagents.core.model.client.HttpClient;
|
import com.easyagents.core.model.client.HttpClient;
|
||||||
import com.easyagents.core.model.embedding.BaseEmbeddingModel;
|
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.JSONUtil;
|
||||||
import com.easyagents.core.util.Maps;
|
import com.easyagents.core.util.Maps;
|
||||||
import com.easyagents.core.util.StringUtil;
|
import com.easyagents.core.util.StringUtil;
|
||||||
import com.alibaba.fastjson2.JSON;
|
|
||||||
import com.alibaba.fastjson2.JSONObject;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
public class OpenAIEmbeddingModel extends BaseEmbeddingModel<OpenAIEmbeddingConfig> {
|
public class OpenAIEmbeddingModel extends BaseEmbeddingModel<OpenAIEmbeddingConfig> {
|
||||||
@@ -69,19 +55,63 @@ public class OpenAIEmbeddingModel extends BaseEmbeddingModel<OpenAIEmbeddingConf
|
|||||||
|
|
||||||
VectorData vectorData = new VectorData();
|
VectorData vectorData = new VectorData();
|
||||||
double[] embedding = JSONUtil.readDoubleArray(jsonObject, "$.data[0].embedding");
|
double[] embedding = JSONUtil.readDoubleArray(jsonObject, "$.data[0].embedding");
|
||||||
|
if (embedding == null || embedding.length == 0) {
|
||||||
|
throw new ModelException(buildMissingEmbeddingMessage());
|
||||||
|
}
|
||||||
vectorData.setVector(embedding);
|
vectorData.setVector(embedding);
|
||||||
|
|
||||||
return vectorData;
|
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) {
|
public static String promptToEmbeddingsPayload(Document text, EmbeddingOptions options, OpenAIEmbeddingConfig config) {
|
||||||
// https://platform.openai.com/docs/api-reference/making-requests
|
String model = options.getModelOrDefault(config.getModel());
|
||||||
return Maps.of("model", options.getModelOrDefault(config.getModel()))
|
return Maps.of("model", model)
|
||||||
.set("encoding_format", options.getEncodingFormatOrDefault("float"))
|
.set("encoding_format", options.getEncodingFormatOrDefault("float"))
|
||||||
.set("input", text.getContent())
|
.set("input", text.getContent())
|
||||||
.setIfNotEmpty("user", options.getUser())
|
.setIfNotEmpty("user", options.getUser())
|
||||||
.setIfNotEmpty("dimensions", options.getDimensions())
|
.setIf(
|
||||||
|
supportsDimensionsParameter(model) && options.getDimensions() != null,
|
||||||
|
"dimensions",
|
||||||
|
options.getDimensions()
|
||||||
|
)
|
||||||
.toJSON();
|
.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