diff --git a/api/server/middleware/buildEndpointOption.js b/api/server/middleware/buildEndpointOption.js index 7205b76473..3de13ed2e6 100644 --- a/api/server/middleware/buildEndpointOption.js +++ b/api/server/middleware/buildEndpointOption.js @@ -42,6 +42,15 @@ async function buildEndpointOption(req, res, next) { return handleError(res, { text: 'Model spec mismatch' }); } + if ( + currentModelSpec.preset.endpoint !== EModelEndpoint.gptPlugins && + currentModelSpec.preset.tools + ) { + return handleError(res, { + text: `Only the "${EModelEndpoint.gptPlugins}" endpoint can have tools defined in the preset`, + }); + } + const isValidModelSpec = enforceModelSpec(currentModelSpec, parsedBody); if (!isValidModelSpec) { return handleError(res, { text: 'Model spec mismatch' }); diff --git a/api/server/middleware/enforceModelSpec.js b/api/server/middleware/enforceModelSpec.js index c2c10b3860..17270a5cf8 100644 --- a/api/server/middleware/enforceModelSpec.js +++ b/api/server/middleware/enforceModelSpec.js @@ -24,21 +24,32 @@ const enforceModelSpec = (modelSpec, parsedBody) => { /** * Checks if there is a match for the given key and value in the parsed body - * or any of its interchangeable keys. + * or any of its interchangeable keys, including deep comparison for objects and arrays. * @param {string} key * @param {any} value - * @param {TConversation} parsedBody + * @param {object} parsedBody * @returns {boolean} */ const checkMatch = (key, value, parsedBody) => { - if (parsedBody[key] === value) { + const isEqual = (a, b) => { + if (Array.isArray(a) && Array.isArray(b)) { + return a.length === b.length && a.every((val, index) => isEqual(val, b[index])); + } else if (typeof a === 'object' && typeof b === 'object' && a !== null && b !== null) { + const keysA = Object.keys(a); + const keysB = Object.keys(b); + return keysA.length === keysB.length && keysA.every((k) => isEqual(a[k], b[k])); + } + return a === b; + }; + + if (isEqual(parsedBody[key], value)) { return true; } if (interchangeableKeys.has(key)) { return interchangeableKeys .get(key) - .some((interchangeableKey) => parsedBody[interchangeableKey] === value); + .some((interchangeableKey) => isEqual(parsedBody[interchangeableKey], value)); } return false; diff --git a/api/server/middleware/enforceModelSpec.spec.js b/api/server/middleware/enforceModelSpec.spec.js new file mode 100644 index 0000000000..04a8e5b35f --- /dev/null +++ b/api/server/middleware/enforceModelSpec.spec.js @@ -0,0 +1,47 @@ +// enforceModelSpec.test.js + +const enforceModelSpec = require('./enforceModelSpec'); + +describe('enforceModelSpec function', () => { + test('returns true when all model specs match parsed body directly', () => { + const modelSpec = { preset: { title: 'Dialog', status: 'Active' } }; + const parsedBody = { title: 'Dialog', status: 'Active' }; + expect(enforceModelSpec(modelSpec, parsedBody)).toBe(true); + }); + + test('returns true when model specs match via interchangeable keys', () => { + const modelSpec = { preset: { chatGptLabel: 'GPT-4' } }; + const parsedBody = { modelLabel: 'GPT-4' }; + expect(enforceModelSpec(modelSpec, parsedBody)).toBe(true); + }); + + test('returns false if any key value does not match', () => { + const modelSpec = { preset: { language: 'English', level: 'Advanced' } }; + const parsedBody = { language: 'Spanish', level: 'Advanced' }; + expect(enforceModelSpec(modelSpec, parsedBody)).toBe(false); + }); + + test('ignores the \'endpoint\' key in model spec', () => { + const modelSpec = { preset: { endpoint: 'ignored', feature: 'Special' } }; + const parsedBody = { feature: 'Special' }; + expect(enforceModelSpec(modelSpec, parsedBody)).toBe(true); + }); + + test('handles nested objects correctly', () => { + const modelSpec = { preset: { details: { time: 'noon', location: 'park' } } }; + const parsedBody = { details: { time: 'noon', location: 'park' } }; + expect(enforceModelSpec(modelSpec, parsedBody)).toBe(true); + }); + + test('handles arrays within objects', () => { + const modelSpec = { preset: { tags: ['urgent', 'important'] } }; + const parsedBody = { tags: ['urgent', 'important'] }; + expect(enforceModelSpec(modelSpec, parsedBody)).toBe(true); + }); + + test('fails when arrays in objects do not match', () => { + const modelSpec = { preset: { tags: ['urgent', 'important'] } }; + const parsedBody = { tags: ['important', 'urgent'] }; // Different order + expect(enforceModelSpec(modelSpec, parsedBody)).toBe(false); + }); +});