Semantic search
Auto-embedding fields, hybrid ranking with alpha, and when to use explicit vector queries instead.
Keyword search finds documents that share tokens with the query. Semantic search finds documents that mean the same thing — "how much does pro cost" matching a page titled "Pricing". AACSearch supports both in one index, and one request can blend them.
Two ways to get vectors
| Approach | Who computes the vector | Use when |
|---|---|---|
| Auto-embedding | The engine, from the fields you point at | You want semantic search without running an embedding pipeline |
| Explicit vectors | You, before indexing | You already have embeddings, or use a model the node cannot call |
Both need a float[] field. They differ in who fills it.
Auto-embedding field
Declare a vector field with an embed block when creating the index:
{
"name": "Documentation",
"slug": "documentation",
"fields": [
{
"name": "embedding",
"type": "float[]",
"embed": {
"from": ["title", "content"],
"model": "openai/text-embedding-3-small"
},
"optional": true
}
]
}from takes 1–4 source fields. The model must be available to the search node. OpenAI models without a custom URL may use managed platform authentication. Custom URLs receive only explicitly supplied customer credentials. Built-in models and other providers do not receive the platform OpenAI key.
Provider keys and URL credentials are encrypted at rest and masked in schema responses. Legacy plaintext remains readable for compatibility; the rotation command seals it. Changing the model, source fields, endpoint or key requires an index rebuild.
Two rules the platform enforces: the field must be float[], and it must not be declared twice — you cannot list embedding in fields and in settings.vector.
Documents are then sent without a vector. The engine embeds title and content at index time:
curl -sS -X POST "$AAC_API/indexes/documentation/documents" \
-H "Authorization: Bearer $AAC_KEY" \
-H "Content-Type: application/json" \
-d '{
"documents": [
{ "external_id": "pricing", "title": "Pricing", "content": "Pro is 29 USD per month and includes 500,000 searches." }
],
"action": "upsert"
}'Changing the from fields or the model later requires a reindex — embeddings are computed at write time.
Hybrid search with alpha
curl -sS -G "$AAC_API/indexes/documentation/search" \
-H "Authorization: Bearer $AAC_KEY" \
--data-urlencode "q=what does the pro plan include" \
--data-urlencode "semantic=true" \
--data-urlencode "alpha=0.6" \
--data-urlencode "per_page=5"What the platform does with semantic=true:
- Looks up the index's auto-embedding field. None found →
400 INVALID_QUERY, messageIndex "…" has no auto-embedding field; add one in the schema to use semantic search. - Appends the embedding field to
query_by. - Forces
prefix=false, because a remote embedder cannot prefix-match. - Adds
vector_query=<field>:([], k:<per_page>, alpha:<alpha>)— the empty vector asks the engine to embed the query text itself, andkfollows your page size (capped at 250).
alpha is the blend: 0 ranks by keyword only, 1 by vector only, the default 0.5 is an even mix. Start at 0.5, raise it when users phrase questions rather than keywords, lower it when exact terms (SKUs, error codes) must win.
{
"index": "documentation",
"query": "what does the pro plan include",
"language": "en",
"found": 3,
"out_of": 137,
"hits": [
{
"id": "pricing",
"score": 6890,
"document": { "title": "Pricing", "external_id": "pricing" },
"highlights": []
}
],
"request_id": "req_6a8c0e2b4d6f8a0c2e4b6d8f"
}Hybrid hits often come back without highlights, because a vector match has no matching token to mark. Render document.title and a stored excerpt rather than relying on highlights when semantic=true.
Explicit vectors
If you embed documents yourself, declare the dimensions and metric instead of an embed block:
{
"fields": [
{
"name": "embedding",
"type": "float[]",
"numDim": 1536,
"vecDist": "cosine",
"optional": true
}
]
}Then send embedding with each document and query with a vector:
curl -sS -G "$AAC_API/indexes/documentation/search" \
-H "Authorization: Bearer $AAC_KEY" \
--data-urlencode "q=pro plan" \
--data-urlencode "vector_query=embedding:([0.0123,-0.0441,0.0872], k:10, alpha:0.5)" \
--data-urlencode "per_page=10"vector_query must match field:([numbers], k:…, alpha:…); the k-NN parameters are validated before the engine sees them and a malformed expression is 400 INVALID_QUERY with the message vector_query must look like embedding:([0.1,0.2], k:10, alpha:0.6).
numDim accepts 1–4096 and vecDist is cosine (default), ip or l2.
Plan gates
| Capability | Feature flag | Notes |
|---|---|---|
semantic=true | hybridSearch | Off on Free |
vector_query | vectorSearch | Off on Free |
| Auto-embedding at index time | engine ≥ 30 | Check GET /capabilities → features.autoEmbedding |
A request that uses a gated feature on a plan that lacks it fails rather than degrading silently — that is deliberate: a keyword-only result set returned for a semantic query looks like a relevance bug.
Practical advice
- Embed short, search short. Auto-embedding averages the
fromfields; pointing it at a 200 KBcontentfield dilutes the vector. Consider a dedicatedsummaryfield of 1–2 sentences. - Keep keywords for exactness. Leave
alphabelow 1 unless you have no codes or names in the corpus. - Reindex after changing the model. Old documents keep old vectors; mixing two embedding spaces in one field produces nonsense rankings.
- Measure zero-result rate, not vibes.
/analytics/overviewgives the zero-result rate before and after;/analytics/experimentscompares two profiles over the same traffic. - Facets still work. Filters and facets are applied to the candidate set before ranking, so hybrid search does not break categorised browsing.
Schema views and migration plans display provider keys and URL credentials as ***. Echoing a mask for the same provider and endpoint preserves the stored credentials. Supply credentials again when changing either. Editing another field does not require copying the provider key from settings.