@cpwc/node-red-contrib-ai-intent 3.1.0-alpha
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/.eslintrc +49 -0
- package/.idea/modules.xml +8 -0
- package/.idea/node-red-contrib-ai-intent.iml +12 -0
- package/.idea/vcs.xml +6 -0
- package/LICENSE +21 -0
- package/README.md +233 -0
- package/call-intent/icons/promotion-icon.svg +8 -0
- package/call-intent/index.html +114 -0
- package/call-intent/index.js +110 -0
- package/constants.js +31 -0
- package/database.js +9 -0
- package/examples/home-assistant-automation.json +167 -0
- package/examples/llm-chat-node-example.json +208 -0
- package/examples/openai-call-registered-intent-example.json +174 -0
- package/examples/openai-system-node-example.json +178 -0
- package/examples/openai-tool-node-example.json +120 -0
- package/examples/openai-user-node-exampe.json +234 -0
- package/geminiai-chat/geminiai-configuration/index.html +18 -0
- package/geminiai-chat/geminiai-configuration/index.js +7 -0
- package/geminiai-chat/icons/diamond.svg +8 -0
- package/geminiai-chat/icons/gemini-icon.svg +1 -0
- package/geminiai-chat/icons/gemini.svg +8 -0
- package/geminiai-chat/index.html +189 -0
- package/geminiai-chat/index.js +92 -0
- package/globalUtils.js +39 -0
- package/images/call_register_intent.jpeg +0 -0
- package/images/finally.jpg +0 -0
- package/images/set-config-node.gif +0 -0
- package/llm-chat/AzureOpenAIHelper.js +204 -0
- package/llm-chat/ChatGPTHelper.js +197 -0
- package/llm-chat/GeminiHelper.js +260 -0
- package/llm-chat/OllamaHelper.js +196 -0
- package/llm-chat/icons/bot-message-square.svg +1 -0
- package/llm-chat/icons/brain-circuit.svg +1 -0
- package/llm-chat/icons/chatgpt-icon.svg +7 -0
- package/llm-chat/index.html +205 -0
- package/llm-chat/index.js +73 -0
- package/llm-chat/platform-configuration/index.html +136 -0
- package/llm-chat/platform-configuration/index.js +16 -0
- package/localai-chat/icons/gem-icon.svg +1 -0
- package/localai-chat/icons/llama.svg +8 -0
- package/localai-chat/index.html +244 -0
- package/localai-chat/index.js +108 -0
- package/localai-chat/localai-configuration/index.html +18 -0
- package/localai-chat/localai-configuration/index.js +7 -0
- package/openai-chat/icons/chatgpt-icon.svg +7 -0
- package/openai-chat/index.html +196 -0
- package/openai-chat/index.js +58 -0
- package/openai-chat/openai-configuration/index.html +18 -0
- package/openai-chat/openai-configuration/index.js +7 -0
- package/openai-response/index.html +66 -0
- package/openai-response/index.js +154 -0
- package/openai-system/index.html +68 -0
- package/openai-system/index.js +28 -0
- package/openai-tool/index.html +57 -0
- package/openai-tool/index.js +50 -0
- package/openai-user/index.html +76 -0
- package/openai-user/index.js +26 -0
- package/package.json +49 -0
- package/register-intent/icons/register-icon.svg +8 -0
- package/register-intent/index.html +195 -0
- package/register-intent/index.js +72 -0
- package/register-intent/utils.js +10 -0
- package/utilities/chat-controller.js +249 -0
- package/utilities/chat-ledger.js +122 -0
- package/utilities/conversationHistory.js +68 -0
- package/utilities/format.js +94 -0
- package/utilities/gemini-controller.js +243 -0
- package/utilities/global-context.js +30 -0
- package/utilities/validateSchema.js +74 -0
@@ -0,0 +1,197 @@
|
|
1
|
+
const { GlobalContext } = require("../utilities/global-context");
|
2
|
+
const { TOOL_CHOICE} = require("../constants");
|
3
|
+
const OpenAI = require("openai");
|
4
|
+
const {ContextDatabase} = require("../globalUtils");
|
5
|
+
const {ConversationHistory} = require("../utilities/conversationHistory");
|
6
|
+
const {Format} = require("../utilities/format");
|
7
|
+
|
8
|
+
const chatGPTHelper = (props,callback) => {
|
9
|
+
const {node, config, msg, RED} = props
|
10
|
+
const nodeDB = new GlobalContext(node);
|
11
|
+
const {model, credentials} = node.platform
|
12
|
+
const apiKey = credentials.api
|
13
|
+
|
14
|
+
if (!apiKey) {
|
15
|
+
node.status({fill:"red",shape:"dot",text:"Error"});
|
16
|
+
return callback("Api key missing for OpenAI. Please add openaiAPIKey key-value pair to the functionGlobalContext.");
|
17
|
+
}
|
18
|
+
|
19
|
+
const openai = new OpenAI({ apiKey });
|
20
|
+
const {options = {}, system = "", user = ""} = msg?.payload || {}
|
21
|
+
const conversation_id = config.conversation_id;
|
22
|
+
const conversationHistory = new ConversationHistory(nodeDB, conversation_id)
|
23
|
+
|
24
|
+
if(msg.clearChatHistory){
|
25
|
+
conversationHistory.clearHistory()
|
26
|
+
node.warn("Conversation history cleared")
|
27
|
+
}
|
28
|
+
|
29
|
+
if(!user){
|
30
|
+
node.status({fill:"red",shape:"dot",text:"Stopped"});
|
31
|
+
return node.warn("payload.user is empty. Stopping the flow ")
|
32
|
+
}
|
33
|
+
|
34
|
+
conversationHistory.addSystemMessage(system)
|
35
|
+
conversationHistory.addUserMessage(user)
|
36
|
+
|
37
|
+
if(conversation_id) {
|
38
|
+
conversationHistory.saveHistory()
|
39
|
+
}
|
40
|
+
|
41
|
+
const toolProperties = getToolProperties(config, msg.tools, RED)
|
42
|
+
const finalProps = {
|
43
|
+
...options,
|
44
|
+
...toolProperties,
|
45
|
+
model,
|
46
|
+
messages: conversationHistory.conversation
|
47
|
+
};
|
48
|
+
|
49
|
+
openai.chat.completions
|
50
|
+
.create(finalProps)
|
51
|
+
.then((response) => {
|
52
|
+
|
53
|
+
response.choices.forEach(choice => {
|
54
|
+
conversationHistory.addAssistantMessage(choice.message.content)
|
55
|
+
})
|
56
|
+
conversationHistory.saveHistory()
|
57
|
+
|
58
|
+
return createPayload(finalProps, response, msg, conversationHistory.conversation)
|
59
|
+
})
|
60
|
+
.then(msg => {
|
61
|
+
callback(null, msg)
|
62
|
+
})
|
63
|
+
.catch((err) => {
|
64
|
+
callback(err)
|
65
|
+
});
|
66
|
+
}
|
67
|
+
|
68
|
+
const createPayload = (request, response, previousMsg, conversationHistory) => {
|
69
|
+
const format = new Format()
|
70
|
+
const payload = format.formatPayloadForOpenAI(response.choices)
|
71
|
+
|
72
|
+
return {
|
73
|
+
...previousMsg,
|
74
|
+
payload,
|
75
|
+
apiResponse: response,
|
76
|
+
_debug: {
|
77
|
+
...request,
|
78
|
+
messages: conversationHistory
|
79
|
+
},
|
80
|
+
}
|
81
|
+
}
|
82
|
+
|
83
|
+
const getAllTools = (RED) => {
|
84
|
+
const context = new ContextDatabase(RED);
|
85
|
+
const intents = context.getNodeStore() || {};
|
86
|
+
return createFunctionsFromContext(intents)
|
87
|
+
}
|
88
|
+
|
89
|
+
/**
|
90
|
+
* Converts the raw intents into functions that the LLM can use.
|
91
|
+
* @param {*} node
|
92
|
+
* @returns
|
93
|
+
*/
|
94
|
+
getRegisteredIntentFunctions = (RED) => {
|
95
|
+
const intents = getRawIntents(RED);
|
96
|
+
return createFunctionsFromContext(intents);
|
97
|
+
};
|
98
|
+
|
99
|
+
/**
|
100
|
+
* This will return all stored Registered Intents throughout the entire system
|
101
|
+
* and Tool Nodes that are attached directly to this flow
|
102
|
+
* This will return:
|
103
|
+
* type RawIntent = {
|
104
|
+
* [node_id]: node // could be Registered Intent or Tool node
|
105
|
+
* }
|
106
|
+
*/
|
107
|
+
getRawIntents = (RED) => {
|
108
|
+
const context = new ContextDatabase(RED);
|
109
|
+
return context.getNodeStore() || {};
|
110
|
+
};
|
111
|
+
|
112
|
+
/**
|
113
|
+
* converts the registered intents stored in the context into functions that can be used by the LLM.
|
114
|
+
* The registered intent will be ignored if excludeFromOpenAi is set to true.
|
115
|
+
* rawIntents may have tool nodes included so the values need to be filtered by the node type.
|
116
|
+
* rawIntents have the following shape:
|
117
|
+
*
|
118
|
+
* type RawIntents = {
|
119
|
+
* [node_id]: node // node could be Registered Intent or Tool node
|
120
|
+
* }
|
121
|
+
*/
|
122
|
+
const createFunctionsFromContext = (rawIntents = {}) => {
|
123
|
+
return (
|
124
|
+
Object.values(rawIntents)
|
125
|
+
.filter((payload) => {
|
126
|
+
return payload.type === "Register Intent";
|
127
|
+
})
|
128
|
+
.map((payload) => {
|
129
|
+
if (payload.excludeFromOpenAi) {
|
130
|
+
return undefined;
|
131
|
+
}
|
132
|
+
|
133
|
+
const parameters = payload.code?.trim() ?
|
134
|
+
JSON.parse(payload.code) : {type: "object", properties: {}, required: []};
|
135
|
+
|
136
|
+
|
137
|
+
//TODO - Remove after all the old versions are deprecated
|
138
|
+
const {properties = {}, required = []} = parameters
|
139
|
+
required.push("isRegisteredIntent")
|
140
|
+
properties.isRegisteredIntent = { type:"boolean", const: true }
|
141
|
+
|
142
|
+
return {
|
143
|
+
type: "function",
|
144
|
+
function: {
|
145
|
+
name: payload.name,
|
146
|
+
description: payload.description,
|
147
|
+
parameters: {
|
148
|
+
...parameters,
|
149
|
+
properties,
|
150
|
+
required,
|
151
|
+
additionalProperties: false
|
152
|
+
},
|
153
|
+
strict: true
|
154
|
+
},
|
155
|
+
};
|
156
|
+
})
|
157
|
+
.filter(Boolean) || []
|
158
|
+
);
|
159
|
+
};
|
160
|
+
|
161
|
+
|
162
|
+
/**
|
163
|
+
*
|
164
|
+
* @param config
|
165
|
+
* @param deprecatedTools
|
166
|
+
* @returns {{}}
|
167
|
+
*/
|
168
|
+
const getToolProperties = (
|
169
|
+
config,
|
170
|
+
deprecatedTools = [],
|
171
|
+
RED
|
172
|
+
) => {
|
173
|
+
|
174
|
+
const tool_choice = config.tool_choice
|
175
|
+
const tool_string_ids = config.tools;
|
176
|
+
const tool_ids = tool_string_ids.split(",");
|
177
|
+
const toolProperties = {}
|
178
|
+
const allTools = getAllTools(RED)
|
179
|
+
const tools = []
|
180
|
+
|
181
|
+
if(tool_choice !== TOOL_CHOICE.None){
|
182
|
+
[...deprecatedTools, ...allTools].forEach(tool => {
|
183
|
+
if(tool_ids.includes(tool.function.name)){
|
184
|
+
tools.push(tool);
|
185
|
+
}
|
186
|
+
})
|
187
|
+
|
188
|
+
toolProperties.tools = tools
|
189
|
+
toolProperties.tool_choice = tool_choice
|
190
|
+
}
|
191
|
+
|
192
|
+
return toolProperties
|
193
|
+
}
|
194
|
+
|
195
|
+
module.exports = {
|
196
|
+
chatGPTHelper
|
197
|
+
}
|
@@ -0,0 +1,260 @@
|
|
1
|
+
const { GlobalContext } = require("../utilities/global-context");
|
2
|
+
const { TOOL_CHOICE} = require("../constants");
|
3
|
+
const {ContextDatabase, end} = require("../globalUtils");
|
4
|
+
const {ConversationHistory} = require("../utilities/conversationHistory");
|
5
|
+
const {Format} = require("../utilities/format");
|
6
|
+
const { GoogleGenerativeAI } = require("@google/generative-ai");
|
7
|
+
const Sugar = require("sugar");
|
8
|
+
|
9
|
+
const geminiHelper = (props,callback) => {
|
10
|
+
const {node, config, msg, RED} = props
|
11
|
+
const nodeDB = new GlobalContext(node);
|
12
|
+
const {model, credentials} = node.platform
|
13
|
+
const apiKey = credentials.api
|
14
|
+
|
15
|
+
if (!apiKey) {
|
16
|
+
node.status({fill:"red",shape:"dot",text:"Error"});
|
17
|
+
return callback("Api key missing for Gemini. Please update the configuration");
|
18
|
+
}
|
19
|
+
|
20
|
+
const {options , system = "", user = ""} = msg?.payload || {}
|
21
|
+
const conversation_id = config.conversation_id;
|
22
|
+
const conversationHistory = new ConversationHistory(nodeDB, conversation_id)
|
23
|
+
|
24
|
+
if(msg.clearChatHistory){
|
25
|
+
conversationHistory.clearHistory()
|
26
|
+
node.warn("Conversation history cleared")
|
27
|
+
}
|
28
|
+
|
29
|
+
if(!user){
|
30
|
+
node.status({fill:"red",shape:"dot",text:"Stopped"});
|
31
|
+
return node.warn("payload.user is empty. Stopping the flow ")
|
32
|
+
}
|
33
|
+
|
34
|
+
conversationHistory.addSystemMessage(system)
|
35
|
+
conversationHistory.addUserMessage(user)
|
36
|
+
|
37
|
+
if(conversation_id) {
|
38
|
+
conversationHistory.saveHistory()
|
39
|
+
}
|
40
|
+
|
41
|
+
// Access your API key as an environment variable (see "Set up your API key" above)
|
42
|
+
const genAI = new GoogleGenerativeAI(apiKey);
|
43
|
+
const toolProperties = getToolProperties(config, msg.tools, RED)
|
44
|
+
const modelParams = {
|
45
|
+
...toolProperties,
|
46
|
+
model
|
47
|
+
};
|
48
|
+
|
49
|
+
if(options && typeof options === "object"){
|
50
|
+
modelParams.generationConfig = options
|
51
|
+
}
|
52
|
+
|
53
|
+
// The Gemini 1.5 models are versatile and work with most use cases
|
54
|
+
const genModel = genAI.getGenerativeModel(modelParams);
|
55
|
+
const {history,message, updated} = convertChatToGeminiCompatibleChat(conversationHistory.conversation)
|
56
|
+
const chat = genModel.startChat({
|
57
|
+
history,
|
58
|
+
});
|
59
|
+
const finalProps = {
|
60
|
+
...modelParams,
|
61
|
+
messages: updated
|
62
|
+
};
|
63
|
+
chat
|
64
|
+
.sendMessage(message)
|
65
|
+
.then((result) => result.response)
|
66
|
+
.then((response) => {
|
67
|
+
return {
|
68
|
+
functions: response.functionCalls() || [],
|
69
|
+
text: response.text(),
|
70
|
+
};
|
71
|
+
})
|
72
|
+
.then((payload) => {
|
73
|
+
console.log("RESPONSE: ", payload)
|
74
|
+
conversationHistory.addAssistantMessage(payload.text)
|
75
|
+
conversationHistory.saveHistory()
|
76
|
+
|
77
|
+
return createPayload(finalProps, payload, msg)
|
78
|
+
})
|
79
|
+
.then(msg => {
|
80
|
+
callback(null, msg)
|
81
|
+
})
|
82
|
+
.catch((err) => {
|
83
|
+
callback(err)
|
84
|
+
});
|
85
|
+
}
|
86
|
+
|
87
|
+
const createPayload = (request, response, previousMsg) => {
|
88
|
+
const format = new Format()
|
89
|
+
const payload = format.formatPayloadForGeminiAI(response)
|
90
|
+
|
91
|
+
return {
|
92
|
+
...previousMsg,
|
93
|
+
payload,
|
94
|
+
apiResponse: response,
|
95
|
+
_debug: {
|
96
|
+
...request
|
97
|
+
},
|
98
|
+
}
|
99
|
+
}
|
100
|
+
|
101
|
+
const getAllTools = (RED) => {
|
102
|
+
const context = new ContextDatabase(RED);
|
103
|
+
const intents = context.getNodeStore() || {};
|
104
|
+
return createFunctionsFromContext(intents)
|
105
|
+
}
|
106
|
+
|
107
|
+
/**
|
108
|
+
* Converts the raw intents into functions that the LLM can use.
|
109
|
+
* @param {*} node
|
110
|
+
* @returns
|
111
|
+
*/
|
112
|
+
getRegisteredIntentFunctions = (RED) => {
|
113
|
+
const intents = getRawIntents(RED);
|
114
|
+
return createFunctionsFromContext(intents);
|
115
|
+
};
|
116
|
+
|
117
|
+
/**
|
118
|
+
* This will return all stored Registered Intents throughout the entire system
|
119
|
+
* and Tool Nodes that are attached directly to this flow
|
120
|
+
* This will return:
|
121
|
+
* type RawIntent = {
|
122
|
+
* [node_id]: node // could be Registered Intent or Tool node
|
123
|
+
* }
|
124
|
+
*/
|
125
|
+
getRawIntents = (RED) => {
|
126
|
+
const context = new ContextDatabase(RED);
|
127
|
+
return context.getNodeStore() || {};
|
128
|
+
};
|
129
|
+
|
130
|
+
/**
|
131
|
+
* converts the registered intents stored in the context into functions that can be used by the LLM.
|
132
|
+
* The registered intent will be ignored if excludeFromOpenAi is set to true.
|
133
|
+
* rawIntents may have tool nodes included so the values need to be filtered by the node type.
|
134
|
+
* rawIntents have the following shape:
|
135
|
+
*
|
136
|
+
* type RawIntents = {
|
137
|
+
* [node_id]: node // node could be Registered Intent or Tool node
|
138
|
+
* }
|
139
|
+
*/
|
140
|
+
const createFunctionsFromContext = (rawIntents = {}) => {
|
141
|
+
return (
|
142
|
+
Object.values(rawIntents)
|
143
|
+
.filter((payload) => {
|
144
|
+
return payload.type === "Register Intent";
|
145
|
+
})
|
146
|
+
.map((payload) => {
|
147
|
+
if (payload.excludeFromOpenAi) {
|
148
|
+
return undefined;
|
149
|
+
}
|
150
|
+
const parameters = payload.code.trim() ?
|
151
|
+
JSON.parse(payload.code) : {type: "object", properties: {}, required: []};
|
152
|
+
|
153
|
+
|
154
|
+
//TODO - Remove after all the old versions are deprecated
|
155
|
+
const {properties = {}, required = []} = parameters
|
156
|
+
required.push("isRegisteredIntent")
|
157
|
+
properties.isRegisteredIntent = { type: "string", enum: ["true"] }
|
158
|
+
|
159
|
+
return {
|
160
|
+
type: "function",
|
161
|
+
function: {
|
162
|
+
name: payload.name,
|
163
|
+
description: payload.description,
|
164
|
+
parameters: {
|
165
|
+
...parameters,
|
166
|
+
properties,
|
167
|
+
required,
|
168
|
+
}
|
169
|
+
}
|
170
|
+
};
|
171
|
+
})
|
172
|
+
.filter(Boolean) || []
|
173
|
+
);
|
174
|
+
};
|
175
|
+
|
176
|
+
|
177
|
+
const convertChatToGeminiCompatibleChat = (messages = []) => {
|
178
|
+
const original = Sugar.Object.clone(messages, true);
|
179
|
+
const updated = messages.map((message) => {
|
180
|
+
let role = message.role;
|
181
|
+
// Gemini doesn't seem to have a system role. We wil convert it to a user
|
182
|
+
if (role === "system") {
|
183
|
+
role = "user";
|
184
|
+
}
|
185
|
+
|
186
|
+
return { role, parts: [{ text: message.content }] };
|
187
|
+
});
|
188
|
+
|
189
|
+
const history = Sugar.Object.clone(updated, true);
|
190
|
+
const nextMessage = history.pop()
|
191
|
+
const message = nextMessage?.parts[0]?.text || "";
|
192
|
+
|
193
|
+
return {
|
194
|
+
original, // contains the full chat in it's original form
|
195
|
+
updated, // full list of gemini compatible chat
|
196
|
+
message, // the next gemini compatible chat item
|
197
|
+
history // contains the gemini compatible chat w/o the user's current message (the last message in the array)
|
198
|
+
};
|
199
|
+
};
|
200
|
+
|
201
|
+
const convertToolsToGeminiCompatibleTools = (tools = []) => {
|
202
|
+
return {
|
203
|
+
functionDeclarations: tools.map((tool) => {
|
204
|
+
return tool.function;
|
205
|
+
}),
|
206
|
+
};
|
207
|
+
};
|
208
|
+
|
209
|
+
|
210
|
+
/**
|
211
|
+
*
|
212
|
+
* @param config
|
213
|
+
* @param deprecatedTools
|
214
|
+
* @returns {{}}
|
215
|
+
*/
|
216
|
+
const getToolProperties = (
|
217
|
+
config,
|
218
|
+
deprecatedTools = [],
|
219
|
+
RED
|
220
|
+
) => {
|
221
|
+
const mode = convertToolChoiceToGeminiCompatibleChoice(config.tool_choice)
|
222
|
+
const tool_string_ids = config.tools;
|
223
|
+
const allowed_function_names = tool_string_ids.split(",");
|
224
|
+
const toolProperties = {}
|
225
|
+
const allTools = getAllTools(RED)
|
226
|
+
const tools = []
|
227
|
+
const tool_config = {
|
228
|
+
function_calling_config:{
|
229
|
+
mode,
|
230
|
+
allowed_function_names
|
231
|
+
}
|
232
|
+
}
|
233
|
+
|
234
|
+
if(mode !== TOOL_CHOICE.None){
|
235
|
+
[...deprecatedTools, ...allTools].forEach(tool => {
|
236
|
+
if(allowed_function_names.includes(tool.function.name)){
|
237
|
+
tools.push(tool);
|
238
|
+
}
|
239
|
+
})
|
240
|
+
|
241
|
+
toolProperties.tools = convertToolsToGeminiCompatibleTools(tools)
|
242
|
+
toolProperties.tool_config = tool_config
|
243
|
+
}
|
244
|
+
|
245
|
+
return toolProperties
|
246
|
+
}
|
247
|
+
|
248
|
+
function convertToolChoiceToGeminiCompatibleChoice(mode){
|
249
|
+
switch(mode){
|
250
|
+
case TOOL_CHOICE.Required:
|
251
|
+
return TOOL_CHOICE.Any.toUpperCase()
|
252
|
+
default:
|
253
|
+
return mode.toUpperCase()
|
254
|
+
}
|
255
|
+
}
|
256
|
+
|
257
|
+
|
258
|
+
module.exports = {
|
259
|
+
geminiHelper
|
260
|
+
}
|
@@ -0,0 +1,196 @@
|
|
1
|
+
const { GlobalContext } = require("../utilities/global-context");
|
2
|
+
const { TOOL_CHOICE} = require("../constants");
|
3
|
+
const {ContextDatabase} = require("../globalUtils");
|
4
|
+
const {ConversationHistory} = require("../utilities/conversationHistory");
|
5
|
+
const {Format} = require("../utilities/format");
|
6
|
+
const {Ollama} = require("ollama");
|
7
|
+
|
8
|
+
const ollamaHelper = (props,callback) => {
|
9
|
+
const {node, config, msg, RED} = props
|
10
|
+
const nodeDB = new GlobalContext(node);
|
11
|
+
const {url: host, model} = node.platform
|
12
|
+
|
13
|
+
if (!host) {
|
14
|
+
node.status({fill:"red",shape:"dot",text:"Error"});
|
15
|
+
return callback("URL is missing. Please update the config to point to a valid URL for your local llm");
|
16
|
+
}
|
17
|
+
|
18
|
+
const {options = {}, system = "", user = ""} = msg?.payload || {}
|
19
|
+
const conversation_id = config.conversation_id;
|
20
|
+
const conversationHistory = new ConversationHistory(nodeDB, conversation_id)
|
21
|
+
|
22
|
+
if(msg.clearChatHistory){
|
23
|
+
conversationHistory.clearHistory()
|
24
|
+
node.warn("Conversation history cleared")
|
25
|
+
}
|
26
|
+
|
27
|
+
if(!user){
|
28
|
+
node.status({fill:"red",shape:"dot",text:"Stopped"});
|
29
|
+
return node.warn("payload.user is empty. Stopping the flow ")
|
30
|
+
}
|
31
|
+
|
32
|
+
conversationHistory.addSystemMessage(system)
|
33
|
+
conversationHistory.addUserMessage(user)
|
34
|
+
|
35
|
+
if(conversation_id) {
|
36
|
+
conversationHistory.saveHistory()
|
37
|
+
}
|
38
|
+
|
39
|
+
const toolProperties = getToolProperties(config, msg.tools, RED)
|
40
|
+
const finalProps = {
|
41
|
+
...options,
|
42
|
+
...toolProperties,
|
43
|
+
model,
|
44
|
+
messages: conversationHistory.conversation
|
45
|
+
};
|
46
|
+
|
47
|
+
const ollama = new Ollama({ host });
|
48
|
+
|
49
|
+
ollama.chat(finalProps)
|
50
|
+
.then((response) => {
|
51
|
+
console.log("RESPONSE: ", response)
|
52
|
+
|
53
|
+
conversationHistory.addAssistantMessage(response.message.content)
|
54
|
+
conversationHistory.saveHistory()
|
55
|
+
|
56
|
+
return createPayload(finalProps, response, msg, conversationHistory.conversation)
|
57
|
+
})
|
58
|
+
.then(msg => {
|
59
|
+
callback(null, msg)
|
60
|
+
})
|
61
|
+
.catch((err) => {
|
62
|
+
callback(err)
|
63
|
+
});
|
64
|
+
}
|
65
|
+
|
66
|
+
const createPayload = (request, response, previousMsg, conversationHistory) => {
|
67
|
+
const format = new Format()
|
68
|
+
|
69
|
+
const payload = format.formatPayloadForLocalAI(response.message)
|
70
|
+
|
71
|
+
return {
|
72
|
+
...previousMsg,
|
73
|
+
payload,
|
74
|
+
apiResponse: response,
|
75
|
+
_debug: {
|
76
|
+
...request,
|
77
|
+
messages: conversationHistory
|
78
|
+
},
|
79
|
+
}
|
80
|
+
}
|
81
|
+
|
82
|
+
const getAllTools = (RED) => {
|
83
|
+
const context = new ContextDatabase(RED);
|
84
|
+
const intents = context.getNodeStore() || {};
|
85
|
+
return createFunctionsFromContext(intents)
|
86
|
+
}
|
87
|
+
|
88
|
+
/**
|
89
|
+
* Converts the raw intents into functions that the LLM can use.
|
90
|
+
* @param {*} node
|
91
|
+
* @returns
|
92
|
+
*/
|
93
|
+
getRegisteredIntentFunctions = (RED) => {
|
94
|
+
const intents = getRawIntents(RED);
|
95
|
+
return createFunctionsFromContext(intents);
|
96
|
+
};
|
97
|
+
|
98
|
+
/**
|
99
|
+
* This will return all stored Registered Intents throughout the entire system
|
100
|
+
* and Tool Nodes that are attached directly to this flow
|
101
|
+
* This will return:
|
102
|
+
* type RawIntent = {
|
103
|
+
* [node_id]: node // could be Registered Intent or Tool node
|
104
|
+
* }
|
105
|
+
*/
|
106
|
+
getRawIntents = (RED) => {
|
107
|
+
const context = new ContextDatabase(RED);
|
108
|
+
return context.getNodeStore() || {};
|
109
|
+
};
|
110
|
+
|
111
|
+
/**
|
112
|
+
* converts the registered intents stored in the context into functions that can be used by the LLM.
|
113
|
+
* The registered intent will be ignored if excludeFromOpenAi is set to true.
|
114
|
+
* rawIntents may have tool nodes included so the values need to be filtered by the node type.
|
115
|
+
* rawIntents have the following shape:
|
116
|
+
*
|
117
|
+
* type RawIntents = {
|
118
|
+
* [node_id]: node // node could be Registered Intent or Tool node
|
119
|
+
* }
|
120
|
+
*/
|
121
|
+
const createFunctionsFromContext = (rawIntents = {}) => {
|
122
|
+
return (
|
123
|
+
Object.values(rawIntents)
|
124
|
+
.filter((payload) => {
|
125
|
+
return payload.type === "Register Intent";
|
126
|
+
})
|
127
|
+
.map((payload) => {
|
128
|
+
if (payload.excludeFromOpenAi) {
|
129
|
+
return undefined;
|
130
|
+
}
|
131
|
+
const parameters = payload.code.trim() ?
|
132
|
+
JSON.parse(payload.code) : {type: "object", properties: {}, required: []};
|
133
|
+
|
134
|
+
|
135
|
+
//TODO - Remove after all the old versions are deprecated
|
136
|
+
const {properties = {}, required = []} = parameters
|
137
|
+
required.push("isRegisteredIntent")
|
138
|
+
properties.isRegisteredIntent = { type:"boolean", const: true }
|
139
|
+
|
140
|
+
return {
|
141
|
+
type: "function",
|
142
|
+
function: {
|
143
|
+
name: payload.name,
|
144
|
+
description: payload.description,
|
145
|
+
parameters: {
|
146
|
+
...parameters,
|
147
|
+
properties,
|
148
|
+
required,
|
149
|
+
additionalProperties: false
|
150
|
+
},
|
151
|
+
strict: true
|
152
|
+
},
|
153
|
+
};
|
154
|
+
})
|
155
|
+
.filter(Boolean) || []
|
156
|
+
);
|
157
|
+
};
|
158
|
+
|
159
|
+
|
160
|
+
/**
|
161
|
+
*
|
162
|
+
* @param config
|
163
|
+
* @param deprecatedTools
|
164
|
+
* @returns {{}}
|
165
|
+
*/
|
166
|
+
const getToolProperties = (
|
167
|
+
config,
|
168
|
+
deprecatedTools = [],
|
169
|
+
RED
|
170
|
+
) => {
|
171
|
+
|
172
|
+
const tool_choice = config.tool_choice
|
173
|
+
const tool_string_ids = config.tools;
|
174
|
+
const tool_ids = tool_string_ids.split(",");
|
175
|
+
const toolProperties = {}
|
176
|
+
const allTools = getAllTools(RED)
|
177
|
+
const tools = []
|
178
|
+
|
179
|
+
if(tool_choice !== TOOL_CHOICE.None){
|
180
|
+
[...deprecatedTools, ...allTools].forEach(tool => {
|
181
|
+
if(tool_ids.includes(tool.function.name)){
|
182
|
+
tools.push(tool);
|
183
|
+
}
|
184
|
+
})
|
185
|
+
|
186
|
+
toolProperties.tools = tools
|
187
|
+
toolProperties.tool_choice = tool_choice
|
188
|
+
}
|
189
|
+
|
190
|
+
return toolProperties
|
191
|
+
}
|
192
|
+
|
193
|
+
|
194
|
+
module.exports = {
|
195
|
+
ollamaHelper
|
196
|
+
}
|
@@ -0,0 +1 @@
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#ffffff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-bot-message-square"><path d="M12 6V2H8"/><path d="m8 18-4 4V8a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2Z"/><path d="M2 12h2"/><path d="M9 11v2"/><path d="M15 11v2"/><path d="M20 12h2"/></svg>
|
@@ -0,0 +1 @@
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#ffffff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-brain-circuit"><path d="M12 5a3 3 0 1 0-5.997.125 4 4 0 0 0-2.526 5.77 4 4 0 0 0 .556 6.588A4 4 0 1 0 12 18Z"/><path d="M9 13a4.5 4.5 0 0 0 3-4"/><path d="M6.003 5.125A3 3 0 0 0 6.401 6.5"/><path d="M3.477 10.896a4 4 0 0 1 .585-.396"/><path d="M6 18a4 4 0 0 1-1.967-.516"/><path d="M12 13h4"/><path d="M12 18h6a2 2 0 0 1 2 2v1"/><path d="M12 8h8"/><path d="M16 8V5a2 2 0 0 1 2-2"/><circle cx="16" cy="13" r=".5"/><circle cx="18" cy="3" r=".5"/><circle cx="20" cy="21" r=".5"/><circle cx="20" cy="8" r=".5"/></svg>
|
@@ -0,0 +1,7 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
2
|
+
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
3
|
+
<svg width="100%" height="100%" viewBox="0 0 512 512" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
4
|
+
<g>
|
5
|
+
<path d="M423.895,220.444C432.789,193.751 429.726,164.509 415.503,140.229C394.114,102.988 351.116,83.829 309.122,92.845C290.44,71.799 263.6,59.831 235.461,60.002C192.536,59.904 154.45,87.541 141.244,128.383C113.669,134.03 89.866,151.291 75.938,175.755C54.39,212.898 59.302,259.718 88.09,291.569C79.196,318.262 82.259,347.504 96.481,371.784C117.87,409.025 160.868,428.184 202.862,419.168C221.531,440.214 248.384,452.182 276.523,451.999C319.472,452.109 357.571,424.448 370.776,383.569C398.351,377.922 422.154,360.661 436.082,336.197C457.606,299.054 452.681,252.271 423.905,220.42L423.895,220.444ZM276.549,426.383C259.362,426.407 242.714,420.393 229.52,409.38C230.12,409.061 231.162,408.486 231.835,408.069L309.894,362.988C313.888,360.722 316.338,356.471 316.313,351.877L316.313,241.833L349.303,260.882C349.658,261.054 349.891,261.397 349.94,261.789L349.94,352.919C349.892,393.442 317.073,426.297 276.549,426.383ZM118.717,358.97C110.105,344.098 107.006,326.666 109.958,309.748C110.534,310.091 111.551,310.716 112.273,311.132L190.332,356.213C194.289,358.528 199.189,358.528 203.158,356.213L298.453,301.185L298.453,339.283C298.478,339.675 298.294,340.055 297.988,340.3L219.084,385.859C183.938,406.096 139.053,394.067 118.73,358.97L118.717,358.97ZM98.173,188.581C106.748,173.685 120.285,162.292 136.406,156.375C136.406,157.049 136.369,158.237 136.369,159.07L136.369,249.244C136.344,253.826 138.795,258.076 142.776,260.343L238.071,315.359L205.081,334.408C204.75,334.628 204.334,334.665 203.966,334.506L125.05,288.91C89.978,268.599 77.948,223.726 98.161,188.593L98.173,188.581ZM369.222,251.657L273.927,196.629L306.917,177.592C307.248,177.372 307.664,177.335 308.032,177.494L386.948,223.053C422.082,243.352 434.124,288.298 413.825,323.432C405.238,338.304 391.713,349.696 375.604,355.626L375.604,262.757C375.641,258.175 373.203,253.937 369.234,251.658L369.222,251.658L369.222,251.657ZM402.053,202.24C401.477,201.885 400.461,201.272 399.738,200.856L321.679,155.775C317.722,153.46 312.822,153.46 308.853,155.775L213.558,210.803L213.558,172.705C213.534,172.313 213.717,171.933 214.023,171.688L292.927,126.166C328.073,105.892 373.007,117.958 393.269,153.117C401.832,167.964 404.931,185.347 402.028,202.241L402.053,202.241L402.053,202.24ZM195.624,270.143L162.622,251.094C162.267,250.923 162.034,250.58 161.985,250.188L161.985,159.058C162.01,118.485 194.926,85.605 235.499,85.63C252.662,85.63 269.273,91.657 282.467,102.633C281.867,102.951 280.838,103.527 280.152,103.944L202.093,149.025C198.099,151.291 195.649,155.53 195.674,160.124L195.625,270.119L195.625,270.143L195.624,270.143ZM213.546,231.506L255.993,206.993L298.44,231.494L298.44,280.507L255.993,305.008L213.546,280.507L213.546,231.506Z" style="fill:white;fill-rule:nonzero;"/>
|
6
|
+
</g>
|
7
|
+
</svg>
|