Search or Ask
One search field that also answers questions. A visitor who types a name gets results, as always. A visitor who types a question gets results too, and an offer to have it answered, in writing, from your own records.
Search or ask is built into Indx Server. It needs no change to your data, and the search itself works exactly as before whether the feature is on or off.
How It Works
On each pause in typing, a fast judge reads what has been typed and the top results, and decides what the visitor is doing:
- Looking something up: a name, a code, a keyword. The results are the answer, and nothing more is offered.
- Asking a question: how to do something, why something happens, which of two things to choose. The page offers to answer it.
- Describing what to list (catalogues only): "a funny space movie from the 80s", "private schools in Bergen". The description can become filters on the list.
The offer appears as the visitor types and goes away when they keep typing a name. When the visitor asks, an answer model writes the answer from the records the search found, citing them. It uses those records and nothing else, and says so when they do not hold the answer.
The judge takes a fraction of a second and costs about five cents per thousand calls. The answer is written only when the visitor asks for it.
When It Fits
Search or ask has two kinds, and the dataset's records decide which one fits.
| Kind | Records | What an answer does |
|---|---|---|
Documentation | Written to explain: help pages, manuals, policies, long descriptions | Says what no single page shows, from the pages that hold it |
Catalogue | Facts about things: products, places, films, organisations | Turns a description into filters, and answers a question about one item from its fields |
Records of identifiers alone, such as part numbers or codes, fit neither. Their visitors look things up, and the search already serves them.
Turning It On
Three switches, each with its own owner:
- The instance gets its model keys. In the console, Admin > Settings > Experimental, show Search or ask and save a key for each provider you use, or set them in the configuration as
Indx:SearchOrAsk:Keys:OpenRouter,Keys:OpenAi,Keys:AnthropicandKeys:TypeSafe. Then choose a provider for the judge and one for the answers; switching later is a choice, not a new key. One OpenRouter key serves both. One OpenAI key does too: OpenAI for the answers (Sol and Luna by default, Ultrafast if you want it) and OpenAI's Decisions API as the judge. Anthropic, or any other OpenAI-compatible endpoint, can write the answers as well. The models offered are read from the provider's own list. Keep requests in the EU uses OpenRouter's EU endpoint for the answers, or OpenAI's EU data residency for everything. Both need the right account first: OpenRouter serves its EU endpoint only on a Business or Enterprise plan, and OpenAI's needs a key from a project created with Europe as its region. Without it every answer is refused. When a provider refuses a call, the Search or ask tab and the settings page show its own reason and what to do. - The dataset shows it. A team admin turns on Search or ask on the dataset's Options tab. Until then, its routes answer 404 as if they were not there.
- The dataset switches it on. An editor sets it up on its own Search or ask tab and turns on Offer answers.
Privacy. With Search or ask on, what visitors type leaves your server: to the judge on each pause, and to the answer model when they ask. Name the providers in your privacy notice. The Search or ask tab says who they are.
The tab has a tester beside the settings: type as a visitor would and see the offer, the results the judge read, and the answer, with your settings saved or not. Each score is drawn against the thresholds it is held to, so you see how near an offer was.
Settings
Every setting has an explanation on its name in the console. Here they are in one place.
The site
| Setting | What it does |
|---|---|
Kind of site | Documentation or catalogue, as above. It changes the questions the judge asks and how answers are written. |
About the site | One or two sentences the judge and the answer model read with every request: who reads the site and what the records hold. Empty uses the dataset's description. |
About the fields (catalogue) | What a value means on this site, one per line, such as barneskole: grades 1 to 7. It lets "schools for young children" become the right filter. |
Knowledge | Markdown with what is true about the site that no record holds: what the set covers, what a rating means, that prices include VAT, delivery times, what you do not sell. An answer may state it, saying the site says so; about a particular record, the record decides. Up to 8,000 characters, sent with every question, never to the judge. Download and upload it as .md. |
The answers
| Setting | What it does |
|---|---|
Answers | The model that writes the answers. Best quality and Fast are the instance's two; an admin can offer more in Admin > Settings > Experimental. Where a model has answered on the dataset, the time to its first words over the last 30 days is shown beside it. |
Searches | On a catalogue: Several, an agent that searches, sorts and filters until it has the answer, about 10 seconds; or One search, quicker and cheaper, which misses what a single search cannot see. Until you choose, the best quality model searches several times and the fast one once. |
Answer instructions | How the answers speak: voice, house rules, what to say when nothing is found. They shape the wording; what an answer may claim stays bound to the records. |
Answer language | The visitor's language by default, or one language for every answer. |
Daily call budget | Calls per day, counted from midnight UTC. Every judgement, every question asked and every filter the model makes while a visitor types is one call. Once the budget is spent, the search works as before and no offers show until midnight. 0 stops all calls. Default 5,000. |
What the judge sees of a result
Title, Section and Snippet: the field that names a result, the fields that say where it belongs (a category, a year), and the prose the judge reads and answers are written from. Leave them on Automatic unless the judge reads the wrong thing; never pick identifiers or links as snippets.
Ask phrases
While a question is typed, the list searches what it is about. "Help me find the movie about the green ogre" searches green ogre, which finds Shrek; the whole sentence would not, since "movie", "help" and "find" match titles better than "ogre" does.
The phrases that come off are in four groups, matched in order from the start of the text:
| Group | Examples |
|---|---|
Lead-ins | "help me find", "i'm looking for", "what is" |
What is asked for | "the movie", "the product", "the one" |
Links | "about", "with", "where", "starring" |
Articles | "the", "a", "an" |
Nothing is taken unless the start has a question's shape and a word of substance is left, so a title like Help or What We Do in the Shadows searches as typed. What is asked for also comes off the end of a question when two words stand before it: "show me green ogre movie" searches green ogre, while "show me bee movie" keeps its title.
The list is English by default. Make it the dataset's own: add the names your visitors use for your records, and lead-ins in your site's language. Edit Ask phrases opens the four groups, a tab each, where you add, search and change phrases, and Try it shows what a typed question comes to. A phrase belongs to one group. With every group empty the defaults apply; once any group has a phrase, the four are the dataset's own.
Two switches go with them. Refocus the search is the behaviour above; off, the list searches the text as typed. Invite on an Ask phrase shows the invitation the moment a phrase matches, before the judge has read the text: Ask about this, or in the inviting modes For a quick answer, type a bit more. It only invites; the full offer, and an answer that starts on its own, still wait for the judge. Strict ignores it.
When to offer
First, how forward the offer is. The mode goes from the most careful to the most forward:
| Mode | What the visitor sees |
|---|---|
Strict | Only the full offer, once the judge is sure it is a question. For a site where a wrong offer costs more than a missed one. |
Soft (the default) | The quieter Ask about this early, the full offer once the judge is sure. |
Open | An Ask button whenever something is typed, and Enter asks, whatever the judge says. The judge decides only how strong the offer looks. |
Inviting | Open, with For a quick answer, type a bit more and a highlight sweeping across it as soon as the judge reads a question starting, from the first word. The answer waits until the visitor asks. Made to teach visitors that the field answers. |
Proactive | Inviting, and the answer starts on its own once the judge is sure, reads the question as finished, and the visitor has stopped typing for a second. what type is is still being typed and waits. |
Proactive writes answers nobody clicked for, so it has a cap of its own: Proactive answers, 500 a day by default. Past it the field works as Inviting until midnight UTC.
The early invitation has a threshold of its own, Hint, low on purpose: the judge scores a question's first word (How, Hvordan) above a name or a place, in any language. Its default is 0.25 on a catalogue and 0.31 on documentation, where lookups score higher.
Then the scores. The judge scores what is typed from 0, a lookup, to 1, a question. Two offers, each with two thresholds:
| Offer | Shows | Default |
|---|---|---|
Answer offer | Ask the assistant, which Enter asks | from 0.65, kept down to 0.45 |
Invitation | the quieter Ask about this | from 0.40, kept down to 0.30 |
The second number keeps an offer up while the score dips, so it does not blink as the visitor types on. On a catalogue, a sure description of what to list earns the full offer too.
While asking
What the page does with its list while the full offer is up:
- The list: keep only the results that still match the question. A question's words match many records loosely; this keeps the ones it is about.
- Matching: the page's searches match whole words only, so a long question does not pick up typos and partial words everywhere. The answer still searches with the full setup.
- Filters (catalogue): when the text describes what to list, filter the list only after the visitor asks, at once from facet values (instant), or with the model (about a second and a half).
Building It Into a Page
With intrface, Search or ask is a hook and two props inside your SearchProvider:
import {
SearchProvider, SearchInput, SearchResults, AskPanel,
useSearchOrAsk, useFilterSuggestion,
} from '@indxsearch/intrface';
import { useState } from 'react';
function Search() {
const sor = useSearchOrAsk();
const suggestion = useFilterSuggestion({ auto: sor });
const [question, setQuestion] = useState<string | null>(null);
const [proactive, setProactive] = useState(false);
return (
<>
<SearchInput
searchOrAsk={sor}
onAsk={(text, how) => { setQuestion(text); setProactive(!!how?.proactive); }}
/>
{question && (
<AskPanel question={question} proactive={proactive} followUps onFilters={suggestion.apply} />
)}
<SearchResults fields={['title', 'description']} searchOrAsk={sor} />
</>
);
}
export default function App() {
return (
<SearchProvider url="https://your-server" team="your-team" dataset="your-dataset"
preAuthenticatedToken="a Search key">
<Search />
</SearchProvider>
);
}useSearchOrAsk()reads the dataset's settings, asks the judge on each pause, and applies the Ask phrases to the page's searches. It asks the judge while the visitor types as well, so the offer can appear as soon as they stop;prefetch: falsewaits for the pause.modelets one page choose its own, such asstricton a checkout page;proactiveapplies only where the dataset is set to proactive, since only then does the server start answers on its own, and isinvitingelsewhere. Where Search or ask is off or not available, it stays quiet and the page is an ordinary search.SearchInputshows the offer in the field, and Enter on the full offer, or a click on it, callsonAskwith the question.askOffer="below"puts the offer under the field instead. It follows the dataset's mode: in Proactive it callsonAsk(text, { proactive: true })on its own once the judge is sure.QuickAnsweris the offer as a row of its own, for above the results, as Notion's quick answer: the sweeping hint, the full offer, or proactive's Thinking before the answer starts. GiveSearchInputaskOffer="none"beside it, so the offer shows in one place.AskPanelwrites the answer as it streams, with its sources numbered and a field for follow-up questions (followUps).fillmakes it take the height of its container, for a panel beside the results. Pass itproactivewhenonAsksaid so, so the statistics count an answer nobody clicked for apart.SearchResultswithsearchOrAskkeeps the list as the While asking settings say.useFilterSuggestionturns a catalogue's described filters into filters on the list, while typing or when the answer brings them.
Use a Search key in the page. It is the only kind that belongs in a browser, and it reads the settings without the Knowledge text.
The HTTP API
The same, for a front end of your own. All routes are under /api/teams/{team}/datasets/{dataset}/search-or-ask, and take a Search key unless noted.
| Route | What it does |
|---|---|
GET | The dataset's settings, what it runs with after defaults ( effective, including the Ask phrases in effect), and whether the instance can run it (available). |
PUT | Replaces the settings (Full key). Applies from the next call; nothing is re-indexed. |
POST /intent | Judges { "text": "..." }: p, the probability the visitor is asking; answerable, whether the results could answer it; criteria, whether it describes what to list; and the offer those come to (none, invite, strong). Call it on a typing pause, beside your own search. A judge that is slow or failing answers p 0 with a reason; the call never fails. |
POST /filters | A catalogue's description, { "text": "..." }, as filters on the dataset's own fields, and the text no filter covers. |
POST /ask | Answers { "question": "...", "history": [...] } as server-sent events: status (what it is doing), sources (the records it may use, numbered), filters (on a catalogue, what it searched with), text (the answer as it is written, citing [n]), then done; or error with a code (nothingFound, upstreamBusy, failed). The response's Indx-Ask-Id header names the answer for the statistics. |
Texts are 1 to 300 characters, questions 1 to 1,000. A call over the per-address limit or the daily budget answers 429 with Retry-After; one on a dataset where Search or ask is off answers 409 (searchOrAskDisabled), or searchOrAskUnavailable where the instance has no model key.
Pass ?session=, ?source= and ?queryId= (the search behind the call) as you do on searches, so the statistics tie the judgements and questions to the searches they came from. When your page searches something other than what was typed, send the typed text as ?typed= on the search: the statistics then count a visitor's typing once.
Costs and Statistics
Most of the cost is the answers. On a film catalogue, a judgement averaged $0.00005 and a Best answer $0.035: one answer costs as much as about 700 judgements. Fast answers cost a twentieth of Best.
The Statistics tab gets a Search or ask section:
- Proactive answers, once the proactive mode has written one: the answers no visitor asked for, kept out of Questions asked.
- Offers shown, Questions asked and Not answered: the questions the search found nothing for, or whose sources the judge doubted could answer them. These are what to write next.
- Cost: the judge, the answers and the filters together, as OpenRouter reports them for each call. Other providers report only tokens, so the tile shows "—" and says which provider to look up; your bill is in that provider's own console.
- Asked again: answers the visitor followed within five minutes with the same question in other words, the quiet sign an answer missed. Under Questions, the pairs show how each question was meant. It needs the
?session=your page sends; a follow-up to an answer is not counted. - Needs: what visitors wanted, in all the ways they asked it, each with how its visits ended (answered at the first try, after asking again, or given up), the wordings, and the records the answers landed on. The ones given up most come first.
- Calls: against the daily budget, with the days it ran out.
- Questions and Offers tables: each question with its outcome and answerable score, and each text an offer was shown beside, so a false offer can be found.
The same is served by GET /api/teams/{team}/datasets/{dataset}/statistics/search-or-ask?days=30 (a Read key).