Spaces:
Running
Running
| """ | |
| Test script for LLM inference API endpoints. | |
| Run the server first: | |
| python app.py | |
| Then in another terminal: | |
| python test_llm_api.py | |
| """ | |
| import requests | |
| import json | |
| from typing import Dict, Any | |
| BASE_URL = "http://localhost:7860" | |
| def test_health() -> None: | |
| """Test the health endpoint.""" | |
| print("\n=== Testing GET /api/v1/llm/health ===") | |
| response = requests.get(f"{BASE_URL}/api/v1/llm/health") | |
| print(f"Status: {response.status_code}") | |
| print(f"Response: {json.dumps(response.json(), indent=2)}") | |
| assert response.status_code == 200 or response.status_code == 503 | |
| def test_chat_simple() -> None: | |
| """Test basic chat without context.""" | |
| print("\n=== Testing POST /api/v1/llm/chat (simple) ===") | |
| payload = { | |
| "content": "What is drip irrigation?", | |
| "max_tokens": 150, | |
| "temperature": 0.7, | |
| } | |
| response = requests.post(f"{BASE_URL}/api/v1/llm/chat", json=payload) | |
| print(f"Status: {response.status_code}") | |
| data = response.json() | |
| print(f"Model: {data.get('model')}") | |
| print(f"Content: {data.get('content')}") | |
| print(f"Latency (ms): {data.get('latency_ms')}") | |
| assert response.status_code == 200 or response.status_code == 500 | |
| def test_chat_with_context() -> None: | |
| """Test chat with farm context.""" | |
| print("\n=== Testing POST /api/v1/llm/chat (with farm context) ===") | |
| payload = { | |
| "content": "How much water should I apply weekly?", | |
| "farm_context": { | |
| "farm_name": "Johnson Farm", | |
| "crop": "tomato", | |
| "area_ha": 2.5, | |
| }, | |
| "max_tokens": 200, | |
| "temperature": 0.5, | |
| } | |
| response = requests.post(f"{BASE_URL}/api/v1/llm/chat", json=payload) | |
| print(f"Status: {response.status_code}") | |
| data = response.json() | |
| print(f"Model: {data.get('model')}") | |
| print(f"Content: {data.get('content')}") | |
| print(f"Latency (ms): {data.get('latency_ms')}") | |
| print(f"Metadata: {json.dumps(data.get('metadata'), indent=2)}") | |
| assert response.status_code == 200 or response.status_code == 500 | |
| def test_chat_with_history() -> None: | |
| """Test chat with conversation history.""" | |
| print("\n=== Testing POST /api/v1/llm/chat (with history) ===") | |
| payload = { | |
| "content": "What about pepper crops?", | |
| "conversation_history": [ | |
| {"role": "user", "content": "How much water should I apply weekly?"}, | |
| {"role": "assistant", "content": "For tomatoes, 25-40mm per week is typical."}, | |
| ], | |
| "farm_context": { | |
| "farm_name": "Johnson Farm", | |
| "crop": "pepper", | |
| "area_ha": 1.5, | |
| }, | |
| "max_tokens": 200, | |
| } | |
| response = requests.post(f"{BASE_URL}/api/v1/llm/chat", json=payload) | |
| print(f"Status: {response.status_code}") | |
| data = response.json() | |
| print(f"Content: {data.get('content')}") | |
| print(f"Conversation history length: {data.get('metadata', {}).get('conversation_history_length')}") | |
| assert response.status_code == 200 or response.status_code == 500 | |
| def test_validate_context_valid() -> None: | |
| """Test context validation with valid data.""" | |
| print("\n=== Testing POST /api/v1/llm/validate-context (valid) ===") | |
| payload = { | |
| "farm_context": { | |
| "farm_name": "Smith Farm", | |
| "crop": "lettuce", | |
| "area_ha": 0.5, | |
| } | |
| } | |
| response = requests.post(f"{BASE_URL}/api/v1/llm/validate-context", json=payload) | |
| print(f"Status: {response.status_code}") | |
| data = response.json() | |
| print(f"Valid: {data.get('valid')}") | |
| print(f"Warnings: {data.get('warnings')}") | |
| print(f"Errors: {data.get('errors')}") | |
| assert response.status_code == 200 | |
| assert data.get('valid') is True | |
| def test_validate_context_invalid_crop() -> None: | |
| """Test context validation with invalid crop.""" | |
| print("\n=== Testing POST /api/v1/llm/validate-context (invalid crop) ===") | |
| payload = { | |
| "farm_context": { | |
| "farm_name": "Smith Farm", | |
| "crop": "invalid_crop", | |
| "area_ha": 0.5, | |
| } | |
| } | |
| response = requests.post(f"{BASE_URL}/api/v1/llm/validate-context", json=payload) | |
| print(f"Status: {response.status_code}") | |
| data = response.json() | |
| print(f"Valid: {data.get('valid')}") | |
| print(f"Warnings: {data.get('warnings')}") | |
| assert response.status_code == 200 | |
| # Should still be valid (warnings don't fail validation) | |
| assert data.get('valid') is True | |
| def test_validate_context_invalid_area() -> None: | |
| """Test context validation with invalid area type.""" | |
| print("\n=== Testing POST /api/v1/llm/validate-context (invalid area) ===") | |
| payload = { | |
| "farm_context": { | |
| "farm_name": "Smith Farm", | |
| "crop": "tomato", | |
| "area_ha": "not a number", | |
| } | |
| } | |
| response = requests.post(f"{BASE_URL}/api/v1/llm/validate-context", json=payload) | |
| print(f"Status: {response.status_code}") | |
| data = response.json() | |
| print(f"Valid: {data.get('valid')}") | |
| print(f"Errors: {data.get('errors')}") | |
| assert response.status_code == 200 | |
| assert data.get('valid') is False | |
| def test_batch_chat() -> None: | |
| """Test batch chat endpoint.""" | |
| print("\n=== Testing POST /api/v1/llm/chat/batch ===") | |
| payload = [ | |
| { | |
| "content": "What is drip irrigation?", | |
| "max_tokens": 100, | |
| }, | |
| { | |
| "content": "How do I install valves?", | |
| "max_tokens": 100, | |
| }, | |
| { | |
| "content": "What is emitter spacing?", | |
| "max_tokens": 100, | |
| "farm_context": { | |
| "crop": "tomato", | |
| "area_ha": 1.0, | |
| } | |
| }, | |
| ] | |
| response = requests.post(f"{BASE_URL}/api/v1/llm/chat/batch", json=payload) | |
| print(f"Status: {response.status_code}") | |
| if response.status_code == 200: | |
| data = response.json() | |
| print(f"Number of responses: {len(data)}") | |
| for i, resp in enumerate(data): | |
| print(f"\nResponse {i+1}:") | |
| print(f" Content: {resp.get('content')[:100]}...") | |
| print(f" Latency: {resp.get('latency_ms'):.1f}ms") | |
| def test_chat_invalid_request() -> None: | |
| """Test chat with invalid request (missing required field).""" | |
| print("\n=== Testing POST /api/v1/llm/chat (invalid request) ===") | |
| payload = { | |
| "max_tokens": 100, | |
| # Missing required 'content' field | |
| } | |
| response = requests.post(f"{BASE_URL}/api/v1/llm/chat", json=payload) | |
| print(f"Status: {response.status_code}") | |
| print(f"Response: {json.dumps(response.json(), indent=2)}") | |
| assert response.status_code == 422 | |
| def test_chat_oversized_request() -> None: | |
| """Test chat with oversized content.""" | |
| print("\n=== Testing POST /api/v1/llm/chat (oversized content) ===") | |
| payload = { | |
| "content": "x" * 3000, # Exceeds max_length of 2000 | |
| } | |
| response = requests.post(f"{BASE_URL}/api/v1/llm/chat", json=payload) | |
| print(f"Status: {response.status_code}") | |
| print(f"Response: {json.dumps(response.json(), indent=2)}") | |
| assert response.status_code == 422 | |
| if __name__ == "__main__": | |
| print("=" * 70) | |
| print("LLM Inference API Test Suite") | |
| print("=" * 70) | |
| try: | |
| # Health check first | |
| test_health() | |
| # Basic tests | |
| test_chat_simple() | |
| test_chat_with_context() | |
| test_chat_with_history() | |
| # Context validation tests | |
| test_validate_context_valid() | |
| test_validate_context_invalid_crop() | |
| test_validate_context_invalid_area() | |
| # Batch test | |
| test_batch_chat() | |
| # Error handling tests | |
| test_chat_invalid_request() | |
| test_chat_oversized_request() | |
| print("\n" + "=" * 70) | |
| print("✅ All tests completed!") | |
| print("=" * 70) | |
| except requests.exceptions.ConnectionError: | |
| print("\n❌ Error: Could not connect to server.") | |
| print(" Make sure the server is running: python app.py") | |
| except Exception as e: | |
| print(f"\n❌ Unexpected error: {e}") | |
| import traceback | |
| traceback.print_exc() | |