diff --git a/ag2/config/__init__.py b/ag2/config/__init__.py index 62e8b862ad44..9688167fb441 100644 --- a/ag2/config/__init__.py +++ b/ag2/config/__init__.py @@ -12,11 +12,13 @@ ContainerInfo, ContainerManager, ExpiresAfter, + OpenAICompletionsConfig, OpenAIConfig, OpenAIDecisionsConfig, OpenAIResponsesConfig, ) except ImportError as e: + OpenAICompletionsConfig = missing_optional_dependency("OpenAICompletionsConfig", "openai", e) # type: ignore[misc] OpenAIConfig = missing_optional_dependency("OpenAIConfig", "openai", e) # type: ignore[misc] OpenAIResponsesConfig = missing_optional_dependency("OpenAIResponsesConfig", "openai", e) # type: ignore[misc] OpenAIDecisionsConfig = missing_optional_dependency("OpenAIDecisionsConfig", "openai", e) # type: ignore[misc] @@ -83,6 +85,7 @@ "ModelConfig", "ModelProvider", "OllamaConfig", + "OpenAICompletionsConfig", "OpenAIConfig", "OpenAIDecisionsConfig", "OpenAIResponsesConfig", diff --git a/ag2/config/input_acceptance.py b/ag2/config/input_acceptance.py index 89ee912dd994..46275254395e 100644 --- a/ag2/config/input_acceptance.py +++ b/ag2/config/input_acceptance.py @@ -16,6 +16,7 @@ MistralConfig, ModelConfig, OllamaConfig, + OpenAICompletionsConfig, OpenAIConfig, OpenAIResponsesConfig, TypeSafeConfig, @@ -29,7 +30,7 @@ # Each entry names (position, source, kind). Text is accepted everywhere. # The table describes mapper behavior, not model-specific ability. _MEDIA: dict[type[object], set[tuple[str, str, BinaryType | None]]] = { - OpenAIConfig: { + OpenAICompletionsConfig: { ("user", "data", BinaryType.IMAGE), ("user", "url", BinaryType.IMAGE), ("user", "data", BinaryType.AUDIO), @@ -110,6 +111,8 @@ def _described(config: ModelConfig | None) -> type[object] | None: return None # Compared by identity, never `isinstance`: a provider whose extra is not # installed is a stand-in here, not a class. + if type(config) is OpenAIConfig: + return OpenAICompletionsConfig return type(config) if type(config) in _MEDIA else None @@ -162,7 +165,7 @@ def accepts_input(config: ModelConfig | None, position: str, part: Input) -> boo }, } return kind is not None and part.media_type in supported_media.get(kind, set()) - if described is OpenAIConfig and kind is BinaryType.AUDIO: + if described is OpenAICompletionsConfig and kind is BinaryType.AUDIO: return part.media_type in ("audio/wav", "audio/mpeg", "audio/mp3") if described is AnthropicConfig and kind is BinaryType.IMAGE: return part.media_type in ("image/jpeg", "image/png", "image/gif", "image/webp") diff --git a/ag2/config/openai/__init__.py b/ag2/config/openai/__init__.py index ba32c8e4cc74..38d20a26ba6b 100644 --- a/ag2/config/openai/__init__.py +++ b/ag2/config/openai/__init__.py @@ -2,7 +2,7 @@ # # SPDX-License-Identifier: Apache-2.0 -from .config import OpenAIConfig, OpenAIDecisionsConfig, OpenAIResponsesConfig +from .config import OpenAICompletionsConfig, OpenAIConfig, OpenAIDecisionsConfig, OpenAIResponsesConfig from .containers import ContainerInfo, ContainerManager, ExpiresAfter from .decisions_mappers import DecisionRefusedError, UnsupportedResponseSchemaError from .openai_client import OpenAIClient @@ -15,6 +15,7 @@ "DecisionRefusedError", "ExpiresAfter", "OpenAIClient", + "OpenAICompletionsConfig", "OpenAIConfig", "OpenAIDecisionsClient", "OpenAIDecisionsConfig", diff --git a/ag2/config/openai/config.py b/ag2/config/openai/config.py index 45eb7a47dab4..8c7cc5528c12 100644 --- a/ag2/config/openai/config.py +++ b/ag2/config/openai/config.py @@ -17,7 +17,7 @@ from openai.types.responses import ServiceTier as ResponseServiceTier from openai.types.responses.response_create_params import PromptCacheOptions as ResponsePromptCacheOptions from openai.types.shared_params import DecisionQuestionInputPart, Reasoning -from typing_extensions import Unpack +from typing_extensions import Self, Unpack, deprecated from ag2.config.config import ModelConfig, ModelProvider from ag2.events import Input @@ -75,7 +75,9 @@ class OpenAIConfigOverrides(TypedDict, total=False): @dataclass(slots=True) -class OpenAIConfig(ModelConfig): +class OpenAICompletionsConfig(ModelConfig): + """Configure the OpenAI Chat Completions API, including compatible endpoints.""" + model: ChatModel | str api_key: str | None = None base_url: str | None = None @@ -127,7 +129,7 @@ class OpenAIConfig(ModelConfig): def provider(self) -> ModelProvider: return ModelProvider.OPENAI - def copy(self, /, **overrides: Unpack[OpenAIConfigOverrides]) -> "OpenAIConfig": + def copy(self, /, **overrides: Unpack[OpenAIConfigOverrides]) -> Self: return replace(self, **overrides) def create(self) -> OpenAIClient: @@ -183,6 +185,24 @@ def create_files_client(self) -> OpenAIFilesClient: return OpenAIFilesClient(self) +OPENAI_CONFIG_DEPRECATION = ( + "OpenAIConfig as a Chat Completions config is deprecated and will be removed in 1.4.0. " + "Use OpenAICompletionsConfig explicitly to keep Chat Completions, or OpenAIResponsesConfig for the Responses API. " + "In 1.4.0, OpenAIConfig will become an alias for OpenAIResponsesConfig." +) + + +@deprecated(OPENAI_CONFIG_DEPRECATION, category=None) +class OpenAIConfig(OpenAICompletionsConfig): + """Compatibility name for Chat Completions until the Responses alias switch in 1.4.0.""" + + __slots__ = () + + @deprecated(OPENAI_CONFIG_DEPRECATION) + def create(self) -> OpenAIClient: + return super().create() + + class OpenAIResponsesConfigOverrides(TypedDict, total=False): model: ChatModel | str api_key: str | None diff --git a/ag2/config/openai/files.py b/ag2/config/openai/files.py index 8838bf8677dc..07bee01b7024 100644 --- a/ag2/config/openai/files.py +++ b/ag2/config/openai/files.py @@ -11,7 +11,7 @@ from ag2.files.types import FileContent, FileProvider, UploadedFile, _created_at_to_float if TYPE_CHECKING: - from ag2.config.openai.config import OpenAIConfig, OpenAIResponsesConfig + from ag2.config.openai.config import OpenAICompletionsConfig, OpenAIResponsesConfig class OpenAIFilesClient: @@ -19,7 +19,7 @@ class OpenAIFilesClient: __slots__ = ("_client",) - def __init__(self, config: "OpenAIConfig | OpenAIResponsesConfig") -> None: + def __init__(self, config: "OpenAICompletionsConfig | OpenAIResponsesConfig") -> None: self._client = AsyncOpenAI( api_key=config.api_key, organization=config.organization, diff --git a/ag2/live/cascade.py b/ag2/live/cascade.py index e4d9df979977..92f16cfb4a4b 100644 --- a/ag2/live/cascade.py +++ b/ag2/live/cascade.py @@ -49,7 +49,7 @@ class CascadeConfig(RealtimeConfig): "assistant", config=CascadeConfig( stt=ElevenLabsTranscriber("scribe_v2"), - model=config.OpenAIConfig("gpt-5-mini", streaming=True), + model=config.OpenAIResponsesConfig("gpt-5-mini", streaming=True), tts=ElevenLabsStreamingTTSConfig("eleven_flash_v2_5"), ), ) diff --git a/ag2/tools/builtin/image_generation.py b/ag2/tools/builtin/image_generation.py index 2ced6e23651e..45d498c83599 100644 --- a/ag2/tools/builtin/image_generation.py +++ b/ag2/tools/builtin/image_generation.py @@ -36,7 +36,7 @@ class ImageGenerationTool(Tool): Instructs the model to generate images inline during the conversation. Only supported with ``OpenAIResponsesConfig`` — raises ``UnsupportedToolError`` - when used with ``OpenAIConfig`` (Chat Completions API). + when used with ``OpenAICompletionsConfig`` (Chat Completions API). Generated images are returned as ``list[BinaryResult]`` via ``reply.files``. diff --git a/docs/adr/0026-openai-config-transitions-to-responses.md b/docs/adr/0026-openai-config-transitions-to-responses.md new file mode 100644 index 000000000000..2a353f4d4d1c --- /dev/null +++ b/docs/adr/0026-openai-config-transitions-to-responses.md @@ -0,0 +1,32 @@ +--- +status: accepted +date: 2026-10-27 +--- + +# 0026. OpenAIConfig transitions to the Responses API in 1.4.0 + +## Context + +`OpenAIConfig` currently selects Chat Completions. Responses is the intended +OpenAI default, but immediately rebinding the name would change endpoints and +reject Chat Completions parameters in existing applications. + +## Decision + +Expose the existing implementation as `OpenAICompletionsConfig`. Until 1.4.0, +`OpenAIConfig` remains a compatibility subclass with the same constructor, +copy behavior and client. Mark the class deprecated for type checkers and emit +a `DeprecationWarning` from `create()`, following ADR 0025's rule that +construction and copying stay free of side effects. + +In 1.4.0, remove the deprecated Chat Completions binding and bind +`OpenAIConfig` to `OpenAIResponsesConfig`. Recommend the explicit classes +during the transition: `OpenAICompletionsConfig` preserves Chat Completions; +`OpenAIResponsesConfig` opts into Responses now. + +## Consequences + +Existing code keeps its endpoint during the deprecation period. The explicit +Chat Completions class remains supported after 1.4.0, including for compatible +endpoints. Applications migrating to Responses must translate API-specific +options; changing the name alone does not translate those options. diff --git a/test/config/openai/test_files.py b/test/config/openai/test_files.py index 518b0d9afdfa..312a44b13024 100644 --- a/test/config/openai/test_files.py +++ b/test/config/openai/test_files.py @@ -9,7 +9,7 @@ import pytest from dirty_equals import IsFloat -from ag2.config import OpenAIConfig +from ag2.config import OpenAICompletionsConfig from ag2.config.openai.files import OpenAIFilesClient from ag2.files import FilesAPI from ag2.files.types import FileContent, FileProvider, UploadedFile @@ -48,7 +48,7 @@ def handler(request: httpx2.Request) -> httpx2.Response: ) async with httpx2.AsyncClient(transport=httpx2.MockTransport(handler)) as http_client: - files = await FilesAPI(OpenAIConfig(model="test", api_key="test", http_client=http_client)).list() + files = await FilesAPI(OpenAICompletionsConfig(model="test", api_key="test", http_client=http_client)).list() assert files == [ UploadedFile( diff --git a/test/config/openai/test_http_client_family.py b/test/config/openai/test_http_client_family.py index 353cfe366647..6a751c28bbd8 100644 --- a/test/config/openai/test_http_client_family.py +++ b/test/config/openai/test_http_client_family.py @@ -18,7 +18,7 @@ from fast_depends.use import SerializerCls from ag2 import Context, MemoryStream -from ag2.config import OpenAIConfig, OpenAIResponsesConfig +from ag2.config import OpenAICompletionsConfig, OpenAIResponsesConfig from ag2.events import ModelRequest, TextInput _COMPLETION = { @@ -74,7 +74,7 @@ def handler(request: httpx.Request) -> httpx.Response: return httpx.AsyncClient(transport=httpx.MockTransport(handler)) -async def _ask(config: OpenAIConfig | OpenAIResponsesConfig) -> None: +async def _ask(config: OpenAICompletionsConfig | OpenAIResponsesConfig) -> None: client = config.create() await client( messages=[ModelRequest([TextInput("capital of France?")])], @@ -99,7 +99,9 @@ class TestHttpx2Client: async def test_completions(self) -> None: captured: dict[str, object] = {} - await _ask(OpenAIConfig(model="gpt-4o", api_key="test", http_client=_modern_client(captured, _COMPLETION))) + await _ask( + OpenAICompletionsConfig(model="gpt-4o", api_key="test", http_client=_modern_client(captured, _COMPLETION)) + ) assert captured["body"] == _COMPLETION_BODY @@ -120,7 +122,9 @@ class TestLegacyHttpxClient: async def test_completions(self) -> None: captured: dict[str, object] = {} - await _ask(OpenAIConfig(model="gpt-4o", api_key="test", http_client=_legacy_client(captured, _COMPLETION))) + await _ask( + OpenAICompletionsConfig(model="gpt-4o", api_key="test", http_client=_legacy_client(captured, _COMPLETION)) + ) assert captured["body"] == _COMPLETION_BODY diff --git a/test/config/openai/test_openai_config.py b/test/config/openai/test_openai_config.py index 7dddfa5714ca..36ebf3e65e8a 100644 --- a/test/config/openai/test_openai_config.py +++ b/test/config/openai/test_openai_config.py @@ -2,11 +2,11 @@ # # SPDX-License-Identifier: Apache-2.0 -from ag2.config import OpenAIConfig +from ag2.config import OpenAICompletionsConfig def test_copy_without_overrides_returns_new_equal_instance() -> None: - config = OpenAIConfig(model="gpt-5", temperature=0.2, streaming=True) + config = OpenAICompletionsConfig(model="gpt-5", temperature=0.2, streaming=True) copied = config.copy() @@ -15,7 +15,7 @@ def test_copy_without_overrides_returns_new_equal_instance() -> None: def test_copy_applies_overrides_without_mutating_original() -> None: - config = OpenAIConfig(model="gpt-5", api_key="key", temperature=0.2, streaming=False) + config = OpenAICompletionsConfig(model="gpt-5", api_key="key", temperature=0.2, streaming=False) copied = config.copy(model="gpt-5-mini", temperature=0.8, streaming=True, api_key=None) @@ -31,14 +31,16 @@ def test_copy_applies_overrides_without_mutating_original() -> None: def test_extra_body_defaults_to_none() -> None: - config = OpenAIConfig(model="gpt-5") + config = OpenAICompletionsConfig(model="gpt-5") assert config.extra_body is None def test_extra_body_passed_to_create_options() -> None: extra = {"chat_template_kwargs": {"enable_thinking": True}} - config = OpenAIConfig(model="test-model", base_url="http://localhost:8000/v1", api_key="EMPTY", extra_body=extra) + config = OpenAICompletionsConfig( + model="test-model", base_url="http://localhost:8000/v1", api_key="EMPTY", extra_body=extra + ) client = config.create() @@ -46,7 +48,7 @@ def test_extra_body_passed_to_create_options() -> None: def test_copy_with_extra_body() -> None: - config = OpenAIConfig(model="gpt-5") + config = OpenAICompletionsConfig(model="gpt-5") extra = {"reasoning_split": True} copied = config.copy(extra_body=extra) diff --git a/test/config/openai/test_openai_config_deprecation.py b/test/config/openai/test_openai_config_deprecation.py new file mode 100644 index 000000000000..0676bd7fd896 --- /dev/null +++ b/test/config/openai/test_openai_config_deprecation.py @@ -0,0 +1,105 @@ +# Copyright (c) 2026, AG2ai, Inc., AG2ai open-source projects maintainers and core contributors +# +# SPDX-License-Identifier: Apache-2.0 + +import json +import warnings +from pathlib import Path + +import httpx2 +import pytest +from dirty_equals import IsPartialDict + +from ag2 import Agent +from ag2.config import OpenAICompletionsConfig, OpenAIConfig, OpenAIResponsesConfig +from ag2.config.input_acceptance import accepts_input, input_modalities +from ag2.config.openai import OpenAICompletionsConfig as ProviderCompletionsConfig +from ag2.config.openai import OpenAIConfig as ProviderConfig +from ag2.events import BinaryInput, BinaryType + + +def _chat_response(request: httpx2.Request) -> httpx2.Response: + assert request.url.path == "/v1/chat/completions" + assert json.loads(request.content) == IsPartialDict({ + "model": "test-model", + "max_completion_tokens": 20, + "extra_option": True, + }) + return httpx2.Response( + 200, + json={ + "id": "chat-1", + "object": "chat.completion", + "created": 0, + "model": "test-model", + "choices": [{"index": 0, "message": {"role": "assistant", "content": "Hello"}, "finish_reason": "stop"}], + }, + ) + + +@pytest.mark.parametrize("config_class", [OpenAICompletionsConfig, ProviderCompletionsConfig, OpenAIResponsesConfig]) +def test_explicit_configs_do_not_warn( + config_class: type[OpenAICompletionsConfig] | type[OpenAIResponsesConfig], +) -> None: + with warnings.catch_warnings(record=True) as caught: + warnings.simplefilter("always", DeprecationWarning) + config = config_class("test-model", api_key="test") + copied = config.copy(model="other-model") + config.create() + copied.create() + + assert not caught + assert type(copied) is config_class + assert config.model == "test-model" + assert copied.model == "other-model" + + +@pytest.mark.parametrize("config_class", [OpenAIConfig, ProviderConfig]) +def test_legacy_construction_and_copy_do_not_warn(config_class: type[OpenAIConfig]) -> None: + with warnings.catch_warnings(record=True) as caught: + warnings.simplefilter("always", DeprecationWarning) + config = config_class("test-model", api_key="test", max_completion_tokens=20) + copied = config.copy(temperature=0.5) + + assert not caught + assert type(copied) is config_class + assert copied.max_completion_tokens == 20 + assert copied.temperature == 0.5 + assert config.max_completion_tokens == 20 + with pytest.warns(DeprecationWarning, match="OpenAICompletionsConfig.*OpenAIResponsesConfig.*1.4.0") as emitted: + copied.create() + assert len(emitted) == 1 + assert Path(emitted[0].filename) == Path(__file__) + + +@pytest.mark.asyncio +@pytest.mark.parametrize("config_class", [OpenAICompletionsConfig, OpenAIConfig, ProviderConfig]) +async def test_chat_endpoint_is_preserved(config_class: type[OpenAICompletionsConfig]) -> None: + async with httpx2.AsyncClient(transport=httpx2.MockTransport(_chat_response)) as http_client: + config = config_class( + "test-model", + api_key="test", + http_client=http_client, + max_completion_tokens=20, + extra_body={"extra_option": True}, + ) + agent = Agent("test", config=config) + with warnings.catch_warnings(record=True) as caught: + warnings.simplefilter("always", DeprecationWarning) + reply = await agent.ask("Hi") + + assert reply.body == "Hello" + assert len(caught) == (0 if config_class is OpenAICompletionsConfig else 1) + if caught: + assert issubclass(caught[0].category, DeprecationWarning) + assert "1.4.0" in str(caught[0].message) + + +@pytest.mark.parametrize("config_class", [OpenAICompletionsConfig, OpenAIConfig]) +def test_chat_input_acceptance_is_preserved(config_class: type[OpenAICompletionsConfig]) -> None: + config = config_class("test-model") + + assert input_modalities(config) == {"image": True, "audio": True, "video": False, "pdf": True} + assert accepts_input(config, "user", BinaryInput(b"audio", media_type="audio/wav", kind=BinaryType.AUDIO)) + assert not accepts_input(config, "user", BinaryInput(b"audio", media_type="audio/ogg", kind=BinaryType.AUDIO)) + assert not accepts_input(config, "tool", BinaryInput(b"image", media_type="image/png", kind=BinaryType.IMAGE)) diff --git a/test/config/openai/test_prompt_cache.py b/test/config/openai/test_prompt_cache.py index 0e559e3ce6de..116e3406614b 100644 --- a/test/config/openai/test_prompt_cache.py +++ b/test/config/openai/test_prompt_cache.py @@ -17,7 +17,7 @@ from fast_depends.use import SerializerCls from ag2 import Context, MemoryStream -from ag2.config import OpenAIConfig, OpenAIResponsesConfig +from ag2.config import OpenAICompletionsConfig, OpenAIResponsesConfig from ag2.events import ModelRequest, TextInput CHAT_COMPLETION: dict[str, Any] = { @@ -54,7 +54,7 @@ def handler(request: httpx2.Request) -> httpx2.Response: return httpx2.AsyncClient(transport=httpx2.MockTransport(handler)) -async def _ask(config: OpenAIConfig | OpenAIResponsesConfig) -> None: +async def _ask(config: OpenAICompletionsConfig | OpenAIResponsesConfig) -> None: await config.create()( messages=[ModelRequest([TextInput("capital of France?")])], context=Context(stream=MemoryStream()), @@ -67,7 +67,7 @@ async def _ask(config: OpenAIConfig | OpenAIResponsesConfig) -> None: async def _chat_body(**overrides: Any) -> dict[str, Any]: captured: dict[str, Any] = {} await _ask( - OpenAIConfig( + OpenAICompletionsConfig( model="m", api_key="test", base_url="http://test/v1", @@ -161,7 +161,7 @@ async def test_the_cache_and_the_end_user_are_named_separately(body_of: Surface) def test_chat_cache_settings_survive_a_copy() -> None: - config = OpenAIConfig(model="m").copy( + config = OpenAICompletionsConfig(model="m").copy( prompt_cache_key="checkout-agent", prompt_cache_options={"mode": "explicit"}, ) diff --git a/website/AGENTS.md b/website/AGENTS.md index 27c580871634..a86a6778a287 100644 --- a/website/AGENTS.md +++ b/website/AGENTS.md @@ -29,9 +29,9 @@ Example: ```python linenums="1" -from ag2.config import OpenAIConfig +from ag2.config import OpenAIResponsesConfig -config = OpenAIConfig(model="gpt-5") +config = OpenAIResponsesConfig(model="gpt-5") ``` diff --git a/website/docs/user-guide/model_configuration.mdx b/website/docs/user-guide/model_configuration.mdx index 98dd1078dacd..1198df787d06 100644 --- a/website/docs/user-guide/model_configuration.mdx +++ b/website/docs/user-guide/model_configuration.mdx @@ -14,7 +14,7 @@ AG2 supports multiple LLM providers through dedicated configuration classes. Eac | Provider | Configuration Class | Installation Command | | :--- | :--- | :--- | | **[OpenAI Responses](https://developers.openai.com/api/reference/responses/overview)** | `OpenAIResponsesConfig` | `pip install "ag2[openai]"` | -| **[OpenAI](https://developers.openai.com/api/reference/overview)** | `OpenAIConfig` | `pip install "ag2[openai]"` | +| **[OpenAI Chat Completions](https://developers.openai.com/api/reference/overview)** | `OpenAICompletionsConfig` | `pip install "ag2[openai]"` | | **[OpenAI Decisions](https://developers.openai.com/api/docs/guides/decisions)** | `OpenAIDecisionsConfig` | `pip install "ag2[openai]"` | | **[Anthropic](https://platform.claude.com/docs/en/build-with-claude/overview)** | `AnthropicConfig` | `pip install "ag2[anthropic]"` | | **[Gemini](https://ai.google.dev/gemini-api/docs)** | `GeminiConfig` | `pip install "ag2[gemini]"` | @@ -27,7 +27,17 @@ AG2 supports multiple LLM providers through dedicated configuration classes. Eac | **[Mistral](https://docs.mistral.ai)** | `MistralConfig` | `pip install "ag2[mistral]"` | | **[TypeSafe (Jev)](https://docs.typesafe.ai)** | `TypeSafeConfig` | `pip install "ag2[typesafe]"` | -*(Note: `OpenAIConfig` is also available for OpenAI-compatible endpoints).* +Use `OpenAIResponsesConfig` for new OpenAI integrations. Use `OpenAICompletionsConfig` for the Chat Completions API and compatible endpoints. + +!!! warning "OpenAIConfig changes in 1.4.0" + + `OpenAIConfig` still uses Chat Completions, but this compatibility name is deprecated. + Its `create()` method emits a `DeprecationWarning`; constructing or copying the config does not. + Use `OpenAICompletionsConfig` explicitly to preserve Chat Completions behavior, or migrate to + `OpenAIResponsesConfig` for the Responses API. In **1.4.0**, the deprecated Chat Completions + binding will be removed and `OpenAIConfig` will become an alias for `OpenAIResponsesConfig`. + Chat Completions parameters such as `max_completion_tokens` and `reasoning_effort` must be + translated to Responses parameters such as `max_output_tokens` and `reasoning` when migrating. --- @@ -51,12 +61,12 @@ config = OpenAIResponsesConfig( ``` - + ```python linenums="1" -from ag2.config import OpenAIConfig +from ag2.config import OpenAICompletionsConfig # Configure an OpenAI model -config = OpenAIConfig( +config = OpenAICompletionsConfig( model="gpt-4o-mini", api_key="sk-...", temperature=0.2, @@ -212,17 +222,17 @@ For security and convenience, you don't need to hardcode your API keys. If `api_ The system looks for provider-specific keys (e.g., `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, `GEMINI_API_KEY`, `XAI_API_KEY`, `ZAI_API_KEY`, `TYPESAFE_API_KEY`). ```python linenums="1" -from ag2.config import OpenAIConfig +from ag2.config import OpenAIResponsesConfig # Automatically falls back to OPENAI_API_KEY from the environment -config = OpenAIConfig(model="gpt-5") +config = OpenAIResponsesConfig(model="gpt-5") ``` ### Controlling OpenAI Reasoning OpenAI reasoning models spend extra tokens thinking before they answer. Both OpenAI configs let you bound that effort, and the Responses config can also request a summary of the thinking. See the [OpenAI reasoning guide](https://developers.openai.com/api/docs/guides/reasoning){.external-link target="_blank"} for which values each model accepts. -`OpenAIConfig` (Chat Completions) takes `reasoning_effort`. `OpenAIResponsesConfig` takes the Responses API `reasoning` object: +`OpenAICompletionsConfig` (Chat Completions) takes `reasoning_effort`. `OpenAIResponsesConfig` takes the Responses API `reasoning` object: @@ -237,12 +247,12 @@ config = OpenAIResponsesConfig( ``` - + ```python linenums="1" -from ag2.config import OpenAIConfig +from ag2.config import OpenAICompletionsConfig # Auth resolved from OPENAI_API_KEY when omitted -config = OpenAIConfig(model="gpt-5-mini", reasoning_effort="low") +config = OpenAICompletionsConfig(model="gpt-5-mini", reasoning_effort="low") ``` @@ -521,7 +531,7 @@ config = OllamaConfig( !!! warning "`httpx`, not `httpx2`" - Unlike `OpenAIConfig` and `AnthropicConfig`, which take an `httpx2` client, Ollama's `http_client` is an `httpx` client. Use `proxy=` rather than `proxies=`, which current `httpx` rejects. + Unlike `OpenAICompletionsConfig` and `AnthropicConfig`, which take an `httpx2` client, Ollama's `http_client` is an `httpx` client. Use `proxy=` rather than `proxies=`, which current `httpx` rejects. #### Tool Results @@ -868,13 +878,13 @@ Structured data extraction, one of the decision shapes in [TypeSafe's docs](http ### Self-Hosted and OpenAI-Compatible Models (vLLM, LM Studio, etc.) -If you are using a self-hosted model or an API that is compatible with the OpenAI format (such as **vLLM**, **LM Studio**, **FastChat**, or **Together AI**), you can use the `OpenAIConfig` class and specify a custom `base_url`. +If you are using a self-hosted model or an API that is compatible with the OpenAI format (such as **vLLM**, **LM Studio**, **FastChat**, or **Together AI**), you can use the `OpenAICompletionsConfig` class and specify a custom `base_url`. ```python linenums="1" hl_lines="6" -from ag2.config import OpenAIConfig +from ag2.config import OpenAICompletionsConfig # Configure a vLLM or other OpenAI-compatible endpoint -config = OpenAIConfig( +config = OpenAICompletionsConfig( model="qwen-3", base_url="http://localhost:8000/v1", # Some endpoints don't require an API key, but the client expects a non-empty string @@ -887,9 +897,9 @@ config = OpenAIConfig( ```python linenums="1" hl_lines="8" import httpx2 - from ag2.config import OpenAIConfig + from ag2.config import OpenAICompletionsConfig - config = OpenAIConfig( + config = OpenAICompletionsConfig( model="qwen-3", base_url="https://localhost:8000/v1", api_key="NotRequired", @@ -899,7 +909,7 @@ config = OpenAIConfig( !!! note "`http_client` is an `httpx2` client" From `openai>=3` the OpenAI SDK uses [`httpx2`](https://pypi.org/project/httpx2/){.external-link target="_blank"}, which - `ag2[openai]` installs for you. `OpenAIConfig` and `OpenAIResponsesConfig` annotate + `ag2[openai]` installs for you. `OpenAICompletionsConfig` and `OpenAIResponsesConfig` annotate `http_client` accordingly, and ag2 forwards whatever you pass to the SDK untouched. A legacy `httpx.AsyncClient` still works at runtime: the SDK keeps a compatibility path @@ -920,9 +930,9 @@ config = OpenAIConfig( ```python import httpx2 - from ag2.config import OpenAIConfig + from ag2.config import OpenAICompletionsConfig - config = OpenAIConfig(model="gpt-4o", http_client=httpx2.AsyncClient(verify="/path/to/ca-bundle.crt")) + config = OpenAICompletionsConfig(model="gpt-4o", http_client=httpx2.AsyncClient(verify="/path/to/ca-bundle.crt")) ``` `certifi` remains installed — ag2's own core depends on `httpx` — but the OpenAI client @@ -930,15 +940,15 @@ config = OpenAIConfig( ### Extra Body Parameters -Some OpenAI API-compatible providers require additional, provider-specific parameters in the request body. Use the `extra_body` parameter on `OpenAIConfig` to pass these through directly to the API call. +Some OpenAI API-compatible providers require additional, provider-specific parameters in the request body. Use the `extra_body` parameter on `OpenAICompletionsConfig` to pass these through directly to the API call. This is useful for enabling features like extended thinking on self-hosted or third-party models: ```python linenums="1" hl_lines="7" -from ag2.config import OpenAIConfig +from ag2.config import OpenAICompletionsConfig # NVIDIA NIM -nemotron = OpenAIConfig( +nemotron = OpenAICompletionsConfig( model="nvidia/nemotron-3-super-120b-a12b", base_url="https://integrate.api.nvidia.com/v1", extra_body={"chat_template_kwargs": {"thinking": True}}, @@ -951,9 +961,9 @@ Model configurations are **immutable**. If you need to reuse a configuration for ```python linenums="1" hl_lines="4 9 15" from ag2.agent import Agent -from ag2.config import OpenAIConfig +from ag2.config import OpenAICompletionsConfig -base_config = OpenAIConfig(model="gpt-5") +base_config = OpenAICompletionsConfig(model="gpt-5") agent1 = Agent( "Assistant", @@ -976,7 +986,7 @@ You can accomplish this by passing the configuration to the `.ask()` method when ```python linenums="1" hl_lines="15-18" from ag2.agent import Agent -from ag2.config import OpenAIConfig +from ag2.config import OpenAICompletionsConfig # Define an agent without an initial model config, # or with a default one you plan to override later @@ -989,7 +999,7 @@ agent = Agent( # Ask the agent, passing the explicit model configuration response = await agent.ask( "Hello!", - config=OpenAIConfig( + config=OpenAICompletionsConfig( model="gpt-5", api_key="sk-user-specific-key" ) @@ -1001,16 +1011,16 @@ response = await agent.ask( ```python linenums="1" hl_lines="6 12" from ag2.agent import Agent - from ag2.config import OpenAIConfig + from ag2.config import OpenAICompletionsConfig agent = Agent( "Assistant", - config=OpenAIConfig(model="gpt-5"), + config=OpenAICompletionsConfig(model="gpt-5"), ) response = await agent.ask( "Hello!", # overrides the original model configuration - config=OpenAIConfig(model="gpt-5-mini") + config=OpenAICompletionsConfig(model="gpt-5-mini") ) ```