Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -138,3 +138,4 @@ dist
vite.config.js.timestamp-*
vite.config.ts.timestamp-*
plan.md

26 changes: 21 additions & 5 deletions HOW_IT_WORKS.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@

1. Configure `supabaseVectorStore()` with an `indexName`, embedder, and Supabase connection.
2. Call `ai.index()` with `supabaseIndexerRef(indexName)`.
3. The plugin extracts text, creates embeddings, and upserts rows into your configured table.
4. Call `ai.retrieve()` with `supabaseRetrieverRef(indexName)`.
5. The plugin embeds the query, calls your match RPC, and maps rows back into Genkit documents.
6. If `similarityThreshold` is set, low-scoring rows are dropped after the RPC response is validated.
7. If you want an answer, pass the retrieved docs into `ai.generate({ docs, prompt })`.
3. The plugin reads `metadata.id` when present, or generates one by default. If `onMissingId: 'error'` is configured, indexing fails when `metadata.id` is missing.
4. The plugin extracts text-only content, joins multi-part text with spaces, creates embeddings, and upserts rows into your configured table.
5. Call `ai.retrieve()` with `supabaseRetrieverRef(indexName)`.
6. The plugin embeds the query text, calls your match RPC, and maps rows back into Genkit documents while preserving `string` or `number` IDs.
7. If `similarityThreshold` is set, low-scoring rows are dropped after the RPC response is validated.
8. If you want an answer, pass the retrieved docs into `ai.generate({ docs, prompt })`.

## Mental model

Expand All @@ -19,6 +20,21 @@
- Genkit retriever: keeps retrieval inside standard Genkit flows
- Your SQL RPC: defines how similarity, filtering, and extra row metadata work

## Input and delete rules

- Documents and retrieval queries must contain text content.
- Multi-part text is joined with spaces before embedding.
- Delete operations must resolve at least one ID from `options.ids` or document `metadata.id`.
- Metadata filters must be plain JSON objects.

## RPC contract

Your RPC should:

- accept `query_embedding`, `match_count`, and `filter`
- return rows with `id`, `content`, and `metadata`
- return numeric `similarity` values when you use `similarityThreshold`

Without `ai.generate()`, this package still gives you retrieval results.

For the complete API, see [`docs/API.md`](./docs/API.md). For setup and runtime issues, see [`docs/TROUBLESHOOTING.md`](./docs/TROUBLESHOOTING.md).
66 changes: 63 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ It lets you keep embeddings, content, and JSONB metadata in Supabase while using
- index documents with `ai.index()`
- retrieve relevant documents with `ai.retrieve()`
- filter retrievals with JSONB metadata
- optionally apply a post-RPC `similarityThreshold`

## Why Supabase + Genkit

Expand All @@ -25,6 +26,8 @@ Supabase gives you managed Postgres, `pgvector`, and JSONB in one place. Genkit
- delete by id through `ai.index()`
- top-k semantic retrieval through a Supabase RPC function
- JSONB metadata filtering
- optional strict `metadata.id` enforcement with `onMissingId: 'error'`
- optional post-retrieval score filtering with `similarityThreshold`
- configurable schema, table, RPC, and column names
- useful validation and runtime errors

