Skip to content

Providers

samesake never calls a model directly. You supply functions; the bundle passes opaque model strings through from your collection config.

ClosureTypeRequired?Used for
models.embedEmbedFn | EmbedderYesIndex + query vectors
models.generateGenerateFnOptionalEnrich stages, NLQ, judge, llmRerank
rerankRerankFnOptional (default off)Second-stage rerank — see Reranking
groundImageGroundImageFnOptionalCrop/segment before embed

model on each request is an opaque string from your catalog config. Your function decides how to map it to a provider endpoint.

Ready-made adapters — @samesake/providers

Section titled “Ready-made adapters — @samesake/providers”

You rarely need to hand-write the glue: @samesake/providers ships zero-dependency factories for generation, parsing, and reranking. Its Gemini and Voyage embedding exports are aliases of the dual-form implementations in @samesake/embed, which provide both single-request and batch forms for search and enrichment.

import { samesake } from "@samesake/postgres";
import { geminiEmbedder, geminiGenerator, cohereReranker } from "@samesake/providers";
const app = samesake({
url: process.env.SAMESAKE_DATABASE_URL!,
collection,
models: {
embed: geminiEmbedder(), // multimodal (text + image); GEMINI_API_KEY
generate: geminiGenerator(), // NLQ, enrichment, judge
},
rerank: cohereReranker(), // optional second stage; COHERE_API_KEY
});
Providerembedgenerateparsererank
GeminigeminiEmbedder (multimodal, @samesake/embed)geminiGeneratorgeminiParser
OpenAIopenaiEmbedderopenaiGeneratoropenaiParser
VoyagevoyageEmbedder (@samesake/embed)voyageReranker
CoherecohereEmbeddercohereReranker

Generation and reranking factories accept { apiKey, model, baseUrl, minIntervalMs, retries }. The Gemini and Voyage embedding aliases resolve GEMINI_API_KEY/VOYAGE_API_KEY and accept the @samesake/embed options; model and dimension normally come from each collection’s embedding definition.

Already on the AI SDK? @samesake/providers/ai-sdk wraps any AI SDK model object (ai is an optional peer dependency, only needed for this subpath):

import { google } from "@ai-sdk/google";
import { aiSdkEmbedder, aiSdkGenerator } from "@samesake/providers/ai-sdk";
const app = samesake({
url: process.env.SAMESAKE_DATABASE_URL!,
collection,
models: {
embed: aiSdkEmbedder(google.textEmbedding("gemini-embedding-2"), {
providerOptions: ({ dim, taskType }) => ({
google: { outputDimensionality: dim, ...(taskType ? { taskType } : {}) },
}),
}),
generate: aiSdkGenerator(google("gemini-2.5-flash-lite")),
},
});

Also aiSdkParser(model) and aiSdkReranker(model) (AI SDK v6 reranking models). Note the AI SDK’s embed() is text-only — image spaces need a multimodal embedder (geminiEmbedder) or BYO.

BYO stays first-class: anything satisfying the contracts below works.

interface EmbedRequest {
text?: string;
image?: { url?: string; bytes?: Uint8Array; mimeType?: string };
model: string;
dim: number;
taskType?: string; // e.g. Gemini "RETRIEVAL_QUERY" / "RETRIEVAL_DOCUMENT"
inputType?: "query" | "document"; // Voyage cares; most providers ignore
}
type EmbedFn = (req: EmbedRequest) => Promise<number[]>;

Return a vector of length dim or createEmbedder’s wrapper throws a clear dimension mismatch.

interface GenerateRequest {
model?: string;
system?: string;
prompt: string;
images?: { mimeType: string; data: Uint8Array | string }[];
schema: Record<string, unknown>; // JSON Schema for structured output
}
type GenerateFn = (req: GenerateRequest) => Promise<unknown>;

Used by enrich pipelines, NLQ, the relevance judge, and llmRerank.

Examples only — not endorsements. Pick what fits your latency, cost, and compliance needs.

ProviderEmbed exampleGenerate / judge exampleNotes
Geminigemini-embedding-2gemini-3.1-flash-litetaskType matters (RETRIEVAL_QUERY vs RETRIEVAL_DOCUMENT); multimodal embed
OpenAItext-embedding-3-largegpt-4.1-miniIgnores taskType; set dimensions to match catalog dim
Voyagevoyage-3-largeinputType query vs document; rerank scores already [0,1]
Cohereembed-v4.0Rerank relevance_score already [0,1]
nomicnomic-embed-text-v2Local/cloud embed
Ollama / sentence-transformersnomic-embed-textlocal LLMSelf-hosted; no taskType
Local cross-encoderRerank only; normalize logits to [0,1] yourself

Fully custom wiring (when the adapters don’t fit)

Section titled “Fully custom wiring (when the adapters don’t fit)”

An example of implementing the closures yourself against the Vercel AI SDK — the same thing @samesake/providers/ai-sdk does for you:

embed.ts
import { embed } from "ai";
import { google } from "@ai-sdk/google";
import type { EmbedFn } from "@samesake/core";
export const geminiEmbed: EmbedFn = async ({ text, model, dim, taskType, inputType }) => {
const { embedding } = await embed({
model: google.textEmbedding(model ?? "gemini-embedding-2"),
value: text!,
providerOptions: {
google: {
outputDimensionality: dim,
taskType:
taskType ??
(inputType === "query" ? "RETRIEVAL_QUERY" : "RETRIEVAL_DOCUMENT"),
},
},
});
return Array.from(embedding);
};
generate.ts
import { generateObject } from "ai";
import { google } from "@ai-sdk/google";
import type { GenerateFn } from "@samesake/core";
export const geminiGenerate: GenerateFn = async ({ model, system, prompt, schema }) => {
const { object } = await generateObject({
model: google.languageModel(model ?? "gemini-3.1-flash-lite"),
schema,
system,
prompt,
temperature: 0,
});
return object;
};
search.ts
import { samesake } from "@samesake/postgres";
import { llmRerank } from "@samesake/server";
import { geminiEmbed } from "./embed.ts";
import { geminiGenerate } from "./generate.ts";
const app = samesake({
url: process.env.SAMESAKE_DATABASE_URL!,
collection,
models: { embed: geminiEmbed, generate: geminiGenerate },
rerank: llmRerank(geminiGenerate, { model: "gemini-3.1-flash-lite" }),
});

Declare models in your catalog to match:

embeddings: {
doc: { model: "gemini-embedding-2", dim: 1536, taskType: "RETRIEVAL_DOCUMENT" },
},
enrich: pipeline(
stage("vision", { model: "gemini-3.1-flash-lite", /* ... */ }),
),