@juspay/neurolink 9.23.0 → 9.25.0

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.
Files changed (225) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +10 -13
  3. package/dist/adapters/tts/googleTTSHandler.js +26 -1
  4. package/dist/adapters/video/vertexVideoHandler.js +23 -17
  5. package/dist/cli/commands/config.d.ts +3 -3
  6. package/dist/cli/commands/observability.d.ts +53 -0
  7. package/dist/cli/commands/observability.js +453 -0
  8. package/dist/cli/commands/telemetry.d.ts +63 -0
  9. package/dist/cli/commands/telemetry.js +689 -0
  10. package/dist/cli/factories/commandFactory.d.ts +34 -0
  11. package/dist/cli/factories/commandFactory.js +321 -116
  12. package/dist/cli/parser.js +6 -9
  13. package/dist/cli/utils/formatters.d.ts +13 -0
  14. package/dist/cli/utils/formatters.js +23 -0
  15. package/dist/constants/contextWindows.js +6 -0
  16. package/dist/constants/enums.d.ts +6 -0
  17. package/dist/constants/enums.js +8 -2
  18. package/dist/context/budgetChecker.js +75 -48
  19. package/dist/context/contextCompactor.js +135 -127
  20. package/dist/core/baseProvider.d.ts +5 -0
  21. package/dist/core/baseProvider.js +158 -102
  22. package/dist/core/conversationMemoryInitializer.js +7 -4
  23. package/dist/core/conversationMemoryManager.d.ts +2 -0
  24. package/dist/core/conversationMemoryManager.js +6 -2
  25. package/dist/core/modules/GenerationHandler.d.ts +2 -2
  26. package/dist/core/modules/GenerationHandler.js +12 -12
  27. package/dist/evaluation/ragasEvaluator.js +39 -19
  28. package/dist/evaluation/scoring.js +46 -20
  29. package/dist/features/ppt/index.d.ts +1 -1
  30. package/dist/features/ppt/index.js +1 -1
  31. package/dist/features/ppt/presentationOrchestrator.js +23 -0
  32. package/dist/features/ppt/slideGenerator.js +13 -0
  33. package/dist/features/ppt/slideRenderers.d.ts +1 -1
  34. package/dist/features/ppt/slideRenderers.js +6 -4
  35. package/dist/features/ppt/slideTypeInference.d.ts +1 -1
  36. package/dist/features/ppt/slideTypeInference.js +75 -73
  37. package/dist/files/fileTools.d.ts +6 -6
  38. package/dist/index.d.ts +46 -12
  39. package/dist/index.js +79 -17
  40. package/dist/lib/adapters/tts/googleTTSHandler.js +26 -1
  41. package/dist/lib/adapters/video/vertexVideoHandler.js +23 -17
  42. package/dist/lib/constants/contextWindows.js +6 -0
  43. package/dist/lib/constants/enums.d.ts +6 -0
  44. package/dist/lib/constants/enums.js +8 -2
  45. package/dist/lib/context/budgetChecker.js +75 -48
  46. package/dist/lib/context/contextCompactor.js +135 -127
  47. package/dist/lib/core/baseProvider.d.ts +5 -0
  48. package/dist/lib/core/baseProvider.js +158 -102
  49. package/dist/lib/core/conversationMemoryInitializer.js +7 -4
  50. package/dist/lib/core/conversationMemoryManager.d.ts +2 -0
  51. package/dist/lib/core/conversationMemoryManager.js +6 -2
  52. package/dist/lib/core/modules/GenerationHandler.d.ts +2 -2
  53. package/dist/lib/core/modules/GenerationHandler.js +12 -12
  54. package/dist/lib/evaluation/ragasEvaluator.js +39 -19
  55. package/dist/lib/evaluation/scoring.js +46 -20
  56. package/dist/lib/features/ppt/index.d.ts +1 -1
  57. package/dist/lib/features/ppt/index.js +1 -1
  58. package/dist/lib/features/ppt/presentationOrchestrator.js +23 -0
  59. package/dist/lib/features/ppt/slideGenerator.js +13 -0
  60. package/dist/lib/features/ppt/slideRenderers.d.ts +1 -1
  61. package/dist/lib/features/ppt/slideRenderers.js +6 -4
  62. package/dist/lib/features/ppt/slideTypeInference.d.ts +1 -1
  63. package/dist/lib/features/ppt/slideTypeInference.js +75 -73
  64. package/dist/lib/files/fileTools.d.ts +6 -6
  65. package/dist/lib/index.d.ts +46 -12
  66. package/dist/lib/index.js +79 -17
  67. package/dist/lib/mcp/httpRateLimiter.js +39 -12
  68. package/dist/lib/mcp/httpRetryHandler.js +22 -1
  69. package/dist/lib/mcp/mcpClientFactory.js +13 -15
  70. package/dist/lib/memory/memoryRetrievalTools.js +22 -0
  71. package/dist/lib/neurolink.d.ts +64 -72
  72. package/dist/lib/neurolink.js +984 -566
  73. package/dist/lib/observability/exporterRegistry.d.ts +152 -0
  74. package/dist/lib/observability/exporterRegistry.js +414 -0
  75. package/dist/lib/observability/exporters/arizeExporter.d.ts +32 -0
  76. package/dist/lib/observability/exporters/arizeExporter.js +139 -0
  77. package/dist/lib/observability/exporters/baseExporter.d.ts +117 -0
  78. package/dist/lib/observability/exporters/baseExporter.js +191 -0
  79. package/dist/lib/observability/exporters/braintrustExporter.d.ts +30 -0
  80. package/dist/lib/observability/exporters/braintrustExporter.js +155 -0
  81. package/dist/lib/observability/exporters/datadogExporter.d.ts +37 -0
  82. package/dist/lib/observability/exporters/datadogExporter.js +197 -0
  83. package/dist/lib/observability/exporters/index.d.ts +13 -0
  84. package/dist/lib/observability/exporters/index.js +14 -0
  85. package/dist/lib/observability/exporters/laminarExporter.d.ts +48 -0
  86. package/dist/lib/observability/exporters/laminarExporter.js +303 -0
  87. package/dist/lib/observability/exporters/langfuseExporter.d.ts +47 -0
  88. package/dist/lib/observability/exporters/langfuseExporter.js +200 -0
  89. package/dist/lib/observability/exporters/langsmithExporter.d.ts +26 -0
  90. package/dist/lib/observability/exporters/langsmithExporter.js +124 -0
  91. package/dist/lib/observability/exporters/otelExporter.d.ts +39 -0
  92. package/dist/lib/observability/exporters/otelExporter.js +165 -0
  93. package/dist/lib/observability/exporters/posthogExporter.d.ts +48 -0
  94. package/dist/lib/observability/exporters/posthogExporter.js +288 -0
  95. package/dist/lib/observability/exporters/sentryExporter.d.ts +32 -0
  96. package/dist/lib/observability/exporters/sentryExporter.js +166 -0
  97. package/dist/lib/observability/index.d.ts +25 -0
  98. package/dist/lib/observability/index.js +32 -0
  99. package/dist/lib/observability/metricsAggregator.d.ts +260 -0
  100. package/dist/lib/observability/metricsAggregator.js +553 -0
  101. package/dist/lib/observability/otelBridge.d.ts +49 -0
  102. package/dist/lib/observability/otelBridge.js +132 -0
  103. package/dist/lib/observability/retryPolicy.d.ts +192 -0
  104. package/dist/lib/observability/retryPolicy.js +384 -0
  105. package/dist/lib/observability/sampling/index.d.ts +4 -0
  106. package/dist/lib/observability/sampling/index.js +5 -0
  107. package/dist/lib/observability/sampling/samplers.d.ts +116 -0
  108. package/dist/lib/observability/sampling/samplers.js +217 -0
  109. package/dist/lib/observability/spanProcessor.d.ts +129 -0
  110. package/dist/lib/observability/spanProcessor.js +288 -0
  111. package/dist/lib/observability/tokenTracker.d.ts +156 -0
  112. package/dist/lib/observability/tokenTracker.js +414 -0
  113. package/dist/lib/observability/types/exporterTypes.d.ts +250 -0
  114. package/dist/lib/observability/types/exporterTypes.js +6 -0
  115. package/dist/lib/observability/types/index.d.ts +6 -0
  116. package/dist/lib/observability/types/index.js +5 -0
  117. package/dist/lib/observability/types/spanTypes.d.ts +244 -0
  118. package/dist/lib/observability/types/spanTypes.js +93 -0
  119. package/dist/lib/observability/utils/index.d.ts +4 -0
  120. package/dist/lib/observability/utils/index.js +5 -0
  121. package/dist/lib/observability/utils/spanSerializer.d.ts +115 -0
  122. package/dist/lib/observability/utils/spanSerializer.js +287 -0
  123. package/dist/lib/providers/amazonSagemaker.d.ts +5 -4
  124. package/dist/lib/providers/amazonSagemaker.js +3 -4
  125. package/dist/lib/providers/googleVertex.d.ts +7 -0
  126. package/dist/lib/providers/googleVertex.js +80 -2
  127. package/dist/lib/rag/pipeline/RAGPipeline.d.ts +0 -5
  128. package/dist/lib/rag/pipeline/RAGPipeline.js +122 -87
  129. package/dist/lib/rag/ragIntegration.js +30 -0
  130. package/dist/lib/rag/retrieval/hybridSearch.js +22 -0
  131. package/dist/lib/server/abstract/baseServerAdapter.js +51 -19
  132. package/dist/lib/server/middleware/common.js +44 -12
  133. package/dist/lib/services/server/ai/observability/instrumentation.d.ts +2 -2
  134. package/dist/lib/services/server/ai/observability/instrumentation.js +10 -5
  135. package/dist/lib/types/cli.d.ts +18 -2
  136. package/dist/lib/types/conversationMemoryInterface.d.ts +2 -0
  137. package/dist/lib/types/generateTypes.d.ts +2 -2
  138. package/dist/lib/types/modelTypes.d.ts +18 -18
  139. package/dist/lib/types/providers.d.ts +5 -0
  140. package/dist/lib/utils/pricing.js +25 -1
  141. package/dist/lib/utils/ttsProcessor.js +74 -59
  142. package/dist/lib/workflow/config.d.ts +36 -36
  143. package/dist/lib/workflow/core/ensembleExecutor.js +10 -0
  144. package/dist/lib/workflow/core/judgeScorer.js +20 -2
  145. package/dist/lib/workflow/core/workflowRunner.js +34 -1
  146. package/dist/mcp/httpRateLimiter.js +39 -12
  147. package/dist/mcp/httpRetryHandler.js +22 -1
  148. package/dist/mcp/mcpClientFactory.js +13 -15
  149. package/dist/memory/memoryRetrievalTools.js +22 -0
  150. package/dist/neurolink.d.ts +64 -72
  151. package/dist/neurolink.js +984 -566
  152. package/dist/observability/FEATURE-STATUS.md +269 -0
  153. package/dist/observability/exporterRegistry.d.ts +152 -0
  154. package/dist/observability/exporterRegistry.js +413 -0
  155. package/dist/observability/exporters/arizeExporter.d.ts +32 -0
  156. package/dist/observability/exporters/arizeExporter.js +138 -0
  157. package/dist/observability/exporters/baseExporter.d.ts +117 -0
  158. package/dist/observability/exporters/baseExporter.js +190 -0
  159. package/dist/observability/exporters/braintrustExporter.d.ts +30 -0
  160. package/dist/observability/exporters/braintrustExporter.js +154 -0
  161. package/dist/observability/exporters/datadogExporter.d.ts +37 -0
  162. package/dist/observability/exporters/datadogExporter.js +196 -0
  163. package/dist/observability/exporters/index.d.ts +13 -0
  164. package/dist/observability/exporters/index.js +13 -0
  165. package/dist/observability/exporters/laminarExporter.d.ts +48 -0
  166. package/dist/observability/exporters/laminarExporter.js +302 -0
  167. package/dist/observability/exporters/langfuseExporter.d.ts +47 -0
  168. package/dist/observability/exporters/langfuseExporter.js +199 -0
  169. package/dist/observability/exporters/langsmithExporter.d.ts +26 -0
  170. package/dist/observability/exporters/langsmithExporter.js +123 -0
  171. package/dist/observability/exporters/otelExporter.d.ts +39 -0
  172. package/dist/observability/exporters/otelExporter.js +164 -0
  173. package/dist/observability/exporters/posthogExporter.d.ts +48 -0
  174. package/dist/observability/exporters/posthogExporter.js +287 -0
  175. package/dist/observability/exporters/sentryExporter.d.ts +32 -0
  176. package/dist/observability/exporters/sentryExporter.js +165 -0
  177. package/dist/observability/index.d.ts +25 -0
  178. package/dist/observability/index.js +31 -0
  179. package/dist/observability/metricsAggregator.d.ts +260 -0
  180. package/dist/observability/metricsAggregator.js +552 -0
  181. package/dist/observability/otelBridge.d.ts +49 -0
  182. package/dist/observability/otelBridge.js +131 -0
  183. package/dist/observability/retryPolicy.d.ts +192 -0
  184. package/dist/observability/retryPolicy.js +383 -0
  185. package/dist/observability/sampling/index.d.ts +4 -0
  186. package/dist/observability/sampling/index.js +4 -0
  187. package/dist/observability/sampling/samplers.d.ts +116 -0
  188. package/dist/observability/sampling/samplers.js +216 -0
  189. package/dist/observability/spanProcessor.d.ts +129 -0
  190. package/dist/observability/spanProcessor.js +287 -0
  191. package/dist/observability/tokenTracker.d.ts +156 -0
  192. package/dist/observability/tokenTracker.js +413 -0
  193. package/dist/observability/types/exporterTypes.d.ts +250 -0
  194. package/dist/observability/types/exporterTypes.js +5 -0
  195. package/dist/observability/types/index.d.ts +6 -0
  196. package/dist/observability/types/index.js +4 -0
  197. package/dist/observability/types/spanTypes.d.ts +244 -0
  198. package/dist/observability/types/spanTypes.js +92 -0
  199. package/dist/observability/utils/index.d.ts +4 -0
  200. package/dist/observability/utils/index.js +4 -0
  201. package/dist/observability/utils/spanSerializer.d.ts +115 -0
  202. package/dist/observability/utils/spanSerializer.js +286 -0
  203. package/dist/providers/amazonSagemaker.d.ts +5 -4
  204. package/dist/providers/amazonSagemaker.js +3 -4
  205. package/dist/providers/googleVertex.d.ts +7 -0
  206. package/dist/providers/googleVertex.js +80 -2
  207. package/dist/rag/pipeline/RAGPipeline.d.ts +0 -5
  208. package/dist/rag/pipeline/RAGPipeline.js +122 -87
  209. package/dist/rag/ragIntegration.js +30 -0
  210. package/dist/rag/retrieval/hybridSearch.js +22 -0
  211. package/dist/server/abstract/baseServerAdapter.js +51 -19
  212. package/dist/server/middleware/common.js +44 -12
  213. package/dist/services/server/ai/observability/instrumentation.d.ts +2 -2
  214. package/dist/services/server/ai/observability/instrumentation.js +10 -5
  215. package/dist/types/cli.d.ts +18 -2
  216. package/dist/types/conversationMemoryInterface.d.ts +2 -0
  217. package/dist/types/generateTypes.d.ts +2 -2
  218. package/dist/types/providers.d.ts +5 -0
  219. package/dist/utils/pricing.js +25 -1
  220. package/dist/utils/ttsProcessor.js +74 -59
  221. package/dist/workflow/config.d.ts +52 -52
  222. package/dist/workflow/core/ensembleExecutor.js +10 -0
  223. package/dist/workflow/core/judgeScorer.js +20 -2
  224. package/dist/workflow/core/workflowRunner.js +34 -1
  225. package/package.json +1 -1