Expand Down Expand Up @@ -75,7 +78,7 @@ await ai.index({
indexer: docsIndexer,
documents: [
Document.fromText('Supabase stores embeddings in Postgres.', {
id: 'doc-1',
id: 1,
topic: 'supabase',
}),
],
Expand All @@ -95,13 +98,14 @@ The package expects:
- a pgvector-enabled table for documents
- an RPC function that accepts `query_embedding`, `match_count`, and `filter`
- rows containing your configured `id`, `content`, and `metadata` fields
- text content in both indexed documents and retrieval queries

SQL examples are included in:

- [`sql/001_create_rag_documents.sql`](./sql/001_create_rag_documents.sql)
- [`sql/002_match_rag_documents.sql`](./sql/002_match_rag_documents.sql)

Default schema:
Default schema example:

- `id text primary key`
- `content text not null`
Expand All @@ -112,6 +116,8 @@ Default schema:

If your embedder uses a different dimension, update both the table definition and the RPC function signature to match.

If you want numeric primary keys instead, that is supported by the package, but your table schema and RPC must return numeric `id` values consistently.

## Usage

### Configure the plugin
Expand Down Expand Up @@ -141,6 +147,7 @@ const ai = genkit({
embeddingColumn: 'embedding',
defaultK: 5,
embeddingDimension: 3072,
onMissingId: 'generate',
},
]),
],
Expand All @@ -149,7 +156,11 @@ const ai = genkit({

### Index documents

Document ids are read from `document.metadata.id`. If a document already exists with the same id, it is updated.
Document ids are read from `document.metadata.id`. IDs may be `string` or `number`. If a document already exists with the same id, it is updated.

This package indexes text-only content. Multi-part content is joined with spaces before embedding and storage.

By default, missing `metadata.id` values are filled with a generated UUID. If you want strict ingestion, set `onMissingId: 'error'`.

```ts
import { Document } from 'genkit';
Expand Down Expand Up @@ -191,6 +202,8 @@ await ai.index({

You can also omit `options.ids` and pass documents that contain `metadata.id`.

Delete requests must resolve at least one id from `options.ids` or document `metadata.id`. Empty delete requests are rejected with a validation error.

### Retrieve documents

```ts
Expand All @@ -205,6 +218,8 @@ const docs = await ai.retrieve({
});
```

Retrieval queries are also text-only. If the query contains multiple text parts, they are joined with spaces before embedding.

### Metadata filter example

Metadata filters are passed to the RPC function as JSONB and work well with `metadata @> filter`.
Expand All @@ -223,6 +238,21 @@ const docs = await ai.retrieve({
});
```

### Similarity threshold example

If your RPC returns a numeric `similarity` column, you can drop low-confidence rows after retrieval:

```ts
const docs = await ai.retrieve({
retriever: productsRetriever,
query: 'Show me the strongest backpack match',
options: {
k: 5,
similarityThreshold: 0.8,
},
});
```

## Public API

### `supabaseVectorStore(configs)`
Expand All @@ -244,6 +274,7 @@ Config fields:
- `defaultK?`
- `embeddingDimension?`
- `embedderOptions?`
- `onMissingId?: 'generate' | 'error'`

Defaults:

Expand All @@ -255,6 +286,14 @@ Defaults:
- `metadataColumn: 'metadata'`
- `embeddingColumn: 'embedding'`
- `defaultK: 3`
- `onMissingId: 'generate'`

Behavior notes:

- `metadata.id` may be a `string` or `number`.
- With `onMissingId: 'generate'`, missing IDs are replaced with generated UUIDs.
- With `onMissingId: 'error'`, indexing fails unless each document includes `metadata.id`.
- Documents and queries must contain text content. Non-text-only inputs are rejected.

### `supabaseIndexerRef(indexName)`

Expand All @@ -266,6 +305,12 @@ Supported indexer options:
- `ids?: Array<string | number>`
- `batchSize?: number`

Delete behavior:

- `ids` may contain strings or numbers.
- Delete resolves ids from `options.ids` first, then falls back to document `metadata.id`.
- If no ids are resolved, the operation fails with a validation error.

### `supabaseRetrieverRef(indexName)`

Returns the Genkit retriever reference for `ai.retrieve()`.
Expand All @@ -274,6 +319,21 @@ Supported retriever options:

- `k?: number`
- `filter?: Record<string, unknown>`
- `similarityThreshold?: number`

Retriever behavior:

- `filter` must be a plain JSON object.
- `similarityThreshold` must be between `0` and `1`.
- When `similarityThreshold` is set, your RPC must return a numeric `similarity` column for each row.

## Constraints And Guarantees

- Text-only input: indexing and retrieval embed only text content, and multi-part text is joined with spaces.
- ID policy: document IDs are preserved as `string` or `number` across indexing, delete, and retrieval flows.
- Metadata normalization: metadata and filters must be plain JSON-compatible values with no `undefined`.
- Dimension enforcement: `embeddingDimension` validates embedder output length before data is sent to Postgres.
- RPC expectations: the RPC must accept `query_embedding`, `match_count`, and `filter`, and should return `id`, `content`, and `metadata`.

## Production Notes

Expand Down
4 changes: 4 additions & 0 deletions docs/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ Registers one or more Supabase-backed vector stores by `indexName`.
- Postgres identifiers must use letters, numbers, and underscores only.
- `onMissingId: 'generate'` preserves current behavior and fills `metadata.id` with a generated UUID when needed.
- `onMissingId: 'error'` requires every indexed document to include `metadata.id`.
- `metadata.id` may be a `string` or `number`, and that scalar type is preserved through indexing, retrieval, and delete calls.
- Indexed documents must contain text content. Multi-part text is joined with spaces before embedding.

## `supabaseIndexerRef(indexName)`

Expand All @@ -62,6 +64,7 @@ Returns a Genkit indexer ref for `ai.index()`.

- Upserts use the configured `idColumn` as the conflict target.
- Delete operations accept `options.ids` or document `metadata.id` values.
- Delete operations require at least one resolved ID and fail fast when none are provided.
- `batchSize` must be a positive integer and is validated before embedding work begins.

## `supabaseRetrieverRef(indexName)`
Expand All @@ -79,6 +82,7 @@ Returns a Genkit retriever ref for `ai.retrieve()`.
- `filter` is normalized as plain JSON and passed to your RPC as `jsonb`.
- `similarityThreshold` must be between `0` and `1`.
- When `similarityThreshold` is set, the RPC response must include a numeric `similarity` column for each row.
- Retrieval queries must contain text content. Multi-part query text is joined with spaces before embedding.

## SQL contract

Expand Down
Loading