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.

KindRecordsWhat 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:

  1. 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:Anthropic and Keys: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.
  2. 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.
  3. 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

SettingWhat 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

SettingWhat 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:

GroupExamples
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:

ModeWhat 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:

OfferShowsDefault
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:

tsx
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: false waits for the pause. mode lets one page choose its own, such as strict on a checkout page; proactive applies only where the dataset is set to proactive, since only then does the server start answers on its own, and is inviting elsewhere. Where Search or ask is off or not available, it stays quiet and the page is an ordinary search.
  • SearchInput shows the offer in the field, and Enter on the full offer, or a click on it, calls onAsk with the question. askOffer="below" puts the offer under the field instead. It follows the dataset's mode: in Proactive it calls onAsk(text, { proactive: true }) on its own once the judge is sure.
  • QuickAnswer is 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. Give SearchInput askOffer="none" beside it, so the offer shows in one place.
  • AskPanel writes the answer as it streams, with its sources numbered and a field for follow-up questions (followUps). fill makes it take the height of its container, for a panel beside the results. Pass it proactive when onAsk said so, so the statistics count an answer nobody clicked for apart.
  • SearchResults with searchOrAsk keeps the list as the While asking settings say.
  • useFilterSuggestion turns 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.

RouteWhat 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).