AACSearch

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

ApproachWho computes the vectorUse when
Auto-embeddingThe engine, from the fields you point atYou want semantic search without running an embedding pipeline
Explicit vectorsYou, before indexingYou 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:

  1. Looks up the index's auto-embedding field. None found → 400 INVALID_QUERY, message Index "…" has no auto-embedding field; add one in the schema to use semantic search.
  2. Appends the embedding field to query_by.
  3. Forces prefix=false, because a remote embedder cannot prefix-match.
  4. Adds vector_query=<field>:([], k:<per_page>, alpha:<alpha>) — the empty vector asks the engine to embed the query text itself, and k follows 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

CapabilityFeature flagNotes
semantic=truehybridSearchOff on Free
vector_queryvectorSearchOff on Free
Auto-embedding at index timeengine ≥ 30Check 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 from fields; pointing it at a 200 KB content field dilutes the vector. Consider a dedicated summary field of 1–2 sentences.
  • Keep keywords for exactness. Leave alpha below 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/overview gives the zero-result rate before and after; /analytics/experiments compares 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.

On this page