Skip to content

Commit e548500

Browse files
committed
chore: include unit tests for refactoring get_llm
1 parent 87fad0c commit e548500

5 files changed

Lines changed: 200 additions & 2 deletions

File tree

BaseAgent/tests/README_TESTS.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ pytest BaseAgent/tests/test_integration.py -v
4343

4444
# LLM usage metrics
4545
pytest BaseAgent/tests/test_llm_usage_metrics.py -v
46+
47+
# LLM LangChain chatmodel
48+
pytest BaseAgent/tests/test_llm_providers.py -v
4649
```
4750

4851
### Run Tests by Marker

BaseAgent/tests/conftest.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,15 @@
77
import sys
88
from pathlib import Path
99
from typing import Callable
10+
from dotenv import load_dotenv
1011

1112
import pytest
1213

14+
# Load environment variables first
15+
env_path = Path(__file__).resolve().parents[2] / ".env"
16+
if env_path.exists():
17+
load_dotenv(env_path, override=True)
18+
1319
# Add parent directory to path
1420
ROOT = Path(__file__).resolve().parents[2]
1521
if str(ROOT) not in sys.path:
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
"""
2+
Unit tests for LLM providers using get_llm function.
3+
4+
These tests verify that get_llm() can successfully initialize chat models
5+
for different providers and that they generate valid responses.
6+
7+
Run with: pytest test_llm_providers.py -v
8+
Run specific provider: pytest test_llm_providers.py -v -k "openai"
9+
"""
10+
11+
from __future__ import annotations
12+
13+
import os
14+
import sys
15+
from pathlib import Path
16+
17+
import pytest
18+
19+
from BaseAgent.llm import get_llm, _detect_source
20+
21+
22+
def has_api_key(env_var: str) -> bool:
23+
"""Check if an API key environment variable is set."""
24+
return os.getenv(env_var) is not None
25+
26+
27+
def validate_llm_response(response: str) -> None:
28+
"""
29+
Validate that a response from an LLM is valid.
30+
31+
Args:
32+
response: The generated response text
33+
"""
34+
assert response, "Response should not be empty"
35+
assert isinstance(response, str), "Response should be a string"
36+
assert len(response.strip()) > 3, "Response should have meaningful content"
37+
38+
39+
@pytest.mark.unit
40+
class TestSourceDetection:
41+
"""Test automatic LLM source detection."""
42+
43+
def test_auto_detect_openai(self):
44+
"""Test that OpenAI models are auto-detected from model name."""
45+
source = _detect_source(model="gpt-4o-mini")
46+
assert source == "OpenAI"
47+
48+
def test_auto_detect_azure_openai(self):
49+
"""Test that Azure OpenAI models are auto-detected from model name."""
50+
source = _detect_source(model="azure-gpt-4o")
51+
assert source == "AzureOpenAI"
52+
53+
def test_auto_detect_anthropic(self):
54+
"""Test that Anthropic models are auto-detected from model name."""
55+
source = _detect_source(model="claude-3-5-haiku-20241022")
56+
assert source == "Anthropic"
57+
58+
def test_auto_detect_anthropic_foundry(self):
59+
"""Test that Azure OpenAI models are auto-detected from model name."""
60+
source = _detect_source(model="azure-claude-sonnet-4-5")
61+
assert source == "AnthropicFoundry"
62+
63+
def test_auto_detect_gemini(self):
64+
"""Test that Gemini models are auto-detected from model name."""
65+
source = _detect_source(model="gemini-1.5-flash")
66+
assert source == "Gemini"
67+
68+
69+
@pytest.mark.integration
70+
@pytest.mark.skipif(not has_api_key("OPENAI_API_KEY"), reason="OpenAI API key not found")
71+
class TestOpenAI:
72+
"""Test OpenAI provider."""
73+
74+
def test_openai_connection(self):
75+
"""Test OpenAI with GPT-4o model."""
76+
_, chat_model = get_llm(model="gpt-4o")
77+
response = chat_model.invoke("What's 2+2? Answer in one short sentence.")
78+
validate_llm_response(response.content)
79+
80+
81+
@pytest.mark.integration
82+
@pytest.mark.skipif(
83+
not (has_api_key("AZURE_FOUNDRY_API_KEY") and os.getenv("AZURE_FOUNDRY_BASE_URL")),
84+
reason="Azure Foundry credentials not found"
85+
)
86+
class TestAzureOpenAI:
87+
"""Test Azure OpenAI provider."""
88+
89+
def test_azure_openai_connection(self):
90+
"""Test Azure OpenAI connection."""
91+
_, chat_model = get_llm(model="azure-gpt-5.1")
92+
response = chat_model.invoke("What's 2+2? Answer in one short sentence.")
93+
validate_llm_response(response.content)
94+
95+
96+
@pytest.mark.integration
97+
@pytest.mark.skipif(not has_api_key("ANTHROPIC_API_KEY"), reason="Anthropic API key not found")
98+
class TestAnthropic:
99+
"""Test Anthropic provider."""
100+
101+
def test_anthropic_connection(self):
102+
"""Test Anthropic connection."""
103+
_, chat_model = get_llm(model="claude-sonnet-4-5")
104+
response = chat_model.invoke("What's 2+2? Answer in one short sentence.")
105+
validate_llm_response(response.content)
106+
107+
108+
@pytest.mark.integration
109+
@pytest.mark.skipif(
110+
not (has_api_key("ANTHROPIC_FOUNDRY_API_KEY") and os.getenv("ANTHROPIC_FOUNDRY_BASE_URL")),
111+
reason="Anthropic Foundry credentials not found"
112+
)
113+
class TestAnthropicFoundry:
114+
"""Test Anthropic Foundry (Azure) provider."""
115+
116+
def test_anthropic_foundry_connection(self):
117+
"""Test Anthropic Foundry connection."""
118+
_, chat_model = get_llm(model="azure-claude-sonnet-4-5")
119+
response = chat_model.invoke("What's 2+2? Answer in one short sentence.")
120+
validate_llm_response(response.content)
121+
122+
123+
@pytest.mark.integration
124+
class TestOllama:
125+
"""Test Ollama provider (assumes Ollama is running locally)."""
126+
127+
@pytest.mark.skipif(
128+
not os.path.exists(os.path.expanduser("~/.ollama")),
129+
reason="Ollama not installed"
130+
)
131+
def test_ollama_connection(self):
132+
"""Test Ollama with Llama model."""
133+
try:
134+
_, chat_model = get_llm(model="llama3.2:3b")
135+
response = chat_model.invoke("What's 2+2? Answer in one short sentence.")
136+
validate_llm_response(response.content)
137+
except Exception as e:
138+
pytest.skip(f"Ollama service not available: {e}")
139+
140+
141+
@pytest.mark.integration
142+
@pytest.mark.skipif(not has_api_key("GEMINI_API_KEY"), reason="Gemini API key not found")
143+
class TestGemini:
144+
"""Test Google Gemini provider."""
145+
146+
def test_gemini_connection(self):
147+
"""Test Gemini with Gemini Pro model."""
148+
_, chat_model = get_llm(model="gemini-1.5-pro")
149+
response = chat_model.invoke("What's 2+2? Answer in one short sentence.")
150+
validate_llm_response(response.content)
151+
152+
153+
@pytest.mark.integration
154+
@pytest.mark.skipif(not has_api_key("GROQ_API_KEY"), reason="Groq API key not found")
155+
class TestGroq:
156+
"""Test Groq provider."""
157+
158+
def test_groq_connection(self):
159+
"""Test Groq with Llama model."""
160+
_, chat_model = get_llm(
161+
model="llama-3.3-70b-versatile",
162+
source="Groq"
163+
)
164+
response = chat_model.invoke("What's 2+2? Answer in one short sentence.")
165+
validate_llm_response(response.content)
166+
167+
168+
@pytest.mark.integration
169+
@pytest.mark.skipif(
170+
not (has_api_key("AWS_ACCESS_KEY_ID") and has_api_key("AWS_SECRET_ACCESS_KEY")),
171+
reason="AWS credentials not found"
172+
)
173+
class TestBedrock:
174+
"""Test AWS Bedrock provider."""
175+
176+
def test_bedrock_connection(self):
177+
"""Test Bedrock with Claude model."""
178+
_, chat_model = get_llm(model="anthropic.claude-3-sonnet-20240229-v1:0", source="Bedrock")
179+
response = chat_model.invoke("What's 2+2? Answer in one short sentence.")
180+
validate_llm_response(response.content)
181+
182+
183+
if __name__ == "__main__":
184+
pytest.main([__file__, "-v"])

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,4 +107,5 @@ ignore_missing_imports = true
107107
[dependency-groups]
108108
dev = [
109109
"ipykernel>=7.1.0",
110+
"pytest>=9.0.1",
110111
]

uv.lock

Lines changed: 6 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)