| import os |
| import sys |
| import types |
|
|
| import pytest |
|
|
| os.environ["DEBUG"] = "false" |
|
|
| from landppt.ai.base import AIMessage, MessageRole |
| from landppt.ai.providers import OpenAIProvider |
|
|
|
|
| class _FailingChatCompletions: |
| async def create(self, **kwargs): |
| raise AssertionError("chat.completions.create should not be used when responses API is enabled") |
|
|
|
|
| class _FakeChatCompletions: |
| def __init__(self): |
| self.create_calls = [] |
|
|
| async def create(self, **kwargs): |
| self.create_calls.append(kwargs) |
| return types.SimpleNamespace( |
| model=kwargs["model"], |
| choices=[ |
| types.SimpleNamespace( |
| message=types.SimpleNamespace(content="Hello from chat completions"), |
| finish_reason="stop", |
| ) |
| ], |
| usage=types.SimpleNamespace(prompt_tokens=13, completion_tokens=8, total_tokens=21), |
| ) |
|
|
|
|
| class _FakeResponsesStream: |
| def __init__(self, events): |
| self._events = list(events) |
|
|
| def __aiter__(self): |
| async def _iterate(): |
| for event in self._events: |
| yield event |
|
|
| return _iterate() |
|
|
|
|
| class _FakeResponsesStreamManager: |
| def __init__(self, events): |
| self._events = events |
|
|
| async def __aenter__(self): |
| return _FakeResponsesStream(self._events) |
|
|
| async def __aexit__(self, exc_type, exc, tb): |
| return None |
|
|
|
|
| class _FakeResponsesAPI: |
| def __init__(self): |
| self.create_calls = [] |
| self.stream_calls = [] |
|
|
| async def create(self, **kwargs): |
| self.create_calls.append(kwargs) |
| return types.SimpleNamespace( |
| model=kwargs["model"], |
| output_text="Hello from responses", |
| usage=types.SimpleNamespace(input_tokens=11, output_tokens=7, total_tokens=18), |
| status="completed", |
| incomplete_details=None, |
| ) |
|
|
| def stream(self, **kwargs): |
| self.stream_calls.append(kwargs) |
| events = [ |
| types.SimpleNamespace(type="response.output_text.delta", delta="Hello "), |
| types.SimpleNamespace(type="response.output_text.delta", delta="from "), |
| types.SimpleNamespace(type="response.output_text.delta", delta="responses"), |
| ] |
| return _FakeResponsesStreamManager(events) |
|
|
|
|
| @pytest.mark.asyncio |
| async def test_openai_provider_chat_completion_uses_responses_api(monkeypatch): |
| instances = [] |
|
|
| class _FakeAsyncOpenAI: |
| def __init__(self, **kwargs): |
| self.responses = _FakeResponsesAPI() |
| self.chat = types.SimpleNamespace(completions=_FailingChatCompletions()) |
| instances.append(self) |
|
|
| monkeypatch.setitem(sys.modules, "openai", types.SimpleNamespace(AsyncOpenAI=_FakeAsyncOpenAI)) |
|
|
| provider = OpenAIProvider( |
| { |
| "api_key": "test-key", |
| "base_url": "https://api.openai.com/v1", |
| "model": "gpt-4.1", |
| "use_responses_api": True, |
| "enable_reasoning": True, |
| "reasoning_effort": "high", |
| } |
| ) |
|
|
| response = await provider.chat_completion( |
| [AIMessage(role=MessageRole.USER, content="hello")], |
| max_output_tokens=16, |
| ) |
|
|
| assert response.content == "Hello from responses" |
| assert response.usage == {"prompt_tokens": 11, "completion_tokens": 7, "total_tokens": 18} |
| assert response.metadata["transport"] == "responses" |
| assert len(instances) == 1 |
| assert instances[0].responses.create_calls[0]["input"][0]["content"] == "hello" |
| assert instances[0].responses.create_calls[0]["reasoning"] == {"effort": "high"} |
| assert "max_output_tokens" not in instances[0].responses.create_calls[0] |
|
|
|
|
| @pytest.mark.asyncio |
| async def test_openai_provider_streaming_uses_responses_api(monkeypatch): |
| instances = [] |
|
|
| class _FakeAsyncOpenAI: |
| def __init__(self, **kwargs): |
| self.responses = _FakeResponsesAPI() |
| self.chat = types.SimpleNamespace(completions=_FailingChatCompletions()) |
| instances.append(self) |
|
|
| monkeypatch.setitem(sys.modules, "openai", types.SimpleNamespace(AsyncOpenAI=_FakeAsyncOpenAI)) |
|
|
| provider = OpenAIProvider( |
| { |
| "api_key": "test-key", |
| "base_url": "https://api.openai.com/v1", |
| "model": "gpt-4.1", |
| "use_responses_api": True, |
| "enable_reasoning": True, |
| "reasoning_effort": "minimal", |
| } |
| ) |
|
|
| chunks = [] |
| async for chunk in provider.stream_chat_completion( |
| [AIMessage(role=MessageRole.USER, content="hello")], |
| ): |
| chunks.append(chunk) |
|
|
| assert "".join(chunks) == "Hello from responses" |
| assert len(instances) == 1 |
| assert instances[0].responses.stream_calls[0]["input"][0]["content"] == "hello" |
| assert instances[0].responses.stream_calls[0]["reasoning"] == {"effort": "minimal"} |
|
|
|
|
| @pytest.mark.asyncio |
| async def test_openai_provider_chat_completions_uses_reasoning_effort(monkeypatch): |
| instances = [] |
|
|
| class _FakeAsyncOpenAI: |
| def __init__(self, **kwargs): |
| self.responses = _FakeResponsesAPI() |
| self.chat = types.SimpleNamespace(completions=_FakeChatCompletions()) |
| instances.append(self) |
|
|
| monkeypatch.setitem(sys.modules, "openai", types.SimpleNamespace(AsyncOpenAI=_FakeAsyncOpenAI)) |
|
|
| provider = OpenAIProvider( |
| { |
| "api_key": "test-key", |
| "base_url": "https://api.openai.com/v1", |
| "model": "gpt-4.1", |
| "max_tokens": 4096, |
| "enable_reasoning": True, |
| "reasoning_effort": "low", |
| } |
| ) |
|
|
| response = await provider.chat_completion( |
| [AIMessage(role=MessageRole.USER, content="hello")], |
| ) |
|
|
| assert response.content == "Hello from chat completions" |
| assert response.metadata["transport"] == "chat_completions" |
| assert len(instances) == 1 |
| assert instances[0].chat.completions.create_calls[0]["reasoning_effort"] == "low" |
| assert "max_tokens" not in instances[0].chat.completions.create_calls[0] |
|
|