@molecule/api-ai-embeddings-gemini

Provider bond · ai-embeddings · API (Node) · v1.0.1 · Apache-2.0

Gemini API ai-embeddings provider for molecule.dev — hosted gemini-embedding-2: text, images, audio and video in one vector space (REST via fetch, zero dependencies)

npm install @molecule/api-ai-embeddings-gemini

npm · Source on GitHub · Implements @molecule/api-ai-embeddings

How it works

@molecule/api-ai-embeddings-gemini is a provider bond on the API (Node) side: it implements the ai-embeddings core interface (@molecule/api-ai-embeddings) with a concrete vendor or library behind it.

Your code calls the core; you wire this provider once at startup. Swapping vendors later is one line in that wiring, not a rewrite.

import { readFile } from 'node:fs/promises'

import { requireProvider, setProvider } from '@molecule/api-ai-embeddings'
import { createProvider } from '@molecule/api-ai-embeddings-gemini'

// Reads GOOGLE_AI_API_KEY (or GEMINI_API_KEY) per request.
setProvider(createProvider({ dimensions: 768 }))

const embeddings = requireProvider()
const queryVector = await embeddings.embedQuery('a turtle swimming in the ocean')

// One input = one vector; media need their MIME type.
const {
  embeddings: [clipVector],
} = await embeddings.embedContent!({
  inputs: [{ video: { data: await readFile('dive.mp4'), mimeType: 'video/mp4' } }],
})
console.log(queryVector.length, clipVector?.length) // 768 768

Works with: @molecule/api-ai-embeddings, @molecule/api-secrets

Secrets: GOOGLE_AI_API_KEY

Reference

Gemini API ai-embeddings provider for molecule.dev.

Hosted gemini-embedding-2: text, images, audio and video in one vector space (128–3072 dimensions, default 3072), over Google's REST API with the runtime's fetch — no SDK. Task prefixes are applied for you.

Auto-generated, AI-first package reference for the molecule.dev ecosystem. It is written to be read by coding agents as much as by people, and is generated from this package's source — edit src/index.ts JSDoc, not this file.

Quick Start

import { readFile } from 'node:fs/promises'

import { requireProvider, setProvider } from '@molecule/api-ai-embeddings'
import { createProvider } from '@molecule/api-ai-embeddings-gemini'

// Reads GOOGLE_AI_API_KEY (or GEMINI_API_KEY) per request.
setProvider(createProvider({ dimensions: 768 }))

const embeddings = requireProvider()
const queryVector = await embeddings.embedQuery('a turtle swimming in the ocean')

// One input = one vector; media need their MIME type.
const {
  embeddings: [clipVector],
} = await embeddings.embedContent!({
  inputs: [{ video: { data: await readFile('dive.mp4'), mimeType: 'video/mp4' } }],
})
console.log(queryVector.length, clipVector?.length) // 768 768

Type

provider

Installation

npm install @molecule/api-ai-embeddings-gemini @molecule/api-ai-embeddings @molecule/api-secrets

API

Interfaces

GeminiEmbeddingsConfig

Configuration for the Gemini API embeddings provider.

interface GeminiEmbeddingsConfig {
  /** API key. Defaults to the `GOOGLE_AI_API_KEY` env var, then `GEMINI_API_KEY`. */
  apiKey?: string
  /** Default model. Defaults to `gemini-embedding-2`. */
  defaultModel?: string
  /**
   * API base URL including the version. Defaults to `GOOGLE_AI_BASE_URL` or
   * `https://generativelanguage.googleapis.com/v1beta`.
   */
  baseUrl?: string
  /** Default output size, 128–3072 (`output_dimensionality`). Omitted = the model default (3072). */
  dimensions?: number
  /** Default task for prefixes. Defaults to `search`. */
  task?: EmbeddingTask
  /**
   * Prepend the task prefixes Google recommends (`task: search result | query: …`,
   * `title: none | text: …`). Defaults to `true`; text that already starts with
   * `task: ` or `title: ` is never prefixed twice.
   */
  applyPrefixes?: boolean
  /** Inputs per `batchEmbedContents` request. Defaults to 100; larger lists are split. */
  maxBatchSize?: number
}

