mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-25 20:05:46 -06:00
fix(google): honor supported Gemini embedding dimensions (#129038)
This commit is contained in:
committed by
GitHub
parent
28c9f9abd8
commit
a556d5379b
@@ -212,7 +212,7 @@ Use `provider: "openai-compatible"` for a generic OpenAI-compatible
|
||||
| Key | Type | Default | Description |
|
||||
| ---------------------- | -------- | ---------------------- | ------------------------------------------- |
|
||||
| `model` | `string` | `gemini-embedding-001` | Also supports `gemini-embedding-2` |
|
||||
| `outputDimensionality` | `number` | `3072` | For Embedding 2: 768, 1536, or 3072 |
|
||||
| `outputDimensionality` | `number` | `3072` | 128-3072; recommended: 768, 1536, or 3072 |
|
||||
|
||||
The legacy `gemini-embedding-2-preview` identifier remains accepted during
|
||||
migration to the stable model.
|
||||
@@ -226,8 +226,10 @@ Use `provider: "openai-compatible"` for a generic OpenAI-compatible
|
||||
configuration. Before this release, the stable model's dimension was
|
||||
omitted from index identity whether `outputDimensionality` was absent or
|
||||
explicitly set. After upgrade, an absent setting resolves to 3072, while an
|
||||
explicit 768, 1536, or 3072 setting becomes part of the identity. For either
|
||||
path, check the affected agent with
|
||||
explicit setting between 128 and 3072 becomes part of the identity. The
|
||||
default `gemini-embedding-001` keeps its existing identity when this setting
|
||||
is absent; an explicitly configured value that was previously ignored now
|
||||
also changes the identity. For either path, check the affected agent with
|
||||
`openclaw memory status --deep --agent <id>`, then rebuild when ready with
|
||||
`openclaw memory index --force --agent <id>`.
|
||||
</Warning>
|
||||
|
||||
@@ -168,19 +168,71 @@ describe("Gemini embedding provider", () => {
|
||||
},
|
||||
);
|
||||
|
||||
it("rejects unsupported Gemini 2 output dimensions through provider creation", async () => {
|
||||
it.each(
|
||||
["gemini-embedding-001", "gemini-embedding-2", "gemini-embedding-2-preview"].flatMap((model) =>
|
||||
[128, 512, 1024, 3072].map((dimensions) => [model, dimensions] as const),
|
||||
),
|
||||
)("supports %s with %i output dimensions", async (model, dimensions) => {
|
||||
const fetchMock = installFetchMock((input) => {
|
||||
const url = input instanceof URL ? input.href : typeof input === "string" ? input : input.url;
|
||||
return url.endsWith(":batchEmbedContents")
|
||||
? { embeddings: [{ values: axisVector(dimensions) }] }
|
||||
: { embedding: { values: axisVector(dimensions) } };
|
||||
});
|
||||
const { provider, client } = await createGeminiEmbeddingProvider({
|
||||
config: {} as never,
|
||||
provider: "gemini",
|
||||
remote: { apiKey: "placeholder" },
|
||||
model,
|
||||
outputDimensionality: dimensions,
|
||||
fallback: "none",
|
||||
});
|
||||
|
||||
expect(client.outputDimensionality).toBe(dimensions);
|
||||
await expect(provider.embedQuery("query")).resolves.toHaveLength(dimensions);
|
||||
await expect(provider.embedBatch(["document"])).resolves.toEqual([axisVector(dimensions)]);
|
||||
expect(fetchJsonBody(fetchMock, 0)).toMatchObject({ outputDimensionality: dimensions });
|
||||
expect(fetchJsonBody(fetchMock, 1)).toMatchObject({
|
||||
requests: [{ outputDimensionality: dimensions }],
|
||||
});
|
||||
});
|
||||
|
||||
it.each(
|
||||
["gemini-embedding-001", "gemini-embedding-2", "gemini-embedding-2-preview"].flatMap((model) =>
|
||||
[127, 512.5, 3073].map((dimensions) => [model, dimensions] as const),
|
||||
),
|
||||
)("rejects unsupported %s dimension %i before making a request", async (model, dimensions) => {
|
||||
await expect(
|
||||
createGeminiEmbeddingProvider({
|
||||
config: {} as never,
|
||||
provider: "gemini",
|
||||
remote: { apiKey: "placeholder" },
|
||||
model: "gemini-embedding-2",
|
||||
outputDimensionality: 1024,
|
||||
model,
|
||||
outputDimensionality: dimensions,
|
||||
fallback: "none",
|
||||
}),
|
||||
).rejects.toThrow(/Valid values: 768, 1536, 3072/);
|
||||
).rejects.toThrow(/integer between 128 and 3072/);
|
||||
});
|
||||
|
||||
it.each([
|
||||
["gemini-embedding-001", undefined],
|
||||
["gemini-embedding-2", 3072],
|
||||
["gemini-embedding-2-preview", 3072],
|
||||
] as const)(
|
||||
"preserves the existing default dimension identity for %s",
|
||||
async (model, dimensions) => {
|
||||
const { client } = await createGeminiEmbeddingProvider({
|
||||
config: {} as never,
|
||||
provider: "gemini",
|
||||
remote: { apiKey: "placeholder" },
|
||||
model,
|
||||
fallback: "none",
|
||||
});
|
||||
|
||||
expect(client.outputDimensionality).toBe(dimensions);
|
||||
},
|
||||
);
|
||||
|
||||
it("handles legacy and v2 request/response behavior", async () => {
|
||||
const fetchMock = installFetchMock((input) => {
|
||||
const url = input instanceof URL ? input.href : typeof input === "string" ? input : input.url;
|
||||
|
||||
@@ -55,7 +55,6 @@ type GeminiTaskType = NonNullable<MemoryEmbeddingProviderCreateOptions["taskType
|
||||
const GEMINI_EMBEDDING_2_MODELS = new Set(["gemini-embedding-2", "gemini-embedding-2-preview"]);
|
||||
|
||||
const GEMINI_EMBEDDING_2_DEFAULT_DIMENSIONS = 3072;
|
||||
const GEMINI_EMBEDDING_2_VALID_DIMENSIONS = [768, 1536, 3072] as const;
|
||||
const GEMINI_EMBEDDING_2_TASK_PREFIXES: Record<GeminiTaskType, string> = {
|
||||
RETRIEVAL_QUERY: "task: search result | query:",
|
||||
RETRIEVAL_DOCUMENT: "title: none | text:",
|
||||
@@ -165,29 +164,22 @@ export function buildGeminiEmbeddingRequest(params: {
|
||||
return request;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the given model name is a gemini-embedding-2 variant that
|
||||
* supports `outputDimensionality` and extended task types.
|
||||
*/
|
||||
/** Returns true for Gemini Embedding 2 variants with multimodal and extended task support. */
|
||||
export function isGeminiEmbedding2Model(model: string): boolean {
|
||||
return GEMINI_EMBEDDING_2_MODELS.has(normalizeGeminiModel(model));
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate and return the `outputDimensionality` for gemini-embedding-2 models.
|
||||
* Returns `undefined` for older models (they don't support the param).
|
||||
*/
|
||||
function resolveGeminiOutputDimensionality(model: string, requested?: number): number | undefined {
|
||||
if (!isGeminiEmbedding2Model(model)) {
|
||||
const isEmbedding2 = isGeminiEmbedding2Model(model);
|
||||
if (!isEmbedding2 && model !== DEFAULT_GEMINI_EMBEDDING_MODEL) {
|
||||
return undefined;
|
||||
}
|
||||
if (requested == null) {
|
||||
return GEMINI_EMBEDDING_2_DEFAULT_DIMENSIONS;
|
||||
return isEmbedding2 ? GEMINI_EMBEDDING_2_DEFAULT_DIMENSIONS : undefined;
|
||||
}
|
||||
const valid: readonly number[] = GEMINI_EMBEDDING_2_VALID_DIMENSIONS;
|
||||
if (!valid.includes(requested)) {
|
||||
if (!Number.isInteger(requested) || requested < 128 || requested > 3072) {
|
||||
throw new Error(
|
||||
`Invalid outputDimensionality ${requested} for ${model}. Valid values: ${valid.join(", ")}`,
|
||||
`Invalid outputDimensionality ${requested} for ${model}. Use an integer between 128 and 3072.`,
|
||||
);
|
||||
}
|
||||
return requested;
|
||||
@@ -302,7 +294,6 @@ export async function createGeminiEmbeddingProvider(
|
||||
client.baseUrl,
|
||||
`${client.modelPath}:batchEmbedContents`,
|
||||
);
|
||||
const isV2 = isGeminiEmbedding2Model(client.model);
|
||||
const outputDimensionality = client.outputDimensionality;
|
||||
|
||||
const embedQuery = async (
|
||||
@@ -320,14 +311,11 @@ export async function createGeminiEmbeddingProvider(
|
||||
model: client.model,
|
||||
role: "query",
|
||||
taskType: options.taskType ?? "RETRIEVAL_QUERY",
|
||||
outputDimensionality: isV2 ? outputDimensionality : undefined,
|
||||
outputDimensionality,
|
||||
}),
|
||||
signal: callOptions?.signal,
|
||||
});
|
||||
return sanitizeGeminiEmbedding(
|
||||
readGeminiSingleEmbedding(payload),
|
||||
isV2 ? outputDimensionality : undefined,
|
||||
);
|
||||
return sanitizeGeminiEmbedding(readGeminiSingleEmbedding(payload), outputDimensionality);
|
||||
};
|
||||
|
||||
const embedBatchInputs = async (
|
||||
@@ -348,16 +336,14 @@ export async function createGeminiEmbeddingProvider(
|
||||
role: "document",
|
||||
modelPath: client.modelPath,
|
||||
taskType: options.taskType ?? "RETRIEVAL_DOCUMENT",
|
||||
outputDimensionality: isV2 ? outputDimensionality : undefined,
|
||||
outputDimensionality,
|
||||
}),
|
||||
),
|
||||
},
|
||||
signal: callOptions?.signal,
|
||||
});
|
||||
const embeddings = readGeminiBatchEmbeddings(payload, inputs.length);
|
||||
return embeddings.map((values) =>
|
||||
sanitizeGeminiEmbedding(values, isV2 ? outputDimensionality : undefined),
|
||||
);
|
||||
return embeddings.map((values) => sanitizeGeminiEmbedding(values, outputDimensionality));
|
||||
};
|
||||
|
||||
const embedBatch = async (
|
||||
|
||||
@@ -225,7 +225,7 @@ export const MODEL_FIELD_HELP: Record<string, string> = {
|
||||
"memory.search.documentInputType":
|
||||
"Optional provider-specific `input_type` value for document and indexing memory embeddings. Use this with OpenAI-compatible asymmetric embedding endpoints that require a passage or document label.",
|
||||
"memory.search.outputDimensionality":
|
||||
"Provider-specific output vector size override for memory embeddings. Gemini embedding-2 supports 768, 1536, or 3072; Bedrock families such as Titan V2, Cohere V4, and Nova expose their own allowed sizes. Expect a full reindex when you change it because stored vector dimensions must stay consistent.",
|
||||
"Provider-specific output vector size override for memory embeddings. Gemini models support 128-3072 dimensions and recommend 768, 1536, or 3072; Bedrock families such as Titan V2, Cohere V4, and Nova expose their own allowed sizes. Expect a full reindex when you change it because stored vector dimensions must stay consistent.",
|
||||
"memory.search.remote.baseUrl":
|
||||
"Overrides the embedding API endpoint, such as an OpenAI-compatible proxy or custom Gemini base URL. Use this only when routing through your own gateway or vendor endpoint; keep provider defaults otherwise.",
|
||||
"memory.search.remote.apiKey":
|
||||
|
||||
@@ -61,8 +61,8 @@ export type MemorySearchConfig = {
|
||||
/** Optional provider-specific embedding input_type for document/index embeddings. */
|
||||
documentInputType?: string;
|
||||
/**
|
||||
* Gemini embedding-2 models only: output vector dimensions.
|
||||
* Supported values today are 768, 1536, and 3072.
|
||||
* Provider-specific output vector dimensions. Gemini supports 128 to 3072.
|
||||
* Google recommends 768, 1536, or 3072 dimensions.
|
||||
*/
|
||||
outputDimensionality?: number;
|
||||
/** Local embedding settings for the managed llama.cpp server. */
|
||||
|
||||
Reference in New Issue
Block a user