@@ -33,12 +33,16 @@ import { InMemoryBM25Index, createHybridSearch, } from "../retrieval/hybridSearc
33
33
  import { GraphRAG } from "../graphRag/graphRAG.js";
34
34
  import { rerank } from "../reranker/reranker.js";
35
35
  import { ProviderFactory } from "../../factories/providerFactory.js";
36
+ import { SpanSerializer, SpanType, SpanStatus, getMetricsAggregator, } from "../../observability/index.js";
36
37
  import { logger } from "../../utils/logger.js";
38
+ import { withTimeout } from "../../utils/async/withTimeout.js";
37
39
  /**
38
40
  * RAG Pipeline Orchestrator
39
41
  *
40
42
  * Complete end-to-end pipeline for Retrieval-Augmented Generation.
41
43
  */
44
+ /** Default timeout for external provider calls (30 seconds) */
45
+ const DEFAULT_TIMEOUT_MS = 30_000;
42
46
  export class RAGPipeline {
43
47
  id;
44
48
  config;
@@ -195,96 +199,127 @@ export class RAGPipeline {
195
199
  * @returns RAG response with retrieved context and optional generated answer
196
200
  */
197
201
  async query(query, options) {
198
- await this.ensureInitialized();
199
- const startTime = Date.now();
200
- const topK = options?.topK || this.config.defaultTopK || 5;
201
- const useHybrid = options?.hybrid ?? this.config.enableHybridSearch;
202
- const useGraph = options?.graph ?? this.config.enableGraphRAG;
203
- const useRerank = options?.rerank ?? this.config.enableReranking;
204
- let results;
205
- let retrievalMethod = "vector";
206
- // Generate query embedding
207
- const queryEmbedding = await this.generateEmbedding(query);
208
- if (useGraph && this.config.enableGraphRAG) {
209
- // Graph RAG search
210
- retrievalMethod = "graph";
211
- const graphResults = this.graphRAG.query({
212
- query: queryEmbedding,
213
- topK: topK * 2, // Get more for potential reranking
214
- });
215
- results = graphResults.map((r) => ({
216
- id: r.id,
217
- text: r.content,
218
- score: r.score,
219
- metadata: r.metadata,
220
- }));
221
- }
222
- else if (useHybrid && this.hybridSearch) {
223
- // Hybrid search
224
- retrievalMethod = "hybrid";
225
- const hybridResults = await this.hybridSearch(query, { topK: topK * 2 });
226
- results = hybridResults.map((r) => ({
202
+ const span = SpanSerializer.createSpan(SpanType.RAG, "rag.pipeline", {
203
+ "rag.operation": "pipeline",
204
+ "rag.query": query.slice(0, 200),
205
+ "rag.topK": options?.topK ?? this.config.defaultTopK ?? 5,
206
+ "rag.hybrid": options?.hybrid ?? this.config.enableHybridSearch ?? false,
207
+ "rag.graph": options?.graph ?? this.config.enableGraphRAG ?? false,
208
+ "rag.rerank": options?.rerank ?? this.config.enableReranking ?? false,
209
+ });
210
+ const spanStartTime = Date.now();
211
+ try {
212
+ await this.ensureInitialized();
213
+ const startTime = Date.now();
214
+ const topK = options?.topK || this.config.defaultTopK || 5;
215
+ const useHybrid = options?.hybrid ?? this.config.enableHybridSearch;
216
+ const useGraph = options?.graph ?? this.config.enableGraphRAG;
217
+ const useRerank = options?.rerank ?? this.config.enableReranking;
218
+ let results;
219
+ let retrievalMethod = "vector";
220
+ // Generate query embedding
221
+ const queryEmbedding = await this.generateEmbedding(query);
222
+ if (useGraph && this.config.enableGraphRAG) {
223
+ // Graph RAG search
224
+ retrievalMethod = "graph";
225
+ const graphResults = this.graphRAG.query({
226
+ query: queryEmbedding,
227
+ topK: topK * 2, // Get more for potential reranking
228
+ });
229
+ results = graphResults.map((r) => ({
230
+ id: r.id,
231
+ text: r.content,
232
+ score: r.score,
233
+ metadata: r.metadata,
234
+ }));
235
+ }
236
+ else if (useHybrid && this.hybridSearch) {
237
+ // Hybrid search
238
+ retrievalMethod = "hybrid";
239
+ const hybridResults = await this.hybridSearch(query, {
240
+ topK: topK * 2,
241
+ });
242
+ results = hybridResults.map((r) => ({
243
+ id: r.id,
244
+ text: r.text,
245
+ score: r.score,
246
+ metadata: r.metadata,
247
+ }));
248
+ }
249
+ else {
250
+ // Vector search
251
+ results = await this.vectorStore.query({
252
+ indexName: this.config.indexName ?? "default",
253
+ queryVector: queryEmbedding,
254
+ topK: topK * 2,
255
+ filter: options?.filter,
256
+ });
257
+ }
258
+ // Apply reranking if enabled
259
+ let reranked = false;
260
+ if (useRerank && this.config.rerankingModel && results.length > 0) {
261
+ const rerankModel = await ProviderFactory.createProvider(this.config.rerankingModel.provider, this.config.rerankingModel.modelName);
262
+ const rerankedResults = await rerank(results, query, rerankModel, {
263
+ topK,
264
+ queryEmbedding,
265
+ });
266
+ results = rerankedResults.map((r) => r.result);
267
+ reranked = true;
268
+ }
269
+ // Take top K results
270
+ results = results.slice(0, topK);
271
+ // Assemble context
272
+ const context = this.assembleContext(results);
273
+ // Format sources
274
+ const sources = results.map((r) => ({
227
275
  id: r.id,
228
- text: r.text,
229
- score: r.score,
276
+ text: r.text || r.metadata?.text || "",
277
+ score: r.score || 0,
230
278
  metadata: r.metadata,
231
279
  }));
232
- }
233
- else {
234
- // Vector search
235
- results = await this.vectorStore.query({
236
- indexName: this.config.indexName ?? "default",
237
- queryVector: queryEmbedding,
238
- topK: topK * 2,
239
- filter: options?.filter,
240
- });
241
- }
242
- // Apply reranking if enabled
243
- let reranked = false;
244
- if (useRerank && this.config.rerankingModel && results.length > 0) {
245
- const rerankModel = await ProviderFactory.createProvider(this.config.rerankingModel.provider, this.config.rerankingModel.modelName);
246
- const rerankedResults = await rerank(results, query, rerankModel, {
247
- topK,
248
- queryEmbedding,
280
+ // Generate answer if requested
281
+ let answer;
282
+ if (options?.generate !== false && this.generationProvider) {
283
+ answer = await this.generateAnswer(query, context, options?.systemPrompt, options?.temperature);
284
+ }
285
+ const queryTime = Date.now() - startTime;
286
+ logger.info("[RAGPipeline] Query completed", {
287
+ query: query.slice(0, 50),
288
+ retrievalMethod,
289
+ resultsCount: results.length,
290
+ reranked,
291
+ queryTime,
249
292
  });
250
- results = rerankedResults.map((r) => r.result);
251
- reranked = true;
293
+ const response = {
294
+ answer,
295
+ context,
296
+ sources,
297
+ metadata: {
298
+ queryTime,
299
+ retrievalMethod,
300
+ chunksRetrieved: results.length,
301
+ reranked,
302
+ },
303
+ };
304
+ span.durationMs = Date.now() - spanStartTime;
305
+ const endedSpan = SpanSerializer.endSpan(span, SpanStatus.OK);
306
+ endedSpan.attributes = {
307
+ ...endedSpan.attributes,
308
+ "rag.retrieval_method": retrievalMethod,
309
+ "rag.results_count": results.length,
310
+ "rag.reranked": reranked,
311
+ };
312
+ getMetricsAggregator().recordSpan(endedSpan);
313
+ return response;
252
314
  }
253
- // Take top K results
254
- results = results.slice(0, topK);
255
- // Assemble context
256
- const context = this.assembleContext(results);
257
- // Format sources
258
- const sources = results.map((r) => ({
259
- id: r.id,
260
- text: r.text || r.metadata?.text || "",
261
- score: r.score || 0,
262
- metadata: r.metadata,
263
- }));
264
- // Generate answer if requested
265
- let answer;
266
- if (options?.generate !== false && this.generationProvider) {
267
- answer = await this.generateAnswer(query, context, options?.systemPrompt, options?.temperature);
315
+ catch (error) {
316
+ span.durationMs = Date.now() - spanStartTime;
317
+ const endedSpan = SpanSerializer.endSpan(span, SpanStatus.ERROR);
318
+ endedSpan.statusMessage =
319
+ error instanceof Error ? error.message : String(error);
320
+ getMetricsAggregator().recordSpan(endedSpan);
321
+ throw error;
268
322
  }
269
- const queryTime = Date.now() - startTime;
270
- logger.info("[RAGPipeline] Query completed", {
271
- query: query.slice(0, 50),
272
- retrievalMethod,
273
- resultsCount: results.length,
274
- reranked,
275
- queryTime,
276
- });
277
- return {
278
- answer,
279
- context,
280
- sources,
281
- metadata: {
282
- queryTime,
283
- retrievalMethod,
284
- chunksRetrieved: results.length,
285
- reranked,
286
- },
287
- };
288
323
  }
289
324
  /**
290
325
  * Get pipeline statistics
@@ -340,7 +375,7 @@ export class RAGPipeline {
340
375
  .embed !== "function") {
341
376
  throw new Error(`Provider ${this.config.embeddingModel.provider} does not support embeddings`);
342
377
  }
343
- return await this.embeddingProvider.embed(text);
378
+ return await withTimeout(this.embeddingProvider.embed(text), DEFAULT_TIMEOUT_MS, "Embedding generation timed out");
344
379
  }
345
380
  /**
346
381
  * Assemble context from results
@@ -367,12 +402,12 @@ Use only the information from the context to answer the question.
367
402
  If the context doesn't contain relevant information, say so.
368
403
  Cite sources when possible using [Source N] format.`;
369
404
  const prompt = `Context:\n${context}\n\nQuestion: ${query}\n\nAnswer:`;
370
- const result = await this.generationProvider.generate({
405
+ const result = await withTimeout(this.generationProvider.generate({
371
406
  prompt,
372
407
  systemPrompt,
373
408
  temperature: temperature ?? this.config.generationModel?.temperature ?? 0.7,
374
409
  maxTokens: this.config.generationModel?.maxTokens ?? 1000,
375
- });
410
+ }), DEFAULT_TIMEOUT_MS * 2, "Answer generation timed out");
376
411
  return result?.content || "";
377
412
  }
378
413
  }
@@ -9,6 +9,7 @@
9
9
  import { existsSync, readFileSync } from "fs";
10
10
  import { extname, resolve } from "path";
11
11
  import { z } from "zod";
12
+ import { SpanSerializer, SpanType, SpanStatus, getMetricsAggregator, } from "../observability/index.js";
12
13
  import { logger } from "../utils/logger.js";
13
14
  import { createChunker } from "./ChunkerFactory.js";
14
15
  import { createVectorQueryTool, InMemoryVectorStore, } from "./retrieval/vectorQueryTool.js";
@@ -149,6 +150,35 @@ function diversifyResults(results, topK) {
149
150
  * @returns Prepared RAG tool to inject into the tools record
150
151
  */
151
152
  export async function prepareRAGTool(ragConfig, fallbackProvider) {
153
+ const span = SpanSerializer.createSpan(SpanType.RAG, "rag.prepare", {
154
+ "rag.operation": "prepare",
155
+ "rag.files_count": ragConfig.files?.length ?? 0,
156
+ "rag.strategy": ragConfig.strategy ?? "auto",
157
+ "rag.chunk_size": ragConfig.chunkSize ?? 1000,
158
+ });
159
+ const startTime = Date.now();
160
+ try {
161
+ const result = await _prepareRAGToolInner(ragConfig, fallbackProvider);
162
+ span.durationMs = Date.now() - startTime;
163
+ const endedSpan = SpanSerializer.endSpan(span, SpanStatus.OK);
164
+ endedSpan.attributes = {
165
+ ...endedSpan.attributes,
166
+ "rag.chunks_indexed": result.chunksIndexed,
167
+ "rag.files_loaded": result.filesLoaded,
168
+ };
169
+ getMetricsAggregator().recordSpan(endedSpan);
170
+ return result;
171
+ }
172
+ catch (error) {
173
+ span.durationMs = Date.now() - startTime;
174
+ const endedSpan = SpanSerializer.endSpan(span, SpanStatus.ERROR);
175
+ endedSpan.statusMessage =
176
+ error instanceof Error ? error.message : String(error);
177
+ getMetricsAggregator().recordSpan(endedSpan);
178
+ throw error;
179
+ }
180
+ }
181
+ async function _prepareRAGToolInner(ragConfig, fallbackProvider) {
152
182
  const { files, strategy: userStrategy, chunkSize = 1000, chunkOverlap = 200, topK: userTopK = 5, toolName = "search_knowledge_base", toolDescription = "REQUIRED: Search through pre-loaded local documents to find relevant information. Use this tool FIRST before any web search or other tools. This searches an indexed knowledge base of documents the user has provided.", embeddingProvider, embeddingModel, } = ragConfig;
153
183
  if (!files || files.length === 0) {
154
184
  throw new Error("RAG config requires at least one file path in 'files'");
@@ -5,6 +5,7 @@
5
5
  * Supports multiple fusion methods: Reciprocal Rank Fusion (RRF) and Linear Combination.
6
6
  */
7
7
  import { ProviderFactory } from "../../factories/providerFactory.js";
8
+ import { SpanSerializer, SpanType, SpanStatus, getMetricsAggregator, } from "../../observability/index.js";
8
9
  import { logger } from "../../utils/logger.js";
9
10
  import { rerank } from "../reranker/reranker.js";
10
11
  /**
@@ -166,6 +167,13 @@ export function createHybridSearch(options) {
166
167
  return async function hybridSearch(query, config) {
167
168
  const startTime = Date.now();
168
169
  const { vectorWeight = defaultConfig.vectorWeight ?? 0.5, bm25Weight = defaultConfig.bm25Weight ?? 0.5, fusionMethod = defaultConfig.fusionMethod ?? "rrf", rrfK = defaultConfig.rrfK ?? 60, topK = defaultConfig.topK ?? 10, enableReranking = defaultConfig.enableReranking ?? false, reranker: rerankerConfig = defaultConfig.reranker, } = config || {};
170
+ const span = SpanSerializer.createSpan(SpanType.RAG, "rag.search", {
171
+ "rag.operation": "search",
172
+ "rag.topK": topK,
173
+ "rag.fusionMethod": fusionMethod,
174
+ "rag.query": query.slice(0, 200),
175
+ });
176
+ const spanStartTime = Date.now();
169
177
  try {
170
178
  // Generate query embedding
171
179
  const embeddingProvider = await ProviderFactory.createProvider(embeddingModel?.provider, embeddingModel?.modelName);
@@ -300,9 +308,23 @@ export function createHybridSearch(options) {
300
308
  fusionMethod,
301
309
  queryTime,
302
310
  });
311
+ span.durationMs = Date.now() - spanStartTime;
312
+ const endedSpan = SpanSerializer.endSpan(span, SpanStatus.OK);
313
+ endedSpan.attributes = {
314
+ ...endedSpan.attributes,
315
+ "rag.results_count": fusedResults.length,
316
+ "rag.vector_results": vectorResults.length,
317
+ "rag.bm25_results": bm25Results.length,
318
+ };
319
+ getMetricsAggregator().recordSpan(endedSpan);
303
320
  return fusedResults;
304
321
  }
305
322
  catch (error) {
323
+ span.durationMs = Date.now() - spanStartTime;
324
+ const endedSpan = SpanSerializer.endSpan(span, SpanStatus.ERROR);
325
+ endedSpan.statusMessage =
326
+ error instanceof Error ? error.message : String(error);
327
+ getMetricsAggregator().recordSpan(endedSpan);
306
328
  logger.error("[HybridSearch] Search failed", {
307
329
  query: query.slice(0, 50),
308
330
  error: error instanceof Error ? error.message : String(error),
@@ -4,6 +4,8 @@
4
4
  * Follows NeuroLink's composition and factory patterns
5
5
  */
6
6
  import { EventEmitter } from "events";
7
+ import { getMetricsAggregator } from "../../observability/index.js";
8
+ import { SpanSerializer, SpanStatus, SpanType, } from "../../observability/index.js";
7
9
  import { withTimeout } from "../../utils/errorHandling.js";
8
10
  import { logger } from "../../utils/logger.js";
9
11
  import { DrainTimeoutError, InvalidLifecycleStateError, ShutdownTimeoutError, } from "../errors.js";
@@ -112,6 +114,12 @@ export class BaseServerAdapter extends EventEmitter {
112
114
  host: this.config.host,
113
115
  basePath: this.config.basePath,
114
116
  });
117
+ const span = SpanSerializer.createSpan(SpanType.SERVER_REQUEST, "server.initialize", {
118
+ "server.operation": "initialize",
119
+ "server.port": this.config.port,
120
+ "server.host": this.config.host,
121
+ });
122
+ const startTime = Date.now();
115
123
  try {
116
124
  // Initialize framework-specific setup
117
125
  this.initializeFramework();
@@ -129,9 +137,17 @@ export class BaseServerAdapter extends EventEmitter {
129
137
  routes: this.routes.size,
130
138
  middlewares: this.middlewares.length,
131
139
  });
140
+ span.durationMs = Date.now() - startTime;
141
+ const endedSpan = SpanSerializer.endSpan(span, SpanStatus.OK);
142
+ getMetricsAggregator().recordSpan(endedSpan);
132
143
  }
133
144
  catch (error) {
134
145
  this.lifecycleState = "error";
146
+ span.durationMs = Date.now() - startTime;
147
+ const endedSpan = SpanSerializer.endSpan(span, SpanStatus.ERROR);
148
+ endedSpan.statusMessage =
149
+ error instanceof Error ? error.message : String(error);
150
+ getMetricsAggregator().recordSpan(endedSpan);
135
151
  throw error;
136
152
  }
137
153
  }
@@ -397,29 +413,34 @@ export class BaseServerAdapter extends EventEmitter {
397
413
  gracefulShutdownTimeoutMs,
398
414
  drainTimeoutMs,
399
415
  });
400
- // Set draining state
401
- this.lifecycleState = "draining";
402
- // Stop accepting new connections
403
- await this.stopAcceptingConnections();
404
- logger.info("[ServerAdapter] Stopped accepting new connections");
405
- // Create drain promise that resolves when all connections are closed
406
- const drainPromise = this.drainConnections();
416
+ const shutdownSpan = SpanSerializer.createSpan(SpanType.SERVER_REQUEST, "server.shutdown", {
417
+ "server.operation": "gracefulShutdown",
418
+ "server.activeConnections": this.activeConnections.size,
419
+ });
420
+ const shutdownStartTime = Date.now();
407
421
  // Timer references for cleanup
408
422
  let shutdownTimer;
409
423
  let drainTimer;
410
- // Create timeout promise for overall shutdown
411
- const shutdownTimeoutPromise = new Promise((_, reject) => {
412
- shutdownTimer = setTimeout(() => {
413
- reject(new ShutdownTimeoutError(gracefulShutdownTimeoutMs, this.activeConnections.size));
414
- }, gracefulShutdownTimeoutMs);
415
- });
416
- // Create timeout promise for drain phase
417
- const drainTimeoutPromise = new Promise((resolve) => {
418
- drainTimer = setTimeout(() => {
419
- resolve("drain_timeout");
420
- }, drainTimeoutMs);
421
- });
422
424
  try {
425
+ // Set draining state
426
+ this.lifecycleState = "draining";
427
+ // Stop accepting new connections (covered by shutdown span)
428
+ await this.stopAcceptingConnections();
429
+ logger.info("[ServerAdapter] Stopped accepting new connections");
430
+ // Create drain promise that resolves when all connections are closed
431
+ const drainPromise = this.drainConnections();
432
+ // Create timeout promise for overall shutdown
433
+ const shutdownTimeoutPromise = new Promise((_, reject) => {
434
+ shutdownTimer = setTimeout(() => {
435
+ reject(new ShutdownTimeoutError(gracefulShutdownTimeoutMs, this.activeConnections.size));
436
+ }, gracefulShutdownTimeoutMs);
437
+ });
438
+ // Create timeout promise for drain phase
439
+ const drainTimeoutPromise = new Promise((resolve) => {
440
+ drainTimer = setTimeout(() => {
441
+ resolve("drain_timeout");
442
+ }, drainTimeoutMs);
443
+ });
423
444
  // Race drain against drain timeout
424
445
  const drainResult = await Promise.race([
425
446
  drainPromise,
@@ -442,6 +463,9 @@ export class BaseServerAdapter extends EventEmitter {
442
463
  // Close the server with shutdown timeout
443
464
  await Promise.race([this.closeServer(), shutdownTimeoutPromise]);
444
465
  logger.info("[ServerAdapter] Server closed successfully");
466
+ shutdownSpan.durationMs = Date.now() - shutdownStartTime;
467
+ const endedShutdownSpan = SpanSerializer.endSpan(shutdownSpan, SpanStatus.OK);
468
+ getMetricsAggregator().recordSpan(endedShutdownSpan);
445
469
  }
446
470
  catch (error) {
447
471
  // If force close is enabled and we hit a timeout, try to force close
@@ -453,9 +477,17 @@ export class BaseServerAdapter extends EventEmitter {
453
477
  });
454
478
  await this.forceCloseConnections();
455
479
  await this.closeServer();
480
+ shutdownSpan.durationMs = Date.now() - shutdownStartTime;
481
+ const endedShutdownSpan = SpanSerializer.endSpan(shutdownSpan, SpanStatus.OK);
482
+ getMetricsAggregator().recordSpan(endedShutdownSpan);
456
483
  }
457
484
  else {
458
485
  this.lifecycleState = "error";
486
+ shutdownSpan.durationMs = Date.now() - shutdownStartTime;
487
+ const endedShutdownSpan = SpanSerializer.endSpan(shutdownSpan, SpanStatus.ERROR);
488
+ endedShutdownSpan.statusMessage =
489
+ error instanceof Error ? error.message : String(error);
490
+ getMetricsAggregator().recordSpan(endedShutdownSpan);
459
491
  throw error;
460
492
  }
461
493
  }
@@ -2,6 +2,8 @@
2
2
  * Common Middleware Components
3
3
  * Utility middleware for common server operations
4
4
  */
5
+ import { getMetricsAggregator } from "../../observability/index.js";
6
+ import { SpanSerializer, SpanStatus, SpanType, } from "../../observability/index.js";
5
7
  import { logger } from "../../utils/logger.js";
6
8
  /**
7
9
  * Create request timing middleware
@@ -21,17 +23,34 @@ export function createTimingMiddleware() {
21
23
  const startHrTime = process.hrtime.bigint();
22
24
  // Store start time in metadata
23
25
  ctx.metadata.requestStartTime = startTime;
24
- const result = await next();
25
- // Calculate duration
26
- const endHrTime = process.hrtime.bigint();
27
- const durationNs = Number(endHrTime - startHrTime);
28
- const durationMs = durationNs / 1_000_000;
29
- // Add timing headers to responseHeaders (adapters read from here)
30
- ctx.responseHeaders = ctx.responseHeaders || {};
31
- ctx.responseHeaders["X-Response-Time"] = `${durationMs.toFixed(2)}ms`;
32
- ctx.responseHeaders["Server-Timing"] =
33
- `total;dur=${durationMs.toFixed(2)}`;
34
- return result;
26
+ const span = SpanSerializer.createSpan(SpanType.SERVER_REQUEST, "server.middleware.timing", {
27
+ "server.operation": "middleware",
28
+ "server.middleware": "timing",
29
+ });
30
+ try {
31
+ const result = await next();
32
+ // Calculate duration
33
+ const endHrTime = process.hrtime.bigint();
34
+ const durationNs = Number(endHrTime - startHrTime);
35
+ const durationMs = durationNs / 1_000_000;
36
+ // Add timing headers to responseHeaders (adapters read from here)
37
+ ctx.responseHeaders = ctx.responseHeaders || {};
38
+ ctx.responseHeaders["X-Response-Time"] = `${durationMs.toFixed(2)}ms`;
39
+ ctx.responseHeaders["Server-Timing"] =
40
+ `total;dur=${durationMs.toFixed(2)}`;
41
+ span.durationMs = Date.now() - startTime;
42
+ const endedSpan = SpanSerializer.endSpan(span, SpanStatus.OK);
43
+ getMetricsAggregator().recordSpan(endedSpan);
44
+ return result;
45
+ }
46
+ catch (error) {
47
+ span.durationMs = Date.now() - startTime;
48
+ const endedSpan = SpanSerializer.endSpan(span, SpanStatus.ERROR);
49
+ endedSpan.statusMessage =
50
+ error instanceof Error ? error.message : String(error);
51
+ getMetricsAggregator().recordSpan(endedSpan);
52
+ throw error;
53
+ }
35
54
  },
36
55
  };
37
56
  }
@@ -77,8 +96,17 @@ export function createErrorHandlingMiddleware(options) {
77
96
  name: "error-handling",
78
97
  order: 1, // Run very early to catch all errors
79
98
  handler: async (ctx, next) => {
99
+ const span = SpanSerializer.createSpan(SpanType.SERVER_REQUEST, "server.middleware.errorHandling", {
100
+ "server.operation": "middleware",
101
+ "server.middleware": "error-handling",
102
+ });
103
+ const middlewareStartTime = Date.now();
80
104
  try {
81
- return await next();
105
+ const result = await next();
106
+ span.durationMs = Date.now() - middlewareStartTime;
107
+ const endedSpan = SpanSerializer.endSpan(span, SpanStatus.OK);
108
+ getMetricsAggregator().recordSpan(endedSpan);
109
+ return result;
82
110
  }
83
111
  catch (error) {
84
112
  const err = error;
@@ -89,6 +117,10 @@ export function createErrorHandlingMiddleware(options) {
89
117
  stack: err.stack,
90
118
  });
91
119
  }
120
+ span.durationMs = Date.now() - middlewareStartTime;
121
+ const endedSpan = SpanSerializer.endSpan(span, SpanStatus.ERROR);
122
+ endedSpan.statusMessage = err.message;
123
+ getMetricsAggregator().recordSpan(endedSpan);
92
124
  // Use custom handler if provided
93
125
  if (onError) {
94
126
  return onError(err, ctx);
@@ -6,10 +6,10 @@
6
6
  *
7
7
  * Flow: Vercel AI SDK → OpenTelemetry Spans → LangfuseSpanProcessor → Langfuse Platform
8
8
  */
9
- import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node";
10
9
  import { LangfuseSpanProcessor } from "@langfuse/otel";
11
- import type { SpanProcessor } from "@opentelemetry/sdk-trace-base";
12
10
  import { trace } from "@opentelemetry/api";
11
+ import type { SpanProcessor } from "@opentelemetry/sdk-trace-base";
12
+ import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node";
13
13
  import type { LangfuseConfig } from "../../../../types/observability.js";
14
14
  /**
15
15
  * Extended context for Langfuse spans
@@ -6,12 +6,12 @@
6
6
  *
7
7
  * Flow: Vercel AI SDK → OpenTelemetry Spans → LangfuseSpanProcessor → Langfuse Platform
8
8
  */
9
- import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node";
10
9
  import { LangfuseSpanProcessor } from "@langfuse/otel";
11
- import { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION, } from "@opentelemetry/semantic-conventions";
10
+ import { trace } from "@opentelemetry/api";
12
11
  import { resourceFromAttributes } from "@opentelemetry/resources";
12
+ import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node";
13
+ import { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION, } from "@opentelemetry/semantic-conventions";
13
14
  import { AsyncLocalStorage } from "async_hooks";
14
- import { trace } from "@opentelemetry/api";
15
15
  import { logger } from "../../../../utils/logger.js";
16
16
  const LOG_PREFIX = "[OpenTelemetry]";
17
17
  const contextStorage = new AsyncLocalStorage();
@@ -343,11 +343,16 @@ class ContextEnricher {
343
343
  * @param config - Langfuse configuration passed from parent application
344
344
  */
345
345
  export function initializeOpenTelemetry(config) {
346
- // Guard against multiple initializations
346
+ // Guard against multiple initializations — but always update config
347
+ // so that later NeuroLink instances can change traceNameFormat,
348
+ // autoDetectOperationName, and other configuration preferences
349
+ // without re-initializing the OTEL infrastructure.
347
350
  if (isInitialized) {
348
- logger.debug(`${LOG_PREFIX} Already initialized`, {
351
+ currentConfig = config;
352
+ logger.debug(`${LOG_PREFIX} Already initialized, config updated`, {
349
353
  usingExternalProvider,
350
354
  hasLangfuseProcessor: !!langfuseProcessor,
355
+ hasTraceNameFormat: typeof config.traceNameFormat === "function",
351
356
  });
352
357
  return;
353
358
  }
@@ -68,8 +68,8 @@ export type GenerateCommandArgs = BaseCommandArgs & {
68
68
  thinkingLevel?: "minimal" | "low" | "medium" | "high";
69
69
  /** Vertex AI region */
70
70
  region?: string;
71
- /** Output mode - 'text' for standard generation, 'video' for video generation */
72
- outputMode?: "text" | "video";
71
+ /** Output mode - 'text' for standard generation, 'video' for video generation, 'ppt' for presentation */
72
+ outputMode?: "text" | "video" | "ppt";
73
73
  /** Path to save generated video file */
74
74
  videoOutput?: string;
75
75
  /** Video output resolution (720p or 1080p) */
@@ -80,6 +80,20 @@ export type GenerateCommandArgs = BaseCommandArgs & {
80
80
  videoAspectRatio?: "9:16" | "16:9";
81
81
  /** Enable/disable audio generation in video */
82
82
  videoAudio?: boolean;
83
+ /** Number of slides to generate (5-50) */
84
+ pptPages?: number;
85
+ /** Presentation theme/style */
86
+ pptTheme?: "modern" | "corporate" | "creative" | "minimal" | "dark";
87
+ /** Target audience */
88
+ pptAudience?: "business" | "students" | "technical" | "general";
89
+ /** Presentation tone/style */
90
+ pptTone?: "professional" | "casual" | "educational" | "persuasive";
91
+ /** Path to save generated PPTX file */
92
+ pptOutput?: string;
93
+ /** PPT aspect ratio */
94
+ pptAspectRatio?: "16:9" | "4:3";
95
+ /** Disable AI image generation for PPT slides */
96
+ pptNoImages?: boolean;
83
97
  /** Custom path for generated image output */
84
98
  imageOutput?: string;
85
99
  };
@@ -371,6 +385,8 @@ export type GenerateResult = CommandResult & {
371
385
  audio?: import("./index.js").TTSResult;
372
386
  /** Video generation result when video mode is enabled */
373
387
  video?: import("./multimodal.js").VideoGenerationResult;
388
+ /** PPT generation result when ppt mode is enabled */
389
+ ppt?: import("./pptTypes.js").PPTGenerationResult;
374
390
  imageOutput?: {
375
391
  base64: string;
376
392
  savedPath?: string;
@@ -28,4 +28,6 @@ export type IConversationMemoryManager = {
28
28
  getSessionMessages(sessionId: string, userId?: string): Promise<ChatMessage[]>;
29
29
  /** Replace the entire messages array for a session */
30
30
  setSessionMessages(sessionId: string, messages: ChatMessage[], userId?: string): Promise<void>;
31
+ /** Close/shutdown the memory manager and release resources (e.g., Redis connections) */
32
+ close?(): Promise<void>;
31
33
  };