Classes

GeminiEmbeddingsError

A Gemini API request failed. Carries the vendor's HTTP status and its error code (e.g. INVALID_ARGUMENT, RESOURCE_EXHAUSTED). Deliberately NOT tagged with statusCode/errorKey, so API middleware answers with its generic 500 instead of echoing Google's status to the caller.

class GeminiEmbeddingsError extends Error {
  /** HTTP status Google answered with. */
  readonly status: number
  /** Google's error status string, when it sent one. */
  readonly code: string | undefined

  /**
   * Creates the error.
   *
   * @param message - Human-readable detail from Google.
   * @param status - HTTP status.
   * @param code - Google's error status string.
   */
  constructor(message: string, status: number, code?: string) {
    super(`Gemini Embeddings API error (HTTP ${status}${code ? ` ${code}` : ''}): ${message}`)
    this.name = 'GeminiEmbeddingsError'
    this.status = status
    this.code = code
  }
}

Functions

applyTaskPrefix(text, task, inputType)

Prefix one text for the given task and side.

Asymmetric tasks: a query becomes task: <task> | query: <text> and a document title: none | text: <text>. Symmetric tasks (classification, clustering, similarity) use the query form for every input.

function applyTaskPrefix(text: string, task: EmbeddingTask, inputType: EmbeddingInputType): string
  • text — The raw text.
  • task — What the vectors are for.
  • inputType — Query or document side.

Returns: The prefixed text (unchanged if it already carries a prefix).

isEmbeddingTask(value)

Whether a string is a supported task name (for env-var parsing).

function isEmbeddingTask(value: string): boolean
  • value — Candidate task name.

Returns: true if it is an EmbeddingTask.

createProvider(config)

Creates a Gemini API embeddings provider.

function createProvider(config?: GeminiEmbeddingsConfig): AIEmbeddingsProvider
  • config — Optional configuration (key, model, base URL, dimensions, task).

Returns: An AIEmbeddingsProvider backed by gemini-embedding-2.

Constants

GEMINI_EMBEDDINGS_DEFAULT_BASE_URL

Default API base URL (version included).

const GEMINI_EMBEDDINGS_DEFAULT_BASE_URL: 'https://generativelanguage.googleapis.com/v1beta'

GEMINI_EMBEDDINGS_DEFAULT_MODEL

Default model: Google's hosted multimodal embedding model.

const GEMINI_EMBEDDINGS_DEFAULT_MODEL: 'gemini-embedding-2'

provider

The provider implementation (reads GOOGLE_AI_API_KEY per request). Bond it with the ai-embeddings core's setProvider.

const provider: AIEmbeddingsProvider

aiEmbeddingsGeminiSecretDefinitions

Secret definitions required by the Gemini API embeddings bond.

const aiEmbeddingsGeminiSecretDefinitions: SecretDefinition[]

Core Interface

Implements @molecule/api-ai-embeddings interface.

Bond Wiring

Setup function to register this provider with the core interface:

import { setProvider } from '@molecule/api-ai-embeddings'
import { provider } from '@molecule/api-ai-embeddings-gemini'

export function setupAiEmbeddingsGemini(): void {
  setProvider(provider)
}

Injection Notes

Requirements

Peer dependencies:

  • @molecule/api-ai-embeddings >=1.1.0
  • @molecule/api-secrets ^1.0.1

Environment Variables

  • GOOGLE_AI_API_KEY (required) — Google AI (Gemini) API key

