@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,68 @@
|
|
1
|
+
const {CONVERSATION_CONTEXT, ROLES} = require("../constants");
|
2
|
+
|
3
|
+
class ConversationHistory {
|
4
|
+
constructor(nodeDB, conversationId) {
|
5
|
+
this.conversationId = conversationId;
|
6
|
+
this.nodeDB = nodeDB;
|
7
|
+
this.conversation = []
|
8
|
+
|
9
|
+
if(!nodeDB){
|
10
|
+
throw new Error("nodeDB does not exist");
|
11
|
+
}else if(conversationId){
|
12
|
+
const allConversations = nodeDB.getValueFromGlobalContext(CONVERSATION_CONTEXT) || {}
|
13
|
+
this.conversation = allConversations[conversationId] || []
|
14
|
+
}
|
15
|
+
}
|
16
|
+
|
17
|
+
addSystemMessage(content){
|
18
|
+
if(!content){
|
19
|
+
return false
|
20
|
+
}
|
21
|
+
|
22
|
+
const entry = {role: ROLES.System, content }
|
23
|
+
|
24
|
+
if(this.conversation[0]?.role === ROLES.System){
|
25
|
+
this.conversation[0] = entry
|
26
|
+
}else{
|
27
|
+
this.conversation = [entry, ...this.conversation]
|
28
|
+
}
|
29
|
+
|
30
|
+
this.saveHistory()
|
31
|
+
}
|
32
|
+
|
33
|
+
addUserMessage(content){
|
34
|
+
if(!content){
|
35
|
+
return false
|
36
|
+
}
|
37
|
+
|
38
|
+
const entry = {role: ROLES.User, content }
|
39
|
+
this.conversation.push(entry)
|
40
|
+
this.saveHistory()
|
41
|
+
}
|
42
|
+
addAssistantMessage(content){
|
43
|
+
if(!content){
|
44
|
+
return false
|
45
|
+
}
|
46
|
+
|
47
|
+
const entry = {role: ROLES.Assistant, content }
|
48
|
+
this.conversation.push(entry)
|
49
|
+
this.saveHistory()
|
50
|
+
}
|
51
|
+
|
52
|
+
clearHistory(){
|
53
|
+
this.conversation = []
|
54
|
+
this.saveHistory()
|
55
|
+
}
|
56
|
+
|
57
|
+
saveHistory(){
|
58
|
+
if(this.conversationId){
|
59
|
+
const allConversations = this.nodeDB.getValueFromGlobalContext(CONVERSATION_CONTEXT) || {}
|
60
|
+
allConversations[this.conversationId] = this.conversation
|
61
|
+
this.nodeDB.setValueToGlobalContext(allConversations, CONVERSATION_CONTEXT)
|
62
|
+
}
|
63
|
+
}
|
64
|
+
}
|
65
|
+
|
66
|
+
module.exports = {
|
67
|
+
ConversationHistory,
|
68
|
+
};
|
@@ -0,0 +1,94 @@
|
|
1
|
+
const Sugar = require("sugar");
|
2
|
+
|
3
|
+
class Format {
|
4
|
+
createConsistentPayload(content){
|
5
|
+
return {
|
6
|
+
args: {
|
7
|
+
response: content
|
8
|
+
},
|
9
|
+
};
|
10
|
+
};
|
11
|
+
|
12
|
+
formatPayloadForLocalAI (msg){
|
13
|
+
const { tool_calls = [], content } = msg;
|
14
|
+
const output = [];
|
15
|
+
const payload = this.createConsistentPayload(content);
|
16
|
+
|
17
|
+
if(tool_calls.length){
|
18
|
+
tool_calls.forEach((answer) => {
|
19
|
+
|
20
|
+
const payload = this.createConsistentPayload(answer.content);
|
21
|
+
|
22
|
+
if (answer.function) {
|
23
|
+
const deepCopyPayload = Sugar.Object.clone(payload, true);
|
24
|
+
|
25
|
+
deepCopyPayload.args = {
|
26
|
+
...answer.function.arguments,
|
27
|
+
};
|
28
|
+
deepCopyPayload.nodeName = answer.function.name;
|
29
|
+
output.push(deepCopyPayload);
|
30
|
+
|
31
|
+
} else {
|
32
|
+
output.push(payload);
|
33
|
+
}
|
34
|
+
});
|
35
|
+
}else{
|
36
|
+
output.push(payload);
|
37
|
+
}
|
38
|
+
|
39
|
+
return output
|
40
|
+
};
|
41
|
+
|
42
|
+
formatPayloadForOpenAI (choices) {
|
43
|
+
const output = [];
|
44
|
+
choices.forEach((answer) => {
|
45
|
+
const { content = "", tool_calls } = answer.message;
|
46
|
+
const payload = this.createConsistentPayload(content);
|
47
|
+
|
48
|
+
if (tool_calls) {
|
49
|
+
tool_calls.forEach((tool) => {
|
50
|
+
const deepCopyPayload = Sugar.Object.clone(payload, true);
|
51
|
+
|
52
|
+
if (tool.type === "function") {
|
53
|
+
deepCopyPayload.args = {
|
54
|
+
...JSON.parse(tool.function.arguments),
|
55
|
+
};
|
56
|
+
deepCopyPayload.nodeName = tool.function.name;
|
57
|
+
output.push(deepCopyPayload);
|
58
|
+
}
|
59
|
+
});
|
60
|
+
} else {
|
61
|
+
output.push(payload);
|
62
|
+
}
|
63
|
+
});
|
64
|
+
|
65
|
+
return output;
|
66
|
+
};
|
67
|
+
|
68
|
+
formatPayloadForGeminiAI (msg) {
|
69
|
+
const output = [];
|
70
|
+
const { functions = [], text } = msg;
|
71
|
+
const payload = this.createConsistentPayload(text);
|
72
|
+
|
73
|
+
if (functions.length > 0) {
|
74
|
+
functions.forEach((tool) => {
|
75
|
+
const { name, args } = tool;
|
76
|
+
output.push({
|
77
|
+
args: {
|
78
|
+
...payload.args,
|
79
|
+
...args,
|
80
|
+
},
|
81
|
+
nodeName: name,
|
82
|
+
});
|
83
|
+
});
|
84
|
+
} else {
|
85
|
+
output.push(payload);
|
86
|
+
}
|
87
|
+
|
88
|
+
return output;
|
89
|
+
};
|
90
|
+
}
|
91
|
+
|
92
|
+
module.exports = {
|
93
|
+
Format,
|
94
|
+
};
|
@@ -0,0 +1,243 @@
|
|
1
|
+
const { TOOL_CHOICE } = require("../constants");
|
2
|
+
const { ChatLedger } = require("./chat-ledger");
|
3
|
+
const { ContextDatabase } = require("../globalUtils");
|
4
|
+
|
5
|
+
class GeminiController {
|
6
|
+
constructor(node, config, msg, RED) {
|
7
|
+
this.msg = msg;
|
8
|
+
this.node = node;
|
9
|
+
this.config = config;
|
10
|
+
this.apiProperties = getChatCompletionProps(msg, config, node);
|
11
|
+
|
12
|
+
const registeredIntentFunctions = this.getRegisteredIntentFunctions(RED);
|
13
|
+
const rawIntents = this.getRawIntents(RED);
|
14
|
+
|
15
|
+
this.tools = [
|
16
|
+
...this.apiProperties.tools,
|
17
|
+
...registeredIntentFunctions,
|
18
|
+
].filter(Boolean);
|
19
|
+
|
20
|
+
this.tools = convertToolsToGeminiCompatibleTools(this.tools);
|
21
|
+
|
22
|
+
const toolProperties = determineToolProperties(
|
23
|
+
rawIntents,
|
24
|
+
this.tools,
|
25
|
+
this.apiProperties.tool_choice
|
26
|
+
);
|
27
|
+
|
28
|
+
this.toolProperties = validateToolProperties(toolProperties, node);
|
29
|
+
this.tools;
|
30
|
+
}
|
31
|
+
|
32
|
+
mergeResponseWithMessage = (payload, request) => {
|
33
|
+
const { user, system, tools, ...rest } = this.msg;
|
34
|
+
const ledger = new ChatLedger(this.config.conversation_id, this.node);
|
35
|
+
const response = {
|
36
|
+
functions: payload.functions,
|
37
|
+
message: { role: "assistant", content: payload.text },
|
38
|
+
};
|
39
|
+
const fullConversation = ledger.addResponseToConversationAndSave(
|
40
|
+
request,
|
41
|
+
response,
|
42
|
+
this.node.type
|
43
|
+
);
|
44
|
+
|
45
|
+
return {
|
46
|
+
...rest,
|
47
|
+
payload: response,
|
48
|
+
_debug: {
|
49
|
+
...request,
|
50
|
+
type: this.node.type,
|
51
|
+
fullConversation,
|
52
|
+
conversation_id: this.config.conversation_id,
|
53
|
+
},
|
54
|
+
};
|
55
|
+
};
|
56
|
+
|
57
|
+
/**
|
58
|
+
* Converts the raw intents into functions that the LLM can use.
|
59
|
+
* @param {*} node
|
60
|
+
* @returns
|
61
|
+
*/
|
62
|
+
getRegisteredIntentFunctions = (RED) => {
|
63
|
+
const intents = this.getRawIntents(RED);
|
64
|
+
return createFunctionsFromContext(intents);
|
65
|
+
};
|
66
|
+
|
67
|
+
/**
|
68
|
+
* This will return all stored Registered Intents throughout the entire system
|
69
|
+
* and Tool Nodes that are attached directly to this flow
|
70
|
+
* This will return:
|
71
|
+
* type RawIntent = {
|
72
|
+
* [node_id]: node // could be Registered Intent or Tool node
|
73
|
+
* }
|
74
|
+
*/
|
75
|
+
getRawIntents = (RED) => {
|
76
|
+
const context = new ContextDatabase(RED);
|
77
|
+
return context.getNodeStore() || {};
|
78
|
+
};
|
79
|
+
}
|
80
|
+
|
81
|
+
/**
|
82
|
+
* If no tools exist, then remove tools and toolChoice from the payload
|
83
|
+
*/
|
84
|
+
const validateToolProperties = (toolProperties, node) => {
|
85
|
+
if (!toolProperties.tools?.functionDeclarations?.length) {
|
86
|
+
const { toolChoice, ...rest } = toolProperties;
|
87
|
+
|
88
|
+
if (toolChoice && toolChoice !== "none") {
|
89
|
+
node.warn(
|
90
|
+
"Removing tools from payload since no tools are available. Flow will continue."
|
91
|
+
);
|
92
|
+
}
|
93
|
+
|
94
|
+
return rest;
|
95
|
+
}
|
96
|
+
return toolProperties;
|
97
|
+
};
|
98
|
+
|
99
|
+
/**
|
100
|
+
* combines various properties from `msg` and `config` to return all the properties needed for OpenAI API request
|
101
|
+
* @param {Record<string,any>} msg
|
102
|
+
* @param {Record<string, any>} config
|
103
|
+
* @returns
|
104
|
+
*/
|
105
|
+
const getChatCompletionProps = (msg, config, node) => {
|
106
|
+
const model = msg.payload?.model || config.model;
|
107
|
+
const temperature = Number(msg.payload?.temperature || config.temperature);
|
108
|
+
const max_tokens = Number(msg.payload?.max_tokens || config.max_tokens);
|
109
|
+
const top_p = Number(msg.payload?.top_p || config.top_p);
|
110
|
+
const top_k = Number(msg.payload?.top_k || config.top_k);
|
111
|
+
const tools = msg?.tools || [];
|
112
|
+
const tool_choice = msg.payload?.tool_choice || config?.tool_choice || "auto";
|
113
|
+
const ledger = new ChatLedger(config.conversation_id, node);
|
114
|
+
const messages = ledger.combineExistingMessages(msg.user, msg.system);
|
115
|
+
const { updated, original } = convertChatToGeminiCompatibleChat(messages);
|
116
|
+
const history = [...updated];
|
117
|
+
const nextMessage = history.pop();
|
118
|
+
const message = nextMessage?.parts[0]?.text || "";
|
119
|
+
|
120
|
+
return {
|
121
|
+
model,
|
122
|
+
temperature,
|
123
|
+
maxOutputTokens: max_tokens,
|
124
|
+
topP: top_p,
|
125
|
+
topK: top_k,
|
126
|
+
messages: original, // contains the full chat in it's original form
|
127
|
+
history, // contains the gemini compatible chat w/o the user's current message (the last message in the array)
|
128
|
+
message,
|
129
|
+
tool_choice,
|
130
|
+
tools,
|
131
|
+
};
|
132
|
+
};
|
133
|
+
|
134
|
+
const convertChatToGeminiCompatibleChat = (messages = []) => {
|
135
|
+
const original = [...messages];
|
136
|
+
const updated = messages.map((message) => {
|
137
|
+
let role = message.role;
|
138
|
+
// Gemini doesn't seem to have a system role. We wil convert it to a user
|
139
|
+
if (role === "system") {
|
140
|
+
role = "user";
|
141
|
+
}
|
142
|
+
|
143
|
+
return { role, parts: [{ text: message.content }] };
|
144
|
+
});
|
145
|
+
return { original, updated };
|
146
|
+
};
|
147
|
+
|
148
|
+
const convertToolsToGeminiCompatibleTools = (tools = []) => {
|
149
|
+
return {
|
150
|
+
functionDeclarations: tools.map((tool) => {
|
151
|
+
return tool.function;
|
152
|
+
}),
|
153
|
+
};
|
154
|
+
};
|
155
|
+
|
156
|
+
/**
|
157
|
+
* converts the registered intents stored in the context into functions that can be used by the LLM.
|
158
|
+
* The registered intent will be ignored if excludeFromOpenAi is set to true.
|
159
|
+
* rawIntents may have tool nodes included so the values need to be filtered by the node type.
|
160
|
+
* rawIntents have the following shape:
|
161
|
+
*
|
162
|
+
* type RawIntents = {
|
163
|
+
* [node_id]: node // node could be Registered Intent or Tool node
|
164
|
+
* }
|
165
|
+
*/
|
166
|
+
const createFunctionsFromContext = (rawIntents = {}) => {
|
167
|
+
return (
|
168
|
+
Object.values(rawIntents)
|
169
|
+
.filter((payload) => {
|
170
|
+
return payload.type === "Register Intent";
|
171
|
+
})
|
172
|
+
.map((payload) => {
|
173
|
+
if (payload.excludeFromOpenAi) {
|
174
|
+
return undefined;
|
175
|
+
}
|
176
|
+
|
177
|
+
return {
|
178
|
+
type: "function",
|
179
|
+
function: {
|
180
|
+
name: payload.name,
|
181
|
+
description: payload.description,
|
182
|
+
parameters: {
|
183
|
+
type: "object",
|
184
|
+
properties: {
|
185
|
+
isRegisteredIntent: { type: "string", enum: ["true"] },
|
186
|
+
response: {
|
187
|
+
type: "string",
|
188
|
+
description: "A friendly response to the given command",
|
189
|
+
},
|
190
|
+
},
|
191
|
+
required: ["isRegisteredIntent", "response"],
|
192
|
+
},
|
193
|
+
},
|
194
|
+
};
|
195
|
+
})
|
196
|
+
.filter(Boolean) || []
|
197
|
+
);
|
198
|
+
};
|
199
|
+
|
200
|
+
/**
|
201
|
+
* Based on the tool_choice the tool properties will be created. This is based off
|
202
|
+
* https://cookbook.openai.com/examples/how_to_call_functions_with_chat_models
|
203
|
+
*
|
204
|
+
* @param {Object} context - contains all the saved nodes that represents functions
|
205
|
+
* @param {*} tools - All the tools to be sent as functions
|
206
|
+
* @param {*} toolChoice - Specifies which tools to use
|
207
|
+
* @returns
|
208
|
+
*/
|
209
|
+
const determineToolProperties = (
|
210
|
+
context = {},
|
211
|
+
tools = [],
|
212
|
+
toolChoice = TOOL_CHOICE.Auto
|
213
|
+
) => {
|
214
|
+
const props = {
|
215
|
+
tools,
|
216
|
+
tool_choice: toolChoice,
|
217
|
+
};
|
218
|
+
if (toolChoice === TOOL_CHOICE.None) {
|
219
|
+
// No tools chosen
|
220
|
+
props.tools = [];
|
221
|
+
return props;
|
222
|
+
} else if (
|
223
|
+
toolChoice === TOOL_CHOICE.Auto ||
|
224
|
+
toolChoice === TOOL_CHOICE.Any
|
225
|
+
) {
|
226
|
+
// set the choice to auto or any
|
227
|
+
return props;
|
228
|
+
} else if (context[toolChoice]?.name) {
|
229
|
+
// A specific tool was chosen
|
230
|
+
props.tool_choice = TOOL_CHOICE.Any;
|
231
|
+
props.allowedFunctionNames = [context[toolChoice].name];
|
232
|
+
return props;
|
233
|
+
}
|
234
|
+
// Something funky happened so we will use auto instead
|
235
|
+
return {
|
236
|
+
tools,
|
237
|
+
tool_choice: TOOL_CHOICE.Auto,
|
238
|
+
};
|
239
|
+
};
|
240
|
+
|
241
|
+
module.exports = {
|
242
|
+
GeminiController,
|
243
|
+
};
|
@@ -0,0 +1,30 @@
|
|
1
|
+
class GlobalContext {
|
2
|
+
constructor(node) {
|
3
|
+
if (!node) {
|
4
|
+
throw new Error(
|
5
|
+
"Fatal Error: Cannot access global context without a node."
|
6
|
+
);
|
7
|
+
}
|
8
|
+
this.node = node;
|
9
|
+
}
|
10
|
+
|
11
|
+
setValueToGlobalContext = (value, key) => {
|
12
|
+
const globalContext = getGlobalContext(this.node);
|
13
|
+
|
14
|
+
globalContext.set(key, value);
|
15
|
+
};
|
16
|
+
|
17
|
+
getValueFromGlobalContext = (key) => {
|
18
|
+
const globalContext = getGlobalContext(this.node);
|
19
|
+
|
20
|
+
return globalContext.get(key);
|
21
|
+
};
|
22
|
+
}
|
23
|
+
|
24
|
+
const getGlobalContext = (node) => {
|
25
|
+
return node.context().global;
|
26
|
+
};
|
27
|
+
|
28
|
+
module.exports = {
|
29
|
+
GlobalContext,
|
30
|
+
};
|
@@ -0,0 +1,74 @@
|
|
1
|
+
const Ajv = require("ajv");
|
2
|
+
const addFormats = require("ajv-formats");
|
3
|
+
|
4
|
+
// Initialize AJV
|
5
|
+
const ajv = new Ajv({ allErrors: true });
|
6
|
+
addFormats(ajv); // Add extra format validations
|
7
|
+
|
8
|
+
/**
|
9
|
+
* Validate the user schema against OpenAI function calling requirements.
|
10
|
+
* @param {object} userSchema - The schema to validate.
|
11
|
+
* @returns {object} { isValid: boolean, errorMsg: string }
|
12
|
+
*/
|
13
|
+
function validateOpenAISchema(userSchema) {
|
14
|
+
// Ensure root schema type is "object"
|
15
|
+
if (userSchema.type !== "object") {
|
16
|
+
return { isValid: false, errorMsg: "OpenAI function calling requires `type: object` at the root level." };
|
17
|
+
}
|
18
|
+
|
19
|
+
// Ensure the schema has a "properties" field and it is an object
|
20
|
+
if (!userSchema.properties || typeof userSchema.properties !== "object") {
|
21
|
+
return { isValid: false, errorMsg: "OpenAI function calling requires a `properties` field with key-value pairs." };
|
22
|
+
}
|
23
|
+
|
24
|
+
// Validate each property inside the schema
|
25
|
+
for (const [key, value] of Object.entries(userSchema.properties)) {
|
26
|
+
if (typeof value !== "object") {
|
27
|
+
return { isValid: false, errorMsg: `Property '${key}' must be an object with type definitions.` };
|
28
|
+
}
|
29
|
+
|
30
|
+
// Check if type is defined and valid
|
31
|
+
const validTypes = ["string", "number", "integer", "boolean", "array", "object"];
|
32
|
+
if (!value.type || !validTypes.includes(value.type)) {
|
33
|
+
return { isValid: false, errorMsg: `Property '${key}' must have a valid type (string, number, integer, boolean, array, object).` };
|
34
|
+
}
|
35
|
+
|
36
|
+
// If `enum` is present, ensure it is an array with at least one item
|
37
|
+
if (value.enum) {
|
38
|
+
if (!Array.isArray(value.enum) || value.enum.length === 0) {
|
39
|
+
return { isValid: false, errorMsg: `Property '${key}' has an invalid "enum". It must be a non-empty array.` };
|
40
|
+
}
|
41
|
+
}
|
42
|
+
}
|
43
|
+
|
44
|
+
// If `required` is present, ensure it is an array of existing properties
|
45
|
+
if (userSchema.required) {
|
46
|
+
if (!Array.isArray(userSchema.required) || !userSchema.required.every((reqKey) => userSchema.properties.hasOwnProperty(reqKey))) {
|
47
|
+
return { isValid: false, errorMsg: `"required" field must be an array of existing property keys.` };
|
48
|
+
}
|
49
|
+
}
|
50
|
+
|
51
|
+
return { isValid: true, errorMsg: "" };
|
52
|
+
}
|
53
|
+
|
54
|
+
// // Example: User-Submitted JSON Schema
|
55
|
+
// const userSchema = {
|
56
|
+
// type: "object",
|
57
|
+
// properties: {
|
58
|
+
// query: { type: "string" },
|
59
|
+
// max_results: { type: "integer", minimum: 1 },
|
60
|
+
// mode: { type: "string", enum: ["fast", "slow", "balanced"] } // Example of an enum
|
61
|
+
// },
|
62
|
+
// required: ["query"],
|
63
|
+
// additionalProperties: false
|
64
|
+
// };
|
65
|
+
//
|
66
|
+
// // Run validation
|
67
|
+
// const result = validateOpenAISchema(userSchema);
|
68
|
+
// console.log(result);
|
69
|
+
|
70
|
+
|
71
|
+
|
72
|
+
module.exports = {
|
73
|
+
validateOpenAISchema
|
74
|
+
}
|