← Files InsForgeARCHIVED FILE

skills/insforge/ai/embeddings-and-rag.md

4.99 KB · Oct 5, 2026 · 18:29 UTC

↓ Download file

# Embeddings and RAG

Use OpenRouter embeddings through the OpenAI SDK, then store vectors in
InsForge Postgres with pgvector. InsForge remains the database/vector store;
OpenRouter provides the embedding model gateway.

Official references:

- [OpenRouter embeddings](https://openrouter.ai/docs/api-reference/embeddings)
- [OpenAI SDK with OpenRouter](https://openrouter.ai/docs/guides/community/openai-sdk)
- [InsForge CLI pgvector setup](../../insforge-cli/references/database/vector.md)

## Setup

Bring up the `vector` extension, vector tables, indexes, and match RPCs through
an InsForge CLI database migration; see
[database/vector.md](../../insforge-cli/references/database/vector.md).

Run the AI key setup from the linked app directory:

```bash
npx @insforge/cli ai setup
```

Initialize the OpenAI SDK with OpenRouter as shown in [overview.md](overview.md).
Do not expose `OPENROUTER_API_KEY` to the browser. Generate embeddings through a
server route, server action, function, or backend.

## Model and Schema

Check current embedding models before shipping:

```bash
curl https://openrouter.ai/api/v1/embeddings/models \
  -H "Authorization: Bearer $OPENROUTER_API_KEY"
```

Common dimensions:

| Model | Dimensions |
|-------|------------|
| `openai/text-embedding-3-small` | 1536 |
| `openai/text-embedding-3-large` | 3072 |
| `google/gemini-embedding-001` | 3072 |

The Postgres column must match the embedding dimension, for example
`embedding vector(1536)`. Store the embedding model next to ingested content if
you expect to re-embed or migrate later.

## Store Documents

Use InsForge for the durable content row and vector. Keep one embedding model
per vector column.

```typescript
async function storeDocument(content: string) {
  const response = await openai.embeddings.create({
    model: process.env.OPENROUTER_EMBEDDING_MODEL ?? 'openai/text-embedding-3-small',
    input: content,
  })

  const { data, error } = await insforge.database.from('documents').insert([{
    content,
    embedding: response.data[0].embedding,
  }]).select()

  if (error) {
    throw error
  }

  return data
}
```

For batch ingestion, pass an array to `input` and insert rows in the same order
as `response.data`.

## Search and Answer

Embed the user query, retrieve matching rows through the `match_documents` RPC,
then pass retrieved content into a chat completion. Store the final answer in
your app's own table if the product has conversation history.

```typescript
async function askQuestion(sessionId: string, question: string) {
  const embeddingResponse = await openai.embeddings.create({
    model: process.env.OPENROUTER_EMBEDDING_MODEL ?? 'openai/text-embedding-3-small',
    input: question,
  })

  const { data: documents, error: searchError } = await insforge.database.rpc('match_documents', {
    query_embedding: embeddingResponse.data[0].embedding,
    match_count: 5,
    match_threshold: 0.78,
  })

  if (searchError) {
    throw searchError
  }

  const context = (documents ?? [])
    .map((doc: { content: string }) => doc.content)
    .join('\n\n')

  const completion = await openai.chat.completions.create({
    model: process.env.OPENROUTER_CHAT_MODEL ?? 'openai/gpt-4o',
    messages: [
      { role: 'system', content: `Answer using this context:\n\n${context}` },
      { role: 'user', content: question },
    ],
  })

  const answer = completion.choices[0]?.message?.content ?? ''

  const { error: insertError } = await insforge.database.from('chat_messages').insert([
    { session_id: sessionId, role: 'user', content: question },
    {
      session_id: sessionId,
      role: 'assistant',
      content: answer,
      model: completion.model,
      retrieval_count: documents?.length ?? 0,
    },
  ])

  if (insertError) {
    throw insertError
  }

  return answer
}
```

## Best Practices

1. Design the InsForge tables first: source documents, vector column dimension,
   match RPC, and optional chat history table.
2. If your schema includes an `embedding_model` column, store the model ID with
   embedded rows so future migrations can identify which vectors need
   re-embedding.
3. Use one embedding model per vector column. Re-embed when changing models.
4. Add chunking, query rewriting, re-ranking, context truncation, and retrieval
   evaluation before treating a RAG prototype as production-ready.
5. Use RLS on source documents and chat history. If a `SECURITY DEFINER` RPC
   bypasses RLS, re-filter inside the function by `auth.uid()` or tenant ID.

## Common Mistakes

| Mistake | Fix |
|---------|-----|
| Asking the user to enable an embedding model in old AI Settings | Use OpenRouter embedding models and `OPENROUTER_API_KEY` |
| Querying `ai.configs` for embedding models | Use `/api/v1/embeddings/models` |
| Putting `OPENROUTER_API_KEY` in browser env vars | Keep it server-side |
| Column dimension does not match model dimension | Match `vector(N)` to the model output |
| `encoding_format: 'base64'` into pgvector | Use float arrays |
| Mixing embedding models in one column | Pick one; mixed vectors give meaningless search results |

SHA-256: 509302e8efd55466aca6795ccb19cfdf7f0faad0b6a269a82d97f4fe977ba54e