Runtime Dependencies

  • @molecule/api-ai-embeddings

  • @molecule/api-secrets

  • Use embedQuery for what the user types and embedDocuments for what you index. gemini-embedding-2 rejects task_type; the task goes in the text (task: search result | query: … vs title: none | text: …), which this bond adds. embed() treats input as DOCUMENTS unless you pass inputType: 'query'. Text already starting with task: / title: is left alone; applyPrefixes: false turns prefixing off.

  • This is NOT EmbeddingGemma 2. gemini-embedding-2 is a different, proprietary model with its own vector space — not comparable with @molecule/api-ai-embeddings-embeddinggemma, gemini-embedding-001, OpenAI or bge vectors, nor across dimensions settings. Switching re-embeds the index.

  • Media need a MIME type: pass { data: bytes, mimeType } (sent inline, base64) or { data: fileUri, mimeType } for a Files API upload. Bare bytes or a bare URL throw. Supported: PNG/JPEG images (max 6 per request), MP3/WAV audio (max 180 s), MP4/MOV video (max 120 s, ≤32 frames sampled, its audio track ignored). One input's 8,192 tokens are shared by all its parts; Google silently truncates anything past that.

  • One embedContent input = one vector, even with several parts (Gemini aggregates them). <|image|>-style placeholders are removed from the text — media are sent as their own parts.

  • Billed per token — text, image, audio and video are priced differently, and video is the most expensive. usage.promptTokens is Google's promptTokenCount. Auth + rate-limit any endpoint that embeds caller input.

  • dimensions must be an integer 128–3072; vectors below 3072 come back re-normalized by Google.

  • A missing key throws the tagged config.notConfigured error naming GOOGLE_AI_API_KEY (the same key as @molecule/api-ai-google). API failures throw GeminiEmbeddingsError with Google's HTTP status and code (INVALID_ARGUMENT, RESOURCE_EXHAUSTED, …); 429/500/503 are retried 3 times first. GOOGLE_AI_BASE_URL (version included, e.g. …/v1beta) points it at a gateway, which may be keyless.

  • Lists are sent through batchEmbedContents in chunks of maxBatchSize (default 100) — one request per text, never one merged vector.

E2E Tests

Integration checklist — drive the real flow (no mocks), adapt each item to this app's actual data and features, and check every box off one by one. A box you can't check is an integration bug to fix — not a skip. Embeddings are infrastructure, so PROVE them through the feature they power (semantic search / "related items" / dedup) AND with a direct property check on the vectors:

  • embedQuery(text) returns a non-empty numeric number[] of the model's fixed dimension, and every vector from embed/embedDocuments has that SAME length — no empty arrays, no NaN/null entries, and the length is identical across calls (a query and a document must be comparable).
  • The semantic property holds — there is NO built-in similarity helper, so compute cosine similarity yourself (dot product over the two magnitudes): two related texts ("dog"/"puppy", or a query and a matching doc) score HIGH, two unrelated texts ("dog"/"quarterly taxes") score clearly LOWER. If every pair scores alike the vectors are dead — one positive check alone doesn't prove it.
  • Ranking works: embed a query plus a handful of documents and sort by cosine similarity — the semantically closest document ranks ABOVE the unrelated ones. This ordering is the whole point; a query that ranks an off-topic doc first is broken.
  • The feature built on it works end-to-end from the real UI: semantic search / "related items" / dedup returns results ranked by MEANING, not keyword — a search for a synonym or paraphrase finds the right item even when it shares NO words with the query (the case a plain text/keyword search would miss).
  • Embedding is stable: the same text embedded twice yields (near-)identical vectors, so a stored index stays valid — re-embedding an item must not silently drift it out of its own neighborhood.
  • Batching is order-preserving: embedDocuments([a, b, c]) (or embed({ input })) returns exactly one vector per input in the SAME order — embeddings[i] is the vector for input[i], never shuffled, merged, or dropped.
  • Embedding runs SERVER-SIDE: the call goes through the app's API and the provider key never reaches the browser (no provider request or key in the Network tab). Any endpoint that embeds caller-supplied text is authenticated and rate-limited — an open embed endpoint is an unbounded per-token cost/abuse vector.