Per-Request Overrides

How to override retriever and reranker parameters on a single chat request without changing global env defaults.

Most RAG knobs in GENIE.AI are deployment-wide (set in .env). A subset can be overridden on a single chat request via the OpenAI-compatible retriever_parameters and reranker_parameters fields on the request body — useful for an admin tool that wants to A/B a chunk count, for an experiment harness that varies top_n per query, or for an integration test that needs to lock in a known reranker strategy.

This page is for backend developers building integrations against ChatQnA and operators experimenting with retrieval and reranking knobs.

Prerequisites

  • Familiarity with Retrieval and Reranking — these override the same knobs.
  • The canonical Pydantic models are GenieaiRetrieverParms(RetrieverParms) and GenieaiRerankerParms(RerankerParms) in genie-ai-overlay/chatqna/genieai_chatqna.py:595-602.
  • The request must include Authorization: Bearer <keycloak_jwt> and a traceparent header (ChatQnA propagates the trace).

How overrides flow

POST /v1/chatqna
   request.retriever_parameters = {top_k: 2, score_threshold: 0.2}
   request.reranker_parameters  = {top_n: 1, reranking_strategy: "slice"}
        ↓
ChatQnA (chatqna.py:908-941)
   safe_params = model_dump(exclude_unset=True, exclude_none=True)
   inputs.update(safe_params)
        ↓
Retriever microservice / Reranker microservice
   inputs["top_k"] / inputs["top_n"] / ... consumed by that stage

Two important behaviours:

  1. exclude_unset=True, exclude_none=True. Any field the caller does not set falls back to the env var / in-code default. You don’t need to repeat the whole schema — send only what you want to override.
  2. Encoding the label filter. The label filter is encoded in the standard search_start field ("chunk" / "chunk_label_filter"). Overriding search_start overrides the auto-filter — see encode_filter_labels (defined in genie-ai-overlay/core/label_contract.py, imported and called at genieai_chatqna.py:933-936) and the data contract note in Retrieval → Label filtering.

Retriever overrides

Backed by GenieaiRetrieverParms(RetrieverParms) in genieai_chatqna.py:595. The fields forwarded to the retriever service:

FieldTypeDefaultEffect
embeddinglist[float](none)Pre-computed query embedding. Skips the embedding service call.
search_typestring"chunk""chunk" or "chunk_label_filter" (encodes a label filter).
search_startstring"chunk"One of the standard EmbedDoc search_start values; encodes the label filter when search_type=chunk_label_filter.
kintRETRIEVER_ARANGO_KChunks returned.
fetch_kintRETRIEVER_ARANGO_FETCH_KCandidate fetch width.
score_thresholdfloatRETRIEVER_ARANGO_SCORE_THRESHOLDMinimum fused score.
distance_thresholdfloatRETRIEVER_ARANGO_DISTANCE_THRESHOLDMaximum cosine distance.
lambda_multfloatRETRIEVER_ARANGO_LAMBDA_MULTLegacy langchain-style blend.
enable_traversalboolRETRIEVER_ARANGO_TRAVERSAL_ENABLEDTurn on graph traversal.
traversal_max_depthintRETRIEVER_ARANGO_TRAVERSAL_MAX_DEPTHGraph hops.
traversal_max_returnedintRETRIEVER_ARANGO_TRAVERSAL_MAX_RETURNEDGraph chunk cap.
traversal_score_thresholdfloatRETRIEVER_ARANGO_TRAVERSAL_SCORE_THRESHOLDGraph chunk min score.
traversal_concurrent_batchesintRETRIEVER_ARANGO_TRAVERSAL_CONCURRENT_BATCHESConcurrency.
filter_labelslist[string][]Constrains retrieval to chunks whose labels intersect this list. Encoded into search_start.

Reranker overrides

Backed by GenieaiRerankerParms(RerankerParms) in genieai_chatqna.py:602. The fields forwarded to the reranker service:

FieldTypeDefaultEffect
top_nintRERANKER_TOP_NChunks kept for slice / slice_threshold.
reranking_strategystringRERANKING_STRATEGYslice, adaptive, threshold, slice_threshold, knee_threshold.
reranking_thresholdfloatRERANKING_THRESHOLDScore gate. Read by threshold / slice_threshold only — sending it with any other strategy (including the adaptive default) is accepted and ignored.

Example: A/B a top-k comparison

# Default env: RETRIEVER_ARANGO_K=4. We want to A/B with top_k=2.
curl -sk -X POST http://localhost:8888/v1/chatqna \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -H "traceparent: 00-$TRACE_ID-01" \
  -d '{
    "messages": [{"role": "user", "content": "How do I renew my permit?"}],
    "stream": false,
    "retriever_parameters": {
      "k": 2,
      "fetch_k": 12
    },
    "reranker_parameters": {
      "top_n": 1
    }
  }'

The response carries the same source_documents array as the streaming metadata event — compare its length and metadata.score to the un-overridden baseline.

Example: constrain retrieval to a service category

# Filter retrieval to chunks whose labels include "Healthcare".
curl -sk -X POST http://localhost:8888/v1/chatqna \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "messages": [{"role": "user", "content": "What are the diabetes management guidelines?"}],
    "stream": false,
    "retriever_parameters": {
      "filter_labels": ["Healthcare"]
    }
  }'

filter_labels is intersected with the labels actually assigned to chunks at ingest time — empty intersection returns no chunks.

Verification

After a per-request override, the result is observable through the source_documents array in the SSE metadata event — ChatQnA does not emit retriever.parameters / reranker.parameters as chatqna span attributes (the overrides flow into the per-request retriever and reranker microservice HTTP calls, but no dedicated span attribute is set on the chatqna.orchestrate root span). The reranker.strategy attribute on the reranker microservice span can confirm the strategy override landed.

Failure modes

SymptomLikely causeFix
Override has no effectexclude_unset=True — your field was at the env defaultSend an explicitly different value.
All chunks filtered outfilter_labels doesn’t intersect any chunk’s labelsInspect a known chunk’s labels via ArangoDB (FOR d IN chunks FILTER ... RETURN d.labels); adjust.
Retriever returns a different k than requestedBackend cache / request shape stripped the fieldConfirm retriever_parameters is at the top level of the request body, not nested in messages.
ChatQnA returns 422One of the override values failed Pydantic validationCheck the field types in the tables above.