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.
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})`;
}import { ChangeDetectionStrategy, Component, computed, input } from "@angular/core";
import type { Result } from "@sinequa/atomic";
@Component({
selector: "app-answer",
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
@if (best(); as answer) {
<section>
<p>{{ answer.text }}</p>
<footer>
from {{ answer.$article?.title ?? answer.recordId }} · score
{{ answer.score | number: '1.2-2' }}
</footer>
</section>
}
@if (passages().length) {
<ul>
@for (passage of passages(); track passage.id) {
<li>{{ passage.text }}</li>
}
</ul>
}
`,
})
export class AnswerComponent {
readonly result = input.required<Result>();
/** answers can be absent OR explicitly null, hence the double guard. */
readonly best = computed(() => {
const answers = this.result().answers?.answers ?? [];
return [...answers].sort((a, b) => b.score - a.score)[0];
});
readonly passages = computed(() => this.result().topPassages?.passages ?? []);
}import type { Result } from "@sinequa/atomic";
export function AnswerPanel({ result }: { result: Result }) {
// answers can be absent OR explicitly null, hence the double guard.
const answers = result.answers?.answers ?? [];
const best = [...answers].sort((a, b) => b.score - a.score)[0];
const passages = result.topPassages?.passages ?? [];
return (
<>
{best && (
<section>
<p>{best.text}</p>
<footer>
{/* $article is attached by the library when the document is in the response. */}
from {best.$article?.title ?? best.recordId} · score {best.score.toFixed(2)}
</footer>
</section>
)}
{passages.length > 0 && (
<ul>
{passages.map((passage) => (
<li key={passage.id}>{passage.text}</li>
))}
</ul>
)}
</>
);
}Spelling correction
didYouMean reports the correction and which version was executed — the distinction that decides what your
interface should offer:
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.
// 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:
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.