@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,244 @@
|
|
1
|
+
<script type="text/javascript">
|
2
|
+
|
3
|
+
const DYNAMIC_OPTIONS = [
|
4
|
+
{ value: "auto", label: "Automatic" },
|
5
|
+
{ value: "none", label: "None" }
|
6
|
+
]
|
7
|
+
|
8
|
+
const removeDuplicates = (data) => {
|
9
|
+
const intents = {}
|
10
|
+
const tools = []
|
11
|
+
|
12
|
+
data.forEach((intent => {
|
13
|
+
if (intent.type === "OpenAI Tool") {
|
14
|
+
if (!intents[intent.name]) {
|
15
|
+
intents[intent.name] = true
|
16
|
+
tools.push(intent)
|
17
|
+
}
|
18
|
+
} else {
|
19
|
+
tools.push(intent)
|
20
|
+
}
|
21
|
+
}))
|
22
|
+
return tools
|
23
|
+
}
|
24
|
+
$.getJSON('registered-intents', function (data = RED.settings.callIntentRegistry) {
|
25
|
+
const tools = removeDuplicates(data)
|
26
|
+
window.__tools = getToolOptions(tools)
|
27
|
+
initialize()
|
28
|
+
});
|
29
|
+
|
30
|
+
const getToolOptions = (intents = []) => {
|
31
|
+
const options = intents.map(intent => {
|
32
|
+
const suffix = intent.type === "Register Intent" ? " (Registered Intent)" : ""
|
33
|
+
return { value: intent.id, label: `${intent.name}${suffix}`}
|
34
|
+
})
|
35
|
+
|
36
|
+
return [...DYNAMIC_OPTIONS, ...options]
|
37
|
+
}
|
38
|
+
|
39
|
+
RED.nodes.registerType("LocalAI Chat", {
|
40
|
+
category: 'AI Intent',
|
41
|
+
color: 'rgba(255, 0, 119, .75)',
|
42
|
+
icon:"llama.svg",
|
43
|
+
defaults: {
|
44
|
+
name: { value: "" },
|
45
|
+
local_settings: {value: "", type: "localai-configuration", required: true},
|
46
|
+
tool_choice: {
|
47
|
+
value: getToolOptions(RED.settings.callIntentRegistry),
|
48
|
+
},
|
49
|
+
conversation_id: { value: "" },
|
50
|
+
model: { value: "phi3", required: true },
|
51
|
+
temperature: { value: .7, required: true },
|
52
|
+
max_tokens: { value: 1200, required: true },
|
53
|
+
top_p: { value: 1, required: true },
|
54
|
+
frequency_penalty: { value: 0, required: true },
|
55
|
+
presence_penalty: { value: 0, required: true },
|
56
|
+
stream: {value: false},
|
57
|
+
keep_alive: {value: 5, required: true},
|
58
|
+
seed: { value: 42, required: true },
|
59
|
+
json: { value: false }
|
60
|
+
},
|
61
|
+
inputs: 1,
|
62
|
+
outputs: 1,
|
63
|
+
paletteLabel: "Local LLM (Deprecated)",
|
64
|
+
label: function () {
|
65
|
+
return this.name ? `${this.name} (Deprecated)` : "Local LLM (Deprecated)";
|
66
|
+
},
|
67
|
+
|
68
|
+
oneditprepare: function(x){
|
69
|
+
$("#node-input-temperature").typedInput({
|
70
|
+
type: "num",
|
71
|
+
})
|
72
|
+
$("#node-input-max_tokens").typedInput({
|
73
|
+
type: "num",
|
74
|
+
})
|
75
|
+
$("#node-input-top_p").typedInput({
|
76
|
+
type: "num",
|
77
|
+
})
|
78
|
+
$("#node-input-frequency_penalty").typedInput({
|
79
|
+
type: "num",
|
80
|
+
})
|
81
|
+
$("#node-input-presence_penalty").typedInput({
|
82
|
+
type: "num",
|
83
|
+
})
|
84
|
+
|
85
|
+
$("#node-input-keep_alive").typedInput({
|
86
|
+
type: "num",
|
87
|
+
})
|
88
|
+
|
89
|
+
$("#node-input-seed").typedInput({
|
90
|
+
type: "num",
|
91
|
+
})
|
92
|
+
|
93
|
+
$.getJSON('registered-intents', function (data = RED.settings.callIntentRegistry) {
|
94
|
+
const tools = removeDuplicates(data)
|
95
|
+
window.__tools = getToolOptions(tools)
|
96
|
+
|
97
|
+
$("#node-input-tool_choice").typedInput({
|
98
|
+
types: [
|
99
|
+
{
|
100
|
+
value: "",
|
101
|
+
options: window.__tools
|
102
|
+
}
|
103
|
+
]
|
104
|
+
})
|
105
|
+
});
|
106
|
+
|
107
|
+
}
|
108
|
+
});
|
109
|
+
|
110
|
+
</script>
|
111
|
+
|
112
|
+
<script type="text/html" data-template-name="LocalAI Chat">
|
113
|
+
<div class="form-row">
|
114
|
+
<label for="node-input-name"> Name</label>
|
115
|
+
<input type="text" id="node-input-name" placeholder="Name">
|
116
|
+
</div>
|
117
|
+
<div class="form-row">
|
118
|
+
<label for="node-input-local_settings">Local Settings</label>
|
119
|
+
<input type="text" id="node-input-local_settings" placeholder="0a1b2c3b4d5e6f">
|
120
|
+
</div>
|
121
|
+
<div class="form-row">
|
122
|
+
<label for="node-input-conversation_id"><i class="fa fa-tag"></i> Conversation Id</label>
|
123
|
+
<input type="text" id="node-input-conversation_id" placeholder="any arbitrary name">
|
124
|
+
</div>
|
125
|
+
<div class="form-row">
|
126
|
+
<label for="node-input-model"> Model</label>
|
127
|
+
<input type="text" id="node-input-model" placeholder="gpt-3.5-turbo">
|
128
|
+
</div>
|
129
|
+
<div class="form-row">
|
130
|
+
<label for="node-input-tool_choice"> Tool Choice</label>
|
131
|
+
<input type="text" id="node-input-tool_choice" placeholder="Automatic">
|
132
|
+
</div>
|
133
|
+
<div class="form-tips"><b>Tip:</b> For reproducible outputs, set temperature to 0 and seed to a number</div>
|
134
|
+
<div class="form-row">
|
135
|
+
|
136
|
+
<label for="node-input-temperature"> Temperature</label>
|
137
|
+
<input type="number" id="node-input-temperature" placeholder="0.7">
|
138
|
+
</div>
|
139
|
+
<div class="form-row">
|
140
|
+
<label for="node-input-max_tokens"> Max Tokens</label>
|
141
|
+
<input type="number" id="node-input-max_tokens" placeholder="1200">
|
142
|
+
</div>
|
143
|
+
<div class="form-row">
|
144
|
+
<label for="node-input-top_p"> Top P</label>
|
145
|
+
<input type="number" id="node-input-top_p" placeholder="0">
|
146
|
+
</div>
|
147
|
+
<div class="form-row">
|
148
|
+
<label for="node-input-frequency_penalty"> Frequency Penalty</label>
|
149
|
+
<input type="number" id="node-input-frequency_penalty" placeholder="0">
|
150
|
+
</div>
|
151
|
+
<div class="form-row">
|
152
|
+
<label for="node-input-presence_penalty"> Presence Penalty</label>
|
153
|
+
<input type="number" id="node-input-presence_penalty" placeholder="0">
|
154
|
+
</div>
|
155
|
+
|
156
|
+
<div class="form-row">
|
157
|
+
<label for="node-input-seed">Seed</label>
|
158
|
+
<input type="number" id="node-input-seed">
|
159
|
+
</div>
|
160
|
+
|
161
|
+
<div class="form-tips"><b>Tip:</b> Set to -1 to keep cache indefinately. Set to 0 to clear cache immediately</div>
|
162
|
+
<div class="form-row" style="display: flex; align-items: center;">
|
163
|
+
|
164
|
+
<label for="node-input-keep_alive"> Keep Alive</label>
|
165
|
+
<div style="display: flex; align-items: baseline;">
|
166
|
+
<input type="num" id="node-input-keep_alive">
|
167
|
+
<p style="margin-left: 5px">Minutes</p>
|
168
|
+
</div>
|
169
|
+
</div>
|
170
|
+
|
171
|
+
<div class="form-row" style="display: flex; justify-content: center;">
|
172
|
+
<label for="node-input-stream" style="display: flex;">Stream
|
173
|
+
<input type="checkbox" id="node-input-stream" style="margin-left: 1rem">
|
174
|
+
</label>
|
175
|
+
<label for="node-input-json" style="display: flex;">JSON Format
|
176
|
+
<input type="checkbox" id="node-input-json" style="margin-left: 1rem">
|
177
|
+
</label>
|
178
|
+
</div>
|
179
|
+
|
180
|
+
</script>
|
181
|
+
|
182
|
+
<script type="text/html" data-help-name="LocalAI Chat">
|
183
|
+
<p>Calls a local LLM API and returns the response. This was tested using Ollama but it
|
184
|
+
can work with other local systems as long as they have the same interface as Ollama or OpenAI
|
185
|
+
</p>
|
186
|
+
|
187
|
+
<h3>Important</h3>
|
188
|
+
<p>The local setting stores the URL to your locall LLM. If you are using a system other than Ollama,
|
189
|
+
You must either make sure that the interface for the endpoint matches the signature of Ollama/OpenAI or
|
190
|
+
you must write a middleware (Proxy) layer that matches the expected interface.
|
191
|
+
</p>
|
192
|
+
|
193
|
+
|
194
|
+
<h3>Inputs</h3>
|
195
|
+
<p>The local LLMs do not natively support function calling so the Tool Choice option has been removed.
|
196
|
+
</p>
|
197
|
+
<dl class="message-properties">
|
198
|
+
<dt class="optional">Conversation ID
|
199
|
+
<span class="property-type">string</span>
|
200
|
+
</dt>
|
201
|
+
<dd> By including this identifier, AI-Intent will save the conversation in the global context
|
202
|
+
and will pass the entire conversation to the LLM when any Chat node with the same conversation id
|
203
|
+
is triggered. This mean that you can have many Chat nodes in different flows with the same conversation id
|
204
|
+
and AI-Intent will ensure that conversation context is maintained between them. It should be noted that only
|
205
|
+
<b>one</b> System message will be maintained. As a result the flow with the current System node will
|
206
|
+
overwrite any system prompt that was previously saved.
|
207
|
+
</dd>
|
208
|
+
</dl>
|
209
|
+
<dl class="message-properties">
|
210
|
+
<dt class="required">model
|
211
|
+
<span class="property-type">string</span>
|
212
|
+
</dt>
|
213
|
+
<dd> Required field that dictates the model to use when calling Ollama.
|
214
|
+
Ensure that you use the exact same name as the model you downloaded to your local environment</dd>
|
215
|
+
</dl>
|
216
|
+
<dl class="message-properties">
|
217
|
+
<dt class="required">
|
218
|
+
<span class="property-type">Keep Alive</span>
|
219
|
+
</dt>
|
220
|
+
<dd>
|
221
|
+
This property will keep store LLM cache for the specified amount of time (the number represents minutes).
|
222
|
+
This can improve performance speed as the LLM does not need to cold start.
|
223
|
+
Using a negative number will keep the cache alive forever while specifiying 0 will clear the cache immediately.
|
224
|
+
</dd>
|
225
|
+
</dl>
|
226
|
+
|
227
|
+
<dl class="message-properties">
|
228
|
+
<dt class="required">
|
229
|
+
<span class="property-type">Stream</span>
|
230
|
+
</dt>
|
231
|
+
<dd>
|
232
|
+
Will cause the node to send the data in chunks. Instead of waiting for the entire object to arrive.
|
233
|
+
Be careful that this could have negative impact on your system if you try to do too much while
|
234
|
+
the data is streaming. To provide a stable experience and prevent other AI-Intent features from
|
235
|
+
breaking, the streamed output will only be saved at the end of the stream if a <pre>conversation_id</pre>
|
236
|
+
is provided
|
237
|
+
</dd>
|
238
|
+
</dl>
|
239
|
+
|
240
|
+
<h3>Details</h3>
|
241
|
+
<p>At minimum the User node should be used before this node. The OpenAI User node adds necessary
|
242
|
+
information to the msg object to allow the chat to work.</p>
|
243
|
+
|
244
|
+
</script>
|
@@ -0,0 +1,108 @@
|
|
1
|
+
const { TYPES } = require("../constants");
|
2
|
+
const { end } = require("../globalUtils");
|
3
|
+
const Ollama = require("ollama").Ollama;
|
4
|
+
const { ChatController } = require("../utilities/chat-controller");
|
5
|
+
|
6
|
+
module.exports = function (RED) {
|
7
|
+
function LocalAIChatHandlerNode(config) {
|
8
|
+
RED.nodes.createNode(this, config);
|
9
|
+
// Retrieve the config node with URL data
|
10
|
+
const { url } = RED.nodes.getNode(config.local_settings);
|
11
|
+
const node = this;
|
12
|
+
|
13
|
+
async function runFetch(finalProps, controller, send) {
|
14
|
+
const { config, msg } = controller;
|
15
|
+
|
16
|
+
const ollama = new Ollama({ host: url });
|
17
|
+
|
18
|
+
try {
|
19
|
+
const response = await ollama.chat(finalProps);
|
20
|
+
let streamedContent = "";
|
21
|
+
let payload = undefined;
|
22
|
+
|
23
|
+
if (config.stream) {
|
24
|
+
for await (const part of response) {
|
25
|
+
streamedContent += part.message.content;
|
26
|
+
|
27
|
+
const result = { ...part };
|
28
|
+
result.message.content = streamedContent;
|
29
|
+
payload = {
|
30
|
+
...msg,
|
31
|
+
payload: result,
|
32
|
+
_debug: { type: controller.node.type },
|
33
|
+
};
|
34
|
+
|
35
|
+
if (!part.done) {
|
36
|
+
send(payload);
|
37
|
+
}
|
38
|
+
}
|
39
|
+
|
40
|
+
const finalResult = controller.mergeResponseWithMessage(
|
41
|
+
payload.payload,
|
42
|
+
finalProps
|
43
|
+
);
|
44
|
+
|
45
|
+
return send(finalResult);
|
46
|
+
} else {
|
47
|
+
const finalResult = controller.mergeResponseWithMessage(
|
48
|
+
response,
|
49
|
+
finalProps
|
50
|
+
);
|
51
|
+
return send(finalResult);
|
52
|
+
}
|
53
|
+
} catch (err) {
|
54
|
+
console.error(err);
|
55
|
+
throw err;
|
56
|
+
}
|
57
|
+
}
|
58
|
+
|
59
|
+
this.on("input", function (msg, send, done = () => {}) {
|
60
|
+
const controller = new ChatController(node, config, msg, RED);
|
61
|
+
const stream = msg?.payload?.stream ?? config.stream;
|
62
|
+
const seed = Number(msg?.payload?.seed ?? config.seed);
|
63
|
+
const keep_alive = (msg?.payload?.keep_alive ?? config.keep_alive) + "m";
|
64
|
+
const useJSONFormat = msg?.payload?.json ?? config.json;
|
65
|
+
const format = useJSONFormat ? "json" : undefined;
|
66
|
+
send =
|
67
|
+
send ||
|
68
|
+
function () {
|
69
|
+
config.send.apply(node, arguments);
|
70
|
+
};
|
71
|
+
|
72
|
+
if (!url) {
|
73
|
+
return end(
|
74
|
+
done,
|
75
|
+
"LocalAI-Chat node requires the url to the server. Please add openaiAPIKey key-value paire to the functionGlobalContext."
|
76
|
+
);
|
77
|
+
}
|
78
|
+
|
79
|
+
const { apiProperties, toolProperties } = controller;
|
80
|
+
|
81
|
+
const finalProps = {
|
82
|
+
...toolProperties,
|
83
|
+
model: apiProperties.model,
|
84
|
+
messages: apiProperties.messages,
|
85
|
+
options: {
|
86
|
+
temperature: apiProperties.temperature,
|
87
|
+
max_tokens: apiProperties.max_tokens,
|
88
|
+
top_p: apiProperties.top_p,
|
89
|
+
frequency_penalty: apiProperties.frequency_penalty,
|
90
|
+
presence_penalty: apiProperties.presence_penalty,
|
91
|
+
seed,
|
92
|
+
},
|
93
|
+
stream,
|
94
|
+
format,
|
95
|
+
keep_alive,
|
96
|
+
};
|
97
|
+
|
98
|
+
runFetch(finalProps, controller, send)
|
99
|
+
.then(() => {
|
100
|
+
end(done);
|
101
|
+
})
|
102
|
+
.catch((error) => {
|
103
|
+
end(done, error);
|
104
|
+
});
|
105
|
+
});
|
106
|
+
}
|
107
|
+
RED.nodes.registerType(TYPES.LocalAIChat, LocalAIChatHandlerNode);
|
108
|
+
};
|
@@ -0,0 +1,18 @@
|
|
1
|
+
<script type="text/javascript">
|
2
|
+
RED.nodes.registerType('localai-configuration', {
|
3
|
+
category: 'config',
|
4
|
+
defaults: {
|
5
|
+
url: { value: "", required: true }
|
6
|
+
},
|
7
|
+
label: function () {
|
8
|
+
return "Local AI Server Settings"
|
9
|
+
}
|
10
|
+
});
|
11
|
+
</script>
|
12
|
+
|
13
|
+
<script type="text/html" data-template-name="localai-configuration">
|
14
|
+
<div class="form-row">
|
15
|
+
<label for="node-config-input-url"><i class="fa fa-bookmark"></i>LLM Url</label>
|
16
|
+
<input type="text" id="node-config-input-url" placeholder="http://127.0.0.0:11434">
|
17
|
+
</div>
|
18
|
+
</script>
|
@@ -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>
|
@@ -0,0 +1,196 @@
|
|
1
|
+
<script type="text/javascript">
|
2
|
+
(() => {
|
3
|
+
const DYNAMIC_OPTIONS = [
|
4
|
+
{ value: "auto", label: "Automatic" },
|
5
|
+
{ value: "none", label: "None" }
|
6
|
+
]
|
7
|
+
|
8
|
+
const removeDuplicates = (data) => {
|
9
|
+
const intents = {}
|
10
|
+
const tools = []
|
11
|
+
|
12
|
+
data.forEach((intent => {
|
13
|
+
if (intent.type === "OpenAI Tool") {
|
14
|
+
if (!intents[intent.name]) {
|
15
|
+
intents[intent.name] = true
|
16
|
+
tools.push(intent)
|
17
|
+
}
|
18
|
+
} else {
|
19
|
+
tools.push(intent)
|
20
|
+
}
|
21
|
+
}))
|
22
|
+
return tools
|
23
|
+
}
|
24
|
+
$.getJSON('registered-intents', function (data = RED.settings.callIntentRegistry) {
|
25
|
+
const tools = removeDuplicates(data)
|
26
|
+
window.__tools = getToolOptions(tools)
|
27
|
+
initialize()
|
28
|
+
});
|
29
|
+
|
30
|
+
const getToolOptions = (intents = []) => {
|
31
|
+
const options = intents.map(intent => {
|
32
|
+
const suffix = intent.type === "Register Intent" ? " (Registered Intent)" : ""
|
33
|
+
return { value: intent.id, label: `${intent.name}${suffix}`}
|
34
|
+
})
|
35
|
+
|
36
|
+
return [...DYNAMIC_OPTIONS, ...options]
|
37
|
+
}
|
38
|
+
|
39
|
+
RED.nodes.registerType("OpenAI Chat", {
|
40
|
+
category: 'AI Intent',
|
41
|
+
color: 'rgba(255, 0, 119, .75)',
|
42
|
+
icon:"chatgpt-icon.svg",
|
43
|
+
defaults: {
|
44
|
+
name: { value: "" },
|
45
|
+
tool_choice: {
|
46
|
+
value: getToolOptions(RED.settings.callIntentRegistry),
|
47
|
+
},
|
48
|
+
conversation_id: { value: "" },
|
49
|
+
token: {value: "", type: "openai-configuration", required: false},
|
50
|
+
model: { value: "gpt-4-1106-preview", required: true },
|
51
|
+
temperature: { value: .7, required: true },
|
52
|
+
max_tokens: { value: 1200, required: true },
|
53
|
+
top_p: { value: 1, required: true },
|
54
|
+
frequency_penalty: { value: 0, required: true },
|
55
|
+
presence_penalty: { value: 0, required: true },
|
56
|
+
},
|
57
|
+
inputs: 1,
|
58
|
+
outputs: 1,
|
59
|
+
paletteLabel: "OpenAI (Deprecated)",
|
60
|
+
label: function () {
|
61
|
+
return this.name ? `${this.name} (Deprecated)` : "OpenAI (Deprecated)";
|
62
|
+
},
|
63
|
+
|
64
|
+
oneditprepare: function(x){
|
65
|
+
$("#node-input-temperature").typedInput({
|
66
|
+
type: "num",
|
67
|
+
})
|
68
|
+
$("#node-input-max_tokens").typedInput({
|
69
|
+
type: "num",
|
70
|
+
})
|
71
|
+
$("#node-input-top_p").typedInput({
|
72
|
+
type: "num",
|
73
|
+
})
|
74
|
+
$("#node-input-frequency_penalty").typedInput({
|
75
|
+
type: "num",
|
76
|
+
})
|
77
|
+
$("#node-input-presence_penalty").typedInput({
|
78
|
+
type: "num",
|
79
|
+
})
|
80
|
+
|
81
|
+
$.getJSON('registered-intents', function (data = RED.settings.callIntentRegistry) {
|
82
|
+
const tools = removeDuplicates(data)
|
83
|
+
window.__tools = getToolOptions(tools)
|
84
|
+
|
85
|
+
$("#node-input-tool_choice").typedInput({
|
86
|
+
types: [
|
87
|
+
{
|
88
|
+
value: "",
|
89
|
+
options: window.__tools
|
90
|
+
}
|
91
|
+
]
|
92
|
+
})
|
93
|
+
});
|
94
|
+
}
|
95
|
+
});
|
96
|
+
})()
|
97
|
+
|
98
|
+
</script>
|
99
|
+
|
100
|
+
<script type="text/html" data-template-name="OpenAI Chat">
|
101
|
+
<div class="form-row">
|
102
|
+
<label for="node-input-name"> Name</label>
|
103
|
+
<input type="text" id="node-input-name" placeholder="Name">
|
104
|
+
</div>
|
105
|
+
<div class="form-row">
|
106
|
+
<label for="node-input-token"> Token</label>
|
107
|
+
<input type="text" id="node-input-token" placeholder="0a1b2c3b4d5e6f">
|
108
|
+
</div>
|
109
|
+
<div class="form-row">
|
110
|
+
<label for="node-input-conversation_id"><i class="fa fa-tag"></i> Conversation Id</label>
|
111
|
+
<input type="text" id="node-input-conversation_id" placeholder="any arbitrary name">
|
112
|
+
</div>
|
113
|
+
<div class="form-row">
|
114
|
+
<label for="node-input-model"> Model</label>
|
115
|
+
<input type="text" id="node-input-model" placeholder="gpt-3.5-turbo">
|
116
|
+
</div>
|
117
|
+
<div class="form-row">
|
118
|
+
<label for="node-input-tool_choice"> Tool Choice</label>
|
119
|
+
<input type="text" id="node-input-tool_choice" placeholder="Automatic">
|
120
|
+
</div>
|
121
|
+
<div class="form-row">
|
122
|
+
<label for="node-input-temperature"> Temperature</label>
|
123
|
+
<input type="number" id="node-input-temperature" placeholder="0.7">
|
124
|
+
</div>
|
125
|
+
<div class="form-row">
|
126
|
+
<label for="node-input-max_tokens"> Max Tokens</label>
|
127
|
+
<input type="number" id="node-input-max_tokens" placeholder="1200">
|
128
|
+
</div>
|
129
|
+
<div class="form-row">
|
130
|
+
<label for="node-input-top_p"> Top P</label>
|
131
|
+
<input type="number" id="node-input-top_p" placeholder="0">
|
132
|
+
</div>
|
133
|
+
<div class="form-row">
|
134
|
+
<label for="node-input-frequency_penalty"> Frequency Penalty</label>
|
135
|
+
<input type="number" id="node-input-frequency_penalty" placeholder="0">
|
136
|
+
</div>
|
137
|
+
<div class="form-row">
|
138
|
+
<label for="node-input-presence_penalty"> Presence Penalty</label>
|
139
|
+
<input type="number" id="node-input-presence_penalty" placeholder="0">
|
140
|
+
</div>
|
141
|
+
|
142
|
+
</script>
|
143
|
+
|
144
|
+
<script type="text/html" data-help-name="OpenAI Chat">
|
145
|
+
<p>Calls OpenAI and returns the response</p>
|
146
|
+
|
147
|
+
<h3>Important</h3>
|
148
|
+
<p>To use this node you need an API Key from OpenAI. Add the API Key to the settings.js file in the node-red folder under
|
149
|
+
the functonGlobalContext section using the key "openaiAPIKey"</p>
|
150
|
+
<pre>
|
151
|
+
functionGlobalContext: {
|
152
|
+
openaiAPIKey: "Your Key Goes Here",
|
153
|
+
}
|
154
|
+
</pre>
|
155
|
+
|
156
|
+
Alternatively, you can add the token via the token configuration dropdown.
|
157
|
+
See the <a href="https://github.com/montaque22/node-red-contrib-ai-intent" target="_blank">Read Me</a> for more information
|
158
|
+
|
159
|
+
<h3>Inputs</h3>
|
160
|
+
<dl class="message-properties">
|
161
|
+
<dt class="optional">Conversation ID
|
162
|
+
<span class="property-type">string</span>
|
163
|
+
</dt>
|
164
|
+
<dd> By including this identifier, AI-Intent will save the conversation in the global context
|
165
|
+
and will pass the entire conversation to the LLM when any Chat node with the same conversation id
|
166
|
+
is triggered. This mean that you can have many Chat nodes in different flows with the same conversation id
|
167
|
+
and AI-Intent will ensure that conversation context is maintained between them. It should be noted that only
|
168
|
+
<b>one</b> System message will be maintained. As a result the flow with the current System node will
|
169
|
+
overwrite any system prompt that was previously saved.</dd>
|
170
|
+
</dl>
|
171
|
+
<dl class="message-properties">
|
172
|
+
<dt class="required">model
|
173
|
+
<span class="property-type">string</span>
|
174
|
+
</dt>
|
175
|
+
<dd> Required field that dictates the model to use when calling OpenAI </dd>
|
176
|
+
</dl>
|
177
|
+
<dl class="message-properties">
|
178
|
+
<dt class="required">Tool Choice
|
179
|
+
<span class="property-type">Dropdown</span>
|
180
|
+
</dt>
|
181
|
+
<dd> This setting controls whether or not GPT uses the provided functions to respond.
|
182
|
+
If you want to force GPT to use a specific function, selected it from the dropdown.
|
183
|
+
<b>Automatic</b> lets GPT decides what is best (This is the default). <b>None</b> disables the use of functions.
|
184
|
+
to further optimize the API call, AI-Intent will not pass any of the tools
|
185
|
+
to save tokens if "None" is selected. Check documentation for more detail on
|
186
|
+
how tools work: <a href="https://cookbook.openai.com/examples/how_to_call_functions_with_chat_models" target="_blank">Function Calling & Tools</a>
|
187
|
+
</dd>
|
188
|
+
</dl>
|
189
|
+
|
190
|
+
<p>To learn more about the other properties, look at <a target="_blank" href="https://platform.openai.com/docs/api-reference/chat/create">OpenAI's documentation</a></p>
|
191
|
+
|
192
|
+
<h3>Details</h3>
|
193
|
+
<p>At minimum the User node should be used before this node. The OpenAI User node adds necessary
|
194
|
+
information to the msg object to allow the chat to work.</p>
|
195
|
+
|
196
|
+
</script>
|
@@ -0,0 +1,58 @@
|
|
1
|
+
const OpenAI = require("openai");
|
2
|
+
const { OPEN_AI_KEY, TYPES } = require("../constants");
|
3
|
+
const { end } = require("../globalUtils");
|
4
|
+
const { ChatController } = require("../utilities/chat-controller");
|
5
|
+
const { GlobalContext } = require("../utilities/global-context");
|
6
|
+
|
7
|
+
module.exports = function (RED) {
|
8
|
+
function OpenAIChatHandlerNode(config) {
|
9
|
+
RED.nodes.createNode(this, config);
|
10
|
+
// Retrieve the config node with API token data.
|
11
|
+
this.token = RED.nodes.getNode(config.token);
|
12
|
+
const node = this;
|
13
|
+
this.on("input", function (msg, send, done = () => {}) {
|
14
|
+
const controller = new ChatController(node, config, msg, RED);
|
15
|
+
const nodeDB = new GlobalContext(node);
|
16
|
+
const apiKey =
|
17
|
+
node.token?.api || nodeDB.getValueFromGlobalContext(OPEN_AI_KEY);
|
18
|
+
|
19
|
+
send =
|
20
|
+
send ||
|
21
|
+
function () {
|
22
|
+
config.send.apply(node, arguments);
|
23
|
+
};
|
24
|
+
|
25
|
+
if (!apiKey) {
|
26
|
+
return end(
|
27
|
+
done,
|
28
|
+
"Api key missing for OpenAI. Please add openaiAPIKey key-value paire to the functionGlobalContext."
|
29
|
+
);
|
30
|
+
}
|
31
|
+
|
32
|
+
const { apiProperties, toolProperties } = controller;
|
33
|
+
const openai = new OpenAI({ apiKey });
|
34
|
+
const finalProps = {
|
35
|
+
...toolProperties,
|
36
|
+
model: apiProperties.model,
|
37
|
+
messages: apiProperties.messages,
|
38
|
+
temperature: apiProperties.temperature,
|
39
|
+
max_tokens: apiProperties.max_tokens,
|
40
|
+
top_p: apiProperties.top_p,
|
41
|
+
frequency_penalty: apiProperties.frequency_penalty,
|
42
|
+
presence_penalty: apiProperties.presence_penalty,
|
43
|
+
};
|
44
|
+
|
45
|
+
openai.chat.completions
|
46
|
+
.create(finalProps)
|
47
|
+
.then((answer) => {
|
48
|
+
send(controller.mergeResponseWithMessage(answer, finalProps));
|
49
|
+
end(done);
|
50
|
+
})
|
51
|
+
.catch((err) => {
|
52
|
+
end(done, err);
|
53
|
+
});
|
54
|
+
});
|
55
|
+
}
|
56
|
+
|
57
|
+
RED.nodes.registerType(TYPES.OpenAIChat, OpenAIChatHandlerNode);
|
58
|
+
};
|
@@ -0,0 +1,18 @@
|
|
1
|
+
<script type="text/javascript">
|
2
|
+
RED.nodes.registerType('openai-configuration', {
|
3
|
+
category: 'config',
|
4
|
+
defaults: {
|
5
|
+
api: { value: "", required: true }
|
6
|
+
},
|
7
|
+
label: function () {
|
8
|
+
return "OpenAI API Token"
|
9
|
+
}
|
10
|
+
});
|
11
|
+
</script>
|
12
|
+
|
13
|
+
<script type="text/html" data-template-name="openai-configuration">
|
14
|
+
<div class="form-row">
|
15
|
+
<label for="node-config-input-api"><i class="fa fa-bookmark"></i> API Token</label>
|
16
|
+
<input type="text" id="node-config-input-api">
|
17
|
+
</div>
|
18
|
+
</script>
|