BrainAPI
BrainAPI
RetrieveSearch

For the complete BrainAPI documentation index, see llms.txt. A markdown version of any docs page is available by appending .md to its URL. Docs MCP: /docs/mcp.

Search

Build ranked hybrid, graph-aware, plugin-assisted, and personalized search over one BrainAPI knowledge base

GET|POST /retrieve/search returns addressable, ranked hits from a BrainAPI brain. Use it to power documentation search, support portals, research discovery, incident consoles, catalogs, and any interface that needs ordered results with scores and snippets.

A search hit is deliberately smaller and more explicit than an answer. It tells the caller what matched, why it ranked, and where to retrieve or display it. Your application decides whether to show the hit, open the source, send it to an LLM, or begin a deeper investigation.

For agents

  • Search is opt-in: set SEARCH_ENABLED=true and use PostgreSQL
  • Defaults: k=10, channels=["passages"], expand="none", mode="default", and no plugin reranker
  • Use POST for the full contract, including extras, fusion_alpha, and mode
  • A disabled Search endpoint returns HTTP 404; an unknown or unloaded plugin returns HTTP 400
  • The default <200 ms p50 target excludes embed.query and applies only to non-personalized, non-catalog, non-reranked Search

The mental model

Think of Search as a pipeline with three responsibilities:

  1. Find candidates. BM25 finds exact terminology, dense retrieval finds semantic similarity, graph channels find connected nodes, and retriever plugins can add another candidate list.
  2. Order candidates. BrainAPI fuses the first-stage lists and can optionally rerank a bounded head.
  3. Shape the result. Metadata filters, catalog node mapping, query-gated personalization, truncation, and facets produce the final response.

The default path intentionally does less: it retrieves passages with BM25 and dense search and fuses the lists. Add graph channels, plugins, or personalization only when they address a measured problem.

Why Search is separate from Context

The same brain supports several read products, but they answer different caller needs:

SurfaceInputOutputUse it when
POST /retrieve/contextA question or conversational statePrompt-ready context, facts, and evidenceAn answering model needs a compact evidence pack
GET /retrieve/search or POST /retrieve/searchQuery text and ranking controlsRanked hits with component scoresA UI or service must display, inspect, filter, or further process results
GET /retrieve/recommend or POST /retrieve/recommendA target user or entityRanked item nodesThe task is query-free next-item or affinity recommendation
MCP and deep RESTA sequence of tool callsIterative observations and evidenceThe question needs traversal, refinement, or verification across several steps

/retrieve/context is not a hidden search endpoint. Enabling Search may change the passage mode available to Context, but it does not give Context ranked hits, facets, or Search plugins.

Before the first query

Search requires PostgreSQL because BrainAPI stores generated full-text data and indexes beside the passages and vector data. At least one of BM25 or dense retrieval must be enabled.

DATA_DB="postgresql"
SEARCH_ENABLED="true"
SEARCH_USE_DENSE="true"
SEARCH_USE_BM25="true"
SEARCH_FUSION="rrf"

Restart BrainAPI after changing configuration. Then ingest text into the same brain you will search. For a documentation corpus that does not need automatic graph enrichment, use skip_enrichment=true:

curl -X POST "<DEPLOYMENT_URL>/ingest/" \
  -H "Content-Type: application/json" \
  -H "BrainPAT: YOUR_BRAIN_PAT" \
  -H "X-Brain-ID: developer-docs" \
  -d '{
    "data": {
      "data_type": "text",
      "text_data": "AUTH-041 occurs when a refresh token has expired. Reauthenticate the client and request a new token."
    },
    "meta_keys": {
      "product": "identity",
      "locale": "en",
      "version": "2"
    },
    "skip_enrichment": true
  }'

Ingestion is asynchronous. Wait for the returned task to complete before treating an empty result as a ranking problem. See Saving text and ingestion tasks.

Why passages are the default

Passages are the most general searchable object: they exist for ordinary text ingestion, retain the source language, and work without a hand-built graph. Graph nodes are valuable only when their labels and relationships represent something useful to the query. Starting with passages gives every brain a predictable baseline and prevents an incomplete graph from silently replacing text recall.

