440 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
			
		
		
	
	
			440 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
| # Lance Vector Backend (RESP + JSON-RPC)
 | ||
| 
 | ||
| This document explains how to use HeroDB’s Lance-backed vector store. It is text-first: users provide text, and HeroDB computes embeddings server-side (no manual vectors). It includes copy-pasteable RESP (redis-cli) and JSON-RPC examples for:
 | ||
| 
 | ||
| - Creating a Lance database
 | ||
| - Embedding provider configuration (OpenAI, Azure OpenAI, or deterministic test provider)
 | ||
| - Dataset lifecycle: CREATE, LIST, INFO, DROP
 | ||
| - Ingestion: STORE text (+ optional metadata)
 | ||
| - Search: QUERY with K, optional FILTER and RETURN
 | ||
| - Delete by id
 | ||
| - Index creation (currently a placeholder/no-op)
 | ||
| 
 | ||
| References:
 | ||
| - Implementation: [src/lance_store.rs](src/lance_store.rs), [src/cmd.rs](src/cmd.rs), [src/rpc.rs](src/rpc.rs), [src/server.rs](src/server.rs), [src/embedding.rs](src/embedding.rs)
 | ||
| 
 | ||
| Notes:
 | ||
| - Admin DB 0 cannot be Lance (or Tantivy). Only databases with id >= 1 can use Lance.
 | ||
| - Permissions:
 | ||
|   - Read operations (SEARCH, LIST, INFO) require read permission.
 | ||
|   - Mutating operations (CREATE, STORE, CREATEINDEX, DEL, DROP, EMBEDDING CONFIG SET) require readwrite permission.
 | ||
| - Backend gating:
 | ||
|   - If a DB is Lance, only LANCE.* and basic control commands (PING, ECHO, SELECT, INFO, CLIENT, etc.) are permitted.
 | ||
|   - If a DB is not Lance, LANCE.* commands return an error.
 | ||
| 
 | ||
| Storage layout and schema:
 | ||
| - Files live at: <base_dir>/lance/<db_id>/<dataset>.lance
 | ||
| - Records schema:
 | ||
|   - id: Utf8 (non-null)
 | ||
|   - vector: FixedSizeList<Float32, dim> (non-null)
 | ||
|   - text: Utf8 (nullable)
 | ||
|   - meta: Utf8 JSON (nullable)
 | ||
| - Search is an L2 KNN brute-force scan for now (lower score = better). Index creation is a no-op placeholder to be implemented later.
 | ||
| 
 | ||
| Prerequisites:
 | ||
| - Start HeroDB with RPC enabled (for management calls):
 | ||
|   - See [docs/basics.md](./basics.md) for flags. Example:
 | ||
|     ```bash
 | ||
|     ./target/release/herodb --dir /tmp/herodb --admin-secret mysecret --port 6379 --enable-rpc
 | ||
|     ```
 | ||
| 
 | ||
| 
 | ||
| ## 0) Create a Lance-backed database (JSON-RPC)
 | ||
| 
 | ||
| Use the management API to create a database with backend "Lance". DB 0 is reserved for admin and cannot be Lance.
 | ||
| 
 | ||
| Request:
 | ||
| ```json
 | ||
| {
 | ||
|   "jsonrpc": "2.0",
 | ||
|   "id": 1,
 | ||
|   "method": "herodb_createDatabase",
 | ||
|   "params": [
 | ||
|     "Lance",
 | ||
|     { "name": "vectors-db", "storage_path": null, "max_size": null, "redis_version": null },
 | ||
|     null
 | ||
|   ]
 | ||
| }
 | ||
| ```
 | ||
| 
 | ||
| - Response contains the allocated db_id (>= 1). Use that id below (replace 1 with your actual id).
 | ||
| 
 | ||
| Select the database over RESP:
 | ||
| ```bash
 | ||
| redis-cli -p 6379 SELECT 1
 | ||
| # → OK
 | ||
| ```
 | ||
| 
 | ||
| 
 | ||
| ## 1) Configure embedding provider (server-side embeddings)
 | ||
| 
 | ||
| HeroDB embeds text internally at STORE/SEARCH time using a per-dataset EmbeddingConfig sidecar. Configure provider before creating a dataset to choose dimensions and provider.
 | ||
