Skip to content

Searching & answering

  • Search


    /api/search returns ranked chunk matches (no LLM generation).

  • Answer


    /api/answer runs retrieval, then generates an answer with sources.

  • Control the legs


    Toggle vector/sparse/graph per request and tune fusion/reranking via config.

Quickstart Indexing Retrieval overview Configuration

Default API base

http://127.0.0.1:8012/api

Costs

Answer generation and some reranking modes can incur provider costs. If you’re experimenting, start with /api/search until you trust your retrieval.

1) Use /api/search to inspect retrieval quality

/api/search is the fastest way to debug relevance: it returns matches, their scores, and which leg produced them.

BASE="http://127.0.0.1:8012/api"
curl -sS -X POST "$BASE/search" \
  -H "Content-Type: application/json" \
  -d '{
    "corpus_id": "demo",
    "query": "where is the FastAPI router mounted?",
    "top_k": 8,
    "include_vector": true,
    "include_sparse": true,
    "include_graph": true
  }' | jq '.matches[] | {file_path, start_line, end_line, score, source}'
import httpx

BASE = "http://127.0.0.1:8012/api"
payload = {
    "corpus_id": "demo",
    "query": "where is the FastAPI router mounted?",
    "top_k": 8,
    "include_vector": True,
    "include_sparse": True,
    "include_graph": True,
}
res = httpx.post(f"{BASE}/search", json=payload, timeout=30).json()
for m in res.get("matches", []):
    print(m["source"], m["file_path"], m["start_line"], m["end_line"], m["score"])

2) Use /api/answer when retrieval is “good enough”

/api/answer uses retrieval matches as context and asks the selected generation model to produce an answer.

Answers fail closed

If generation cannot run (no provider, spend limit, empty reply) or retrieval itself fails, /api/answer answers a typed 503 and /api/answer/stream emits a typed error event before done — neither returns a "retrieval-only" answer assembled from the sources. The error card names the failure class and the operator hint; see Chat models.

BASE="http://127.0.0.1:8012/api"
curl -sS -X POST "$BASE/answer" \
  -H "Content-Type: application/json" \
  -d '{
    "corpus_id": "demo",
    "query": "How do I start the system locally and verify it is ready?",
    "top_k": 10
  }' | jq '{answer, sources_count: (.sources | length), model}'

3) Streaming answers

If you want tokens as they arrive, use /api/answer/stream.

Clients differ

Some HTTP clients buffer output unless you disable buffering. Prefer curl -N or an SSE-aware client.

BASE="http://127.0.0.1:8012/api"
curl -N -sS -X POST "$BASE/answer/stream" \
  -H "Content-Type: application/json" \
  -d '{"corpus_id":"demo","query":"Summarize the indexing pipeline","top_k":10}'

Quick tuning checklist (practical)

When answers are wrong, the cause is almost always one of:

1) the corpus wasn’t indexed as you think
2) the retrieval legs didn’t pull the right chunks
3) fusion/reranking reshuffled results in a surprising way

  • Confirm the correct corpus: corpus_id in every request
  • Check index status: /api/index/<corpus>/status
  • Run /api/search and inspect source (vector|sparse|graph) for top matches
  • If recall is low, raise candidate sizes and/or reduce thresholds (via config)
  • If precision is low, enable reranking and/or raise confidence gates (via config)
When to disable a leg
  • Disable graph if Neo4j is down or you want baseline performance.
  • Disable sparse if your corpus is mostly natural language without identifiers.
  • Disable vector if you’re debugging exact-identifier matching.