Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .changeset/brave-impalas-pull.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
'@ai-sdk/openai-compatible': patch
---

Refine OpenAI-compatible chat option handling by separating stable options from legacy normalized compatibility options.

- Keep `user` as the stable normalized chat option.
- Continue supporting `reasoningEffort`, `textVerbosity`, and `strictJsonSchema` for backwards compatibility.
- Emit deprecation warnings when legacy normalized options are used and recommend provider-native fields.
- Update provider docs to clarify the normalized compatibility layer and preferred provider-native configuration.
26 changes: 11 additions & 15 deletions content/providers/02-openai-compatible-providers/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -423,27 +423,13 @@ const { text } = await generateText({

## Chat Model Options

The following provider options are available for chat models via `providerOptions`:
The following provider option is available for chat models via `providerOptions`:

- **user** _string_

A unique identifier representing your end-user, which can help the provider to
monitor and detect abuse.

- **reasoningEffort** _string_

Reasoning effort for reasoning models. The exact values depend on the provider.

- **textVerbosity** _string_

Controls the verbosity of the generated text. The exact values depend on the provider.

- **strictJsonSchema** _boolean_

Whether to use strict JSON schema validation. When true, the model uses constrained
decoding to guarantee schema compliance. Only used when the provider supports
structured outputs and a schema is provided. Defaults to `true`.

```ts
const { text } = await generateText({
model: provider('model-id'),
Expand All @@ -457,6 +443,16 @@ const { text } = await generateText({
});
```

### Legacy normalized options (deprecated)

The following normalized chat options are still supported for compatibility, but are deprecated:

- `reasoningEffort`
- `textVerbosity`
- `strictJsonSchema`

Prefer provider-native fields directly in `providerOptions.providerName` for long-term compatibility.

## Provider-specific options

The OpenAI Compatible provider supports adding provider-specific options to the request body. These are specified with the `providerOptions` field in the request body.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -469,6 +469,38 @@ describe('doGenerate', () => {
});
});

it('should keep normalized chat options for compatibility and emit deprecation warning', async () => {
prepareJsonResponse();

const result = await provider('grok-beta').doGenerate({
prompt: TEST_PROMPT,
providerOptions: {
'test-provider': {
reasoningEffort: 'high',
},
},
});

expect(await server.calls[0].requestBodyJson).toMatchInlineSnapshot(`
{
"messages": [
{
"content": "Hello",
"role": "user",
},
],
"model": "grok-beta",
"reasoning_effort": "high",
}
`);

expect(result.warnings).toContainEqual({
type: 'other',
message:
"The normalized OpenAI-compatible chat options ('reasoningEffort', 'textVerbosity', 'strictJsonSchema') are deprecated. Use provider-native fields in providerOptions.test-provider instead.",
});
});