| 
 | ||
| Supported providers:
 | ||
| - openai (standard OpenAI API or custom OpenAI-compatible endpoints)
 | ||
| - testhash (deterministic, CI-friendly; no network)
 | ||
| 
 | ||
| Environment variable for OpenAI:
 | ||
| - Standard OpenAI: export OPENAI_API_KEY=sk-...
 | ||
| 
 | ||
| RESP examples:
 | ||
| ```bash
 | ||
| # Standard OpenAI with default dims (model-dependent, e.g. 1536)
 | ||
| redis-cli -p 6379 LANCE.EMBEDDING CONFIG SET myset PROVIDER openai MODEL text-embedding-3-small
 | ||
| 
 | ||
| # OpenAI with reduced output dimension (e.g., 512) when supported
 | ||
| redis-cli -p 6379 LANCE.EMBEDDING CONFIG SET myset PROVIDER openai MODEL text-embedding-3-small PARAM dim 512
 | ||
| 
 | ||
| # Custom OpenAI-compatible endpoint (e.g., self-hosted)
 | ||
| redis-cli -p 6379 LANCE.EMBEDDING CONFIG SET myset PROVIDER openai MODEL text-embedding-3-small \
 | ||
|   PARAM endpoint http://localhost:8081/v1/embeddings \
 | ||
|   PARAM dim 512
 | ||
| 
 | ||
| # Deterministic test provider (no network, stable vectors)
 | ||
| redis-cli -p 6379 LANCE.EMBEDDING CONFIG SET myset PROVIDER testhash MODEL any
 | ||
| ```
 | ||
| 
 | ||
| Read config:
 | ||
| ```bash
 | ||
| redis-cli -p 6379 LANCE.EMBEDDING CONFIG GET myset
 | ||
| # → JSON blob describing provider/model/params
 | ||
| ```
 | ||
| 
 | ||
| JSON-RPC examples:
 | ||
| ```json
 | ||
| {
 | ||
|   "jsonrpc": "2.0",
 | ||
|   "id": 2,
 | ||
|   "method": "herodb_lanceSetEmbeddingConfig",
 | ||
|   "params": [
 | ||
|     1,
 | ||
|     "myset",
 | ||
|     "openai",
 | ||
|     "text-embedding-3-small",
 | ||
|     { "dim": "512" }
 | ||
|   ]
 | ||
| }
 | ||
| ```
 | ||
| 
 | ||
| ```json
 | ||
| {
 | ||
|   "jsonrpc": "2.0",
 | ||
|   "id": 3,
 | ||
|   "method": "herodb_lanceGetEmbeddingConfig",
 | ||
|   "params": [1, "myset"]
 | ||
| }
 | ||
| ```
 | ||
| 
 | ||
| 
 | ||
| ## 2) Create a dataset
 | ||
| 
 | ||
| Choose a dimension that matches your embedding configuration. For OpenAI text-embedding-3-small without dimension override, typical dimension is 1536; when `dim` is set (e.g., 512), use that. The current API requires an explicit DIM.
 | ||
| 
 | ||
| RESP:
 | ||
| ```bash
 | ||
| redis-cli -p 6379 LANCE.CREATE myset DIM 512
 | ||
| # → OK
 | ||
| ```
 | ||
| 
 | ||
| JSON-RPC:
 | ||
| ```json
 | ||
| {
 | ||
|   "jsonrpc": "2.0",
 | ||
|   "id": 4,
 | ||
|   "method": "herodb_lanceCreate",
 | ||
|   "params": [1, "myset", 512]
 | ||
| }
 | ||
| ```
 | ||
| 
 | ||
| 
 | ||
| ## 3) Store text documents (server-side embedding)
 | ||
| 
 | ||
| Provide your id, the text to embed, and optional META fields. The server computes the embedding using the configured provider and stores id/vector/text/meta in the Lance dataset. Upserts by id are supported via delete-then-append semantics.
 | ||
| 
 | ||
| RESP:
 | ||
| ```bash
 | ||
| redis-cli -p 6379 LANCE.STORE myset ID doc-1 TEXT "Hello vector world" META title "Hello" category "demo"
 | ||
| # → OK
 | ||
| ```
 | ||
