Files
tradingagents/tests/test_openai_compatible_provider.py
Yijia-Xiao e3bc872982 chore(lint): make the repository ruff-clean under the strict select
Clear the deferred full-repo lint backlog so the whole tree passes the strict
ruff select (E,W,F,I,B,UP,C4,SIM). Mechanical fixes dominate: import sorting,
pep585/604 annotations, dropped dead imports, and whitespace. The few semantic
changes are behavior-preserving: declare __all__ on the agent_utils and
alpha_vantage re-export hubs; expand 'from x import *' to explicit names; use
immutable tuple defaults instead of mutable list defaults; contextlib.suppress
for try/except/pass; and narrow an over-broad assertRaises.
2026-06-14 16:38:36 +00:00

75 lines
3.3 KiB
Python

"""Generic OpenAI-compatible provider (vLLM / LM Studio / llama.cpp / relays).
Verifies the user-supplied base_url is required and honored, the key is optional
(keyless local default), Chat Completions (not the Responses API) is used, any
model name is accepted, and the env backend URL precedence (#978).
"""
import pytest
from tradingagents.llm_clients.api_key_env import get_api_key_env
from tradingagents.llm_clients.factory import create_llm_client
from tradingagents.llm_clients.validators import validate_model
# Note: assert by class NAME, not isinstance — other tests reload the
# openai_client module, which would otherwise create a second class identity.
@pytest.mark.unit
def test_factory_routes_to_openai_client():
client = create_llm_client(
provider="openai_compatible", model="my-model", base_url="http://localhost:8000/v1"
)
assert type(client).__name__ == "OpenAIClient"
@pytest.mark.unit
def test_base_url_required(monkeypatch):
monkeypatch.delenv("OPENAI_COMPATIBLE_API_KEY", raising=False)
with pytest.raises(ValueError, match="requires a base_url"):
create_llm_client(provider="openai_compatible", model="m").get_llm()
@pytest.mark.unit
def test_keyless_local_uses_placeholder_and_chat_completions(monkeypatch):
monkeypatch.delenv("OPENAI_COMPATIBLE_API_KEY", raising=False)
llm = create_llm_client(
provider="openai_compatible", model="qwen2.5", base_url="http://localhost:8000/v1"
).get_llm()
assert type(llm).__name__ == "NormalizedChatOpenAI"
assert str(llm.openai_api_base) == "http://localhost:8000/v1"
# keyless local servers: a placeholder key is sent
key = llm.openai_api_key.get_secret_value() if hasattr(llm.openai_api_key, "get_secret_value") else llm.openai_api_key
assert key == "EMPTY"
# must use Chat Completions, not OpenAI's Responses API
assert getattr(llm, "use_responses_api", False) in (False, None)
@pytest.mark.unit
def test_optional_key_from_env(monkeypatch):
monkeypatch.setenv("OPENAI_COMPATIBLE_API_KEY", "sk-relay-123")
llm = create_llm_client(
provider="openai_compatible", model="m", base_url="https://relay.example/v1"
).get_llm()
key = llm.openai_api_key.get_secret_value() if hasattr(llm.openai_api_key, "get_secret_value") else llm.openai_api_key
assert key == "sk-relay-123"
@pytest.mark.unit
def test_any_model_accepted_no_forced_key():
assert validate_model("openai_compatible", "literally-anything") is True
# The key env exists (read for keyed relays) but the provider is marked
# key-optional, so the CLI never forces a prompt and keyless servers work.
assert get_api_key_env("openai_compatible") == "OPENAI_COMPATIBLE_API_KEY"
from tradingagents.llm_clients.openai_client import OPENAI_COMPATIBLE_PROVIDERS
assert OPENAI_COMPATIBLE_PROVIDERS["openai_compatible"].key_optional is True
@pytest.mark.unit
def test_env_backend_url_precedence():
# #978: explicit env URL wins over the menu/default regardless of provider source.
from cli.utils import resolve_backend_url
assert resolve_backend_url("openai", "https://api.openai.com/v1", env_url="http://proxy/v1") == "http://proxy/v1"
assert resolve_backend_url("openai", "https://api.openai.com/v1", env_url=None) == "https://api.openai.com/v1"
assert resolve_backend_url("deepseek", None, None) == "https://api.deepseek.com"