| 
									
										
										
										
											2025-03-29 21:31:33 +00:00
										 |  |  | import type { ChatPipelineInput, ChatPipelineConfig, PipelineMetrics, StreamCallback } from './interfaces.js'; | 
					
						
							|  |  |  | import type { ChatResponse, StreamChunk } from '../ai_interface.js'; | 
					
						
							|  |  |  | import { ContextExtractionStage } from './stages/context_extraction_stage.js'; | 
					
						
							|  |  |  | import { SemanticContextExtractionStage } from './stages/semantic_context_extraction_stage.js'; | 
					
						
							|  |  |  | import { AgentToolsContextStage } from './stages/agent_tools_context_stage.js'; | 
					
						
							|  |  |  | import { MessagePreparationStage } from './stages/message_preparation_stage.js'; | 
					
						
							|  |  |  | import { ModelSelectionStage } from './stages/model_selection_stage.js'; | 
					
						
							|  |  |  | import { LLMCompletionStage } from './stages/llm_completion_stage.js'; | 
					
						
							|  |  |  | import { ResponseProcessingStage } from './stages/response_processing_stage.js'; | 
					
						
							| 
									
										
										
										
											2025-04-06 20:50:08 +00:00
										 |  |  | import { ToolCallingStage } from './stages/tool_calling_stage.js'; | 
					
						
							|  |  |  | import { VectorSearchStage } from './stages/vector_search_stage.js'; | 
					
						
							|  |  |  | import toolRegistry from '../tools/tool_registry.js'; | 
					
						
							|  |  |  | import toolInitializer from '../tools/tool_initializer.js'; | 
					
						
							| 
									
										
										
										
											2025-03-29 21:31:33 +00:00
										 |  |  | import log from '../../log.js'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * Pipeline for managing the entire chat flow | 
					
						
							|  |  |  |  * Implements a modular, composable architecture where each stage is a separate component | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | export class ChatPipeline { | 
					
						
							|  |  |  |     stages: { | 
					
						
							|  |  |  |         contextExtraction: ContextExtractionStage; | 
					
						
							|  |  |  |         semanticContextExtraction: SemanticContextExtractionStage; | 
					
						
							|  |  |  |         agentToolsContext: AgentToolsContextStage; | 
					
						
							|  |  |  |         messagePreparation: MessagePreparationStage; | 
					
						
							|  |  |  |         modelSelection: ModelSelectionStage; | 
					
						
							|  |  |  |         llmCompletion: LLMCompletionStage; | 
					
						
							|  |  |  |         responseProcessing: ResponseProcessingStage; | 
					
						
							| 
									
										
										
										
											2025-04-06 20:50:08 +00:00
										 |  |  |         toolCalling: ToolCallingStage; | 
					
						
							|  |  |  |         vectorSearch: VectorSearchStage; | 
					
						
							| 
									
										
										
										
											2025-03-29 21:31:33 +00:00
										 |  |  |     }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     config: ChatPipelineConfig; | 
					
						
							|  |  |  |     metrics: PipelineMetrics; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * Create a new chat pipeline | 
					
						
							|  |  |  |      * @param config Optional pipeline configuration | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     constructor(config?: Partial<ChatPipelineConfig>) { | 
					
						
							|  |  |  |         // Initialize all pipeline stages
 | 
					
						
							|  |  |  |         this.stages = { | 
					
						
							|  |  |  |             contextExtraction: new ContextExtractionStage(), | 
					
						
							|  |  |  |             semanticContextExtraction: new SemanticContextExtractionStage(), | 
					
						
							|  |  |  |             agentToolsContext: new AgentToolsContextStage(), | 
					
						
							|  |  |  |             messagePreparation: new MessagePreparationStage(), | 
					
						
							|  |  |  |             modelSelection: new ModelSelectionStage(), | 
					
						
							|  |  |  |             llmCompletion: new LLMCompletionStage(), | 
					
						
							| 
									
										
										
										
											2025-04-06 20:50:08 +00:00
										 |  |  |             responseProcessing: new ResponseProcessingStage(), | 
					
						
							|  |  |  |             toolCalling: new ToolCallingStage(), | 
					
						
							|  |  |  |             vectorSearch: new VectorSearchStage() | 
					
						
							| 
									
										
										
										
											2025-03-29 21:31:33 +00:00
										 |  |  |         }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // Set default configuration values
 | 
					
						
							|  |  |  |         this.config = { | 
					
						
							|  |  |  |             enableStreaming: true, | 
					
						
							|  |  |  |             enableMetrics: true, | 
					
						
							|  |  |  |             maxToolCallIterations: 5, | 
					
						
							|  |  |  |             ...config | 
					
						
							|  |  |  |         }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // Initialize metrics
 | 
					
						
							|  |  |  |         this.metrics = { | 
					
						
							|  |  |  |             totalExecutions: 0, | 
					
						
							|  |  |  |             averageExecutionTime: 0, | 
					
						
							|  |  |  |             stageMetrics: {} | 
					
						
							|  |  |  |         }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // Initialize stage metrics
 | 
					
						
							|  |  |  |         Object.keys(this.stages).forEach(stageName => { | 
					
						
							|  |  |  |             this.metrics.stageMetrics[stageName] = { | 
					
						
							|  |  |  |                 totalExecutions: 0, | 
					
						
							|  |  |  |                 averageExecutionTime: 0 | 
					
						
							|  |  |  |             }; | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * Execute the chat pipeline | 
					
						
							|  |  |  |      * This is the main entry point that orchestrates all pipeline stages | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     async execute(input: ChatPipelineInput): Promise<ChatResponse> { | 
					
						
							|  |  |  |         log.info(`Executing chat pipeline with ${input.messages.length} messages`); | 
					
						
							|  |  |  |         const startTime = Date.now(); | 
					
						
							|  |  |  |         this.metrics.totalExecutions++; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // Initialize streaming handler if requested
 | 
					
						
							|  |  |  |         let streamCallback = input.streamCallback; | 
					
						
							|  |  |  |         let accumulatedText = ''; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         try { | 
					
						
							|  |  |  |             // Extract content length for model selection
 | 
					
						
							|  |  |  |             let contentLength = 0; | 
					
						
							|  |  |  |             for (const message of input.messages) { | 
					
						
							|  |  |  |                 contentLength += message.content.length; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-04-06 20:50:08 +00:00
										 |  |  |             // Initialize tools if needed
 | 
					
						
							|  |  |  |             try { | 
					
						
							|  |  |  |                 const toolCount = toolRegistry.getAllTools().length; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 // If there are no tools registered, initialize them
 | 
					
						
							|  |  |  |                 if (toolCount === 0) { | 
					
						
							|  |  |  |                     log.info('No tools found in registry, initializing tools...'); | 
					
						
							|  |  |  |                     await toolInitializer.initializeTools(); | 
					
						
							|  |  |  |                     log.info(`Tools initialized, now have ${toolRegistry.getAllTools().length} tools`); | 
					
						
							|  |  |  |                 } else { | 
					
						
							|  |  |  |                     log.info(`Found ${toolCount} tools already registered`); | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |             } catch (error: any) { | 
					
						
							|  |  |  |                 log.error(`Error checking/initializing tools: ${error.message || String(error)}`); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             // First, select the appropriate model based on query complexity and content length
 | 
					
						
							|  |  |  |             const modelSelectionStartTime = Date.now(); | 
					
						
							|  |  |  |             const modelSelection = await this.stages.modelSelection.execute({ | 
					
						
							|  |  |  |                 options: input.options, | 
					
						
							|  |  |  |                 query: input.query, | 
					
						
							|  |  |  |                 contentLength | 
					
						
							|  |  |  |             }); | 
					
						
							|  |  |  |             this.updateStageMetrics('modelSelection', modelSelectionStartTime); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             // Determine if we should use tools or semantic context
 | 
					
						
							|  |  |  |             const useTools = modelSelection.options.enableTools === true; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-03-29 21:31:33 +00:00
										 |  |  |             // Determine which pipeline flow to use
 | 
					
						
							|  |  |  |             let context: string | undefined; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             // For context-aware chats, get the appropriate context
 | 
					
						
							|  |  |  |             if (input.noteId && input.query) { | 
					
						
							|  |  |  |                 const contextStartTime = Date.now(); | 
					
						
							|  |  |  |                 if (input.showThinking) { | 
					
						
							|  |  |  |                     // Get enhanced context with agent tools if thinking is enabled
 | 
					
						
							|  |  |  |                     const agentContext = await this.stages.agentToolsContext.execute({ | 
					
						
							|  |  |  |                         noteId: input.noteId, | 
					
						
							|  |  |  |                         query: input.query, | 
					
						
							|  |  |  |                         showThinking: input.showThinking | 
					
						
							|  |  |  |                     }); | 
					
						
							|  |  |  |                     context = agentContext.context; | 
					
						
							|  |  |  |                     this.updateStageMetrics('agentToolsContext', contextStartTime); | 
					
						
							| 
									
										
										
										
											2025-04-06 20:50:08 +00:00
										 |  |  |                 } else if (!useTools) { | 
					
						
							|  |  |  |                     // Only get semantic context if tools are NOT enabled
 | 
					
						
							|  |  |  |                     // When tools are enabled, we'll let the LLM request context via tools instead
 | 
					
						
							|  |  |  |                     log.info('Getting semantic context for note using pipeline stages'); | 
					
						
							|  |  |  |                      | 
					
						
							|  |  |  |                     // First use the vector search stage to find relevant notes
 | 
					
						
							|  |  |  |                     const vectorSearchStartTime = Date.now(); | 
					
						
							|  |  |  |                     log.info(`Executing vector search stage for query: "${input.query?.substring(0, 50)}..."`); | 
					
						
							|  |  |  |                      | 
					
						
							|  |  |  |                     const vectorSearchResult = await this.stages.vectorSearch.execute({ | 
					
						
							|  |  |  |                         query: input.query || '', | 
					
						
							|  |  |  |                         noteId: input.noteId, | 
					
						
							|  |  |  |                         options: { | 
					
						
							|  |  |  |                             maxResults: 10, | 
					
						
							|  |  |  |                             useEnhancedQueries: true, | 
					
						
							|  |  |  |                             threshold: 0.6 | 
					
						
							|  |  |  |                         } | 
					
						
							|  |  |  |                     }); | 
					
						
							|  |  |  |                      | 
					
						
							|  |  |  |                     this.updateStageMetrics('vectorSearch', vectorSearchStartTime); | 
					
						
							|  |  |  |                      | 
					
						
							|  |  |  |                     log.info(`Vector search found ${vectorSearchResult.searchResults.length} relevant notes`); | 
					
						
							|  |  |  |                      | 
					
						
							|  |  |  |                     // Then pass to the semantic context stage to build the formatted context
 | 
					
						
							| 
									
										
										
										
											2025-03-29 21:31:33 +00:00
										 |  |  |                     const semanticContext = await this.stages.semanticContextExtraction.execute({ | 
					
						
							|  |  |  |                         noteId: input.noteId, | 
					
						
							| 
									
										
										
										
											2025-03-30 22:13:40 +00:00
										 |  |  |                         query: input.query, | 
					
						
							|  |  |  |                         messages: input.messages | 
					
						
							| 
									
										
										
										
											2025-03-29 21:31:33 +00:00
										 |  |  |                     }); | 
					
						
							| 
									
										
										
										
											2025-04-06 20:50:08 +00:00
										 |  |  |                      | 
					
						
							| 
									
										
										
										
											2025-03-29 21:31:33 +00:00
										 |  |  |                     context = semanticContext.context; | 
					
						
							|  |  |  |                     this.updateStageMetrics('semanticContextExtraction', contextStartTime); | 
					
						
							| 
									
										
										
										
											2025-04-06 20:50:08 +00:00
										 |  |  |                 } else { | 
					
						
							|  |  |  |                     log.info('Tools are enabled - using minimal direct context to avoid race conditions'); | 
					
						
							|  |  |  |                     // Get context from current note directly without semantic search
 | 
					
						
							|  |  |  |                     if (input.noteId) { | 
					
						
							|  |  |  |                         try { | 
					
						
							|  |  |  |                             const contextExtractor = new (await import('../../llm/context/index.js')).ContextExtractor(); | 
					
						
							|  |  |  |                             // Just get the direct content of the current note
 | 
					
						
							|  |  |  |                             context = await contextExtractor.extractContext(input.noteId, { | 
					
						
							|  |  |  |                                 includeContent: true, | 
					
						
							|  |  |  |                                 includeParents: true, | 
					
						
							|  |  |  |                                 includeChildren: true, | 
					
						
							|  |  |  |                                 includeLinks: true, | 
					
						
							|  |  |  |                                 includeSimilar: false // Skip semantic search to avoid race conditions
 | 
					
						
							|  |  |  |                             }); | 
					
						
							|  |  |  |                             log.info(`Direct context extracted (${context.length} chars) without semantic search`); | 
					
						
							|  |  |  |                         } catch (error: any) { | 
					
						
							|  |  |  |                             log.error(`Error extracting direct context: ${error.message}`); | 
					
						
							|  |  |  |                             context = ""; // Fallback to empty context if extraction fails
 | 
					
						
							|  |  |  |                         } | 
					
						
							|  |  |  |                     } else { | 
					
						
							|  |  |  |                         context = ""; // No note ID, so no context
 | 
					
						
							|  |  |  |                     } | 
					
						
							| 
									
										
										
										
											2025-03-29 21:31:33 +00:00
										 |  |  |                 } | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             // Prepare messages with context and system prompt
 | 
					
						
							|  |  |  |             const messagePreparationStartTime = Date.now(); | 
					
						
							|  |  |  |             const preparedMessages = await this.stages.messagePreparation.execute({ | 
					
						
							|  |  |  |                 messages: input.messages, | 
					
						
							|  |  |  |                 context, | 
					
						
							|  |  |  |                 systemPrompt: input.options?.systemPrompt, | 
					
						
							|  |  |  |                 options: modelSelection.options | 
					
						
							|  |  |  |             }); | 
					
						
							|  |  |  |             this.updateStageMetrics('messagePreparation', messagePreparationStartTime); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             // Generate completion using the LLM
 | 
					
						
							|  |  |  |             const llmStartTime = Date.now(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             // Setup streaming handler if streaming is enabled and callback provided
 | 
					
						
							| 
									
										
										
										
											2025-03-30 22:13:40 +00:00
										 |  |  |             const enableStreaming = this.config.enableStreaming && | 
					
						
							| 
									
										
										
										
											2025-03-29 21:31:33 +00:00
										 |  |  |                                   modelSelection.options.stream !== false && | 
					
						
							|  |  |  |                                   typeof streamCallback === 'function'; | 
					
						
							| 
									
										
										
										
											2025-03-30 22:13:40 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-03-29 21:31:33 +00:00
										 |  |  |             if (enableStreaming) { | 
					
						
							|  |  |  |                 // Make sure stream is enabled in options
 | 
					
						
							|  |  |  |                 modelSelection.options.stream = true; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             const completion = await this.stages.llmCompletion.execute({ | 
					
						
							|  |  |  |                 messages: preparedMessages.messages, | 
					
						
							|  |  |  |                 options: modelSelection.options | 
					
						
							|  |  |  |             }); | 
					
						
							|  |  |  |             this.updateStageMetrics('llmCompletion', llmStartTime); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             // Handle streaming if enabled and available
 | 
					
						
							|  |  |  |             if (enableStreaming && completion.response.stream && streamCallback) { | 
					
						
							|  |  |  |                 // Setup stream handler that passes chunks through response processing
 | 
					
						
							|  |  |  |                 await completion.response.stream(async (chunk: StreamChunk) => { | 
					
						
							|  |  |  |                     // Process the chunk text
 | 
					
						
							|  |  |  |                     const processedChunk = await this.processStreamChunk(chunk, input.options); | 
					
						
							| 
									
										
										
										
											2025-03-30 22:13:40 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-03-29 21:31:33 +00:00
										 |  |  |                     // Accumulate text for final response
 | 
					
						
							|  |  |  |                     accumulatedText += processedChunk.text; | 
					
						
							| 
									
										
										
										
											2025-03-30 22:13:40 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-03-29 21:31:33 +00:00
										 |  |  |                     // Forward to callback
 | 
					
						
							|  |  |  |                     await streamCallback!(processedChunk.text, processedChunk.done); | 
					
						
							|  |  |  |                 }); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-04-06 20:50:08 +00:00
										 |  |  |             // Process any tool calls in the response
 | 
					
						
							|  |  |  |             let currentMessages = preparedMessages.messages; | 
					
						
							|  |  |  |             let currentResponse = completion.response; | 
					
						
							|  |  |  |             let needsFollowUp = false; | 
					
						
							|  |  |  |             let toolCallIterations = 0; | 
					
						
							|  |  |  |             const maxToolCallIterations = this.config.maxToolCallIterations; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             // Check if tools were enabled in the options
 | 
					
						
							|  |  |  |             const toolsEnabled = modelSelection.options.enableTools !== false; | 
					
						
							|  |  |  |              | 
					
						
							|  |  |  |             log.info(`========== TOOL CALL PROCESSING ==========`); | 
					
						
							|  |  |  |             log.info(`Tools enabled: ${toolsEnabled}`); | 
					
						
							|  |  |  |             log.info(`Tool calls in response: ${currentResponse.tool_calls ? currentResponse.tool_calls.length : 0}`); | 
					
						
							|  |  |  |             log.info(`Current response format: ${typeof currentResponse}`); | 
					
						
							|  |  |  |             log.info(`Response keys: ${Object.keys(currentResponse).join(', ')}`); | 
					
						
							|  |  |  |              | 
					
						
							|  |  |  |             // Detailed tool call inspection
 | 
					
						
							|  |  |  |             if (currentResponse.tool_calls) { | 
					
						
							|  |  |  |                 currentResponse.tool_calls.forEach((tool, idx) => { | 
					
						
							|  |  |  |                     log.info(`Tool call ${idx+1}: ${JSON.stringify(tool)}`); | 
					
						
							|  |  |  |                 }); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             // Process tool calls if present and tools are enabled
 | 
					
						
							|  |  |  |             if (toolsEnabled && currentResponse.tool_calls && currentResponse.tool_calls.length > 0) { | 
					
						
							|  |  |  |                 log.info(`Response contains ${currentResponse.tool_calls.length} tool calls, processing...`); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 // Start tool calling loop
 | 
					
						
							|  |  |  |                 log.info(`Starting tool calling loop with max ${maxToolCallIterations} iterations`); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 do { | 
					
						
							|  |  |  |                     log.info(`Tool calling iteration ${toolCallIterations + 1}`); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                     // Execute tool calling stage
 | 
					
						
							|  |  |  |                     const toolCallingStartTime = Date.now(); | 
					
						
							|  |  |  |                     const toolCallingResult = await this.stages.toolCalling.execute({ | 
					
						
							|  |  |  |                         response: currentResponse, | 
					
						
							|  |  |  |                         messages: currentMessages, | 
					
						
							|  |  |  |                         options: modelSelection.options | 
					
						
							|  |  |  |                     }); | 
					
						
							|  |  |  |                     this.updateStageMetrics('toolCalling', toolCallingStartTime); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                     // Update state for next iteration
 | 
					
						
							|  |  |  |                     currentMessages = toolCallingResult.messages; | 
					
						
							|  |  |  |                     needsFollowUp = toolCallingResult.needsFollowUp; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                     // Make another call to the LLM if needed
 | 
					
						
							|  |  |  |                     if (needsFollowUp) { | 
					
						
							|  |  |  |                         log.info(`Tool execution completed, making follow-up LLM call (iteration ${toolCallIterations + 1})...`); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                         // Generate a new LLM response with the updated messages
 | 
					
						
							|  |  |  |                         const followUpStartTime = Date.now(); | 
					
						
							|  |  |  |                         log.info(`Sending follow-up request to LLM with ${currentMessages.length} messages (including tool results)`); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                         const followUpCompletion = await this.stages.llmCompletion.execute({ | 
					
						
							|  |  |  |                             messages: currentMessages, | 
					
						
							|  |  |  |                             options: modelSelection.options | 
					
						
							|  |  |  |                         }); | 
					
						
							|  |  |  |                         this.updateStageMetrics('llmCompletion', followUpStartTime); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                         // Update current response for next iteration
 | 
					
						
							|  |  |  |                         currentResponse = followUpCompletion.response; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                         // Check for more tool calls
 | 
					
						
							|  |  |  |                         const hasMoreToolCalls = !!(currentResponse.tool_calls && currentResponse.tool_calls.length > 0); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                         if (hasMoreToolCalls) { | 
					
						
							|  |  |  |                             log.info(`Follow-up response contains ${currentResponse.tool_calls?.length || 0} more tool calls`); | 
					
						
							|  |  |  |                         } else { | 
					
						
							|  |  |  |                             log.info(`Follow-up response contains no more tool calls - completing tool loop`); | 
					
						
							|  |  |  |                         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                         // Continue loop if there are more tool calls
 | 
					
						
							|  |  |  |                         needsFollowUp = hasMoreToolCalls; | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                     // Increment iteration counter
 | 
					
						
							|  |  |  |                     toolCallIterations++; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 } while (needsFollowUp && toolCallIterations < maxToolCallIterations); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 // If we hit max iterations but still have tool calls, log a warning
 | 
					
						
							|  |  |  |                 if (toolCallIterations >= maxToolCallIterations && needsFollowUp) { | 
					
						
							|  |  |  |                     log.error(`Reached maximum tool call iterations (${maxToolCallIterations}), stopping`); | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 log.info(`Completed ${toolCallIterations} tool call iterations`); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             // For non-streaming responses, process the final response
 | 
					
						
							| 
									
										
										
										
											2025-03-29 21:31:33 +00:00
										 |  |  |             const processStartTime = Date.now(); | 
					
						
							|  |  |  |             const processed = await this.stages.responseProcessing.execute({ | 
					
						
							| 
									
										
										
										
											2025-04-06 20:50:08 +00:00
										 |  |  |                 response: currentResponse, | 
					
						
							| 
									
										
										
										
											2025-03-29 21:31:33 +00:00
										 |  |  |                 options: input.options | 
					
						
							|  |  |  |             }); | 
					
						
							|  |  |  |             this.updateStageMetrics('responseProcessing', processStartTime); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             // Combine response with processed text, using accumulated text if streamed
 | 
					
						
							|  |  |  |             const finalResponse: ChatResponse = { | 
					
						
							| 
									
										
										
										
											2025-04-06 20:50:08 +00:00
										 |  |  |                 ...currentResponse, | 
					
						
							| 
									
										
										
										
											2025-03-29 21:31:33 +00:00
										 |  |  |                 text: accumulatedText || processed.text | 
					
						
							|  |  |  |             }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             const endTime = Date.now(); | 
					
						
							|  |  |  |             const executionTime = endTime - startTime; | 
					
						
							| 
									
										
										
										
											2025-03-30 22:13:40 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-03-29 21:31:33 +00:00
										 |  |  |             // Update overall average execution time
 | 
					
						
							| 
									
										
										
										
											2025-03-30 22:13:40 +00:00
										 |  |  |             this.metrics.averageExecutionTime = | 
					
						
							| 
									
										
										
										
											2025-03-29 21:31:33 +00:00
										 |  |  |                 (this.metrics.averageExecutionTime * (this.metrics.totalExecutions - 1) + executionTime) / | 
					
						
							|  |  |  |                 this.metrics.totalExecutions; | 
					
						
							| 
									
										
										
										
											2025-03-30 22:13:40 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-03-29 21:31:33 +00:00
										 |  |  |             log.info(`Chat pipeline completed in ${executionTime}ms`); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             return finalResponse; | 
					
						
							|  |  |  |         } catch (error: any) { | 
					
						
							|  |  |  |             log.error(`Error in chat pipeline: ${error.message}`); | 
					
						
							|  |  |  |             throw error; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * Process a stream chunk through the response processing stage | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     private async processStreamChunk(chunk: StreamChunk, options?: any): Promise<StreamChunk> { | 
					
						
							|  |  |  |         try { | 
					
						
							|  |  |  |             // Only process non-empty chunks
 | 
					
						
							|  |  |  |             if (!chunk.text) return chunk; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             // Create a minimal response object for the processor
 | 
					
						
							|  |  |  |             const miniResponse = { | 
					
						
							|  |  |  |                 text: chunk.text, | 
					
						
							|  |  |  |                 model: 'streaming', | 
					
						
							|  |  |  |                 provider: 'streaming' | 
					
						
							|  |  |  |             }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             // Process the chunk text
 | 
					
						
							|  |  |  |             const processed = await this.stages.responseProcessing.execute({ | 
					
						
							|  |  |  |                 response: miniResponse, | 
					
						
							|  |  |  |                 options: options | 
					
						
							|  |  |  |             }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             // Return processed chunk
 | 
					
						
							|  |  |  |             return { | 
					
						
							|  |  |  |                 ...chunk, | 
					
						
							|  |  |  |                 text: processed.text | 
					
						
							|  |  |  |             }; | 
					
						
							|  |  |  |         } catch (error) { | 
					
						
							|  |  |  |             // On error, return original chunk
 | 
					
						
							|  |  |  |             log.error(`Error processing stream chunk: ${error}`); | 
					
						
							|  |  |  |             return chunk; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * Update metrics for a pipeline stage | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     private updateStageMetrics(stageName: string, startTime: number) { | 
					
						
							|  |  |  |         if (!this.config.enableMetrics) return; | 
					
						
							| 
									
										
										
										
											2025-03-30 22:13:40 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-03-29 21:31:33 +00:00
										 |  |  |         const executionTime = Date.now() - startTime; | 
					
						
							|  |  |  |         const metrics = this.metrics.stageMetrics[stageName]; | 
					
						
							| 
									
										
										
										
											2025-03-30 22:13:40 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-03-29 21:31:33 +00:00
										 |  |  |         metrics.totalExecutions++; | 
					
						
							| 
									
										
										
										
											2025-03-30 22:13:40 +00:00
										 |  |  |         metrics.averageExecutionTime = | 
					
						
							| 
									
										
										
										
											2025-03-29 21:31:33 +00:00
										 |  |  |             (metrics.averageExecutionTime * (metrics.totalExecutions - 1) + executionTime) / | 
					
						
							|  |  |  |             metrics.totalExecutions; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * Get the current pipeline metrics | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     getMetrics(): PipelineMetrics { | 
					
						
							|  |  |  |         return this.metrics; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * Reset pipeline metrics | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     resetMetrics(): void { | 
					
						
							|  |  |  |         this.metrics.totalExecutions = 0; | 
					
						
							|  |  |  |         this.metrics.averageExecutionTime = 0; | 
					
						
							| 
									
										
										
										
											2025-03-30 22:13:40 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-03-29 21:31:33 +00:00
										 |  |  |         Object.keys(this.metrics.stageMetrics).forEach(stageName => { | 
					
						
							|  |  |  |             this.metrics.stageMetrics[stageName] = { | 
					
						
							|  |  |  |                 totalExecutions: 0, | 
					
						
							|  |  |  |                 averageExecutionTime: 0 | 
					
						
							|  |  |  |             }; | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } |