From 7b63ab207e91a9545531889912ab901f58a5e874 Mon Sep 17 00:00:00 2001 From: Enio Kaso Date: Wed, 27 May 2026 00:57:18 +0200 Subject: [PATCH] fix: Agent OpenAI calls fail for GPT-5 and o-series models The agent hardcoded `max_tokens` and `temperature` in the Chat Completions request body. Newer OpenAI models (the GPT-5 family and the o-reasoning series) reject `max_tokens` in favor of `max_completion_tokens` and only accept the default temperature, so configuring e.g. `gpt-5.5` failed with "Unsupported parameter: 'max_tokens' is not supported with this model." Add a pure, testable helper (openAIModelParams.js) that detects the model generation and emits the correct token/temperature parameters, preserving legacy behavior for GPT-4.1 and older. Co-Authored-By: Claude Opus 4.7 --- Parse-Dashboard/app.js | 9 +-- Parse-Dashboard/openAIModelParams.js | 74 +++++++++++++++++++++++++ README.md | 4 +- src/lib/tests/openAIModelParams.test.js | 69 +++++++++++++++++++++++ 4 files changed, 151 insertions(+), 5 deletions(-) create mode 100644 Parse-Dashboard/openAIModelParams.js create mode 100644 src/lib/tests/openAIModelParams.test.js diff --git a/Parse-Dashboard/app.js b/Parse-Dashboard/app.js index 8c2845d8ac..49f7927761 100644 --- a/Parse-Dashboard/app.js +++ b/Parse-Dashboard/app.js @@ -5,6 +5,7 @@ const path = require('path'); const Authentication = require('./Authentication.js'); const fs = require('fs'); const ConfigKeyCache = require('./configKeyCache.js'); +const { buildModelParams } = require('./openAIModelParams.js'); const currentVersionFeatures = require('../package.json').parseDashboardFeatures; const Parse = require('parse/node'); @@ -984,8 +985,9 @@ You have direct access to the Parse database through function calls, so you can const requestBody = { model: model, messages: messages, - temperature: 0.7, - max_tokens: 2000, + // Token-limit / temperature params depend on the model generation so + // both legacy (GPT-4.1 and older) and next-gen (GPT-5+/o-series) work. + ...buildModelParams(model), tools: databaseTools, tool_choice: 'auto', stream: false @@ -1074,8 +1076,7 @@ You have direct access to the Parse database through function calls, so you can const followUpRequestBody = { model: model, messages: followUpMessages, - temperature: 0.7, - max_tokens: 2000, + ...buildModelParams(model), tools: databaseTools, tool_choice: 'auto', stream: false diff --git a/Parse-Dashboard/openAIModelParams.js b/Parse-Dashboard/openAIModelParams.js new file mode 100644 index 0000000000..c2d855af6e --- /dev/null +++ b/Parse-Dashboard/openAIModelParams.js @@ -0,0 +1,74 @@ +'use strict'; + +/** + * Helpers for building OpenAI Chat Completions request parameters that work + * across both legacy and next-generation models. + * + * OpenAI changed the Chat Completions API contract starting with the GPT-5 + * family and the "o" reasoning series (o1, o3, ...): + * - The token-limit parameter was renamed from `max_tokens` to + * `max_completion_tokens`. Sending `max_tokens` to these models fails with + * "Unsupported parameter: 'max_tokens' is not supported with this model. + * Use 'max_completion_tokens' instead." + * - Only the default `temperature` (1) is accepted. Sending a custom value + * fails with "Unsupported value: 'temperature' does not support ...". + * + * Legacy models (GPT-4.1 and older, GPT-4o, GPT-4-turbo, GPT-3.5, ...) keep the + * original contract. These helpers detect the model generation and emit the + * correct parameters so both old and new models work without the caller needing + * to know the difference. + */ + +const MAX_OUTPUT_TOKENS = 2000; +const DEFAULT_TEMPERATURE = 0.7; + +/** + * Determine whether a model uses the next-generation Chat Completions contract + * (renamed token parameter, fixed default temperature). + * + * @param {string} model The configured model name, e.g. "gpt-4.1", "gpt-5.5". + * @returns {boolean} True for GPT-5 and later plus the "o" reasoning series. + */ +function usesNextGenContract(model) { + const normalized = typeof model === 'string' ? model.trim().toLowerCase() : ''; + + // "o" reasoning series: o1, o1-mini, o3, o3-mini, o4-mini, ... + if (/^o\d/.test(normalized)) { + return true; + } + + // "gpt-[.]..." — treat major version 5 and above as next-gen. + const match = normalized.match(/^gpt-(\d+)/); + if (match) { + return parseInt(match[1], 10) >= 5; + } + + // Unknown/unmatched names default to the legacy contract to preserve + // backwards compatibility. + return false; +} + +/** + * Build the request-body parameters that differ between model generations: the + * token-limit field and (for legacy models only) the temperature. + * + * @param {string} model The configured model name. + * @returns {object} A partial request body with the correct token/temperature keys. + */ +function buildModelParams(model) { + if (usesNextGenContract(model)) { + // Next-gen models: renamed token field, and only the default temperature is + // allowed — so we omit `temperature` entirely. + return { max_completion_tokens: MAX_OUTPUT_TOKENS }; + } + + // Legacy models: original parameters. + return { max_tokens: MAX_OUTPUT_TOKENS, temperature: DEFAULT_TEMPERATURE }; +} + +module.exports = { + usesNextGenContract, + buildModelParams, + MAX_OUTPUT_TOKENS, + DEFAULT_TEMPERATURE, +}; diff --git a/README.md b/README.md index 7821464eea..e351958474 100644 --- a/README.md +++ b/README.md @@ -1799,11 +1799,13 @@ To configure the AI agent for your dashboard, you need to add the `agent` config | `agent.models` | Array | Yes | Array of AI model configurations available to the agent. | | `agent.models[*].name` | String | Yes | The display name for the model (e.g., `ChatGPT 4.1`). | | `agent.models[*].provider` | String | Yes | The AI provider identifier (e.g., "openai"). | -| `agent.models[*].model` | String | Yes | The specific model name from the provider (e.g., `gpt-4.1`). | +| `agent.models[*].model` | String | Yes | The specific model name from the provider (e.g., `gpt-4.1`, `gpt-5.5`). | | `agent.models[*].apiKey` | String | Yes | The API key for authenticating with the AI provider. | The agent will use the configured models to process natural language commands and perform database operations using the master key from your app configuration. +Both legacy OpenAI models (GPT-4.1 and older, e.g. `gpt-4.1`, `gpt-4o`, `gpt-3.5-turbo`) and newer models (the GPT-5 family and the `o`-reasoning series, e.g. `gpt-5.4`, `gpt-5.5`, `o1`, `o3`) are supported. The dashboard automatically adapts the request parameters to each model's API contract — newer models require `max_completion_tokens` instead of `max_tokens` and only accept the default `temperature` — so no extra configuration is needed when switching between model generations. + ### Providers > [!Note] diff --git a/src/lib/tests/openAIModelParams.test.js b/src/lib/tests/openAIModelParams.test.js new file mode 100644 index 0000000000..6574dadf61 --- /dev/null +++ b/src/lib/tests/openAIModelParams.test.js @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2016-present, Parse, LLC + * All rights reserved. + * + * This source code is licensed under the license found in the LICENSE file in + * the root directory of this source tree. + */ +jest.dontMock('../../../Parse-Dashboard/openAIModelParams.js'); + +const { + usesNextGenContract, + buildModelParams, + MAX_OUTPUT_TOKENS, + DEFAULT_TEMPERATURE, +} = require('../../../Parse-Dashboard/openAIModelParams.js'); + +describe('openAIModelParams.usesNextGenContract', () => { + it('treats GPT-4.1 and older as legacy', () => { + expect(usesNextGenContract('gpt-4.1')).toBe(false); + expect(usesNextGenContract('gpt-4')).toBe(false); + expect(usesNextGenContract('gpt-4o')).toBe(false); + expect(usesNextGenContract('gpt-4-turbo')).toBe(false); + expect(usesNextGenContract('gpt-4.5-preview')).toBe(false); + expect(usesNextGenContract('gpt-3.5-turbo')).toBe(false); + }); + + it('treats GPT-5 and newer as next-gen', () => { + expect(usesNextGenContract('gpt-5')).toBe(true); + expect(usesNextGenContract('gpt-5.4')).toBe(true); + expect(usesNextGenContract('gpt-5.5')).toBe(true); + expect(usesNextGenContract('gpt-6')).toBe(true); + expect(usesNextGenContract('gpt-10')).toBe(true); + }); + + it('treats the "o" reasoning series as next-gen', () => { + expect(usesNextGenContract('o1')).toBe(true); + expect(usesNextGenContract('o1-mini')).toBe(true); + expect(usesNextGenContract('o3')).toBe(true); + expect(usesNextGenContract('o4-mini')).toBe(true); + }); + + it('is case- and whitespace-insensitive', () => { + expect(usesNextGenContract(' GPT-5.5 ')).toBe(true); + expect(usesNextGenContract('GPT-4.1')).toBe(false); + }); + + it('defaults unknown/invalid names to legacy for backwards compatibility', () => { + expect(usesNextGenContract('')).toBe(false); + expect(usesNextGenContract(undefined)).toBe(false); + expect(usesNextGenContract(null)).toBe(false); + expect(usesNextGenContract('some-custom-model')).toBe(false); + }); +}); + +describe('openAIModelParams.buildModelParams', () => { + it('uses max_tokens and temperature for legacy models', () => { + expect(buildModelParams('gpt-4.1')).toEqual({ + max_tokens: MAX_OUTPUT_TOKENS, + temperature: DEFAULT_TEMPERATURE, + }); + }); + + it('uses max_completion_tokens and omits temperature for next-gen models', () => { + const params = buildModelParams('gpt-5.5'); + expect(params).toEqual({ max_completion_tokens: MAX_OUTPUT_TOKENS }); + expect(params).not.toHaveProperty('max_tokens'); + expect(params).not.toHaveProperty('temperature'); + }); +});