| 
 | ||
| JSON-RPC:
 | ||
| ```json
 | ||
| {
 | ||
|   "jsonrpc": "2.0",
 | ||
|   "id": 5,
 | ||
|   "method": "herodb_lanceStoreText",
 | ||
|   "params": [
 | ||
|     1,
 | ||
|     "myset",
 | ||
|     "doc-1",
 | ||
|     "Hello vector world",
 | ||
|     { "title": "Hello", "category": "demo" }
 | ||
|   ]
 | ||
| }
 | ||
| ```
 | ||
| 
 | ||
| 
 | ||
| ## 4) Search with a text query
 | ||
| 
 | ||
| Provide a query string; the server embeds it and performs KNN search. Optional: FILTER expression and RETURN subset of fields.
 | ||
| 
 | ||
| RESP:
 | ||
| ```bash
 | ||
| # K nearest neighbors for the query text
 | ||
| redis-cli -p 6379 LANCE.SEARCH myset K 5 QUERY "greetings to vectors"
 | ||
| # → Array of hits: [id, score, [k,v, ...]] pairs, lower score = closer
 | ||
| 
 | ||
| # With a filter on meta fields and return only title
 | ||
| redis-cli -p 6379 LANCE.SEARCH myset K 3 QUERY "greetings to vectors" FILTER "category = 'demo'" RETURN 1 title
 | ||
| ```
 | ||
| 
 | ||
| JSON-RPC:
 | ||
| ```json
 | ||
| {
 | ||
|   "jsonrpc": "2.0",
 | ||
|   "id": 6,
 | ||
|   "method": "herodb_lanceSearchText",
 | ||
|   "params": [1, "myset", "greetings to vectors", 5, null, null]
 | ||
| }
 | ||
| ```
 | ||
| 
 | ||
| With filter and selected fields:
 | ||
| ```json
 | ||
| {
 | ||
|   "jsonrpc": "2.0",
 | ||
|   "id": 7,
 | ||
|   "method": "herodb_lanceSearchText",
 | ||
|   "params": [1, "myset", "greetings to vectors", 3, "category = 'demo'", ["title"]]
 | ||
| }
 | ||
| ```
 | ||
| 
 | ||
| Response shape:
 | ||
| - RESP over redis-cli: an array of hits [id, score, [k, v, ...]].
 | ||
| - JSON-RPC returns an object containing the RESP-encoded wire format string or a structured result depending on implementation. See [src/rpc.rs](src/rpc.rs) for details.
 | ||
| 
 | ||
| 
 | ||
| ## 5) Create an index (placeholder)
 | ||
| 
 | ||
| Index creation currently returns OK but is a no-op. It will integrate Lance vector indices in a future update.
 | ||
| 
 | ||
| RESP:
 | ||
| ```bash
 | ||
| redis-cli -p 6379 LANCE.CREATEINDEX myset TYPE "ivf_pq" PARAM nlist 100 PARAM pq_m 16
 | ||
| # → OK (no-op for now)
 | ||
| ```
 | ||
| 
 | ||
| JSON-RPC:
 | ||
| ```json
 | ||
| {
 | ||
|   "jsonrpc": "2.0",
 | ||
|   "id": 8,
 | ||
|   "method": "herodb_lanceCreateIndex",
 | ||
|   "params": [1, "myset", "ivf_pq", { "nlist": "100", "pq_m": "16" }]
 | ||
| }
 | ||
| ```
 | ||
| 
 | ||
| 
 | ||
| ## 6) Inspect datasets
 | ||
| 
 | ||
| RESP:
 | ||
| ```bash
 | ||
| # List datasets in current Lance DB
 | ||
| redis-cli -p 6379 LANCE.LIST
 | ||
| 
 | ||
| # Get dataset info
 | ||
| redis-cli -p 6379 LANCE.INFO myset
 | ||
| ```
 | ||
| 
 | ||
| JSON-RPC:
 | ||
| ```json
 | ||
| {
 | ||
|   "jsonrpc": "2.0",
 | ||
|   "id": 9,
 | ||
|   "method": "herodb_lanceList",
 | ||
|   "params": [1]
 | ||
| }
 | ||
| ```
 | ||
| 
 | ||