it('should include provider-specific options', async () => {
prepareJsonResponse();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,17 @@ import { getResponseMetadata } from './get-response-metadata';
import { mapOpenAICompatibleFinishReason } from './map-openai-compatible-finish-reason';
import {
OpenAICompatibleChatModelId,
openaiCompatibleLanguageModelChatLegacyOptions,
openaiCompatibleLanguageModelChatOptions,
} from './openai-compatible-chat-options';
import { MetadataExtractor } from './openai-compatible-metadata-extractor';
import { prepareTools } from './openai-compatible-prepare-tools';

const compatibilityOptionKeys = new Set([
...Object.keys(openaiCompatibleLanguageModelChatOptions.shape),
...Object.keys(openaiCompatibleLanguageModelChatLegacyOptions.shape),
]);

export type OpenAICompatibleChatConfig = {
provider: string;
headers: () => Record<string, string | undefined>;
Expand Down Expand Up @@ -129,34 +135,70 @@ export class OpenAICompatibleChatLanguageModel implements LanguageModelV3 {
const warnings: SharedV3Warning[] = [];

// Parse provider options - check for deprecated 'openai-compatible' key
const deprecatedOptions = await parseProviderOptions({
const deprecatedBaseOptions = await parseProviderOptions({
provider: 'openai-compatible',
providerOptions,
schema: openaiCompatibleLanguageModelChatOptions,
});
const deprecatedLegacyOptions = await parseProviderOptions({
provider: 'openai-compatible',
providerOptions,
schema: openaiCompatibleLanguageModelChatLegacyOptions,
});

if (deprecatedOptions != null) {
if (deprecatedBaseOptions != null || deprecatedLegacyOptions != null) {
warnings.push({
type: 'other',
message: `The 'openai-compatible' key in providerOptions is deprecated. Use 'openaiCompatible' instead.`,
});
}

const openaiCompatibleBaseOptions = await parseProviderOptions({
provider: 'openaiCompatible',
providerOptions,
schema: openaiCompatibleLanguageModelChatOptions,
});

const openaiCompatibleLegacyOptions = await parseProviderOptions({
provider: 'openaiCompatible',
providerOptions,
schema: openaiCompatibleLanguageModelChatLegacyOptions,
});

const providerBaseOptions = await parseProviderOptions({
provider: this.providerOptionsName,
providerOptions,
schema: openaiCompatibleLanguageModelChatOptions,
});

const providerLegacyOptions = await parseProviderOptions({
provider: this.providerOptionsName,
providerOptions,
schema: openaiCompatibleLanguageModelChatLegacyOptions,
});

const compatibleOptions = Object.assign(
deprecatedOptions ?? {},
(await parseProviderOptions({
provider: 'openaiCompatible',
providerOptions,
schema: openaiCompatibleLanguageModelChatOptions,
})) ?? {},
(await parseProviderOptions({
provider: this.providerOptionsName,
providerOptions,
schema: openaiCompatibleLanguageModelChatOptions,
})) ?? {},
deprecatedBaseOptions ?? {},
openaiCompatibleBaseOptions ?? {},
providerBaseOptions ?? {},
);
const legacyCompatibilityOptions = Object.assign(
deprecatedLegacyOptions ?? {},
openaiCompatibleLegacyOptions ?? {},
providerLegacyOptions ?? {},
);

if (Object.keys(legacyCompatibilityOptions).length > 0) {
warnings.push({
type: 'other',
message:
`The normalized OpenAI-compatible chat options ('reasoningEffort', 'textVerbosity', 'strictJsonSchema') are deprecated. ` +
`Use provider-native fields in providerOptions.${this.providerOptionsName} instead.`,
});
}

const strictJsonSchema = compatibleOptions?.strictJsonSchema ?? true;
const strictJsonSchema =
legacyCompatibilityOptions.strictJsonSchema ?? true;

if (topK != null) {
warnings.push({ type: 'unsupported', feature: 'topK' });
Expand Down Expand Up @@ -219,16 +261,11 @@ export class OpenAICompatibleChatLanguageModel implements LanguageModelV3 {
...Object.fromEntries(
Object.entries(
providerOptions?.[this.providerOptionsName] ?? {},
).filter(
([key]) =>
!Object.keys(
openaiCompatibleLanguageModelChatOptions.shape,
).includes(key),
),
).filter(([key]) => !compatibilityOptionKeys.has(key)),
),

reasoning_effort: compatibleOptions.reasoningEffort,
verbosity: compatibleOptions.textVerbosity,
reasoning_effort: legacyCompatibilityOptions.reasoningEffort,
verbosity: legacyCompatibilityOptions.textVerbosity,

// messages:
messages: convertToOpenAICompatibleChatMessages(prompt),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,27 +8,34 @@ export const openaiCompatibleLanguageModelChatOptions = z.object({
* monitor and detect abuse.
*/
user: z.string().optional(),
});

/**
* Legacy normalized chat options kept for backwards compatibility.
*
* Prefer provider-native option names passed through `providerOptions[providerName]`.
*/
export const openaiCompatibleLanguageModelChatLegacyOptions = z.object({
/**
* Reasoning effort for reasoning models. Defaults to `medium`.
* @deprecated Use provider-native request fields for reasoning configuration.
*/
reasoningEffort: z.string().optional(),

/**
* Controls the verbosity of the generated text. Defaults to `medium`.
* @deprecated Use provider-native request fields for verbosity configuration.
*/
textVerbosity: z.string().optional(),

/**
* Whether to use strict JSON schema validation.
* When true, the model uses constrained decoding to guarantee schema compliance.
* Only used when the provider supports structured outputs and a schema is provided.
*
* @default true
* @deprecated Use provider-native request fields for schema strictness configuration.
*/
strictJsonSchema: z.boolean().optional(),
});

export type OpenAICompatibleLanguageModelChatOptions = z.infer<
typeof openaiCompatibleLanguageModelChatOptions
>;

export type OpenAICompatibleLanguageModelChatLegacyOptions = z.infer<
typeof openaiCompatibleLanguageModelChatLegacyOptions
>;
Loading