diff --git a/packages/server/package.json b/packages/server/package.json index 5ed4579667..136eeb609b 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -47,7 +47,7 @@ "@apidevtools/swagger-parser": "10.0.3", "@budibase/backend-core": "0.0.1", "@budibase/client": "0.0.1", - "@budibase/pro": "0.0.1", + "@budibase/pro": "develop", "@budibase/shared-core": "0.0.1", "@budibase/string-templates": "0.0.1", "@budibase/types": "0.0.1", @@ -99,6 +99,7 @@ "mysql2": "2.3.3", "node-fetch": "2.6.7", "open": "8.4.0", + "openai": "^3.2.1", "pg": "8.10.0", "posthog-node": "1.3.0", "pouchdb": "7.3.0", diff --git a/packages/server/src/automations/actions.ts b/packages/server/src/automations/actions.ts index 94d41deabe..2f64e75816 100644 --- a/packages/server/src/automations/actions.ts +++ b/packages/server/src/automations/actions.ts @@ -71,10 +71,15 @@ export const BUILTIN_ACTION_DEFINITIONS: Record = // ran at all if (env.SELF_HOSTED) { const bash = require("./steps/bash") + const openai = require("./steps/openai") + // @ts-ignore ACTION_IMPLS["EXECUTE_BASH"] = bash.run // @ts-ignore BUILTIN_ACTION_DEFINITIONS["EXECUTE_BASH"] = bash.definition + + ACTION_IMPLS.OPENAI = openai.run + BUILTIN_ACTION_DEFINITIONS.OPENAI = openai.definition } export async function getActionDefinitions() { diff --git a/packages/server/src/automations/steps/openai.ts b/packages/server/src/automations/steps/openai.ts new file mode 100644 index 0000000000..88d3fb8b85 --- /dev/null +++ b/packages/server/src/automations/steps/openai.ts @@ -0,0 +1,105 @@ +import { Configuration, OpenAIApi } from "openai" +import { + AutomationActionStepId, + AutomationStepSchema, + AutomationStepInput, + AutomationStepType, + AutomationIOType, +} from "@budibase/types" +import * as automationUtils from "../automationUtils" +import environment from "../../environment" + +enum Model { + GPT_35_TURBO = "gpt-3.5-turbo", + // will only work with api keys that have access to the GPT4 API + GPT_4 = "gpt-4", +} + +export const definition: AutomationStepSchema = { + name: "OpenAI", + tagline: "Send prompts to ChatGPT", + icon: "Algorithm", + description: "Interact with the OpenAI ChatGPT API.", + type: AutomationStepType.ACTION, + internal: true, + stepId: AutomationActionStepId.OPENAI, + inputs: { + prompt: "", + }, + schema: { + inputs: { + properties: { + prompt: { + type: AutomationIOType.STRING, + title: "Prompt", + }, + model: { + type: AutomationIOType.STRING, + title: "Model", + enum: Object.values(Model), + }, + }, + required: ["prompt", "model"], + }, + outputs: { + properties: { + success: { + type: AutomationIOType.BOOLEAN, + description: "Whether the action was successful", + }, + response: { + type: AutomationIOType.STRING, + description: "What was output", + }, + }, + required: ["success", "response"], + }, + }, +} + +export async function run({ inputs, context }: AutomationStepInput) { + if (!environment.OPENAI_API_KEY) { + return { + success: false, + response: + "OpenAI API Key not configured - please add the OPENAI_API_KEY environment variable.", + } + } + + if (inputs.prompt == null) { + return { + success: false, + response: "Budibase OpenAI Automation Failed: No prompt supplied", + } + } + + try { + const configuration = new Configuration({ + apiKey: environment.OPENAI_API_KEY, + }) + + const openai = new OpenAIApi(configuration) + + const completion = await openai.createChatCompletion({ + model: inputs.model, + messages: [ + { + role: "user", + content: inputs.prompt, + }, + ], + }) + + const response = completion?.data?.choices[0]?.message?.content + + return { + response, + success: true, + } + } catch (err) { + return { + success: false, + response: automationUtils.getError(err), + } + } +} diff --git a/packages/server/src/automations/tests/openai.spec.ts b/packages/server/src/automations/tests/openai.spec.ts new file mode 100644 index 0000000000..31f7e48305 --- /dev/null +++ b/packages/server/src/automations/tests/openai.spec.ts @@ -0,0 +1,86 @@ +const setup = require("./utilities") +import environment from "../../environment" +import openai from "openai" + +jest.mock( + "openai", + jest.fn(() => ({ + Configuration: jest.fn(), + OpenAIApi: jest.fn(() => ({ + createChatCompletion: jest.fn(() => ({ + data: { + choices: [ + { + message: { + content: "This is a test", + }, + }, + ], + }, + })), + })), + })) +) + +const OPENAI_PROMPT = "What is the meaning of life?" + +describe("test the openai action", () => { + let config = setup.getConfig() + + beforeAll(async () => { + await config.init() + }) + + beforeEach(() => { + environment.OPENAI_API_KEY = "abc123" + }) + + afterAll(setup.afterAll) + + it("should present the correct error message when the OPENAI_API_KEY variable isn't set", async () => { + delete environment.OPENAI_API_KEY + + let res = await setup.runStep("OPENAI", { + prompt: OPENAI_PROMPT, + }) + expect(res.response).toEqual( + "OpenAI API Key not configured - please add the OPENAI_API_KEY environment variable." + ) + expect(res.success).toBeFalsy() + }) + + it("should be able to receive a response from ChatGPT given a prompt", async () => { + const res = await setup.runStep("OPENAI", { + prompt: OPENAI_PROMPT, + }) + expect(res.response).toEqual("This is a test") + expect(res.success).toBeTruthy() + }) + + it("should present the correct error message when a prompt is not provided", async () => { + const res = await setup.runStep("OPENAI", { + prompt: null, + }) + expect(res.response).toEqual( + "Budibase OpenAI Automation Failed: No prompt supplied" + ) + expect(res.success).toBeFalsy() + }) + + it("should present the correct error message when an error is thrown from the createChatCompletion call", async () => { + openai.OpenAIApi.mockImplementation(() => ({ + createChatCompletion: jest.fn(() => { + throw new Error("An error occurred while calling createChatCompletion") + }), + })) + + const res = await setup.runStep("OPENAI", { + prompt: OPENAI_PROMPT, + }) + + expect(res.response).toEqual( + "Error: An error occurred while calling createChatCompletion" + ) + expect(res.success).toBeFalsy() + }) +}) diff --git a/packages/server/src/environment.ts b/packages/server/src/environment.ts index bed4c711f6..3e5e475360 100644 --- a/packages/server/src/environment.ts +++ b/packages/server/src/environment.ts @@ -71,6 +71,7 @@ const environment = { BB_ADMIN_USER_EMAIL: process.env.BB_ADMIN_USER_EMAIL, BB_ADMIN_USER_PASSWORD: process.env.BB_ADMIN_USER_PASSWORD, PLUGINS_DIR: process.env.PLUGINS_DIR || "/plugins", + OPENAI_API_KEY: process.env.OPENAI_API_KEY, // flags ALLOW_DEV_AUTOMATIONS: process.env.ALLOW_DEV_AUTOMATIONS, DISABLE_THREADING: process.env.DISABLE_THREADING, diff --git a/packages/types/src/documents/app/automation.ts b/packages/types/src/documents/app/automation.ts index 53318777f0..a537b026d5 100644 --- a/packages/types/src/documents/app/automation.ts +++ b/packages/types/src/documents/app/automation.ts @@ -57,6 +57,7 @@ export enum AutomationActionStepId { FILTER = "FILTER", QUERY_ROWS = "QUERY_ROWS", LOOP = "LOOP", + OPENAI = "OPENAI", // these used to be lowercase step IDs, maintain for backwards compat discord = "discord", slack = "slack", diff --git a/packages/worker/package.json b/packages/worker/package.json index 684bd8755b..dded20cf30 100644 --- a/packages/worker/package.json +++ b/packages/worker/package.json @@ -38,7 +38,7 @@ "license": "GPL-3.0", "dependencies": { "@budibase/backend-core": "0.0.1", - "@budibase/pro": "0.0.1", + "@budibase/pro": "develop", "@budibase/string-templates": "0.0.1", "@budibase/types": "0.0.1", "@koa/router": "8.0.8", diff --git a/yarn.lock b/yarn.lock index e044074a50..33225932ea 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1728,6 +1728,47 @@ resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== +"@budibase/backend-core@2.6.19-alpha.4": + version "2.6.19-alpha.4" + resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-2.6.19-alpha.4.tgz#525dd7ba6c5db4404cf00d1165f79d34a1093826" + integrity sha512-1pfOr+J9xYawedVmvqpQ4b/8C2SQP4cKhFmSz5uErM2SCgbRj+JuzOUTPNX0vzAXPvat/kEegt79xThummDvhA== + dependencies: + "@budibase/nano" "10.1.2" + "@budibase/pouchdb-replication-stream" "1.2.10" + "@budibase/types" "2.6.19-alpha.4" + "@shopify/jest-koa-mocks" "5.0.1" + "@techpass/passport-openidconnect" "0.3.2" + aws-cloudfront-sign "2.2.0" + aws-sdk "2.1030.0" + bcrypt "5.0.1" + bcryptjs "2.4.3" + bull "4.10.1" + correlation-id "4.0.0" + dotenv "16.0.1" + emitter-listener "1.1.2" + ioredis "4.28.0" + joi "17.6.0" + jsonwebtoken "9.0.0" + koa-passport "4.1.4" + koa-pino-logger "4.0.0" + lodash "4.17.21" + lodash.isarguments "3.1.0" + node-fetch "2.6.7" + passport-google-oauth "2.0.0" + passport-jwt "4.0.0" + passport-local "1.0.0" + passport-oauth2-refresh "^2.1.0" + pino "8.11.0" + pino-http "8.3.3" + posthog-node "1.3.0" + pouchdb "7.3.0" + pouchdb-find "7.2.2" + redlock "4.2.0" + sanitize-s3-objectkey "0.0.1" + semver "7.3.7" + tar-fs "2.1.1" + uuid "8.3.2" + "@budibase/bbui@^0.9.139": version "0.9.190" resolved "https://registry.yarnpkg.com/@budibase/bbui/-/bbui-0.9.190.tgz#e1ec400ac90f556bfbc80fc23a04506f1585ea81" @@ -1828,6 +1869,32 @@ pouchdb-promise "^6.0.4" through2 "^2.0.0" +"@budibase/pro@develop": + version "2.6.19-alpha.4" + resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-2.6.19-alpha.4.tgz#5d4c885ac9ac4ccfb2f8896961aca903c1178750" + integrity sha512-iu2QzV8Z77c00muBSK+NVsZdug3lLD0lR+vcKancGEz1PPE4yNIH7g8jB6i8h9agArbx9S2ICeHQqGb6nQaGHQ== + dependencies: + "@budibase/backend-core" "2.6.19-alpha.4" + "@budibase/shared-core" "2.6.19-alpha.4" + "@budibase/string-templates" "2.6.19-alpha.4" + "@budibase/types" "2.6.19-alpha.4" + "@koa/router" "8.0.8" + bull "4.10.1" + joi "17.6.0" + jsonwebtoken "8.5.1" + lru-cache "^7.14.1" + memorystream "^0.3.1" + node-fetch "^2.6.1" + scim-patch "^0.7.0" + scim2-parse-filter "^0.2.8" + +"@budibase/shared-core@2.6.19-alpha.4": + version "2.6.19-alpha.4" + resolved "https://registry.yarnpkg.com/@budibase/shared-core/-/shared-core-2.6.19-alpha.4.tgz#dd22dd0a18ee4d6739b629f461e5caec90706282" + integrity sha512-ac6iWSsgz70OYbdA+QHPLpTnRbIZ4OecVc6Y7gnEZ78hZ4S5da8a+73jswuy0/t4YsiT/gjukjzjoihg3NemVg== + dependencies: + "@budibase/types" "2.6.19-alpha.4" + "@budibase/standard-components@^0.9.139": version "0.9.139" resolved "https://registry.yarnpkg.com/@budibase/standard-components/-/standard-components-0.9.139.tgz#cf8e2b759ae863e469e50272b3ca87f2827e66e3" @@ -1846,6 +1913,25 @@ svelte-apexcharts "^1.0.2" svelte-flatpickr "^3.1.0" +"@budibase/string-templates@2.6.19-alpha.4": + version "2.6.19-alpha.4" + resolved "https://registry.yarnpkg.com/@budibase/string-templates/-/string-templates-2.6.19-alpha.4.tgz#92ebd69a6841174b8af91f338c4754ca7c402707" + integrity sha512-KsH3NlQcJibRj98Q8zQ3KQHhfSIWPQfvR80MmBTIe05llEZGox4re4pQQUnlMafaUEyNNtIqVnbTJ1XP0LmFng== + dependencies: + "@budibase/handlebars-helpers" "^0.11.8" + dayjs "^1.10.4" + handlebars "^4.7.6" + handlebars-utils "^1.0.6" + lodash "^4.17.20" + vm2 "^3.9.15" + +"@budibase/types@2.6.19-alpha.4": + version "2.6.19-alpha.4" + resolved "https://registry.yarnpkg.com/@budibase/types/-/types-2.6.19-alpha.4.tgz#bcf81699329d3f8509e4b0a489211f35b6cfa7ce" + integrity sha512-qFsXHZTSigcfCv02aTZGsf17vBT/MC+zK9ky7WZVX4h0sJiE0li4A66/tMaSDz3/vQ7ToPRhJK/p+LOWA/oceg== + dependencies: + scim-patch "^0.7.0" + "@bull-board/api@3.7.0": version "3.7.0" resolved "https://registry.yarnpkg.com/@bull-board/api/-/api-3.7.0.tgz#231f687187c0cb34e0b97f463917b6aaeb4ef6af" @@ -3457,11 +3543,6 @@ resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24" integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== -"@jridgewell/sourcemap-codec@^1.4.13": - version "1.4.15" - resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" - integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== - "@jridgewell/trace-mapping@0.3.9": version "0.3.9" resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9" @@ -4242,7 +4323,7 @@ dependencies: slash "^3.0.0" -"@rollup/plugin-commonjs@16.0.0", "@rollup/plugin-commonjs@^16.0.0": +"@rollup/plugin-commonjs@^16.0.0": version "16.0.0" resolved "https://registry.yarnpkg.com/@rollup/plugin-commonjs/-/plugin-commonjs-16.0.0.tgz#169004d56cd0f0a1d0f35915d31a036b0efe281f" integrity sha512-LuNyypCP3msCGVQJ7ki8PqYdpjfEkE/xtFa5DqlF+7IBD0JsfMZ87C58heSwIMint58sAUZbt3ITqOmdQv/dXw== @@ -4325,22 +4406,6 @@ "@rollup/pluginutils" "^3.1.0" magic-string "^0.25.7" -"@rollup/plugin-replace@^5.0.2": - version "5.0.2" - resolved "https://registry.yarnpkg.com/@rollup/plugin-replace/-/plugin-replace-5.0.2.tgz#45f53501b16311feded2485e98419acb8448c61d" - integrity sha512-M9YXNekv/C/iHHK+cvORzfRYfPbq0RDD8r0G+bMiTXjNGKulPnCT9O3Ss46WfhI6ZOCgApOP7xAdmCQJ+U2LAA== - dependencies: - "@rollup/pluginutils" "^5.0.1" - magic-string "^0.27.0" - -"@rollup/plugin-typescript@8.3.0": - version "8.3.0" - resolved "https://registry.yarnpkg.com/@rollup/plugin-typescript/-/plugin-typescript-8.3.0.tgz#bc1077fa5897b980fc27e376c4e377882c63e68b" - integrity sha512-I5FpSvLbtAdwJ+naznv+B4sjXZUcIvLLceYpITAn7wAP8W0wqc5noLdGIp9HGVntNhRWXctwPYrSSFQxtl0FPA== - dependencies: - "@rollup/pluginutils" "^3.1.0" - resolve "^1.17.0" - "@rollup/pluginutils@^3.0.8", "@rollup/pluginutils@^3.1.0": version "3.1.0" resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-3.1.0.tgz#706b4524ee6dc8b103b3c995533e5ad680c02b9b" @@ -12368,7 +12433,7 @@ fs.realpath@^1.0.0: resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== -fsevents@^2.1.2, fsevents@^2.3.2, fsevents@~2.3.1, fsevents@~2.3.2: +fsevents@^2.1.2, fsevents@^2.3.2, fsevents@~2.3.2: version "2.3.2" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== @@ -17416,13 +17481,6 @@ magic-string@^0.26.2: dependencies: sourcemap-codec "^1.4.8" -magic-string@^0.27.0: - version "0.27.0" - resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.27.0.tgz#e4a3413b4bab6d98d2becffd48b4a257effdbbf3" - integrity sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA== - dependencies: - "@jridgewell/sourcemap-codec" "^1.4.13" - make-dir@3.1.0, make-dir@^3.0.0, make-dir@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" @@ -19173,6 +19231,14 @@ open@^8.4.0: is-docker "^2.1.1" is-wsl "^2.2.0" +openai@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/openai/-/openai-3.2.1.tgz#1fa35bdf979cbde8453b43f2dd3a7d401ee40866" + integrity sha512-762C9BNlJPbjjlWZi4WYK9iM2tAVAv0uUp1UmI34vb0CN5T2mjB/qM6RYBmNKMh/dN9fC+bxqPwWJZUTWW052A== + dependencies: + axios "^0.26.0" + form-data "^4.0.0" + openapi-response-validator@^9.2.0: version "9.3.1" resolved "https://registry.yarnpkg.com/openapi-response-validator/-/openapi-response-validator-9.3.1.tgz#54284d8be608ef53283cbe7448accce8106b1c56" @@ -22241,13 +22307,6 @@ rollup-pluginutils@^2.3.1, rollup-pluginutils@^2.5.0, rollup-pluginutils@^2.6.0, dependencies: estree-walker "^0.6.1" -rollup@2.45.2: - version "2.45.2" - resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.45.2.tgz#8fb85917c9f35605720e92328f3ccbfba6f78b48" - integrity sha512-kRRU7wXzFHUzBIv0GfoFFIN3m9oteY4uAsKllIpQDId5cfnkWF2J130l+27dzDju0E6MScKiV0ZM5Bw8m4blYQ== - optionalDependencies: - fsevents "~2.3.1" - rollup@^2.36.2, rollup@^2.44.0, rollup@^2.45.2, rollup@^2.79.1: version "2.79.1" resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.79.1.tgz#bedee8faef7c9f93a2647ac0108748f497f081c7" @@ -24253,7 +24312,7 @@ timed-out@^4.0.1: resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-4.0.1.tgz#f32eacac5a175bea25d7fab565ab3ed8741ef56f" integrity sha512-G7r3AhovYtr5YKOWQkta8RKAPb+J9IsO4uVmzjl8AZwfhs8UcUwTiD6gcJYSgOtzyjvQKrKYn41syHbUWMkafA== -timekeeper@2.2.0, timekeeper@^2.2.0: +timekeeper@2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/timekeeper/-/timekeeper-2.2.0.tgz#9645731fce9e3280a18614a57a9d1b72af3ca368" integrity sha512-W3AmPTJWZkRwu+iSNxPIsLZ2ByADsOLbbLxe46UJyWj3mlYLlwucKiq+/dPm0l9wTzqoF3/2PH0AGFCebjq23A==