Embed text
EmbedderContract is the sibling of ModelContract on the SDK adapter. Text-in, vector-out. No streaming, no tools, no relationship to chat completions. It’s a separate primitive on purpose — different cost profiles, different per-request limits, different failure modes.
The contract
Section titled “The contract”interface EmbedderContract { readonly name: string; readonly provider: string; readonly dimensions: number; // 0 until first call when no override given
embed(input: string): Promise<EmbeddingResult>; embedMany(inputs: string[]): Promise<EmbeddingBatchResult>;}embedder() is optional on SDKAdapterContract — not every provider supports embeddings. Check before reaching for it:
if (typeof sdk.embedder === "function") { const embedder = sdk.embedder({ name: "text-embedding-3-small" });}OpenAI adapter — usage
Section titled “OpenAI adapter — usage”import { OpenAISDK } from "@warlock.js/ai-openai";
const openai = new OpenAISDK({ apiKey: process.env.OPENAI_API_KEY! });const embedder = openai.embedder({ name: "text-embedding-3-small" });
const one = await embedder.embed("Hello, world.");// { vector: number[], usage: Usage, dimensions: number }
const many = await embedder.embedMany(["foo", "bar", "baz"]);// { vectors: number[][], usage: Usage, dimensions: number }Not wired into the agent loop
Section titled “Not wired into the agent loop”Embeddings are deliberately not automatic. Consumers obtain an embedder from the adapter and call it directly. It composes into:
- Retrieval tools the agent can call (RAG pattern).
runsteps in a workflow (vector ingest, catalog item embedding).- Query vectors for
ai.middleware.semanticCache. - Vector columns in Cascade for native pgvector search.
- Cache similarity via
cache.set({ vector })+cache.similar(...).
Inside a workflow run step
Section titled “Inside a workflow run step”ai.step({ name: "embed", run: async (ctx) => { const text = `${ctx.steps.extract.output.name} ${ctx.steps.extract.output.description}`; const { vector } = await embedder.embed(text); ctx.state.embedding = vector; }, output: { extract: (ctx) => ({ dims: (ctx.state.embedding as number[]).length }) },});Pattern — RAG tool
Section titled “Pattern — RAG tool”Wrap the embedder + your vector store in a tool the agent can call:
import { v } from "@warlock.js/seal";
const searchKb = ai.tool({ name: "searchKb", description: "Search the knowledge base for relevant passages.", input: v.object({ query: v.string(), k: v.number().optional() }), execute: async ({ query, k }) => { const { vector } = await embedder.embed(query); const hits = await vectorStore.query(vector, { topK: k ?? 5 }); return hits.map((h) => ({ text: h.text, score: h.score, source: h.source })); },});
const agent = ai.agent({ model, tools: [searchKb] });The agent decides when to call it. The model never sees the vector — only the retrieved text.
Dimensions
Section titled “Dimensions”embedder.dimensions is 0 on a fresh embedder when no override is given — it’s populated from the first embed call’s response. Pre-seed via the adapter’s dimensions config when you need the value BEFORE the first call (sizing a vector column in a Cascade migration, for example).
Retrieval is your problem
Section titled “Retrieval is your problem”@warlock.js/ai doesn’t ship a vector store. Bring your own — pgvector, Qdrant, Pinecone, Chroma — and wrap it in an ai.tool({...}). Or use @warlock.js/cache with pgvector to keep both KV and vector retrieval in one driver.
Related
Section titled “Related”- Run agent — composing embedders into tools.
- Run workflow — embeddings inside
runsteps. - Attach middleware —
semanticCacheuses an embedder. - Persist AI data — vector storage guidance.