diff --git a/artemis/llm/router.py b/artemis/llm/router.py index f2e6b42b6..a56cc34d9 100644 --- a/artemis/llm/router.py +++ b/artemis/llm/router.py @@ -22,6 +22,7 @@ from enum import StrEnum import hashlib import os +import re from typing import Any from langchain_core.language_models.chat_models import BaseChatModel @@ -383,6 +384,11 @@ def create_model(cls, endpoint: ModelEndpoint) -> BaseChatModel: } if endpoint.reasoning_effort: kwargs["reasoning_effort"] = endpoint.reasoning_effort + # Some OpenAI-compatible endpoints serve function tools only on the Responses API (e.g. GPT-6.x on + # Amazon Bedrock). Opt in per model with ARTEMIS_OPENAI_RESPONSES_API, a regex on the model name. + responses_models = os.environ.get("ARTEMIS_OPENAI_RESPONSES_API") + if responses_models and re.search(responses_models, endpoint.model_name or ""): + kwargs["use_responses_api"] = True return ChatOpenAI(**{k: v for k, v in kwargs.items() if v is not None}) else: diff --git a/tests/unit/test_model_factory_responses_api.py b/tests/unit/test_model_factory_responses_api.py new file mode 100644 index 000000000..9ca424ded --- /dev/null +++ b/tests/unit/test_model_factory_responses_api.py @@ -0,0 +1,42 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""ARTEMIS_OPENAI_RESPONSES_API opts matching OpenAI-compatible models into the Responses API.""" + +from unittest.mock import patch + +from artemis.llm.router import ModelEndpoint, ModelFactory, ModelProvider + + +def _kwargs(model_name: str) -> dict: + with patch("langchain_openai.ChatOpenAI") as chat_openai: + ModelFactory.create_model( + ModelEndpoint(provider=ModelProvider.CUSTOM, model_name=model_name) + ) + return chat_openai.call_args.kwargs + + +def test_matching_model_uses_the_responses_api(monkeypatch): + monkeypatch.setenv("ARTEMIS_OPENAI_RESPONSES_API", r"openai\.gpt-6") + assert _kwargs("global.openai.gpt-6.1-sol")["use_responses_api"] is True + + +def test_non_matching_model_keeps_chat_completions(monkeypatch): + monkeypatch.setenv("ARTEMIS_OPENAI_RESPONSES_API", r"openai\.gpt-6") + assert "use_responses_api" not in _kwargs("qwen.qwen3-vl-235b-a22b") + + +def test_unset_opt_in_changes_nothing(monkeypatch): + monkeypatch.delenv("ARTEMIS_OPENAI_RESPONSES_API", raising=False) + assert "use_responses_api" not in _kwargs("global.openai.gpt-6.1-sol")