diff --git a/agentscope-extensions/agentscope-extensions-rag/agentscope-extensions-rag-simple/src/main/java/io/agentscope/core/embedding/openai/OpenAITextEmbedding.java b/agentscope-extensions/agentscope-extensions-rag/agentscope-extensions-rag-simple/src/main/java/io/agentscope/core/embedding/openai/OpenAITextEmbedding.java index 35290f27a0..f1c8339cf7 100644 --- a/agentscope-extensions/agentscope-extensions-rag/agentscope-extensions-rag-simple/src/main/java/io/agentscope/core/embedding/openai/OpenAITextEmbedding.java +++ b/agentscope-extensions/agentscope-extensions-rag/agentscope-extensions-rag-simple/src/main/java/io/agentscope/core/embedding/openai/OpenAITextEmbedding.java @@ -47,9 +47,12 @@ public class OpenAITextEmbedding implements EmbeddingModel { private static final Logger log = LoggerFactory.getLogger(OpenAITextEmbedding.class); + /** Default dimension when not explicitly configured, per EmbeddingModel interface contract. */ + private static final int DEFAULT_DIMENSIONS = 1024; + private final String apiKey; private final String modelName; - private final int dimensions; + private final Integer dimensions; private final ExecutionConfig defaultExecutionConfig; private final String baseUrl; @@ -57,6 +60,9 @@ public class OpenAITextEmbedding implements EmbeddingModel { /** * Creates a new OpenAI text embedding model instance. * + *
This constructor is kept for binary compatibility with existing compiled code. + * It delegates to {@link #OpenAITextEmbedding(String, String, Integer, ExecutionConfig, String)}. + * * @param apiKey the API key for OpenAI authentication * @param modelName the model name (e.g., "text-embedding-3-small") * @param dimensions the dimension of embedding vectors @@ -69,6 +75,32 @@ public OpenAITextEmbedding( int dimensions, ExecutionConfig defaultExecutionConfig, String baseUrl) { + this(apiKey, modelName, Integer.valueOf(dimensions), defaultExecutionConfig, baseUrl); + } + + /** + * Creates a new OpenAI text embedding model instance with optional dimensions. + * + *
When {@code dimensions} is null, the dimensions parameter will not be sent
+ * to the API, allowing open-source models that don't support matryoshka
+ * representation (e.g., BAAI/bge-large-zh-v1.5) to work correctly.
+ *
+ * @param apiKey the API key for OpenAI authentication
+ * @param modelName the model name (e.g., "text-embedding-3-small")
+ * @param dimensions the dimension of embedding vectors (null to omit from API request)
+ * @param defaultExecutionConfig default execution configuration for timeout and retry
+ * @param baseUrl custom base URL for OpenAI API (null for default)
+ * @throws IllegalArgumentException if dimensions is non-null and not positive
+ */
+ public OpenAITextEmbedding(
+ String apiKey,
+ String modelName,
+ Integer dimensions,
+ ExecutionConfig defaultExecutionConfig,
+ String baseUrl) {
+ if (dimensions != null && dimensions <= 0) {
+ throw new IllegalArgumentException("dimensions must be positive, got: " + dimensions);
+ }
this.apiKey = apiKey;
this.modelName = modelName;
this.dimensions = dimensions;
@@ -132,15 +164,19 @@ public Mono Returns the explicitly configured dimensions, or {@value #DEFAULT_DIMENSIONS} if {@code
+ * dimensions} was never configured (per the {@link EmbeddingModel#getDimensions()} contract,
+ * which documents 1024 as the default when unspecified). This fallback is a placeholder only:
+ * for open-source models where dimensions is intentionally left unset, the true embedding
+ * vector length returned by {@link #embed} may differ from this value. Callers needing the
+ * exact size should inspect the length of the array returned by {@link #embed}, not this
+ * getter.
+ */
@Override
public int getDimensions() {
- return dimensions;
+ return dimensions != null ? dimensions : DEFAULT_DIMENSIONS;
}
/**
@@ -234,7 +282,7 @@ public int getDimensions() {
public static class Builder {
private String apiKey;
private String modelName;
- private int dimensions = 1536;
+ private Integer dimensions = 1536;
private ExecutionConfig defaultExecutionConfig;
private String baseUrl;
@@ -261,12 +309,21 @@ public Builder modelName(String modelName) {
}
/**
- * Sets the dimension of embedding vectors.
+ * Sets the dimension of embedding vectors. Defaults to 1536.
+ *
+ * When set to a positive value, the dimensions parameter will be included in the API
+ * request. This is only supported by OpenAI official embedding models (e.g.,
+ * text-embedding-3-small, text-embedding-3-large) that support matryoshka
+ * representation.
+ *
+ * For open-source models (e.g., BAAI/bge-large-zh-v1.5) that do not support this
+ * parameter and will return a 400 error if it is sent, pass {@code null} to omit the
+ * parameter from the request entirely.
*
- * @param dimensions the dimension
+ * @param dimensions the dimension, or null to omit the parameter from the API request
* @return this builder instance
*/
- public Builder dimensions(int dimensions) {
+ public Builder dimensions(Integer dimensions) {
this.dimensions = dimensions;
return this;
}
@@ -311,7 +368,7 @@ public OpenAITextEmbedding build() {
throw new IllegalStateException(
"modelName is required and cannot be null or empty");
}
- if (dimensions <= 0) {
+ if (dimensions != null && dimensions <= 0) {
throw new IllegalStateException("dimensions must be positive, got: " + dimensions);
}
diff --git a/agentscope-extensions/agentscope-extensions-rag/agentscope-extensions-rag-simple/src/test/java/io/agentscope/core/embedding/openai/OpenAITextEmbeddingEmbedTest.java b/agentscope-extensions/agentscope-extensions-rag/agentscope-extensions-rag-simple/src/test/java/io/agentscope/core/embedding/openai/OpenAITextEmbeddingEmbedTest.java
index d8185974d0..6f8da795c7 100644
--- a/agentscope-extensions/agentscope-extensions-rag/agentscope-extensions-rag-simple/src/test/java/io/agentscope/core/embedding/openai/OpenAITextEmbeddingEmbedTest.java
+++ b/agentscope-extensions/agentscope-extensions-rag/agentscope-extensions-rag-simple/src/test/java/io/agentscope/core/embedding/openai/OpenAITextEmbeddingEmbedTest.java
@@ -314,4 +314,100 @@ void testEmptyEmbeddingListInResponse() {
.verify();
}
}
+
+ @Test
+ @DisplayName("Should successfully embed without dimensions parameter (open-source models)")
+ void testEmbedWithoutDimensions() {
+ // Explicitly omit dimensions - simulates open-source model usage
+ OpenAITextEmbedding modelWithoutDimensions =
+ OpenAITextEmbedding.builder()
+ .apiKey("mock_api_key")
+ .modelName("BAAI/bge-large-zh-v1.5")
+ .dimensions(null)
+ .executionConfig(ExecutionConfig.builder().maxAttempts(1).build())
+ .build();
+
+ // Prepare mock response
+ Embedding mockEmbedding = mock(Embedding.class);
+ when(mockEmbedding.embedding()).thenReturn(Arrays.asList(0.5f, 0.6f, 0.7f, 0.8f));
+
+ CreateEmbeddingResponse mockResponse = mock(CreateEmbeddingResponse.class);
+ when(mockResponse.data()).thenReturn(Arrays.asList(mockEmbedding));
+
+ try (MockedStatic