Skip to content

Commit d1b7ba9

Browse files
committed
feat(embeddings): store knowledge bases at five vector widths and add Ollama
Knowledge bases were pinned to one 1536-dimension pgvector column, so only models that emit exactly that width could index one. The embedding table now carries a column per storable width — 384, 768, 1024, 1536, 3072 — and a base records which one it uses at creation, chosen with EMBEDDING_OUTPUT_DIMS. Ollama becomes an embedding provider on both paths: KB_EMBEDDING_MODEL accepts ollama/<model> for knowledge bases, and the Embeddings block offers the models installed on the configured server, with no API key and nothing billed.
1 parent b890e24 commit d1b7ba9

77 files changed

Lines changed: 24061 additions & 353 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

apps/docs/content/docs/integrations/embeddings.mdx

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,19 +13,21 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
1313
{/* MANUAL-CONTENT-START:intro */}
1414
An embedding turns a piece of text into a list of numbers that captures its meaning. Two texts that mean similar things get similar numbers, so you can compare meaning directly instead of matching keywords. That is what powers semantic search, grouping related items, and spotting near-duplicates that are worded differently.
1515

16-
The Embeddings block generates those numbers using OpenAI, Google Gemini, Cohere, or Mistral. Pick a provider, pick one of its models, pass in text, and get a vector back — one vector per input, in the order you supplied them. You can embed a single string or a list of strings in one call.
16+
The Embeddings block generates those numbers using OpenAI, Google Gemini, Cohere, Mistral, OpenRouter, or a model on your own Ollama server. Pick a provider, pick one of its models, pass in text, and get a vector back — one vector per input, in the order you supplied them. You can embed a single string or a list of strings in one call.
1717

1818
Models differ in what they are good at and what they cost. `text-embedding-3-small` is the cost-efficient general choice, `gemini-embedding-001` gives the highest retrieval quality, `embed-v4.0` handles multilingual content, and `codestral-embed` is tuned for source code. Some models also let you trade vector size against quality, and some accept a task type so the vector is conditioned for how it will be used — the block only offers those controls for the models that actually support them.
1919

2020
Two things worth knowing before you build on it. Vectors are only comparable when they come from the same model at the same size, so changing either means re-embedding everything you intend to compare. And input longer than the model's limit is shortened to fit rather than rejected, with a warning in the run, so chunk long documents yourself when the tail matters.
2121

22-
Sim's knowledge bases embed separately, at a fixed vector width and from a smaller set of models. This block is for embedding text yourself inside a workflow.
22+
Ollama is the exception to most of the above. It runs on your own deployment, so it needs no API key and costs nothing, and the model list is whatever you have pulled onto that server rather than a catalog Sim maintains — the block reads it live, filters it to models that can actually embed, and shows each one's vector width next to its name. Ollama accepts neither a task type nor a size reduction, so the block does not offer those controls for it. Self-hosted deployments configure the server with `OLLAMA_URL`; on Sim Cloud there is no Ollama to reach, so the list comes back empty.
23+
24+
Sim's knowledge bases embed separately: a base fixes one model and one vector width when it is created, from a smaller set of models. This block is for embedding text yourself inside a workflow.
2325
{/* MANUAL-CONTENT-END */}
2426

2527

2628
## Usage Instructions
2729

28-
Turn text into embedding vectors for semantic search, clustering, and similarity. Supports OpenAI, OpenRouter, Google Gemini, Cohere, and Mistral embedding models.
30+
Turn text into embedding vectors for semantic search, clustering, and similarity. Supports OpenAI, OpenRouter, Google Gemini, Cohere, and Mistral embedding models, plus any model on a self-hosted Ollama.
2931

3032

3133

@@ -151,4 +153,25 @@ Generate embeddings from text using Mistral's embedding models
151153
| `dimensions` | number | Dimensionality of each vector |
152154
| `usage` | json | Token usage |
153155

