Feature: Semantic Search
Relevant source files
The following files were used as context for generating this wiki page:The Semantic Search feature enables users to find relevant notes based on conceptual meaning rather than just keyword matching. It achieves this by converting text notes into numerical representations (vector embeddings) and storing them in a Qdrant vector database. When a user queries, their search term is also converted into a vector, and the system retrieves the most similar notes from the database using vector similarity search.
This system is orchestrated by the search service, which integrates an embedding generation module and a Qdrant client. The embedding module supports both local models via Ollama and cloud-based models via OpenAI. The core logic for ingesting notes and performing queries is demonstrated in the application's main entry point.
Sources: cmd/app/main.go:10-31, internal/search/service.go:42-93
Architecture Overview
The semantic search functionality is composed of three main components: the Search Service, the Embedding Service, and the Qdrant Vector Database Client. The Search Service acts as the central coordinator, using the Embedding Service to generate vectors and the Qdrant Client to interact with the database for storage and retrieval.
The following diagram illustrates the high-level architecture of the system.
Configuration
The system is configured using environment variables, which are loaded from a .env file. These variables control the connection to the Qdrant database and the choice of embedding model.
| Environment Variable | Description | Default Value | Source File |
|---|---|---|---|
QDRANT_URL | The base URL for the Qdrant instance. | (none) | internal/qdrant/client.go:72 |
QDRANT_API_KEY | The API key for authenticating with Qdrant. | (none) | internal/qdrant/client.go:73 |
QDRANT_COLLECTION | The name of the collection in Qdrant to store the note vectors. | (none) | internal/qdrant/client.go:74 |
EMBEDDING_MODEL | The name of the embedding model to use. | nomic-embed-text | internal/search/service.go:14, internal/embeddings/ollama.go:29 |
OLLAMA_URL | The URL for the Ollama service. | http://localhost:11434 | internal/embeddings/ollama.go:23 |
OPENAI_API_KEY | The API key for the OpenAI service. | (none) | internal/embeddings/openai.go:27 |
Sources: internal/qdrant/client.go:70-76, internal/search/service.go:12-16, internal/embeddings/ollama.go:21-31, internal/embeddings/openai.go:25-29
Embedding Generation
Text is converted into vector embeddings via an external service, either Ollama (for local models) or OpenAI (for cloud models). The search service determines the vector dimension based on the selected EMBEDDING_MODEL.
Supported Models and Dimensions
| Model Name | Vector Dimension | Recommended For | Source File |
|---|---|---|---|
nomic-embed-text | 768 | General purpose (default) | internal/search/service.go:21, setup-ollama.md:21 |
all-minilm | 384 | Faster performance | internal/search/service.go:23, setup-ollama.md:24 |
mxbai-embed-large | 512 | Higher accuracy | internal/search/service.go:25, setup-ollama.md:27 |
Note: The dimension for mxbai-embed-large is listed as 1024 in setup-ollama.md but implemented as 512 in internal/search/service.go. The code's implementation (512) takes precedence.
Sources: internal/search/service.go:18-29, setup-ollama.md:20-28
Embedding Providers
Ollama
The embeddings.FromOllama function sends a POST request to the /api/embeddings endpoint of the configured OLLAMA_URL. It uses the model specified by EMBEDDING_MODEL.
Sources: internal/embeddings/ollama.go:19-63
OpenAI
The embeddings.FromOpenAI function sends a POST request to the https://api.openai.com/v1/embeddings endpoint, authenticating with an OPENAI_API_KEY.
Sources: internal/embeddings/openai.go:23-65
The diagram below shows the sequence for generating an embedding from a text string using Ollama.
Data Ingestion
The search.IngestNotes function handles the process of adding notes to the vector database. This involves initializing the Qdrant client, ensuring the collection exists, generating an embedding for each note, and batch-upserting the data.
The ingestion flow is as follows:
- A new Qdrant client is created.
- The vector dimension is determined from the
EMBEDDING_MODEL. - A Qdrant collection is created if it doesn't already exist, using the determined dimension and
Cosinedistance metric. - For each note string:
a. An embedding vector is generated usingembeddings.FromOllama.
b. Aqdrant.Pointobject is created, containing a unique ID, the vector, and a payload with the original text. - All
Pointobjects are sent to Qdrant in a singleUpsertoperation.
Sources: internal/search/service.go:42-70
This flowchart illustrates the data ingestion process.
Sources: internal/search/service.go:42-70
Data Structure: qdrant.Point
Each note is stored as a Point in Qdrant. This structure maps a vector to its original content and metadata.
| Field | Type | Description |
|---|---|---|
ID | interface{} | A unique identifier for the point. In this implementation, it's an integer. |
Vector | []float32 | The embedding vector representing the note's content. |
Payload | map[string]any | A JSON object containing metadata, such as the original text and tags. |
Sources: internal/qdrant/client.go:26-30, internal/search/service.go:57-63
Search and Querying
The search.Query function performs a semantic search. It takes a query string and the desired number of results (topK) as input.
The query process is as follows:
- A Qdrant client is created.
- The query string
qis converted into an embedding vector usingembeddings.FromOllama. - The
client.Searchmethod is called with the query vector andtopKlimit. - A naive reranking is applied: if a result's text contains the query string (case-insensitive), its score is boosted by
0.01. - The list of results is returned.
Sources: internal/search/service.go:72-93
This sequence diagram shows the query workflow.
Qdrant Client
The internal/qdrant/client.go package provides a lightweight HTTP client for interacting with the Qdrant vector database API.
Client Structure and Methods
The Client struct holds the configuration needed to communicate with a Qdrant instance.
Sources: internal/qdrant/client.go:65-219
Key API Interactions
| Method | HTTP Verb | Endpoint | Purpose |
|---|---|---|---|
CreateCollectionIfNotExists | PUT | /collections/{name} | Creates a new vector collection. Ignores 409 Conflict if it already exists. |
Upsert | PUT | /collections/{name}/points?wait=true | Inserts or updates a batch of points in the collection. |
Search | POST | /collections/{name}/points/search | Finds the most similar points to a given query vector. |
Sources: internal/qdrant/client.go:107-219