diff --git a/fern/versions/latest/pages/concepts/models/default-model-settings.mdx b/fern/versions/latest/pages/concepts/models/default-model-settings.mdx
index b13c61947..a7387ce8a 100644
--- a/fern/versions/latest/pages/concepts/models/default-model-settings.mdx
+++ b/fern/versions/latest/pages/concepts/models/default-model-settings.mdx
@@ -58,9 +58,9 @@ The following model configurations are automatically available when `OPENAI_API_
| Alias | Model | Use Case | Inference Parameters |
|-------|-------|----------|---------------------|
-| `openai-text` | `gpt-4.1` | General text generation | `temperature=0.85, top_p=0.95` |
-| `openai-reasoning` | `gpt-5` | Reasoning and analysis tasks | `extra_body={"reasoning_effort": "medium"}` |
-| `openai-vision` | `gpt-5` | Vision and image understanding | `extra_body={"reasoning_effort": "medium"}` |
+| `openai-text` | `gpt-5.6-luna` | General text generation | `temperature=0.85, top_p=0.95, extra_body={"reasoning_effort": "none"}` |
+| `openai-reasoning` | `gpt-5.6-luna` | Reasoning and analysis tasks | `extra_body={"reasoning_effort": "medium"}` |
+| `openai-vision` | `gpt-5.6-luna` | Vision and image understanding | `extra_body={"reasoning_effort": "medium"}` |
| `openai-embedding` | `text-embedding-3-large` | Text embeddings | `encoding_format="float"` |
### OpenRouter Models
@@ -93,9 +93,23 @@ When the Data Designer library or the CLI is initialized, default model configur
- **Model Providers**: `~/.data-designer/model_providers.yaml`
- Upgrades do not overwrite existing configuration files. If `nvidia-text` or `nvidia-reasoning` still references a retired model, update `~/.data-designer/model_configs.yaml` manually or run `data-designer config reset` to restore the current defaults. Resetting deletes all custom configurations.
+ Upgrades do not overwrite existing configuration files. If `nvidia-text`, `nvidia-reasoning`, or an `openai-*` alias still references a retired model (for example `gpt-5` or `gpt-4.1`), update `~/.data-designer/model_configs.yaml` manually or run `data-designer config reset` to restore the current defaults. Resetting deletes all custom configurations.
+If you only change the `model` of an existing alias, the saved inference parameters stay as they were. `gpt-5.6-luna` also needs `reasoning_effort` set explicitly for the text alias (omitting it returned HTTP 400), so update the parameters together with the model. For example, an `openai-text` alias that used to point at `gpt-4.1`:
+
+```yaml
+model_configs:
+ - alias: openai-text
+ model: gpt-5.6-luna
+ provider: openai
+ inference_parameters:
+ temperature: 0.85
+ top_p: 0.95
+ extra_body:
+ reasoning_effort: none
+```
+
While these files provide a convenient way to specify settings for your model providers and configuration you use most often, they can always be set programmatically in your SDG workflow.
diff --git a/packages/data-designer-config/src/data_designer/config/utils/constants.py b/packages/data-designer-config/src/data_designer/config/utils/constants.py
index 30452e760..56e4c9f2d 100644
--- a/packages/data-designer-config/src/data_designer/config/utils/constants.py
+++ b/packages/data-designer-config/src/data_designer/config/utils/constants.py
@@ -349,6 +349,7 @@ class NordColor(Enum):
}
NEMOTRON_3_NANO_OMNI_30B_A3B_REASONING_INFERENCE_PARAMS = {"temperature": 0.60, "top_p": 0.95}
GPT5_INFERENCE_PARAMS = {"extra_body": {"reasoning_effort": "medium"}}
+GPT5_TEXT_INFERENCE_PARAMS = DEFAULT_TEXT_INFERENCE_PARAMS | {"extra_body": {"reasoning_effort": "none"}}
PREDEFINED_PROVIDERS_MODEL_MAP = {
NVIDIA_PROVIDER_NAME: {
@@ -370,9 +371,9 @@ class NordColor(Enum):
},
},
OPENAI_PROVIDER_NAME: {
- "text": {"model": "gpt-4.1", "inference_parameters": DEFAULT_TEXT_INFERENCE_PARAMS},
- "reasoning": {"model": "gpt-5", "inference_parameters": GPT5_INFERENCE_PARAMS},
- "vision": {"model": "gpt-5", "inference_parameters": GPT5_INFERENCE_PARAMS},
+ "text": {"model": "gpt-5.6-luna", "inference_parameters": GPT5_TEXT_INFERENCE_PARAMS},
+ "reasoning": {"model": "gpt-5.6-luna", "inference_parameters": GPT5_INFERENCE_PARAMS},
+ "vision": {"model": "gpt-5.6-luna", "inference_parameters": GPT5_INFERENCE_PARAMS},
"embedding": {"model": "text-embedding-3-large", "inference_parameters": DEFAULT_EMBEDDING_INFERENCE_PARAMS},
},
OPENROUTER_PROVIDER_NAME: {
diff --git a/packages/data-designer-config/tests/config/test_default_model_settings.py b/packages/data-designer-config/tests/config/test_default_model_settings.py
index accd4340f..91b57bb6f 100644
--- a/packages/data-designer-config/tests/config/test_default_model_settings.py
+++ b/packages/data-designer-config/tests/config/test_default_model_settings.py
@@ -78,13 +78,18 @@ def test_get_builtin_model_configs():
assert builtin_model_configs[3].model == "nvidia/nemotron-3-embed-1b"
assert builtin_model_configs[3].provider == "nvidia"
assert builtin_model_configs[4].alias == "openai-text"
- assert builtin_model_configs[4].model == "gpt-4.1"
+ assert builtin_model_configs[4].model == "gpt-5.6-luna"
assert builtin_model_configs[4].provider == "openai"
+ assert builtin_model_configs[4].inference_parameters == ChatCompletionInferenceParams(
+ temperature=0.85,
+ top_p=0.95,
+ extra_body={"reasoning_effort": "none"},
+ )
assert builtin_model_configs[5].alias == "openai-reasoning"
- assert builtin_model_configs[5].model == "gpt-5"
+ assert builtin_model_configs[5].model == "gpt-5.6-luna"
assert builtin_model_configs[5].provider == "openai"
assert builtin_model_configs[6].alias == "openai-vision"
- assert builtin_model_configs[6].model == "gpt-5"
+ assert builtin_model_configs[6].model == "gpt-5.6-luna"
assert builtin_model_configs[6].provider == "openai"
assert builtin_model_configs[7].alias == "openai-embedding"
assert builtin_model_configs[7].model == "text-embedding-3-large"
diff --git a/packages/data-designer-engine/src/data_designer/engine/models/clients/adapters/openai_compatible.py b/packages/data-designer-engine/src/data_designer/engine/models/clients/adapters/openai_compatible.py
index e9f9228a1..3098b3137 100644
--- a/packages/data-designer-engine/src/data_designer/engine/models/clients/adapters/openai_compatible.py
+++ b/packages/data-designer-engine/src/data_designer/engine/models/clients/adapters/openai_compatible.py
@@ -4,6 +4,7 @@
from __future__ import annotations
from typing import Any
+from urllib.parse import urlparse
from data_designer.config.utils.media_helpers import audio_format_from_mime_type
from data_designer.engine.models.clients.adapters.http_model_client import (
@@ -30,6 +31,8 @@
TransportKwargs,
)
+_OPENAI_HOSTNAME = "api.openai.com"
+
class OpenAICompatibleClient(HttpModelClient):
"""Native HTTP adapter for OpenAI-compatible provider APIs.
@@ -57,6 +60,21 @@ def supports_embeddings(self) -> bool:
def supports_image_generation(self) -> bool:
return True
+ def _adapt_chat_body(self, body: dict[str, Any]) -> dict[str, Any]:
+ """Rename ``max_tokens`` to ``max_completion_tokens`` for the hosted OpenAI API.
+
+ Newer OpenAI models (for example ``gpt-5.6-luna``) reject ``max_tokens`` on Chat
+ Completions. Other OpenAI-compatible servers (vLLM, NVIDIA, ...) still expect it,
+ so the rename only applies to ``api.openai.com``. An explicit ``max_completion_tokens``
+ (for example from ``extra_body``) wins.
+ """
+ if "max_tokens" not in body or urlparse(self._endpoint).hostname != _OPENAI_HOSTNAME:
+ return body
+ adapted = dict(body)
+ max_tokens = adapted.pop("max_tokens")
+ adapted.setdefault("max_completion_tokens", max_tokens)
+ return adapted
+
# -------------------------------------------------------------------
# Chat completion
# -------------------------------------------------------------------
@@ -68,7 +86,7 @@ def completion(self, request: ChatCompletionRequest) -> ChatCompletionResponse:
provider_name=self.provider_name,
model_name=request.model,
)
- payload = {"model": request.model, "messages": messages, **transport.body}
+ payload = {"model": request.model, "messages": messages, **self._adapt_chat_body(transport.body)}
response_json = self._post_sync(self._ROUTE_CHAT, payload, transport.headers, request.model, transport.timeout)
return parse_chat_completion_response(response_json)
@@ -79,7 +97,7 @@ async def acompletion(self, request: ChatCompletionRequest) -> ChatCompletionRes
provider_name=self.provider_name,
model_name=request.model,
)
- payload = {"model": request.model, "messages": messages, **transport.body}
+ payload = {"model": request.model, "messages": messages, **self._adapt_chat_body(transport.body)}
response_json = await self._apost(
self._ROUTE_CHAT, payload, transport.headers, request.model, transport.timeout
)
diff --git a/packages/data-designer-engine/tests/engine/models/clients/test_openai_compatible.py b/packages/data-designer-engine/tests/engine/models/clients/test_openai_compatible.py
index 3f9e2ec8c..1ab256759 100644
--- a/packages/data-designer-engine/tests/engine/models/clients/test_openai_compatible.py
+++ b/packages/data-designer-engine/tests/engine/models/clients/test_openai_compatible.py
@@ -32,11 +32,12 @@ def _make_client(
sync_client: MagicMock | None = None,
async_client: MagicMock | None = None,
api_key: str | None = "sk-test-key",
+ endpoint: str = ENDPOINT,
) -> OpenAICompatibleClient:
concurrency_mode = ClientConcurrencyMode.ASYNC if async_client is not None else ClientConcurrencyMode.SYNC
return OpenAICompatibleClient(
provider_name=PROVIDER,
- endpoint=ENDPOINT,
+ endpoint=endpoint,
api_key=api_key,
concurrency_mode=concurrency_mode,
sync_client=sync_client,
@@ -156,6 +157,60 @@ def test_completion_posts_to_chat_completions_route() -> None:
assert call_args.kwargs["headers"]["X-Trace"] == "1"
+def test_completion_translates_max_tokens_for_openai_api() -> None:
+ sync_mock = make_mock_sync_client(_chat_response())
+ client = _make_client(sync_client=sync_mock, endpoint="https://api.openai.com/v1")
+
+ client.completion(ChatCompletionRequest(model=MODEL, messages=[{"role": "user", "content": "Hi"}], max_tokens=256))
+
+ payload = sync_mock.post.call_args.kwargs["json"]
+ assert payload["max_completion_tokens"] == 256
+ assert "max_tokens" not in payload
+
+
+def test_completion_keeps_explicit_max_completion_tokens_for_openai_api() -> None:
+ sync_mock = make_mock_sync_client(_chat_response())
+ client = _make_client(sync_client=sync_mock, endpoint="https://api.openai.com/v1")
+
+ client.completion(
+ ChatCompletionRequest(
+ model=MODEL,
+ messages=[{"role": "user", "content": "Hi"}],
+ max_tokens=256,
+ extra_body={"max_completion_tokens": 512},
+ )
+ )
+
+ payload = sync_mock.post.call_args.kwargs["json"]
+ assert payload["max_completion_tokens"] == 512
+ assert "max_tokens" not in payload
+
+
+def test_completion_keeps_max_tokens_for_other_openai_compatible_endpoints() -> None:
+ sync_mock = make_mock_sync_client(_chat_response())
+ client = _make_client(sync_client=sync_mock)
+
+ client.completion(ChatCompletionRequest(model=MODEL, messages=[{"role": "user", "content": "Hi"}], max_tokens=256))
+
+ payload = sync_mock.post.call_args.kwargs["json"]
+ assert payload["max_tokens"] == 256
+ assert "max_completion_tokens" not in payload
+
+
+@pytest.mark.asyncio
+async def test_acompletion_translates_max_tokens_for_openai_api() -> None:
+ async_mock = make_mock_async_client(_chat_response())
+ client = _make_client(async_client=async_mock, endpoint="https://api.openai.com/v1")
+
+ await client.acompletion(
+ ChatCompletionRequest(model=MODEL, messages=[{"role": "user", "content": "Hi"}], max_tokens=256)
+ )
+
+ payload = async_mock.post.call_args.kwargs["json"]
+ assert payload["max_completion_tokens"] == 256
+ assert "max_tokens" not in payload
+
+
@pytest.mark.parametrize(
"tool_choice",
[