acb / tests /test_intent_classifier.py
Kagan Tek
merge
79d4fd5
Raw
History Blame Contribute Delete
13.1 kB
# Tests for intent classifier
import pytest
from unittest.mock import Mock, patch, MagicMock
from src.agent.intent_classifier import (
IntentClassifier,
IntentType,
ClassificationResult,
get_intent_classifier,
reset_intent_classifier,
)
class TestIntentType:
# Tests for IntentType enum
def test_intent_types_exist(self):
assert IntentType.DOCUMENT_QUERY is not None
assert IntentType.DATABASE_QUERY is not None
assert IntentType.HYBRID_QUERY is not None
assert IntentType.CONVERSATIONAL is not None
assert IntentType.AMBIGUOUS is not None
def test_intent_type_values(self):
assert IntentType.DOCUMENT_QUERY.value == "document_query"
assert IntentType.DATABASE_QUERY.value == "database_query"
assert IntentType.HYBRID_QUERY.value == "hybrid_query"
assert IntentType.CONVERSATIONAL.value == "conversational"
assert IntentType.AMBIGUOUS.value == "ambiguous"
class TestClassificationResult:
# Tests for ClassificationResult dataclass
def test_classification_result_creation(self):
result = ClassificationResult(
intent=IntentType.DATABASE_QUERY,
confidence=0.95,
reasoning="Test reasoning",
suggested_tables=["users", "orders"],
)
assert result.intent == IntentType.DATABASE_QUERY
assert result.confidence == 0.95
assert result.reasoning == "Test reasoning"
assert result.suggested_tables == ["users", "orders"]
def test_classification_result_default_values(self):
result = ClassificationResult(
intent=IntentType.DOCUMENT_QUERY,
confidence=0.8,
reasoning="Default test",
)
assert result.suggested_tables == []
assert result.requires_clarification is False
def test_classification_result_to_dict(self):
result = ClassificationResult(
intent=IntentType.HYBRID_QUERY,
confidence=0.85,
reasoning="Both sources needed",
suggested_tables=["products"],
)
data = result.to_dict()
assert data["intent"] == "hybrid_query"
assert data["confidence"] == 0.85
assert data["reasoning"] == "Both sources needed"
assert data["suggested_tables"] == ["products"]
def test_is_database_related(self):
db_result = ClassificationResult(IntentType.DATABASE_QUERY, 0.9, "test")
hybrid_result = ClassificationResult(IntentType.HYBRID_QUERY, 0.8, "test")
doc_result = ClassificationResult(IntentType.DOCUMENT_QUERY, 0.9, "test")
assert db_result.is_database_related() is True
assert hybrid_result.is_database_related() is True
assert doc_result.is_database_related() is False
def test_is_document_related(self):
doc_result = ClassificationResult(IntentType.DOCUMENT_QUERY, 0.9, "test")
hybrid_result = ClassificationResult(IntentType.HYBRID_QUERY, 0.8, "test")
db_result = ClassificationResult(IntentType.DATABASE_QUERY, 0.9, "test")
assert doc_result.is_document_related() is True
assert hybrid_result.is_document_related() is True
assert db_result.is_document_related() is False
class TestIntentClassifier:
# Tests for IntentClassifier class
@pytest.fixture
def classifier(self):
with patch("src.agent.intent_classifier.Groq") as mock_groq_class:
mock_client = Mock()
mock_groq_class.return_value = mock_client
classifier = IntentClassifier(api_key="test_key")
classifier._client = mock_client
return classifier
def test_classifier_initialization(self):
classifier = IntentClassifier(api_key="test", confidence_threshold=0.8)
assert classifier._confidence_threshold == 0.8
def test_classifier_with_custom_threshold(self):
classifier = IntentClassifier(
api_key="test",
confidence_threshold=0.5,
)
assert classifier._confidence_threshold == 0.5
def test_classify_database_query(self, classifier):
mock_response = Mock()
mock_response.choices = [
Mock(message=Mock(content='{"intent": "DATABASE_QUERY", "confidence": 0.9, "reasoning": "SQL needed", "suggested_tables": ["musteriler"]}'))
]
classifier._client.chat.completions.create.return_value = mock_response
result = classifier.classify("Kac tane musterimiz var?")
assert result.intent == IntentType.DATABASE_QUERY
assert result.confidence == 0.9
assert "musteriler" in result.suggested_tables
def test_classify_document_query(self, classifier):
mock_response = Mock()
mock_response.choices = [
Mock(message=Mock(content='{"intent": "DOCUMENT_QUERY", "confidence": 0.85, "reasoning": "Document search", "suggested_tables": []}'))
]
classifier._client.chat.completions.create.return_value = mock_response
result = classifier.classify("Sirketi kim kurdu?")
assert result.intent == IntentType.DOCUMENT_QUERY
def test_classify_hybrid_query(self, classifier):
mock_response = Mock()
mock_response.choices = [
Mock(message=Mock(content='{"intent": "HYBRID_QUERY", "confidence": 0.75, "reasoning": "Both needed", "suggested_tables": ["urunler"]}'))
]
classifier._client.chat.completions.create.return_value = mock_response
result = classifier.classify("Urun politikamiz nedir ve kac urunumuz var?")
assert result.intent == IntentType.HYBRID_QUERY
def test_classify_conversational(self, classifier):
mock_response = Mock()
mock_response.choices = [
Mock(message=Mock(content='{"intent": "CONVERSATIONAL", "confidence": 0.95, "reasoning": "Greeting", "suggested_tables": []}'))
]
classifier._client.chat.completions.create.return_value = mock_response
result = classifier.classify("Merhaba!")
assert result.intent == IntentType.CONVERSATIONAL
def test_classify_with_context(self, classifier):
mock_response = Mock()
mock_response.choices = [
Mock(message=Mock(content='{"intent": "DATABASE_QUERY", "confidence": 0.88, "reasoning": "With context", "suggested_tables": []}'))
]
classifier._client.chat.completions.create.return_value = mock_response
chat_history = [{"role": "user", "content": "previous query"}]
result = classifier.classify("Devam et", chat_history)
assert result is not None
classifier._client.chat.completions.create.assert_called_once()
def test_classify_invalid_json_response(self, classifier):
mock_response = Mock()
mock_response.choices = [Mock(message=Mock(content="Invalid JSON"))]
classifier._client.chat.completions.create.return_value = mock_response
result = classifier.classify("Test query")
# Implementation defaults to DOCUMENT_QUERY on parse failure
assert result.intent == IntentType.DOCUMENT_QUERY
assert result.confidence == 0.5
def test_classify_api_error(self, classifier):
classifier._client.chat.completions.create.side_effect = Exception("API Error")
result = classifier.classify("Test query")
# Implementation defaults to DOCUMENT_QUERY on API error
assert result.intent == IntentType.DOCUMENT_QUERY
assert result.confidence == 0.5
class TestIntentClassifierSingleton:
# Tests for singleton pattern
def setup_method(self):
reset_intent_classifier()
def teardown_method(self):
reset_intent_classifier()
@patch("src.agent.intent_classifier.Groq")
def test_get_intent_classifier_creates_instance(self, mock_groq):
classifier = get_intent_classifier()
assert classifier is not None
assert isinstance(classifier, IntentClassifier)
@patch("src.agent.intent_classifier.Groq")
def test_get_intent_classifier_returns_same_instance(self, mock_groq):
classifier1 = get_intent_classifier()
classifier2 = get_intent_classifier()
assert classifier1 is classifier2
@patch("src.agent.intent_classifier.Groq")
def test_reset_intent_classifier(self, mock_groq):
classifier1 = get_intent_classifier()
reset_intent_classifier()
classifier2 = get_intent_classifier()
assert classifier1 is not classifier2
class TestIntentClassifierEdgeCases:
# Tests for edge cases and confidence thresholds
@pytest.fixture
def classifier(self):
with patch("src.agent.intent_classifier.Groq") as mock_groq_class:
mock_client = Mock()
mock_groq_class.return_value = mock_client
classifier = IntentClassifier(api_key="test_key", confidence_threshold=0.7)
classifier._client = mock_client
return classifier
def test_classify_empty_query(self, classifier):
# Empty query should return AMBIGUOUS with requires_clarification
result = classifier.classify("")
assert result.intent == IntentType.AMBIGUOUS
assert result.requires_clarification is True
assert result.confidence == 1.0
def test_classify_whitespace_only_query(self, classifier):
result = classifier.classify(" ")
assert result.intent == IntentType.AMBIGUOUS
assert result.requires_clarification is True
def test_classify_with_available_tables(self, classifier):
# Verify available_tables is included in prompt
mock_response = Mock()
mock_response.choices = [
Mock(message=Mock(content='{"intent": "DATABASE_QUERY", "confidence": 0.9, "reasoning": "Tables hint", "suggested_tables": ["CreditFileVersion"]}'))
]
classifier._client.chat.completions.create.return_value = mock_response
result = classifier.classify(
"Kac kredi dosyasi var?",
available_tables=["CreditFileVersion", "CreditFileElement", "Contract"],
)
assert result.intent == IntentType.DATABASE_QUERY
# Verify the LLM was called with tables context
call_args = classifier._client.chat.completions.create.call_args
user_msg = call_args[1]["messages"][1]["content"]
assert "CreditFileVersion" in user_msg
def test_low_confidence_sets_requires_clarification(self, classifier):
mock_response = Mock()
mock_response.choices = [
Mock(message=Mock(content='{"intent": "DATABASE_QUERY", "confidence": 0.4, "reasoning": "Not sure"}'))
]
classifier._client.chat.completions.create.return_value = mock_response
result = classifier.classify("bilgi ver")
assert result.confidence == 0.4
assert result.requires_clarification is True
def test_high_confidence_no_clarification(self, classifier):
mock_response = Mock()
mock_response.choices = [
Mock(message=Mock(content='{"intent": "DOCUMENT_QUERY", "confidence": 0.95, "reasoning": "Clear question"}'))
]
classifier._client.chat.completions.create.return_value = mock_response
result = classifier.classify("Atlas ERP nedir?")
assert result.confidence == 0.95
assert result.requires_clarification is False
def test_classify_json_with_code_fence(self, classifier):
# Test that JSON wrapped in code fences is parsed correctly
mock_response = Mock()
mock_response.choices = [
Mock(message=Mock(content='```json\n{"intent": "CONVERSATIONAL", "confidence": 0.9, "reasoning": "Greeting"}\n```'))
]
classifier._client.chat.completions.create.return_value = mock_response
result = classifier.classify("Merhaba!")
assert result.intent == IntentType.CONVERSATIONAL
def test_classify_batch(self, classifier):
mock_response = Mock()
mock_response.choices = [
Mock(message=Mock(content='{"intent": "DOCUMENT_QUERY", "confidence": 0.8, "reasoning": "test"}'))
]
classifier._client.chat.completions.create.return_value = mock_response
results = classifier.classify_batch(["q1", "q2", "q3"])
assert len(results) == 3
assert all(isinstance(r, ClassificationResult) for r in results)
def test_classification_time_recorded(self, classifier):
mock_response = Mock()
mock_response.choices = [
Mock(message=Mock(content='{"intent": "DOCUMENT_QUERY", "confidence": 0.8, "reasoning": "test"}'))
]
classifier._client.chat.completions.create.return_value = mock_response
result = classifier.classify("test query")
assert result.classification_time_ms >= 0