@juspay/neurolink
Version:
The pipe layer of an AI nervous system: one interface connecting provider neurons to your application, across three inference types — generate, stream and decide. `decide` returns typed, calibrated judgements from a non-generative model (~400ms, ~$0.00002
180 lines (179 loc) • 7.96 kB
JavaScript
/**
* KnowledgeGroundingEngine — the provider-neutral orchestrator.
*
* Loads the source set once at construction, reuses the process-level
* in-memory index cache when possible, and runs one retrieval + context
* assembly per eligible turn. The engine's snapshot is never reset after
* construction — sources are supplied only via the constructor config.
*
* The engine never throws from `ground()` — any failure fails open to "no
* grounding" with a `failureReason`, so an informational turn is never broken.
* It performs no logging and no telemetry itself; the client wiring reads
* `getStatus()`/the outcome metadata and emits spans.
*/
import { assembleKnowledgeContext } from "./context.js";
import { DEFAULT_ALIAS_BOOST, DEFAULT_CANDIDATE_LIMIT, DEFAULT_EXACT_BOOST, DEFAULT_FIELD_WEIGHTS, DEFAULT_RECENT_TURNS, DEFAULT_RELATION_LIMIT, DEFAULT_RESULT_LIMIT, DEFAULT_TIMEOUT_MS, } from "./defaults.js";
import { getOrBuildKnowledgeIndexSnapshot } from "./indexCache.js";
import { retrieve } from "./retrieval.js";
import { withTimeout } from "../utils/async/withTimeout.js";
/** Fallback source version when a source (or manifest) declares none. */
const FALLBACK_VERSION = "0";
const resolveRetrieval = (retrieval) => ({
candidateLimit: retrieval?.candidateLimit ?? DEFAULT_CANDIDATE_LIMIT,
resultLimit: retrieval?.resultLimit ?? DEFAULT_RESULT_LIMIT,
relationLimit: retrieval?.relationLimit ?? DEFAULT_RELATION_LIMIT,
fieldWeights: retrieval?.fieldWeights ?? DEFAULT_FIELD_WEIGHTS,
exactBoost: retrieval?.exactBoost ?? DEFAULT_EXACT_BOOST,
aliasBoost: retrieval?.aliasBoost ?? DEFAULT_ALIAS_BOOST,
});
const emptyMetadata = (durationMs, failureReason) => ({
retrievalMode: "lexical",
selectedIds: [],
expandedIds: [],
candidateCount: 0,
contextTokens: 0,
truncated: false,
durationMs,
failureReason,
});
export class KnowledgeGroundingEngine {
config;
resolved;
snapshot = null;
buildPromise = null;
lastError = null;
validationIssues = [];
now;
constructor(config, now = () => Date.now()) {
this.config = config;
this.resolved = resolveRetrieval(config.retrieval);
this.now = now;
if (config.enabled && config.sources && config.sources.length > 0) {
this.buildPromise = this.build(config.sources).catch((error) => {
this.lastError = String(error);
});
}
}
isEnabled() {
return this.config.enabled;
}
/** Resolve once the one-time build settles. Safe to call before every turn. */
async ready() {
if (this.buildPromise) {
await this.buildPromise;
}
}
getStatus() {
return {
enabled: this.config.enabled,
ready: this.snapshot !== null,
entryCount: this.snapshot?.entryCount ?? 0,
lastError: this.lastError,
validationIssues: this.validationIssues,
};
}
async build(sources) {
const { snapshot, validation } = await getOrBuildKnowledgeIndexSnapshot(sources, FALLBACK_VERSION, this.resolved.fieldWeights);
this.validationIssues = validation.issues;
if (!validation.ok) {
// Never load a partially valid registry — leave the index unbuilt.
const errorCount = validation.issues.filter((issue) => issue.level === "error").length;
this.lastError = `knowledge validation failed with ${errorCount} error(s)`;
return;
}
// The snapshot may be newly built or reused from another NeuroLink instance
// in this process. It is immutable and safe to share across conversations.
this.snapshot = snapshot;
this.lastError = null;
}
buildRequest(input) {
const scope = input.scope ?? {};
return {
query: input.query,
recentTurns: (input.recentTurns ?? []).slice(-DEFAULT_RECENT_TURNS),
enabledIntegrations: scope.enabledIntegrations ?? [],
};
}
/**
* Retrieve + assemble for one turn. Returns the ephemeral context to inject
* (or null), aggregate metadata, and the full retrieval. Never throws.
*/
async ground(input) {
const started = this.now();
const wallClockStarted = Date.now();
if (!this.config.enabled) {
return {
ephemeralContext: null,
metadata: emptyMetadata(0),
retrieval: null,
};
}
try {
const timeoutMs = this.config.timeoutMs ?? DEFAULT_TIMEOUT_MS;
const outcome = await withTimeout((async () => {
await this.ready();
const snapshot = this.snapshot;
if (!snapshot) {
return {
ephemeralContext: null,
metadata: emptyMetadata(this.now() - started, "index-not-ready"),
retrieval: null,
};
}
const request = this.buildRequest(input);
const selection = retrieve(snapshot, request, this.resolved, this.config.blockedDomains);
const assembled = assembleKnowledgeContext(selection, this.config.context);
const durationMs = this.now() - started;
const retrieval = {
entries: [...selection.primary, ...selection.expanded],
assembledContext: assembled.assembledContext,
confidence: selection.confidence,
citations: assembled.citations,
selectedEntryIds: selection.primary.map((entry) => entry.id),
expandedEntryIds: selection.expanded.map((entry) => entry.id),
candidateCount: selection.candidateCount,
contextTokens: assembled.contextTokens,
truncated: assembled.truncated,
durationMs,
};
const metadata = {
retrievalMode: "lexical",
selectedIds: retrieval.selectedEntryIds,
expandedIds: retrieval.expandedEntryIds,
candidateCount: selection.candidateCount,
contextTokens: assembled.contextTokens,
truncated: assembled.truncated,
durationMs,
confidence: selection.confidence,
};
// Nothing to inject when retrieval found nothing (or all matches were filtered out).
if (!assembled.assembledContext) {
return { ephemeralContext: null, metadata, retrieval };
}
const ephemeralContext = {
content: assembled.assembledContext,
kind: "knowledge",
trusted: true,
citations: assembled.citations,
metadata: { confidence: selection.confidence },
};
return { ephemeralContext, metadata, retrieval };
})(), timeoutMs, `Knowledge grounding timed out after ${timeoutMs}ms`);
// Promise timers cannot pre-empt synchronous indexing/scoring work. Check
// elapsed wall time as well so an over-budget synchronous pass still
// fails open instead of injecting a late result.
if (Date.now() - wallClockStarted > timeoutMs) {
throw new Error(`Knowledge grounding timed out after ${timeoutMs}ms`);
}
return outcome;
}
catch (error) {
// Fail open: no grounding rather than a broken turn.
return {
ephemeralContext: null,
metadata: emptyMetadata(this.now() - started, String(error)),
retrieval: null,
};
}
}
}