156+
### Ollama Embeddings
157+
158+
Generate embeddings on a self-hosted Ollama server
159+
160+
#### Input
161+
162+
| Parameter | Type | Required | Description |
163+
| --------- | ---- | -------- | ----------- |
164+
| `input` | string | Yes | Text to embed, or an array of texts to embed in one call |
165+
| `model` | string | Yes | Embedding model pulled on the configured Ollama server |
166+
167+
#### Output
168+
169+
| Parameter | Type | Description |
170+
| --------- | ---- | ----------- |
171+
| `embeddings` | json | Generated embeddings |
172+
| `model` | string | Model used |
173+
| `provider` | string | Provider used |
174+
| `dimensions` | number | Dimensionality of each vector |
175+
| `usage` | json | Token usage |
176+
154177

apps/docs/content/docs/knowledgebase/index.mdx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ When you upload a document, Sim processes it in the background:
2323

2424
A document is searchable once its status reads `completed`. Open any document to view, edit, merge, or split its chunks.
2525

26+
Every knowledge base records the embedding model and the vector width it was built with, and keeps them for its lifetime. Two bases built differently cannot be searched in one request — vectors are only comparable when they come from the same model at the same size — so search them separately, or rebuild one to match. On Sim Cloud every base uses `text-embedding-3-small` at 1,536 dimensions; self-hosted deployments choose both with [`KB_EMBEDDING_MODEL` and `EMBEDDING_OUTPUT_DIMS`](/platform/self-hosting/environment-variables), including models on their own Ollama.
27+
2628
## What you can upload
2729

