Add provider model catalogs to news risk settings
This commit is contained in:
1 parent
b54863f40a
commit
376d07250e
7 files changed
+235
-4
No files matched your search
@@ -73,3 +73,84 @@ def test_delete_provider_handles_news_risk_reference(trading, monkeypatch, enabl
|
||||
assert reference.provider_id is None
|
||||
session.delete.assert_awaited_once_with(provider)
|
||||
session.commit.assert_awaited_once()
|
||||
|
||||
|
||||
@pytest.mark.parametrize('protocol,base_url', [('openai', 'https://example.invalid/v1/'), ('anthropic', 'https://example.invalid/')])
|
||||
def test_model_catalog_uses_selected_protocol_and_reads_all_pages(trading, monkeypatch, protocol, base_url):
|
||||
from alphapulse.ai import llm
|
||||
options = []
|
||||
|
||||
class Page:
|
||||
async def __aiter__(self):
|
||||
for identifier in ['model-b', 'model-a', 'model-b']:
|
||||
yield SimpleNamespace(id=identifier)
|
||||
|
||||
class Client:
|
||||
def __init__(self, **kwargs):
|
||||
options.append(kwargs)
|
||||
self.models = SimpleNamespace(list=AsyncMock(return_value=Page()))
|
||||
async def __aenter__(self): return self
|
||||
async def __aexit__(self, *args): pass
|
||||
|
||||
monkeypatch.setattr(llm, 'decrypt', lambda value: 'test-only-key')
|
||||
monkeypatch.setattr(llm.openai if protocol == 'openai' else llm.anthropic,
|
||||
'AsyncOpenAI' if protocol == 'openai' else 'AsyncAnthropic', Client)
|
||||
provider = SimpleNamespace(protocol=protocol, base_url=base_url, api_key_encrypted=b'test')
|
||||
assert asyncio.run(llm.list_models(provider)) == ['model-a', 'model-b']
|
||||
assert options == [{'base_url': 'https://example.invalid/v1' if protocol == 'openai' else 'https://example.invalid',
|
||||
'api_key': 'test-only-key', 'timeout': 10, 'max_retries': 0}]
|
||||
|
||||
|
||||
@pytest.mark.parametrize('failure', ['http', 'timeout', 'invalid_id'])
|
||||
def test_model_catalog_failures_remain_explicit_without_exposing_upstream_details(trading, monkeypatch, failure):
|
||||
from alphapulse.ai import llm
|
||||
|
||||
class Page:
|
||||
async def __aiter__(self):
|
||||
yield SimpleNamespace(id=None)
|
||||
|
||||
class Client:
|
||||
def __init__(self, **kwargs):
|
||||
self.models = SimpleNamespace(list=AsyncMock(return_value=Page()))
|
||||
if failure == 'timeout': self.models.list.side_effect = TimeoutError()
|
||||
if failure == 'http':
|
||||
response = httpx.Response(401, request=httpx.Request('GET', 'https://example.invalid/v1/models'))
|
||||
self.models.list.side_effect = llm.openai.APIStatusError('private-upstream-details', response=response, body=None)
|
||||
async def __aenter__(self): return self
|
||||
async def __aexit__(self, *args): pass
|
||||
|
||||
monkeypatch.setattr(llm, 'decrypt', lambda value: 'test-only-key')
|
||||
monkeypatch.setattr(llm.openai, 'AsyncOpenAI', Client)
|
||||
provider = SimpleNamespace(protocol='openai', base_url='https://example.invalid', api_key_encrypted=b'test')
|
||||
with pytest.raises(llm.LlmError) as error:
|
||||
asyncio.run(llm.list_models(provider))
|
||||
assert 'private-upstream-details' not in str(error.value)
|
||||
assert '手动填写' in str(error.value)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('failure', [None, 'unavailable', 'missing'])
|
||||
def test_provider_model_catalog_http_contract(trading, monkeypatch, failure):
|
||||
from fastapi import FastAPI, HTTPException
|
||||
from alphapulse.api import settings
|
||||
provider = SimpleNamespace(id=1)
|
||||
|
||||
async def get_provider(session, identifier):
|
||||
assert identifier == 1
|
||||
if failure == 'missing': raise HTTPException(404, '模型配置不存在')
|
||||
return provider
|
||||
|
||||
catalog = AsyncMock(return_value=['model-a', 'model-b'])
|
||||
if failure == 'unavailable': catalog.side_effect = settings.llm.LlmError('读取模型列表失败')
|
||||
monkeypatch.setattr(settings, 'get_provider', get_provider)
|
||||
monkeypatch.setattr(settings.llm, 'list_models', catalog)
|
||||
app = FastAPI()
|
||||
app.include_router(settings.router)
|
||||
app.dependency_overrides[settings.require_admin] = lambda: None
|
||||
app.dependency_overrides[settings.get_session] = lambda: None
|
||||
|
||||
async def check():
|
||||
async with httpx.AsyncClient(transport=httpx.ASGITransport(app), base_url='http://test') as client:
|
||||
response = await client.get('/api/settings/providers/1/models')
|
||||
assert response.status_code == ({None: 200, 'unavailable': 502, 'missing': 404}[failure])
|
||||
if failure is None: assert response.json()['data'] == ['model-a', 'model-b']
|
||||
asyncio.run(check())
|
||||
Reference in new issue
Block a user