| ```json
 | ||
| {
 | ||
|   "jsonrpc": "2.0",
 | ||
|   "id": 10,
 | ||
|   "method": "herodb_lanceInfo",
 | ||
|   "params": [1, "myset"]
 | ||
| }
 | ||
| ```
 | ||
| 
 | ||
| 
 | ||
| ## 7) Delete and drop
 | ||
| 
 | ||
| RESP:
 | ||
| ```bash
 | ||
| # Delete by id
 | ||
| redis-cli -p 6379 LANCE.DEL myset doc-1
 | ||
| # → OK
 | ||
| 
 | ||
| # Drop the entire dataset
 | ||
| redis-cli -p 6379 LANCE.DROP myset
 | ||
| # → OK
 | ||
| ```
 | ||
| 
 | ||
| JSON-RPC:
 | ||
| ```json
 | ||
| {
 | ||
|   "jsonrpc": "2.0",
 | ||
|   "id": 11,
 | ||
|   "method": "herodb_lanceDel",
 | ||
|   "params": [1, "myset", "doc-1"]
 | ||
| }
 | ||
| ```
 | ||
| 
 | ||
| ```json
 | ||
| {
 | ||
|   "jsonrpc": "2.0",
 | ||
|   "id": 12,
 | ||
|   "method": "herodb_lanceDrop",
 | ||
|   "params": [1, "myset"]
 | ||
| }
 | ||
| ```
 | ||
| 
 | ||
| 
 | ||
| ## 8) End-to-end example (RESP)
 | ||
| 
 | ||
| ```bash
 | ||
| # 1. Select Lance DB (assume db_id=1 created via RPC)
 | ||
| redis-cli -p 6379 SELECT 1
 | ||
| 
 | ||
| # 2. Configure embedding provider (OpenAI small model at 512 dims)
 | ||
| redis-cli -p 6379 LANCE.EMBEDDING CONFIG SET myset PROVIDER openai MODEL text-embedding-3-small PARAM dim 512
 | ||
| 
 | ||
| # 3. Create dataset
 | ||
| redis-cli -p 6379 LANCE.CREATE myset DIM 512
 | ||
| 
 | ||
| # 4. Store documents
 | ||
| redis-cli -p 6379 LANCE.STORE myset ID doc-1 TEXT "The quick brown fox jumps over the lazy dog" META title "Fox" category "animal"
 | ||
| redis-cli -p 6379 LANCE.STORE myset ID doc-2 TEXT "A fast auburn fox vaulted a sleepy canine" META title "Fox paraphrase" category "animal"
 | ||
| 
 | ||
| # 5. Search
 | ||
| redis-cli -p 6379 LANCE.SEARCH myset K 2 QUERY "quick brown fox" RETURN 1 title
 | ||
| 
 | ||
| # 6. Dataset info and listing
 | ||
| redis-cli -p 6379 LANCE.INFO myset
 | ||
| redis-cli -p 6379 LANCE.LIST
 | ||
| 
 | ||
| # 7. Delete and drop
 | ||
| redis-cli -p 6379 LANCE.DEL myset doc-2
 | ||
| redis-cli -p 6379 LANCE.DROP myset
 | ||
| ```
 | ||
| 
 | ||
| 
 | ||
| ## 9) End-to-end example (JSON-RPC)
 | ||
| 
 | ||
| Assume RPC server on port 8080. Replace ids and ports as needed.
 | ||
| 
 | ||
| 1) Create Lance DB:
 | ||
| ```json
 | ||
| {
 | ||
|   "jsonrpc": "2.0",
 | ||
|   "id": 100,
 | ||
|   "method": "herodb_createDatabase",
 | ||
|   "params": ["Lance", { "name": "vectors-db", "storage_path": null, "max_size": null, "redis_version": null }, null]
 | ||
| }
 | ||
| ```
 | ||
| 
 | ||
| 2) Set embedding config:
 | ||
| ```json
 | ||
| {
 | ||
|   "jsonrpc": "2.0",
 | ||
|   "id": 101,
 | ||
|   "method": "herodb_lanceSetEmbeddingConfig",
 | ||
|   "params": [1, "myset", "openai", "text-embedding-3-small", { "dim": "512" }]
 | ||
| }
 | ||
| ```
 | ||