The same corpus can answer an exact diagnostic lookup and a semantic question:

curl -X POST "<DEPLOYMENT_URL>/retrieve/search" \
  -H "Content-Type: application/json" \
  -H "BrainPAT: YOUR_BRAIN_PAT" \
  -H "X-Brain-ID: developer-docs" \
  -d '{
    "query": "sign in fails after refresh token expires",
    "k": 10,
    "channels": ["passages"],
    "fusion": "rrf",
    "profile_stages": true
  }'

The exact AUTH-041 query gives BM25 a strong identifier signal. The natural-language query gives dense retrieval a chance to match the meaning even when the wording differs. With the default RRF fusion, a passage supported by both lists receives evidence from both without comparing incompatible raw score scales.

Why POST is the complete interface

GET is convenient for browser-addressable searches and simple integrations. It supports query, k, channels, node_labels, community_labels, expand, fusion, rerank, target, profile_stages, and header-based brain scoping.

POST uses the structured SearchRequestBody and additionally exposes extras for scalar metadata filters, fusion_alpha for convex-combination tuning, and mode for the deeper catalog candidate pool. Use POST for production clients whose search configuration may grow.

Anatomy of a response

{
  "hits": [
    {
      "id": "chunk-auth-041",
      "channel": "passages",
      "score": 0.0325,
      "scores": {
        "bm25": 7.84,
        "dense": 0.81,
        "rrf": 0.0325,
        "cc": null,
        "rerank": null,
        "plugin": null,
        "graph": null,
        "personalize": null
      },
      "snippet": "AUTH-041 occurs when a refresh token has expired...",
      "labels": [],
      "extras": {"product": "identity", "locale": "en", "version": "2"},
      "node_id": null
    }
  ],
  "stage_timings": {"search.retrieve": {"duration_ms": 42.1}},
  "channel_lists": {
    "dense": ["chunk-auth-041"],
    "bm25": ["chunk-auth-041"],
    "entities": [],
    "events": [],
    "communities": [],
    "neighbors": [],
    "literal": []
  },
  "facets": {
    "product": {"identity": 1},
    "locale": {"en": 1},
    "version": {"2": 1}
  },
  "node_ids": []
}

Read the response in layers:

  • hits is the final ordered list your application normally consumes.
  • score is the final ranking score; compare it only within the same request and configuration.
  • scores exposes available component evidence. null means that stage did not score the hit.
  • channel_lists is diagnostic and can contain candidates that did not survive later stages.
  • extras contains scalar metadata. facets counts those values only over the returned hits.
  • node_id links a passage to an addressable entity when a DOCID <id> marker is present. node_ids is the de-duplicated response-level list.
  • stage_timings appears only when profile_stages=true; embed.query is separated from retrieval time.

Request reference

Query and result size

FieldTypeDefaultMeaning
querystringrequiredKeyword, identifier, phrase, or natural-language query.
brain_idstringdefaultPOST model field. Deployment brain scoping such as X-Brain-ID is authoritative.
kinteger10Final number of hits, from 1 through 200.

Candidate generation

FieldTypeDefaultMeaning
channelsstring[]["passages"]Core: passages, entities, events, communities; plugins use plugin:<name>. GET accepts comma-separated text.
node_labelsstring[]unsetRestricts the entities channel. GET accepts comma-separated text.
community_labelsstring[]server settingHub labels for communities; normally TYPE, CLASS, and TOPIC. GET accepts comma-separated text.
expandnone or neighborsnoneAdds bounded one-hop neighbors from graph-channel seeds.

Fusion and reranking

FieldTypeDefaultMeaning
fusionrrf or ccSEARCH_FUSIONOverrides the server fusion strategy.
fusion_alphanumber0.5POST only. Dense weight for convex combination, from 0 through 1.
rerankstringunsetnone or plugin:<name>. Unknown or unloaded plugins return HTTP 400.
modedefault or catalogdefaultPOST only. Catalog mode deepens candidate retrieval and raises the bounded rerank cap.

Filtering, personalization, and diagnostics

