mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-05-18 23:03:06 +08:00
fix: harden openai-compatible provider responses
This commit is contained in:
@@ -1,7 +1,10 @@
|
||||
from types import SimpleNamespace
|
||||
|
||||
import pytest
|
||||
|
||||
from llm.core.types import LLMInput, Message, Role, ToolDefinition
|
||||
from llm.providers.claude import ClaudeProvider
|
||||
from llm.providers.constants import EMPTY_FILTERED_RESPONSE_ERROR
|
||||
from llm.providers.openai import OpenAIProvider
|
||||
|
||||
|
||||
@@ -14,21 +17,20 @@ def _tool() -> ToolDefinition:
|
||||
|
||||
|
||||
class _OpenAICompletions:
|
||||
def __init__(self) -> None:
|
||||
def __init__(self, response: SimpleNamespace | None = None) -> None:
|
||||
self.params = None
|
||||
self.response = response
|
||||
|
||||
def create(self, **params):
|
||||
self.params = params
|
||||
return SimpleNamespace(
|
||||
choices=[SimpleNamespace(message=SimpleNamespace(content="ok", tool_calls=None), finish_reason="stop")],
|
||||
model=params["model"],
|
||||
usage=SimpleNamespace(prompt_tokens=1, completion_tokens=1, total_tokens=2),
|
||||
)
|
||||
if self.response:
|
||||
return self.response
|
||||
return _openai_response(model=params["model"])
|
||||
|
||||
|
||||
class _OpenAIClient:
|
||||
def __init__(self) -> None:
|
||||
self.completions = _OpenAICompletions()
|
||||
def __init__(self, response: SimpleNamespace | None = None) -> None:
|
||||
self.completions = _OpenAICompletions(response=response)
|
||||
self.chat = SimpleNamespace(completions=self.completions)
|
||||
|
||||
|
||||
@@ -52,6 +54,16 @@ class _AnthropicClient:
|
||||
self.api_key = "test"
|
||||
|
||||
|
||||
def _openai_response(**overrides) -> SimpleNamespace:
|
||||
defaults = {
|
||||
"choices": [SimpleNamespace(message=SimpleNamespace(content="ok", tool_calls=None), finish_reason="stop")],
|
||||
"model": "gpt-4o-mini",
|
||||
"usage": SimpleNamespace(prompt_tokens=1, completion_tokens=1, total_tokens=2),
|
||||
}
|
||||
defaults.update(overrides)
|
||||
return SimpleNamespace(**defaults)
|
||||
|
||||
|
||||
def test_openai_provider_serializes_tools_for_chat_completions():
|
||||
provider = OpenAIProvider(api_key="test")
|
||||
client = _OpenAIClient()
|
||||
@@ -72,6 +84,36 @@ def test_openai_provider_serializes_tools_for_chat_completions():
|
||||
]
|
||||
|
||||
|
||||
def test_openai_provider_can_be_constructed_without_credentials(monkeypatch):
|
||||
monkeypatch.delenv("OPENAI_API_KEY", raising=False)
|
||||
|
||||
provider = OpenAIProvider()
|
||||
|
||||
assert provider.validate_config() is False
|
||||
|
||||
|
||||
def test_openai_provider_rejects_empty_or_filtered_responses():
|
||||
provider = OpenAIProvider(api_key="test")
|
||||
|
||||
for response in [
|
||||
_openai_response(choices=[]),
|
||||
_openai_response(choices=[SimpleNamespace(message=None, finish_reason="content_filter")]),
|
||||
]:
|
||||
provider.client = _OpenAIClient(response=response)
|
||||
with pytest.raises(ValueError, match=EMPTY_FILTERED_RESPONSE_ERROR):
|
||||
provider.generate(LLMInput(messages=[Message(role=Role.USER, content="hi")]))
|
||||
|
||||
|
||||
def test_openai_provider_allows_missing_usage():
|
||||
provider = OpenAIProvider(api_key="test")
|
||||
provider.client = _OpenAIClient(response=_openai_response(usage=None))
|
||||
|
||||
output = provider.generate(LLMInput(messages=[Message(role=Role.USER, content="hi")]))
|
||||
|
||||
assert output.content == "ok"
|
||||
assert output.usage is None
|
||||
|
||||
|
||||
def test_claude_provider_serializes_tools_for_messages_api():
|
||||
provider = ClaudeProvider(api_key="test")
|
||||
client = _AnthropicClient()
|
||||
|
||||
Reference in New Issue
Block a user