| 
 | ||
| 3) Create dataset:
 | ||
| ```json
 | ||
| {
 | ||
|   "jsonrpc": "2.0",
 | ||
|   "id": 102,
 | ||
|   "method": "herodb_lanceCreate",
 | ||
|   "params": [1, "myset", 512]
 | ||
| }
 | ||
| ```
 | ||
| 
 | ||
| 4) Store text:
 | ||
| ```json
 | ||
| {
 | ||
|   "jsonrpc": "2.0",
 | ||
|   "id": 103,
 | ||
|   "method": "herodb_lanceStoreText",
 | ||
|   "params": [1, "myset", "doc-1", "The quick brown fox jumps over the lazy dog", { "title": "Fox", "category": "animal" }]
 | ||
| }
 | ||
| ```
 | ||
| 
 | ||
| 5) Search text:
 | ||
| ```json
 | ||
| {
 | ||
|   "jsonrpc": "2.0",
 | ||
|   "id": 104,
 | ||
|   "method": "herodb_lanceSearchText",
 | ||
|   "params": [1, "myset", "quick brown fox", 2, null, ["title"]]
 | ||
| }
 | ||
| ```
 | ||
| 
 | ||
| 6) Info/list:
 | ||
| ```json
 | ||
| {
 | ||
|   "jsonrpc": "2.0",
 | ||
|   "id": 105,
 | ||
|   "method": "herodb_lanceInfo",
 | ||
|   "params": [1, "myset"]
 | ||
| }
 | ||
| ```
 | ||
| 
 | ||
| ```json
 | ||
| {
 | ||
|   "jsonrpc": "2.0",
 | ||
|   "id": 106,
 | ||
|   "method": "herodb_lanceList",
 | ||
|   "params": [1]
 | ||
| }
 | ||
| ```
 | ||
| 
 | ||
| 7) Delete/drop:
 | ||
| ```json
 | ||
| {
 | ||
|   "jsonrpc": "2.0",
 | ||
|   "id": 107,
 | ||
|   "method": "herodb_lanceDel",
 | ||
|   "params": [1, "myset", "doc-1"]
 | ||
| }
 | ||
| ```
 | ||
| 
 | ||
| ```json
 | ||
| {
 | ||
|   "jsonrpc": "2.0",
 | ||
|   "id": 108,
 | ||
|   "method": "herodb_lanceDrop",
 | ||
|   "params": [1, "myset"]
 | ||
| }
 | ||
| ```
 | ||
| 
 | ||
| 
 | ||
| ## 10) Operational notes and troubleshooting
 | ||
| 
 | ||
| - If using OpenAI and you see “missing API key env”, set:
 | ||
|   - Standard: `export OPENAI_API_KEY=sk-...`
 | ||
|   - Azure: `export AZURE_OPENAI_API_KEY=...` and pass `use_azure true`, `azure_endpoint`, `azure_deployment`, `azure_api_version`.
 | ||
| - Dimensions mismatch:
 | ||
|   - Ensure the dataset DIM equals the provider’s embedding dim. For OpenAI text-embedding-3 models, set `PARAM dim 512` (or another supported size) and use that same DIM for `LANCE.CREATE`.
 | ||
| - DB 0 restriction:
 | ||
|   - Lance is not allowed on DB 0. Use db_id >= 1.
 | ||
| - Permissions:
 | ||
|   - Read operations (SEARCH, LIST, INFO) require read permission.
 | ||
|   - Mutations (CREATE, STORE, CREATEINDEX, DEL, DROP, EMBEDDING CONFIG SET) require readwrite permission.
 | ||
| - Backend gating:
 | ||
|   - On Lance DBs, only LANCE.* commands are accepted (plus basic control).
 | ||
| - Current index behavior:
 | ||
|   - `LANCE.CREATEINDEX` returns OK but is a no-op. Future versions will integrate Lance vector indices.
 | ||
| - Implementation files for reference:
 | ||
|   - [src/lance_store.rs](src/lance_store.rs), [src/cmd.rs](src/cmd.rs), [src/rpc.rs](src/rpc.rs), [src/server.rs](src/server.rs), [src/embedding.rs](src/embedding.rs) |