@juspay/neurolink 12.52.0 → 12.52.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +3 -3
- package/dist/browser/neurolink.min.js +401 -401
- package/dist/rag/chunking/latexChunker.js +29 -4
- package/dist/rag/ragIntegration.d.ts +4 -0
- package/dist/rag/ragIntegration.js +33 -1
- package/package.json +3 -2
|
@@ -5,6 +5,31 @@
|
|
|
5
5
|
* Best for academic papers, scientific documents, and mathematical content.
|
|
6
6
|
*/
|
|
7
7
|
import { randomUUID } from "crypto";
|
|
8
|
+
const BEGIN_DOCUMENT = "\\begin{document}";
|
|
9
|
+
const END_DOCUMENT = "\\end{document}";
|
|
10
|
+
/**
|
|
11
|
+
* Split a LaTeX source into the text before `\begin{document}` and the body up
|
|
12
|
+
* to the first `\end{document}` after it, or null when there is no such pair.
|
|
13
|
+
*
|
|
14
|
+
* This is what `/^([\s\S]*?)\\begin\{document\}([\s\S]*?)\\end\{document\}/`
|
|
15
|
+
* returned, without its cost: with no `\end{document}` in the text, that
|
|
16
|
+
* pattern rescanned to the end of the input from every `\begin{document}`,
|
|
17
|
+
* so N occurrences took O(N * length). If the first `\begin{document}` has no
|
|
18
|
+
* closing marker after it, a later one cannot either, so two `indexOf` calls
|
|
19
|
+
* decide the match and the work is linear.
|
|
20
|
+
*/
|
|
21
|
+
function splitDocumentBody(text) {
|
|
22
|
+
const begin = text.indexOf(BEGIN_DOCUMENT);
|
|
23
|
+
if (begin === -1) {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
const bodyStart = begin + BEGIN_DOCUMENT.length;
|
|
27
|
+
const end = text.indexOf(END_DOCUMENT, bodyStart);
|
|
28
|
+
if (end === -1) {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
return { preamble: text.slice(0, begin), body: text.slice(bodyStart, end) };
|
|
32
|
+
}
|
|
8
33
|
/**
|
|
9
34
|
* LaTeX-aware chunker implementation
|
|
10
35
|
* Splits based on LaTeX structure (sections, environments)
|
|
@@ -37,12 +62,12 @@ export class LaTeXChunker {
|
|
|
37
62
|
return chunks;
|
|
38
63
|
}
|
|
39
64
|
// Extract preamble if present
|
|
40
|
-
const
|
|
65
|
+
const documentParts = splitDocumentBody(text);
|
|
41
66
|
let preamble = "";
|
|
42
67
|
let documentContent = text;
|
|
43
|
-
if (
|
|
44
|
-
preamble =
|
|
45
|
-
documentContent =
|
|
68
|
+
if (documentParts) {
|
|
69
|
+
preamble = documentParts.preamble.trim();
|
|
70
|
+
documentContent = documentParts.body;
|
|
46
71
|
// Add preamble as first chunk if requested
|
|
47
72
|
if (includePreamble && preamble.length > 0) {
|
|
48
73
|
chunks.push({
|
|
@@ -25,6 +25,10 @@ declare function diversifyResults(results: VectorQueryResult[], topK: number): V
|
|
|
25
25
|
* @param ragConfig - RAG configuration from generate/stream options
|
|
26
26
|
* @param fallbackProvider - Provider to use for embeddings if not specified in ragConfig
|
|
27
27
|
* @returns Prepared RAG tool to inject into the tools record
|
|
28
|
+
* @throws When `files` is empty, when no source can be loaded, and when the
|
|
29
|
+
* built-in hash embedding is given a chunk longer than 1,048,576 characters (it
|
|
30
|
+
* is the default, and the fallback when a configured embedding provider fails).
|
|
31
|
+
* The returned tool rejects a query over the same length the same way.
|
|
28
32
|
*/
|
|
29
33
|
export declare function prepareRAGTool(ragConfig: RAGConfig, fallbackProvider?: string): Promise<RAGPreparedTool>;
|
|
30
34
|
export {};
|
|
@@ -4,6 +4,7 @@ import { z } from "zod";
|
|
|
4
4
|
import { SpanSerializer, SpanType, SpanStatus, getMetricsAggregator, } from "../observability/index.js";
|
|
5
5
|
import { logger } from "../utils/logger.js";
|
|
6
6
|
import { redactUrlForError } from "../utils/logSanitize.js";
|
|
7
|
+
import { ErrorFactory } from "../utils/errorHandling.js";
|
|
7
8
|
import { ImageProcessor } from "../utils/imageProcessor.js";
|
|
8
9
|
import { createChunker } from "./ChunkerFactory.js";
|
|
9
10
|
import { createVectorQueryTool, InMemoryVectorStore, } from "./retrieval/vectorQueryTool.js";
|
|
@@ -49,6 +50,22 @@ function detectStrategy(filePath) {
|
|
|
49
50
|
}
|
|
50
51
|
/** Embedding dimension used by {@link generateSimpleEmbedding}. */
|
|
51
52
|
const EMBEDDING_DIMENSION = 128;
|
|
53
|
+
/**
|
|
54
|
+
* Longest text, in UTF-16 code units, that {@link generateSimpleEmbedding}
|
|
55
|
+
* will hash: 1,048,576.
|
|
56
|
+
*
|
|
57
|
+
* The 128-dimension output does not bound the work. The function walks every
|
|
58
|
+
* character and every word of its input, and nothing upstream limits that
|
|
59
|
+
* input: a chunk is as large as the caller's `chunkSize` allows, and a search
|
|
60
|
+
* query is whatever the model sends. A default chunk is 1,000 characters, so
|
|
61
|
+
* this is about a thousand times that and no ordinary input reaches it. Longer
|
|
62
|
+
* text is refused with an error rather than truncated, because a clipped text
|
|
63
|
+
* would be embedded as a different document and rank as one.
|
|
64
|
+
*
|
|
65
|
+
* It applies only to the hash embedding. A configured embedding provider
|
|
66
|
+
* receives the text as before and applies its own limits.
|
|
67
|
+
*/
|
|
68
|
+
const MAX_HASH_EMBEDDING_TEXT_LENGTH = 1_048_576;
|
|
52
69
|
/**
|
|
53
70
|
* Simple hash function for strings (FNV-1a variant).
|
|
54
71
|
* Maps a word to a bucket index deterministically.
|
|
@@ -68,6 +85,9 @@ function hashWord(word, buckets) {
|
|
|
68
85
|
* When a real embedding provider is configured, it will be used instead.
|
|
69
86
|
*/
|
|
70
87
|
function generateSimpleEmbedding(text, dimension) {
|
|
88
|
+
if (text.length > MAX_HASH_EMBEDDING_TEXT_LENGTH) {
|
|
89
|
+
throw ErrorFactory.invalidParameters("rag.hashEmbedding", new Error(`text is ${text.length} characters, over the ${MAX_HASH_EMBEDDING_TEXT_LENGTH} character limit of the built-in hash embedding. Lower chunkSize, shorten the query, or configure an embeddingProvider.`), { length: text.length, maxLength: MAX_HASH_EMBEDDING_TEXT_LENGTH });
|
|
90
|
+
}
|
|
71
91
|
const charEmbedding = new Array(dimension).fill(0);
|
|
72
92
|
const wordEmbedding = new Array(dimension).fill(0);
|
|
73
93
|
// Character-frequency features
|
|
@@ -148,6 +168,10 @@ function diversifyResults(results, topK) {
|
|
|
148
168
|
* @param ragConfig - RAG configuration from generate/stream options
|
|
149
169
|
* @param fallbackProvider - Provider to use for embeddings if not specified in ragConfig
|
|
150
170
|
* @returns Prepared RAG tool to inject into the tools record
|
|
171
|
+
* @throws When `files` is empty, when no source can be loaded, and when the
|
|
172
|
+
* built-in hash embedding is given a chunk longer than 1,048,576 characters (it
|
|
173
|
+
* is the default, and the fallback when a configured embedding provider fails).
|
|
174
|
+
* The returned tool rejects a query over the same length the same way.
|
|
151
175
|
*/
|
|
152
176
|
export async function prepareRAGTool(ragConfig, fallbackProvider) {
|
|
153
177
|
const span = SpanSerializer.createSpan(SpanType.RAG, "rag.prepare", {
|
|
@@ -356,6 +380,7 @@ async function _prepareRAGToolInner(ragConfig, fallbackProvider) {
|
|
|
356
380
|
const embedProviderName = embeddingProvider || fallbackProvider || "vertex";
|
|
357
381
|
const embedModelName = embeddingModel || "gemini-2.5-flash";
|
|
358
382
|
let embedFn = (text) => Promise.resolve(generateSimpleEmbedding(text, EMBEDDING_DIMENSION));
|
|
383
|
+
let embeddingFromProvider = false;
|
|
359
384
|
if (wantProviderEmbeddings) {
|
|
360
385
|
try {
|
|
361
386
|
const { AIProviderFactory } = await import("../core/factory.js");
|
|
@@ -363,6 +388,7 @@ async function _prepareRAGToolInner(ragConfig, fallbackProvider) {
|
|
|
363
388
|
if (typeof embedderProvider.embed === "function") {
|
|
364
389
|
const providerEmbed = embedderProvider.embed.bind(embedderProvider);
|
|
365
390
|
embedFn = (text) => providerEmbed(text, embedModelName);
|
|
391
|
+
embeddingFromProvider = true;
|
|
366
392
|
}
|
|
367
393
|
else {
|
|
368
394
|
logger.warn(`[RAG] Embedding provider '${embedProviderName}' has no embed(); falling back to hash embeddings`);
|
|
@@ -387,6 +413,12 @@ async function _prepareRAGToolInner(ragConfig, fallbackProvider) {
|
|
|
387
413
|
imageVectors = allVectors.slice(allChunks.length);
|
|
388
414
|
}
|
|
389
415
|
catch (error) {
|
|
416
|
+
// The hash embedding refuses text over its length limit. With no provider
|
|
417
|
+
// in use there is nothing to fall back from, and retrying would only repeat
|
|
418
|
+
// that refusal after a log line claiming a provider failed.
|
|
419
|
+
if (!embeddingFromProvider) {
|
|
420
|
+
throw error;
|
|
421
|
+
}
|
|
390
422
|
// One failed chunk must not leave a mixed-space index — flip the whole
|
|
391
423
|
// index AND all queries back to the hash space together.
|
|
392
424
|
logger.warn("[RAG] Provider embedding failed mid-index; falling back to hash embeddings for index and queries", { error: error instanceof Error ? error.message : String(error) });
|
|
@@ -456,7 +488,7 @@ async function _prepareRAGToolInner(ragConfig, fallbackProvider) {
|
|
|
456
488
|
// would be scored against provider-space vectors of another
|
|
457
489
|
// dimension, which cosine reads as 0 for every chunk, so the tool
|
|
458
490
|
// would return arbitrary chunks as if they were relevant. A hash-built
|
|
459
|
-
// index
|
|
491
|
+
// index only rejects here for a query over the hash length limit.
|
|
460
492
|
const queryEmbedding = await embedFn(query);
|
|
461
493
|
// Fetch more candidates than needed so diversity can select across files and images
|
|
462
494
|
const loadedSources = fileContents.length + imageChunks.length;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@juspay/neurolink",
|
|
3
|
-
"version": "12.52.
|
|
3
|
+
"version": "12.52.1",
|
|
4
4
|
"packageManager": "pnpm@10.15.1",
|
|
5
5
|
"description": "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/call) for routing, tool selection and context budgeting. MCP-native (4 transports), voice TTS/STT/realtime, RAG, agents, memory, compaction, 9 observability exporters. OpenAI · Anthropic · Gemini · Bedrock · Azure · Ollama · TypeSafe Jev and more.",
|
|
6
6
|
"author": {
|
|
@@ -166,6 +166,7 @@
|
|
|
166
166
|
"test:pdf-image-streaming": "pnpm exec tsx test/continuous-test-suite-pdf-image-streaming.ts",
|
|
167
167
|
"test:rag": "pnpm exec tsx test/continuous-test-suite-rag.ts",
|
|
168
168
|
"test:rag-html-markup": "pnpm exec tsx test/continuous-test-suite-rag-html-markup.ts",
|
|
169
|
+
"test:rag-bounds": "pnpm exec tsx test/continuous-test-suite-rag-bounds.ts",
|
|
169
170
|
"test:skills": "pnpm exec tsx test/continuous-test-suite-skills.ts",
|
|
170
171
|
"test:decide": "pnpm exec tsx test/continuous-test-suite-decide.ts",
|
|
171
172
|
"test:decide-catalog": "pnpm exec tsx test/continuous-test-suite-decide-catalog.ts",
|
|
@@ -220,7 +221,7 @@
|
|
|
220
221
|
"// CI tier — fast, no live AI calls, safe for every commit (test:unit; also see the separate provider-safety-net CI job, which runs build + test:providers-mocked + test:provider-structure + test:error-classifier-contract on every PR)": "",
|
|
221
222
|
"test:tool-routing": "pnpm exec tsx test/continuous-test-suite-tool-routing.ts",
|
|
222
223
|
"test:tool-routing-semantic": "pnpm exec tsx test/continuous-test-suite-tool-routing-semantic.ts",
|
|
223
|
-
"test:unit": "pnpm run test:bugfixes && pnpm run test:mcp:infra && pnpm run test:mcp:spans && pnpm run test:tool-routing && pnpm run test:tool-routing-cli && pnpm run test:tool-dedup && pnpm run test:model-pool && pnpm run test:classifier-router && pnpm run test:tool-routing-semantic && pnpm run test:mcp-result-cache && pnpm run test:mcp-direct-name-repair && pnpm run test:mcp-breaker-resolved-errors && pnpm run test:archive:security && pnpm run test:office:security && pnpm run test:image-exif && pnpm run test:vector-chroma && pnpm run test:vector-pgvector && pnpm run test:vector-pinecone && pnpm run test:provider-wiring && pnpm run test:docs-mcp && pnpm run test:retired-model-defaults && pnpm run test:cli-fallback && pnpm run test:embedding-response-validation && pnpm run test:xiaomi-mimo-pilot",
|
|
224
|
+
"test:unit": "pnpm run test:bugfixes && pnpm run test:mcp:infra && pnpm run test:mcp:spans && pnpm run test:tool-routing && pnpm run test:tool-routing-cli && pnpm run test:tool-dedup && pnpm run test:model-pool && pnpm run test:classifier-router && pnpm run test:tool-routing-semantic && pnpm run test:mcp-result-cache && pnpm run test:mcp-direct-name-repair && pnpm run test:mcp-breaker-resolved-errors && pnpm run test:archive:security && pnpm run test:office:security && pnpm run test:image-exif && pnpm run test:vector-chroma && pnpm run test:vector-pgvector && pnpm run test:vector-pinecone && pnpm run test:provider-wiring && pnpm run test:docs-mcp && pnpm run test:retired-model-defaults && pnpm run test:cli-fallback && pnpm run test:embedding-response-validation && pnpm run test:xiaomi-mimo-pilot && pnpm run test:rag-bounds",
|
|
224
225
|
"// CI tier — live providers, runs only when API keys are present (test:credentials and test:dynamic make real provider calls when keys are set, so they live here, not in test:unit; test:matrix, a different suite covering the full provider capability matrix, runs nightly via .github/workflows/live-matrix.yml — test:providers itself is still only wired into test:live, not any GitHub Actions workflow)": "",
|
|
225
226
|
"test:live": "pnpm run test:providers && pnpm run test:mcp:http && pnpm run test:mcp:sdk && pnpm run test:mcp:cli && pnpm run test:observability && pnpm run test:context && pnpm run test:memory && pnpm run test:tool-reliability && pnpm run test:evaluation && pnpm run test:autoresearch && pnpm run test:credentials && pnpm run test:dynamic && pnpm run test:model-not-found-retryable",
|
|
226
227
|
"// CI tier — product output (image/video/TTS/PPT) — costs $$ per run (not wired into any GitHub Actions workflow as of this comment; run manually or add to live-matrix.yml if nightly coverage is needed)": "",
|