mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-18 18:04:45 +00:00
fix(bedrock): wrap malformed embedding json
This commit is contained in:
@@ -60,6 +60,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Google Meet: report malformed browser-control status JSON with plugin-owned errors instead of leaking raw parser failures.
|
||||
- Google provider: report malformed SSE stream JSON with provider-owned errors instead of leaking raw parser failures.
|
||||
- Node host: report malformed built-in invoke `paramsJSON` with stable invalid-request errors instead of leaking raw parser failures.
|
||||
- Amazon Bedrock embeddings: report malformed provider response JSON with provider-owned errors instead of leaking raw parser failures.
|
||||
- Models config/auth: stop inferring provider env-var markers from broad `^[A-Z_][A-Z0-9_]*$` strings, and resolve config-backed provider `apiKey` values only through structured env SecretRefs (`secrets.providers[id]` / `secrets.defaults`), so unrelated env vars cannot accidentally become provider credentials. Thanks @sallyom.
|
||||
- Media fetch: skip allocating and buffering the response body for bodyless media responses (HEAD probes and 204-style empty bodies), avoiding wasted heap on streams that carry no payload. Thanks @shakkernerd.
|
||||
- CLI/onboarding: forward provider-specific auth flags (e.g. `--openai-api-key`) through the onboarding wizard so they reach provider auth methods via `ctx.opts`, letting `--openai-api-key "$OPENAI_API_KEY"` skip the redundant "use existing env var?" prompt in non-interactive harnesses. (#81669) Thanks @sjf.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { hasAwsCredentials } from "./embedding-provider.js";
|
||||
import { __testing, hasAwsCredentials } from "./embedding-provider.js";
|
||||
|
||||
describe("hasAwsCredentials", () => {
|
||||
it("accepts static AWS key credentials without loading the credential chain", async () => {
|
||||
@@ -63,3 +63,17 @@ describe("hasAwsCredentials", () => {
|
||||
await expect(hasAwsCredentials({}, loadCredentialProvider)).resolves.toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("bedrock embedding response parsers", () => {
|
||||
it("wraps malformed single embedding JSON", () => {
|
||||
expect(() => __testing.parseSingle("titan-v2", "{not json")).toThrow(
|
||||
"Amazon Bedrock embedding response returned malformed JSON",
|
||||
);
|
||||
});
|
||||
|
||||
it("wraps malformed batch embedding JSON", () => {
|
||||
expect(() => __testing.parseCohereBatch("cohere-v3", "{not json")).toThrow(
|
||||
"Amazon Bedrock embedding response returned malformed JSON",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -224,37 +224,69 @@ function buildCohereBody(
|
||||
// Response parsers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
type BedrockEmbeddingResponseJson = {
|
||||
embedding?: unknown;
|
||||
embeddings?: unknown;
|
||||
data?: unknown;
|
||||
};
|
||||
|
||||
function parseBedrockEmbeddingResponseJson(raw: string): BedrockEmbeddingResponseJson {
|
||||
try {
|
||||
const parsed = JSON.parse(raw) as unknown;
|
||||
return parsed && typeof parsed === "object" && !Array.isArray(parsed)
|
||||
? (parsed as BedrockEmbeddingResponseJson)
|
||||
: {};
|
||||
} catch {
|
||||
throw new Error("Amazon Bedrock embedding response returned malformed JSON");
|
||||
}
|
||||
}
|
||||
|
||||
function asNumberArray(value: unknown): number[] {
|
||||
return Array.isArray(value) ? (value as number[]) : [];
|
||||
}
|
||||
|
||||
function asNumberArrayBatch(value: unknown): number[][] {
|
||||
return Array.isArray(value) ? (value.filter(Array.isArray) as number[][]) : [];
|
||||
}
|
||||
|
||||
function parseSingle(family: Family, raw: string): number[] {
|
||||
const data = JSON.parse(raw);
|
||||
const data = parseBedrockEmbeddingResponseJson(raw);
|
||||
switch (family) {
|
||||
case "nova":
|
||||
return data.embeddings?.[0]?.embedding ?? [];
|
||||
return asNumberArray(Array.isArray(data.embeddings) ? data.embeddings[0]?.embedding : null);
|
||||
case "twelvelabs": {
|
||||
if (Array.isArray(data.data)) {
|
||||
return data.data[0]?.embedding ?? [];
|
||||
return asNumberArray(data.data[0]?.embedding);
|
||||
}
|
||||
if (Array.isArray(data.data?.embedding)) {
|
||||
return data.data.embedding;
|
||||
if (data.data && typeof data.data === "object") {
|
||||
return asNumberArray((data.data as { embedding?: unknown }).embedding);
|
||||
}
|
||||
return data.embedding ?? [];
|
||||
return asNumberArray(data.embedding);
|
||||
}
|
||||
default:
|
||||
return data.embedding ?? [];
|
||||
return asNumberArray(data.embedding);
|
||||
}
|
||||
}
|
||||
|
||||
function parseCohereBatch(family: Family, raw: string): number[][] {
|
||||
const data = JSON.parse(raw);
|
||||
const data = parseBedrockEmbeddingResponseJson(raw);
|
||||
const embeddings = data.embeddings;
|
||||
if (!embeddings) {
|
||||
return [];
|
||||
}
|
||||
if (family === "cohere-v4" && !Array.isArray(embeddings)) {
|
||||
return embeddings.float ?? [];
|
||||
return embeddings && typeof embeddings === "object"
|
||||
? asNumberArrayBatch((embeddings as { float?: unknown }).float)
|
||||
: [];
|
||||
}
|
||||
return embeddings;
|
||||
return asNumberArrayBatch(embeddings);
|
||||
}
|
||||
|
||||
export const __testing = {
|
||||
parseCohereBatch,
|
||||
parseSingle,
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Provider
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user