Rust demo service for semantic search over user behavior text using fastembed, Axum, SeaORM, Postgres, and pgvector.
The project stores short behavior descriptions, turns them into embeddings, and searches for behavior records by semantic similarity instead of exact keyword matching.
Traditional search usually compares words directly. Embedding search converts text into vectors, then compares the meaning of those vectors.
This service follows this flow:
- A client sends user behavior text to
POST /api/v0/user. - The app uses
fastembedwithAllMiniLML6V2to convert the behavior text into a384-dimension vector. - The original behavior text and its vector are stored in Postgres.
- A client sends search text to
GET /api/v0/search. - The app embeds the search text with the same model.
- Postgres ranks stored rows by cosine distance with
pgvectorusingembedding <=> query_embedding. - The API returns the top 5 most similar behavior records.
Example: a search for likes outdoor activities can match records like went hiking on the weekend even though the words are not exactly the same.
HTTP client
|
v
Axum API (`src/transport/http`)
|
v
Storage trait (`src/storage.rs`)
|
+--> fastembed model (`src/embedding`)
|
+--> Postgres + pgvector (`src/storage/postgres.rs`)
Main components:
src/main.rs: CLI entry point for starting the server and running migrations.src/transport/http: Axum routes and HTTP server setup.src/embedding: wrapper around thefastembedtext embedding model.src/storage: storage interface, Postgres implementation, SeaORM models, and migrations.docker-compose.yaml: local Postgres database using thepgvector/pgvectorimage.
Start by adding a few behavior records:
curl -X POST http://127.0.0.1:8080/api/v0/user \
-H 'content-type: application/json' \
-d '{"user_id":"user-1","behavior":"watched several videos about Rust web services"}'curl -X POST http://127.0.0.1:8080/api/v0/user \
-H 'content-type: application/json' \
-d '{"user_id":"user-2","behavior":"read articles about hiking trails and camping gear"}'curl -X POST http://127.0.0.1:8080/api/v0/user \
-H 'content-type: application/json' \
-d '{"user_id":"user-3","behavior":"searched for async programming tutorials"}'Then search by meaning:
curl -X GET http://127.0.0.1:8080/api/v0/search \
-H 'content-type: text/plain' \
--data 'learning backend development with Rust'The result should prioritize behavior records that are semantically close to the query, such as Rust web service or async programming activity.
- Rust toolchain
- Docker
- Docker Compose
docker compose up -dThis starts Postgres on localhost:5432 with:
- user:
app - password:
app - database:
app
cargo run -- startBy default, the server listens on 127.0.0.1:8080 and runs migrations automatically.
The first run may download the embedding model into .fastembed_cache.
cargo run -- migrate upcargo run -- migrate downThe migration creates:
- the
vectorPostgres extension - the
user_behaviortable - a
vector(384)embedding column - an HNSW index using
vector_cosine_ops
Configuration is available through CLI flags and environment variables.
| Setting | Default | Description |
|---|---|---|
DATABASE / --database |
postgresql://app:app@localhost:5432/app |
Postgres connection string |
HTTP_ADDR / --http-addr |
127.0.0.1:8080 |
HTTP listen address |
MIGRATION / --migration |
true |
Run migrations when starting the server |
EMBEDDING_BATCH / --embedding-batch |
unset | Optional embedding batch size passed to fastembed |
STEP / --step |
unset | Optional migration step count for migrate up or migrate down |
Example:
HTTP_ADDR=127.0.0.1:3000 cargo run -- start- Embedding model:
AllMiniLML6V2 - Embedding dimension:
384 - Search distance: cosine distance through
pgvector - Search limit: top 5 records
- Database access: SeaORM with Postgres
- HTTP framework: Axum
Local generated data is ignored by git:
target/data/.fastembed_cache/
If Postgres cannot start, check whether another process is already using port 5432.
If the API cannot connect to the database, make sure docker compose up -d is running and the database URL matches postgresql://app:app@localhost:5432/app.
If the first request or startup is slow, the embedding model may still be downloading or initializing.
If you want to reset local database state, stop Postgres and remove the local data/ directory:
docker compose down
rm -rf data
docker compose up -d