Providers
samesake never calls a model directly. You supply functions; the bundle passes opaque model strings through from your collection config.
Model closures
Section titled “Model closures”| Closure | Type | Required? | Used for |
|---|---|---|---|
models.embed | EmbedFn | Embedder | Yes | Index + query vectors |
models.generate | GenerateFn | Optional | Enrich stages, NLQ, judge, llmRerank |
rerank | RerankFn | Optional (default off) | Second-stage rerank — see Reranking |
groundImage | GroundImageFn | Optional | Crop/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});| Provider | embed | generate | parse | rerank |
|---|---|---|---|---|
| Gemini | geminiEmbedder (multimodal, @samesake/embed) | geminiGenerator | geminiParser | — |
| OpenAI | openaiEmbedder | openaiGenerator | openaiParser | — |
| Voyage | voyageEmbedder (@samesake/embed) | — | — | voyageReranker |
| Cohere | cohereEmbedder | — | — | cohereReranker |
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.
Vercel AI SDK bridge
Section titled “Vercel AI SDK bridge”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.
EmbedFn
Section titled “EmbedFn”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.
GenerateFn
Section titled “GenerateFn”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.
Provider matrix
Section titled “Provider matrix”Examples only — not endorsements. Pick what fits your latency, cost, and compliance needs.
| Provider | Embed example | Generate / judge example | Notes |
|---|---|---|---|
| Gemini | gemini-embedding-2 | gemini-3.1-flash-lite | taskType matters (RETRIEVAL_QUERY vs RETRIEVAL_DOCUMENT); multimodal embed |
| OpenAI | text-embedding-3-large | gpt-4.1-mini | Ignores taskType; set dimensions to match catalog dim |
| Voyage | voyage-3-large | — | inputType query vs document; rerank scores already [0,1] |
| Cohere | embed-v4.0 | — | Rerank relevance_score already [0,1] |
| nomic | nomic-embed-text-v2 | — | Local/cloud embed |
| Ollama / sentence-transformers | nomic-embed-text | local LLM | Self-hosted; no taskType |
| Local cross-encoder | — | — | Rerank 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:
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);};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;};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", /* ... */ }),),Related
Section titled “Related”- Reranking — wire a remote
RerankFnorllmRerank - Relevance judge — what
generatemust support for eval - Build a search experience — end-to-end mental model