from __future__ import annotations import httpx import pytest import respx from app.llm.base import ProviderError, RateLimitError from app.llm.groq import GroqProvider URL = "https://api.groq.com/openai/v1/chat/completions" def _provider() -> GroqProvider: return GroqProvider(api_key="k") @respx.mock async def test_groq_returns_content(): respx.post(URL).mock( return_value=httpx.Response( 200, json={"choices": [{"finish_reason": "stop", "message": {"content": "hola"}}]}, ) ) r = await _provider().chat(messages=[{"role": "user", "content": "x"}], tools=[], model="m") assert r.content == "hola" assert r.tool_calls == [] @respx.mock async def test_groq_parses_tool_calls(): respx.post(URL).mock( return_value=httpx.Response( 200, json={ "choices": [ { "finish_reason": "tool_calls", "message": { "content": None, "tool_calls": [ { "id": "c1", "type": "function", "function": { "name": "lookup_order", "arguments": '{"email":"a@b.c"}', }, } ], }, } ] }, ) ) r = await _provider().chat( messages=[{"role": "user", "content": "x"}], tools=[], model="llama-3.1-8b-instant" ) assert r.tool_calls[0].name == "lookup_order" assert r.tool_calls[0].arguments == {"email": "a@b.c"} @respx.mock async def test_groq_captures_token_usage(): respx.post(URL).mock( return_value=httpx.Response( 200, json={ "choices": [{"finish_reason": "stop", "message": {"content": "ok"}}], "usage": {"prompt_tokens": 120, "completion_tokens": 30, "total_tokens": 150}, }, ) ) r = await _provider().chat(messages=[{"role": "user", "content": "x"}], tools=[], model="m") assert r.usage["total_tokens"] == 150 assert r.usage["prompt_tokens"] == 120 @respx.mock async def test_groq_429_raises_ratelimit(): respx.post(URL).mock(return_value=httpx.Response(429, text="slow down")) with pytest.raises(RateLimitError): await _provider().chat(messages=[{"role": "user", "content": "x"}], tools=[], model="m") @respx.mock async def test_groq_500_raises_provider_error(): respx.post(URL).mock(return_value=httpx.Response(500, text="boom")) with pytest.raises(ProviderError): await _provider().chat(messages=[{"role": "user", "content": "x"}], tools=[], model="m") @respx.mock async def test_groq_malformed_200_raises_provider_error_for_failover(): # 200 OK but no choices → must become ProviderError so the router fails over. respx.post(URL).mock(return_value=httpx.Response(200, json={"unexpected": True})) with pytest.raises(ProviderError): await _provider().chat(messages=[{"role": "user", "content": "x"}], tools=[], model="m")