2830
Sim accepts PDF, Word, text, Markdown, HTML, Excel, PowerPoint, CSV, JSON, and YAML files, up to 100 MB each (best under 50 MB). Scanned PDFs work too: with Azure or [Mistral OCR](https://docs.mistral.ai/ocr/) configured, Sim extracts text from image-based pages.

apps/docs/content/docs/platform/self-hosting/environment-variables.mdx

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ import { Callout } from 'fumadocs-ui/components/callout'
5555
| `VERTEX_PROJECT` / `VERTEX_LOCATION` | Google Vertex AI — GCP project ID and region (region defaults to `us-central1`) |
5656

5757
<Callout type="warn">
58-
**Knowledge bases require a hosted embedding provider.** Three are supported, selected with `KB_EMBEDDING_MODEL`: `text-embedding-3-small` (default) and `text-embedding-3-large` on OpenAI or Azure OpenAI, and `gemini-embedding-001` on Gemini. There is no local embedding backend — configuring Ollama or vLLM does not substitute, because embeddings do not route through the configured chat model.
58+
**Knowledge bases need an embedding model, selected with `KB_EMBEDDING_MODEL`.** Hosted: `text-embedding-3-small` (default) and `text-embedding-3-large` on OpenAI or Azure OpenAI, and `gemini-embedding-001` on Gemini. Local: any embedding model on your own Ollama, named `ollama/<model>` (for example `ollama/nomic-embed-text`) with `OLLAMA_URL` set. Ollama serves embeddings only through this variable — embeddings never route through the configured chat model, so vLLM and LM Studio do not substitute.
5959
</Callout>
6060

6161
<Callout type="info">
@@ -190,10 +190,35 @@ See [Observability](/platform/self-hosting/observability).
190190

191191
| Variable | Description |
192192
|----------|-------------|
193-
| `KB_EMBEDDING_MODEL` | Embedding model for new knowledge bases. Defaults to `text-embedding-3-small`; an unsupported value falls back to the default |
193+
| `KB_EMBEDDING_MODEL` | Embedding model for new knowledge bases. Defaults to `text-embedding-3-small`; use `ollama/<model>` for a model on your own Ollama. An unsupported value falls back to the default |
194+
| `EMBEDDING_OUTPUT_DIMS` | Vector width new knowledge bases are stored at: `384`, `768`, `1024`, `1536` (default), or `3072`. It must be a width the chosen model can emit; anything else falls back to `1536` with a warning |
194195
| `OPENROUTER_API_KEY` | Fallback route for the OpenAI embedding models — used when it is set and `OPENAI_API_KEY` is not the chosen path |
195196
| `COHERE_API_KEY` | Enables the Knowledge block reranker |
196197

198+
Both variables apply at creation time and are recorded on the knowledge base, so changing either
199+
affects new knowledge bases only. Existing ones keep the model and width they were built with, and
200+
knowledge bases with different settings cannot be searched together.
201+
202+
Matching the width to the model is yours to get right. Sim only knows the widths a hosted provider
203+
documents, and nothing at all about a model on your Ollama, so a mismatch surfaces as a failed
204+
document rather than a rejected configuration. Common pairings:
205+
206+
| `EMBEDDING_OUTPUT_DIMS` | Works with |
207+
|----------|-------------|
208+
| `384` | `ollama/all-minilm` |
209+
| `768` | `ollama/nomic-embed-text`, `ollama/embeddinggemma`, `text-embedding-3-small`, `gemini-embedding-001` |
210+
| `1024` | `ollama/mxbai-embed-large`, `ollama/bge-m3`, `text-embedding-3-small`, `text-embedding-3-large` |
211+
| `1536` | `text-embedding-3-small`, `text-embedding-3-large`, `gemini-embedding-001` |
212+
| `3072` | `text-embedding-3-large`, `gemini-embedding-001` |
213+
214+
`sim-setup add knowledge-embeddings` walks through all of this — pick OpenAI, Azure OpenAI, Gemini,
215+
or Ollama and it writes the variables that family needs. `sim-setup status` then reports the one
216+
family your `KB_EMBEDDING_MODEL` actually selects, rather than every provider you happen to hold a
217+
key for.
218+
219+
The Embeddings block reads the same `OLLAMA_URL`. It lists the embedding-capable models installed on
220+
that server with the width each one emits, so a workflow can embed locally without an API key.
221+
197222
## Chat & PII
198223

199224
| Variable | Description |

apps/docs/content/docs/platform/self-hosting/index.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ Sim is self-contained for the core editor and execution engine. A few features r
109109

110110
| Feature | Requires | Notes |
111111
|---|---|---|
112-
| **Knowledge bases** | An OpenAI, Azure OpenAI, or Gemini API key | Embeddings are generated by a hosted provider, selected with `KB_EMBEDDING_MODEL` (`text-embedding-3-small` by default). There is no local embedding backend — knowledge bases are unavailable without one of these keys. |
112+
| **Knowledge bases** | An OpenAI, Azure OpenAI, or Gemini API key, or an Ollama server | Embeddings are generated by the model named in `KB_EMBEDDING_MODEL` (`text-embedding-3-small` by default). Set it to `ollama/<model>` with `OLLAMA_URL` to embed locally instead. `EMBEDDING_OUTPUT_DIMS` sets the vector width. |
113113
| **Agent blocks** | An API key for at least one model provider | Or a self-hosted OpenAI-compatible endpoint: Ollama, vLLM, LM Studio, or LiteLLM. |
114114
| **Chat module** | `COPILOT_API_KEY` from sim.ai | Set `NEXT_PUBLIC_CHAT_DISABLED=true` to hide the module instead. |
115115
| **Integrations** | Your own OAuth app per service | See [Integrations & OAuth](/platform/self-hosting/integrations-oauth). |

apps/docs/content/docs/platform/self-hosting/troubleshooting.mdx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,9 @@ The load balancer's backend timeout is closing them. On GKE, attach a `BackendCo
250250

251251
## Knowledge base upload fails
252252

253-
Embeddings need a hosted provider — set `OPENAI_API_KEY`, configure Azure OpenAI, or set `KB_EMBEDDING_MODEL=gemini-embedding-001` with a Gemini key. There is no local embedding backend, so Ollama or vLLM does not substitute. If a key is set, verify pgvector is installed on the database.
253+
Embeddings need a provider — set `OPENAI_API_KEY`, configure Azure OpenAI, set `KB_EMBEDDING_MODEL=gemini-embedding-001` with a Gemini key, or set `KB_EMBEDDING_MODEL=ollama/<model>` with `OLLAMA_URL` to embed on your own Ollama. If one is configured, verify pgvector is installed on the database.
254+
255+
A document that fails with `vector 0 has N unexpected dimensions` means `EMBEDDING_OUTPUT_DIMS` does not match what the model actually emits. Sim cannot check this for you — the message names both widths, so set the variable to the one the model returned and recreate the knowledge base. Existing knowledge bases keep the width they were created with.
254256

255257
## Credentials Unreadable After a Restore
256258

apps/docs/content/docs/platform/self-hosting/verify.mdx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ The full command list is in the [Docker guide](/platform/self-hosting/docker#the
4747
| 5 | Paste a model API key in settings and run a two-block workflow | Execution engine, credential encryption, outbound network | App logs; check `ENCRYPTION_KEY` is set and outbound egress is allowed |
4848
| 6 | Upload a small file in Files | File storage end to end | With object storage configured: presigned URL + bucket CORS. On local disk: the upload proxies through the app |
4949
| 7 | Upload a file larger than 50 MB | Multipart upload path (object storage only) | Check app logs for provider part-listing or completion errors |
50-
| 8 | Create a knowledge base and upload a PDF | Document parsing, embeddings, pgvector | Needs a hosted embedding provider — see below |
50+
| 8 | Create a knowledge base and upload a PDF | Document parsing, embeddings, pgvector | Needs an embedding provider — see below |
5151
| 9 | Invite a teammate from workspace settings | Email delivery | App logs for the mailer; see [Email](/platform/self-hosting/email) |
5252
| 10 | Connect an integration account | OAuth configuration | Redirect URI mismatch → see [Integrations & OAuth](/platform/self-hosting/integrations-oauth) |
5353
| 11 | Create a workflow with a Schedule trigger set to every minute, deploy it, wait 2 minutes | **Background jobs** | Check the scheduler's logs — see [Background Jobs](/platform/self-hosting/background-jobs) |
@@ -102,7 +102,7 @@ All six should be present on Compose: `simstudio`, `realtime`, `db`, `redis`, `c
102102

103103
**Step 6 or 7 fails.** With object storage configured, a CORS error in the browser console means the bucket policy does not allow your Sim origin or the signed upload headers. If step 7 fails only during completion, check the app logs and verify the server identity can list multipart parts (for S3, `s3:ListMultipartUploadParts`). On local-disk storage there is no CORS involved — uploads proxy through the app, so look at the app logs and the proxy body-size limit instead.
104104

105-
**Step 8 fails — knowledge base upload errors.** Knowledge bases need a hosted embedding providerOpenAI, Azure OpenAI, or Gemini. There is no local embedding backend. If a key is set, check pgvector is installed on the database.
105+
**Step 8 fails — knowledge base upload errors.** Knowledge bases need an embedding provider: OpenAI, Azure OpenAI, or Gemini with an API key, or a model on your own Ollama via `KB_EMBEDDING_MODEL=ollama/<model>` and `OLLAMA_URL`. If one is configured, check pgvector is installed on the database.
106106

107107
**Step 9 fails — no email arrives.** With no provider configured the mailer no-ops: it records the recipient, subject, and sender at `info` and reports success, never the message body. Raise `LOG_LEVEL` to `INFO` to see that line — the variable is uppercase-only, and at the production default of `ERROR` nothing is logged at all.
108108

apps/sim/app/api/knowledge/search/utils.test.ts

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,14 @@ afterEach(() => {
4848
})
4949

5050
import { WORKSPACE_ACCESS_SCOPE } from '@/lib/knowledge/access/scope'
51-
import { generateSearchEmbedding } from '@/lib/knowledge/embeddings'
51+
import { generateSearchEmbedding, type KbEmbeddingTarget } from '@/lib/knowledge/embeddings'
52+
53+
/** The platform default model and vector width, as a knowledge base records them. */
54+
const DEFAULT_EMBEDDING_TARGET: KbEmbeddingTarget = {
55+
model: 'text-embedding-3-small',
56+
dimensions: 1536,
57+
}
58+
5259
import {
5360
executeKeywordSearch,
5461
executeKnowledgeSearch,
@@ -596,7 +603,7 @@ describe('Knowledge Search Utils', () => {
596603

597604
mockNextEmbeddingResponse()
598605

599-
const result = await generateSearchEmbedding('test query')
606+
const result = await generateSearchEmbedding('test query', DEFAULT_EMBEDDING_TARGET)
600607

601608
expect(vi.mocked(fetch)).toHaveBeenCalledWith(
602609
'https://test.openai.azure.com/openai/deployments/text-embedding-ada-002/embeddings?api-version=2024-12-01-preview',
@@ -621,7 +628,7 @@ describe('Knowledge Search Utils', () => {
621628

622629
mockNextEmbeddingResponse()
623630

624-
const result = await generateSearchEmbedding('test query')
631+
const result = await generateSearchEmbedding('test query', DEFAULT_EMBEDDING_TARGET)
625632

626633
expect(vi.mocked(fetch)).toHaveBeenCalledWith(
627634
'https://api.openai.com/v1/embeddings',
@@ -649,7 +656,7 @@ describe('Knowledge Search Utils', () => {
649656

650657
mockNextEmbeddingResponse()
651658

652-
await generateSearchEmbedding('test query')
659+
await generateSearchEmbedding('test query', DEFAULT_EMBEDDING_TARGET)
653660

654661
expect(vi.mocked(fetch)).toHaveBeenCalledWith(
655662
'https://api.openai.com/v1/embeddings',
@@ -673,7 +680,7 @@ describe('Knowledge Search Utils', () => {
673680

674681
mockNextEmbeddingResponse()
675682

676-
await generateSearchEmbedding('test query', 'text-embedding-3-small')
683+
await generateSearchEmbedding('test query', DEFAULT_EMBEDDING_TARGET)
677684

678685
expect(vi.mocked(fetch)).toHaveBeenCalledWith(
679686
'https://test.openai.azure.com/openai/deployments/custom-embedding-model/embeddings?api-version=2024-12-01-preview',
@@ -695,7 +702,7 @@ describe('Knowledge Search Utils', () => {
695702
OPENROUTER_API_KEY: undefined,
696703
})
697704

698-
await expect(generateSearchEmbedding('test query')).rejects.toThrow(
705+
await expect(generateSearchEmbedding('test query', DEFAULT_EMBEDDING_TARGET)).rejects.toThrow(
699706
'OPENAI_API_KEY is not configured'
700707
)
701708
})
@@ -717,7 +724,9 @@ describe('Knowledge Search Utils', () => {
717724
text: 'Deployment not found',
718725
})
719726

720-
await expect(generateSearchEmbedding('test query')).rejects.toThrow('Embedding API failed')
727+
await expect(generateSearchEmbedding('test query', DEFAULT_EMBEDDING_TARGET)).rejects.toThrow(
728+
'Embedding API failed'
729+
)
721730

722731
// Clean up
723732
Object.keys(env).forEach((key) => delete (env as any)[key])
@@ -738,7 +747,9 @@ describe('Knowledge Search Utils', () => {
738747
text: 'Rate limit exceeded',
739748
})
740749

741-
await expect(generateSearchEmbedding('test query')).rejects.toThrow('Embedding API failed')
750+
await expect(generateSearchEmbedding('test query', DEFAULT_EMBEDDING_TARGET)).rejects.toThrow(
751+
'Embedding API failed'
752+
)
742753

743754
// Clean up
744755
Object.keys(env).forEach((key) => delete (env as any)[key])
@@ -756,7 +767,7 @@ describe('Knowledge Search Utils', () => {
756767

757768
mockNextEmbeddingResponse()
758769

759-
await generateSearchEmbedding('test query')
770+
await generateSearchEmbedding('test query', DEFAULT_EMBEDDING_TARGET)
760771

761772
expect(vi.mocked(fetch)).toHaveBeenCalledWith(
762773
expect.any(String),
@@ -782,7 +793,7 @@ describe('Knowledge Search Utils', () => {
782793

783794
mockNextEmbeddingResponse()
784795

785-
await generateSearchEmbedding('test query', 'text-embedding-3-small')
796+
await generateSearchEmbedding('test query', DEFAULT_EMBEDDING_TARGET)
786797

787798
expect(vi.mocked(fetch)).toHaveBeenCalledWith(
788799
expect.any(String),
@@ -811,7 +822,7 @@ describe('Knowledge Search Utils', () => {
811822
registry.recordResolved('TOKEN', 'secret-value')
812823

813824
await runWithKnowledgeModelInputProvenance(registry, () =>
814-
generateSearchEmbedding('prefix secret-value suffix', 'text-embedding-3-small')
825+
generateSearchEmbedding('prefix secret-value suffix', DEFAULT_EMBEDDING_TARGET)
815826
)
816827

817828
expect(vi.mocked(fetch)).toHaveBeenCalledWith(

0 commit comments

Comments
 (0)