@sinequa/atomic

Language features

Answers, passages, spelling correction, query analysis and query intents — what the engine adds beyond a list of documents.

A search response can carry more than documents: a short answer to the question, the passages that support it, a spelling correction, and the engine's own reading of the query. All of it is optional — present only when the query web service is configured for it, which is why every field here needs a guard.

What can come back

Prop

Type

Answers and passages

An Answer is a short span with a score, the recordId it came from, and a $article the library attaches when it can resolve the document. matchingpassages on a record are that document's relevant passages; topPassages are the best across the corpus.

Show an answer above the results, when there is one
src/answers.ts
import { createAtomicClient } from "@sinequa/atomic";
import type { Answer, Result, TopPassage } from "@sinequa/atomic";

const client = createAtomicClient({
  app: "<your-app>",
  backendUrl: "https://<your-sinequa-server>",
});

await client.initializeConfig();
await client.auth.login();

const result = await client.api.query.search({
  name: "<your-query>",
  text: "how do I reset my password",
  pageSize: 10,
});

render(result);

function render(result: Result) {
  if (result.$error) throw result.$error;

  // Every one of these is optional — and `answers` can be explicitly null.
  const best = bestAnswer(result);

  if (best) {
    console.log(`Answer (${best.score.toFixed(2)}): ${best.text}`);
    // $article is attached by the library when the document is in the response.
    console.log(`from: ${best.$article?.title ?? best.recordId}`);
  }

  for (const passage of result.topPassages?.passages ?? []) {
    console.log(`passage (${passage.score.toFixed(2)}): ${excerpt(passage)}`);
  }

  console.log(`${result.rowCount} document(s)`);
}

function bestAnswer(result: Result): Answer | undefined {
  const answers = result.answers?.answers ?? [];
  // The server orders them, but do not rely on it for a single highlighted answer.
  return [...answers].sort((a, b) => b.score - a.score)[0];
}

function excerpt(passage: TopPassage) {
  // `location` is a [start, end] pair of offsets into the document's text.
  const [start, end] = passage.location;
  return `${passage.text} (offsets ${start}–${end})`;
}

Spelling correction

didYouMean reports the correction and which version was executed — the distinction that decides what your interface should offer:

correct · force dymonly false the user typedmaangement spellingCorrectionMode the corrected query ranshow: results for management the original ranoffer: did you mean management? no correction at all
reacting to a correction
const { original, corrected } = result.didYouMean?.text ?? {};

if (corrected && corrected !== original) {
  // Whether `corrected` was actually run depends on the configured mode: offer it
  // as a link when it was not, and say so when it was.
  console.log(`did you mean: ${corrected}?`);
}

spellingCorrectionMode on the query overrides the service's configured mode. DidYouMeanKind — Original or Corrected — is the enum for expressing which one you are showing.

Query analysis and intents

queryAnalysis is the engine explaining itself: the lemmas it reduced words to, the entities it recognised, the synonyms and reformulations it applied. Its elements are recursive, so a display walks a tree. It answers "why did I get this result", which is worth surfacing in an advanced or debugging view.

queryIntents is a different thing: a server-side rule matched a pattern in the query — "flights to Paris", "contract signed by X" — and attached structured data or an action to it. Use it to render something other than a result list when an intent fires.

acting on an intent
// The key is absent, not empty, when no intent configuration exists.
for (const intent of result.queryIntents ?? []) {
  console.log(intent.name, intent.component);

  for (const action of intent.actions) {
    console.log(action.type, action.data);
  }
}

fetchQuery also accepts query-intent input as its third argument (QueryIntentData), for feeding context back to the intent engine. And client.api.query.intent(query) asks for intents alone, without running a search.

Turning it off

Neural search powers answers and top passages. It is on when available, and a query can decline it:

keyword search only
const result = await client.api.query.search({
  name: "<your-query>",
  text: "exact reference AB-1234",
  neuralSearch: false,
});

Worth doing when the user is looking for an exact identifier, where semantic matching adds noise rather than recall.

Pitfalls

What's next

On this page