Skip to content

Commit 9aef73f

Browse files
authored
03 classification api test (#304)
* test: add Classification API intent classification test Add e2e test for standalone Classification API service that validates the /api/v1/classify/intent endpoint correctly classifies different types of queries. Test validates: - Math queries are classified as 'math' - Computer science queries are classified as 'computer science' - Business queries are classified as 'business' - History queries are classified as 'history' - Batch classification endpoint processes multiple texts correctly The Classification API (port 8080) is a standalone service separate from the ExtProc router, providing direct classification capabilities for applications that need text classification without LLM routing. Test requirements: - Classification API must be running on port 8080 - Start with: make run-router-e2e Signed-off-by: Yossi Ovadia <[email protected]> * docs: update README with 03-classification-api-test Add 03-classification-api-test.py to the test suite documentation: - Add to test flow list as test #4 - Update numbering for remaining tests - Add to Available Tests section with usage example Signed-off-by: Yossi Ovadia <[email protected]> * style: apply pre-commit fixes to 03-classification-api-test.py - Apply black formatter: remove unnecessary parentheses - Fix end of file: remove extra blank line Signed-off-by: Yossi Ovadia <[email protected]> --------- Signed-off-by: Yossi Ovadia <[email protected]>
1 parent 5141a00 commit 9aef73f

File tree

2 files changed

+229
-4
lines changed

2 files changed

+229
-4
lines changed
Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
#!/usr/bin/env python3
2+
"""
3+
03-classification-api-test.py - Classification API tests
4+
5+
This test validates the standalone Classification API service,
6+
which provides direct classification capabilities without LLM routing.
7+
The API is separate from the ExtProc router and runs on port 8080.
8+
"""
9+
10+
import json
11+
import sys
12+
import unittest
13+
14+
import requests
15+
16+
# Import test base from same directory
17+
from test_base import SemanticRouterTestBase
18+
19+
# Constants
20+
CLASSIFICATION_API_URL = "http://localhost:8080"
21+
INTENT_ENDPOINT = "/api/v1/classify/intent"
22+
23+
# Test cases with expected categories based on config.e2e.yaml
24+
INTENT_TEST_CASES = [
25+
{
26+
"name": "Math Query",
27+
"text": "Solve the quadratic equation x^2 + 5x + 6 = 0",
28+
"expected_category": "math",
29+
},
30+
{
31+
"name": "Computer Science Query",
32+
"text": "Write a Python function to implement a linked list",
33+
"expected_category": "computer science",
34+
},
35+
{
36+
"name": "Business Query",
37+
"text": "What are the key principles of supply chain management?",
38+
"expected_category": "business",
39+
},
40+
{
41+
"name": "History Query",
42+
"text": "Describe the main causes of World War I",
43+
"expected_category": "history",
44+
},
45+
]
46+
47+
48+
class ClassificationAPITest(SemanticRouterTestBase):
49+
"""Test the standalone Classification API service."""
50+
51+
def setUp(self):
52+
"""Check if the Classification API is running before running tests."""
53+
self.print_test_header(
54+
"Setup Check",
55+
"Verifying that Classification API is running and accepting requests",
56+
)
57+
58+
try:
59+
# Test health endpoint
60+
health_response = requests.get(
61+
f"{CLASSIFICATION_API_URL}/health", timeout=5
62+
)
63+
64+
if health_response.status_code != 200:
65+
self.skipTest(
66+
f"Classification API health check failed: {health_response.status_code}"
67+
)
68+
69+
self.print_response_info(
70+
health_response, {"Service": "Classification API Health"}
71+
)
72+
73+
except requests.exceptions.ConnectionError:
74+
self.skipTest(
75+
"Cannot connect to Classification API on port 8080. Is it running? Start with: make run-router-e2e"
76+
)
77+
except requests.exceptions.Timeout:
78+
self.skipTest("Classification API health check timed out")
79+
80+
def test_intent_classification(self):
81+
"""Test that intent classification returns correct categories for different query types."""
82+
self.print_test_header(
83+
"Intent Classification Test",
84+
"Verifies that Classification API correctly classifies different query types",
85+
)
86+
87+
for test_case in INTENT_TEST_CASES:
88+
self.print_subtest_header(test_case["name"])
89+
90+
payload = {
91+
"text": test_case["text"],
92+
"options": {"return_probabilities": False},
93+
}
94+
95+
self.print_request_info(
96+
payload=payload,
97+
expectations=f"Expect: Correctly classified as '{test_case['expected_category']}'",
98+
)
99+
100+
response = requests.post(
101+
f"{CLASSIFICATION_API_URL}{INTENT_ENDPOINT}",
102+
headers={"Content-Type": "application/json"},
103+
json=payload,
104+
timeout=10,
105+
)
106+
107+
response_json = response.json()
108+
# The response may be nested in "classification" or at top level
109+
if "classification" in response_json:
110+
classification = response_json["classification"]
111+
actual_category = classification.get("category", "unknown")
112+
confidence = classification.get("confidence", 0.0)
113+
else:
114+
actual_category = response_json.get("category", "unknown")
115+
confidence = response_json.get("confidence", 0.0)
116+
117+
# Check if classification is correct
118+
category_correct = actual_category == test_case["expected_category"]
119+
is_placeholder = actual_category == "general"
120+
passed = response.status_code == 200 and category_correct
121+
122+
self.print_response_info(
123+
response,
124+
{
125+
"Expected Category": test_case["expected_category"],
126+
"Actual Category": actual_category,
127+
"Confidence": f"{confidence:.2f}",
128+
"Is Placeholder": "⚠️ Yes" if is_placeholder else "No",
129+
"Category Match": "✅" if category_correct else "❌",
130+
},
131+
)
132+
133+
if not category_correct:
134+
if is_placeholder:
135+
failure_message = f"Classification failed: returned placeholder 'general' instead of '{test_case['expected_category']}'"
136+
else:
137+
failure_message = (
138+
f"Classification incorrect: expected '{test_case['expected_category']}', "
139+
f"got '{actual_category}'"
140+
)
141+
else:
142+
failure_message = None
143+
144+
self.print_test_result(
145+
passed=passed,
146+
message=(
147+
f"Correctly classified as '{actual_category}'"
148+
if passed
149+
else failure_message
150+
),
151+
)
152+
153+
self.assertEqual(
154+
response.status_code,
155+
200,
156+
f"Request failed with status {response.status_code}",
157+
)
158+
159+
self.assertEqual(
160+
actual_category,
161+
test_case["expected_category"],
162+
f"{test_case['name']}: Expected category '{test_case['expected_category']}', got '{actual_category}'",
163+
)
164+
165+
def test_batch_classification(self):
166+
"""Test batch classification endpoint works correctly."""
167+
self.print_test_header(
168+
"Batch Classification Test",
169+
"Verifies that batch classification endpoint processes multiple texts correctly",
170+
)
171+
172+
texts = [tc["text"] for tc in INTENT_TEST_CASES]
173+
expected_categories = [tc["expected_category"] for tc in INTENT_TEST_CASES]
174+
175+
payload = {"texts": texts, "task_type": "intent"}
176+
177+
self.print_request_info(
178+
payload={"texts": f"{len(texts)} texts", "task_type": "intent"},
179+
expectations=f"Expect: {len(texts)} classifications matching expected categories",
180+
)
181+
182+
response = requests.post(
183+
f"{CLASSIFICATION_API_URL}/api/v1/classify/batch",
184+
headers={"Content-Type": "application/json"},
185+
json=payload,
186+
timeout=30,
187+
)
188+
189+
response_json = response.json()
190+
results = response_json.get("results", [])
191+
192+
self.print_response_info(
193+
response,
194+
{
195+
"Total Texts": len(texts),
196+
"Results Count": len(results),
197+
"Processing Time (ms)": response_json.get("processing_time_ms", 0),
198+
},
199+
)
200+
201+
passed = response.status_code == 200 and len(results) == len(texts)
202+
203+
self.print_test_result(
204+
passed=passed,
205+
message=(
206+
f"Successfully classified {len(results)} texts"
207+
if passed
208+
else f"Batch classification failed or returned wrong count"
209+
),
210+
)
211+
212+
self.assertEqual(response.status_code, 200, "Batch request failed")
213+
self.assertEqual(len(results), len(texts), "Result count mismatch")
214+
215+
216+
if __name__ == "__main__":
217+
unittest.main()

e2e-tests/README.md

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,21 +21,27 @@ This test suite provides a progressive approach to testing the Semantic Router,
2121
- Tests classification consistency across identical requests
2222
- Validates metrics collection for classification operations
2323

24-
4. **03-model-routing-test.py** - TBD (To Be Developed)
24+
4. **03-classification-api-test.py** - Classification API tests ✅
25+
- Tests standalone Classification API service (port 8080)
26+
- Validates intent classification for different query types
27+
- Tests batch classification endpoint
28+
- Verifies classification accuracy without LLM routing
29+
30+
5. **04-model-routing-test.py** - TBD (To Be Developed)
2531
- Tests that requests are routed to the correct backend model
2632
- Verifies model header modifications
2733

28-
5. **04-cache-test.py** - TBD (To Be Developed)
34+
6. **04-cache-test.py** - TBD (To Be Developed)
2935
- Tests cache hit/miss behavior
3036
- Verifies similarity thresholds
3137
- Tests cache TTL
3238

33-
6. **05-e2e-category-test.py** - TBD (To Be Developed)
39+
7. **05-e2e-category-test.py** - TBD (To Be Developed)
3440
- Tests math queries route to the math-specialized model
3541
- Tests creative queries route to the creative-specialized model
3642
- Tests other domain-specific routing
3743

38-
7. **06-metrics-test.py** - TBD (To Be Developed)
44+
8. **06-metrics-test.py** - TBD (To Be Developed)
3945
- Tests Prometheus metrics endpoints
4046
- Verifies correct metrics are being recorded
4147

@@ -77,13 +83,15 @@ Currently implemented:
7783
- **00-client-request-test.py** ✅ - Complete client request validation and smart routing
7884
- **01-envoy-extproc-test.py** ✅ - Envoy ExtProc interaction and processing tests
7985
- **02-router-classification-test.py** ✅ - Router classification and model selection tests
86+
- **03-classification-api-test.py** ✅ - Standalone Classification API service tests
8087

8188
Individual tests can be run with:
8289

8390
```bash
8491
python e2e-tests/00-client-request-test.py
8592
python e2e-tests/01-envoy-extproc-test.py
8693
python e2e-tests/02-router-classification-test.py
94+
python e2e-tests/03-classification-api-test.py
8795
```
8896

8997
Or run all available tests with:

0 commit comments

Comments
 (0)