Spaces:
Sleeping
Sleeping
| # 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 | |
| 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() | |
| def test_get_intent_classifier_creates_instance(self, mock_groq): | |
| classifier = get_intent_classifier() | |
| assert classifier is not None | |
| assert isinstance(classifier, IntentClassifier) | |
| def test_get_intent_classifier_returns_same_instance(self, mock_groq): | |
| classifier1 = get_intent_classifier() | |
| classifier2 = get_intent_classifier() | |
| assert classifier1 is classifier2 | |
| 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 | |
| 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 | |