FieldTypeDefaultMeaning
extrasobject of stringsunsetPOST only. Case-insensitive scalar equality filters combined with hard AND.
targetstringunsetOptional USER UUID or id for query-gated personalization of retrieved catalog nodes.
profile_stagesbooleanfalseIncludes stage-level timing data.

Channels and why to choose them

ChannelRetrievesUseful exampleAvoid it when
passagesBM25 and/or dense text chunksManuals, policies, support articles, research abstractsNever by default; this is the baseline
entitiesGraph entities using lexical and dense node signalsServices, people, projects, assetsEntity extraction is incomplete or labels have no search meaning
eventsEvent nodes with recency-aware graph scoresDeployments, incidents, meetings, policy changesThe query is timeless and passages are sufficient
communitiesItems reached through typed hubsRunbooks grouped by service, papers grouped by topicHubs are generic high-degree buckets that add noise
plugin:<name>A plugin-supplied candidate listDomain terminology or token-level matchingThe plugin has not beaten the core baseline on representative queries

Graph channels can be combined with passages:

{
  "query": "authentication outages after the August gateway rollout",
  "k": 20,
  "channels": ["passages", "entities", "events"],
  "node_labels": ["SERVICE", "ENTITY"],
  "expand": "neighbors",
  "profile_stages": true
}

This remains a one-shot ranking request. Use agentic retrieval when answering the question requires following and verifying several relationships.

Common patterns

Exact identifiers

Keep channels=["passages"] and BM25 enabled for error codes, function names, ticket IDs, statutes, and version strings. Dense-only retrieval can blur small but decisive identifier differences.

Semantic questions

Use the default hybrid passage path for questions such as “how do I recover a client after its credentials expire?” Hybrid retrieval preserves exact evidence while allowing a paraphrase to match.

Hard metadata constraints

{
  "query": "reset a locked account",
  "k": 20,
  "channels": ["passages"],
  "extras": {"locale": "it", "product": "identity"}
}

Both values must match, case-insensitively. Filtering occurs after candidate retrieval, so a narrow filter may return fewer than k; BrainAPI does not repeatedly retrieve until the page fills.

Learned stages

Use Search SPLADE or Search ColBERT as explicit first-stage channels. Use Search Rerank when candidate recall is already good but the top order needs finer query-candidate comparison.

Why plugins are opt-in

Learned retrieval and reranking introduce model downloads, device requirements, index lifecycle, memory usage, and variable latency. They can also make relevance worse on a corpus unlike their training data. BrainAPI never silently substitutes a missing plugin or attaches Search plugins to /retrieve/context.

Troubleshooting

SymptomLikely causeDiagnoseFix
HTTP 404 on /retrieve/searchSearch is disabledInspect SEARCH_ENABLED and startup configurationEnable Search with PostgreSQL and restart
HTTP 400 naming plugin:<name>Plugin is missing, unloaded, or misspelledCheck its health route and loaded packagesInstall, restart, and use the registered hook name
HTTP 400 for rerank="linear"Linear reranking is not implementedInspect the response detailUse none or a loaded plugin reranker
HTTP 422Invalid bounds, enum, or request typeCompare validation detail with the request tableCorrect k, fusion, mode, expansion, or field types
Empty results after ingestTask incomplete or wrong brainPoll the task and verify X-Brain-IDWait for completion and query the same brain
Exact codes rank poorlyBM25 disabled or identifier not storedInspect channel_lists.bm25 and source textEnable BM25 and preserve identifiers
Semantic queries are emptyDense retrieval disabled or embeddings missingInspect channel_lists.dense and timingsEnable dense retrieval and verify ingestion
Fewer than k with extrasHard-AND filtering removed candidatesCompare channel lists with final hitsBroaden filters or retrieve more in the application design
Graph channels add noiseLabels or hubs are too broadCompare passage-only and graph armsRestrict labels or remove the channel
Plugin returns no candidatesIts valid in-memory index may be emptyCall health with the brain idBuild or rebuild the plugin index
Latency unexpectedly risesEmbedding, graph, plugin, or rerank work was addedSet profile_stages=trueRemove unhelpful stages or route expensive searches separately

Continue learning

Edit on GitHub

Last updated on

On this page