From 879a4d40b280b84a940d597dae6221103372209a Mon Sep 17 00:00:00 2001 From: David Durman Date: Tue, 10 Dec 2024 15:50:41 +0100 Subject: [PATCH 1/9] utils.ai.AIAgent,CallTool,CallToolOutput (minor) new components for defining AI agents --- src/appmixer/utils/ai/AIAgent/AIAgent.js | 226 ++++++++++++++++ src/appmixer/utils/ai/AIAgent/component.json | 69 +++++ src/appmixer/utils/ai/CallTool/CallTool.js | 26 ++ src/appmixer/utils/ai/CallTool/component.json | 65 +++++ .../utils/ai/CallToolOutput/CallToolOutput.js | 12 + .../utils/ai/CallToolOutput/component.json | 24 ++ src/appmixer/utils/ai/bundle.json | 5 +- src/appmixer/utils/ai/package-lock.json | 243 ++++++++++++++++++ src/appmixer/utils/ai/package.json | 3 +- 9 files changed, 671 insertions(+), 2 deletions(-) create mode 100644 src/appmixer/utils/ai/AIAgent/AIAgent.js create mode 100644 src/appmixer/utils/ai/AIAgent/component.json create mode 100644 src/appmixer/utils/ai/CallTool/CallTool.js create mode 100644 src/appmixer/utils/ai/CallTool/component.json create mode 100644 src/appmixer/utils/ai/CallToolOutput/CallToolOutput.js create mode 100644 src/appmixer/utils/ai/CallToolOutput/component.json create mode 100644 src/appmixer/utils/ai/package-lock.json diff --git a/src/appmixer/utils/ai/AIAgent/AIAgent.js b/src/appmixer/utils/ai/AIAgent/AIAgent.js new file mode 100644 index 000000000..6718acecd --- /dev/null +++ b/src/appmixer/utils/ai/AIAgent/AIAgent.js @@ -0,0 +1,226 @@ +'use strict'; + +const OpenAI = require('openai'); + +module.exports = { + + start: async function(context) { + + const assistant = await this.createAssistant(context); + return context.stateSet('assistant', assistant); + }, + + createAssistant: async function(context) { + + const flowDescriptor = context.flowDescriptor; + const agentComponentId = context.componentId; + const toolsPort = 'tools'; + + // Create a new assistant with tools defined in the branches connected to my 'tools' output port. + const tools = {}; + let error; + + // Find all components connected to my 'tools' output port. + Object.keys(flowDescriptor).forEach((componentId) => { + const component = flowDescriptor[componentId]; + const sources = component.source; + Object.keys(sources || {}).forEach((inPort) => { + const source = sources[inPort]; + if (source[agentComponentId] && source[agentComponentId].includes(toolsPort)) { + tools[componentId] = component; + // assert(flowDescriptor[componentId].type === 'appmixer.utils.ai.CallTool') + if (component.type !== 'appmixer.utils.ai.CallTool') { + error = `Component ${componentId} is not of type 'ai.CallTool' but ${comopnent.type}. + Every tool chain connected to the '${toolsPort}' port of the AI Agent + must start with 'ai.CallTool' and end with 'ai.CallToolOutput'. + This is where you describe what the tool does and what parameters should the AI model provide to it.`; + } + } + }); + }); + + // Teach the user via logs that they need to use the 'ai.CallTool' component. + if (error) { + throw new context.CancelError(error); + } + + const toolsDefinition = this.getToolsDefinition(tools); + + const instructions = context.properties.instructions || null; + await context.log({ step: 'create-assistant', tools: toolsDefinition, instructions }); + + const apiKey = context.config.apiKey; + + if (!apiKey) { + throw new context.CancelError('Missing \'apiKey\' system setting of the appmixer.utils.ai module pointing to OpenAI. Please provide it in the Connector Configuration section of the Appmixer Backoffice.'); + } + + const client = new OpenAI({ apiKey }); + const assistant = await client.beta.assistants.create({ + model: context.config.AIAgentModel || 'gpt-4o', + instructions, + tools: toolsDefinition + }); + + await context.log({ step: 'created-assistant', assistant }); + return assistant; + }, + + getToolsDefinition: function(tools) { + + // https://platform.openai.com/docs/assistants/tools/function-calling + const toolsDefinition = []; + + Object.keys(tools).forEach((componentId) => { + const component = tools[componentId]; + const toolParameters = { + type: 'object', + properties: {} + }; + component.config.properties.parameters.ADD.forEach((parameter) => { + toolParameters.properties[parameter.name] = { + type: parameter.type, + description: parameter.description + }; + }); + const toolDefinition = { + type: 'function', + function: { + name: componentId, + description: component.config.properties.description, + parameters: toolParameters + } + }; + toolsDefinition.push(toolDefinition); + }); + return toolsDefinition; + }, + + handleRunStatus: async function(context, client, thread, run) { + + await context.log({ step: 'run-status', run }); + + // Check if the run is completed + if (run.status === 'completed') { + let messages = await client.beta.threads.messages.list(thread.id); + await context.log({ step: 'completed-run', run, messages }); + await context.sendJson({ + answer: messages.data[0].content[0].text.value, + prompt: context.messages.in.content.prompt + }, 'out'); + } else if (run.status === 'requires_action') { + await this.handleRequiresAction(context, client, thread, run); + } else { + await context.log({ step: 'unexpected-run-state', run }); + } + }, + + handleRequiresAction: async function(context, client, thread, run) { + + await context.log({ step: 'requires-action', run }); + + // Check if there are tools that require outputs. + if ( + run.required_action && + run.required_action.submit_tool_outputs && + run.required_action.submit_tool_outputs.tool_calls + ) { + // Loop through each tool in the required action section. + const toolOutputs = []; + const toolCalls = []; + for (const toolCall of run.required_action.submit_tool_outputs.tool_calls) { + const componentId = toolCall.function.name; + const args = JSON.parse(toolCall.function.arguments); + toolCalls.push({ componentId, args, toolCallId: toolCall.id }); + + await context.log({ step: 'call-tool', toolCallId: toolCall.id, componentId, args }); + await context.callAppmixer({ + endPoint: `/flows/${context.flowId}/components/${componentId}`, + method: 'POST', + body: args, + qs: { enqueueOnly: true, correlationId: toolCall.id } + }); + } + + // Output of each tool is expected to be stored in the service state + // under the ID of the tool call. This is done in the CallToolOutput component. + // Collect outputs of all the required tool calls. + await context.log({ step: 'collect-tools-output', threadId: thread.id, runId: run.id }); + const outputs = []; + const pollInterval = 1000; + const pollTimeout = 20000; + const pollStart = Date.now(); + while ((outputs.length < toolCalls.length) && (Date.now() - pollStart < pollTimeout)) { + for (const toolCall of toolCalls) { + const output = await context.service.stateGet(toolCall.toolCallId); + if (output) { + outputs.push({ tool_call_id: toolCall.toolCallId, output }); + await context.service.stateUnset(toolCall.toolCallId); + } + } + // Sleep. + await new Promise((resolve) => setTimeout(resolve, pollInterval)); + } + await context.log({ step: 'collected-tools-output', threadId: thread.id, runId: run.id, outputs }); + + // Submit tool outputs to the assistant. + if (outputs && outputs.length) { + await context.log({ step: 'tool-outputs', tools: toolCalls, outputs }); + run = await client.beta.threads.runs.submitToolOutputsAndPoll( + thread.id, + run.id, + { tool_outputs: outputs }, + ); + } else { + await context.log({ step: 'no-tool-outputs', tools: toolCalls }); + } + + // Check status after submitting tool outputs. + return this.handleRunStatus(context, client, thread, run); + } + }, + + receive: async function(context) { + + const { prompt } = context.messages.in.content; + let threadId = context.messages.in.content.threadId || context.messages.in.correlationId; + const apiKey = context.config.apiKey; + const client = new OpenAI({ apiKey }); + const assistant = await context.stateGet('assistant'); + + // Check if a thread with a given ID exists. + let thread; + if (threadId) { + thread = await context.stateGet(threadId); + } + if (!thread) { + await context.log({ step: 'create-thread', assistantId: assistant.id, internalThreadId: threadId }); + thread = await client.beta.threads.create(); + await context.stateSet(threadId, thread); + } else { + await context.log({ step: 'use-thread', assistantId: assistant.id, thread }); + } + + await context.log({ step: 'create-thread-message', assistantId: assistant.id, threadId: thread.id }); + await client.beta.threads.messages.create(thread.id, { + role: 'user', + content: prompt + }); + + await context.log({ step: 'create-thread-run', assistantId: assistant.id, threadId: thread.id }); + const run = await client.beta.threads.runs.createAndPoll(thread.id, { + assistant_id: assistant.id + }); + + await context.log({ step: 'created-thread-run', assistantId: assistant.id, threadId: thread.id, runId: run.id }); + await this.handleRunStatus(context, client, thread, run); + }, + + stop: async function(context) { + + const apiKey = context.config.apiKey; + const client = new OpenAI({ apiKey }); + const assistant = await context.stateGet('assistant'); + await client.beta.assistants.del(assistant.id); + } +}; diff --git a/src/appmixer/utils/ai/AIAgent/component.json b/src/appmixer/utils/ai/AIAgent/component.json new file mode 100644 index 000000000..5ad64d153 --- /dev/null +++ b/src/appmixer/utils/ai/AIAgent/component.json @@ -0,0 +1,69 @@ +{ + "name": "appmixer.utils.ai.AIAgent", + "author": "Appmixer ", + "description": "Build an AI agent responding with contextual answers or performing contextual actions.", + "properties": { + "schema": { + "type": "object", + "properties": { + "instructions": { "type": "string", "maxLength": 256000 } + } + }, + "inspector": { + "inputs": { + "instructions": { + "type": "textarea", + "label": "Instructions", + "index": 1, + "tooltip": "The system instructions that the assistant uses. The maximum length is 256,000 characters. For example 'You are a personal math tutor.'." + } + } + } + }, + "inPorts": [{ + "name": "in", + "schema": { + "type": "object", + "properties": { + "prompt": { "type": "string" }, + "threadId": { "type": "string" } + }, + "required": ["prompt"] + }, + "inspector": { + "inputs": { + "prompt": { + "label": "Prompt", + "type": "textarea", + "index": 1 + }, + "threadId": { + "label": "Thread ID", + "type": "text", + "index": 2, + "tooltip": "By setting a thread ID you can keep the context of the conversation." + } + } + } + }], + "outPorts": [{ + "name": "out", + "options": [{ + "label": "Answer", + "value": "answer", + "schema": { "type": "string" } + }, { + "label": "Prompt", + "value": "prompt", + "schema": { "type": "string" } + }] + }, { + "name": "tools", + "options": [{ + "label": "Prompt", + "value": "prompt", + "schema": { "type": "string" } + }] + }], + "icon": "data:image/png;base64,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" +} diff --git a/src/appmixer/utils/ai/CallTool/CallTool.js b/src/appmixer/utils/ai/CallTool/CallTool.js new file mode 100644 index 000000000..46b2ff572 --- /dev/null +++ b/src/appmixer/utils/ai/CallTool/CallTool.js @@ -0,0 +1,26 @@ +'use strict'; + +module.exports = { + + receive: async function(context) { + + if (context.properties.generateOutputPortOptions) { + return this.getOutputPortOptions(context); + } + + if (context.messages.webhook) { + // Tool chain triggered by AI Agent. + await context.sendJson(context.messages.webhook.content.data, 'out'); + return context.response({}); + } + }, + + getOutputPortOptions(context) { + + const options = []; + context.properties.parameters.ADD.forEach(parameter => { + options.push({ label: parameter.name, value: parameter.name, schema: { type: parameter.type } }); + }); + return context.sendJson(options, 'out'); + } +}; diff --git a/src/appmixer/utils/ai/CallTool/component.json b/src/appmixer/utils/ai/CallTool/component.json new file mode 100644 index 000000000..6ab02749e --- /dev/null +++ b/src/appmixer/utils/ai/CallTool/component.json @@ -0,0 +1,65 @@ +{ + "name": "appmixer.utils.ai.CallTool", + "author": "Appmixer ", + "description": "Call a chain of tools. This has to be the first component connected to the AI Agent tools output port. The tool chain must be ended with CallToolOutput.", + "properties": { + "schema": { + "type": "object", + "properties": { + "generateOutputPortOptions": { "type": "boolean" }, + "description": { "type": "string" }, + "parameters": { "type": "object" } + } + }, + "inspector": { + "inputs": { + "description": { + "type": "textarea", + "label": "Description", + "index": 1 + }, + "parameters": { + "type": "expression", + "levels": ["ADD"], + "label": "Parameters", + "index": 2, + "fields": { + "name": { + "type": "text", + "label": "Name" + }, + "description": { + "type": "textarea", + "label": "Description" + }, + "type": { + "type": "select", + "label": "Type", + "options": [ + { "value": "string", "label": "String" }, + { "value": "number", "label": "Number" }, + { "value": "boolean", "label": "Boolean" } + ] + } + } + } + } + } + }, + "inPorts": [{ + "name": "in" + }], + "outPorts": [{ + "name": "out", + "source": { + "url": "/component/appmixer/utils/ai/CallTool?outPort=out", + "data": { + "properties": { + "generateOutputPortOptions": true, + "parameters": "properties/parameters" + } + } + } + }], + "icon": "data:image/png;base64,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" +} diff --git a/src/appmixer/utils/ai/CallToolOutput/CallToolOutput.js b/src/appmixer/utils/ai/CallToolOutput/CallToolOutput.js new file mode 100644 index 000000000..c92c794da --- /dev/null +++ b/src/appmixer/utils/ai/CallToolOutput/CallToolOutput.js @@ -0,0 +1,12 @@ +'use strict'; + +module.exports = { + + receive: async function(context) { + + await context.log({ step: 'call-tool-output', toolCallId: context.messages.in.correlationId, output: context.messages.in.content.output }); + // The AI agent expects to see the output in the service state under the toolCallId key. + // correlationId is the toolCallId. + return context.service.stateSet(context.messages.in.correlationId, context.messages.in.content.output); + } +}; diff --git a/src/appmixer/utils/ai/CallToolOutput/component.json b/src/appmixer/utils/ai/CallToolOutput/component.json new file mode 100644 index 000000000..f4d582ad9 --- /dev/null +++ b/src/appmixer/utils/ai/CallToolOutput/component.json @@ -0,0 +1,24 @@ +{ + "name": "appmixer.utils.ai.CallToolOutput", + "author": "Appmixer ", + "description": "AI Agent end of tool call chain.", + "inPorts": [{ + "name": "in", + "schema": { + "type": "object", + "properties": { + "output": { "type": "string" } + } + }, + "inspector": { + "inputs": { + "output": { + "type": "textarea", + "label": "Output", + "index": 1 + } + } + } + }], + "icon": "data:image/png;base64,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" +} diff --git a/src/appmixer/utils/ai/bundle.json b/src/appmixer/utils/ai/bundle.json index 107242f8b..4e66c9391 100644 --- a/src/appmixer/utils/ai/bundle.json +++ b/src/appmixer/utils/ai/bundle.json @@ -1,6 +1,6 @@ { "name": "appmixer.utils.ai", - "version": "1.1.1", + "version": "1.2.0", "changelog": { "1.0.0": [ "Initial version." @@ -10,6 +10,9 @@ ], "1.1.1": [ "DescribeImages: images are now required" + ], + "1.2.0": [ + "New components: AIAgent, CallTool and CallToolOutput for defining AI agents." ] } } diff --git a/src/appmixer/utils/ai/package-lock.json b/src/appmixer/utils/ai/package-lock.json new file mode 100644 index 000000000..e944729e6 --- /dev/null +++ b/src/appmixer/utils/ai/package-lock.json @@ -0,0 +1,243 @@ +{ + "name": "appmixer.utils.ai", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "appmixer.utils.ai", + "version": "1.0.0", + "dependencies": { + "form-data": "4.0.0", + "openai": "4.76.0" + } + }, + "node_modules/@types/node": { + "version": "18.19.67", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.67.tgz", + "integrity": "sha512-wI8uHusga+0ZugNp0Ol/3BqQfEcCCNfojtO6Oou9iVNGPTL6QNSdnUdqq85fRgIorLhLMuPIKpsN98QE9Nh+KQ==", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/node-fetch": { + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.12.tgz", + "integrity": "sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA==", + "dependencies": { + "@types/node": "*", + "form-data": "^4.0.0" + } + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/agentkeepalive": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.5.0.tgz", + "integrity": "sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==", + "dependencies": { + "humanize-ms": "^1.2.1" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/form-data-encoder": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.2.tgz", + "integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==" + }, + "node_modules/formdata-node": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz", + "integrity": "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==", + "dependencies": { + "node-domexception": "1.0.0", + "web-streams-polyfill": "4.0.0-beta.3" + }, + "engines": { + "node": ">= 12.20" + } + }, + "node_modules/humanize-ms": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", + "dependencies": { + "ms": "^2.0.0" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/openai": { + "version": "4.76.0", + "resolved": "https://registry.npmjs.org/openai/-/openai-4.76.0.tgz", + "integrity": "sha512-QBGIetjX1C9xDp5XGa/3mPnfKI9BgAe2xHQX6PmO98wuW9qQaurBaumcYptQWc9LHZZq7cH/Y1Rjnsr6uUDdVw==", + "dependencies": { + "@types/node": "^18.11.18", + "@types/node-fetch": "^2.6.4", + "abort-controller": "^3.0.0", + "agentkeepalive": "^4.2.1", + "form-data-encoder": "1.7.2", + "formdata-node": "^4.3.2", + "node-fetch": "^2.6.7" + }, + "bin": { + "openai": "bin/cli" + }, + "peerDependencies": { + "zod": "^3.23.8" + }, + "peerDependenciesMeta": { + "zod": { + "optional": true + } + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" + }, + "node_modules/web-streams-polyfill": { + "version": "4.0.0-beta.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz", + "integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==", + "engines": { + "node": ">= 14" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + } + } +} diff --git a/src/appmixer/utils/ai/package.json b/src/appmixer/utils/ai/package.json index 7916d331a..f62f974b6 100644 --- a/src/appmixer/utils/ai/package.json +++ b/src/appmixer/utils/ai/package.json @@ -2,6 +2,7 @@ "name": "appmixer.utils.ai", "version": "1.0.0", "dependencies": { - "form-data": "4.0.0" + "form-data": "4.0.0", + "openai": "4.76.0" } } From 632f0451d85bcae34e31bcd951a7a78b4440d740 Mon Sep 17 00:00:00 2001 From: David Durman Date: Tue, 10 Dec 2024 15:54:25 +0100 Subject: [PATCH 2/9] fix linting errors --- src/appmixer/utils/ai/AIAgent/AIAgent.js | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/appmixer/utils/ai/AIAgent/AIAgent.js b/src/appmixer/utils/ai/AIAgent/AIAgent.js index 6718acecd..4a1eb5c7c 100644 --- a/src/appmixer/utils/ai/AIAgent/AIAgent.js +++ b/src/appmixer/utils/ai/AIAgent/AIAgent.js @@ -93,7 +93,7 @@ module.exports = { }; toolsDefinition.push(toolDefinition); }); - return toolsDefinition; + return toolsDefinition; }, handleRunStatus: async function(context, client, thread, run) { @@ -114,8 +114,8 @@ module.exports = { await context.log({ step: 'unexpected-run-state', run }); } }, - - handleRequiresAction: async function(context, client, thread, run) { + + handleRequiresAction: async function(context, client, thread, run) { await context.log({ step: 'requires-action', run }); @@ -126,7 +126,6 @@ module.exports = { run.required_action.submit_tool_outputs.tool_calls ) { // Loop through each tool in the required action section. - const toolOutputs = []; const toolCalls = []; for (const toolCall of run.required_action.submit_tool_outputs.tool_calls) { const componentId = toolCall.function.name; @@ -163,18 +162,18 @@ module.exports = { } await context.log({ step: 'collected-tools-output', threadId: thread.id, runId: run.id, outputs }); - // Submit tool outputs to the assistant. + // Submit tool outputs to the assistant. if (outputs && outputs.length) { await context.log({ step: 'tool-outputs', tools: toolCalls, outputs }); run = await client.beta.threads.runs.submitToolOutputsAndPoll( thread.id, run.id, - { tool_outputs: outputs }, + { tool_outputs: outputs } ); } else { await context.log({ step: 'no-tool-outputs', tools: toolCalls }); } - + // Check status after submitting tool outputs. return this.handleRunStatus(context, client, thread, run); } From aabb1ab4cbfc4154bdd2ba5c84fba96f323d4bfd Mon Sep 17 00:00:00 2001 From: David Durman Date: Wed, 11 Dec 2024 09:59:50 +0100 Subject: [PATCH 3/9] GenerateEmbeddings (new) --- .../GenerateEmbeddings/GenerateEmbeddings.js | 56 ++++ .../ai/GenerateEmbeddings/component.json | 61 +++++ src/appmixer/utils/ai/bundle.json | 3 +- src/appmixer/utils/ai/package-lock.json | 243 ------------------ src/appmixer/utils/ai/package.json | 1 + 5 files changed, 120 insertions(+), 244 deletions(-) create mode 100644 src/appmixer/utils/ai/GenerateEmbeddings/GenerateEmbeddings.js create mode 100644 src/appmixer/utils/ai/GenerateEmbeddings/component.json delete mode 100644 src/appmixer/utils/ai/package-lock.json diff --git a/src/appmixer/utils/ai/GenerateEmbeddings/GenerateEmbeddings.js b/src/appmixer/utils/ai/GenerateEmbeddings/GenerateEmbeddings.js new file mode 100644 index 000000000..bcd4793f7 --- /dev/null +++ b/src/appmixer/utils/ai/GenerateEmbeddings/GenerateEmbeddings.js @@ -0,0 +1,56 @@ +'use strict'; + +const { RecursiveCharacterTextSplitter } = require('langchain/text_splitter'); +const OpenAI = require('openai'); + +const BATCH_SIZE = 10; + +module.exports = { + + receive: async function(context) { + + const correlationId = context.messages.in.correlationId; + const { text, chunkSize, chunkOverlap } = context.messages.in.content; + + const chunks = await this.splitText(text, chunkSize, chunkOverlap); + await context.log({ step: 'split-text', message: 'Text succesfully split into chunks.', chunksLength: chunks.length }); + + const apiKey = context.config.apiKey; + if (!apiKey) { + throw new context.CancelError('Missing \'apiKey\' system setting of the appmixer.utils.ai module pointing to OpenAI. Please provide it in the Connector Configuration section of the Appmixer Backoffice.'); + } + const client = new OpenAI({ apiKey }); + + // Process chunks in batches + const embeddings = []; + for (let i = 0; i < chunks.length; i += BATCH_SIZE) { + const batch = chunks.slice(i, i + BATCH_SIZE); + + const response = await client.embeddings.create({ + model: context.config.GenerateEmbeddingsModel || 'text-embedding-ada-002', + input: batch, + encoding_format: 'float' + }); + + // Collect embeddings for the current batch. + response.data.forEach((item, index) => { + embeddings.push({ + id: `${correlationId}:${i}:${index}`, + values: item.embedding, + metadata: { text: batch[index] } + }); + }); + } + return context.sendJson({ embeddings }, 'out'); + }, + + splitText(text, chunkSize, chunkOverlap) { + + const splitter = new RecursiveCharacterTextSplitter({ + chunkSize: typeof chunkSize !== 'undefined' ? chunkSize : 500, + chunkOverlap: typeof chunkOverlap !== 'undefined' ? chunkOverlap : 50 + }); + + return splitter.splitText(text); + } +}; diff --git a/src/appmixer/utils/ai/GenerateEmbeddings/component.json b/src/appmixer/utils/ai/GenerateEmbeddings/component.json new file mode 100644 index 000000000..0902f2c8c --- /dev/null +++ b/src/appmixer/utils/ai/GenerateEmbeddings/component.json @@ -0,0 +1,61 @@ +{ + "name": "appmixer.utils.ai.GenerateEmbeddings", + "author": "Appmixer ", + "description": "Generate embeddings for text data. It automatically splits the text into chunks and get embedding for each chunk.", + "inPorts": [{ + "name": "in", + "schema": { + "type": "object", + "properties": { + "text": { "type": "string" }, + "chunkSize": { "type": "integer" }, + "chunkOverlap": { "type": "integer" } + } + }, + "inspector": { + "inputs": { + "text": { + "type": "textarea", + "label": "Text", + "index": 1 + }, + "chunkSize": { + "type": "number", + "label": "Chunk Size", + "tooltip": "Maximum size of each chunk for text splitting. The default is 500.", + "index": 2 + }, + "chunkOverlap": { + "type": "number", + "label": "Chunk Overlap", + "tooltip": "Overlap between chunks for text splitting to maintain context. The default is 50.", + "index": 3 + } + } + } + }], + "outPorts": [{ + "name": "out", + "options": [{ + "label": "Embeddings", + "value": "embeddings", + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { "type": "string" }, + "values": { "type": "array", "items": { "type": "number" } }, + "metadata": { + "type": "object", + "properties": { + "text": { "type": "string" } + } + } + } + } + } + }] + }], + "icon": "data:image/png;base64,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" +} diff --git a/src/appmixer/utils/ai/bundle.json b/src/appmixer/utils/ai/bundle.json index 4e66c9391..b171d38ff 100644 --- a/src/appmixer/utils/ai/bundle.json +++ b/src/appmixer/utils/ai/bundle.json @@ -12,7 +12,8 @@ "DescribeImages: images are now required" ], "1.2.0": [ - "New components: AIAgent, CallTool and CallToolOutput for defining AI agents." + "New components: AIAgent, CallTool and CallToolOutput for defining AI agents.", + "New component: GenerateEmbeddings to generate embeddings from text." ] } } diff --git a/src/appmixer/utils/ai/package-lock.json b/src/appmixer/utils/ai/package-lock.json deleted file mode 100644 index e944729e6..000000000 --- a/src/appmixer/utils/ai/package-lock.json +++ /dev/null @@ -1,243 +0,0 @@ -{ - "name": "appmixer.utils.ai", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "appmixer.utils.ai", - "version": "1.0.0", - "dependencies": { - "form-data": "4.0.0", - "openai": "4.76.0" - } - }, - "node_modules/@types/node": { - "version": "18.19.67", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.67.tgz", - "integrity": "sha512-wI8uHusga+0ZugNp0Ol/3BqQfEcCCNfojtO6Oou9iVNGPTL6QNSdnUdqq85fRgIorLhLMuPIKpsN98QE9Nh+KQ==", - "dependencies": { - "undici-types": "~5.26.4" - } - }, - "node_modules/@types/node-fetch": { - "version": "2.6.12", - "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.12.tgz", - "integrity": "sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA==", - "dependencies": { - "@types/node": "*", - "form-data": "^4.0.0" - } - }, - "node_modules/abort-controller": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", - "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", - "dependencies": { - "event-target-shim": "^5.0.0" - }, - "engines": { - "node": ">=6.5" - } - }, - "node_modules/agentkeepalive": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.5.0.tgz", - "integrity": "sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==", - "dependencies": { - "humanize-ms": "^1.2.1" - }, - "engines": { - "node": ">= 8.0.0" - } - }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/event-target-shim": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", - "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", - "engines": { - "node": ">=6" - } - }, - "node_modules/form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/form-data-encoder": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.2.tgz", - "integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==" - }, - "node_modules/formdata-node": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz", - "integrity": "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==", - "dependencies": { - "node-domexception": "1.0.0", - "web-streams-polyfill": "4.0.0-beta.3" - }, - "engines": { - "node": ">= 12.20" - } - }, - "node_modules/humanize-ms": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", - "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", - "dependencies": { - "ms": "^2.0.0" - } - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - }, - "node_modules/node-domexception": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", - "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/jimmywarting" - }, - { - "type": "github", - "url": "https://paypal.me/jimmywarting" - } - ], - "engines": { - "node": ">=10.5.0" - } - }, - "node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, - "node_modules/openai": { - "version": "4.76.0", - "resolved": "https://registry.npmjs.org/openai/-/openai-4.76.0.tgz", - "integrity": "sha512-QBGIetjX1C9xDp5XGa/3mPnfKI9BgAe2xHQX6PmO98wuW9qQaurBaumcYptQWc9LHZZq7cH/Y1Rjnsr6uUDdVw==", - "dependencies": { - "@types/node": "^18.11.18", - "@types/node-fetch": "^2.6.4", - "abort-controller": "^3.0.0", - "agentkeepalive": "^4.2.1", - "form-data-encoder": "1.7.2", - "formdata-node": "^4.3.2", - "node-fetch": "^2.6.7" - }, - "bin": { - "openai": "bin/cli" - }, - "peerDependencies": { - "zod": "^3.23.8" - }, - "peerDependenciesMeta": { - "zod": { - "optional": true - } - } - }, - "node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" - }, - "node_modules/undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" - }, - "node_modules/web-streams-polyfill": { - "version": "4.0.0-beta.3", - "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz", - "integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==", - "engines": { - "node": ">= 14" - } - }, - "node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" - }, - "node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - } - } -} diff --git a/src/appmixer/utils/ai/package.json b/src/appmixer/utils/ai/package.json index f62f974b6..2d642fbbb 100644 --- a/src/appmixer/utils/ai/package.json +++ b/src/appmixer/utils/ai/package.json @@ -3,6 +3,7 @@ "version": "1.0.0", "dependencies": { "form-data": "4.0.0", + "langchain": "0.3.6", "openai": "4.76.0" } } From bf80d6e7a8a131c8282246b867fe137c9ae6d093 Mon Sep 17 00:00:00 2001 From: David Durman Date: Wed, 11 Dec 2024 12:49:43 +0100 Subject: [PATCH 4/9] minor robust check --- src/appmixer/utils/ai/AIAgent/AIAgent.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/appmixer/utils/ai/AIAgent/AIAgent.js b/src/appmixer/utils/ai/AIAgent/AIAgent.js index 4a1eb5c7c..951c78260 100644 --- a/src/appmixer/utils/ai/AIAgent/AIAgent.js +++ b/src/appmixer/utils/ai/AIAgent/AIAgent.js @@ -73,11 +73,12 @@ module.exports = { Object.keys(tools).forEach((componentId) => { const component = tools[componentId]; + const parameters = component.config.properties.parameters?.ADD || []; const toolParameters = { type: 'object', properties: {} }; - component.config.properties.parameters.ADD.forEach((parameter) => { + parameters.forEach((parameter) => { toolParameters.properties[parameter.name] = { type: parameter.type, description: parameter.description @@ -88,9 +89,11 @@ module.exports = { function: { name: componentId, description: component.config.properties.description, - parameters: toolParameters } }; + if (parameters.length) { + toolDefinition.function.parameters = toolParameters; + } toolsDefinition.push(toolDefinition); }); return toolsDefinition; @@ -133,6 +136,7 @@ module.exports = { toolCalls.push({ componentId, args, toolCallId: toolCall.id }); await context.log({ step: 'call-tool', toolCallId: toolCall.id, componentId, args }); + // Trigger the CallTool component. await context.callAppmixer({ endPoint: `/flows/${context.flowId}/components/${componentId}`, method: 'POST', From 81a6cf40fa85b71a4c213e74eecb1bd19fd398a1 Mon Sep 17 00:00:00 2001 From: David Durman Date: Wed, 11 Dec 2024 20:09:21 +0100 Subject: [PATCH 5/9] fix AIAgent, CallTool and CallToolOutput --- src/appmixer/utils/ai/AIAgent/AIAgent.js | 47 ++++++++++--------- src/appmixer/utils/ai/CallTool/CallTool.js | 19 +++++--- .../utils/ai/CallToolOutput/CallToolOutput.js | 25 ++++++++-- 3 files changed, 57 insertions(+), 34 deletions(-) diff --git a/src/appmixer/utils/ai/AIAgent/AIAgent.js b/src/appmixer/utils/ai/AIAgent/AIAgent.js index 951c78260..d20c6ea51 100644 --- a/src/appmixer/utils/ai/AIAgent/AIAgent.js +++ b/src/appmixer/utils/ai/AIAgent/AIAgent.js @@ -1,7 +1,12 @@ 'use strict'; +const { max } = require('lodash'); const OpenAI = require('openai'); +const COLLECT_TOOL_OUTPUTS_POLL_TIMEOUT = 60 * 1000; // 60 seconds +const COLLECT_TOOL_OUTPUTS_POLL_INTERVAL = 1 * 1000; // 1 second +const MAX_RUN_DURATION = 5 * 60 * 1000; // 5 minutes + module.exports = { start: async function(context) { @@ -101,8 +106,13 @@ module.exports = { handleRunStatus: async function(context, client, thread, run) { - await context.log({ step: 'run-status', run }); + if (Date.now() - (run.created_at * 1000) > MAX_RUN_DURATION) { + await context.log({ step: 'run-timeout', run }); + await client.beta.threads.runs.cancel(thread.id, run.id); + throw new context.CancelError('The run took too long to complete.'); + } + await context.log({ step: 'run-status', run }); // Check if the run is completed if (run.status === 'completed') { let messages = await client.beta.threads.messages.list(thread.id); @@ -128,41 +138,32 @@ module.exports = { run.required_action.submit_tool_outputs && run.required_action.submit_tool_outputs.tool_calls ) { - // Loop through each tool in the required action section. const toolCalls = []; for (const toolCall of run.required_action.submit_tool_outputs.tool_calls) { const componentId = toolCall.function.name; const args = JSON.parse(toolCall.function.arguments); - toolCalls.push({ componentId, args, toolCallId: toolCall.id }); - - await context.log({ step: 'call-tool', toolCallId: toolCall.id, componentId, args }); - // Trigger the CallTool component. - await context.callAppmixer({ - endPoint: `/flows/${context.flowId}/components/${componentId}`, - method: 'POST', - body: args, - qs: { enqueueOnly: true, correlationId: toolCall.id } - }); + toolCalls.push({ componentId, args, id: toolCall.id }); } + // Send to all tools. Each ai.CallTool ignores tool calls that are not intended for it. + await context.sendJson({ toolCalls, prompt: context.messages.in.content.prompt }, 'tools'); + // Output of each tool is expected to be stored in the service state // under the ID of the tool call. This is done in the CallToolOutput component. // Collect outputs of all the required tool calls. await context.log({ step: 'collect-tools-output', threadId: thread.id, runId: run.id }); const outputs = []; - const pollInterval = 1000; - const pollTimeout = 20000; const pollStart = Date.now(); - while ((outputs.length < toolCalls.length) && (Date.now() - pollStart < pollTimeout)) { + while ((outputs.length < toolCalls.length) && (Date.now() - pollStart < COLLECT_TOOL_OUTPUTS_POLL_TIMEOUT)) { for (const toolCall of toolCalls) { - const output = await context.service.stateGet(toolCall.toolCallId); - if (output) { - outputs.push({ tool_call_id: toolCall.toolCallId, output }); - await context.service.stateUnset(toolCall.toolCallId); + const result = await context.flow.stateGet(toolCall.id); + if (result) { + outputs.push({ tool_call_id: toolCall.id, output: result.output }); + await context.flow.stateUnset(toolCall.id); } } // Sleep. - await new Promise((resolve) => setTimeout(resolve, pollInterval)); + await new Promise((resolve) => setTimeout(resolve, COLLECT_TOOL_OUTPUTS_POLL_INTERVAL)); } await context.log({ step: 'collected-tools-output', threadId: thread.id, runId: run.id, outputs }); @@ -174,12 +175,12 @@ module.exports = { run.id, { tool_outputs: outputs } ); + // Check status after submitting tool outputs. + await this.handleRunStatus(context, client, thread, run); + } else { await context.log({ step: 'no-tool-outputs', tools: toolCalls }); } - - // Check status after submitting tool outputs. - return this.handleRunStatus(context, client, thread, run); } }, diff --git a/src/appmixer/utils/ai/CallTool/CallTool.js b/src/appmixer/utils/ai/CallTool/CallTool.js index 46b2ff572..15fe36112 100644 --- a/src/appmixer/utils/ai/CallTool/CallTool.js +++ b/src/appmixer/utils/ai/CallTool/CallTool.js @@ -7,20 +7,25 @@ module.exports = { if (context.properties.generateOutputPortOptions) { return this.getOutputPortOptions(context); } - - if (context.messages.webhook) { - // Tool chain triggered by AI Agent. - await context.sendJson(context.messages.webhook.content.data, 'out'); - return context.response({}); + const { toolCalls } = context.messages.in.originalContent; + for (const toolCall of toolCalls) { + // Process only those tool calls that are for this component. + // This is because the AI Agent fans out all tool calls by using sendJson(..., 'tools'). + if (toolCall.componentId === context.componentId) { + const out = { args: toolCall.args, toolCallId: toolCall.id }; + await context.sendJson(out, 'out'); + } } }, getOutputPortOptions(context) { const options = []; - context.properties.parameters.ADD.forEach(parameter => { - options.push({ label: parameter.name, value: parameter.name, schema: { type: parameter.type } }); + const parameters = context.properties.parameters?.ADD || []; + parameters.forEach(parameter => { + options.push({ label: parameter.name, value: 'args.' + parameter.name, schema: { type: parameter.type } }); }); + options.push({ label: 'Tool Call ID', value: 'toolCallId', schema: { type: 'string' } }); return context.sendJson(options, 'out'); } }; diff --git a/src/appmixer/utils/ai/CallToolOutput/CallToolOutput.js b/src/appmixer/utils/ai/CallToolOutput/CallToolOutput.js index c92c794da..b363d0635 100644 --- a/src/appmixer/utils/ai/CallToolOutput/CallToolOutput.js +++ b/src/appmixer/utils/ai/CallToolOutput/CallToolOutput.js @@ -4,9 +4,26 @@ module.exports = { receive: async function(context) { - await context.log({ step: 'call-tool-output', toolCallId: context.messages.in.correlationId, output: context.messages.in.content.output }); - // The AI agent expects to see the output in the service state under the toolCallId key. - // correlationId is the toolCallId. - return context.service.stateSet(context.messages.in.correlationId, context.messages.in.content.output); + // Find the toolCallId in the message scope, looking into the first + // component with type 'appmixer.utils.ai.CallTool' and taking + // toolCallId from the output message. + const flowDescriptor = context.flowDescriptor; + const scope = context.messages.in.scope; + let toolCallId; + Object.keys(scope).forEach(componentId => { + const callToolOutputMessage = scope[componentId]; + const component = flowDescriptor[componentId]; + if (component && component.type === 'appmixer.utils.ai.CallTool') { + toolCallId = callToolOutputMessage.out?.toolCallId; + } + }); + + if (!toolCallId) { + throw new context.CancelError('No toolCallId found in the scope. Are you sure you used ai.CallTool to start your tool chain?'); + } + + await context.log({ step: 'call-tool-output', toolCallId, output: context.messages.in.content.output }); + // The AI agent expects to see the output in the flow state under the toolCallId key. + return context.flow.stateSet(toolCallId, { output: context.messages.in.content.output }); } }; From 4ae5649a8258b22d5ba0a4f77baa641446d4352e Mon Sep 17 00:00:00 2001 From: David Durman Date: Thu, 12 Dec 2024 14:14:13 +0100 Subject: [PATCH 6/9] store toolCallId in flow context instead of extracting it from message scope --- src/appmixer/utils/ai/CallTool/CallTool.js | 1 + .../utils/ai/CallToolOutput/CallToolOutput.js | 12 +++++++----- .../ai/GenerateEmbeddings/GenerateEmbeddings.js | 4 ++-- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/appmixer/utils/ai/CallTool/CallTool.js b/src/appmixer/utils/ai/CallTool/CallTool.js index 15fe36112..45fc7668a 100644 --- a/src/appmixer/utils/ai/CallTool/CallTool.js +++ b/src/appmixer/utils/ai/CallTool/CallTool.js @@ -13,6 +13,7 @@ module.exports = { // This is because the AI Agent fans out all tool calls by using sendJson(..., 'tools'). if (toolCall.componentId === context.componentId) { const out = { args: toolCall.args, toolCallId: toolCall.id }; + await context.flow.stateSet(context.componentId + ':' + context.messages.in.correlationId, toolCall.id); await context.sendJson(out, 'out'); } } diff --git a/src/appmixer/utils/ai/CallToolOutput/CallToolOutput.js b/src/appmixer/utils/ai/CallToolOutput/CallToolOutput.js index b363d0635..897423d2f 100644 --- a/src/appmixer/utils/ai/CallToolOutput/CallToolOutput.js +++ b/src/appmixer/utils/ai/CallToolOutput/CallToolOutput.js @@ -6,19 +6,21 @@ module.exports = { // Find the toolCallId in the message scope, looking into the first // component with type 'appmixer.utils.ai.CallTool' and taking - // toolCallId from the output message. + // toolCallId from the output message that is stored in the flow state. const flowDescriptor = context.flowDescriptor; const scope = context.messages.in.scope; let toolCallId; - Object.keys(scope).forEach(componentId => { - const callToolOutputMessage = scope[componentId]; + for (const componentId of Object.keys(scope)) { const component = flowDescriptor[componentId]; if (component && component.type === 'appmixer.utils.ai.CallTool') { - toolCallId = callToolOutputMessage.out?.toolCallId; + const key = componentId + ':' + context.messages.in.correlationId; + toolCallId = await context.flow.stateGet(key); + await context.flow.stateUnset(key); } - }); + } if (!toolCallId) { + await context.log({ step: 'no-tool-call-id', scope, flowDescriptor }); throw new context.CancelError('No toolCallId found in the scope. Are you sure you used ai.CallTool to start your tool chain?'); } diff --git a/src/appmixer/utils/ai/GenerateEmbeddings/GenerateEmbeddings.js b/src/appmixer/utils/ai/GenerateEmbeddings/GenerateEmbeddings.js index bcd4793f7..ced120d31 100644 --- a/src/appmixer/utils/ai/GenerateEmbeddings/GenerateEmbeddings.js +++ b/src/appmixer/utils/ai/GenerateEmbeddings/GenerateEmbeddings.js @@ -9,7 +9,7 @@ module.exports = { receive: async function(context) { - const correlationId = context.messages.in.correlationId; + const messageId = context.messages.in.messageId; const { text, chunkSize, chunkOverlap } = context.messages.in.content; const chunks = await this.splitText(text, chunkSize, chunkOverlap); @@ -35,7 +35,7 @@ module.exports = { // Collect embeddings for the current batch. response.data.forEach((item, index) => { embeddings.push({ - id: `${correlationId}:${i}:${index}`, + id: `${messageId}:${i}:${index}`, values: item.embedding, metadata: { text: batch[index] } }); From 6b14ea884010ff691ae6c6d7db5c83f6b133e8e9 Mon Sep 17 00:00:00 2001 From: David Durman Date: Mon, 16 Dec 2024 12:21:33 +0100 Subject: [PATCH 7/9] rename CallTool to ToolStart and CallToolOutput to ToolOutput; GenerateEmbeddings made more flexible --- src/appmixer/utils/ai/AIAgent/AIAgent.js | 14 +-- src/appmixer/utils/ai/AIAgent/component.json | 2 +- src/appmixer/utils/ai/AIAgent/icon.png | Bin 0 -> 954 bytes src/appmixer/utils/ai/CallTool/component.json | 65 -------------- .../utils/ai/CallToolOutput/component.json | 24 ----- .../GenerateEmbeddings/GenerateEmbeddings.js | 85 +++++++++++++++--- .../ai/GenerateEmbeddings/component.json | 38 +++++++- .../ToolOutput.js} | 8 +- .../utils/ai/ToolOutput/component.json | 24 +++++ src/appmixer/utils/ai/ToolOutput/icon.png | Bin 0 -> 993 bytes .../CallTool.js => ToolStart/ToolStart.js} | 0 .../utils/ai/ToolStart/component.json | 65 ++++++++++++++ src/appmixer/utils/ai/ToolStart/icon.png | Bin 0 -> 956 bytes src/appmixer/utils/ai/bundle.json | 2 +- src/appmixer/utils/ai/package.json | 1 + 15 files changed, 208 insertions(+), 120 deletions(-) create mode 100644 src/appmixer/utils/ai/AIAgent/icon.png delete mode 100644 src/appmixer/utils/ai/CallTool/component.json delete mode 100644 src/appmixer/utils/ai/CallToolOutput/component.json rename src/appmixer/utils/ai/{CallToolOutput/CallToolOutput.js => ToolOutput/ToolOutput.js} (80%) create mode 100644 src/appmixer/utils/ai/ToolOutput/component.json create mode 100644 src/appmixer/utils/ai/ToolOutput/icon.png rename src/appmixer/utils/ai/{CallTool/CallTool.js => ToolStart/ToolStart.js} (100%) create mode 100644 src/appmixer/utils/ai/ToolStart/component.json create mode 100644 src/appmixer/utils/ai/ToolStart/icon.png diff --git a/src/appmixer/utils/ai/AIAgent/AIAgent.js b/src/appmixer/utils/ai/AIAgent/AIAgent.js index d20c6ea51..2ff7962e3 100644 --- a/src/appmixer/utils/ai/AIAgent/AIAgent.js +++ b/src/appmixer/utils/ai/AIAgent/AIAgent.js @@ -33,18 +33,18 @@ module.exports = { const source = sources[inPort]; if (source[agentComponentId] && source[agentComponentId].includes(toolsPort)) { tools[componentId] = component; - // assert(flowDescriptor[componentId].type === 'appmixer.utils.ai.CallTool') - if (component.type !== 'appmixer.utils.ai.CallTool') { - error = `Component ${componentId} is not of type 'ai.CallTool' but ${comopnent.type}. + // assert(flowDescriptor[componentId].type === 'appmixer.utils.ai.ToolStart') + if (component.type !== 'appmixer.utils.ai.ToolStart') { + error = `Component ${componentId} is not of type 'ai.ToolStart' but ${comopnent.type}. Every tool chain connected to the '${toolsPort}' port of the AI Agent - must start with 'ai.CallTool' and end with 'ai.CallToolOutput'. + must start with 'ai.ToolStart' and end with 'ai.ToolOutput'. This is where you describe what the tool does and what parameters should the AI model provide to it.`; } } }); }); - // Teach the user via logs that they need to use the 'ai.CallTool' component. + // Teach the user via logs that they need to use the 'ai.ToolStart' component. if (error) { throw new context.CancelError(error); } @@ -145,11 +145,11 @@ module.exports = { toolCalls.push({ componentId, args, id: toolCall.id }); } - // Send to all tools. Each ai.CallTool ignores tool calls that are not intended for it. + // Send to all tools. Each ai.ToolStart ignores tool calls that are not intended for it. await context.sendJson({ toolCalls, prompt: context.messages.in.content.prompt }, 'tools'); // Output of each tool is expected to be stored in the service state - // under the ID of the tool call. This is done in the CallToolOutput component. + // under the ID of the tool call. This is done in the ToolStartOutput component. // Collect outputs of all the required tool calls. await context.log({ step: 'collect-tools-output', threadId: thread.id, runId: run.id }); const outputs = []; diff --git a/src/appmixer/utils/ai/AIAgent/component.json b/src/appmixer/utils/ai/AIAgent/component.json index 5ad64d153..1226910e2 100644 --- a/src/appmixer/utils/ai/AIAgent/component.json +++ b/src/appmixer/utils/ai/AIAgent/component.json @@ -65,5 +65,5 @@ "schema": { "type": "string" } }] }], - "icon": "data:image/png;base64,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" + "icon": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAYAAAA6/NlyAAAACXBIWXMAACE4AAAhOAFFljFgAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAANPSURBVHgB7ZtBdtMwEIZ/Agt2pDuWYseO3gD1BrlBvGTJDWJOQG6QcgLgBPaSHe0JGk4QOEHRVNKzkliWHI9iJc333jz3xRpZo5mRJVkF/Mxw2vRqv1DygHRUpv7PSIM0z9hj4lGg3hFGUiCMTJGGObTRe/VPOhSIAqeJNNciVOirko2SR0cqoyjAx4OpuwQfEj3af63ktqWwFfp9Cb4Q5DaYUvA72tv+aJ5XwtN+UraGU+9I8JPCwwQZVKBpP3WCjFFcGoVUr6ZUBlsqU7/cvfHCo0AFqXeu4Mf25kfolOiDMNe/RmKhsndKfir50VGuULJQ8g49WHbco8p8+X4soSiRnvaRM1ZtN3weRkdFu3lRK7lHP08dioCOKOH8Vir50lKW2jq4TTY37IAmMA4FmnGAJMmMbeE8oMT4CDRGb8Dc+VOn8gr5INCMJUswUqDxrkBelGi8zDY3t7OY38gPicYZMlR4gjiEud4hP2rn7+B8INZgGyp/kCdrcw2GdKzBZ8PF4HPnFfihPLJbRDW2B5Vj6LMQu5ybY39RQboCcbTp06swNBjFti+amAoF/CuomNmZgH9lFNKPNpgzh2kC7/OERNjLXQsACaYZHqfBobATgfsfBupHwWnw0LXnPdLW/wSnwV1bLmuER9uQPsu0ltPgGu07D2slN0ivHwX3e7iE9lSh5A303JvWqbHhWEIbPjtQP0iKiQeF3pAtlxoJJxuXufS5czH43LkYfO6keC1Zphi2bbpGAlIaTBOGOQ6HZlc1mLmENCPk4VscTpI98JQG57hp3zuk2b7dMBPdrliDrbdCuxJjINAYHIyqWIPtbsQ18vOydP5mSyMyNKeP4S7Jvlu7xx0k8oBO3tk2FWBGoNl33mDcc5iUVq6xq1jFvqd4KLQrNHlcK/kGnTts2zAdCOhTPO4eOD37JuXzBbZP0IwpXec/Z2CmgP7uc2wjKZ0qdI8jEp5BrG9It0E9HDxqwASF7Rrh8F1BO+QKA0L9WEYNxT1itcCBSIx78i4EGSmhPdv2uZYGOUEFX0ZUJowCefifkl/Ij/dKPkEb/XbnHnXGa+j07dX2IZvrx0Ri+4B4rgseVij9yOBTGXcGUyDt/11lB4XxEs+M1tz9DwZPSECQn4BkAAAAAElFTkSuQmCC" } diff --git a/src/appmixer/utils/ai/AIAgent/icon.png b/src/appmixer/utils/ai/AIAgent/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..354ed17ceb980663b46dd685ece60b575a5392e6 GIT binary patch literal 954 zcmV;r14aCaP)k$By0r^#-{?ykyA@VopGAka7ax1oo00?FkxA5I8~6RMNAOSe6`* zVkOP@-aPlk8d;;6ktJCL{LFCL^lHCQ@_=+yrvA@JgEY_BW8zVu-VsJpAdF0rhB>s+ zd-YE`6o>++i8R+DROqXcjgm)4Dx)aC$AhW61RoMT+JCL&wxzHY`h5w$5ac$PQt&(4 z+x}>tSHjbNQnrRu??~{I!ovg^RG?`;b%KmhTpEQ`uclN6TPjn(+`aL_qd*0zcdp>a z_U1?El<0#n*S|($p|W>Mek%FVQLa$3Q1T8vSa#f_%6$2LETJfop7xP5R&6(Y9!5t+ zyLnM7xko=w9g2XCDB=88D*4h{w1)6byB*~0z;XI(k_y{ycZ@}F7 zQ>XF@`5@4YB}*_;pz*FiUX*CO%r-Z-vzXt=4~fPZGE{hiIE1<0AwO#8@8EnO)?gbd ze~>3@a}9=ctifl)#o%-275Je%vRgzowg;7C=(7;JoSS2RQ9-%_akS~vt1vVe#kMb+ zmp|BHy`yU4CN5N_E(6#41 z($@b3r@>uYE^CBG+mJrXw3gH08tpp|Yb7^GuO9{Xd$@%5RH5Vz^xT`)sj=Z$LK{9i zP(MhDi3f$=kS&)Q2`3wux%9bnG5FlM7<}%ecr96`7`AP?MgmeAOcRDV4u@t}HJG`C zG$Z8UTO3Z3J@AwU=eTL#JEYmZ-7pi~u2_Suw?exTV<6BN9E_@1gY9m_HS+VabpB>b zBQi9nkKr!KzFWJ;9VGIAPTtuz6<|i7*>~Px+;fpF07*qoM6N<$f|qr*_y7O^ literal 0 HcmV?d00001 diff --git a/src/appmixer/utils/ai/CallTool/component.json b/src/appmixer/utils/ai/CallTool/component.json deleted file mode 100644 index 6ab02749e..000000000 --- a/src/appmixer/utils/ai/CallTool/component.json +++ /dev/null @@ -1,65 +0,0 @@ -{ - "name": "appmixer.utils.ai.CallTool", - "author": "Appmixer ", - "description": "Call a chain of tools. This has to be the first component connected to the AI Agent tools output port. The tool chain must be ended with CallToolOutput.", - "properties": { - "schema": { - "type": "object", - "properties": { - "generateOutputPortOptions": { "type": "boolean" }, - "description": { "type": "string" }, - "parameters": { "type": "object" } - } - }, - "inspector": { - "inputs": { - "description": { - "type": "textarea", - "label": "Description", - "index": 1 - }, - "parameters": { - "type": "expression", - "levels": ["ADD"], - "label": "Parameters", - "index": 2, - "fields": { - "name": { - "type": "text", - "label": "Name" - }, - "description": { - "type": "textarea", - "label": "Description" - }, - "type": { - "type": "select", - "label": "Type", - "options": [ - { "value": "string", "label": "String" }, - { "value": "number", "label": "Number" }, - { "value": "boolean", "label": "Boolean" } - ] - } - } - } - } - } - }, - "inPorts": [{ - "name": "in" - }], - "outPorts": [{ - "name": "out", - "source": { - "url": "/component/appmixer/utils/ai/CallTool?outPort=out", - "data": { - "properties": { - "generateOutputPortOptions": true, - "parameters": "properties/parameters" - } - } - } - }], - "icon": "data:image/png;base64,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" -} diff --git a/src/appmixer/utils/ai/CallToolOutput/component.json b/src/appmixer/utils/ai/CallToolOutput/component.json deleted file mode 100644 index f4d582ad9..000000000 --- a/src/appmixer/utils/ai/CallToolOutput/component.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "name": "appmixer.utils.ai.CallToolOutput", - "author": "Appmixer ", - "description": "AI Agent end of tool call chain.", - "inPorts": [{ - "name": "in", - "schema": { - "type": "object", - "properties": { - "output": { "type": "string" } - } - }, - "inspector": { - "inputs": { - "output": { - "type": "textarea", - "label": "Output", - "index": 1 - } - } - } - }], - "icon": "data:image/png;base64,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" -} diff --git a/src/appmixer/utils/ai/GenerateEmbeddings/GenerateEmbeddings.js b/src/appmixer/utils/ai/GenerateEmbeddings/GenerateEmbeddings.js index ced120d31..f71ce2402 100644 --- a/src/appmixer/utils/ai/GenerateEmbeddings/GenerateEmbeddings.js +++ b/src/appmixer/utils/ai/GenerateEmbeddings/GenerateEmbeddings.js @@ -3,14 +3,22 @@ const { RecursiveCharacterTextSplitter } = require('langchain/text_splitter'); const OpenAI = require('openai'); -const BATCH_SIZE = 10; +// See https://platform.openai.com/docs/api-reference/embeddings/create#embeddings-create-input. +const MAX_INPUT_LENGTH = 8192 * 4; // max 8192 tokens, 1 token ~ 4 characters. +const MAX_BATCH_SIZE = 2048; module.exports = { receive: async function(context) { const messageId = context.messages.in.messageId; - const { text, chunkSize, chunkOverlap } = context.messages.in.content; + const { + text, + model = 'text-embedding-ada-002', + chunkSize = 500, + chunkOverlap = 50, + embeddingTemplate = '{ "id": "{messageId}:{chunkIndex}", "values": {embedding}, "metadata": { "text": "{chunkText}" } }' + } = context.messages.in.content; const chunks = await this.splitText(text, chunkSize, chunkOverlap); await context.log({ step: 'split-text', message: 'Text succesfully split into chunks.', chunksLength: chunks.length }); @@ -21,36 +29,85 @@ module.exports = { } const client = new OpenAI({ apiKey }); - // Process chunks in batches + // Process chunks in batches. + // the batch size is calculated based on the chunk size and the maximum input length in + // order not to exceed the maximum input length defined in + // https://platform.openai.com/docs/api-reference/embeddings/create#embeddings-create-input + // We devide the maximum input length by 2 to stay on the safe side + // because the token to character ratio might not be accurate. + const batchSize = Math.min(Math.floor((MAX_INPUT_LENGTH / 2) / chunkSize), MAX_BATCH_SIZE); const embeddings = []; - for (let i = 0; i < chunks.length; i += BATCH_SIZE) { - const batch = chunks.slice(i, i + BATCH_SIZE); + // For convenience, the GenerateEmbeddings component returns the first vector. + // This makes it easy to genereate embedding for a prompt and send it e.g. to the pinecone.QueryVectors component + // without having to apply modifiers to the embedding array returned. + let firstVector = null; + for (let i = 0; i < chunks.length; i += batchSize) { + const batch = chunks.slice(i, i + batchSize); const response = await client.embeddings.create({ - model: context.config.GenerateEmbeddingsModel || 'text-embedding-ada-002', + model, input: batch, encoding_format: 'float' }); // Collect embeddings for the current batch. response.data.forEach((item, index) => { - embeddings.push({ - id: `${messageId}:${i}:${index}`, - values: item.embedding, - metadata: { text: batch[index] } - }); + if (!firstVector) { + firstVector = item.embedding; + } + try { + const embedding = this.createEmbedding(embeddingTemplate, { + embedding: JSON.stringify(item.embedding), + chunkIndex: i + index, + chunkText: JSON.stringify(batch[index]).slice(1, -1), + messageId, + now: Date.now() + }); + embeddings.push(embedding); + } catch (err) { + // It does not make sense to retry the component call. + // Things "won't" improve. + throw new context.CancelError(err); + } }); } - return context.sendJson({ embeddings }, 'out'); + return context.sendJson({ embeddings, firstVector }, 'out'); + }, + + createEmbedding(embeddingTemplate, args) { + + const unknownPlaceholders = []; + const embeddingString = embeddingTemplate.replace(/{([^{}]+)}/g, (match, key) => { + if (key in args) { + return args[key]; + } else { + unknownPlaceholders.push(key); + return match; + } + }); + + if (unknownPlaceholders.length) { + throw new Error(`Unknown placeholders in the embedding template: ${unknownPlaceholders.join(', ')}. Template: ${embeddingTemplate}.`); + } + + let embedding; + try { + embedding = JSON.parse(embeddingString); + } catch (err) { + throw new Error(`Failed to parse the embedding string. Error: ${err.message}. ${embeddingString}.`); + } + + return embedding; }, splitText(text, chunkSize, chunkOverlap) { const splitter = new RecursiveCharacterTextSplitter({ - chunkSize: typeof chunkSize !== 'undefined' ? chunkSize : 500, - chunkOverlap: typeof chunkOverlap !== 'undefined' ? chunkOverlap : 50 + chunkSize, + chunkOverlap }); return splitter.splitText(text); } }; + diff --git a/src/appmixer/utils/ai/GenerateEmbeddings/component.json b/src/appmixer/utils/ai/GenerateEmbeddings/component.json index 0902f2c8c..cb1188acf 100644 --- a/src/appmixer/utils/ai/GenerateEmbeddings/component.json +++ b/src/appmixer/utils/ai/GenerateEmbeddings/component.json @@ -7,9 +7,11 @@ "schema": { "type": "object", "properties": { - "text": { "type": "string" }, + "text": { "type": "string", "maxLength": 512000 }, + "model": { "type": "string" }, "chunkSize": { "type": "integer" }, - "chunkOverlap": { "type": "integer" } + "chunkOverlap": { "type": "integer" }, + "embeddingTemplate": { "type": "string" } } }, "inspector": { @@ -17,19 +19,40 @@ "text": { "type": "textarea", "label": "Text", + "tooltip": "Enter the text to generate embeddings for. The text will be split into chunks and embeddings will be generated for each chunk. The maximum length is 512,000 characters. If you need more than 512,000 characters, use the 'Generate Embeddings From File' component.", "index": 1 }, + "model": { + "type": "select", + "label": "Model", + "index": 2, + "defaultValue": "text-embedding-ada-002", + "options": [ + { "label": "text-embedding-3-large (3072 dimension)", "value": "text-embedding-3-large" }, + { "label": "text-embedding-3-small (1536 dimension)", "value": "text-embedding-3-small" }, + { "label": "text-embedding-ada-002 (1536 dimension)", "value": "text-embedding-ada-002" } + ] + }, "chunkSize": { "type": "number", "label": "Chunk Size", + "defaultValue": 500, "tooltip": "Maximum size of each chunk for text splitting. The default is 500.", - "index": 2 + "index": 3 }, "chunkOverlap": { "type": "number", "label": "Chunk Overlap", + "defaultValue": 50, "tooltip": "Overlap between chunks for text splitting to maintain context. The default is 50.", - "index": 3 + "index": 4 + }, + "embeddingTemplate": { + "type": "textarea", + "label": "Embedding Template", + "defaultValue": "{ \"id\": \"{messageId}:{chunkIndex}\", \"values\": {embedding}, \"metadata\": { \"text\": \"{chunkText}\" } }", + "tooltip": "The template for the embeddings. Use the placeholders {embedding} (a vector, e.g. [1.1, 2.2, 3.3]), {now} (a UNIX timestamp in milliseconds), {messageId} (a unique ID for the run of the 'Generate Embeddings' component), {chunkIndex} (the index of the chunk) and {chunkText} (the text of the chunk) to define the structure of the embeddings. Default template is { \"id\": \"{messageId}:{chunkIndex}\", \"values\": {embedding}, \"metadata\": { \"text\": \"{chunkText}\" } }. This template defines embeddings that can be directly used with the Pinecone vector database.", + "index": 5 } } } @@ -55,6 +78,13 @@ } } } + }, { + "label": "First Vector", + "value": "firstVector", + "schema": { + "type": "array", + "items": { "type": "number" } + } }] }], "icon": "data:image/png;base64,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" diff --git a/src/appmixer/utils/ai/CallToolOutput/CallToolOutput.js b/src/appmixer/utils/ai/ToolOutput/ToolOutput.js similarity index 80% rename from src/appmixer/utils/ai/CallToolOutput/CallToolOutput.js rename to src/appmixer/utils/ai/ToolOutput/ToolOutput.js index 897423d2f..f960c0d7b 100644 --- a/src/appmixer/utils/ai/CallToolOutput/CallToolOutput.js +++ b/src/appmixer/utils/ai/ToolOutput/ToolOutput.js @@ -5,14 +5,14 @@ module.exports = { receive: async function(context) { // Find the toolCallId in the message scope, looking into the first - // component with type 'appmixer.utils.ai.CallTool' and taking + // component with type 'appmixer.utils.ai.ToolStart' and taking // toolCallId from the output message that is stored in the flow state. const flowDescriptor = context.flowDescriptor; const scope = context.messages.in.scope; let toolCallId; for (const componentId of Object.keys(scope)) { const component = flowDescriptor[componentId]; - if (component && component.type === 'appmixer.utils.ai.CallTool') { + if (component && component.type === 'appmixer.utils.ai.ToolStart') { const key = componentId + ':' + context.messages.in.correlationId; toolCallId = await context.flow.stateGet(key); await context.flow.stateUnset(key); @@ -21,10 +21,10 @@ module.exports = { if (!toolCallId) { await context.log({ step: 'no-tool-call-id', scope, flowDescriptor }); - throw new context.CancelError('No toolCallId found in the scope. Are you sure you used ai.CallTool to start your tool chain?'); + throw new context.CancelError('No toolCallId found in the scope. Are you sure you used ai.ToolStart to start your tool chain?'); } - await context.log({ step: 'call-tool-output', toolCallId, output: context.messages.in.content.output }); + await context.log({ step: 'tool-output', toolCallId, output: context.messages.in.content.output }); // The AI agent expects to see the output in the flow state under the toolCallId key. return context.flow.stateSet(toolCallId, { output: context.messages.in.content.output }); } diff --git a/src/appmixer/utils/ai/ToolOutput/component.json b/src/appmixer/utils/ai/ToolOutput/component.json new file mode 100644 index 000000000..2112dabdc --- /dev/null +++ b/src/appmixer/utils/ai/ToolOutput/component.json @@ -0,0 +1,24 @@ +{ + "name": "appmixer.utils.ai.ToolOutput", + "author": "Appmixer ", + "description": "Determines the end of tool chain for AI Agent. Set the output of the tool chain.", + "inPorts": [{ + "name": "in", + "schema": { + "type": "object", + "properties": { + "output": { "type": "string" } + } + }, + "inspector": { + "inputs": { + "output": { + "type": "textarea", + "label": "Output", + "index": 1 + } + } + } + }], + "icon": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFEAAAA8CAYAAAAe5VayAAAACXBIWXMAACE4AAAhOAFFljFgAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAN2SURBVHgB7Zv/cZswFMe/8fX/uhuoE9QbVJ2g2SDaIOkEdieIO0G8gdMJ7E5gdwJrg3iDhHegQyboB1ggkdPn7p3BIHh80XtPyBjIZDKZTCaT+fAskDasMI7EmGnLt4XdIxyisNfCnhAOjtLPpNBF/InSwTnS5a6ypCARWWFL1AI+Ir2Q4Sh9pE/y8QmJ+fiCMuyaRo4y9EcgTDivMJyPQaFeqBxdI0xIC4TNibqPKySUdm60ZRKP8uJXRxsGv7tPx3oo7LmwPx77nwuT1aeJTWHf4fYxGhzmXrOotpnCKqTtUPZik49rJMZNY53EOja+o2GP7vgZ9t5yDUxb3hT2q3GueWUSE2KJyyQ+9GCc4bKQHDBxOGoBBcaFFXZCXUQmC+WmmBfBq/NTr0z5AcAIQ90LGeKhbuQDEmZm+F7lvj3iJvG/1ec3jIcqXt7MLAciJOKiKvMXjAN1HsrFlELufBvNMA0+Yxw46g60gaeQUxFxLDa4jD5adwqZRbyE0scPdBTyGhEZyupJ+YMGxUvPdreoq+4O6U2ySvQQsg0B+wwMQz0Y1u0RfsdtGnfsv8P4MLy/xk5CCthFFDBPINiGBweYJx1s54khIsHgIWTfcOaWbQuHU12+j42ER2j3FfEYeNt/hIOhPRrmsN9EUwRJ9MyRAvZwphO25cQV7PCWNlSYGOx++IbzUjuuXrCY5u/Bs00Tho45UsAuojropnKKLtL3+ZZX+5NDW9jDX8BfxDkuL/BZ27ZubFt4tGmDoUXIT+iPRL8psn1loVGTxSosX7RtsmVfV5s2JEqh9Q5j/KlCwN0Tx0CgWzgvqn3Jb9bYRr1xi/c3XrVZw13g9NB3pi+BaYo4JEYB82OfHyTgSlv/jSxiJ6wCEllEO04BiWuqs4IqW59JhCPsA/PYeAloQ8C/sDD0+5F+5XFsgTiFpVMVDtETiT26I5EmnXtgCBElymfLj4BAjxDOhcWMdw409UT1WDT0ayMu1PlDzvLY2BiWe6E/mDPE4wT37ErSUEWM+cilKuQJE4ahfjvL9dtJaO4R72Wqztw4tlMYbatliTLZ0gD5jGHgKCc6ebXeeYCbKurVitcRjSLAd5I3Oq6eqCNQvoc9ZMWWhf1DObc3VG/PIMF3Fac22KYomOxwJwVIQCpyB6T/R86koVDmyGQymUwmk7HyBpgvZFwoOgB/AAAAAElFTkSuQmCC" +} diff --git a/src/appmixer/utils/ai/ToolOutput/icon.png b/src/appmixer/utils/ai/ToolOutput/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..5e4d74a160f6cce558cbfb3fb914df0c02a53cb8 GIT binary patch literal 993 zcmV<710MW|P)`q{J0&9bKgM@dWLni14SRj$p z=kA@sAb5Pydr!z1$Ye5^OeT~0@GOuvtT2wmm}$$mh20|@qOjM(o)8X>($A#OMf{1< z!8y|9uCk;c5m{kN)B-*ty^!H3eWXwF@d=6f_<}O*nnsQ?^hY2}IIl3Ak3pecs2p7* zOh^la&562?DT8 z6NZjCQot^vI_RcyiZ0Uk`IcAvlS5nB!(YH(#5pc6@LAioZfsk4v}uE(MaWO+k3_#I zvVCQtQIo|gEMK&3vSnXan>Hj$q4f@9{Ha2~-4n+^pj$XEs(s0C*4Re9af|miUbR`n zUEuW4ccw3aX^J=`Mgj9`Ky5MU45#r7GkJ6NaIFJInRkNoej7?`5ja z+&H#V7;UR%i89&J0Qrnz;3y|Ld9~hofSW4n)r!{GzK1Kr zLpirMRBz89!FLs>HrS>pnCx&T1xP?DatEaUN6w_7Q0B;FGMP*!ldw0K literal 0 HcmV?d00001 diff --git a/src/appmixer/utils/ai/CallTool/CallTool.js b/src/appmixer/utils/ai/ToolStart/ToolStart.js similarity index 100% rename from src/appmixer/utils/ai/CallTool/CallTool.js rename to src/appmixer/utils/ai/ToolStart/ToolStart.js diff --git a/src/appmixer/utils/ai/ToolStart/component.json b/src/appmixer/utils/ai/ToolStart/component.json new file mode 100644 index 000000000..55646f579 --- /dev/null +++ b/src/appmixer/utils/ai/ToolStart/component.json @@ -0,0 +1,65 @@ +{ + "name": "appmixer.utils.ai.ToolStart", + "author": "Appmixer ", + "description": "Define a tool chain (i.e. a set of actions that might be selected by the AI model to run based on the user prompt). The tool can provide additional context to the AI agent to be able to reply to the user or call actions. This has to be the first component connected to the AI Agent tools output port. The tool chain must be ended with 'Tool Output'.", + "properties": { + "schema": { + "type": "object", + "properties": { + "generateOutputPortOptions": { "type": "boolean" }, + "description": { "type": "string" }, + "parameters": { "type": "object" } + } + }, + "inspector": { + "inputs": { + "description": { + "type": "textarea", + "label": "Description", + "index": 1 + }, + "parameters": { + "type": "expression", + "levels": ["ADD"], + "label": "Parameters", + "index": 2, + "fields": { + "name": { + "type": "text", + "label": "Name" + }, + "description": { + "type": "textarea", + "label": "Description" + }, + "type": { + "type": "select", + "label": "Type", + "options": [ + { "value": "string", "label": "String" }, + { "value": "number", "label": "Number" }, + { "value": "boolean", "label": "Boolean" } + ] + } + } + } + } + } + }, + "inPorts": [{ + "name": "in" + }], + "outPorts": [{ + "name": "out", + "source": { + "url": "/component/appmixer/utils/ai/ToolStart?outPort=out", + "data": { + "properties": { + "generateOutputPortOptions": true, + "parameters": "properties/parameters" + } + } + } + }], + "icon": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFEAAAA8CAYAAAAe5VayAAAACXBIWXMAACE4AAAhOAFFljFgAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAANRSURBVHgB7ZrhkdowEIVfMvkfSlAHoYMoHdBB3EHo4NxBLhXAVZCkAkgFkApwB0cHRBtJZ2MsW7YlWwJ9MzvcnC0hnlerXVlAIpFIJEKBK3MNE7aAezJP/Y5iI+wZ7rkI28E9B/h56KM4CXuFe3yIyFS/GwQChxzMBeXAONzhUkSavlz1R/3SQ19DijoLS8gpcTHYQd0zFlcicsjZYhovhaLZYiQXtkUcnkishP1E6Yk5ZvTEOjSgE9wzaUz8gHl5Efax5TpNlUzYJ/R/8hQW+gh5FPZH2C/D9ULYXthvBAaHeRp/g/TUy8RGM2NlGNMaEUFTphp/OPwGcL0Cb1GK+YSI+Y5ypWaYHgoDJzWGr4gQjnJKzVlekZCvyoIr87rQaQTH/OSQY8kREfTEtReGgB7PruvG9wgHXan8RRicIdOazgoqJBE1R4RDAYuYGKKI0eFSRBcbB1HiSkTK7Q7KoksJxuJKxGXlk1Yzn0Jy9R20itNDy+yb/q9CTsqoKmIICIbrfbchHsnRnZet0Fzv2tS0GzTXyaylzU7dNxkM44Tk6Bax2n/VuioLjvaNVRNWIrpcWAphX9Qn4WNqM8P/F2j3KN5y7TNG4jrFKeBXyDOGUcB9n2/4yBML+BPyh+H/e7Qn6bTRahLrBY5YwPwj2YA2ul2fGMnRHROp/Ra3L7cYulniNqbmHW2sF5YnNK9yrPKl9a0pU5s61T66hOSw3zWhezMM2+1Zwf4kg7WI9SVfk9euZRZtmmCwE5IjvK0n69W5GksKw99t1+r31TnjOh51hYEoYZCboRvcxpa1urbu0aYKiVV9Wd+W3HJE6ok+6SMgwRHxdPbBQg1A19QFrtOeu8KHiA8lIOH6BMSUAjIMf6HVlnz3xqWIU3sgx/CzgnsEKOIcU1i/SLobyCNsV2ETHA++OjP1WWC8B95dIm4Lw/iDjwyG838zoo+SREVIg6b4bnUCIjSeYf/OxDc6zmeIDIbSGxnmgw6Y2uxQBQt5of4BGaZHC2j9IN8hTHKUJ1X3kOekCzhMkGswyHPhGWR2QN9DWcYRkcNQ5mlT2gY9Q0monliFQa6US/ilgOOaOtHMwxYCLuk91RPX0FtCyhhIyOSRI1ghkUgkEolEBPwD+7dnZMlRkGcAAAAASUVORK5CYII=" +} diff --git a/src/appmixer/utils/ai/ToolStart/icon.png b/src/appmixer/utils/ai/ToolStart/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..b6c6c2a9d655f6c72ff3995d4f9aee9770b67dae GIT binary patch literal 956 zcmV;t14I0YP)Uo!b0N>B%(gD4%)LEJ&;;M_qj6~I-Hqyk6!|~SwJF@NF+jmD{~DKwt#ywPyLQ!BkUROxd_|AJqP%C=%Y9Zdxd*^h{#mG z8w3J}9Lxpfz&PCDQbd}*T=hr&(nDWEiaHBR$SH9#*g@Sh7FQy2$k}2VFNLDnVk8%~ zMI4?;tc3kU>4`aJ^u#(epcC9PX-xcpd3h1`vE@2Vl}y+t^ndac7WxQz6!u5hFT?dh z*k0Ig1O|tB8h?ZI)XT(1Gh<~K(;5*$Q>Ge!IQ#~1y#jAhiur_y`Hpg>W|$rd11B|x zuZR$ia!SovUXhTm%7QENy3lC^$Nc1wW62Q_^u)CnhJYT=uItA42nSuM{6!d?A=8@9 z3MwQKHAjR)0gIRzqKMJqQbZRU2T|fmM9S?B%A!jN#^OrEW#yArGoM0o^&PZ|(?iSb zoBpptPRgh%Vh9L^>wUK&9?8+2m#xrT@7mO~8#LF+tfkFP-8UHwiUP<{fRU7nUb9uX3#N zrS+{TQ=2-~KDvbDu-`SIdov8%?ODP8B!jhx^(9}E*REw2ZJJ=P6Otpmttac%xf)9f z!{2S!>i5+-<2oL7g@Gtxkei_!+{Tu*y0-4orV5EweOcd@+;T+fi9YEgAi)vwboRkQ z4M5k_1+LScyTXTv4<#Vs`UJ$30x}GLq1Bdr_r|4&Q+puC@Uwz@1SsMhQ&_)9x*NzN zY*%3u91p%aGxb?uya(Q0B5n)A_=ArOhVy?jqK}e9MM#6D_-=IqA~c?1|IWlWI?iW^ z42IGh8RmlynA+@61q*qh{s9@LM+<2EAm1TOa+D`k_mJnL3ph-WYcR-j;TdFhK;J`I z#u1UjQ08e<+6FyCNvNJ!g+Z>AOZcUL Date: Mon, 13 Jan 2025 10:42:22 +0100 Subject: [PATCH 8/9] add new icons --- src/appmixer/utils/ai/AIAgent/component.json | 2 +- src/appmixer/utils/ai/AIAgent/icon.png | Bin 954 -> 0 bytes src/appmixer/utils/ai/AIAgent/icon.svg | 15 + .../utils/ai/ToolOutput/component.json | 2 +- src/appmixer/utils/ai/ToolOutput/icon.png | Bin 993 -> 0 bytes src/appmixer/utils/ai/ToolOutput/icon.svg | 26 + .../utils/ai/ToolStart/component.json | 2 +- src/appmixer/utils/ai/ToolStart/icon.png | Bin 956 -> 0 bytes src/appmixer/utils/ai/ToolStart/icon.svg | 15 + src/appmixer/utils/ai/package-lock.json | 629 ++++++++++++++++++ 10 files changed, 688 insertions(+), 3 deletions(-) delete mode 100644 src/appmixer/utils/ai/AIAgent/icon.png create mode 100644 src/appmixer/utils/ai/AIAgent/icon.svg delete mode 100644 src/appmixer/utils/ai/ToolOutput/icon.png create mode 100644 src/appmixer/utils/ai/ToolOutput/icon.svg delete mode 100644 src/appmixer/utils/ai/ToolStart/icon.png create mode 100644 src/appmixer/utils/ai/ToolStart/icon.svg create mode 100644 src/appmixer/utils/ai/package-lock.json diff --git a/src/appmixer/utils/ai/AIAgent/component.json b/src/appmixer/utils/ai/AIAgent/component.json index 1226910e2..6e8f8ea71 100644 --- a/src/appmixer/utils/ai/AIAgent/component.json +++ b/src/appmixer/utils/ai/AIAgent/component.json @@ -65,5 +65,5 @@ "schema": { "type": "string" } }] }], - "icon": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAYAAAA6/NlyAAAACXBIWXMAACE4AAAhOAFFljFgAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAANPSURBVHgB7ZtBdtMwEIZ/Agt2pDuWYseO3gD1BrlBvGTJDWJOQG6QcgLgBPaSHe0JGk4QOEHRVNKzkliWHI9iJc333jz3xRpZo5mRJVkF/Mxw2vRqv1DygHRUpv7PSIM0z9hj4lGg3hFGUiCMTJGGObTRe/VPOhSIAqeJNNciVOirko2SR0cqoyjAx4OpuwQfEj3af63ktqWwFfp9Cb4Q5DaYUvA72tv+aJ5XwtN+UraGU+9I8JPCwwQZVKBpP3WCjFFcGoVUr6ZUBlsqU7/cvfHCo0AFqXeu4Mf25kfolOiDMNe/RmKhsndKfir50VGuULJQ8g49WHbco8p8+X4soSiRnvaRM1ZtN3weRkdFu3lRK7lHP08dioCOKOH8Vir50lKW2jq4TTY37IAmMA4FmnGAJMmMbeE8oMT4CDRGb8Dc+VOn8gr5INCMJUswUqDxrkBelGi8zDY3t7OY38gPicYZMlR4gjiEud4hP2rn7+B8INZgGyp/kCdrcw2GdKzBZ8PF4HPnFfihPLJbRDW2B5Vj6LMQu5ybY39RQboCcbTp06swNBjFti+amAoF/CuomNmZgH9lFNKPNpgzh2kC7/OERNjLXQsACaYZHqfBobATgfsfBupHwWnw0LXnPdLW/wSnwV1bLmuER9uQPsu0ltPgGu07D2slN0ivHwX3e7iE9lSh5A303JvWqbHhWEIbPjtQP0iKiQeF3pAtlxoJJxuXufS5czH43LkYfO6keC1Zphi2bbpGAlIaTBOGOQ6HZlc1mLmENCPk4VscTpI98JQG57hp3zuk2b7dMBPdrliDrbdCuxJjINAYHIyqWIPtbsQ18vOydP5mSyMyNKeP4S7Jvlu7xx0k8oBO3tk2FWBGoNl33mDcc5iUVq6xq1jFvqd4KLQrNHlcK/kGnTts2zAdCOhTPO4eOD37JuXzBbZP0IwpXec/Z2CmgP7uc2wjKZ0qdI8jEp5BrG9It0E9HDxqwASF7Rrh8F1BO+QKA0L9WEYNxT1itcCBSIx78i4EGSmhPdv2uZYGOUEFX0ZUJowCefifkl/Ij/dKPkEb/XbnHnXGa+j07dX2IZvrx0Ri+4B4rgseVij9yOBTGXcGUyDt/11lB4XxEs+M1tz9DwZPSECQn4BkAAAAAElFTkSuQmCC" + "icon": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPGcgY2xpcC1wYXRoPSJ1cmwoI2NsaXAwXzUyMjFfMzI0OTApIj4KPHBhdGggZD0iTTEyLjE1OTcgNi44Nzk5VjIuMzk5OUg3LjY3OTY5IiBzdHJva2U9IiMzMDMyMzYiIHN0cm9rZS13aWR0aD0iMi4wMTYiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPgo8cGF0aCBkPSJNMTcuOTIxMiA2Ljg3OTY0SDUuNDQxMTdDNC4yMDQwNSA2Ljg3OTY0IDMuMjAxMTcgNy44ODI1MiAzLjIwMTE3IDkuMTE5NjRWMTguMDc5NkMzLjIwMTE3IDE5LjMxNjggNC4yMDQwNSAyMC4zMTk2IDUuNDQxMTcgMjAuMzE5NkgxNy45MjEyQzE5LjE1ODMgMjAuMzE5NiAyMC4xNjEyIDE5LjMxNjggMjAuMTYxMiAxOC4wNzk2VjkuMTE5NjRDMjAuMTYxMiA3Ljg4MjUyIDE5LjE1ODMgNi44Nzk2NCAxNy45MjEyIDYuODc5NjRaIiBzdHJva2U9IiMzMDMyMzYiIHN0cm9rZS13aWR0aD0iMi4wMTYiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPgo8cGF0aCBkPSJNMC45NTk5NjEgMTMuNTk5NkgzLjE5OTk2IiBzdHJva2U9IiMzMDMyMzYiIHN0cm9rZS13aWR0aD0iMi4wMTYiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPgo8cGF0aCBkPSJNMjEuMTIwMSAxMy41OTk2SDIyLjQwMDEiIHN0cm9rZT0iIzMwMzIzNiIgc3Ryb2tlLXdpZHRoPSIyLjAxNiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIi8+CjxwYXRoIGQ9Ik0xNC43ODQyIDEyLjQ3OTdWMTQuNzE5NyIgc3Ryb2tlPSIjMzAzMjM2IiBzdHJva2Utd2lkdGg9IjIuMDE2IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz4KPHBhdGggZD0iTTguNjM5NjUgMTIuNDc5N1YxNC43MTk3IiBzdHJva2U9IiMzMDMyMzYiIHN0cm9rZS13aWR0aD0iMi4wMTYiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPgo8L2c+CjxkZWZzPgo8Y2xpcFBhdGggaWQ9ImNsaXAwXzUyMjFfMzI0OTAiPgo8cmVjdCB3aWR0aD0iMjQiIGhlaWdodD0iMjMuMDQiIGZpbGw9IndoaXRlIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgwIDAuNDc5OTgpIi8+CjwvY2xpcFBhdGg+CjwvZGVmcz4KPC9zdmc+Cg==" } diff --git a/src/appmixer/utils/ai/AIAgent/icon.png b/src/appmixer/utils/ai/AIAgent/icon.png deleted file mode 100644 index 354ed17ceb980663b46dd685ece60b575a5392e6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 954 zcmV;r14aCaP)k$By0r^#-{?ykyA@VopGAka7ax1oo00?FkxA5I8~6RMNAOSe6`* zVkOP@-aPlk8d;;6ktJCL{LFCL^lHCQ@_=+yrvA@JgEY_BW8zVu-VsJpAdF0rhB>s+ zd-YE`6o>++i8R+DROqXcjgm)4Dx)aC$AhW61RoMT+JCL&wxzHY`h5w$5ac$PQt&(4 z+x}>tSHjbNQnrRu??~{I!ovg^RG?`;b%KmhTpEQ`uclN6TPjn(+`aL_qd*0zcdp>a z_U1?El<0#n*S|($p|W>Mek%FVQLa$3Q1T8vSa#f_%6$2LETJfop7xP5R&6(Y9!5t+ zyLnM7xko=w9g2XCDB=88D*4h{w1)6byB*~0z;XI(k_y{ycZ@}F7 zQ>XF@`5@4YB}*_;pz*FiUX*CO%r-Z-vzXt=4~fPZGE{hiIE1<0AwO#8@8EnO)?gbd ze~>3@a}9=ctifl)#o%-275Je%vRgzowg;7C=(7;JoSS2RQ9-%_akS~vt1vVe#kMb+ zmp|BHy`yU4CN5N_E(6#41 z($@b3r@>uYE^CBG+mJrXw3gH08tpp|Yb7^GuO9{Xd$@%5RH5Vz^xT`)sj=Z$LK{9i zP(MhDi3f$=kS&)Q2`3wux%9bnG5FlM7<}%ecr96`7`AP?MgmeAOcRDV4u@t}HJG`C zG$Z8UTO3Z3J@AwU=eTL#JEYmZ-7pi~u2_Suw?exTV<6BN9E_@1gY9m_HS+VabpB>b zBQi9nkKr!KzFWJ;9VGIAPTtuz6<|i7*>~Px+;fpF07*qoM6N<$f|qr*_y7O^ diff --git a/src/appmixer/utils/ai/AIAgent/icon.svg b/src/appmixer/utils/ai/AIAgent/icon.svg new file mode 100644 index 000000000..640f610a1 --- /dev/null +++ b/src/appmixer/utils/ai/AIAgent/icon.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/src/appmixer/utils/ai/ToolOutput/component.json b/src/appmixer/utils/ai/ToolOutput/component.json index 2112dabdc..ccf99e686 100644 --- a/src/appmixer/utils/ai/ToolOutput/component.json +++ b/src/appmixer/utils/ai/ToolOutput/component.json @@ -20,5 +20,5 @@ } } }], - "icon": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFEAAAA8CAYAAAAe5VayAAAACXBIWXMAACE4AAAhOAFFljFgAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAN2SURBVHgB7Zv/cZswFMe/8fX/uhuoE9QbVJ2g2SDaIOkEdieIO0G8gdMJ7E5gdwJrg3iDhHegQyboB1ggkdPn7p3BIHh80XtPyBjIZDKZTCaT+fAskDasMI7EmGnLt4XdIxyisNfCnhAOjtLPpNBF/InSwTnS5a6ypCARWWFL1AI+Ir2Q4Sh9pE/y8QmJ+fiCMuyaRo4y9EcgTDivMJyPQaFeqBxdI0xIC4TNibqPKySUdm60ZRKP8uJXRxsGv7tPx3oo7LmwPx77nwuT1aeJTWHf4fYxGhzmXrOotpnCKqTtUPZik49rJMZNY53EOja+o2GP7vgZ9t5yDUxb3hT2q3GueWUSE2KJyyQ+9GCc4bKQHDBxOGoBBcaFFXZCXUQmC+WmmBfBq/NTr0z5AcAIQ90LGeKhbuQDEmZm+F7lvj3iJvG/1ec3jIcqXt7MLAciJOKiKvMXjAN1HsrFlELufBvNMA0+Yxw46g60gaeQUxFxLDa4jD5adwqZRbyE0scPdBTyGhEZyupJ+YMGxUvPdreoq+4O6U2ySvQQsg0B+wwMQz0Y1u0RfsdtGnfsv8P4MLy/xk5CCthFFDBPINiGBweYJx1s54khIsHgIWTfcOaWbQuHU12+j42ER2j3FfEYeNt/hIOhPRrmsN9EUwRJ9MyRAvZwphO25cQV7PCWNlSYGOx++IbzUjuuXrCY5u/Bs00Tho45UsAuojropnKKLtL3+ZZX+5NDW9jDX8BfxDkuL/BZ27ZubFt4tGmDoUXIT+iPRL8psn1loVGTxSosX7RtsmVfV5s2JEqh9Q5j/KlCwN0Tx0CgWzgvqn3Jb9bYRr1xi/c3XrVZw13g9NB3pi+BaYo4JEYB82OfHyTgSlv/jSxiJ6wCEllEO04BiWuqs4IqW59JhCPsA/PYeAloQ8C/sDD0+5F+5XFsgTiFpVMVDtETiT26I5EmnXtgCBElymfLj4BAjxDOhcWMdw409UT1WDT0ayMu1PlDzvLY2BiWe6E/mDPE4wT37ErSUEWM+cilKuQJE4ahfjvL9dtJaO4R72Wqztw4tlMYbatliTLZ0gD5jGHgKCc6ebXeeYCbKurVitcRjSLAd5I3Oq6eqCNQvoc9ZMWWhf1DObc3VG/PIMF3Fac22KYomOxwJwVIQCpyB6T/R86koVDmyGQymUwmk7HyBpgvZFwoOgB/AAAAAElFTkSuQmCC" + "icon": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPGcgY2xpcC1wYXRoPSJ1cmwoI2NsaXAwXzUyMjFfMzI1MzcpIj4KPHBhdGggZD0iTTEyLjY2NjcgNi42NjY2N1YySDgiIHN0cm9rZT0iIzE4MTgxOCIgc3Ryb2tlLXdpZHRoPSIyLjEiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPgo8cGF0aCBkPSJNMTguNjY4MyA2LjY2NjVINS42NjgyOUM0LjM3OTYzIDYuNjY2NSAzLjMzNDk2IDcuNzExMTcgMy4zMzQ5NiA4Ljk5OTg0VjE4LjMzMzJDMy4zMzQ5NiAxOS42MjE4IDQuMzc5NjMgMjAuNjY2NSA1LjY2ODI5IDIwLjY2NjVIMTguNjY4M0MxOS45NTcgMjAuNjY2NSAyMS4wMDE2IDE5LjYyMTggMjEuMDAxNiAxOC4zMzMyVjguOTk5ODRDMjEuMDAxNiA3LjcxMTE3IDE5Ljk1NyA2LjY2NjUgMTguNjY4MyA2LjY2NjVaIiBzdHJva2U9IiMxODE4MTgiIHN0cm9rZS13aWR0aD0iMi4xIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz4KPHBhdGggZD0iTTEuMDAwOTggMTMuNjY2M0gzLjMzNDMxIiBzdHJva2U9IiMxODE4MTgiIHN0cm9rZS13aWR0aD0iMi4xIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz4KPHBhdGggZD0iTTIxLjAwMSAxMy42NjYzSDIzLjAzNDUiIHN0cm9rZT0iIzE4MTgxOCIgc3Ryb2tlLXdpZHRoPSIyLjEiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPgo8ZyBmaWx0ZXI9InVybCgjZmlsdGVyMF9kXzUyMjFfMzI1MzcpIj4KPHBhdGggZD0iTTcuMDAwOTggMTMuNDU5NUw5LjYwNzE4IDE2LjA2NTdDMTAuMzA2OCAxNi43NjUzIDExLjQ0OTUgMTYuNzM2NyAxMi4xMTMyIDE2LjAwM0wxNy4wMDEgMTAuNTk5OSIgc3Ryb2tlPSIjMjBCNzVBIiBzdHJva2Utd2lkdGg9IjIuODU1MTIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIvPgo8L2c+CjwvZz4KPGRlZnM+CjxmaWx0ZXIgaWQ9ImZpbHRlcjBfZF81MjIxXzMyNTM3IiB4PSIzLjQ3MjY5IiB5PSI3LjA3MTgxIiB3aWR0aD0iMTcuMDU2NiIgaGVpZ2h0PSIxMy4wMjg3IiBmaWx0ZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIGNvbG9yLWludGVycG9sYXRpb24tZmlsdGVycz0ic1JHQiI+CjxmZUZsb29kIGZsb29kLW9wYWNpdHk9IjAiIHJlc3VsdD0iQmFja2dyb3VuZEltYWdlRml4Ii8+CjxmZUNvbG9yTWF0cml4IGluPSJTb3VyY2VBbHBoYSIgdHlwZT0ibWF0cml4IiB2YWx1ZXM9IjAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDEyNyAwIiByZXN1bHQ9ImhhcmRBbHBoYSIvPgo8ZmVPZmZzZXQvPgo8ZmVHYXVzc2lhbkJsdXIgc3RkRGV2aWF0aW9uPSIxLjA1MDI3Ii8+CjxmZUNvbXBvc2l0ZSBpbjI9ImhhcmRBbHBoYSIgb3BlcmF0b3I9Im91dCIvPgo8ZmVDb2xvck1hdHJpeCB0eXBlPSJtYXRyaXgiIHZhbHVlcz0iMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMC4yNSAwIi8+CjxmZUJsZW5kIG1vZGU9Im5vcm1hbCIgaW4yPSJCYWNrZ3JvdW5kSW1hZ2VGaXgiIHJlc3VsdD0iZWZmZWN0MV9kcm9wU2hhZG93XzUyMjFfMzI1MzciLz4KPGZlQmxlbmQgbW9kZT0ibm9ybWFsIiBpbj0iU291cmNlR3JhcGhpYyIgaW4yPSJlZmZlY3QxX2Ryb3BTaGFkb3dfNTIyMV8zMjUzNyIgcmVzdWx0PSJzaGFwZSIvPgo8L2ZpbHRlcj4KPGNsaXBQYXRoIGlkPSJjbGlwMF81MjIxXzMyNTM3Ij4KPHJlY3Qgd2lkdGg9IjI0IiBoZWlnaHQ9IjI0IiBmaWxsPSJ3aGl0ZSIvPgo8L2NsaXBQYXRoPgo8L2RlZnM+Cjwvc3ZnPgo=" } diff --git a/src/appmixer/utils/ai/ToolOutput/icon.png b/src/appmixer/utils/ai/ToolOutput/icon.png deleted file mode 100644 index 5e4d74a160f6cce558cbfb3fb914df0c02a53cb8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 993 zcmV<710MW|P)`q{J0&9bKgM@dWLni14SRj$p z=kA@sAb5Pydr!z1$Ye5^OeT~0@GOuvtT2wmm}$$mh20|@qOjM(o)8X>($A#OMf{1< z!8y|9uCk;c5m{kN)B-*ty^!H3eWXwF@d=6f_<}O*nnsQ?^hY2}IIl3Ak3pecs2p7* zOh^la&562?DT8 z6NZjCQot^vI_RcyiZ0Uk`IcAvlS5nB!(YH(#5pc6@LAioZfsk4v}uE(MaWO+k3_#I zvVCQtQIo|gEMK&3vSnXan>Hj$q4f@9{Ha2~-4n+^pj$XEs(s0C*4Re9af|miUbR`n zUEuW4ccw3aX^J=`Mgj9`Ky5MU45#r7GkJ6NaIFJInRkNoej7?`5ja z+&H#V7;UR%i89&J0Qrnz;3y|Ld9~hofSW4n)r!{GzK1Kr zLpirMRBz89!FLs>HrS>pnCx&T1xP?DatEaUN6w_7Q0B;FGMP*!ldw0K diff --git a/src/appmixer/utils/ai/ToolOutput/icon.svg b/src/appmixer/utils/ai/ToolOutput/icon.svg new file mode 100644 index 000000000..fa873f377 --- /dev/null +++ b/src/appmixer/utils/ai/ToolOutput/icon.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/appmixer/utils/ai/ToolStart/component.json b/src/appmixer/utils/ai/ToolStart/component.json index 55646f579..7b22bfc7f 100644 --- a/src/appmixer/utils/ai/ToolStart/component.json +++ b/src/appmixer/utils/ai/ToolStart/component.json @@ -61,5 +61,5 @@ } } }], - "icon": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFEAAAA8CAYAAAAe5VayAAAACXBIWXMAACE4AAAhOAFFljFgAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAANRSURBVHgB7ZrhkdowEIVfMvkfSlAHoYMoHdBB3EHo4NxBLhXAVZCkAkgFkApwB0cHRBtJZ2MsW7YlWwJ9MzvcnC0hnlerXVlAIpFIJEKBK3MNE7aAezJP/Y5iI+wZ7rkI28E9B/h56KM4CXuFe3yIyFS/GwQChxzMBeXAONzhUkSavlz1R/3SQ19DijoLS8gpcTHYQd0zFlcicsjZYhovhaLZYiQXtkUcnkishP1E6Yk5ZvTEOjSgE9wzaUz8gHl5Efax5TpNlUzYJ/R/8hQW+gh5FPZH2C/D9ULYXthvBAaHeRp/g/TUy8RGM2NlGNMaEUFTphp/OPwGcL0Cb1GK+YSI+Y5ypWaYHgoDJzWGr4gQjnJKzVlekZCvyoIr87rQaQTH/OSQY8kREfTEtReGgB7PruvG9wgHXan8RRicIdOazgoqJBE1R4RDAYuYGKKI0eFSRBcbB1HiSkTK7Q7KoksJxuJKxGXlk1Yzn0Jy9R20itNDy+yb/q9CTsqoKmIICIbrfbchHsnRnZet0Fzv2tS0GzTXyaylzU7dNxkM44Tk6Bax2n/VuioLjvaNVRNWIrpcWAphX9Qn4WNqM8P/F2j3KN5y7TNG4jrFKeBXyDOGUcB9n2/4yBML+BPyh+H/e7Qn6bTRahLrBY5YwPwj2YA2ul2fGMnRHROp/Ra3L7cYulniNqbmHW2sF5YnNK9yrPKl9a0pU5s61T66hOSw3zWhezMM2+1Zwf4kg7WI9SVfk9euZRZtmmCwE5IjvK0n69W5GksKw99t1+r31TnjOh51hYEoYZCboRvcxpa1urbu0aYKiVV9Wd+W3HJE6ok+6SMgwRHxdPbBQg1A19QFrtOeu8KHiA8lIOH6BMSUAjIMf6HVlnz3xqWIU3sgx/CzgnsEKOIcU1i/SLobyCNsV2ETHA++OjP1WWC8B95dIm4Lw/iDjwyG838zoo+SREVIg6b4bnUCIjSeYf/OxDc6zmeIDIbSGxnmgw6Y2uxQBQt5of4BGaZHC2j9IN8hTHKUJ1X3kOekCzhMkGswyHPhGWR2QN9DWcYRkcNQ5mlT2gY9Q0monliFQa6US/ilgOOaOtHMwxYCLuk91RPX0FtCyhhIyOSRI1ghkUgkEolEBPwD+7dnZMlRkGcAAAAASUVORK5CYII=" + "icon": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPGcgY2xpcC1wYXRoPSJ1cmwoI2NsaXAwXzUyMjFfMzI0OTcpIj4KPHBhdGggZD0iTTExLjk5OTggNS4yVjFINy43OTk4IiBzdHJva2U9IiMxODE4MTgiIHN0cm9rZS13aWR0aD0iMi4xIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz4KPHBhdGggZD0iTTcuNzk5NjEgMTcuOEwzLjU5OTYxIDIyVjcuMjk5OTVDMy41OTk2MSA2Ljc0MyAzLjgyMDg2IDYuMjA4ODUgNC4yMTQ2OSA1LjgxNTAzQzQuNjA4NTEgNS40MjEyIDUuMTQyNjUgNS4xOTk5NSA1LjY5OTYxIDUuMTk5OTVIMTguMjk5NkMxOC44NTY2IDUuMTk5OTUgMTkuMzkwNyA1LjQyMTIgMTkuNzg0NSA1LjgxNTAzQzIwLjE3ODQgNi4yMDg4NSAyMC4zOTk2IDYuNzQzIDIwLjM5OTYgNy4yOTk5NVYxNS43QzIwLjM5OTYgMTYuMjU2OSAyMC4xNzg0IDE2Ljc5MSAxOS43ODQ1IDE3LjE4NDlDMTkuMzkwNyAxNy41Nzg3IDE4Ljg1NjYgMTcuOCAxOC4yOTk2IDE3LjhINy43OTk2MVoiIHN0cm9rZT0iIzE4MTgxOCIgc3Ryb2tlLXdpZHRoPSIyLjEiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPgo8cGF0aCBkPSJNMS41IDExLjQ5OThIMy42IiBzdHJva2U9IiMxODE4MTgiIHN0cm9rZS13aWR0aD0iMi4xIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz4KPHBhdGggZD0iTTguODQ5NjEgMTAuNDVWMTIuNTUiIHN0cm9rZT0iIzE4MTgxOCIgc3Ryb2tlLXdpZHRoPSIyLjEiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPgo8cGF0aCBkPSJNMTUuMTUwNCAxMC40NVYxMi41NSIgc3Ryb2tlPSIjMTgxODE4IiBzdHJva2Utd2lkdGg9IjIuMSIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIi8+CjxwYXRoIGQ9Ik0yMC40MDA0IDExLjQ5OThIMjIuNTAwNCIgc3Ryb2tlPSIjMTgxODE4IiBzdHJva2Utd2lkdGg9IjIuMSIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIi8+CjwvZz4KPGRlZnM+CjxjbGlwUGF0aCBpZD0iY2xpcDBfNTIyMV8zMjQ5NyI+CjxyZWN0IHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgZmlsbD0id2hpdGUiLz4KPC9jbGlwUGF0aD4KPC9kZWZzPgo8L3N2Zz4K" } diff --git a/src/appmixer/utils/ai/ToolStart/icon.png b/src/appmixer/utils/ai/ToolStart/icon.png deleted file mode 100644 index b6c6c2a9d655f6c72ff3995d4f9aee9770b67dae..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 956 zcmV;t14I0YP)Uo!b0N>B%(gD4%)LEJ&;;M_qj6~I-Hqyk6!|~SwJF@NF+jmD{~DKwt#ywPyLQ!BkUROxd_|AJqP%C=%Y9Zdxd*^h{#mG z8w3J}9Lxpfz&PCDQbd}*T=hr&(nDWEiaHBR$SH9#*g@Sh7FQy2$k}2VFNLDnVk8%~ zMI4?;tc3kU>4`aJ^u#(epcC9PX-xcpd3h1`vE@2Vl}y+t^ndac7WxQz6!u5hFT?dh z*k0Ig1O|tB8h?ZI)XT(1Gh<~K(;5*$Q>Ge!IQ#~1y#jAhiur_y`Hpg>W|$rd11B|x zuZR$ia!SovUXhTm%7QENy3lC^$Nc1wW62Q_^u)CnhJYT=uItA42nSuM{6!d?A=8@9 z3MwQKHAjR)0gIRzqKMJqQbZRU2T|fmM9S?B%A!jN#^OrEW#yArGoM0o^&PZ|(?iSb zoBpptPRgh%Vh9L^>wUK&9?8+2m#xrT@7mO~8#LF+tfkFP-8UHwiUP<{fRU7nUb9uX3#N zrS+{TQ=2-~KDvbDu-`SIdov8%?ODP8B!jhx^(9}E*REw2ZJJ=P6Otpmttac%xf)9f z!{2S!>i5+-<2oL7g@Gtxkei_!+{Tu*y0-4orV5EweOcd@+;T+fi9YEgAi)vwboRkQ z4M5k_1+LScyTXTv4<#Vs`UJ$30x}GLq1Bdr_r|4&Q+puC@Uwz@1SsMhQ&_)9x*NzN zY*%3u91p%aGxb?uya(Q0B5n)A_=ArOhVy?jqK}e9MM#6D_-=IqA~c?1|IWlWI?iW^ z42IGh8RmlynA+@61q*qh{s9@LM+<2EAm1TOa+D`k_mJnL3ph-WYcR-j;TdFhK;J`I z#u1UjQ08e<+6FyCNvNJ!g+Z>AOZcUL + + + + + + + + + + + + + + diff --git a/src/appmixer/utils/ai/package-lock.json b/src/appmixer/utils/ai/package-lock.json new file mode 100644 index 000000000..ad69e404d --- /dev/null +++ b/src/appmixer/utils/ai/package-lock.json @@ -0,0 +1,629 @@ +{ + "name": "appmixer.utils.ai", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "appmixer.utils.ai", + "version": "1.0.0", + "dependencies": { + "form-data": "4.0.0", + "langchain": "0.3.6", + "mustache": "4.2.0", + "openai": "4.76.0" + } + }, + "node_modules/@cfworker/json-schema": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@cfworker/json-schema/-/json-schema-4.0.3.tgz", + "integrity": "sha512-ZykIcDTVv5UNmKWSTLAs3VukO6NDJkkSKxrgUTDPBkAlORVT3H9n5DbRjRl8xIotklscHdbLIa0b9+y3mQq73g==", + "peer": true + }, + "node_modules/@langchain/core": { + "version": "0.3.23", + "resolved": "https://registry.npmjs.org/@langchain/core/-/core-0.3.23.tgz", + "integrity": "sha512-Aut43dEJYH/ibccSErFOLQzymkBG4emlN16P0OHWwx02bDosOR9ilZly4JJiCSYcprn2X2H8nee6P/4VMg1oQA==", + "peer": true, + "dependencies": { + "@cfworker/json-schema": "^4.0.2", + "ansi-styles": "^5.0.0", + "camelcase": "6", + "decamelize": "1.2.0", + "js-tiktoken": "^1.0.12", + "langsmith": "^0.2.8", + "mustache": "^4.2.0", + "p-queue": "^6.6.2", + "p-retry": "4", + "uuid": "^10.0.0", + "zod": "^3.22.4", + "zod-to-json-schema": "^3.22.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@langchain/openai": { + "version": "0.3.14", + "resolved": "https://registry.npmjs.org/@langchain/openai/-/openai-0.3.14.tgz", + "integrity": "sha512-lNWjUo1tbvsss45IF7UQtMu1NJ6oUKvhgPYWXnX9f/d6OmuLu7D99HQ3Y88vLcUo9XjjOy417olYHignMduMjA==", + "dependencies": { + "js-tiktoken": "^1.0.12", + "openai": "^4.71.0", + "zod": "^3.22.4", + "zod-to-json-schema": "^3.22.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@langchain/core": ">=0.2.26 <0.4.0" + } + }, + "node_modules/@langchain/textsplitters": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@langchain/textsplitters/-/textsplitters-0.1.0.tgz", + "integrity": "sha512-djI4uw9rlkAb5iMhtLED+xJebDdAG935AdP4eRTB02R7OB/act55Bj9wsskhZsvuyQRpO4O1wQOp85s6T6GWmw==", + "dependencies": { + "js-tiktoken": "^1.0.12" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@langchain/core": ">=0.2.21 <0.4.0" + } + }, + "node_modules/@types/node": { + "version": "18.19.68", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.68.tgz", + "integrity": "sha512-QGtpFH1vB99ZmTa63K4/FU8twThj4fuVSBkGddTp7uIL/cuoLWIUSL2RcOaigBhfR+hg5pgGkBnkoOxrTVBMKw==", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/node-fetch": { + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.12.tgz", + "integrity": "sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA==", + "dependencies": { + "@types/node": "*", + "form-data": "^4.0.0" + } + }, + "node_modules/@types/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==" + }, + "node_modules/@types/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==" + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/agentkeepalive": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.5.0.tgz", + "integrity": "sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==", + "dependencies": { + "humanize-ms": "^1.2.1" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "engines": { + "node": ">=14" + } + }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" + }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/form-data-encoder": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.2.tgz", + "integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==" + }, + "node_modules/formdata-node": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz", + "integrity": "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==", + "dependencies": { + "node-domexception": "1.0.0", + "web-streams-polyfill": "4.0.0-beta.3" + }, + "engines": { + "node": ">= 12.20" + } + }, + "node_modules/humanize-ms": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", + "dependencies": { + "ms": "^2.0.0" + } + }, + "node_modules/js-tiktoken": { + "version": "1.0.15", + "resolved": "https://registry.npmjs.org/js-tiktoken/-/js-tiktoken-1.0.15.tgz", + "integrity": "sha512-65ruOWWXDEZHHbAo7EjOcNxOGasQKbL4Fq3jEr2xsCqSsoOo6VVSqzWQb6PRIqypFSDcma4jO90YP0w5X8qVXQ==", + "dependencies": { + "base64-js": "^1.5.1" + } + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsonpointer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-5.0.1.tgz", + "integrity": "sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/langchain": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/langchain/-/langchain-0.3.6.tgz", + "integrity": "sha512-erZOIKXzwCOrQHqY9AyjkQmaX62zUap1Sigw1KrwMUOnVoLKkVNRmAyxFlNZDZ9jLs/58MaQcaT9ReJtbj3x6w==", + "dependencies": { + "@langchain/openai": ">=0.1.0 <0.4.0", + "@langchain/textsplitters": ">=0.0.0 <0.2.0", + "js-tiktoken": "^1.0.12", + "js-yaml": "^4.1.0", + "jsonpointer": "^5.0.1", + "langsmith": "^0.2.0", + "openapi-types": "^12.1.3", + "p-retry": "4", + "uuid": "^10.0.0", + "yaml": "^2.2.1", + "zod": "^3.22.4", + "zod-to-json-schema": "^3.22.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@langchain/anthropic": "*", + "@langchain/aws": "*", + "@langchain/cohere": "*", + "@langchain/core": ">=0.2.21 <0.4.0", + "@langchain/google-genai": "*", + "@langchain/google-vertexai": "*", + "@langchain/groq": "*", + "@langchain/mistralai": "*", + "@langchain/ollama": "*", + "axios": "*", + "cheerio": "*", + "handlebars": "^4.7.8", + "peggy": "^3.0.2", + "typeorm": "*" + }, + "peerDependenciesMeta": { + "@langchain/anthropic": { + "optional": true + }, + "@langchain/aws": { + "optional": true + }, + "@langchain/cohere": { + "optional": true + }, + "@langchain/google-genai": { + "optional": true + }, + "@langchain/google-vertexai": { + "optional": true + }, + "@langchain/groq": { + "optional": true + }, + "@langchain/mistralai": { + "optional": true + }, + "@langchain/ollama": { + "optional": true + }, + "axios": { + "optional": true + }, + "cheerio": { + "optional": true + }, + "handlebars": { + "optional": true + }, + "peggy": { + "optional": true + }, + "typeorm": { + "optional": true + } + } + }, + "node_modules/langsmith": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/langsmith/-/langsmith-0.2.13.tgz", + "integrity": "sha512-16EOM5nhU6GlMCKGm5sgBIAKOKzS2d30qcDZmF21kSLZJiUhUNTROwvYdqgZLrGfIIzmSMJHCKA7RFd5qf50uw==", + "dependencies": { + "@types/uuid": "^10.0.0", + "commander": "^10.0.1", + "p-queue": "^6.6.2", + "p-retry": "4", + "semver": "^7.6.3", + "uuid": "^10.0.0" + }, + "peerDependencies": { + "openai": "*" + }, + "peerDependenciesMeta": { + "openai": { + "optional": true + } + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/mustache": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz", + "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==", + "bin": { + "mustache": "bin/mustache" + } + }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/openai": { + "version": "4.76.0", + "resolved": "https://registry.npmjs.org/openai/-/openai-4.76.0.tgz", + "integrity": "sha512-QBGIetjX1C9xDp5XGa/3mPnfKI9BgAe2xHQX6PmO98wuW9qQaurBaumcYptQWc9LHZZq7cH/Y1Rjnsr6uUDdVw==", + "dependencies": { + "@types/node": "^18.11.18", + "@types/node-fetch": "^2.6.4", + "abort-controller": "^3.0.0", + "agentkeepalive": "^4.2.1", + "form-data-encoder": "1.7.2", + "formdata-node": "^4.3.2", + "node-fetch": "^2.6.7" + }, + "bin": { + "openai": "bin/cli" + }, + "peerDependencies": { + "zod": "^3.23.8" + }, + "peerDependenciesMeta": { + "zod": { + "optional": true + } + } + }, + "node_modules/openapi-types": { + "version": "12.1.3", + "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.3.tgz", + "integrity": "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==" + }, + "node_modules/p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", + "engines": { + "node": ">=4" + } + }, + "node_modules/p-queue": { + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-6.6.2.tgz", + "integrity": "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==", + "dependencies": { + "eventemitter3": "^4.0.4", + "p-timeout": "^3.2.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-retry": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", + "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", + "dependencies": { + "@types/retry": "0.12.0", + "retry": "^0.13.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-timeout": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", + "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", + "dependencies": { + "p-finally": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "engines": { + "node": ">= 4" + } + }, + "node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" + }, + "node_modules/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/web-streams-polyfill": { + "version": "4.0.0-beta.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz", + "integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==", + "engines": { + "node": ">= 14" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/yaml": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.6.1.tgz", + "integrity": "sha512-7r0XPzioN/Q9kXBro/XPnA6kznR73DHq+GXh5ON7ZozRO6aMjbmiBuKste2wslTFkC5d1dw0GooOCepZXJ2SAg==", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/zod": { + "version": "3.24.1", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.1.tgz", + "integrity": "sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-to-json-schema": { + "version": "3.24.1", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.1.tgz", + "integrity": "sha512-3h08nf3Vw3Wl3PK+q3ow/lIil81IT2Oa7YpQyUUDsEWbXveMesdfK1xBd2RhCkynwZndAxixji/7SYJJowr62w==", + "peerDependencies": { + "zod": "^3.24.1" + } + } + } +} From 8f5c717d87c410d8ee06f5b2b6910f94299f60a7 Mon Sep 17 00:00:00 2001 From: David Durman Date: Wed, 29 Jan 2025 13:49:53 +0100 Subject: [PATCH 9/9] move ai tools to ai/openai and generic agent tools to ai/agenttools --- .../ai/agenttools/ToolOutput/ToolOutput.js | 31 ++ .../ai/agenttools/ToolOutput/component.json | 24 ++ .../ai/agenttools/ToolOutput/icon.svg | 26 ++ .../ai/agenttools/ToolStart/ToolStart.js | 32 ++ .../ai/agenttools/ToolStart/component.json | 65 ++++ src/appmixer/ai/agenttools/ToolStart/icon.svg | 15 + src/appmixer/ai/agenttools/bundle.json | 9 + src/appmixer/ai/agenttools/module.json | 10 + src/appmixer/ai/openai/AIAgent/AIAgent.js | 232 ++++++++++++++ src/appmixer/ai/openai/AIAgent/component.json | 86 +++++ src/appmixer/ai/openai/AIAgent/icon.svg | 15 + .../ai/openai/CreateSpeech/CreateSpeech.js | 29 ++ .../ai/openai/CreateSpeech/component.json | 95 ++++++ .../CreateTranscription.js | 34 ++ .../openai/CreateTranscription/component.json | 63 ++++ .../openai/DescribeImages/DescribeImages.js | 71 +++++ .../ai/openai/DescribeImages/component.json | 70 +++++ .../GenerateEmbeddings/GenerateEmbeddings.js | 81 +++++ .../openai/GenerateEmbeddings/component.json | 90 ++++++ .../GenerateEmbeddingsFromFile.js | 147 +++++++++ .../GenerateEmbeddingsFromFile/component.json | 90 ++++++ .../ai/openai/GenerateImage/GenerateImage.js | 36 +++ .../ai/openai/GenerateImage/component.json | 70 +++++ .../ai/openai/ListModels/ListModels.js | 24 ++ .../ai/openai/ListModels/component.json | 30 ++ src/appmixer/ai/openai/Moderate/Moderate.js | 31 ++ .../ai/openai/Moderate/component.json | 296 ++++++++++++++++++ .../ai/openai/SendPrompt/SendPrompt.js | 40 +++ .../ai/openai/SendPrompt/component.json | 54 ++++ .../TransformTextToJSON.js | 65 ++++ .../openai/TransformTextToJSON/component.json | 65 ++++ src/appmixer/ai/openai/auth.js | 35 +++ src/appmixer/ai/openai/bundle.json | 9 + src/appmixer/ai/openai/module.json | 10 + src/appmixer/ai/openai/package.json | 10 + 35 files changed, 2090 insertions(+) create mode 100644 src/appmixer/ai/agenttools/ToolOutput/ToolOutput.js create mode 100644 src/appmixer/ai/agenttools/ToolOutput/component.json create mode 100644 src/appmixer/ai/agenttools/ToolOutput/icon.svg create mode 100644 src/appmixer/ai/agenttools/ToolStart/ToolStart.js create mode 100644 src/appmixer/ai/agenttools/ToolStart/component.json create mode 100644 src/appmixer/ai/agenttools/ToolStart/icon.svg create mode 100644 src/appmixer/ai/agenttools/bundle.json create mode 100644 src/appmixer/ai/agenttools/module.json create mode 100644 src/appmixer/ai/openai/AIAgent/AIAgent.js create mode 100644 src/appmixer/ai/openai/AIAgent/component.json create mode 100644 src/appmixer/ai/openai/AIAgent/icon.svg create mode 100644 src/appmixer/ai/openai/CreateSpeech/CreateSpeech.js create mode 100644 src/appmixer/ai/openai/CreateSpeech/component.json create mode 100644 src/appmixer/ai/openai/CreateTranscription/CreateTranscription.js create mode 100644 src/appmixer/ai/openai/CreateTranscription/component.json create mode 100644 src/appmixer/ai/openai/DescribeImages/DescribeImages.js create mode 100644 src/appmixer/ai/openai/DescribeImages/component.json create mode 100644 src/appmixer/ai/openai/GenerateEmbeddings/GenerateEmbeddings.js create mode 100644 src/appmixer/ai/openai/GenerateEmbeddings/component.json create mode 100644 src/appmixer/ai/openai/GenerateEmbeddingsFromFile/GenerateEmbeddingsFromFile.js create mode 100644 src/appmixer/ai/openai/GenerateEmbeddingsFromFile/component.json create mode 100644 src/appmixer/ai/openai/GenerateImage/GenerateImage.js create mode 100644 src/appmixer/ai/openai/GenerateImage/component.json create mode 100644 src/appmixer/ai/openai/ListModels/ListModels.js create mode 100644 src/appmixer/ai/openai/ListModels/component.json create mode 100644 src/appmixer/ai/openai/Moderate/Moderate.js create mode 100644 src/appmixer/ai/openai/Moderate/component.json create mode 100644 src/appmixer/ai/openai/SendPrompt/SendPrompt.js create mode 100644 src/appmixer/ai/openai/SendPrompt/component.json create mode 100644 src/appmixer/ai/openai/TransformTextToJSON/TransformTextToJSON.js create mode 100644 src/appmixer/ai/openai/TransformTextToJSON/component.json create mode 100644 src/appmixer/ai/openai/auth.js create mode 100644 src/appmixer/ai/openai/bundle.json create mode 100644 src/appmixer/ai/openai/module.json create mode 100644 src/appmixer/ai/openai/package.json diff --git a/src/appmixer/ai/agenttools/ToolOutput/ToolOutput.js b/src/appmixer/ai/agenttools/ToolOutput/ToolOutput.js new file mode 100644 index 000000000..571e7a702 --- /dev/null +++ b/src/appmixer/ai/agenttools/ToolOutput/ToolOutput.js @@ -0,0 +1,31 @@ +'use strict'; + +module.exports = { + + receive: async function(context) { + + // Find the toolCallId in the message scope, looking into the first + // component with type 'appmixer.utils.ai.ToolStart' and taking + // toolCallId from the output message that is stored in the flow state. + const flowDescriptor = context.flowDescriptor; + const scope = context.messages.in.scope; + let toolCallId; + for (const componentId of Object.keys(scope)) { + const component = flowDescriptor[componentId]; + if (component && component.type === 'appmixer.ai.agenttools.ToolStart') { + const key = componentId + ':' + context.messages.in.correlationId; + toolCallId = await context.flow.stateGet(key); + await context.flow.stateUnset(key); + } + } + + if (!toolCallId) { + await context.log({ step: 'no-tool-call-id', scope, flowDescriptor }); + throw new context.CancelError('No toolCallId found in the scope. Are you sure you used ai.ToolStart to start your tool chain?'); + } + + await context.log({ step: 'tool-output', toolCallId, output: context.messages.in.content.output }); + // The AI agent expects to see the output in the flow state under the toolCallId key. + return context.flow.stateSet(toolCallId, { output: context.messages.in.content.output }); + } +}; diff --git a/src/appmixer/ai/agenttools/ToolOutput/component.json b/src/appmixer/ai/agenttools/ToolOutput/component.json new file mode 100644 index 000000000..3e7a06a2a --- /dev/null +++ b/src/appmixer/ai/agenttools/ToolOutput/component.json @@ -0,0 +1,24 @@ +{ + "name": "appmixer.ai.agenttools.ToolOutput", + "author": "Appmixer ", + "description": "Determines the end of tool chain for AI Agent. Set the output of the tool chain. This output will be sent back to the AI Agent to serve as context.", + "inPorts": [{ + "name": "in", + "schema": { + "type": "object", + "properties": { + "output": { "type": "string" } + } + }, + "inspector": { + "inputs": { + "output": { + "type": "textarea", + "label": "Output", + "index": 1 + } + } + } + }], + "icon": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPGcgY2xpcC1wYXRoPSJ1cmwoI2NsaXAwXzUyMjFfMzI1MzcpIj4KPHBhdGggZD0iTTEyLjY2NjcgNi42NjY2N1YySDgiIHN0cm9rZT0iIzE4MTgxOCIgc3Ryb2tlLXdpZHRoPSIyLjEiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPgo8cGF0aCBkPSJNMTguNjY4MyA2LjY2NjVINS42NjgyOUM0LjM3OTYzIDYuNjY2NSAzLjMzNDk2IDcuNzExMTcgMy4zMzQ5NiA4Ljk5OTg0VjE4LjMzMzJDMy4zMzQ5NiAxOS42MjE4IDQuMzc5NjMgMjAuNjY2NSA1LjY2ODI5IDIwLjY2NjVIMTguNjY4M0MxOS45NTcgMjAuNjY2NSAyMS4wMDE2IDE5LjYyMTggMjEuMDAxNiAxOC4zMzMyVjguOTk5ODRDMjEuMDAxNiA3LjcxMTE3IDE5Ljk1NyA2LjY2NjUgMTguNjY4MyA2LjY2NjVaIiBzdHJva2U9IiMxODE4MTgiIHN0cm9rZS13aWR0aD0iMi4xIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz4KPHBhdGggZD0iTTEuMDAwOTggMTMuNjY2M0gzLjMzNDMxIiBzdHJva2U9IiMxODE4MTgiIHN0cm9rZS13aWR0aD0iMi4xIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz4KPHBhdGggZD0iTTIxLjAwMSAxMy42NjYzSDIzLjAzNDUiIHN0cm9rZT0iIzE4MTgxOCIgc3Ryb2tlLXdpZHRoPSIyLjEiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPgo8ZyBmaWx0ZXI9InVybCgjZmlsdGVyMF9kXzUyMjFfMzI1MzcpIj4KPHBhdGggZD0iTTcuMDAwOTggMTMuNDU5NUw5LjYwNzE4IDE2LjA2NTdDMTAuMzA2OCAxNi43NjUzIDExLjQ0OTUgMTYuNzM2NyAxMi4xMTMyIDE2LjAwM0wxNy4wMDEgMTAuNTk5OSIgc3Ryb2tlPSIjMjBCNzVBIiBzdHJva2Utd2lkdGg9IjIuODU1MTIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIvPgo8L2c+CjwvZz4KPGRlZnM+CjxmaWx0ZXIgaWQ9ImZpbHRlcjBfZF81MjIxXzMyNTM3IiB4PSIzLjQ3MjY5IiB5PSI3LjA3MTgxIiB3aWR0aD0iMTcuMDU2NiIgaGVpZ2h0PSIxMy4wMjg3IiBmaWx0ZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIGNvbG9yLWludGVycG9sYXRpb24tZmlsdGVycz0ic1JHQiI+CjxmZUZsb29kIGZsb29kLW9wYWNpdHk9IjAiIHJlc3VsdD0iQmFja2dyb3VuZEltYWdlRml4Ii8+CjxmZUNvbG9yTWF0cml4IGluPSJTb3VyY2VBbHBoYSIgdHlwZT0ibWF0cml4IiB2YWx1ZXM9IjAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDEyNyAwIiByZXN1bHQ9ImhhcmRBbHBoYSIvPgo8ZmVPZmZzZXQvPgo8ZmVHYXVzc2lhbkJsdXIgc3RkRGV2aWF0aW9uPSIxLjA1MDI3Ii8+CjxmZUNvbXBvc2l0ZSBpbjI9ImhhcmRBbHBoYSIgb3BlcmF0b3I9Im91dCIvPgo8ZmVDb2xvck1hdHJpeCB0eXBlPSJtYXRyaXgiIHZhbHVlcz0iMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMC4yNSAwIi8+CjxmZUJsZW5kIG1vZGU9Im5vcm1hbCIgaW4yPSJCYWNrZ3JvdW5kSW1hZ2VGaXgiIHJlc3VsdD0iZWZmZWN0MV9kcm9wU2hhZG93XzUyMjFfMzI1MzciLz4KPGZlQmxlbmQgbW9kZT0ibm9ybWFsIiBpbj0iU291cmNlR3JhcGhpYyIgaW4yPSJlZmZlY3QxX2Ryb3BTaGFkb3dfNTIyMV8zMjUzNyIgcmVzdWx0PSJzaGFwZSIvPgo8L2ZpbHRlcj4KPGNsaXBQYXRoIGlkPSJjbGlwMF81MjIxXzMyNTM3Ij4KPHJlY3Qgd2lkdGg9IjI0IiBoZWlnaHQ9IjI0IiBmaWxsPSJ3aGl0ZSIvPgo8L2NsaXBQYXRoPgo8L2RlZnM+Cjwvc3ZnPgo=" +} diff --git a/src/appmixer/ai/agenttools/ToolOutput/icon.svg b/src/appmixer/ai/agenttools/ToolOutput/icon.svg new file mode 100644 index 000000000..fa873f377 --- /dev/null +++ b/src/appmixer/ai/agenttools/ToolOutput/icon.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/appmixer/ai/agenttools/ToolStart/ToolStart.js b/src/appmixer/ai/agenttools/ToolStart/ToolStart.js new file mode 100644 index 000000000..45fc7668a --- /dev/null +++ b/src/appmixer/ai/agenttools/ToolStart/ToolStart.js @@ -0,0 +1,32 @@ +'use strict'; + +module.exports = { + + receive: async function(context) { + + if (context.properties.generateOutputPortOptions) { + return this.getOutputPortOptions(context); + } + const { toolCalls } = context.messages.in.originalContent; + for (const toolCall of toolCalls) { + // Process only those tool calls that are for this component. + // This is because the AI Agent fans out all tool calls by using sendJson(..., 'tools'). + if (toolCall.componentId === context.componentId) { + const out = { args: toolCall.args, toolCallId: toolCall.id }; + await context.flow.stateSet(context.componentId + ':' + context.messages.in.correlationId, toolCall.id); + await context.sendJson(out, 'out'); + } + } + }, + + getOutputPortOptions(context) { + + const options = []; + const parameters = context.properties.parameters?.ADD || []; + parameters.forEach(parameter => { + options.push({ label: parameter.name, value: 'args.' + parameter.name, schema: { type: parameter.type } }); + }); + options.push({ label: 'Tool Call ID', value: 'toolCallId', schema: { type: 'string' } }); + return context.sendJson(options, 'out'); + } +}; diff --git a/src/appmixer/ai/agenttools/ToolStart/component.json b/src/appmixer/ai/agenttools/ToolStart/component.json new file mode 100644 index 000000000..7d681203b --- /dev/null +++ b/src/appmixer/ai/agenttools/ToolStart/component.json @@ -0,0 +1,65 @@ +{ + "name": "appmixer.ai.agenttools.ToolStart", + "author": "Appmixer ", + "description": "Define a tool chain (i.e. a set of actions that might be selected by the AI model to run based on the user prompt). The tool can provide additional context to the AI agent to be able to reply to the user or call actions. This has to be the first component connected to the AI Agent tools output port. The tool chain must be ended with 'Tool Output'.", + "properties": { + "schema": { + "type": "object", + "properties": { + "generateOutputPortOptions": { "type": "boolean" }, + "description": { "type": "string" }, + "parameters": { "type": "object" } + } + }, + "inspector": { + "inputs": { + "description": { + "type": "textarea", + "label": "Description", + "index": 1 + }, + "parameters": { + "type": "expression", + "levels": ["ADD"], + "label": "Parameters", + "index": 2, + "fields": { + "name": { + "type": "text", + "label": "Name" + }, + "description": { + "type": "textarea", + "label": "Description" + }, + "type": { + "type": "select", + "label": "Type", + "options": [ + { "value": "string", "label": "String" }, + { "value": "number", "label": "Number" }, + { "value": "boolean", "label": "Boolean" } + ] + } + } + } + } + } + }, + "inPorts": [{ + "name": "in" + }], + "outPorts": [{ + "name": "out", + "source": { + "url": "/component/appmixer/ai/agenttools/ToolStart?outPort=out", + "data": { + "properties": { + "generateOutputPortOptions": true, + "parameters": "properties/parameters" + } + } + } + }], + "icon": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPGcgY2xpcC1wYXRoPSJ1cmwoI2NsaXAwXzUyMjFfMzI0OTcpIj4KPHBhdGggZD0iTTExLjk5OTggNS4yVjFINy43OTk4IiBzdHJva2U9IiMxODE4MTgiIHN0cm9rZS13aWR0aD0iMi4xIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz4KPHBhdGggZD0iTTcuNzk5NjEgMTcuOEwzLjU5OTYxIDIyVjcuMjk5OTVDMy41OTk2MSA2Ljc0MyAzLjgyMDg2IDYuMjA4ODUgNC4yMTQ2OSA1LjgxNTAzQzQuNjA4NTEgNS40MjEyIDUuMTQyNjUgNS4xOTk5NSA1LjY5OTYxIDUuMTk5OTVIMTguMjk5NkMxOC44NTY2IDUuMTk5OTUgMTkuMzkwNyA1LjQyMTIgMTkuNzg0NSA1LjgxNTAzQzIwLjE3ODQgNi4yMDg4NSAyMC4zOTk2IDYuNzQzIDIwLjM5OTYgNy4yOTk5NVYxNS43QzIwLjM5OTYgMTYuMjU2OSAyMC4xNzg0IDE2Ljc5MSAxOS43ODQ1IDE3LjE4NDlDMTkuMzkwNyAxNy41Nzg3IDE4Ljg1NjYgMTcuOCAxOC4yOTk2IDE3LjhINy43OTk2MVoiIHN0cm9rZT0iIzE4MTgxOCIgc3Ryb2tlLXdpZHRoPSIyLjEiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPgo8cGF0aCBkPSJNMS41IDExLjQ5OThIMy42IiBzdHJva2U9IiMxODE4MTgiIHN0cm9rZS13aWR0aD0iMi4xIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz4KPHBhdGggZD0iTTguODQ5NjEgMTAuNDVWMTIuNTUiIHN0cm9rZT0iIzE4MTgxOCIgc3Ryb2tlLXdpZHRoPSIyLjEiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPgo8cGF0aCBkPSJNMTUuMTUwNCAxMC40NVYxMi41NSIgc3Ryb2tlPSIjMTgxODE4IiBzdHJva2Utd2lkdGg9IjIuMSIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIi8+CjxwYXRoIGQ9Ik0yMC40MDA0IDExLjQ5OThIMjIuNTAwNCIgc3Ryb2tlPSIjMTgxODE4IiBzdHJva2Utd2lkdGg9IjIuMSIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIi8+CjwvZz4KPGRlZnM+CjxjbGlwUGF0aCBpZD0iY2xpcDBfNTIyMV8zMjQ5NyI+CjxyZWN0IHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgZmlsbD0id2hpdGUiLz4KPC9jbGlwUGF0aD4KPC9kZWZzPgo8L3N2Zz4K" +} diff --git a/src/appmixer/ai/agenttools/ToolStart/icon.svg b/src/appmixer/ai/agenttools/ToolStart/icon.svg new file mode 100644 index 000000000..b8e110fa5 --- /dev/null +++ b/src/appmixer/ai/agenttools/ToolStart/icon.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/src/appmixer/ai/agenttools/bundle.json b/src/appmixer/ai/agenttools/bundle.json new file mode 100644 index 000000000..20da803db --- /dev/null +++ b/src/appmixer/ai/agenttools/bundle.json @@ -0,0 +1,9 @@ +{ + "name": "appmixer.ai.agenttools", + "version": "1.0.0", + "changelog": { + "1.0.0": [ + "First version." + ] + } +} diff --git a/src/appmixer/ai/agenttools/module.json b/src/appmixer/ai/agenttools/module.json new file mode 100644 index 000000000..da1f12d3f --- /dev/null +++ b/src/appmixer/ai/agenttools/module.json @@ -0,0 +1,10 @@ +{ + "name": "appmixer.ai.agenttools", + "label": "Agent Tools", + "category": "ai", + "categoryIndex": 0, + "index": 2, + "categoryLabel": "AI", + "description": "Generic tool components for building AI agents.", + "icon": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPGcgY2xpcC1wYXRoPSJ1cmwoI2NsaXAwXzUyMjFfMzI0OTApIj4KPHBhdGggZD0iTTEyLjE1OTcgNi44Nzk5VjIuMzk5OUg3LjY3OTY5IiBzdHJva2U9IiMzMDMyMzYiIHN0cm9rZS13aWR0aD0iMi4wMTYiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPgo8cGF0aCBkPSJNMTcuOTIxMiA2Ljg3OTY0SDUuNDQxMTdDNC4yMDQwNSA2Ljg3OTY0IDMuMjAxMTcgNy44ODI1MiAzLjIwMTE3IDkuMTE5NjRWMTguMDc5NkMzLjIwMTE3IDE5LjMxNjggNC4yMDQwNSAyMC4zMTk2IDUuNDQxMTcgMjAuMzE5NkgxNy45MjEyQzE5LjE1ODMgMjAuMzE5NiAyMC4xNjEyIDE5LjMxNjggMjAuMTYxMiAxOC4wNzk2VjkuMTE5NjRDMjAuMTYxMiA3Ljg4MjUyIDE5LjE1ODMgNi44Nzk2NCAxNy45MjEyIDYuODc5NjRaIiBzdHJva2U9IiMzMDMyMzYiIHN0cm9rZS13aWR0aD0iMi4wMTYiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPgo8cGF0aCBkPSJNMC45NTk5NjEgMTMuNTk5NkgzLjE5OTk2IiBzdHJva2U9IiMzMDMyMzYiIHN0cm9rZS13aWR0aD0iMi4wMTYiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPgo8cGF0aCBkPSJNMjEuMTIwMSAxMy41OTk2SDIyLjQwMDEiIHN0cm9rZT0iIzMwMzIzNiIgc3Ryb2tlLXdpZHRoPSIyLjAxNiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIi8+CjxwYXRoIGQ9Ik0xNC43ODQyIDEyLjQ3OTdWMTQuNzE5NyIgc3Ryb2tlPSIjMzAzMjM2IiBzdHJva2Utd2lkdGg9IjIuMDE2IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz4KPHBhdGggZD0iTTguNjM5NjUgMTIuNDc5N1YxNC43MTk3IiBzdHJva2U9IiMzMDMyMzYiIHN0cm9rZS13aWR0aD0iMi4wMTYiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPgo8L2c+CjxkZWZzPgo8Y2xpcFBhdGggaWQ9ImNsaXAwXzUyMjFfMzI0OTAiPgo8cmVjdCB3aWR0aD0iMjQiIGhlaWdodD0iMjMuMDQiIGZpbGw9IndoaXRlIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgwIDAuNDc5OTgpIi8+CjwvY2xpcFBhdGg+CjwvZGVmcz4KPC9zdmc+Cg==" +} diff --git a/src/appmixer/ai/openai/AIAgent/AIAgent.js b/src/appmixer/ai/openai/AIAgent/AIAgent.js new file mode 100644 index 000000000..48a747f12 --- /dev/null +++ b/src/appmixer/ai/openai/AIAgent/AIAgent.js @@ -0,0 +1,232 @@ +'use strict'; + +const OpenAI = require('openai'); + +const COLLECT_TOOL_OUTPUTS_POLL_TIMEOUT = 60 * 1000; // 60 seconds +const COLLECT_TOOL_OUTPUTS_POLL_INTERVAL = 1 * 1000; // 1 second +const MAX_RUN_DURATION = 5 * 60 * 1000; // 5 minutes + +module.exports = { + + start: async function(context) { + + const assistant = await this.createAssistant(context); + return context.stateSet('assistant', assistant); + }, + + createAssistant: async function(context) { + + const flowDescriptor = context.flowDescriptor; + const agentComponentId = context.componentId; + const toolsPort = 'tools'; + + // Create a new assistant with tools defined in the branches connected to my 'tools' output port. + const tools = {}; + let error; + + // Find all components connected to my 'tools' output port. + Object.keys(flowDescriptor).forEach((componentId) => { + const component = flowDescriptor[componentId]; + const sources = component.source; + Object.keys(sources || {}).forEach((inPort) => { + const source = sources[inPort]; + if (source[agentComponentId] && source[agentComponentId].includes(toolsPort)) { + tools[componentId] = component; + if (component.type !== 'appmixer.ai.agenttools.ToolStart') { + error = `Component ${componentId} is not of type 'ToolStart' but ${comopnent.type}. + Every tool chain connected to the '${toolsPort}' port of the AI Agent + must start with 'ToolStart' and end with 'ToolOutput'. + This is where you describe what the tool does and what parameters should the AI model provide to it.`; + } + } + }); + }); + + // Teach the user via logs that they need to use the 'ToolStart' component. + if (error) { + throw new context.CancelError(error); + } + + const toolsDefinition = this.getToolsDefinition(tools); + + const instructions = context.properties.instructions || null; + await context.log({ step: 'create-assistant', tools: toolsDefinition, instructions }); + + const apiKey = context.auth.apiKey; + const client = new OpenAI({ apiKey }); + const assistant = await client.beta.assistants.create({ + model: context.properties.model || 'gpt-4o', + instructions, + tools: toolsDefinition + }); + + await context.log({ step: 'created-assistant', assistant }); + return assistant; + }, + + getToolsDefinition: function(tools) { + + // https://platform.openai.com/docs/assistants/tools/function-calling + const toolsDefinition = []; + + Object.keys(tools).forEach((componentId) => { + const component = tools[componentId]; + const parameters = component.config.properties.parameters?.ADD || []; + const toolParameters = { + type: 'object', + properties: {} + }; + parameters.forEach((parameter) => { + toolParameters.properties[parameter.name] = { + type: parameter.type, + description: parameter.description + }; + }); + const toolDefinition = { + type: 'function', + function: { + name: componentId, + description: component.config.properties.description, + } + }; + if (parameters.length) { + toolDefinition.function.parameters = toolParameters; + } + toolsDefinition.push(toolDefinition); + }); + return toolsDefinition; + }, + + handleRunStatus: async function(context, client, thread, run) { + + if (Date.now() - (run.created_at * 1000) > MAX_RUN_DURATION) { + await context.log({ step: 'run-timeout', run }); + await client.beta.threads.runs.cancel(thread.id, run.id); + throw new context.CancelError('The run took too long to complete.'); + } + + await context.log({ step: 'run-status', run }); + // Check if the run is completed + if (run.status === 'completed') { + let messages = await client.beta.threads.messages.list(thread.id); + await context.log({ step: 'completed-run', run, messages }); + await context.sendJson({ + answer: messages.data[0].content[0].text.value, + prompt: context.messages.in.content.prompt + }, 'out'); + } else if (run.status === 'requires_action') { + await this.handleRequiresAction(context, client, thread, run); + } else { + // incomplete, cancelled, failed, expired + await context.log({ step: 'unexpected-run-state', run }); + } + }, + + handleRequiresAction: async function(context, client, thread, run) { + + await context.log({ step: 'requires-action', run }); + + // Check if there are tools that require outputs. + if ( + run.required_action && + run.required_action.submit_tool_outputs && + run.required_action.submit_tool_outputs.tool_calls + ) { + const toolCalls = []; + for (const toolCall of run.required_action.submit_tool_outputs.tool_calls) { + const componentId = toolCall.function.name; + const args = JSON.parse(toolCall.function.arguments); + toolCalls.push({ componentId, args, id: toolCall.id }); + } + + // Send to all tools. Each ai.ToolStart ignores tool calls that are not intended for it. + await context.sendJson({ toolCalls, prompt: context.messages.in.content.prompt }, 'tools'); + + // Output of each tool is expected to be stored in the service state + // under the ID of the tool call. This is done in the ToolStartOutput component. + // Collect outputs of all the required tool calls. + await context.log({ step: 'collect-tools-output', threadId: thread.id, runId: run.id }); + const outputs = []; + const pollStart = Date.now(); + const runExpiresAt = run.expires_at; + while ( + (outputs.length < toolCalls.length) && + (runExpiresAt ? + Date.now() / 1000 < runExpiresAt : + Date.now() - pollStart < COLLECT_TOOL_OUTPUTS_POLL_TIMEOUT) + ) { + for (const toolCall of toolCalls) { + const result = await context.flow.stateGet(toolCall.id); + if (result) { + outputs.push({ tool_call_id: toolCall.id, output: result.output }); + await context.flow.stateUnset(toolCall.id); + } + } + // Sleep. + await new Promise((resolve) => setTimeout(resolve, COLLECT_TOOL_OUTPUTS_POLL_INTERVAL)); + } + await context.log({ step: 'collected-tools-output', threadId: thread.id, runId: run.id, outputs }); + + // Submit tool outputs to the assistant. + if (outputs && outputs.length) { + await context.log({ step: 'tool-outputs', tools: toolCalls, outputs }); + run = await client.beta.threads.runs.submitToolOutputsAndPoll( + thread.id, + run.id, + { tool_outputs: outputs } + ); + // Check status after submitting tool outputs. + await this.handleRunStatus(context, client, thread, run); + + } else { + await context.log({ step: 'no-tool-outputs', tools: toolCalls }); + } + } + }, + + receive: async function(context) { + + const { prompt } = context.messages.in.content; + let threadId = context.messages.in.content.threadId || context.messages.in.correlationId; + const apiKey = context.auth.apiKey; + const client = new OpenAI({ apiKey }); + const assistant = await context.stateGet('assistant'); + + // Check if a thread with a given ID exists. + let thread; + if (threadId) { + thread = await context.stateGet(threadId); + } + if (!thread) { + await context.log({ step: 'create-thread', assistantId: assistant.id, internalThreadId: threadId }); + thread = await client.beta.threads.create(); + await context.stateSet(threadId, thread); + } else { + await context.log({ step: 'use-thread', assistantId: assistant.id, thread }); + } + + await context.log({ step: 'create-thread-message', assistantId: assistant.id, threadId: thread.id }); + await client.beta.threads.messages.create(thread.id, { + role: 'user', + content: prompt + }); + + await context.log({ step: 'create-thread-run', assistantId: assistant.id, threadId: thread.id }); + let run = await client.beta.threads.runs.create(thread.id, { + assistant_id: assistant.id + }); + await context.log({ step: 'created-thread-run', assistantId: assistant.id, threadId: thread.id, run }); + + // Poll the run status until it reaches a terminal state. + run = await client.beta.threads.runs.poll(thread.id, run.id); + await this.handleRunStatus(context, client, thread, run); + }, + + stop: async function(context) { + + const apiKey = context.auth.apiKey; + const client = new OpenAI({ apiKey }); + const assistant = await context.stateGet('assistant'); + await client.beta.assistants.del(assistant.id); + } +}; diff --git a/src/appmixer/ai/openai/AIAgent/component.json b/src/appmixer/ai/openai/AIAgent/component.json new file mode 100644 index 000000000..63797b0da --- /dev/null +++ b/src/appmixer/ai/openai/AIAgent/component.json @@ -0,0 +1,86 @@ +{ + "name": "appmixer.ai.openai.AIAgent", + "author": "Appmixer ", + "description": "Build an AI agent responding with contextual answers or performing contextual actions.", + "auth": { + "service": "appmixer:ai:openai" + }, + "properties": { + "schema": { + "type": "object", + "properties": { + "model": { "type": "string" }, + "instructions": { "type": "string", "maxLength": 256000 } + } + }, + "inspector": { + "inputs": { + "model": { + "type": "text", + "index": 1, + "label": "Model", + "tooltip": "ID of the model to use.", + "defaultValue": "gpt-4o", + "source": { + "url": "/component/appmixer/ai/openai/ListModels?outPort=out", + "data": { + "transform": "./ListModels#toSelectOptions" + } + } + }, + "instructions": { + "type": "textarea", + "label": "Instructions", + "index": 2, + "tooltip": "The system instructions that the assistant uses. The maximum length is 256,000 characters. For example 'You are a personal math tutor.'." + } + } + } + }, + "inPorts": [{ + "name": "in", + "schema": { + "type": "object", + "properties": { + "prompt": { "type": "string" }, + "threadId": { "type": "string" } + }, + "required": ["prompt"] + }, + "inspector": { + "inputs": { + "prompt": { + "label": "Prompt", + "type": "textarea", + "index": 1 + }, + "threadId": { + "label": "Thread ID", + "type": "text", + "index": 2, + "tooltip": "By setting a thread ID you can keep the context of the conversation." + } + } + } + }], + "outPorts": [{ + "name": "out", + "options": [{ + "label": "Answer", + "value": "answer", + "schema": { "type": "string" } + }, { + "label": "Prompt", + "value": "prompt", + "schema": { "type": "string" } + }] + }, { + "name": "tools", + "options": [{ + "label": "Prompt", + "value": "prompt", + "schema": { "type": "string" } + }] + }], + "icon": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPGcgY2xpcC1wYXRoPSJ1cmwoI2NsaXAwXzUyMjFfMzI0OTApIj4KPHBhdGggZD0iTTEyLjE1OTcgNi44Nzk5VjIuMzk5OUg3LjY3OTY5IiBzdHJva2U9IiMzMDMyMzYiIHN0cm9rZS13aWR0aD0iMi4wMTYiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPgo8cGF0aCBkPSJNMTcuOTIxMiA2Ljg3OTY0SDUuNDQxMTdDNC4yMDQwNSA2Ljg3OTY0IDMuMjAxMTcgNy44ODI1MiAzLjIwMTE3IDkuMTE5NjRWMTguMDc5NkMzLjIwMTE3IDE5LjMxNjggNC4yMDQwNSAyMC4zMTk2IDUuNDQxMTcgMjAuMzE5NkgxNy45MjEyQzE5LjE1ODMgMjAuMzE5NiAyMC4xNjEyIDE5LjMxNjggMjAuMTYxMiAxOC4wNzk2VjkuMTE5NjRDMjAuMTYxMiA3Ljg4MjUyIDE5LjE1ODMgNi44Nzk2NCAxNy45MjEyIDYuODc5NjRaIiBzdHJva2U9IiMzMDMyMzYiIHN0cm9rZS13aWR0aD0iMi4wMTYiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPgo8cGF0aCBkPSJNMC45NTk5NjEgMTMuNTk5NkgzLjE5OTk2IiBzdHJva2U9IiMzMDMyMzYiIHN0cm9rZS13aWR0aD0iMi4wMTYiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPgo8cGF0aCBkPSJNMjEuMTIwMSAxMy41OTk2SDIyLjQwMDEiIHN0cm9rZT0iIzMwMzIzNiIgc3Ryb2tlLXdpZHRoPSIyLjAxNiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIi8+CjxwYXRoIGQ9Ik0xNC43ODQyIDEyLjQ3OTdWMTQuNzE5NyIgc3Ryb2tlPSIjMzAzMjM2IiBzdHJva2Utd2lkdGg9IjIuMDE2IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz4KPHBhdGggZD0iTTguNjM5NjUgMTIuNDc5N1YxNC43MTk3IiBzdHJva2U9IiMzMDMyMzYiIHN0cm9rZS13aWR0aD0iMi4wMTYiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPgo8L2c+CjxkZWZzPgo8Y2xpcFBhdGggaWQ9ImNsaXAwXzUyMjFfMzI0OTAiPgo8cmVjdCB3aWR0aD0iMjQiIGhlaWdodD0iMjMuMDQiIGZpbGw9IndoaXRlIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgwIDAuNDc5OTgpIi8+CjwvY2xpcFBhdGg+CjwvZGVmcz4KPC9zdmc+Cg==" +} diff --git a/src/appmixer/ai/openai/AIAgent/icon.svg b/src/appmixer/ai/openai/AIAgent/icon.svg new file mode 100644 index 000000000..640f610a1 --- /dev/null +++ b/src/appmixer/ai/openai/AIAgent/icon.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/src/appmixer/ai/openai/CreateSpeech/CreateSpeech.js b/src/appmixer/ai/openai/CreateSpeech/CreateSpeech.js new file mode 100644 index 000000000..ff26ed93e --- /dev/null +++ b/src/appmixer/ai/openai/CreateSpeech/CreateSpeech.js @@ -0,0 +1,29 @@ +'use strict'; + +module.exports = { + + receive: async function(context) { + + const { input, voice, responseFormat, speed, model } = context.messages.in.content; + const apiKey = context.auth.apiKey; + + const url = 'https://api.openai.com/v1/audio/speech'; + const { data: readStream } = await context.httpRequest.post(url, { + model: model || 'tts-1', + input, + voice, + response_format: responseFormat, + speed + }, { + responseType: 'stream', + headers: { + 'Authorization': `Bearer ${apiKey}`, + 'Content-Type': 'application/json' + } + }); + + const filename = `generated-audio-${(new Date).toISOString()}.${responseFormat}`; + const file = await context.saveFileStream(filename, readStream); + return context.sendJson({ fileId: file.fileId, input, fileSize: file.length }, 'out'); + } +}; diff --git a/src/appmixer/ai/openai/CreateSpeech/component.json b/src/appmixer/ai/openai/CreateSpeech/component.json new file mode 100644 index 000000000..ccce45051 --- /dev/null +++ b/src/appmixer/ai/openai/CreateSpeech/component.json @@ -0,0 +1,95 @@ +{ + "name": "appmixer.ai.openai.CreateSpeech", + "author": "Appmixer ", + "description": "Generates audio from the input text.", + "auth": { + "service": "appmixer:ai:openai" + }, + "inPorts": [{ + "name": "in", + "schema": { + "type": "object", + "properties": { + "input": { "type": "string" }, + "voice": { "type": "string" }, + "responseFormat": { "type": "string" }, + "speed": { "type": "number" }, + "model": { "type": "string" } + }, + "required": ["input"] + }, + "inspector": { + "inputs": { + "input": { + "label": "Text", + "type": "textarea", + "index": 1 + }, + "voice": { + "label": "Voice", + "type": "select", + "defaultValue": "alloy", + "options": [ + { "label": "Alloy (woman)", "value": "alloy" }, + { "label": "Echo (man)", "value": "echo" }, + { "label": "Fable (woman)", "value": "fable" }, + { "label": "Onyx (man)", "value": "onyx" }, + { "label": "Nova (woman)", "value": "nova" }, + { "label": "Shimmer (woman)", "value": "shimmer" } + ], + "index": 2 + }, + "responseFormat": { + "label": "Response Format", + "type": "select", + "defaultValue": "mp3", + "options": [ + { "label": "mp3", "value": "mp3" }, + { "label": "wav", "value": "wav" }, + { "label": "aac", "value": "aac" }, + { "label": "flac", "value": "flac" }, + { "label": "opus", "value": "opus" }, + { "label": "pcm", "value": "pcm" } + ], + "index": 3 + }, + "speed": { + "label": "Speed", + "type": "number", + "index": 4, + "defaultValue": 1 + }, + "model": { + "type": "text", + "index": 5, + "label": "Model", + "tooltip": "ID of the model to use.", + "defaultValue": "tts-1", + "source": { + "url": "/component/appmixer/ai/openai/ListModels?outPort=out", + "data": { + "transform": "./ListModels#toSelectOptions" + } + } + } + } + } + }], + "outPorts": [{ + "name": "out", + "options": [{ + "label": "File ID", + "value": "fileId", + "schema": { "type": "string", "format": "appmixer-file-id" } + },{ + "label": "File Size", + "value": "fileSize", + "schema": { "type": "number" } + },{ + "label": "Text", + "value": "input", + "schema": { "type": "string" } + }] + }], + "icon": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNHB4IiBoZWlnaHQ9IjI0cHgiIGZpbGw9ImN1cnJlbnRDb2xvciIgdmlld0JveD0iMCAwIDI0IDI0IiBjb2xvcj0iYmxhY2siPjxwYXRoIGQ9Ik0yMi40MTggOS44MjJhNS45MDMgNS45MDMgMCAwIDAtLjUyLTQuOTEgNi4xIDYuMSAwIDAgMC0yLjgyMi0yLjUxMyA2LjIwNCA2LjIwNCAwIDAgMC0zLjc4LS4zODlBNi4wNTUgNi4wNTUgMCAwIDAgMTMuMjMyLjUxOCA2LjEyOSA2LjEyOSAwIDAgMCAxMC43MjYgMGE2LjE4NSA2LjE4NSAwIDAgMC0zLjYxNSAxLjE1M0E2LjA1MiA2LjA1MiAwIDAgMCA0Ljg4IDQuMTg3YTYuMTAyIDYuMTAyIDAgMCAwLTIuMzQ0IDEuMDE4QTYuMDA4IDYuMDA4IDAgMCAwIC44MjggNy4wODdhNS45ODEgNS45ODEgMCAwIDAgLjc1NCA3LjA5IDUuOTA0IDUuOTA0IDAgMCAwIC41MiA0LjkxMSA2LjEwMSA2LjEwMSAwIDAgMCAyLjgyMSAyLjUxMyA2LjIwNSA2LjIwNSAwIDAgMCAzLjc4LjM4OSA2LjA1NyA2LjA1NyAwIDAgMCAyLjA2NSAxLjQ5MiA2LjEzIDYuMTMgMCAwIDAgMi41MDUuNTE4IDYuMTg1IDYuMTg1IDAgMCAwIDMuNjE3LTEuMTU0IDYuMDUyIDYuMDUyIDAgMCAwIDIuMjMyLTMuMDM1IDYuMTAxIDYuMTAxIDAgMCAwIDIuMzQzLTEuMDE4IDYuMDA5IDYuMDA5IDAgMCAwIDEuNzA5LTEuODgzIDUuOTgxIDUuOTgxIDAgMCAwLS43NTYtNy4wODhabS05LjE0MyAxMi42MDlhNC41ODMgNC41ODMgMCAwIDEtMi45MTgtMS4wNGMuMDM3LS4wMi4xMDItLjA1Ni4xNDQtLjA4MWw0Ljg0NC0yLjc2YS43ODMuNzgzIDAgMCAwIC4zOTctLjY4di02LjczOEwxNy43OSAxMi4zYS4wNzIuMDcyIDAgMCAxIC4wNC4wNTV2NS41OGE0LjQ3MyA0LjQ3MyAwIDAgMS0xLjMzNSAzLjE3NiA0LjU5NiA0LjU5NiAwIDAgMS0zLjIxOSAxLjMyMVptLTkuNzkzLTQuMTI3YTQuNDMyIDQuNDMyIDAgMCAxLS41NDQtMy4wMTRjLjAzNi4wMjEuMDk5LjA2LjE0NC4wODVsNC44NDMgMi43NmEuNzk2Ljc5NiAwIDAgMCAuNzk1IDBsNS45MTMtMy4zNjlWMTcuMWEuMDcxLjA3MSAwIDAgMS0uMDI5LjA2Mkw5LjcwOCAxOS45NWE0LjYxNyA0LjYxNyAwIDAgMS0zLjQ1OC40NDcgNC41NTYgNC41NTYgMCAwIDEtMi43NjgtMi4wOTNaTTIuMjA4IDcuODcyQTQuNTI3IDQuNTI3IDAgMCAxIDQuNTggNS45bC0uMDAyLjE2NHY1LjUyYS43NjguNzY4IDAgMCAwIC4zOTcuNjhsNS45MTMgMy4zNjktMi4wNDcgMS4xNjZhLjA3NS4wNzUgMCAwIDEtLjA2OS4wMDZsLTQuODk2LTIuNzkyYTQuNTEgNC41MSAwIDAgMS0yLjEyLTIuNzMgNC40NSA0LjQ1IDAgMCAxIC40NTItMy40MTFabTE2LjgxOCAzLjg2MS01LjkxMy0zLjM2OCAyLjA0Ny0xLjE2NmEuMDc0LjA3NCAwIDAgMSAuMDctLjAwNmw0Ljg5NiAyLjc4OWE0LjUyNiA0LjUyNiAwIDAgMSAxLjc2MiAxLjgxNSA0LjQ0OCA0LjQ0OCAwIDAgMS0uNDE4IDQuODA4IDQuNTU2IDQuNTU2IDAgMCAxLTIuMDQ5IDEuNDk0di01LjY4NmEuNzY3Ljc2NyAwIDAgMC0uMzk1LS42OFptMi4wMzgtMy4wMjVhNi44NzQgNi44NzQgMCAwIDAtLjE0NC0uMDg1bC00Ljg0My0yLjc2YS43OTcuNzk3IDAgMCAwLS43OTYgMEw5LjM2OCA5LjIzVjYuOWEuMDcyLjA3MiAwIDAgMSAuMDMtLjA2Mmw0Ljg5NS0yLjc4N2E0LjYwOCA0LjYwOCAwIDAgMSA0Ljg4NS4yMDcgNC41MSA0LjUxIDAgMCAxIDEuNTk5IDEuOTU1Yy4zMzMuNzg4LjQzMyAxLjY1NC4yODcgMi40OTZaTTguMjU1IDEyLjg2NSA2LjIwOCAxMS43YS4wNzEuMDcxIDAgMCAxLS4wNC0uMDU2di01LjU4YzAtLjg1NC4yNDgtMS42OS43MTMtMi40MTJhNC41NCA0LjU0IDAgMCAxIDEuOTEzLTEuNjU4IDQuNjE0IDQuNjE0IDAgMCAxIDQuODUuNjE2Yy0uMDM3LjAyLS4xMDIuMDU1LS4xNDQuMDhMOC42NTcgNS40NTJhLjc4Mi43ODIgMCAwIDAtLjM5OC42OGwtLjAwNCA2LjczNFpNOS4zNjcgMTAuNSAxMi4wMDEgOWwyLjYzMyAxLjV2M0wxMi4wMDEgMTVsLTIuNjM0LTEuNXYtM1oiLz48L3N2Zz4=" +} diff --git a/src/appmixer/ai/openai/CreateTranscription/CreateTranscription.js b/src/appmixer/ai/openai/CreateTranscription/CreateTranscription.js new file mode 100644 index 000000000..138cd94c2 --- /dev/null +++ b/src/appmixer/ai/openai/CreateTranscription/CreateTranscription.js @@ -0,0 +1,34 @@ +'use strict'; + +const FormData = require('form-data'); + +module.exports = { + + receive: async function(context) { + + const { fileId, responseFormat, model } = context.messages.in.content; + const apiKey = context.auth.apiKey; + + const readStream = await context.getFileReadStream(fileId); + const fileInfo = await context.getFileInfo(fileId); + const formData = new FormData(); + + formData.append('file', readStream, { + filename: fileInfo.filename, + contentType: fileInfo.contentType, + knownLength: fileInfo.length + }); + formData.append('model', model || 'whisper-1'); + formData.append('response_format', responseFormat || 'text'); + + const url = 'https://api.openai.com/v1/audio/transcriptions'; + const { data } = await context.httpRequest.post(url, formData, { + headers: Object.assign({ + 'Authorization': `Bearer ${apiKey}` + }, formData.getHeaders()) + }); + + await context.log({ step: 'response', data: (data || '').substring(0, 100) }); + return context.sendJson({ text: data }, 'out'); + } +}; diff --git a/src/appmixer/ai/openai/CreateTranscription/component.json b/src/appmixer/ai/openai/CreateTranscription/component.json new file mode 100644 index 000000000..445522b23 --- /dev/null +++ b/src/appmixer/ai/openai/CreateTranscription/component.json @@ -0,0 +1,63 @@ +{ + "name": "appmixer.ai.openai.CreateTranscription", + "author": "Appmixer ", + "description": "Transcribes audio into the input language.", + "auth": { + "service": "appmixer:ai:openai" + }, + "inPorts": [{ + "name": "in", + "schema": { + "type": "object", + "properties": { + "fileId": { "type": "string" }, + "responseFormat": { "type": "string" }, + "model": { "type": "string" } + }, + "required": ["fileId"] + }, + "inspector": { + "inputs": { + "fileId": { + "label": "File ID", + "type": "filepicker", + "index": 1, + "tooltip": "The audio file to transcribe, in one of these formats: flac, mp3, mp4, mpeg, mpga, m4a, ogg, wav, or webm." + }, + "responseFormat": { + "label": "Response Format", + "type": "select", + "index": 2, + "defaultValue": "text", + "options": [ + { "label": "Text", "value": "text" }, + { "label": "SRT", "value": "srt" }, + { "label": "VTT", "value": "vtt" } + ] + }, + "model": { + "type": "text", + "index": 3, + "label": "Model", + "tooltip": "ID of the model to use.", + "defaultValue": "whisper-1", + "source": { + "url": "/component/appmixer/ai/openai/ListModels?outPort=out", + "data": { + "transform": "./ListModels#toSelectOptions" + } + } + } + } + } + }], + "outPorts": [{ + "name": "out", + "options": [{ + "label": "Text", + "value": "text", + "schema": { "type": "string" } + }] + }], + "icon": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNHB4IiBoZWlnaHQ9IjI0cHgiIGZpbGw9ImN1cnJlbnRDb2xvciIgdmlld0JveD0iMCAwIDI0IDI0IiBjb2xvcj0iYmxhY2siPjxwYXRoIGQ9Ik0yMi40MTggOS44MjJhNS45MDMgNS45MDMgMCAwIDAtLjUyLTQuOTEgNi4xIDYuMSAwIDAgMC0yLjgyMi0yLjUxMyA2LjIwNCA2LjIwNCAwIDAgMC0zLjc4LS4zODlBNi4wNTUgNi4wNTUgMCAwIDAgMTMuMjMyLjUxOCA2LjEyOSA2LjEyOSAwIDAgMCAxMC43MjYgMGE2LjE4NSA2LjE4NSAwIDAgMC0zLjYxNSAxLjE1M0E2LjA1MiA2LjA1MiAwIDAgMCA0Ljg4IDQuMTg3YTYuMTAyIDYuMTAyIDAgMCAwLTIuMzQ0IDEuMDE4QTYuMDA4IDYuMDA4IDAgMCAwIC44MjggNy4wODdhNS45ODEgNS45ODEgMCAwIDAgLjc1NCA3LjA5IDUuOTA0IDUuOTA0IDAgMCAwIC41MiA0LjkxMSA2LjEwMSA2LjEwMSAwIDAgMCAyLjgyMSAyLjUxMyA2LjIwNSA2LjIwNSAwIDAgMCAzLjc4LjM4OSA2LjA1NyA2LjA1NyAwIDAgMCAyLjA2NSAxLjQ5MiA2LjEzIDYuMTMgMCAwIDAgMi41MDUuNTE4IDYuMTg1IDYuMTg1IDAgMCAwIDMuNjE3LTEuMTU0IDYuMDUyIDYuMDUyIDAgMCAwIDIuMjMyLTMuMDM1IDYuMTAxIDYuMTAxIDAgMCAwIDIuMzQzLTEuMDE4IDYuMDA5IDYuMDA5IDAgMCAwIDEuNzA5LTEuODgzIDUuOTgxIDUuOTgxIDAgMCAwLS43NTYtNy4wODhabS05LjE0MyAxMi42MDlhNC41ODMgNC41ODMgMCAwIDEtMi45MTgtMS4wNGMuMDM3LS4wMi4xMDItLjA1Ni4xNDQtLjA4MWw0Ljg0NC0yLjc2YS43ODMuNzgzIDAgMCAwIC4zOTctLjY4di02LjczOEwxNy43OSAxMi4zYS4wNzIuMDcyIDAgMCAxIC4wNC4wNTV2NS41OGE0LjQ3MyA0LjQ3MyAwIDAgMS0xLjMzNSAzLjE3NiA0LjU5NiA0LjU5NiAwIDAgMS0zLjIxOSAxLjMyMVptLTkuNzkzLTQuMTI3YTQuNDMyIDQuNDMyIDAgMCAxLS41NDQtMy4wMTRjLjAzNi4wMjEuMDk5LjA2LjE0NC4wODVsNC44NDMgMi43NmEuNzk2Ljc5NiAwIDAgMCAuNzk1IDBsNS45MTMtMy4zNjlWMTcuMWEuMDcxLjA3MSAwIDAgMS0uMDI5LjA2Mkw5LjcwOCAxOS45NWE0LjYxNyA0LjYxNyAwIDAgMS0zLjQ1OC40NDcgNC41NTYgNC41NTYgMCAwIDEtMi43NjgtMi4wOTNaTTIuMjA4IDcuODcyQTQuNTI3IDQuNTI3IDAgMCAxIDQuNTggNS45bC0uMDAyLjE2NHY1LjUyYS43NjguNzY4IDAgMCAwIC4zOTcuNjhsNS45MTMgMy4zNjktMi4wNDcgMS4xNjZhLjA3NS4wNzUgMCAwIDEtLjA2OS4wMDZsLTQuODk2LTIuNzkyYTQuNTEgNC41MSAwIDAgMS0yLjEyLTIuNzMgNC40NSA0LjQ1IDAgMCAxIC40NTItMy40MTFabTE2LjgxOCAzLjg2MS01LjkxMy0zLjM2OCAyLjA0Ny0xLjE2NmEuMDc0LjA3NCAwIDAgMSAuMDctLjAwNmw0Ljg5NiAyLjc4OWE0LjUyNiA0LjUyNiAwIDAgMSAxLjc2MiAxLjgxNSA0LjQ0OCA0LjQ0OCAwIDAgMS0uNDE4IDQuODA4IDQuNTU2IDQuNTU2IDAgMCAxLTIuMDQ5IDEuNDk0di01LjY4NmEuNzY3Ljc2NyAwIDAgMC0uMzk1LS42OFptMi4wMzgtMy4wMjVhNi44NzQgNi44NzQgMCAwIDAtLjE0NC0uMDg1bC00Ljg0My0yLjc2YS43OTcuNzk3IDAgMCAwLS43OTYgMEw5LjM2OCA5LjIzVjYuOWEuMDcyLjA3MiAwIDAgMSAuMDMtLjA2Mmw0Ljg5NS0yLjc4N2E0LjYwOCA0LjYwOCAwIDAgMSA0Ljg4NS4yMDcgNC41MSA0LjUxIDAgMCAxIDEuNTk5IDEuOTU1Yy4zMzMuNzg4LjQzMyAxLjY1NC4yODcgMi40OTZaTTguMjU1IDEyLjg2NSA2LjIwOCAxMS43YS4wNzEuMDcxIDAgMCAxLS4wNC0uMDU2di01LjU4YzAtLjg1NC4yNDgtMS42OS43MTMtMi40MTJhNC41NCA0LjU0IDAgMCAxIDEuOTEzLTEuNjU4IDQuNjE0IDQuNjE0IDAgMCAxIDQuODUuNjE2Yy0uMDM3LjAyLS4xMDIuMDU1LS4xNDQuMDhMOC42NTcgNS40NTJhLjc4Mi43ODIgMCAwIDAtLjM5OC42OGwtLjAwNCA2LjczNFpNOS4zNjcgMTAuNSAxMi4wMDEgOWwyLjYzMyAxLjV2M0wxMi4wMDEgMTVsLTIuNjM0LTEuNXYtM1oiLz48L3N2Zz4=" +} diff --git a/src/appmixer/ai/openai/DescribeImages/DescribeImages.js b/src/appmixer/ai/openai/DescribeImages/DescribeImages.js new file mode 100644 index 000000000..d3a7e51b1 --- /dev/null +++ b/src/appmixer/ai/openai/DescribeImages/DescribeImages.js @@ -0,0 +1,71 @@ +'use strict'; + +const path = require('path'); + +module.exports = { + + receive: async function(context) { + + const { prompt, images, model } = context.messages.in.content; + const apiKey = context.auth.apiKey; + const imageFileIds = (images.ADD || []).map(image => (image.fileId || null)).filter(fileId => fileId !== null); + + const imageContent = await Promise.all(imageFileIds.map(async (fileId) => { + const fileInfo = await context.getFileInfo(fileId); + const fileContent = await context.loadFile(fileId); + const base64 = fileContent.toString('base64'); + + let contentType = fileInfo.contentType; + if (!contentType) { + const ext = path.extname(fileInfo.filename).toLowerCase(); + if (ext === '.png') { + contentType = 'image/png'; + } else if (ext === '.jpg' || ext === '.jpeg') { + contentType = 'image/jpeg'; + } else if (ext === '.gif') { + contentType = 'image/gif'; + } else if (ext === '.webp') { + contentType = 'image/webp'; + } + } + + if (!['image/png', 'image/jpeg', 'image/gif', 'image/webp'].includes(contentType)) { + throw new Error(`Unsupported image type: ${contentType}`); + } + return { + type: 'image_url', + image_url: { + url: `data:${contentType};base64,${base64}` + } + }; + })); + + const url = 'https://api.openai.com/v1/chat/completions'; + const payload = { + model: model || 'gpt-4o', + messages: [ + { + role: 'user', + content: [{ + type: 'text', + text: prompt + }].concat(imageContent) + } + ] + }; + const { data } = await context.httpRequest.post(url, payload, { + headers: { + 'Authorization': `Bearer ${apiKey}`, + 'Content-Type': 'application/json' + } + }); + + let answer = ''; + + if (data && data.choices) { + answer = data.choices[0].message.content; + } + + return context.sendJson({ answer, prompt }, 'out'); + } +}; diff --git a/src/appmixer/ai/openai/DescribeImages/component.json b/src/appmixer/ai/openai/DescribeImages/component.json new file mode 100644 index 000000000..e6bd179d5 --- /dev/null +++ b/src/appmixer/ai/openai/DescribeImages/component.json @@ -0,0 +1,70 @@ +{ + "name": "appmixer.ai.openai.DescribeImages", + "author": "Appmixer ", + "description": "Answer questions about the content of one or more images.", + "auth": { + "service": "appmixer:ai:openai" + }, + "inPorts": [{ + "name": "in", + "schema": { + "type": "object", + "properties": { + "prompt": { "type": "string" }, + "images": {}, + "model": { "type": "string" } + }, + "required": ["prompt", "images"] + }, + "inspector": { + "inputs": { + "prompt": { + "label": "Prompt", + "type": "textarea", + "index": 1, + "tooltip": "The question to answer about the content of one or more images." + }, + "images": { + "label": "Images", + "type": "expression", + "index": 2, + "levels": ["ADD"], + "fields": { + "fileId": { + "type": "filepicker", + "label": "File ID", + "tooltip": "The image file to attach. Only PNG, JPEG, non-animated GIF and WEBP image types are supported.", + "index": 1 + } + }, + "model": { + "type": "text", + "index": 3, + "label": "Model", + "tooltip": "ID of the model to use.", + "defaultValue": "gpt-4o", + "source": { + "url": "/component/appmixer/ai/openai/ListModels?outPort=out", + "data": { + "transform": "./ListModels#toSelectOptions" + } + } + } + } + } + } + }], + "outPorts": [{ + "name": "out", + "options": [{ + "label": "Answer", + "value": "answer", + "schema": { "type": "string" } + }, { + "label": "Prompt", + "value": "prompt", + "schema": { "type": "string" } + }] + }], + "icon": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNHB4IiBoZWlnaHQ9IjI0cHgiIGZpbGw9ImN1cnJlbnRDb2xvciIgdmlld0JveD0iMCAwIDI0IDI0IiBjb2xvcj0iYmxhY2siPjxwYXRoIGQ9Ik0yMi40MTggOS44MjJhNS45MDMgNS45MDMgMCAwIDAtLjUyLTQuOTEgNi4xIDYuMSAwIDAgMC0yLjgyMi0yLjUxMyA2LjIwNCA2LjIwNCAwIDAgMC0zLjc4LS4zODlBNi4wNTUgNi4wNTUgMCAwIDAgMTMuMjMyLjUxOCA2LjEyOSA2LjEyOSAwIDAgMCAxMC43MjYgMGE2LjE4NSA2LjE4NSAwIDAgMC0zLjYxNSAxLjE1M0E2LjA1MiA2LjA1MiAwIDAgMCA0Ljg4IDQuMTg3YTYuMTAyIDYuMTAyIDAgMCAwLTIuMzQ0IDEuMDE4QTYuMDA4IDYuMDA4IDAgMCAwIC44MjggNy4wODdhNS45ODEgNS45ODEgMCAwIDAgLjc1NCA3LjA5IDUuOTA0IDUuOTA0IDAgMCAwIC41MiA0LjkxMSA2LjEwMSA2LjEwMSAwIDAgMCAyLjgyMSAyLjUxMyA2LjIwNSA2LjIwNSAwIDAgMCAzLjc4LjM4OSA2LjA1NyA2LjA1NyAwIDAgMCAyLjA2NSAxLjQ5MiA2LjEzIDYuMTMgMCAwIDAgMi41MDUuNTE4IDYuMTg1IDYuMTg1IDAgMCAwIDMuNjE3LTEuMTU0IDYuMDUyIDYuMDUyIDAgMCAwIDIuMjMyLTMuMDM1IDYuMTAxIDYuMTAxIDAgMCAwIDIuMzQzLTEuMDE4IDYuMDA5IDYuMDA5IDAgMCAwIDEuNzA5LTEuODgzIDUuOTgxIDUuOTgxIDAgMCAwLS43NTYtNy4wODhabS05LjE0MyAxMi42MDlhNC41ODMgNC41ODMgMCAwIDEtMi45MTgtMS4wNGMuMDM3LS4wMi4xMDItLjA1Ni4xNDQtLjA4MWw0Ljg0NC0yLjc2YS43ODMuNzgzIDAgMCAwIC4zOTctLjY4di02LjczOEwxNy43OSAxMi4zYS4wNzIuMDcyIDAgMCAxIC4wNC4wNTV2NS41OGE0LjQ3MyA0LjQ3MyAwIDAgMS0xLjMzNSAzLjE3NiA0LjU5NiA0LjU5NiAwIDAgMS0zLjIxOSAxLjMyMVptLTkuNzkzLTQuMTI3YTQuNDMyIDQuNDMyIDAgMCAxLS41NDQtMy4wMTRjLjAzNi4wMjEuMDk5LjA2LjE0NC4wODVsNC44NDMgMi43NmEuNzk2Ljc5NiAwIDAgMCAuNzk1IDBsNS45MTMtMy4zNjlWMTcuMWEuMDcxLjA3MSAwIDAgMS0uMDI5LjA2Mkw5LjcwOCAxOS45NWE0LjYxNyA0LjYxNyAwIDAgMS0zLjQ1OC40NDcgNC41NTYgNC41NTYgMCAwIDEtMi43NjgtMi4wOTNaTTIuMjA4IDcuODcyQTQuNTI3IDQuNTI3IDAgMCAxIDQuNTggNS45bC0uMDAyLjE2NHY1LjUyYS43NjguNzY4IDAgMCAwIC4zOTcuNjhsNS45MTMgMy4zNjktMi4wNDcgMS4xNjZhLjA3NS4wNzUgMCAwIDEtLjA2OS4wMDZsLTQuODk2LTIuNzkyYTQuNTEgNC41MSAwIDAgMS0yLjEyLTIuNzMgNC40NSA0LjQ1IDAgMCAxIC40NTItMy40MTFabTE2LjgxOCAzLjg2MS01LjkxMy0zLjM2OCAyLjA0Ny0xLjE2NmEuMDc0LjA3NCAwIDAgMSAuMDctLjAwNmw0Ljg5NiAyLjc4OWE0LjUyNiA0LjUyNiAwIDAgMSAxLjc2MiAxLjgxNSA0LjQ0OCA0LjQ0OCAwIDAgMS0uNDE4IDQuODA4IDQuNTU2IDQuNTU2IDAgMCAxLTIuMDQ5IDEuNDk0di01LjY4NmEuNzY3Ljc2NyAwIDAgMC0uMzk1LS42OFptMi4wMzgtMy4wMjVhNi44NzQgNi44NzQgMCAwIDAtLjE0NC0uMDg1bC00Ljg0My0yLjc2YS43OTcuNzk3IDAgMCAwLS43OTYgMEw5LjM2OCA5LjIzVjYuOWEuMDcyLjA3MiAwIDAgMSAuMDMtLjA2Mmw0Ljg5NS0yLjc4N2E0LjYwOCA0LjYwOCAwIDAgMSA0Ljg4NS4yMDcgNC41MSA0LjUxIDAgMCAxIDEuNTk5IDEuOTU1Yy4zMzMuNzg4LjQzMyAxLjY1NC4yODcgMi40OTZaTTguMjU1IDEyLjg2NSA2LjIwOCAxMS43YS4wNzEuMDcxIDAgMCAxLS4wNC0uMDU2di01LjU4YzAtLjg1NC4yNDgtMS42OS43MTMtMi40MTJhNC41NCA0LjU0IDAgMCAxIDEuOTEzLTEuNjU4IDQuNjE0IDQuNjE0IDAgMCAxIDQuODUuNjE2Yy0uMDM3LjAyLS4xMDIuMDU1LS4xNDQuMDhMOC42NTcgNS40NTJhLjc4Mi43ODIgMCAwIDAtLjM5OC42OGwtLjAwNCA2LjczNFpNOS4zNjcgMTAuNSAxMi4wMDEgOWwyLjYzMyAxLjV2M0wxMi4wMDEgMTVsLTIuNjM0LTEuNXYtM1oiLz48L3N2Zz4=" +} diff --git a/src/appmixer/ai/openai/GenerateEmbeddings/GenerateEmbeddings.js b/src/appmixer/ai/openai/GenerateEmbeddings/GenerateEmbeddings.js new file mode 100644 index 000000000..6883313b9 --- /dev/null +++ b/src/appmixer/ai/openai/GenerateEmbeddings/GenerateEmbeddings.js @@ -0,0 +1,81 @@ +'use strict'; + +const { RecursiveCharacterTextSplitter } = require('langchain/text_splitter'); +const OpenAI = require('openai'); + +// See https://platform.openai.com/docs/api-reference/embeddings/create#embeddings-create-input. +const MAX_INPUT_LENGTH = 8192 * 4; // max 8192 tokens, 1 token ~ 4 characters. +const MAX_BATCH_SIZE = 2048; + +module.exports = { + + receive: async function(context) { + + const messageId = context.messages.in.messageId; + const { + text, + model = 'text-embedding-ada-002', + chunkSize = 500, + chunkOverlap = 50 + } = context.messages.in.content; + + const chunks = await this.splitText(text, chunkSize, chunkOverlap); + await context.log({ step: 'split-text', message: 'Text succesfully split into chunks.', chunksLength: chunks.length }); + + const apiKey = context.auth.apiKey; + const client = new OpenAI({ apiKey }); + + // Process chunks in batches. + // the batch size is calculated based on the chunk size and the maximum input length in + // order not to exceed the maximum input length defined in + // https://platform.openai.com/docs/api-reference/embeddings/create#embeddings-create-input + // We devide the maximum input length by 2 to stay on the safe side + // because the token to character ratio might not be accurate. + const batchSize = Math.min(Math.floor((MAX_INPUT_LENGTH / 2) / chunkSize), MAX_BATCH_SIZE); + const embeddings = []; + // For convenience, the GenerateEmbeddings component returns the first vector. + // This makes it easy to genereate embedding for a prompt and send it e.g. to the pinecone.QueryVectors component + // without having to apply modifiers to the embedding array returned. + let firstVector = null; + for (let i = 0; i < chunks.length; i += batchSize) { + const batch = chunks.slice(i, i + batchSize); + + const response = await client.embeddings.create({ + model, + input: batch, + encoding_format: 'float' + }); + + // Collect embeddings for the current batch. + response.data.forEach((item, index) => { + if (!firstVector) { + firstVector = item.embedding; + } + try { + const embedding = { + text: batch[index], + vector: item.embedding, + index: i + index + }; + embeddings.push(embedding); + } catch (err) { + // It does not make sense to retry the component call. + // Things "won't" improve. + throw new context.CancelError(err); + } + }); + } + return context.sendJson({ embeddings, firstVector }, 'out'); + }, + + splitText(text, chunkSize, chunkOverlap) { + + const splitter = new RecursiveCharacterTextSplitter({ + chunkSize, + chunkOverlap + }); + + return splitter.splitText(text); + } +}; + diff --git a/src/appmixer/ai/openai/GenerateEmbeddings/component.json b/src/appmixer/ai/openai/GenerateEmbeddings/component.json new file mode 100644 index 000000000..124ebc124 --- /dev/null +++ b/src/appmixer/ai/openai/GenerateEmbeddings/component.json @@ -0,0 +1,90 @@ +{ + "name": "appmixer.ai.openai.GenerateEmbeddings", + "author": "Appmixer ", + "description": "Generate embeddings for text data. The text is split into chunks and embedding is returned for each chunk.
The returned embeddings is an array of the form: [{ \"index\": 0, \"text\": \"chunk1\", \"vector\": [1.1, 1.2, 1.3] }].
TIP: use the JSONata modifier to convert the embeddings array into custom formats. For convenience, the component also returns the first vector in the embeddings array which is useful when querying vector databases to find relevant chunks.", + "auth": { + "service": "appmixer:ai:openai" + }, + "inPorts": [{ + "name": "in", + "schema": { + "type": "object", + "properties": { + "text": { "type": "string", "maxLength": 512000 }, + "model": { "type": "string" }, + "chunkSize": { "type": "integer" }, + "chunkOverlap": { "type": "integer" }, + "embeddingTemplate": { "type": "string" } + } + }, + "inspector": { + "inputs": { + "text": { + "type": "textarea", + "label": "Text", + "tooltip": "Enter the text to generate embeddings for. The text will be split into chunks and embeddings will be generated for each chunk. The maximum length is 512,000 characters. If you need more than 512,000 characters, use the 'Generate Embeddings From File' component.", + "index": 1 + }, + "model": { + "type": "text", + "index": 2, + "label": "Model", + "tooltip": "ID of the model to use.", + "defaultValue": "text-embedding-ada-002", + "source": { + "url": "/component/appmixer/ai/openai/ListModels?outPort=out", + "data": { + "transform": "./ListModels#toSelectOptions" + } + } + }, + "chunkSize": { + "type": "number", + "label": "Chunk Size", + "defaultValue": 500, + "tooltip": "Maximum size of each chunk for text splitting. The default is 500.", + "index": 3 + }, + "chunkOverlap": { + "type": "number", + "label": "Chunk Overlap", + "defaultValue": 50, + "tooltip": "Overlap between chunks for text splitting to maintain context. The default is 50.", + "index": 4 + } + } + } + }], + "outPorts": [{ + "name": "out", + "options": [{ + "label": "Embeddings", + "value": "embeddings", + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "index": { "type": "string" }, + "vector": { "type": "array", "items": { "type": "number" } }, + "text": { "type": "string" } + } + }, + "examples": [ + [{ "index": 0, "text": "chunk1", "vector": [1.1, 1.2, 1.3] }, { "index": 1, "text": "chunk2", "vector": [2.1, 2.2, 2.3] }] + ] + } + }, { + "label": "First Vector", + "value": "firstVector", + "schema": { + "type": "array", + "items": { "type": "number" }, + "examples": [ + [-0.0120379254, -0.0376950279, -0.0133513855, -0.0365983546, -0.0247007012, 0.0158507861, -0.0143460445, 0.00486809108] + ] + } + }] + }], + "icon": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNHB4IiBoZWlnaHQ9IjI0cHgiIGZpbGw9ImN1cnJlbnRDb2xvciIgdmlld0JveD0iMCAwIDI0IDI0IiBjb2xvcj0iYmxhY2siPjxwYXRoIGQ9Ik0yMi40MTggOS44MjJhNS45MDMgNS45MDMgMCAwIDAtLjUyLTQuOTEgNi4xIDYuMSAwIDAgMC0yLjgyMi0yLjUxMyA2LjIwNCA2LjIwNCAwIDAgMC0zLjc4LS4zODlBNi4wNTUgNi4wNTUgMCAwIDAgMTMuMjMyLjUxOCA2LjEyOSA2LjEyOSAwIDAgMCAxMC43MjYgMGE2LjE4NSA2LjE4NSAwIDAgMC0zLjYxNSAxLjE1M0E2LjA1MiA2LjA1MiAwIDAgMCA0Ljg4IDQuMTg3YTYuMTAyIDYuMTAyIDAgMCAwLTIuMzQ0IDEuMDE4QTYuMDA4IDYuMDA4IDAgMCAwIC44MjggNy4wODdhNS45ODEgNS45ODEgMCAwIDAgLjc1NCA3LjA5IDUuOTA0IDUuOTA0IDAgMCAwIC41MiA0LjkxMSA2LjEwMSA2LjEwMSAwIDAgMCAyLjgyMSAyLjUxMyA2LjIwNSA2LjIwNSAwIDAgMCAzLjc4LjM4OSA2LjA1NyA2LjA1NyAwIDAgMCAyLjA2NSAxLjQ5MiA2LjEzIDYuMTMgMCAwIDAgMi41MDUuNTE4IDYuMTg1IDYuMTg1IDAgMCAwIDMuNjE3LTEuMTU0IDYuMDUyIDYuMDUyIDAgMCAwIDIuMjMyLTMuMDM1IDYuMTAxIDYuMTAxIDAgMCAwIDIuMzQzLTEuMDE4IDYuMDA5IDYuMDA5IDAgMCAwIDEuNzA5LTEuODgzIDUuOTgxIDUuOTgxIDAgMCAwLS43NTYtNy4wODhabS05LjE0MyAxMi42MDlhNC41ODMgNC41ODMgMCAwIDEtMi45MTgtMS4wNGMuMDM3LS4wMi4xMDItLjA1Ni4xNDQtLjA4MWw0Ljg0NC0yLjc2YS43ODMuNzgzIDAgMCAwIC4zOTctLjY4di02LjczOEwxNy43OSAxMi4zYS4wNzIuMDcyIDAgMCAxIC4wNC4wNTV2NS41OGE0LjQ3MyA0LjQ3MyAwIDAgMS0xLjMzNSAzLjE3NiA0LjU5NiA0LjU5NiAwIDAgMS0zLjIxOSAxLjMyMVptLTkuNzkzLTQuMTI3YTQuNDMyIDQuNDMyIDAgMCAxLS41NDQtMy4wMTRjLjAzNi4wMjEuMDk5LjA2LjE0NC4wODVsNC44NDMgMi43NmEuNzk2Ljc5NiAwIDAgMCAuNzk1IDBsNS45MTMtMy4zNjlWMTcuMWEuMDcxLjA3MSAwIDAgMS0uMDI5LjA2Mkw5LjcwOCAxOS45NWE0LjYxNyA0LjYxNyAwIDAgMS0zLjQ1OC40NDcgNC41NTYgNC41NTYgMCAwIDEtMi43NjgtMi4wOTNaTTIuMjA4IDcuODcyQTQuNTI3IDQuNTI3IDAgMCAxIDQuNTggNS45bC0uMDAyLjE2NHY1LjUyYS43NjguNzY4IDAgMCAwIC4zOTcuNjhsNS45MTMgMy4zNjktMi4wNDcgMS4xNjZhLjA3NS4wNzUgMCAwIDEtLjA2OS4wMDZsLTQuODk2LTIuNzkyYTQuNTEgNC41MSAwIDAgMS0yLjEyLTIuNzMgNC40NSA0LjQ1IDAgMCAxIC40NTItMy40MTFabTE2LjgxOCAzLjg2MS01LjkxMy0zLjM2OCAyLjA0Ny0xLjE2NmEuMDc0LjA3NCAwIDAgMSAuMDctLjAwNmw0Ljg5NiAyLjc4OWE0LjUyNiA0LjUyNiAwIDAgMSAxLjc2MiAxLjgxNSA0LjQ0OCA0LjQ0OCAwIDAgMS0uNDE4IDQuODA4IDQuNTU2IDQuNTU2IDAgMCAxLTIuMDQ5IDEuNDk0di01LjY4NmEuNzY3Ljc2NyAwIDAgMC0uMzk1LS42OFptMi4wMzgtMy4wMjVhNi44NzQgNi44NzQgMCAwIDAtLjE0NC0uMDg1bC00Ljg0My0yLjc2YS43OTcuNzk3IDAgMCAwLS43OTYgMEw5LjM2OCA5LjIzVjYuOWEuMDcyLjA3MiAwIDAgMSAuMDMtLjA2Mmw0Ljg5NS0yLjc4N2E0LjYwOCA0LjYwOCAwIDAgMSA0Ljg4NS4yMDcgNC41MSA0LjUxIDAgMCAxIDEuNTk5IDEuOTU1Yy4zMzMuNzg4LjQzMyAxLjY1NC4yODcgMi40OTZaTTguMjU1IDEyLjg2NSA2LjIwOCAxMS43YS4wNzEuMDcxIDAgMCAxLS4wNC0uMDU2di01LjU4YzAtLjg1NC4yNDgtMS42OS43MTMtMi40MTJhNC41NCA0LjU0IDAgMCAxIDEuOTEzLTEuNjU4IDQuNjE0IDQuNjE0IDAgMCAxIDQuODUuNjE2Yy0uMDM3LjAyLS4xMDIuMDU1LS4xNDQuMDhMOC42NTcgNS40NTJhLjc4Mi43ODIgMCAwIDAtLjM5OC42OGwtLjAwNCA2LjczNFpNOS4zNjcgMTAuNSAxMi4wMDEgOWwyLjYzMyAxLjV2M0wxMi4wMDEgMTVsLTIuNjM0LTEuNXYtM1oiLz48L3N2Zz4=" +} diff --git a/src/appmixer/ai/openai/GenerateEmbeddingsFromFile/GenerateEmbeddingsFromFile.js b/src/appmixer/ai/openai/GenerateEmbeddingsFromFile/GenerateEmbeddingsFromFile.js new file mode 100644 index 000000000..5a34440c4 --- /dev/null +++ b/src/appmixer/ai/openai/GenerateEmbeddingsFromFile/GenerateEmbeddingsFromFile.js @@ -0,0 +1,147 @@ +'use strict'; + +const { Transform } = require('stream'); +const { RecursiveCharacterTextSplitter } = require('langchain/text_splitter'); +const OpenAI = require('openai'); + +// See https://platform.openai.com/docs/api-reference/embeddings/create#embeddings-create-input. +const MAX_INPUT_LENGTH = 8192 * 4; // max 8192 tokens, 1 token ~ 4 characters. +const MAX_BATCH_SIZE = 2048; +const FILE_PART_SIZE = 1024 * 1024; // 1MB + +module.exports = { + + receive: async function(context) { + + const messageId = context.messages.in.messageId; + const { + fileId + } = context.messages.in.content; + + const apiKey = context.auth.apiKey; + const client = new OpenAI({ apiKey }); + + const readStream = await context.getFileReadStream(fileId); + const fileInfo = await context.getFileInfo(fileId); + await context.log({ step: 'split-file', message: 'Splitting file into parts.', partSize: FILE_PART_SIZE, fileInfo }); + let firstVector; + const partsStream = splitStream(readStream, FILE_PART_SIZE); + for await (const part of partsStream) { + const embeddings = await this.generateEmbeddings(context, client, part.toString()); + if (!firstVector) { + firstVector = embeddings[0].vector; + } + await context.sendJson({ embeddings, firstVector }, 'out'); + } + }, + + generateEmbeddings: async function(context, client, text) { + + const { + model = 'text-embedding-ada-002', + chunkSize = 500, + chunkOverlap = 50 + } = context.messages.in.content; + + const chunks = await this.splitText(text, chunkSize, chunkOverlap); + await context.log({ + step: 'split-text', + message: 'Text succesfully split into chunks.', + chunksLength: chunks.length, + textLength: text.length + }); + + + // Process chunks in batches. + // the batch size is calculated based on the chunk size and the maximum input length in + // order not to exceed the maximum input length defined in + // https://platform.openai.com/docs/api-reference/embeddings/create#embeddings-create-input + // We devide the maximum input length by 2 to stay on the safe side + // because the token to character ratio might not be accurate. + const batchSize = Math.min(Math.floor((MAX_INPUT_LENGTH / 2) / chunkSize), MAX_BATCH_SIZE); + const embeddings = []; + // For convenience, the GenerateEmbeddings component returns the first vector. + // This makes it easy to genereate embedding for a prompt and send it e.g. to the pinecone.QueryVectors component + // without having to apply modifiers to the embedding array returned. + let firstVector = null; + for (let i = 0; i < chunks.length; i += batchSize) { + const batch = chunks.slice(i, i + batchSize); + + const response = await client.embeddings.create({ + model, + input: batch, + encoding_format: 'float' + }); + + // Collect embeddings for the current batch. + response.data.forEach((item, index) => { + if (!firstVector) { + firstVector = item.embedding; + } + try { + const embedding = { + text: batch[index], + vector: item.embedding, + index: i + index + }; + embeddings.push(embedding); + } catch (err) { + // It does not make sense to retry the component call. + // Things "won't" improve. + throw new context.CancelError(err); + } + }); + } + return embeddings; + }, + + splitText(text, chunkSize, chunkOverlap) { + + const splitter = new RecursiveCharacterTextSplitter({ + chunkSize, + chunkOverlap + }); + + return splitter.splitText(text); + } +}; + +/** + * Splits a readable stream into chunks of n bytes. + * @param {Readable} inputStream - The readable stream to split. + * @param {number} chunkSize - Size of each chunk in bytes. + * @returns {Readable} - A readable stream emitting chunks. + */ +function splitStream(inputStream, chunkSize) { + + let leftover = Buffer.alloc(0); + + const transformStream = new Transform({ + transform(chunk, encoding, callback) { + // Combine leftover buffer with the new chunk + const combined = Buffer.concat([leftover, chunk]); + const combinedLength = combined.length; + + // Emit chunks of the desired size + let offset = 0; + while (offset + chunkSize <= combinedLength) { + this.push(combined.slice(offset, offset + chunkSize)); + offset += chunkSize; + } + + // Store leftover data + leftover = combined.slice(offset); + + callback(); + }, + flush(callback) { + // Push any remaining data as the final chunk + if (leftover.length > 0) { + this.push(leftover); + } + callback(); + }, + }); + + return inputStream.pipe(transformStream); + } \ No newline at end of file diff --git a/src/appmixer/ai/openai/GenerateEmbeddingsFromFile/component.json b/src/appmixer/ai/openai/GenerateEmbeddingsFromFile/component.json new file mode 100644 index 000000000..cb7f665f4 --- /dev/null +++ b/src/appmixer/ai/openai/GenerateEmbeddingsFromFile/component.json @@ -0,0 +1,90 @@ +{ + "name": "appmixer.ai.openai.GenerateEmbeddingsFromFile", + "author": "Appmixer ", + "description": "Generate embeddings for a text file. The text is split into parts, each part is split into chunks and embedding is returned for each chunk. The component emits embeddings array for each file part (1MB).
The returned embeddings is an array of the form: [{ \"index\": 0, \"text\": \"chunk1\", \"vector\": [1.1, 1.2, 1.3] }].
TIP: use the JSONata modifier to convert the embeddings array into custom formats.", + "auth": { + "service": "appmixer:ai:openai" + }, + "inPorts": [{ + "name": "in", + "schema": { + "type": "object", + "properties": { + "fileId": { "type": "string" }, + "model": { "type": "string" }, + "chunkSize": { "type": "integer" }, + "chunkOverlap": { "type": "integer" }, + "embeddingTemplate": { "type": "string" } + } + }, + "inspector": { + "inputs": { + "fileId": { + "label": "File ID", + "type": "filepicker", + "index": 1, + "tooltip": "The text file to generate embeddings for. Use plain text or CSV files only." + }, + "model": { + "type": "text", + "index": 2, + "label": "Model", + "tooltip": "ID of the model to use.", + "defaultValue": "text-embedding-ada-002", + "source": { + "url": "/component/appmixer/ai/openai/ListModels?outPort=out", + "data": { + "transform": "./ListModels#toSelectOptions" + } + } + }, + "chunkSize": { + "type": "number", + "label": "Chunk Size", + "defaultValue": 500, + "tooltip": "Maximum size of each chunk for text splitting. The default is 500.", + "index": 3 + }, + "chunkOverlap": { + "type": "number", + "label": "Chunk Overlap", + "defaultValue": 50, + "tooltip": "Overlap between chunks for text splitting to maintain context. The default is 50.", + "index": 4 + } + } + } + }], + "outPorts": [{ + "name": "out", + "options": [{ + "label": "Embeddings", + "value": "embeddings", + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "index": { "type": "string" }, + "vector": { "type": "array", "items": { "type": "number" } }, + "text": { "type": "string" } + } + }, + "examples": [ + [{ "index": 0, "text": "chunk1", "vector": [1.1, 1.2, 1.3] }, { "index": 1, "text": "chunk2", "vector": [2.1, 2.2, 2.3] }] + ] + } + }, { + "label": "First Vector", + "value": "firstVector", + "schema": { + "type": "array", + "items": { "type": "number" }, + "examples": [ + [-0.0120379254, -0.0376950279, -0.0133513855, -0.0365983546, -0.0247007012, 0.0158507861, -0.0143460445, 0.00486809108] + ] + } + }] + }], + "icon": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNHB4IiBoZWlnaHQ9IjI0cHgiIGZpbGw9ImN1cnJlbnRDb2xvciIgdmlld0JveD0iMCAwIDI0IDI0IiBjb2xvcj0iYmxhY2siPjxwYXRoIGQ9Ik0yMi40MTggOS44MjJhNS45MDMgNS45MDMgMCAwIDAtLjUyLTQuOTEgNi4xIDYuMSAwIDAgMC0yLjgyMi0yLjUxMyA2LjIwNCA2LjIwNCAwIDAgMC0zLjc4LS4zODlBNi4wNTUgNi4wNTUgMCAwIDAgMTMuMjMyLjUxOCA2LjEyOSA2LjEyOSAwIDAgMCAxMC43MjYgMGE2LjE4NSA2LjE4NSAwIDAgMC0zLjYxNSAxLjE1M0E2LjA1MiA2LjA1MiAwIDAgMCA0Ljg4IDQuMTg3YTYuMTAyIDYuMTAyIDAgMCAwLTIuMzQ0IDEuMDE4QTYuMDA4IDYuMDA4IDAgMCAwIC44MjggNy4wODdhNS45ODEgNS45ODEgMCAwIDAgLjc1NCA3LjA5IDUuOTA0IDUuOTA0IDAgMCAwIC41MiA0LjkxMSA2LjEwMSA2LjEwMSAwIDAgMCAyLjgyMSAyLjUxMyA2LjIwNSA2LjIwNSAwIDAgMCAzLjc4LjM4OSA2LjA1NyA2LjA1NyAwIDAgMCAyLjA2NSAxLjQ5MiA2LjEzIDYuMTMgMCAwIDAgMi41MDUuNTE4IDYuMTg1IDYuMTg1IDAgMCAwIDMuNjE3LTEuMTU0IDYuMDUyIDYuMDUyIDAgMCAwIDIuMjMyLTMuMDM1IDYuMTAxIDYuMTAxIDAgMCAwIDIuMzQzLTEuMDE4IDYuMDA5IDYuMDA5IDAgMCAwIDEuNzA5LTEuODgzIDUuOTgxIDUuOTgxIDAgMCAwLS43NTYtNy4wODhabS05LjE0MyAxMi42MDlhNC41ODMgNC41ODMgMCAwIDEtMi45MTgtMS4wNGMuMDM3LS4wMi4xMDItLjA1Ni4xNDQtLjA4MWw0Ljg0NC0yLjc2YS43ODMuNzgzIDAgMCAwIC4zOTctLjY4di02LjczOEwxNy43OSAxMi4zYS4wNzIuMDcyIDAgMCAxIC4wNC4wNTV2NS41OGE0LjQ3MyA0LjQ3MyAwIDAgMS0xLjMzNSAzLjE3NiA0LjU5NiA0LjU5NiAwIDAgMS0zLjIxOSAxLjMyMVptLTkuNzkzLTQuMTI3YTQuNDMyIDQuNDMyIDAgMCAxLS41NDQtMy4wMTRjLjAzNi4wMjEuMDk5LjA2LjE0NC4wODVsNC44NDMgMi43NmEuNzk2Ljc5NiAwIDAgMCAuNzk1IDBsNS45MTMtMy4zNjlWMTcuMWEuMDcxLjA3MSAwIDAgMS0uMDI5LjA2Mkw5LjcwOCAxOS45NWE0LjYxNyA0LjYxNyAwIDAgMS0zLjQ1OC40NDcgNC41NTYgNC41NTYgMCAwIDEtMi43NjgtMi4wOTNaTTIuMjA4IDcuODcyQTQuNTI3IDQuNTI3IDAgMCAxIDQuNTggNS45bC0uMDAyLjE2NHY1LjUyYS43NjguNzY4IDAgMCAwIC4zOTcuNjhsNS45MTMgMy4zNjktMi4wNDcgMS4xNjZhLjA3NS4wNzUgMCAwIDEtLjA2OS4wMDZsLTQuODk2LTIuNzkyYTQuNTEgNC41MSAwIDAgMS0yLjEyLTIuNzMgNC40NSA0LjQ1IDAgMCAxIC40NTItMy40MTFabTE2LjgxOCAzLjg2MS01LjkxMy0zLjM2OCAyLjA0Ny0xLjE2NmEuMDc0LjA3NCAwIDAgMSAuMDctLjAwNmw0Ljg5NiAyLjc4OWE0LjUyNiA0LjUyNiAwIDAgMSAxLjc2MiAxLjgxNSA0LjQ0OCA0LjQ0OCAwIDAgMS0uNDE4IDQuODA4IDQuNTU2IDQuNTU2IDAgMCAxLTIuMDQ5IDEuNDk0di01LjY4NmEuNzY3Ljc2NyAwIDAgMC0uMzk1LS42OFptMi4wMzgtMy4wMjVhNi44NzQgNi44NzQgMCAwIDAtLjE0NC0uMDg1bC00Ljg0My0yLjc2YS43OTcuNzk3IDAgMCAwLS43OTYgMEw5LjM2OCA5LjIzVjYuOWEuMDcyLjA3MiAwIDAgMSAuMDMtLjA2Mmw0Ljg5NS0yLjc4N2E0LjYwOCA0LjYwOCAwIDAgMSA0Ljg4NS4yMDcgNC41MSA0LjUxIDAgMCAxIDEuNTk5IDEuOTU1Yy4zMzMuNzg4LjQzMyAxLjY1NC4yODcgMi40OTZaTTguMjU1IDEyLjg2NSA2LjIwOCAxMS43YS4wNzEuMDcxIDAgMCAxLS4wNC0uMDU2di01LjU4YzAtLjg1NC4yNDgtMS42OS43MTMtMi40MTJhNC41NCA0LjU0IDAgMCAxIDEuOTEzLTEuNjU4IDQuNjE0IDQuNjE0IDAgMCAxIDQuODUuNjE2Yy0uMDM3LjAyLS4xMDIuMDU1LS4xNDQuMDhMOC42NTcgNS40NTJhLjc4Mi43ODIgMCAwIDAtLjM5OC42OGwtLjAwNCA2LjczNFpNOS4zNjcgMTAuNSAxMi4wMDEgOWwyLjYzMyAxLjV2M0wxMi4wMDEgMTVsLTIuNjM0LTEuNXYtM1oiLz48L3N2Zz4=" +} diff --git a/src/appmixer/ai/openai/GenerateImage/GenerateImage.js b/src/appmixer/ai/openai/GenerateImage/GenerateImage.js new file mode 100644 index 000000000..e3b3c028b --- /dev/null +++ b/src/appmixer/ai/openai/GenerateImage/GenerateImage.js @@ -0,0 +1,36 @@ +'use strict'; + +module.exports = { + + receive: async function(context) { + + const { prompt, size, model } = context.messages.in.content; + const apiKey = context.auth.apiKey; + + const url = 'https://api.openai.com/v1/images/generations'; + const { data } = await context.httpRequest.post(url, { + model: model || 'dall-e-3', + prompt, + size, + n: 1 + }, { + headers: { + 'Authorization': `Bearer ${apiKey}`, + 'Content-Type': 'application/json' + } + }); + + let imageUrl; + if (data && data.data && data.data.length > 0) { + imageUrl = data.data[0].url; + } + + if (imageUrl) { + const response = await context.httpRequest.get(imageUrl, { responseType: 'stream' }); + const readStream = response.data; + const filename = `generated-image-${(new Date).toISOString()}.png`; + const file = await context.saveFileStream(filename, readStream); + return context.sendJson({ fileId: file.fileId, prompt, size }, 'out'); + } + } +}; diff --git a/src/appmixer/ai/openai/GenerateImage/component.json b/src/appmixer/ai/openai/GenerateImage/component.json new file mode 100644 index 000000000..a047e1ee0 --- /dev/null +++ b/src/appmixer/ai/openai/GenerateImage/component.json @@ -0,0 +1,70 @@ +{ + "name": "appmixer.ai.openai.GenerateImage", + "author": "Appmixer ", + "description": "Generates an image from a text prompt.", + "auth": { + "service": "appmixer:ai:openai" + }, + "inPorts": [{ + "name": "in", + "schema": { + "type": "object", + "properties": { + "prompt": { "type": "string" }, + "size": { "type": "string" }, + "model": { "type": "string" } + }, + "required": ["prompt"] + }, + "inspector": { + "inputs": { + "prompt": { + "label": "Prompt", + "type": "textarea", + "index": 1 + }, + "size": { + "label": "Size", + "type": "select", + "defaultValue": "1024x1024", + "options": [ + { "label": "1024x1024", "value": "1024x1024" }, + { "label": "1792x1024", "value": "1792x1024" }, + { "label": "1024x1792", "value": "1024x1792" } + ], + "index": 2 + }, + "model": { + "type": "text", + "index": 3, + "label": "Model", + "tooltip": "ID of the model to use.", + "defaultValue": "dall-e-3", + "source": { + "url": "/component/appmixer/ai/openai/ListModels?outPort=out", + "data": { + "transform": "./ListModels#toSelectOptions" + } + } + } + } + } + }], + "outPorts": [{ + "name": "out", + "options": [{ + "label": "File ID", + "value": "fileId", + "schema": { "type": "string", "format": "appmixer-file-id" } + },{ + "label": "Prompt", + "value": "prompt", + "schema": { "type": "string" } + },{ + "label": "Size", + "value": "size", + "schema": { "type": "string" } + }] + }], + "icon": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNHB4IiBoZWlnaHQ9IjI0cHgiIGZpbGw9ImN1cnJlbnRDb2xvciIgdmlld0JveD0iMCAwIDI0IDI0IiBjb2xvcj0iYmxhY2siPjxwYXRoIGQ9Ik0yMi40MTggOS44MjJhNS45MDMgNS45MDMgMCAwIDAtLjUyLTQuOTEgNi4xIDYuMSAwIDAgMC0yLjgyMi0yLjUxMyA2LjIwNCA2LjIwNCAwIDAgMC0zLjc4LS4zODlBNi4wNTUgNi4wNTUgMCAwIDAgMTMuMjMyLjUxOCA2LjEyOSA2LjEyOSAwIDAgMCAxMC43MjYgMGE2LjE4NSA2LjE4NSAwIDAgMC0zLjYxNSAxLjE1M0E2LjA1MiA2LjA1MiAwIDAgMCA0Ljg4IDQuMTg3YTYuMTAyIDYuMTAyIDAgMCAwLTIuMzQ0IDEuMDE4QTYuMDA4IDYuMDA4IDAgMCAwIC44MjggNy4wODdhNS45ODEgNS45ODEgMCAwIDAgLjc1NCA3LjA5IDUuOTA0IDUuOTA0IDAgMCAwIC41MiA0LjkxMSA2LjEwMSA2LjEwMSAwIDAgMCAyLjgyMSAyLjUxMyA2LjIwNSA2LjIwNSAwIDAgMCAzLjc4LjM4OSA2LjA1NyA2LjA1NyAwIDAgMCAyLjA2NSAxLjQ5MiA2LjEzIDYuMTMgMCAwIDAgMi41MDUuNTE4IDYuMTg1IDYuMTg1IDAgMCAwIDMuNjE3LTEuMTU0IDYuMDUyIDYuMDUyIDAgMCAwIDIuMjMyLTMuMDM1IDYuMTAxIDYuMTAxIDAgMCAwIDIuMzQzLTEuMDE4IDYuMDA5IDYuMDA5IDAgMCAwIDEuNzA5LTEuODgzIDUuOTgxIDUuOTgxIDAgMCAwLS43NTYtNy4wODhabS05LjE0MyAxMi42MDlhNC41ODMgNC41ODMgMCAwIDEtMi45MTgtMS4wNGMuMDM3LS4wMi4xMDItLjA1Ni4xNDQtLjA4MWw0Ljg0NC0yLjc2YS43ODMuNzgzIDAgMCAwIC4zOTctLjY4di02LjczOEwxNy43OSAxMi4zYS4wNzIuMDcyIDAgMCAxIC4wNC4wNTV2NS41OGE0LjQ3MyA0LjQ3MyAwIDAgMS0xLjMzNSAzLjE3NiA0LjU5NiA0LjU5NiAwIDAgMS0zLjIxOSAxLjMyMVptLTkuNzkzLTQuMTI3YTQuNDMyIDQuNDMyIDAgMCAxLS41NDQtMy4wMTRjLjAzNi4wMjEuMDk5LjA2LjE0NC4wODVsNC44NDMgMi43NmEuNzk2Ljc5NiAwIDAgMCAuNzk1IDBsNS45MTMtMy4zNjlWMTcuMWEuMDcxLjA3MSAwIDAgMS0uMDI5LjA2Mkw5LjcwOCAxOS45NWE0LjYxNyA0LjYxNyAwIDAgMS0zLjQ1OC40NDcgNC41NTYgNC41NTYgMCAwIDEtMi43NjgtMi4wOTNaTTIuMjA4IDcuODcyQTQuNTI3IDQuNTI3IDAgMCAxIDQuNTggNS45bC0uMDAyLjE2NHY1LjUyYS43NjguNzY4IDAgMCAwIC4zOTcuNjhsNS45MTMgMy4zNjktMi4wNDcgMS4xNjZhLjA3NS4wNzUgMCAwIDEtLjA2OS4wMDZsLTQuODk2LTIuNzkyYTQuNTEgNC41MSAwIDAgMS0yLjEyLTIuNzMgNC40NSA0LjQ1IDAgMCAxIC40NTItMy40MTFabTE2LjgxOCAzLjg2MS01LjkxMy0zLjM2OCAyLjA0Ny0xLjE2NmEuMDc0LjA3NCAwIDAgMSAuMDctLjAwNmw0Ljg5NiAyLjc4OWE0LjUyNiA0LjUyNiAwIDAgMSAxLjc2MiAxLjgxNSA0LjQ0OCA0LjQ0OCAwIDAgMS0uNDE4IDQuODA4IDQuNTU2IDQuNTU2IDAgMCAxLTIuMDQ5IDEuNDk0di01LjY4NmEuNzY3Ljc2NyAwIDAgMC0uMzk1LS42OFptMi4wMzgtMy4wMjVhNi44NzQgNi44NzQgMCAwIDAtLjE0NC0uMDg1bC00Ljg0My0yLjc2YS43OTcuNzk3IDAgMCAwLS43OTYgMEw5LjM2OCA5LjIzVjYuOWEuMDcyLjA3MiAwIDAgMSAuMDMtLjA2Mmw0Ljg5NS0yLjc4N2E0LjYwOCA0LjYwOCAwIDAgMSA0Ljg4NS4yMDcgNC41MSA0LjUxIDAgMCAxIDEuNTk5IDEuOTU1Yy4zMzMuNzg4LjQzMyAxLjY1NC4yODcgMi40OTZaTTguMjU1IDEyLjg2NSA2LjIwOCAxMS43YS4wNzEuMDcxIDAgMCAxLS4wNC0uMDU2di01LjU4YzAtLjg1NC4yNDgtMS42OS43MTMtMi40MTJhNC41NCA0LjU0IDAgMCAxIDEuOTEzLTEuNjU4IDQuNjE0IDQuNjE0IDAgMCAxIDQuODUuNjE2Yy0uMDM3LjAyLS4xMDIuMDU1LS4xNDQuMDhMOC42NTcgNS40NTJhLjc4Mi43ODIgMCAwIDAtLjM5OC42OGwtLjAwNCA2LjczNFpNOS4zNjcgMTAuNSAxMi4wMDEgOWwyLjYzMyAxLjV2M0wxMi4wMDEgMTVsLTIuNjM0LTEuNXYtM1oiLz48L3N2Zz4=" +} diff --git a/src/appmixer/ai/openai/ListModels/ListModels.js b/src/appmixer/ai/openai/ListModels/ListModels.js new file mode 100644 index 000000000..5bd312176 --- /dev/null +++ b/src/appmixer/ai/openai/ListModels/ListModels.js @@ -0,0 +1,24 @@ +'use strict'; + +module.exports = { + + receive: async function(context) { + const { prompt } = context.messages.in.content; + const apiKey = context.auth.apiKey; + + const url = 'https://api.openai.com/v1/models'; + const { data } = await context.httpRequest.get(url, { + headers: { + 'Authorization': `Bearer ${apiKey}`, + 'Content-Type': 'application/json' + } + }); + return context.sendJson({ models: data.data }, 'out'); + }, + + toSelectOptions(out) { + return out.models.map(model => { + return { label: model.id, value: model.id }; + }); + } +}; diff --git a/src/appmixer/ai/openai/ListModels/component.json b/src/appmixer/ai/openai/ListModels/component.json new file mode 100644 index 000000000..63244711c --- /dev/null +++ b/src/appmixer/ai/openai/ListModels/component.json @@ -0,0 +1,30 @@ +{ + "name": "appmixer.ai.openai.ListModels", + "author": "Appmixer ", + "description": "List models.", + "auth": { + "service": "appmixer:ai:openai" + }, + "inPorts": [{ + "name": "in" + }], + "outPorts": [{ + "name": "out", + "options": [{ + "label": "Models", + "value": "models", + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { "type": "string" }, + "created": { "type": "integer" }, + "owned_by": { "type": "string" } + } + } + } + }] + }], + "icon": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNHB4IiBoZWlnaHQ9IjI0cHgiIGZpbGw9ImN1cnJlbnRDb2xvciIgdmlld0JveD0iMCAwIDI0IDI0IiBjb2xvcj0iYmxhY2siPjxwYXRoIGQ9Ik0yMi40MTggOS44MjJhNS45MDMgNS45MDMgMCAwIDAtLjUyLTQuOTEgNi4xIDYuMSAwIDAgMC0yLjgyMi0yLjUxMyA2LjIwNCA2LjIwNCAwIDAgMC0zLjc4LS4zODlBNi4wNTUgNi4wNTUgMCAwIDAgMTMuMjMyLjUxOCA2LjEyOSA2LjEyOSAwIDAgMCAxMC43MjYgMGE2LjE4NSA2LjE4NSAwIDAgMC0zLjYxNSAxLjE1M0E2LjA1MiA2LjA1MiAwIDAgMCA0Ljg4IDQuMTg3YTYuMTAyIDYuMTAyIDAgMCAwLTIuMzQ0IDEuMDE4QTYuMDA4IDYuMDA4IDAgMCAwIC44MjggNy4wODdhNS45ODEgNS45ODEgMCAwIDAgLjc1NCA3LjA5IDUuOTA0IDUuOTA0IDAgMCAwIC41MiA0LjkxMSA2LjEwMSA2LjEwMSAwIDAgMCAyLjgyMSAyLjUxMyA2LjIwNSA2LjIwNSAwIDAgMCAzLjc4LjM4OSA2LjA1NyA2LjA1NyAwIDAgMCAyLjA2NSAxLjQ5MiA2LjEzIDYuMTMgMCAwIDAgMi41MDUuNTE4IDYuMTg1IDYuMTg1IDAgMCAwIDMuNjE3LTEuMTU0IDYuMDUyIDYuMDUyIDAgMCAwIDIuMjMyLTMuMDM1IDYuMTAxIDYuMTAxIDAgMCAwIDIuMzQzLTEuMDE4IDYuMDA5IDYuMDA5IDAgMCAwIDEuNzA5LTEuODgzIDUuOTgxIDUuOTgxIDAgMCAwLS43NTYtNy4wODhabS05LjE0MyAxMi42MDlhNC41ODMgNC41ODMgMCAwIDEtMi45MTgtMS4wNGMuMDM3LS4wMi4xMDItLjA1Ni4xNDQtLjA4MWw0Ljg0NC0yLjc2YS43ODMuNzgzIDAgMCAwIC4zOTctLjY4di02LjczOEwxNy43OSAxMi4zYS4wNzIuMDcyIDAgMCAxIC4wNC4wNTV2NS41OGE0LjQ3MyA0LjQ3MyAwIDAgMS0xLjMzNSAzLjE3NiA0LjU5NiA0LjU5NiAwIDAgMS0zLjIxOSAxLjMyMVptLTkuNzkzLTQuMTI3YTQuNDMyIDQuNDMyIDAgMCAxLS41NDQtMy4wMTRjLjAzNi4wMjEuMDk5LjA2LjE0NC4wODVsNC44NDMgMi43NmEuNzk2Ljc5NiAwIDAgMCAuNzk1IDBsNS45MTMtMy4zNjlWMTcuMWEuMDcxLjA3MSAwIDAgMS0uMDI5LjA2Mkw5LjcwOCAxOS45NWE0LjYxNyA0LjYxNyAwIDAgMS0zLjQ1OC40NDcgNC41NTYgNC41NTYgMCAwIDEtMi43NjgtMi4wOTNaTTIuMjA4IDcuODcyQTQuNTI3IDQuNTI3IDAgMCAxIDQuNTggNS45bC0uMDAyLjE2NHY1LjUyYS43NjguNzY4IDAgMCAwIC4zOTcuNjhsNS45MTMgMy4zNjktMi4wNDcgMS4xNjZhLjA3NS4wNzUgMCAwIDEtLjA2OS4wMDZsLTQuODk2LTIuNzkyYTQuNTEgNC41MSAwIDAgMS0yLjEyLTIuNzMgNC40NSA0LjQ1IDAgMCAxIC40NTItMy40MTFabTE2LjgxOCAzLjg2MS01LjkxMy0zLjM2OCAyLjA0Ny0xLjE2NmEuMDc0LjA3NCAwIDAgMSAuMDctLjAwNmw0Ljg5NiAyLjc4OWE0LjUyNiA0LjUyNiAwIDAgMSAxLjc2MiAxLjgxNSA0LjQ0OCA0LjQ0OCAwIDAgMS0uNDE4IDQuODA4IDQuNTU2IDQuNTU2IDAgMCAxLTIuMDQ5IDEuNDk0di01LjY4NmEuNzY3Ljc2NyAwIDAgMC0uMzk1LS42OFptMi4wMzgtMy4wMjVhNi44NzQgNi44NzQgMCAwIDAtLjE0NC0uMDg1bC00Ljg0My0yLjc2YS43OTcuNzk3IDAgMCAwLS43OTYgMEw5LjM2OCA5LjIzVjYuOWEuMDcyLjA3MiAwIDAgMSAuMDMtLjA2Mmw0Ljg5NS0yLjc4N2E0LjYwOCA0LjYwOCAwIDAgMSA0Ljg4NS4yMDcgNC41MSA0LjUxIDAgMCAxIDEuNTk5IDEuOTU1Yy4zMzMuNzg4LjQzMyAxLjY1NC4yODcgMi40OTZaTTguMjU1IDEyLjg2NSA2LjIwOCAxMS43YS4wNzEuMDcxIDAgMCAxLS4wNC0uMDU2di01LjU4YzAtLjg1NC4yNDgtMS42OS43MTMtMi40MTJhNC41NCA0LjU0IDAgMCAxIDEuOTEzLTEuNjU4IDQuNjE0IDQuNjE0IDAgMCAxIDQuODUuNjE2Yy0uMDM3LjAyLS4xMDIuMDU1LS4xNDQuMDhMOC42NTcgNS40NTJhLjc4Mi43ODIgMCAwIDAtLjM5OC42OGwtLjAwNCA2LjczNFpNOS4zNjcgMTAuNSAxMi4wMDEgOWwyLjYzMyAxLjV2M0wxMi4wMDEgMTVsLTIuNjM0LTEuNXYtM1oiLz48L3N2Zz4=" +} diff --git a/src/appmixer/ai/openai/Moderate/Moderate.js b/src/appmixer/ai/openai/Moderate/Moderate.js new file mode 100644 index 000000000..59e69265f --- /dev/null +++ b/src/appmixer/ai/openai/Moderate/Moderate.js @@ -0,0 +1,31 @@ +'use strict'; + +module.exports = { + + receive: async function(context) { + + const { input } = context.messages.in.content; + const apiKey = context.auth.apiKey; + + const url = 'https://api.openai.com/v1/moderations'; + const { data } = await context.httpRequest.post(url, { + model: context.config.ModerateModel || 'text-moderation-latest', + input + }, { + headers: { + 'Authorization': `Bearer ${apiKey}`, + 'Content-Type': 'application/json' + } + }); + + if (data.results) { + const moderation = data.results[0]; + + if (moderation.flagged) { + return context.sendJson({ moderation, input }, 'IsFlagged'); + } else { + return context.sendJson({ moderation, input }, 'NotFlagged'); + } + } + } +}; diff --git a/src/appmixer/ai/openai/Moderate/component.json b/src/appmixer/ai/openai/Moderate/component.json new file mode 100644 index 000000000..028537800 --- /dev/null +++ b/src/appmixer/ai/openai/Moderate/component.json @@ -0,0 +1,296 @@ +{ + "name": "appmixer.ai.openai.Moderate", + "author": "Appmixer ", + "description": "Classify if text is potentially harmful.", + "auth": { + "service": "appmixer:ai:openai" + }, + "inPorts": [ + { + "name": "in", + "schema": { + "type": "object", + "properties": { + "input": { "type": "string" }, + "model": { "type": "string" } + }, + "required": ["input"] + }, + "inspector": { + "inputs": { + "input": { + "label": "Text", + "type": "textarea", + "index": 1 + }, + "model": { + "type": "text", + "index": 2, + "label": "Model", + "tooltip": "ID of the model to use.", + "defaultValue": "text-moderation-latest", + "source": { + "url": "/component/appmixer/ai/openai/ListModels?outPort=out", + "data": { + "transform": "./ListModels#toSelectOptions" + } + } + } + } + } + } + ], + "outPorts": [ + { + "name": "IsFlagged", + "options": [ + { + "label": "Moderation", + "value": "moderation", + "schema": { + "type": "object", + "properties": { + "flagged": { + "type": "boolean", + "title": "Is Flagged" + }, + "categories": { + "type": "object", + "title": "Categories", + "properties": { + "sexual": { + "type": "boolean", + "title": "Is Sexual" + }, + "hate": { + "type": "boolean", + "title": "Is Hate" + }, + "harassment": { + "type": "boolean", + "title": "Is Harassment" + }, + "self-harm": { + "type": "boolean", + "title": "Is Self-Harm" + }, + "sexual/minors": { + "type": "boolean", + "title": "Is Sexual/Minors" + }, + "hate/threatening": { + "type": "boolean", + "title": "Is Hate/Threatening" + }, + "violence/graphic": { + "type": "boolean", + "title": "Is Violence/Graphic" + }, + "self-harm/intent": { + "type": "boolean", + "title": "Is Self-Harm/Intent" + }, + "self-harm/instructions": { + "type": "boolean", + "title": "Is Self-Harm/Instructions" + }, + "harassment/threatening": { + "type": "boolean", + "title": "Is Harassment/Threatening" + }, + "violence": { + "type": "boolean", + "title": "Is Violence" + } + } + }, + "category_scores": { + "type": "object", + "title": "Category Scores", + "properties": { + "sexual": { + "type": "number", + "title": "Sexual Score" + }, + "hate": { + "type": "number", + "title": "Hate Score" + }, + "harassment": { + "type": "number", + "title": "Harassment Score" + }, + "self-harm": { + "type": "number", + "title": "Self-Harm Score" + }, + "sexual/minors": { + "type": "number", + "title": "Sexual/Minors Score" + }, + "hate/threatening": { + "type": "number", + "title": "Hate/Threatening Score" + }, + "violence/graphic": { + "type": "number", + "title": "Violence/Graphic Score" + }, + "self-harm/intent": { + "type": "number", + "title": "Self-Harm/Intent Score" + }, + "self-harm/instructions": { + "type": "number", + "title": "Self-Harm/Instructions Score" + }, + "harassment/threatening": { + "type": "number", + "title": "Harassment/Threatening Score" + }, + "violence": { + "type": "number", + "title": "Violence Score" + } + } + } + } + } + }, + { + "label": "Text", + "value": "input", + "schema": { + "type": "string" + } + } + ] + }, + { + "name": "NotFlagged", + "options": [ + { + "label": "Moderation", + "value": "moderation", + "schema": { + "type": "object", + "properties": { + "flagged": { + "type": "boolean", + "title": "Is Flagged" + }, + "categories": { + "type": "object", + "title": "Categories", + "properties": { + "sexual": { + "type": "boolean", + "title": "Is Sexual" + }, + "hate": { + "type": "boolean", + "title": "Is Hate" + }, + "harassment": { + "type": "boolean", + "title": "Is Harassment" + }, + "self-harm": { + "type": "boolean", + "title": "Is Self-Harm" + }, + "sexual/minors": { + "type": "boolean", + "title": "Is Sexual/Minors" + }, + "hate/threatening": { + "type": "boolean", + "title": "Is Hate/Threatening" + }, + "violence/graphic": { + "type": "boolean", + "title": "Is Violence/Graphic" + }, + "self-harm/intent": { + "type": "boolean", + "title": "Is Self-Harm/Intent" + }, + "self-harm/instructions": { + "type": "boolean", + "title": "Is Self-Harm/Instructions" + }, + "harassment/threatening": { + "type": "boolean", + "title": "Is Harassment/Threatening" + }, + "violence": { + "type": "boolean", + "title": "Is Violence" + } + } + }, + "category_scores": { + "type": "object", + "title": "Category Scores", + "properties": { + "sexual": { + "type": "number", + "title": "Sexual Score" + }, + "hate": { + "type": "number", + "title": "Hate Score" + }, + "harassment": { + "type": "number", + "title": "Harassment Score" + }, + "self-harm": { + "type": "number", + "title": "Self-Harm Score" + }, + "sexual/minors": { + "type": "number", + "title": "Sexual/Minors Score" + }, + "hate/threatening": { + "type": "number", + "title": "Hate/Threatening Score" + }, + "violence/graphic": { + "type": "number", + "title": "Violence/Graphic Score" + }, + "self-harm/intent": { + "type": "number", + "title": "Self-Harm/Intent Score" + }, + "self-harm/instructions": { + "type": "number", + "title": "Self-Harm/Instructions Score" + }, + "harassment/threatening": { + "type": "number", + "title": "Harassment/Threatening Score" + }, + "violence": { + "type": "number", + "title": "Violence Score" + } + } + } + } + } + }, + { + "label": "Text", + "value": "input", + "schema": { + "type": "string" + } + } + ] + } + ], + "icon": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNHB4IiBoZWlnaHQ9IjI0cHgiIGZpbGw9ImN1cnJlbnRDb2xvciIgdmlld0JveD0iMCAwIDI0IDI0IiBjb2xvcj0iYmxhY2siPjxwYXRoIGQ9Ik0yMi40MTggOS44MjJhNS45MDMgNS45MDMgMCAwIDAtLjUyLTQuOTEgNi4xIDYuMSAwIDAgMC0yLjgyMi0yLjUxMyA2LjIwNCA2LjIwNCAwIDAgMC0zLjc4LS4zODlBNi4wNTUgNi4wNTUgMCAwIDAgMTMuMjMyLjUxOCA2LjEyOSA2LjEyOSAwIDAgMCAxMC43MjYgMGE2LjE4NSA2LjE4NSAwIDAgMC0zLjYxNSAxLjE1M0E2LjA1MiA2LjA1MiAwIDAgMCA0Ljg4IDQuMTg3YTYuMTAyIDYuMTAyIDAgMCAwLTIuMzQ0IDEuMDE4QTYuMDA4IDYuMDA4IDAgMCAwIC44MjggNy4wODdhNS45ODEgNS45ODEgMCAwIDAgLjc1NCA3LjA5IDUuOTA0IDUuOTA0IDAgMCAwIC41MiA0LjkxMSA2LjEwMSA2LjEwMSAwIDAgMCAyLjgyMSAyLjUxMyA2LjIwNSA2LjIwNSAwIDAgMCAzLjc4LjM4OSA2LjA1NyA2LjA1NyAwIDAgMCAyLjA2NSAxLjQ5MiA2LjEzIDYuMTMgMCAwIDAgMi41MDUuNTE4IDYuMTg1IDYuMTg1IDAgMCAwIDMuNjE3LTEuMTU0IDYuMDUyIDYuMDUyIDAgMCAwIDIuMjMyLTMuMDM1IDYuMTAxIDYuMTAxIDAgMCAwIDIuMzQzLTEuMDE4IDYuMDA5IDYuMDA5IDAgMCAwIDEuNzA5LTEuODgzIDUuOTgxIDUuOTgxIDAgMCAwLS43NTYtNy4wODhabS05LjE0MyAxMi42MDlhNC41ODMgNC41ODMgMCAwIDEtMi45MTgtMS4wNGMuMDM3LS4wMi4xMDItLjA1Ni4xNDQtLjA4MWw0Ljg0NC0yLjc2YS43ODMuNzgzIDAgMCAwIC4zOTctLjY4di02LjczOEwxNy43OSAxMi4zYS4wNzIuMDcyIDAgMCAxIC4wNC4wNTV2NS41OGE0LjQ3MyA0LjQ3MyAwIDAgMS0xLjMzNSAzLjE3NiA0LjU5NiA0LjU5NiAwIDAgMS0zLjIxOSAxLjMyMVptLTkuNzkzLTQuMTI3YTQuNDMyIDQuNDMyIDAgMCAxLS41NDQtMy4wMTRjLjAzNi4wMjEuMDk5LjA2LjE0NC4wODVsNC44NDMgMi43NmEuNzk2Ljc5NiAwIDAgMCAuNzk1IDBsNS45MTMtMy4zNjlWMTcuMWEuMDcxLjA3MSAwIDAgMS0uMDI5LjA2Mkw5LjcwOCAxOS45NWE0LjYxNyA0LjYxNyAwIDAgMS0zLjQ1OC40NDcgNC41NTYgNC41NTYgMCAwIDEtMi43NjgtMi4wOTNaTTIuMjA4IDcuODcyQTQuNTI3IDQuNTI3IDAgMCAxIDQuNTggNS45bC0uMDAyLjE2NHY1LjUyYS43NjguNzY4IDAgMCAwIC4zOTcuNjhsNS45MTMgMy4zNjktMi4wNDcgMS4xNjZhLjA3NS4wNzUgMCAwIDEtLjA2OS4wMDZsLTQuODk2LTIuNzkyYTQuNTEgNC41MSAwIDAgMS0yLjEyLTIuNzMgNC40NSA0LjQ1IDAgMCAxIC40NTItMy40MTFabTE2LjgxOCAzLjg2MS01LjkxMy0zLjM2OCAyLjA0Ny0xLjE2NmEuMDc0LjA3NCAwIDAgMSAuMDctLjAwNmw0Ljg5NiAyLjc4OWE0LjUyNiA0LjUyNiAwIDAgMSAxLjc2MiAxLjgxNSA0LjQ0OCA0LjQ0OCAwIDAgMS0uNDE4IDQuODA4IDQuNTU2IDQuNTU2IDAgMCAxLTIuMDQ5IDEuNDk0di01LjY4NmEuNzY3Ljc2NyAwIDAgMC0uMzk1LS42OFptMi4wMzgtMy4wMjVhNi44NzQgNi44NzQgMCAwIDAtLjE0NC0uMDg1bC00Ljg0My0yLjc2YS43OTcuNzk3IDAgMCAwLS43OTYgMEw5LjM2OCA5LjIzVjYuOWEuMDcyLjA3MiAwIDAgMSAuMDMtLjA2Mmw0Ljg5NS0yLjc4N2E0LjYwOCA0LjYwOCAwIDAgMSA0Ljg4NS4yMDcgNC41MSA0LjUxIDAgMCAxIDEuNTk5IDEuOTU1Yy4zMzMuNzg4LjQzMyAxLjY1NC4yODcgMi40OTZaTTguMjU1IDEyLjg2NSA2LjIwOCAxMS43YS4wNzEuMDcxIDAgMCAxLS4wNC0uMDU2di01LjU4YzAtLjg1NC4yNDgtMS42OS43MTMtMi40MTJhNC41NCA0LjU0IDAgMCAxIDEuOTEzLTEuNjU4IDQuNjE0IDQuNjE0IDAgMCAxIDQuODUuNjE2Yy0uMDM3LjAyLS4xMDIuMDU1LS4xNDQuMDhMOC42NTcgNS40NTJhLjc4Mi43ODIgMCAwIDAtLjM5OC42OGwtLjAwNCA2LjczNFpNOS4zNjcgMTAuNSAxMi4wMDEgOWwyLjYzMyAxLjV2M0wxMi4wMDEgMTVsLTIuNjM0LTEuNXYtM1oiLz48L3N2Zz4=" +} \ No newline at end of file diff --git a/src/appmixer/ai/openai/SendPrompt/SendPrompt.js b/src/appmixer/ai/openai/SendPrompt/SendPrompt.js new file mode 100644 index 000000000..5723013fc --- /dev/null +++ b/src/appmixer/ai/openai/SendPrompt/SendPrompt.js @@ -0,0 +1,40 @@ +'use strict'; + +module.exports = { + + receive: async function(context) { + + const { prompt, model } = context.messages.in.content; + const apiKey = context.auth.apiKey; + + const url = 'https://api.openai.com/v1/chat/completions'; + const { data } = await context.httpRequest.post(url, { + model: model || 'gpt-4o', + messages: [ + { + role: 'system', + content: 'You are a helpful assistant.', + name: 'system' + }, + { + role: 'user', + content: prompt, + name: 'user' + } + ] + }, { + headers: { + 'Authorization': `Bearer ${apiKey}`, + 'Content-Type': 'application/json' + } + }); + + let answer = ''; + + if (data && data.choices) { + answer = data.choices[0].message.content; + } + + return context.sendJson({ answer }, 'out'); + } +}; diff --git a/src/appmixer/ai/openai/SendPrompt/component.json b/src/appmixer/ai/openai/SendPrompt/component.json new file mode 100644 index 000000000..7439dc09b --- /dev/null +++ b/src/appmixer/ai/openai/SendPrompt/component.json @@ -0,0 +1,54 @@ +{ + "name": "appmixer.ai.openai.SendPrompt", + "author": "Appmixer ", + "description": "Send a prompt to a Large Language Model and receive a response.", + "auth": { + "service": "appmixer:ai:openai" + }, + "inPorts": [{ + "name": "in", + "schema": { + "type": "object", + "properties": { + "prompt": { "type": "string" }, + "model": { "type": "string" } + }, + "required": ["prompt"] + }, + "inspector": { + "inputs": { + "prompt": { + "label": "Prompt", + "type": "textarea", + "index": 1 + }, + "model": { + "type": "text", + "index": 2, + "label": "Model", + "tooltip": "ID of the model to use.", + "defaultValue": "gpt-4o", + "source": { + "url": "/component/appmixer/ai/openai/ListModels?outPort=out", + "data": { + "transform": "./ListModels#toSelectOptions" + } + } + } + } + } + }], + "outPorts": [{ + "name": "out", + "options": [{ + "label": "Answer", + "value": "answer", + "schema": { "type": "string" } + }, { + "label": "Prompt", + "value": "prompt", + "schema": { "type": "string" } + }] + }], + "icon": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNHB4IiBoZWlnaHQ9IjI0cHgiIGZpbGw9ImN1cnJlbnRDb2xvciIgdmlld0JveD0iMCAwIDI0IDI0IiBjb2xvcj0iYmxhY2siPjxwYXRoIGQ9Ik0yMi40MTggOS44MjJhNS45MDMgNS45MDMgMCAwIDAtLjUyLTQuOTEgNi4xIDYuMSAwIDAgMC0yLjgyMi0yLjUxMyA2LjIwNCA2LjIwNCAwIDAgMC0zLjc4LS4zODlBNi4wNTUgNi4wNTUgMCAwIDAgMTMuMjMyLjUxOCA2LjEyOSA2LjEyOSAwIDAgMCAxMC43MjYgMGE2LjE4NSA2LjE4NSAwIDAgMC0zLjYxNSAxLjE1M0E2LjA1MiA2LjA1MiAwIDAgMCA0Ljg4IDQuMTg3YTYuMTAyIDYuMTAyIDAgMCAwLTIuMzQ0IDEuMDE4QTYuMDA4IDYuMDA4IDAgMCAwIC44MjggNy4wODdhNS45ODEgNS45ODEgMCAwIDAgLjc1NCA3LjA5IDUuOTA0IDUuOTA0IDAgMCAwIC41MiA0LjkxMSA2LjEwMSA2LjEwMSAwIDAgMCAyLjgyMSAyLjUxMyA2LjIwNSA2LjIwNSAwIDAgMCAzLjc4LjM4OSA2LjA1NyA2LjA1NyAwIDAgMCAyLjA2NSAxLjQ5MiA2LjEzIDYuMTMgMCAwIDAgMi41MDUuNTE4IDYuMTg1IDYuMTg1IDAgMCAwIDMuNjE3LTEuMTU0IDYuMDUyIDYuMDUyIDAgMCAwIDIuMjMyLTMuMDM1IDYuMTAxIDYuMTAxIDAgMCAwIDIuMzQzLTEuMDE4IDYuMDA5IDYuMDA5IDAgMCAwIDEuNzA5LTEuODgzIDUuOTgxIDUuOTgxIDAgMCAwLS43NTYtNy4wODhabS05LjE0MyAxMi42MDlhNC41ODMgNC41ODMgMCAwIDEtMi45MTgtMS4wNGMuMDM3LS4wMi4xMDItLjA1Ni4xNDQtLjA4MWw0Ljg0NC0yLjc2YS43ODMuNzgzIDAgMCAwIC4zOTctLjY4di02LjczOEwxNy43OSAxMi4zYS4wNzIuMDcyIDAgMCAxIC4wNC4wNTV2NS41OGE0LjQ3MyA0LjQ3MyAwIDAgMS0xLjMzNSAzLjE3NiA0LjU5NiA0LjU5NiAwIDAgMS0zLjIxOSAxLjMyMVptLTkuNzkzLTQuMTI3YTQuNDMyIDQuNDMyIDAgMCAxLS41NDQtMy4wMTRjLjAzNi4wMjEuMDk5LjA2LjE0NC4wODVsNC44NDMgMi43NmEuNzk2Ljc5NiAwIDAgMCAuNzk1IDBsNS45MTMtMy4zNjlWMTcuMWEuMDcxLjA3MSAwIDAgMS0uMDI5LjA2Mkw5LjcwOCAxOS45NWE0LjYxNyA0LjYxNyAwIDAgMS0zLjQ1OC40NDcgNC41NTYgNC41NTYgMCAwIDEtMi43NjgtMi4wOTNaTTIuMjA4IDcuODcyQTQuNTI3IDQuNTI3IDAgMCAxIDQuNTggNS45bC0uMDAyLjE2NHY1LjUyYS43NjguNzY4IDAgMCAwIC4zOTcuNjhsNS45MTMgMy4zNjktMi4wNDcgMS4xNjZhLjA3NS4wNzUgMCAwIDEtLjA2OS4wMDZsLTQuODk2LTIuNzkyYTQuNTEgNC41MSAwIDAgMS0yLjEyLTIuNzMgNC40NSA0LjQ1IDAgMCAxIC40NTItMy40MTFabTE2LjgxOCAzLjg2MS01LjkxMy0zLjM2OCAyLjA0Ny0xLjE2NmEuMDc0LjA3NCAwIDAgMSAuMDctLjAwNmw0Ljg5NiAyLjc4OWE0LjUyNiA0LjUyNiAwIDAgMSAxLjc2MiAxLjgxNSA0LjQ0OCA0LjQ0OCAwIDAgMS0uNDE4IDQuODA4IDQuNTU2IDQuNTU2IDAgMCAxLTIuMDQ5IDEuNDk0di01LjY4NmEuNzY3Ljc2NyAwIDAgMC0uMzk1LS42OFptMi4wMzgtMy4wMjVhNi44NzQgNi44NzQgMCAwIDAtLjE0NC0uMDg1bC00Ljg0My0yLjc2YS43OTcuNzk3IDAgMCAwLS43OTYgMEw5LjM2OCA5LjIzVjYuOWEuMDcyLjA3MiAwIDAgMSAuMDMtLjA2Mmw0Ljg5NS0yLjc4N2E0LjYwOCA0LjYwOCAwIDAgMSA0Ljg4NS4yMDcgNC41MSA0LjUxIDAgMCAxIDEuNTk5IDEuOTU1Yy4zMzMuNzg4LjQzMyAxLjY1NC4yODcgMi40OTZaTTguMjU1IDEyLjg2NSA2LjIwOCAxMS43YS4wNzEuMDcxIDAgMCAxLS4wNC0uMDU2di01LjU4YzAtLjg1NC4yNDgtMS42OS43MTMtMi40MTJhNC41NCA0LjU0IDAgMCAxIDEuOTEzLTEuNjU4IDQuNjE0IDQuNjE0IDAgMCAxIDQuODUuNjE2Yy0uMDM3LjAyLS4xMDIuMDU1LS4xNDQuMDhMOC42NTcgNS40NTJhLjc4Mi43ODIgMCAwIDAtLjM5OC42OGwtLjAwNCA2LjczNFpNOS4zNjcgMTAuNSAxMi4wMDEgOWwyLjYzMyAxLjV2M0wxMi4wMDEgMTVsLTIuNjM0LTEuNXYtM1oiLz48L3N2Zz4=" +} diff --git a/src/appmixer/ai/openai/TransformTextToJSON/TransformTextToJSON.js b/src/appmixer/ai/openai/TransformTextToJSON/TransformTextToJSON.js new file mode 100644 index 000000000..10ce751d6 --- /dev/null +++ b/src/appmixer/ai/openai/TransformTextToJSON/TransformTextToJSON.js @@ -0,0 +1,65 @@ +'use strict'; + +module.exports = { + + receive: async function(context) { + + const { text, jsonSchema: jsonSchemaString, model } = context.messages.in.content; + + const jsonSchema = JSON.parse(jsonSchemaString); + + if (context.properties.generateOutputPortOptions) { + return this.getOutputPortOptions(context, jsonSchema); + } + + const apiKey = context.auth.apiKey; + + const url = 'https://api.openai.com/v1/chat/completions'; + const { data } = await context.httpRequest.post(url, { + model: model || 'gpt-4o-2024-08-06', + messages: [ + { + role: 'system', + content: 'You are an expert at structured data extraction. You will be given unstructured text and should convert it into the given structure.', + name: 'system' + }, + { + role: 'user', + content: text, + name: 'user' + } + ], + response_format: { + type: 'json_schema', + json_schema: { + name: 'json_extraction', + schema: jsonSchema + } + } + }, { + headers: { + 'Authorization': `Bearer ${apiKey}`, + 'Content-Type': 'application/json' + } + }); + + const json = JSON.parse(data.choices[0].message.content); + return context.sendJson({ json }, 'out'); + }, + + getOutputPortOptions: function(context, jsonSchema) { + + return context.sendJson([ + { + value: 'json', + label: 'JSON', + schema: jsonSchema + }, + { + value: 'text', + label: 'Text', + schema: { type: 'string' } + } + ], 'out'); + } +}; diff --git a/src/appmixer/ai/openai/TransformTextToJSON/component.json b/src/appmixer/ai/openai/TransformTextToJSON/component.json new file mode 100644 index 000000000..96e5f186c --- /dev/null +++ b/src/appmixer/ai/openai/TransformTextToJSON/component.json @@ -0,0 +1,65 @@ +{ + "name": "appmixer.ai.openai.TransformTextToJSON", + "author": "Appmixer ", + "description": "Extract structured JSON data from text using AI.", + "auth": { + "service": "appmixer:ai:openai" + }, + "inPorts": [{ + "name": "in", + "schema": { + "type": "object", + "properties": { + "text": { "type": "string" }, + "jsonSchema": { "type": "string" }, + "model": { "type": "string" } + }, + "required": ["text", "jsonSchema"] + }, + "inspector": { + "inputs": { + "text": { + "label": "Text", + "type": "textarea", + "index": 1, + "tooltip": "The text from which to extract structured JSON data. Example: John is 25 years old.." + }, + "jsonSchema": { + "label": "Output JSON Schema", + "type": "textarea", + "index": 2, + "tooltip": "The schema that defines the structure of the output JSON. Use JSON Schema format. Example: {\"type\":\"object\",\"properties\":{\"name\":{\"type\":\"string\"}, \"age\":{\"type\":\"number\"}}}. It must be a valid JSON schema and must be of \"type\": \"object\". If you want to produce an array, you can nest the array under an object property of type array. Example: {\"type\":\"object\",\"properties\":{\"contacts\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"properties\":{\"name\":{\"type\":\"string\"},\"age\":{\"type\":\"number\"}}}}}}." + }, + "model": { + "type": "text", + "index": 3, + "label": "Model", + "tooltip": "ID of the model to use.", + "defaultValue": "gpt-4o-2024-08-06", + "source": { + "url": "/component/appmixer/ai/openai/ListModels?outPort=out", + "data": { + "transform": "./ListModels#toSelectOptions" + } + } + } + } + } + }], + "outPorts": [{ + "name": "out", + "source": { + "url": "/component/appmixer/utils/ai/TransformTextToJSON?outPort=out", + "data": { + "properties": { + "generateOutputPortOptions": true + }, + "messages": { + "in/text": "dummy", + "in/jsonSchema": "inputs/in/jsonSchema" + } + } + } + }], + "icon": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNHB4IiBoZWlnaHQ9IjI0cHgiIGZpbGw9ImN1cnJlbnRDb2xvciIgdmlld0JveD0iMCAwIDI0IDI0IiBjb2xvcj0iYmxhY2siPjxwYXRoIGQ9Ik0yMi40MTggOS44MjJhNS45MDMgNS45MDMgMCAwIDAtLjUyLTQuOTEgNi4xIDYuMSAwIDAgMC0yLjgyMi0yLjUxMyA2LjIwNCA2LjIwNCAwIDAgMC0zLjc4LS4zODlBNi4wNTUgNi4wNTUgMCAwIDAgMTMuMjMyLjUxOCA2LjEyOSA2LjEyOSAwIDAgMCAxMC43MjYgMGE2LjE4NSA2LjE4NSAwIDAgMC0zLjYxNSAxLjE1M0E2LjA1MiA2LjA1MiAwIDAgMCA0Ljg4IDQuMTg3YTYuMTAyIDYuMTAyIDAgMCAwLTIuMzQ0IDEuMDE4QTYuMDA4IDYuMDA4IDAgMCAwIC44MjggNy4wODdhNS45ODEgNS45ODEgMCAwIDAgLjc1NCA3LjA5IDUuOTA0IDUuOTA0IDAgMCAwIC41MiA0LjkxMSA2LjEwMSA2LjEwMSAwIDAgMCAyLjgyMSAyLjUxMyA2LjIwNSA2LjIwNSAwIDAgMCAzLjc4LjM4OSA2LjA1NyA2LjA1NyAwIDAgMCAyLjA2NSAxLjQ5MiA2LjEzIDYuMTMgMCAwIDAgMi41MDUuNTE4IDYuMTg1IDYuMTg1IDAgMCAwIDMuNjE3LTEuMTU0IDYuMDUyIDYuMDUyIDAgMCAwIDIuMjMyLTMuMDM1IDYuMTAxIDYuMTAxIDAgMCAwIDIuMzQzLTEuMDE4IDYuMDA5IDYuMDA5IDAgMCAwIDEuNzA5LTEuODgzIDUuOTgxIDUuOTgxIDAgMCAwLS43NTYtNy4wODhabS05LjE0MyAxMi42MDlhNC41ODMgNC41ODMgMCAwIDEtMi45MTgtMS4wNGMuMDM3LS4wMi4xMDItLjA1Ni4xNDQtLjA4MWw0Ljg0NC0yLjc2YS43ODMuNzgzIDAgMCAwIC4zOTctLjY4di02LjczOEwxNy43OSAxMi4zYS4wNzIuMDcyIDAgMCAxIC4wNC4wNTV2NS41OGE0LjQ3MyA0LjQ3MyAwIDAgMS0xLjMzNSAzLjE3NiA0LjU5NiA0LjU5NiAwIDAgMS0zLjIxOSAxLjMyMVptLTkuNzkzLTQuMTI3YTQuNDMyIDQuNDMyIDAgMCAxLS41NDQtMy4wMTRjLjAzNi4wMjEuMDk5LjA2LjE0NC4wODVsNC44NDMgMi43NmEuNzk2Ljc5NiAwIDAgMCAuNzk1IDBsNS45MTMtMy4zNjlWMTcuMWEuMDcxLjA3MSAwIDAgMS0uMDI5LjA2Mkw5LjcwOCAxOS45NWE0LjYxNyA0LjYxNyAwIDAgMS0zLjQ1OC40NDcgNC41NTYgNC41NTYgMCAwIDEtMi43NjgtMi4wOTNaTTIuMjA4IDcuODcyQTQuNTI3IDQuNTI3IDAgMCAxIDQuNTggNS45bC0uMDAyLjE2NHY1LjUyYS43NjguNzY4IDAgMCAwIC4zOTcuNjhsNS45MTMgMy4zNjktMi4wNDcgMS4xNjZhLjA3NS4wNzUgMCAwIDEtLjA2OS4wMDZsLTQuODk2LTIuNzkyYTQuNTEgNC41MSAwIDAgMS0yLjEyLTIuNzMgNC40NSA0LjQ1IDAgMCAxIC40NTItMy40MTFabTE2LjgxOCAzLjg2MS01LjkxMy0zLjM2OCAyLjA0Ny0xLjE2NmEuMDc0LjA3NCAwIDAgMSAuMDctLjAwNmw0Ljg5NiAyLjc4OWE0LjUyNiA0LjUyNiAwIDAgMSAxLjc2MiAxLjgxNSA0LjQ0OCA0LjQ0OCAwIDAgMS0uNDE4IDQuODA4IDQuNTU2IDQuNTU2IDAgMCAxLTIuMDQ5IDEuNDk0di01LjY4NmEuNzY3Ljc2NyAwIDAgMC0uMzk1LS42OFptMi4wMzgtMy4wMjVhNi44NzQgNi44NzQgMCAwIDAtLjE0NC0uMDg1bC00Ljg0My0yLjc2YS43OTcuNzk3IDAgMCAwLS43OTYgMEw5LjM2OCA5LjIzVjYuOWEuMDcyLjA3MiAwIDAgMSAuMDMtLjA2Mmw0Ljg5NS0yLjc4N2E0LjYwOCA0LjYwOCAwIDAgMSA0Ljg4NS4yMDcgNC41MSA0LjUxIDAgMCAxIDEuNTk5IDEuOTU1Yy4zMzMuNzg4LjQzMyAxLjY1NC4yODcgMi40OTZaTTguMjU1IDEyLjg2NSA2LjIwOCAxMS43YS4wNzEuMDcxIDAgMCAxLS4wNC0uMDU2di01LjU4YzAtLjg1NC4yNDgtMS42OS43MTMtMi40MTJhNC41NCA0LjU0IDAgMCAxIDEuOTEzLTEuNjU4IDQuNjE0IDQuNjE0IDAgMCAxIDQuODUuNjE2Yy0uMDM3LjAyLS4xMDIuMDU1LS4xNDQuMDhMOC42NTcgNS40NTJhLjc4Mi43ODIgMCAwIDAtLjM5OC42OGwtLjAwNCA2LjczNFpNOS4zNjcgMTAuNSAxMi4wMDEgOWwyLjYzMyAxLjV2M0wxMi4wMDEgMTVsLTIuNjM0LTEuNXYtM1oiLz48L3N2Zz4=" +} diff --git a/src/appmixer/ai/openai/auth.js b/src/appmixer/ai/openai/auth.js new file mode 100644 index 000000000..468025fec --- /dev/null +++ b/src/appmixer/ai/openai/auth.js @@ -0,0 +1,35 @@ +'use strict'; + +module.exports = { + + type: 'apiKey', + + definition: () => { + + return { + auth: { + apiKey: { + type: 'text', + name: 'API Key', + tooltip: 'Log into your OpenAI account and find API Keys page in the Settings.' + } + }, + + validate: async (context) => { + + const url = 'https://api.openai.com/v1/models'; + return context.httpRequest.get(url, { + headers: { + 'Authorization': `Bearer ${context.apiKey}`, + 'Content-Type': 'application/json' + } + }); + }, + + accountNameFromProfileInfo: (context) => { + const apiKey = context.apiKey; + return apiKey.substr(0, 6) + '...' + apiKey.substr(-6); + } + }; + } +}; \ No newline at end of file diff --git a/src/appmixer/ai/openai/bundle.json b/src/appmixer/ai/openai/bundle.json new file mode 100644 index 000000000..3c801aaf4 --- /dev/null +++ b/src/appmixer/ai/openai/bundle.json @@ -0,0 +1,9 @@ +{ + "name": "appmixer.ai.openai", + "version": "1.0.0", + "changelog": { + "1.0.0": [ + "First version." + ] + } +} diff --git a/src/appmixer/ai/openai/module.json b/src/appmixer/ai/openai/module.json new file mode 100644 index 000000000..fd8970413 --- /dev/null +++ b/src/appmixer/ai/openai/module.json @@ -0,0 +1,10 @@ +{ + "name": "appmixer.ai.openai", + "label": "OpenAI", + "category": "ai", + "categoryIndex": 0, + "index": 1, + "categoryLabel": "AI", + "description": "OpenAI components for building AI agents.", + "icon": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNHB4IiBoZWlnaHQ9IjI0cHgiIGZpbGw9ImN1cnJlbnRDb2xvciIgdmlld0JveD0iMCAwIDI0IDI0IiBjb2xvcj0iYmxhY2siPjxwYXRoIGQ9Ik0yMi40MTggOS44MjJhNS45MDMgNS45MDMgMCAwIDAtLjUyLTQuOTEgNi4xIDYuMSAwIDAgMC0yLjgyMi0yLjUxMyA2LjIwNCA2LjIwNCAwIDAgMC0zLjc4LS4zODlBNi4wNTUgNi4wNTUgMCAwIDAgMTMuMjMyLjUxOCA2LjEyOSA2LjEyOSAwIDAgMCAxMC43MjYgMGE2LjE4NSA2LjE4NSAwIDAgMC0zLjYxNSAxLjE1M0E2LjA1MiA2LjA1MiAwIDAgMCA0Ljg4IDQuMTg3YTYuMTAyIDYuMTAyIDAgMCAwLTIuMzQ0IDEuMDE4QTYuMDA4IDYuMDA4IDAgMCAwIC44MjggNy4wODdhNS45ODEgNS45ODEgMCAwIDAgLjc1NCA3LjA5IDUuOTA0IDUuOTA0IDAgMCAwIC41MiA0LjkxMSA2LjEwMSA2LjEwMSAwIDAgMCAyLjgyMSAyLjUxMyA2LjIwNSA2LjIwNSAwIDAgMCAzLjc4LjM4OSA2LjA1NyA2LjA1NyAwIDAgMCAyLjA2NSAxLjQ5MiA2LjEzIDYuMTMgMCAwIDAgMi41MDUuNTE4IDYuMTg1IDYuMTg1IDAgMCAwIDMuNjE3LTEuMTU0IDYuMDUyIDYuMDUyIDAgMCAwIDIuMjMyLTMuMDM1IDYuMTAxIDYuMTAxIDAgMCAwIDIuMzQzLTEuMDE4IDYuMDA5IDYuMDA5IDAgMCAwIDEuNzA5LTEuODgzIDUuOTgxIDUuOTgxIDAgMCAwLS43NTYtNy4wODhabS05LjE0MyAxMi42MDlhNC41ODMgNC41ODMgMCAwIDEtMi45MTgtMS4wNGMuMDM3LS4wMi4xMDItLjA1Ni4xNDQtLjA4MWw0Ljg0NC0yLjc2YS43ODMuNzgzIDAgMCAwIC4zOTctLjY4di02LjczOEwxNy43OSAxMi4zYS4wNzIuMDcyIDAgMCAxIC4wNC4wNTV2NS41OGE0LjQ3MyA0LjQ3MyAwIDAgMS0xLjMzNSAzLjE3NiA0LjU5NiA0LjU5NiAwIDAgMS0zLjIxOSAxLjMyMVptLTkuNzkzLTQuMTI3YTQuNDMyIDQuNDMyIDAgMCAxLS41NDQtMy4wMTRjLjAzNi4wMjEuMDk5LjA2LjE0NC4wODVsNC44NDMgMi43NmEuNzk2Ljc5NiAwIDAgMCAuNzk1IDBsNS45MTMtMy4zNjlWMTcuMWEuMDcxLjA3MSAwIDAgMS0uMDI5LjA2Mkw5LjcwOCAxOS45NWE0LjYxNyA0LjYxNyAwIDAgMS0zLjQ1OC40NDcgNC41NTYgNC41NTYgMCAwIDEtMi43NjgtMi4wOTNaTTIuMjA4IDcuODcyQTQuNTI3IDQuNTI3IDAgMCAxIDQuNTggNS45bC0uMDAyLjE2NHY1LjUyYS43NjguNzY4IDAgMCAwIC4zOTcuNjhsNS45MTMgMy4zNjktMi4wNDcgMS4xNjZhLjA3NS4wNzUgMCAwIDEtLjA2OS4wMDZsLTQuODk2LTIuNzkyYTQuNTEgNC41MSAwIDAgMS0yLjEyLTIuNzMgNC40NSA0LjQ1IDAgMCAxIC40NTItMy40MTFabTE2LjgxOCAzLjg2MS01LjkxMy0zLjM2OCAyLjA0Ny0xLjE2NmEuMDc0LjA3NCAwIDAgMSAuMDctLjAwNmw0Ljg5NiAyLjc4OWE0LjUyNiA0LjUyNiAwIDAgMSAxLjc2MiAxLjgxNSA0LjQ0OCA0LjQ0OCAwIDAgMS0uNDE4IDQuODA4IDQuNTU2IDQuNTU2IDAgMCAxLTIuMDQ5IDEuNDk0di01LjY4NmEuNzY3Ljc2NyAwIDAgMC0uMzk1LS42OFptMi4wMzgtMy4wMjVhNi44NzQgNi44NzQgMCAwIDAtLjE0NC0uMDg1bC00Ljg0My0yLjc2YS43OTcuNzk3IDAgMCAwLS43OTYgMEw5LjM2OCA5LjIzVjYuOWEuMDcyLjA3MiAwIDAgMSAuMDMtLjA2Mmw0Ljg5NS0yLjc4N2E0LjYwOCA0LjYwOCAwIDAgMSA0Ljg4NS4yMDcgNC41MSA0LjUxIDAgMCAxIDEuNTk5IDEuOTU1Yy4zMzMuNzg4LjQzMyAxLjY1NC4yODcgMi40OTZaTTguMjU1IDEyLjg2NSA2LjIwOCAxMS43YS4wNzEuMDcxIDAgMCAxLS4wNC0uMDU2di01LjU4YzAtLjg1NC4yNDgtMS42OS43MTMtMi40MTJhNC41NCA0LjU0IDAgMCAxIDEuOTEzLTEuNjU4IDQuNjE0IDQuNjE0IDAgMCAxIDQuODUuNjE2Yy0uMDM3LjAyLS4xMDIuMDU1LS4xNDQuMDhMOC42NTcgNS40NTJhLjc4Mi43ODIgMCAwIDAtLjM5OC42OGwtLjAwNCA2LjczNFpNOS4zNjcgMTAuNSAxMi4wMDEgOWwyLjYzMyAxLjV2M0wxMi4wMDEgMTVsLTIuNjM0LTEuNXYtM1oiLz48L3N2Zz4=" +} diff --git a/src/appmixer/ai/openai/package.json b/src/appmixer/ai/openai/package.json new file mode 100644 index 000000000..a8132549f --- /dev/null +++ b/src/appmixer/ai/openai/package.json @@ -0,0 +1,10 @@ +{ + "name": "appmixer.ai.openai", + "version": "1.0.0", + "dependencies": { + "form-data": "4.0.0", + "langchain": "0.3.6", + "mustache": "4.2.0", + "openai": "4.80.1" + } +}