2024-10-22 01:46:58 -07:00
import type { BaseChatMessageHistory } from '@langchain/core/chat_history' ;
2024-03-22 04:03:54 -07:00
import type { BaseChatModel } from '@langchain/core/language_models/chat_models' ;
2024-10-22 01:46:58 -07:00
import type { BaseLLM } from '@langchain/core/language_models/llms' ;
2024-03-07 02:36:36 -08:00
import type { BaseMessage } from '@langchain/core/messages' ;
2024-08-07 02:20:17 -07:00
import type { Tool } from '@langchain/core/tools' ;
2024-07-16 23:25:37 -07:00
import type { BaseChatMemory } from 'langchain/memory' ;
2024-10-22 01:46:58 -07:00
import { NodeConnectionType , NodeOperationError , jsonStringify } from 'n8n-workflow' ;
2024-10-28 03:37:23 -07:00
import type {
AiEvent ,
IDataObject ,
IExecuteFunctions ,
ISupplyDataFunctions ,
IWebhookFunctions ,
} from 'n8n-workflow' ;
2024-10-22 01:46:58 -07:00
2024-08-07 02:20:17 -07:00
import { N8nTool } from './N8nTool' ;
2024-07-16 23:25:37 -07:00
function hasMethods < T > ( obj : unknown , . . . methodNames : Array < string | symbol > ) : obj is T {
return methodNames . every (
( methodName ) = >
typeof obj === 'object' &&
obj !== null &&
methodName in obj &&
typeof ( obj as Record < string | symbol , unknown > ) [ methodName ] === 'function' ,
) ;
}
2023-11-29 03:13:55 -08:00
export function getMetadataFiltersValues (
2024-10-28 03:37:23 -07:00
ctx : IExecuteFunctions | ISupplyDataFunctions ,
2023-11-29 03:13:55 -08:00
itemIndex : number ,
) : Record < string , never > | undefined {
2024-07-22 07:15:43 -07:00
const options = ctx . getNodeParameter ( 'options' , itemIndex , { } ) ;
2024-07-04 05:16:35 -07:00
if ( options . metadata ) {
const { metadataValues : metadata } = options . metadata as {
metadataValues : Array < {
name : string ;
value : string ;
} > ;
} ;
if ( metadata . length > 0 ) {
return metadata . reduce ( ( acc , { name , value } ) = > ( { . . . acc , [ name ] : value } ) , { } ) ;
}
}
if ( options . searchFilterJson ) {
return ctx . getNodeParameter ( 'options.searchFilterJson' , itemIndex , '' , {
ensureType : 'object' ,
} ) as Record < string , never > ;
2023-11-29 03:13:55 -08:00
}
return undefined ;
}
2024-02-05 07:09:23 -08:00
2024-07-16 23:25:37 -07:00
export function isBaseChatMemory ( obj : unknown ) {
return hasMethods < BaseChatMemory > ( obj , 'loadMemoryVariables' , 'saveContext' ) ;
}
export function isBaseChatMessageHistory ( obj : unknown ) {
return hasMethods < BaseChatMessageHistory > ( obj , 'getMessages' , 'addMessage' ) ;
}
2024-03-22 04:03:54 -07:00
export function isChatInstance ( model : unknown ) : model is BaseChatModel {
2024-07-16 23:25:37 -07:00
const namespace = ( model as BaseLLM ) ? . lc_namespace ? ? [ ] ;
2024-03-22 04:03:54 -07:00
return namespace . includes ( 'chat_models' ) ;
2024-02-05 07:09:23 -08:00
}
2024-02-21 05:59:37 -08:00
2024-07-11 05:41:10 -07:00
export function isToolsInstance ( model : unknown ) : model is Tool {
const namespace = ( model as Tool ) ? . lc_namespace ? ? [ ] ;
return namespace . includes ( 'tools' ) ;
}
2024-02-21 05:59:37 -08:00
export function getPromptInputByType ( options : {
ctx : IExecuteFunctions ;
i : number ;
promptTypeKey : string ;
inputKey : string ;
} ) {
const { ctx , i , promptTypeKey , inputKey } = options ;
const prompt = ctx . getNodeParameter ( promptTypeKey , i ) as string ;
let input ;
if ( prompt === 'auto' ) {
input = ctx . evaluateExpression ( '{{ $json["chatInput"] }}' , i ) as string ;
} else {
input = ctx . getNodeParameter ( inputKey , i ) as string ;
}
if ( input === undefined ) {
throw new NodeOperationError ( ctx . getNode ( ) , 'No prompt specified' , {
description :
"Expected to find the prompt in an input field called 'chatInput' (this is what the chat trigger node outputs). To use something else, change the 'Prompt' parameter" ,
} ) ;
}
return input ;
}
2024-02-23 01:27:39 -08:00
2024-02-27 05:01:15 -08:00
export function getSessionId (
2024-10-28 03:37:23 -07:00
ctx : ISupplyDataFunctions | IWebhookFunctions ,
2024-02-27 05:01:15 -08:00
itemIndex : number ,
selectorKey = 'sessionIdType' ,
autoSelect = 'fromInput' ,
customKey = 'sessionKey' ,
) {
let sessionId = '' ;
const selectorType = ctx . getNodeParameter ( selectorKey , itemIndex ) as string ;
if ( selectorType === autoSelect ) {
2024-07-09 04:45:41 -07:00
// If memory node is used in webhook like node(like chat trigger node), it doesn't have access to evaluateExpression
// so we try to extract sessionId from the bodyData
if ( 'getBodyData' in ctx ) {
const bodyData = ctx . getBodyData ( ) ? ? { } ;
sessionId = bodyData . sessionId as string ;
} else {
sessionId = ctx . evaluateExpression ( '{{ $json.sessionId }}' , itemIndex ) as string ;
}
2024-02-27 05:01:15 -08:00
if ( sessionId === '' || sessionId === undefined ) {
throw new NodeOperationError ( ctx . getNode ( ) , 'No session ID found' , {
description :
"Expected to find the session ID in an input field called 'sessionId' (this is what the chat trigger node outputs). To use something else, change the 'Session ID' parameter" ,
itemIndex ,
} ) ;
}
} else {
sessionId = ctx . getNodeParameter ( customKey , itemIndex , '' ) as string ;
if ( sessionId === '' || sessionId === undefined ) {
throw new NodeOperationError ( ctx . getNode ( ) , 'Key parameter is empty' , {
description :
2024-12-12 05:01:29 -08:00
"Provide a key to use as session ID in the 'Key' parameter or use the 'Connected Chat Trigger Node' option to use the session ID from your Chat Trigger" ,
2024-02-27 05:01:15 -08:00
itemIndex ,
} ) ;
}
}
return sessionId ;
}
2024-10-28 03:37:23 -07:00
export function logAiEvent (
executeFunctions : IExecuteFunctions | ISupplyDataFunctions ,
2024-09-12 03:02:47 -07:00
event : AiEvent ,
2024-02-23 01:27:39 -08:00
data? : IDataObject ,
) {
try {
2024-10-28 03:37:23 -07:00
executeFunctions . logAiEvent ( event , data ? jsonStringify ( data ) : undefined ) ;
2024-02-23 01:27:39 -08:00
} catch ( error ) {
executeFunctions . logger . debug ( ` Error logging AI event: ${ event } ` ) ;
}
}
2024-02-26 05:35:00 -08:00
2024-02-27 05:01:15 -08:00
export function serializeChatHistory ( chatHistory : BaseMessage [ ] ) : string {
2024-02-26 05:35:00 -08:00
return chatHistory
. map ( ( chatMessage ) = > {
if ( chatMessage . _getType ( ) === 'human' ) {
return ` Human: ${ chatMessage . content } ` ;
} else if ( chatMessage . _getType ( ) === 'ai' ) {
return ` Assistant: ${ chatMessage . content } ` ;
} else {
return ` ${ chatMessage . content } ` ;
}
} )
. join ( '\n' ) ;
}
2024-02-29 03:28:38 -08:00
2024-11-19 08:56:52 -08:00
export function escapeSingleCurlyBrackets ( text? : string ) : string | undefined {
if ( text === undefined ) return undefined ;
let result = text ;
result = result
// First handle triple brackets to avoid interference with double brackets
. replace ( /(?<!{){{{(?!{)/g , '{{{{' )
. replace ( /(?<!})}}}(?!})/g , '}}}}' )
// Then handle single brackets, but only if they're not part of double brackets
// Convert single { to {{ if it's not already part of {{ or {{{
. replace ( /(?<!{){(?!{)/g , '{{' )
// Convert single } to }} if it's not already part of }} or }}}
. replace ( /(?<!})}(?!})/g , '}}' ) ;
return result ;
}
2024-08-07 02:20:17 -07:00
export const getConnectedTools = async (
ctx : IExecuteFunctions ,
enforceUniqueNames : boolean ,
convertStructuredTool : boolean = true ,
2024-11-19 08:56:52 -08:00
escapeCurlyBrackets : boolean = false ,
2024-08-07 02:20:17 -07:00
) = > {
2024-03-07 02:36:36 -08:00
const connectedTools =
( ( await ctx . getInputConnectionData ( NodeConnectionType . AiTool , 0 ) ) as Tool [ ] ) || [ ] ;
2024-02-29 03:28:38 -08:00
2024-03-07 02:36:36 -08:00
if ( ! enforceUniqueNames ) return connectedTools ;
2024-02-29 03:28:38 -08:00
2024-03-07 02:36:36 -08:00
const seenNames = new Set < string > ( ) ;
2024-02-29 03:28:38 -08:00
2024-08-07 02:20:17 -07:00
const finalTools = [ ] ;
2024-03-07 02:36:36 -08:00
for ( const tool of connectedTools ) {
const { name } = tool ;
if ( seenNames . has ( name ) ) {
throw new NodeOperationError (
ctx . getNode ( ) ,
` You have multiple tools with the same name: ' ${ name } ', please rename them to avoid conflicts ` ,
) ;
}
seenNames . add ( name ) ;
2024-08-07 02:20:17 -07:00
2024-11-19 08:56:52 -08:00
if ( escapeCurlyBrackets ) {
tool . description = escapeSingleCurlyBrackets ( tool . description ) ? ? tool . description ;
}
2024-08-07 02:20:17 -07:00
if ( convertStructuredTool && tool instanceof N8nTool ) {
finalTools . push ( tool . asDynamicTool ( ) ) ;
} else {
finalTools . push ( tool ) ;
}
2024-03-07 02:36:36 -08:00
}
2024-02-29 03:28:38 -08:00
2024-08-07 02:20:17 -07:00
return finalTools ;
2024-02-29 03:28:38 -08:00
} ;