carepath-api / interpreter /tests /test_cloud_providers.py
tranth3truong's picture
Deploy public Scribe-only CarePath Space
cc678b9
Raw
History Blame Contribute Delete
5.36 kB
import pytest
from app.config import Settings
from app.providers import get_providers
from app.providers.base import ASRProvider, GlossaryEntry, MTProvider, ProviderOutputError
from app.providers.claude_mt import ClaudeMTProvider, parse_mt_output
from app.providers.claude_reviewer import ClaudeReviewerProvider, parse_review_output
from app.providers.openai_asr import OpenAIASRProvider, confidence_from_logprobs
class TextBlock:
def __init__(self, text: str) -> None:
self.text = text
class Message:
def __init__(self, text: str) -> None:
self.content = [TextBlock(text)]
class FakeMessages:
def __init__(self, text: str) -> None:
self.text = text
self.kwargs = {}
def create(self, **kwargs):
self.kwargs = kwargs
return Message(self.text)
class FakeAnthropicClient:
def __init__(self, text: str) -> None:
self.messages = FakeMessages(text)
class FakeTranscriptions:
def __init__(self) -> None:
self.calls = 0
self.kwargs = {}
def create(self, **kwargs):
self.calls += 1
self.kwargs = kwargs
return {"text": "xin chao", "logprobs": [{"logprob": -0.1}, {"logprob": -0.3}]}
class FakeAudio:
def __init__(self) -> None:
self.transcriptions = FakeTranscriptions()
class FakeOpenAIClient:
def __init__(self) -> None:
self.audio = FakeAudio()
def test_registry_returns_cloud_contracts() -> None:
providers = get_providers(
Settings(provider_mode="cloud", openai_api_key="openai", anthropic_api_key="anthropic")
)
assert isinstance(providers.asr, ASRProvider)
assert isinstance(providers.mt, MTProvider)
def test_openai_asr_uses_language_hint_and_logprob_confidence() -> None:
client = FakeOpenAIClient()
provider = OpenAIASRProvider(api_key="", client=client)
result = provider.transcribe(b"audio", "vi")
assert result.text == "xin chao"
expected_confidence = confidence_from_logprobs([{"logprob": -0.1}, {"logprob": -0.3}])
assert round(result.confidence, 3) == round(expected_confidence, 3)
assert client.audio.transcriptions.kwargs["model"] == "gpt-4o-transcribe"
assert client.audio.transcriptions.kwargs["language"] == "vi"
assert client.audio.transcriptions.kwargs["include"] == ["logprobs"]
def test_openai_asr_retries_then_raises() -> None:
class FailingTranscriptions:
def __init__(self) -> None:
self.calls = 0
def create(self, **kwargs):
del kwargs
self.calls += 1
raise TimeoutError("slow")
class FailingClient:
def __init__(self) -> None:
self.audio = type("Audio", (), {"transcriptions": FailingTranscriptions()})()
client = FailingClient()
provider = OpenAIASRProvider(api_key="", client=client, attempts=2)
with pytest.raises(RuntimeError, match="ASR transcription failed"):
provider.transcribe(b"audio", "en")
assert client.audio.transcriptions.calls == 2
def test_claude_mt_strict_json_and_glossary_prompt() -> None:
client = FakeAnthropicClient('{"translation":"Take Augmentin after food","confidence":0.93}')
provider = ClaudeMTProvider(api_key="", client=client)
result = provider.translate(
"Uống Augmentin sau ăn",
"vi",
"en",
[GlossaryEntry(term_vi="Augmentin", term_en="Augmentin", kind="drug")],
)
assert result.text == "Take Augmentin after food"
assert result.confidence == 0.93
prompt = client.messages.kwargs["messages"][0]["content"]
assert "Augmentin" in prompt
assert "Never add advice" in client.messages.kwargs["system"]
@pytest.mark.parametrize(
"text",
[
"Take it. You should drink water.",
'{"translation":"x","confidence":2}',
'{"translation":"x","confidence":0.8,"extra":"no"}',
],
)
def test_claude_mt_rejects_malformed_output(text: str) -> None:
with pytest.raises(ProviderOutputError):
parse_mt_output(text)
def test_claude_reviewer_parses_entities() -> None:
client = FakeAnthropicClient(
'{"back_translation":"Uống 0.5 viên",'
'"entities":[{"kind":"dose","source_text":"nửa viên","translated_text":"half a tablet"}],'
'"flags":[]}'
)
provider = ClaudeReviewerProvider(api_key="", client=client)
review = provider.review("Uống nửa viên", "Take half a tablet", "vi", "en")
assert review.back_translation == "Uống 0.5 viên"
assert review.entities[0].kind == "dose"
assert review.entities[0].source_text == "nửa viên"
assert review.entities[0].translated_text == "half a tablet"
@pytest.mark.parametrize(
"text",
[
"{}",
'{"back_translation":"","entities":[],"flags":[]}',
'{"back_translation":"x","entities":[{"kind":"dose","source_span":[2],"translated_span":[0,1]}],"flags":[]}',
'{"back_translation":"x","entities":[{"kind":"dose","source_text":2,"translated_text":"x"}],"flags":[]}',
],
)
def test_claude_reviewer_rejects_malformed_output(text: str) -> None:
with pytest.raises(ProviderOutputError):
parse_review_output(text)
@pytest.mark.live
def test_openai_asr_live_fixture_is_not_ci_default() -> None:
pytest.skip("live golden-audio fixture runs only with real API keys and committed audio")