UNPKG

@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

125 lines (124 loc) • 5.45 kB
/** * Per-search retrieval planning. * * `RAGPipeline.query()` resolves four knobs — `topK`, `hybrid`, `graph`, * `rerank` — and every one of them is config with an optional per-call * override. Nothing inspects the query. The same plan is used for "what is * the refund window?" (one precise passage, an exact phrase worth matching * lexically) and "how does billing relate to entitlements?" (many passages, * relationships between documents). * * One decision request answers all four for the query in hand, in ~400ms. * Every answer is a *suggestion*: an explicit per-call option always wins, * and a capability the pipeline was not configured with can never be turned * on by a model — `graph: true` against a pipeline with no graph index would * be a failure, not a plan. * * @module rag/retrieval/searchDecision */ import { gateDecisionBoolean, readDecisionScore, } from "../../utils/decisionAnswers.js"; import { logger } from "../../utils/logger.js"; /** * How many passages the question needs, narrowest → widest, as multiples of * the configured default. A `score` rather than a number: a decision model * places things on an ordered scale well and reads digits as text badly. */ const BREADTH_CRITERIA = [ "One specific fact, definition or value. A single passage answers it completely.", "A handful of related points — a procedure, a short comparison, one topic explained.", "Several distinct areas that each need their own supporting passage.", "A broad survey that needs evidence from across the whole corpus.", ]; /** Multiplier applied to the configured topK, indexed by breadth level. */ const BREADTH_MULTIPLIER = [0.5, 1, 1.5, 2.5]; /** Below this the breadth reading is ignored and the configured topK stands. */ const MIN_BREADTH_CONFIDENCE = 0.5; /** Absolute bounds on a suggested topK, whatever the multiplier produces. */ const MIN_TOP_K = 1; const MAX_TOP_K = 50; /** * Ask how this particular query should be retrieved. * * Returns `null` when nothing should change — no decision provider, a failed * call, or an answer set that suggests nothing. Every returned field is * optional and means "use this unless the caller said otherwise". */ export async function decideSearchPlan(query, decide, capabilities) { if (!query.trim()) { return null; } const questions = { breadth: { type: "score", instructions: "How much supporting material does answering this question require from a document collection?", criteria: BREADTH_CRITERIA, }, }; // Only ask about a capability the pipeline actually has. An answer nobody // can act on costs input tokens and invites the mistake of acting on it. if (capabilities.canHybrid) { questions.hybrid = { type: "boolean", instructions: "This question contains exact terms that must be matched literally — an identifier, error code, file name, version number, API name, or a quoted phrase — rather than only a topic to match by meaning.", }; } if (capabilities.canGraph) { questions.graph = { type: "boolean", instructions: "Answering this requires connecting information that lives in separate documents, such as how two things relate, what depends on what, or tracing a chain across sources.", }; } if (capabilities.canRerank) { questions.rerank = { type: "boolean", instructions: "This question is specific enough that the ORDER of the retrieved passages matters — a nearly-right passage would produce a wrong answer, so precision is worth an extra ranking pass.", }; } const result = await decide({ state: { query: query.slice(0, 4000) }, // The map is assembled dynamically above; its members are all valid // question shapes, which the cast asserts once here rather than at four // separate construction sites. questions: questions, timeoutMs: capabilities.timeoutMs, }); if (!result) { return null; } const plan = { model: result.model, latencyMs: result.latencyMs, }; const breadth = readDecisionScore(result.answers, "breadth"); if (breadth && breadth.confidence >= MIN_BREADTH_CONFIDENCE) { const level = Math.max(0, Math.min(BREADTH_MULTIPLIER.length - 1, Math.round(breadth.score))); const multiplier = BREADTH_MULTIPLIER[level] ?? 1; plan.topK = Math.max(MIN_TOP_K, Math.min(MAX_TOP_K, Math.round(capabilities.defaultTopK * multiplier))); plan.breadthLevel = level; } if (capabilities.canHybrid) { plan.hybrid = gateDecisionBoolean(result.answers, "hybrid"); } if (capabilities.canGraph) { plan.graph = gateDecisionBoolean(result.answers, "graph"); } if (capabilities.canRerank) { plan.rerank = gateDecisionBoolean(result.answers, "rerank"); } const suggested = plan.topK !== undefined || plan.hybrid !== undefined || plan.graph !== undefined || plan.rerank !== undefined; if (!suggested) { return null; } logger.debug("[RAG] Search plan decided", { model: result.model, latencyMs: result.latencyMs, topK: plan.topK, hybrid: plan.hybrid, graph: plan.graph, rerank: plan.rerank, }); return plan; }