Yeti - The Performance Platform for Agent-Driven Development. Schema-driven APIs, real-time streaming, and vector search. From prompt to production.
Semantic search in one schema file. Add articles, search by meaning, stream updates in real-time.
Demo-vector shows how a single Vector field in a GraphQL schema gives you automatic text-to-vector embedding, HNSW nearest-neighbor search, and SSE streaming with zero custom backend code. Drop in 100 diverse articles spanning science, history, cooking, and engineering, then search by natural language. "Storage engine tradeoffs" finds the database article. "Baking temperature control" finds the French pastry article. No keyword matching -- pure semantic similarity powered by local ONNX models.
Building semantic search typically means deploying a vector database, an embedding service, a REST API layer, and a frontend -- four moving parts, each with its own configuration and failure modes. Most tutorials skip the hard parts: index management, real-time updates, embedding lifecycle.
Demo-vector collapses all of that into a single schema declaration:
- Zero backend code -- no Rust resources, no custom endpoints. The entire API is auto-generated from one
.graphqlfile. TheVectortype and@indexeddirective do all the work. - Automatic embedding on write -- POST an article with plain text content and the
yeti-vectorsextension generates a 384-dimensional embedding viaBAAI/bge-small-en-v1.5before the record hits storage. - HNSW vector indexing -- cosine similarity search built into the table layer. Sub-millisecond nearest-neighbor queries on native Rust HNSW indexes.
- Real-time SSE streaming -- the
@exportdirective gives you server-sent events for free. The React frontend subscribes and displays enriched articles (with embeddings) as they arrive. - 100 sample articles -- a curated dataset spanning machine learning, French pastry, volcanic geology, jazz theory, coral reef biology, Renaissance architecture, and 94 more topics for meaningful semantic search demonstrations.
- Interactive React UI -- insert articles, watch them stream back with embeddings via SSE, and search by natural language with highlighted JSON results.
- Fully offline -- all embedding models run locally via ONNX. No API keys, no external calls, no internet required.
cd ~/yeti/applications
git clone https://github.com/yetirocks/demo-vector.gitRestart yeti. The frontend builds automatically on first load via npm run build.
curl -X POST https://localhost:9996/demo-vector/Article \
-H "Content-Type: application/json" \
-d '{
"id": "article-1",
"title": "Introduction to Machine Learning",
"author": "Alice Chen",
"category": "Technology",
"tags": "beginner,ai,tutorial",
"content": "Machine learning is a branch of artificial intelligence that focuses on building systems that learn from data."
}'Response:
{
"id": "article-1",
"title": "Introduction to Machine Learning",
"author": "Alice Chen",
"category": "Technology",
"tags": "beginner,ai,tutorial",
"content": "Machine learning is a branch of artificial intelligence...",
"embedding": [0.0234, -0.0891, 0.0412, 0.0567, "... 384 dims"]
}The embedding field is automatically generated from the content field using BAAI/bge-small-en-v1.5. No separate embedding call required.
curl "https://localhost:9996/demo-vector/Article/?query=%7B%22conditions%22%3A%5B%7B%22field%22%3A%22embedding%22%2C%22op%22%3A%22vector%22%2C%22value%22%3A%22neural%20network%20training%22%7D%5D%2C%22limit%22%3A5%7D"The query JSON (URL-decoded):
{
"conditions": [
{ "field": "embedding", "op": "vector", "value": "neural network training" }
],
"limit": 5
}Response (ranked by similarity, nearest first):
[
{
"id": "article-1",
"title": "Introduction to Machine Learning",
"author": "Alice Chen",
"category": "Technology",
"content": "Machine learning is a branch of artificial intelligence...",
"$distance": 0.234,
"embedding": [0.0234, -0.0891, "... 384 dims"]
}
]The search text "neural network training" is embedded on the fly and compared against all stored article embeddings via HNSW cosine similarity. Results include a $distance field (lower is more similar).
# SSE stream -- receive enriched articles as they are inserted
curl -N "https://localhost:9996/demo-vector/Article/"Output (server-sent events):
event: update
data: {"id":"article-2","title":"The Art of French Pastry","author":"Pierre Dubois",...,"embedding":[...]}
event: update
data: {"id":"article-3","title":"Volcanic Activity on Io","author":"Maria Santos",...,"embedding":[...]}
curl "https://localhost:9996/demo-vector/Article/?limit=10"curl -X DELETE "https://localhost:9996/demo-vector/Article/article-1"Navigate to https://localhost:9996/demo-vector/ in your browser. The React frontend provides:
- One-click article insertion from 100 sample articles
- Live SSE stream showing enriched articles with truncated embeddings
- Natural language search with syntax-highlighted JSON results
- Timing information for inserts and searches
- Delete all records with confirmation modal
Browser (React/Vite) CLI / Agents
| |
+-- POST /Article ----------------------->+
+-- GET /Article/?query={vector} ------->+
+-- SSE /Article/ ---------------------->+
| |
v v
+----------------------------------------------------------+
| demo-vector |
| |
| config.yaml ---- schemas/vector.graphql |
| | |
| v |
| Article table (auto-generated) |
| +-----------------------------------+ |
| | id | title | author | category | |
| | tags | content | embedding (384d) | |
| +-----------------------------------+ |
| | |
| v |
| yeti-vectors extension |
| +-----------------------------------+ |
| | BAAI/bge-small-en-v1.5 (ONNX) | |
| | Auto-embed on write | |
| | HNSW index (cosine similarity) | |
| +-----------------------------------+ |
| |
+----------------------------------------------------------+
|
v
Yeti (embedded RocksDB, native HNSW, SSE broadcast)
Write path: POST article -> yeti-vectors intercepts Vector field -> embeds content via ONNX model -> stores record + embedding in RocksDB -> updates HNSW index -> broadcasts via SSE.
Read path: Query with "op": "vector" -> embeds query text on the fly -> HNSW nearest-neighbor search -> returns results ranked by cosine distance.
No custom backend code. The entire application is one schema file and a React frontend. All REST endpoints, SSE streaming, vector embedding, and HNSW indexing are provided by the platform.
The Vector type with @indexed(source: "content") tells yeti to automatically generate an embedding from the content field whenever a record is created or updated. The embedding model is specified in the schema directive:
embedding: Vector @indexed(source: "content", model: "BAAI/bge-small-en-v1.5")No API calls to OpenAI or other services. The BAAI/bge-small-en-v1.5 model runs locally via ONNX runtime, producing 384-dimensional vectors optimized for cosine similarity.
Yeti maintains an in-memory HNSW (Hierarchical Navigable Small World) index for each @indexed Vector field. Search queries embed the input text using the same model and return results ranked by cosine distance:
# Find articles about space exploration
GET /demo-vector/Article/?query={"conditions":[{"field":"embedding","op":"vector","value":"space exploration rockets"}],"limit":5}
# Find articles about food science
GET /demo-vector/Article/?query={"conditions":[{"field":"embedding","op":"vector","value":"fermentation and microbiology"}],"limit":5}The $distance field in results indicates similarity (lower = more similar). Typical distances range from 0.1 (very similar) to 1.0+ (unrelated).
The @export directive on the Article table enables server-sent events. The React frontend subscribes to the SSE endpoint and displays articles as they arrive, complete with their generated embeddings:
updateevents fire when articles are inserted or modifieddeleteevents fire when articles are removed- Automatic reconnection with exponential backoff (1s to 10s)
The schema declares public access for read, create, and delete operations:
@export(public: [read, create, delete])This means the demo works without authentication in both development and production modes. Write operations (POST, DELETE) are open, making it suitable for demonstration purposes.
The frontend includes a curated dataset of 100 articles across diverse topics, making semantic search demonstrations meaningful:
| Category Range | Topics |
|---|---|
| General Knowledge | Machine learning, French pastry, volcanic geology, Silk Road history, jazz improvisation |
| Earth Sciences | Plate tectonics, water cycle, rainforest ecosystems, glaciology, soil science |
| Technology | Cryptographic hashing, CPU architecture, database indexing, quantum computing, distributed consensus |
| Life Sciences | mRNA vaccines, photosynthesis, human microbiome, CRISPR, neuroscience of memory |
| Arts & Humanities | Golden ratio, typography, wabi-sabi, film noir, Sanskrit linguistics |
| Engineering | Nuclear reactors, bridge engineering, aerodynamics, rocket propulsion, optical fiber |
| Interdisciplinary | Sleep science, cheese making, map projections, origami math, game theory |
Each article has structured fields (title, author, category, tags) plus long-form content that produces meaningful vector embeddings.
The web UI at /demo-vector/ provides a two-panel layout:
Left panel -- Insert & Stream:
- "Add Record" button cycles through 100 sample articles
- "Delete All" with confirmation modal
- Inline schema display with GraphQL syntax highlighting
- Live SSE stream showing enriched articles with truncated embedding arrays
Right panel -- Vector Search:
- Natural language search input with Enter key support
- Request JSON display showing the exact query sent
- Results with syntax-highlighted JSON, similarity distances, and timing
- Model badge showing
BAAI/bge-small-en-v1.5
| Field | Type | Directives | Description |
|---|---|---|---|
id |
ID! | @primaryKey | Unique article identifier |
title |
String! | -- | Article title |
author |
String! | -- | Author name |
category |
String! | -- | Topic category |
tags |
String | -- | Comma-separated tags |
content |
String! | -- | Full article text (embedding source) |
embedding |
Vector | @indexed(source: "content", model: "BAAI/bge-small-en-v1.5") | 384-dimensional auto-generated embedding |
type Article @table(database: "demo-vector") @export(public: [read, create, delete]) {
id: ID! @primaryKey
title: String!
author: String!
category: String!
tags: String
content: String!
embedding: Vector @indexed(source: "content", model: "BAAI/bge-small-en-v1.5")
}Key directives:
@table(database: "demo-vector")-- stores records in a dedicated RocksDB database@export(public: [read, create, delete])-- generates REST + SSE endpoints with public access@primaryKey-- designatesidas the record key@indexed(source: "content", model: "...")-- auto-embeds thecontentfield using the specified model
App configuration lives in Cargo.toml under [package.metadata.app]. There is no separate config.yaml or services.yaml.
[package]
name = "demo-vector"
version = "1.0.0"
description = "Automatic text-to-vector embedding with HNSW nearest-neighbor semantic search"
[package.metadata.app]
schemas = "schemas/vector.graphql"
static = { path = "web", source = "source", spa = true, build = "npm install && npm run build" }| Field | Value | Description |
|---|---|---|
package.name |
demo-vector | URL prefix for all endpoints |
schemas |
schemas/vector.graphql | Single schema defining the Article table |
static.path |
web | Built frontend served at /demo-vector/ |
static.spa |
true | SPA mode -- all routes fall back to index.html |
static.source / static.build |
source -> npm install && npm run build | Auto-builds from source/ on first load |
The default model BAAI/bge-small-en-v1.5 is downloaded automatically on first use by the yeti-vectors extension. To manage available models:
# List available models
GET /yeti-vectors/models
# Download a different model
POST /yeti-vectors/models
{ "model": "BAAI/bge-base-en-v1.5" }Supported local embedding models:
| Model | Dimensions | Notes |
|---|---|---|
| BAAI/bge-small-en-v1.5 | 384 | Default. Fast, good quality. Used by this demo. |
| BAAI/bge-base-en-v1.5 | 768 | Higher quality, larger index. |
| BAAI/bge-large-en-v1.5 | 1024 | Best quality, heaviest. |
| sentence-transformers/all-MiniLM-L6-v2 | 384 | Popular alternative. |
| Xenova/jina-embeddings-v2-small-en | 512 | Good for short text. |
All models run locally via ONNX. No API keys, no external calls, no internet required.
All endpoints are auto-generated from the schema. No custom resources exist.
| Endpoint | Methods | Description |
|---|---|---|
/demo-vector/Article |
GET, POST | List articles (with optional vector query) or create a new article |
/demo-vector/Article/{id} |
GET, PUT, DELETE | Read, update, or delete a single article |
/demo-vector/Article/ |
GET (SSE) | Server-sent events stream for real-time updates |
| Parameter | Example | Description |
|---|---|---|
query |
{"conditions":[{"field":"embedding","op":"vector","value":"search text"}],"limit":10} |
Vector similarity search |
limit |
20 |
Maximum number of results |
demo-vector/
├── Cargo.toml # App configuration under [package.metadata.app]
├── schemas/
│ └── vector.graphql # Article table with Vector field
├── source/ # React/Vite frontend
│ ├── package.json # Dependencies: React 18, highlight.js, Vite 5
│ ├── vite.config.ts # Auto-reads base path from Cargo.toml
│ ├── tsconfig.json
│ └── src/
│ ├── main.tsx # React entry point
│ ├── App.tsx # Thin shell -- wires auth gate + page
│ ├── api.ts # Fetch helpers
│ ├── types.ts # Shared TypeScript types
│ ├── utils.ts # JSON syntax highlighting utility
│ ├── articles.ts # 100 sample articles dataset
│ ├── components/
│ │ └── Footer.tsx # Shared UI primitives
│ ├── hooks/
│ │ └── useAuth.ts # Auth state hook (template)
│ ├── pages/
│ │ ├── VectorPage.tsx # Main two-panel search interface
│ │ └── Login.tsx # Configurable login page (template)
│ └── styles/
│ ├── _vars.css # Per-app brand colors and shared tokens
│ ├── yeti.css # Canonical Yeti stylesheet
│ └── index.css # App-specific overrides
└── web/ # Built output (auto-generated)
The src/ layout is the standard yeti UI app structure: a thin App.tsx, root utility modules (api.ts, types.ts, utils.ts), shared UI in components/, hooks in hooks/, pages in pages/ (including the bundled Login.tsx), and stylesheets in styles/. yeti.css is the canonical stylesheet shared across all yeti apps; _vars.css holds this app's brand tokens; index.css carries app-specific overrides.
demo-vector declares @export(public: [read, create, delete]), so the demo runs without credentials. To gate it, add a [package.metadata.auth] section in Cargo.toml (methods, JWT, OAuth providers, role rules) and wrap the SPA with the bundled Login.tsx + useAuth hook:
const auth = useAuth()
if (auth === null) return <Loading/>
if (!auth) return <Login/>
return <VectorPage/>The Login component takes optional logo, title, subtitle, and redirectUri props for branding.
cd ~/yeti/applications/demo-vector/source
# Install dependencies
npm install
# Start dev server with HMR (port 5180)
npm run dev
# Build for production (outputs to ../web/)
npm run buildThe Vite config automatically reads app_id from config.yaml to set the correct base path, so built assets resolve correctly when served by yeti at /demo-vector/.
| demo-vector | Traditional Vector Search Setup | |
|---|---|---|
| Backend code | None -- schema only | REST API + embedding pipeline + index management |
| Embedding | Automatic on write, local ONNX | External API calls (OpenAI, Cohere), API keys, latency |
| Vector index | Built-in HNSW from schema directive | Separate vector DB (Pinecone, Qdrant, Weaviate) |
| Real-time | SSE from @export, zero config |
Custom WebSocket server or polling |
| Search API | Auto-generated query parameter | Custom endpoint, query parsing, result formatting |
| Configuration | One .graphql file, 7 lines |
Vector DB config + embedding service config + API config |
| Deployment | Loads with yeti, no separate services | Docker compose with 3-4 containers |
| Offline | Fully functional, local ONNX models | Requires cloud API connectivity |
| Frontend | Included React app with live demo | Build your own |
Built with Yeti | The Performance Platform for Agent-Driven Development
