TL;DR
- Gemini の埋め込み(REST)で
outputDimensionalityをembedContentConfigの中に書くと無視され、初期値の 3072 次元で返ってきます。batch でも単発でも同じで、エラーにはなりません - 上の階層(
contentと同じ階層)に書くと効きます - Cloudflare Vectorize の次元は 1536 までなので、3072 次元はそもそも入りません。返ってきた長さを確かめ、長すぎたら先頭を切って正規化し直す守りを入れました
はじめに
広島で受託開発をしている株式会社フラップという小さな会社をやっています。
静的な HTML サイトに「お知らせ」「予約」「ネットショップ」などの部品をあとから追加して、お店の人が自分で更新できるようにする CMS「ツギテ」というサービスをはじめました。
最近、部品の1つとして AI チャット(サイトの中身から答えるチャットボット)を追加しました。
サイトの中身を Gemini の埋め込みでベクトルにして、Cloudflare Vectorize で探す、よくある RAG の作りです。
ところが本番に出したその日に、材料の取り込みが止まりました。
原因は Gemini の埋め込みが 768 次元ではなく 3072 次元で返ってきていたことでした。
API のリファレンスどおりに書いたのに、指定が効いていなかったのです。
同じところで止まる人がいそうなので、確かめた結果をまとめます。
環境
- Cloudflare Workers(TypeScript)
- Cloudflare Vectorize(768 次元・cosine で作った index)
- Gemini API の
gemini-embedding-001(REST、v1beta) - 確かめた日:2026-11-06
やりたかったこと
サイトの中身(お知らせ・商品・営業日など)を500文字くらいに分けて、100件ずつ batchEmbedContents で埋め込み、Vectorize に入れます。
index は 768 次元で作りました。768 は Google がすすめる大きさの1つ(768・1536・3072)で、保存の量と精度のバランスがよいためです。
npx wrangler vectorize create my-chat-index --dimensions=768 --metric=cosine
起きたこと
Vectorize に入れるところで、次のエラーになりました(wrangler から同じことを試したときの文です)。
invalid vector for id="test-1", expected 768 dimensions, and got 3072 dimensions; [code: 40012]
768 次元の index に、3072 次元のベクトルを入れようとしていました。
原因:embedContentConfig の中に書いた指定が効いていなかった
最初はこう書いていました。API のリファレンスでは、taskType や outputDimensionality は EmbedContentConfig の中の欄として説明されているので、それに合わせた形です。
const res = await fetch(
"https://generativelanguage.googleapis.com/v1beta/models/gemini-embedding-001:batchEmbedContents",
{
method: "POST",
headers: { "Content-Type": "application/json", "x-goog-api-key": env.GEMINI_API_KEY },
body: JSON.stringify({
requests: texts.map((text) => ({
model: "models/gemini-embedding-001",
content: { parts: [{ text }] },
// リファレンスに合わせて中に書いた(効かなかった)
embedContentConfig: { taskType: "RETRIEVAL_DOCUMENT", outputDimensionality: 768 },
})),
}),
},
);
書き方を変えて、返ってきたベクトルの長さを比べました。
| API | 書き方 | 返った長さ | HTTP |
|---|---|---|---|
batchEmbedContents |
embedContentConfig の中だけ |
3072 | 200 |
batchEmbedContents |
上の階層だけ | 768 | 200 |
batchEmbedContents |
両方 | 768 | 200 |
embedContent |
embedContentConfig の中だけ |
3072 | 200 |
embedContent |
上の階層だけ | 768 | 200 |
embedContentConfig の中に書くと、batch でも単発でも無視され、初期値の 3072 で返ります。 上の階層(content と同じ階層)に書くと効きます。
やっかいなのは、どの書き方でも 200 で返り、エラーにならないことです。返ったベクトルの長さを見るまで気づけません。
手元で確かめるなら、次の curl で長さが見られます。
# embedContentConfig の中だけ → 3072
curl -s "https://generativelanguage.googleapis.com/v1beta/models/gemini-embedding-001:batchEmbedContents" \
-H "x-goog-api-key: $GEMINI_API_KEY" -H "Content-Type: application/json" \
-d '{"requests":[{"model":"models/gemini-embedding-001","content":{"parts":[{"text":"駐車場は3台分あります。"}]},
"embedContentConfig":{"taskType":"RETRIEVAL_DOCUMENT","outputDimensionality":768}}]}' \
| jq '.embeddings[0].values | length'
# => 3072
# 上の階層 → 768
curl -s "https://generativelanguage.googleapis.com/v1beta/models/gemini-embedding-001:batchEmbedContents" \
-H "x-goog-api-key: $GEMINI_API_KEY" -H "Content-Type: application/json" \
-d '{"requests":[{"model":"models/gemini-embedding-001","content":{"parts":[{"text":"駐車場は3台分あります。"}]},
"taskType":"RETRIEVAL_DOCUMENT","outputDimensionality":768}]}' \
| jq '.embeddings[0].values | length'
# => 768
なお、埋め込みの案内のページの curl の例は、上の階層に書く形になっています。リファレンスと案内のページで書き方が違い、実際に効くのは案内のページの形でした。
taskType が効いているかどうかは、ベクトルの長さからは分かりません。ただ、同じ所に書いた outputDimensionality が無視されているので、taskType も無視されていると考えるのが自然です。中だけに書いていた間は、文書用・質問用の区別もされていなかった可能性があります。
直し方
上の階層に書きます。あとで API の動きが変わっても困らないよう、embedContentConfig の中にも同じ値を入れておきました(両方に書いても、上の表のとおり 768 で返ります)。
requests: texts.map((text) => {
const taskType = "RETRIEVAL_DOCUMENT"; // 質問を埋め込むときは "RETRIEVAL_QUERY"
return {
model: "models/gemini-embedding-001",
content: { parts: [{ text }] },
// 上の階層に書かないと効かない
taskType,
outputDimensionality: 768,
// 念のため中にも同じ値
embedContentConfig: { taskType, outputDimensionality: 768 },
};
}),
念のための守り:長さをそろえて、正規化し直す
指定が効かなくなる日がまた来ても止まらないよう、返ってきた長さを確かめる関数を通しています。
const EMBED_DIMENSIONS = 768;
function normalize(values: number[]): number[] {
const len = Math.sqrt(values.reduce((s, v) => s + v * v, 0)) || 1;
return values.map((v) => v / len);
}
export function fitDimensions(values: number[]): number[] {
if (values.length === EMBED_DIMENSIONS) return normalize(values);
if (values.length > EMBED_DIMENSIONS) {
// 先頭を切って使える作りなので、切ってから正規化し直す
console.warn("gemini embed dimensions", values.length, "→", EMBED_DIMENSIONS);
return normalize(values.slice(0, EMBED_DIMENSIONS));
}
console.error("gemini embed dimensions too short", values.length);
throw new Error("embed_failed");
}
ポイントは2つです。
-
先頭を切ってよい理由:
gemini-embedding-001は Matryoshka Representation Learning(MRL)で学習していて、先頭から必要な長さだけ切り出して使える作りです。768・1536 といった小さい大きさを選べるのも、この作りのおかげです。 - 正規化し直す理由:3072 次元のベクトルは長さ1に正規化されていますが、先頭を切ると長さが1でなくなります。案内のページにも、3072 以外の大きさでは自分で正規化するよう書かれています。cosine で比べるなら、768 で正しく返ってきたときも正規化しておくのが安全です。
短いときは直しようがないので、止めてログに長さを出します。
「index を 3072 次元で作り直せばよいのでは?」
それはできません。Vectorize の index の次元は 1536 までなので、3072 次元はそもそも入りません。また、index の次元は作ったあとに変えられません。
Vectorize を使うなら、Gemini 側で 1536 以下に小さくするしかありません。
失敗を見えるようにした
この件では、取り込みが止まっていたことに、お店の画面からは気づけませんでした。そこで、止まった理由を管理画面に出すようにしました。
- 「Gemini の鍵がありません」
- 「Gemini の埋め込みに失敗しました」
- 「Vectorize に入れられませんでした」
Vectorize に入れる所は、失敗したら理由を残してから止めます。
async function vectorCall<T>(run: () => Promise<T>): Promise<T> {
try {
return await run();
} catch (e) {
console.error("vectorize failed", e instanceof Error ? e.message : "unknown");
throw new Error("vector_failed"); // 画面に「Vectorize に入れられませんでした」
}
}
await vectorCall(() =>
env.VECTORS.upsert(rows.map((r, i) => ({ id: r.id, values: vectors[i], namespace: siteId }))),
);
まとめ
- Gemini の埋め込み(REST)で
outputDimensionalityをembedContentConfigの中に書くと、無視されて 3072 次元で返る。batch でも単発でも同じで、エラーにはならない(2026-11-06 時点) - 上の階層に書けば効く。念のため両方に書き、返ってきた長さも確かめる
- 長すぎたら先頭を切って正規化し直す(MRL の作りなので切ってよい)
- Vectorize の次元は 1536 まで。3072 次元の index は作れない
おわりに
この AI チャットは、ツギテの部品として使えます。サイトの中身(お知らせ・商品・営業日・予約の空きなど)から答え、分からないことはお問い合わせのフォームへ案内します。
架空の喫茶店「ことり珈琲」のデモを置いていて、標準語と広島弁を切り替えて、その場で話しかけられます。よかったら試してみてください。