diff --git a/package-lock.json b/package-lock.json index 214aabb7f6..afc9b098f2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -81,7 +81,7 @@ "node-cache": "^5.1.2", "nodemailer": "^6.9.16", "oauth4webapi": "^3.6.0", - "openai": "^4.70.3", + "openai": "^6.16.0", "openid-client": "^6.6.2", "openid-client-legacy": "npm:openid-client@^5.7.1", "opossum": "^8.1.4", @@ -191,7 +191,7 @@ "worker-loader": "^2.0.0" }, "engines": { - "node": ">=22", + "node": ">=22.22", "npm": ">=8" } }, @@ -7987,15 +7987,6 @@ "undici-types": "~7.8.0" } }, - "node_modules/@types/node-fetch": { - "version": "2.6.11", - "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.11.tgz", - "integrity": "sha512-24xFj9R5+rfQJLRyM56qh+wnVSYhyXC2tkoBndtY0U+vubqNsYXGjufB2nn8Q6gt0LrARwL6UBtMCSVCwl4B1g==", - "dependencies": { - "@types/node": "*", - "form-data": "^4.0.0" - } - }, "node_modules/@types/node/node_modules/undici-types": { "version": "7.8.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz", @@ -8935,17 +8926,6 @@ "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" }, - "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/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -9031,17 +9011,6 @@ "node": ">= 6.0.0" } }, - "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/ajv": { "version": "6.12.6", "dev": true, @@ -13146,14 +13115,6 @@ "node": ">= 0.6" } }, - "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": "5.0.1", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", @@ -13838,23 +13799,6 @@ "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/formidable": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/formidable/-/formidable-2.1.1.tgz", @@ -14608,14 +14552,6 @@ "node": ">=16.17.0" } }, - "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/husky": { "version": "9.1.6", "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.6.tgz", @@ -19441,24 +19377,6 @@ "node": ">= 8.0.0" } }, - "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", @@ -19910,39 +19828,26 @@ } }, "node_modules/openai": { - "version": "4.70.3", - "resolved": "https://registry.npmjs.org/openai/-/openai-4.70.3.tgz", - "integrity": "sha512-N2XOWjuT5yKIdLgjZkQt9i5+cAXI7qKM7E5PpIsmVfnTi/Y812omr3rozgKwxXJC6aga8nl2BWos4HRdlZllFA==", - "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" - }, + "version": "6.16.0", + "resolved": "https://registry.npmjs.org/openai/-/openai-6.16.0.tgz", + "integrity": "sha512-fZ1uBqjFUjXzbGc35fFtYKEOxd20kd9fDpFeqWtsOZWiubY8CZ1NAlXHW3iathaFvqmNtCWMIsosCuyeI7Joxg==", + "license": "Apache-2.0", "bin": { "openai": "bin/cli" }, "peerDependencies": { - "zod": "^3.23.8" + "ws": "^8.18.0", + "zod": "^3.25 || ^4.0" }, "peerDependenciesMeta": { + "ws": { + "optional": true + }, "zod": { "optional": true } } }, - "node_modules/openai/node_modules/@types/node": { - "version": "18.19.105", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.105.tgz", - "integrity": "sha512-a+DrwD2VyzqQR2W0EVF8EaCh6Em4ilQAYLEPZnMNkQHXR7ziWW7RUhZMWZAgRpkDDAdUIcJOXSPJT/zBEwz3sA==", - "license": "MIT", - "dependencies": { - "undici-types": "~5.26.4" - } - }, "node_modules/openid-client": { "version": "6.6.2", "resolved": "https://registry.npmjs.org/openid-client/-/openid-client-6.6.2.tgz", @@ -23474,11 +23379,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "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/universalify": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", @@ -23689,14 +23589,6 @@ "makeerror": "1.0.12" } }, - "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": "7.0.0", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", diff --git a/package.json b/package.json index 16501f21e9..509fa94322 100644 --- a/package.json +++ b/package.json @@ -128,7 +128,7 @@ "node-cache": "^5.1.2", "nodemailer": "^6.9.16", "oauth4webapi": "^3.6.0", - "openai": "^4.70.3", + "openai": "^6.16.0", "openid-client": "^6.6.2", "openid-client-legacy": "npm:openid-client@^5.7.1", "opossum": "^8.1.4", diff --git a/src/app/modules/form/admin-form/admin-form.assistance.service.ts b/src/app/modules/form/admin-form/admin-form.assistance.service.ts index 3aed2dbf81..1dca23cc59 100644 --- a/src/app/modules/form/admin-form/admin-form.assistance.service.ts +++ b/src/app/modules/form/admin-form/admin-form.assistance.service.ts @@ -276,7 +276,8 @@ const generateAndsendTextPromptToModel = ({ formId, options: { response_format: { - type: 'json_object', + type: 'json_schema', + json_schema: suggestedFormFieldsLlmSchema, }, }, }).mapErr((error) => { @@ -445,6 +446,126 @@ const generateFormCreationVisionPrompt = ({ ] as Message[] } +const suggestedFormFieldsLlmSchema = { + name: 'suggested_form_fields', + strict: true, + schema: { + type: 'object', + properties: { + fields: { + type: 'array', + minItems: 1, + items: { + anyOf: [ + // 1. Base field schema + { + type: 'object', + properties: { + title: { type: 'string', minLength: 1 }, + fieldType: { + type: 'string', + enum: [ + 'Section', + 'Email', + 'Mobile', + 'HomeNo', + 'Number', + 'Decimal', + 'ShortText', + 'LongText', + 'CountryRegion', + 'YesNo', + 'Attachment', + 'Date', + 'Rating', + 'Nric', + 'Uen', + 'Address', + 'Signature', + ], + }, + required: { type: 'boolean' }, + description: { type: 'string' }, + }, + required: ['title', 'fieldType', 'required', 'description'], // description must be required in Strict Mode if defined + additionalProperties: false, + }, + // 2. Table field schema + { + type: 'object', + properties: { + title: { type: 'string', minLength: 1 }, + fieldType: { type: 'string', const: 'Table' }, + required: { type: 'boolean' }, + description: { type: 'string' }, + columns: { + type: 'array', + items: { type: 'string', minLength: 1 }, + minItems: 1, + }, + minimumRows: { type: 'integer', minimum: 1 }, + maximumRows: { type: 'integer', minimum: 1 }, + addMoreRows: { type: 'boolean' }, + }, + required: [ + 'title', + 'fieldType', + 'required', + 'description', + 'columns', + 'minimumRows', + 'maximumRows', + 'addMoreRows', + ], + additionalProperties: false, + }, + // 3. Statement field schema + { + type: 'object', + properties: { + title: { type: 'string' }, + fieldType: { type: 'string', const: 'Statement' }, + required: { type: 'boolean' }, + description: { type: 'string', minLength: 1 }, + }, + required: ['title', 'fieldType', 'required', 'description'], + additionalProperties: false, + }, + // 4. Choices field schema + { + type: 'object', + properties: { + title: { type: 'string', minLength: 1 }, + fieldType: { + type: 'string', + enum: ['Checkbox', 'Radio', 'Dropdown'], + }, + required: { type: 'boolean' }, + description: { type: 'string' }, + fieldOptions: { + type: 'array', + items: { type: 'string', minLength: 1 }, + minItems: 1, + }, + }, + required: [ + 'title', + 'fieldType', + 'required', + 'description', + 'fieldOptions', + ], + additionalProperties: false, + }, + ], + }, + }, + }, + required: ['fields'], + additionalProperties: false, + }, +} + const generateAndSendVisionPromptToModel = ({ formId, imageDataUrls, @@ -458,7 +579,8 @@ const generateAndSendVisionPromptToModel = ({ formId, options: { response_format: { - type: 'json_object', + type: 'json_schema', + json_schema: suggestedFormFieldsLlmSchema, }, }, }).mapErr((error) => {