File size: 15,874 Bytes
a963d65
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
#!/usr/bin/env python3
"""
πŸ” Mistral API Connectivity Diagnostic Tool
Standalone tool to debug and isolate Mistral OCR API issues
"""

import os
import sys
import json
import time
import base64
import socket
import asyncio
from datetime import datetime
from typing import Dict, Any, Optional

try:
    import httpx
    import ssl
except ImportError:
    print("❌ Missing dependencies. Install with: pip install httpx")
    sys.exit(1)

class MistralConnectivityTester:
    """Comprehensive Mistral API connectivity and authentication tester"""
    
    def __init__(self):
        self.api_key = os.getenv("MISTRAL_API_KEY")
        self.api_base = "https://api.mistral.ai"
        self.test_results = {
            "timestamp": datetime.now().isoformat(),
            "environment": "container" if os.getenv("CONTAINER_MODE") else "host",
            "tests": {}
        }
        
    def log_test(self, test_name: str, success: bool, details: Dict[str, Any], error: str = None):
        """Log test results with detailed information"""
        self.test_results["tests"][test_name] = {
            "success": success,
            "details": details,
            "error": error,
            "timestamp": datetime.now().isoformat()
        }
        
        status = "βœ…" if success else "❌"
        print(f"{status} {test_name}: {details.get('summary', 'No summary')}")
        if error:
            print(f"   Error: {error}")
        if details.get("metrics"):
            for key, value in details["metrics"].items():
                print(f"   {key}: {value}")
        print()

    def test_environment_variables(self) -> bool:
        """Test 1: Environment Variable Validation"""
        print("πŸ”§ Testing Environment Variables...")
        
        details = {
            "summary": "Environment variable validation",
            "api_key_present": bool(self.api_key),
            "api_key_format": "valid" if self.api_key and len(self.api_key) > 20 else "invalid",
            "container_mode": os.getenv("CONTAINER_MODE", "false"),
            "use_mistral_fallback": os.getenv("USE_MISTRAL_FALLBACK", "false"),
            "python_version": sys.version,
            "environment_vars": {
                "MISTRAL_API_KEY": "present" if self.api_key else "missing",
                "USE_MISTRAL_FALLBACK": os.getenv("USE_MISTRAL_FALLBACK", "not_set"),
                "PYTHONPATH": os.getenv("PYTHONPATH", "not_set")
            }
        }
        
        success = bool(self.api_key) and len(self.api_key) > 20
        error = None if success else "MISTRAL_API_KEY missing or invalid format"
        
        self.log_test("Environment Variables", success, details, error)
        return success

    async def test_dns_resolution(self) -> bool:
        """Test 2: DNS Resolution"""
        print("🌐 Testing DNS Resolution...")
        
        start_time = time.time()
        try:
            # Test DNS resolution for Mistral API
            host = "api.mistral.ai"
            addresses = socket.getaddrinfo(host, 443, socket.AF_UNSPEC, socket.SOCK_STREAM)
            resolution_time = time.time() - start_time
            
            details = {
                "summary": f"DNS resolution successful ({resolution_time:.3f}s)",
                "host": host,
                "resolved_addresses": [addr[4][0] for addr in addresses],
                "metrics": {
                    "resolution_time": f"{resolution_time:.3f}s",
                    "address_count": len(addresses)
                }
            }
            
            self.log_test("DNS Resolution", True, details)
            return True
            
        except Exception as e:
            details = {
                "summary": "DNS resolution failed",
                "host": "api.mistral.ai",
                "metrics": {
                    "resolution_time": f"{time.time() - start_time:.3f}s"
                }
            }
            
            self.log_test("DNS Resolution", False, details, str(e))
            return False

    async def test_https_connectivity(self) -> bool:
        """Test 3: HTTPS Connectivity"""
        print("πŸ”— Testing HTTPS Connectivity...")
        
        start_time = time.time()
        try:
            async with httpx.AsyncClient(timeout=30.0) as client:
                response = await client.get(f"{self.api_base}/")
                connection_time = time.time() - start_time
                
                details = {
                    "summary": f"HTTPS connection successful ({connection_time:.3f}s)",
                    "status_code": response.status_code,
                    "response_headers": dict(response.headers),
                    "metrics": {
                        "connection_time": f"{connection_time:.3f}s",
                        "status_code": response.status_code
                    }
                }
                
                success = response.status_code in [200, 404, 405]  # Any valid HTTP response
                error = None if success else f"Unexpected status code: {response.status_code}"
                
                self.log_test("HTTPS Connectivity", success, details, error)
                return success
                
        except Exception as e:
            details = {
                "summary": "HTTPS connection failed",
                "url": f"{self.api_base}/",
                "metrics": {
                    "connection_time": f"{time.time() - start_time:.3f}s"
                }
            }
            
            self.log_test("HTTPS Connectivity", False, details, str(e))
            return False

    async def test_api_authentication(self) -> bool:
        """Test 4: API Authentication"""
        print("πŸ” Testing API Authentication...")
        
        if not self.api_key:
            details = {"summary": "Cannot test authentication - no API key"}
            self.log_test("API Authentication", False, details, "MISTRAL_API_KEY not available")
            return False
        
        start_time = time.time()
        try:
            headers = {
                "Authorization": f"Bearer {self.api_key}",
                "Content-Type": "application/json"
            }
            
            # Test with a minimal valid request to check authentication
            test_payload = {
                "model": "pixtral-12b-2409",
                "messages": [
                    {
                        "role": "user",
                        "content": [
                            {
                                "type": "text",
                                "text": "Hello"
                            }
                        ]
                    }
                ],
                "max_tokens": 10
            }
            
            async with httpx.AsyncClient(timeout=30.0) as client:
                response = await client.post(
                    f"{self.api_base}/v1/chat/completions",
                    headers=headers,
                    json=test_payload
                )
                
                auth_time = time.time() - start_time
                
                details = {
                    "summary": f"Authentication test completed ({auth_time:.3f}s)",
                    "status_code": response.status_code,
                    "api_key_format": f"sk-...{self.api_key[-4:]}" if self.api_key else "none",
                    "metrics": {
                        "auth_time": f"{auth_time:.3f}s",
                        "status_code": response.status_code
                    }
                }
                
                if response.status_code == 200:
                    # Successfully authenticated and got a response
                    success = True
                    error = None
                elif response.status_code == 401:
                    # Authentication failed
                    success = False
                    error = "Invalid API key - authentication failed"
                elif response.status_code == 429:
                    # Rate limited but API key is valid
                    success = True  # Auth is working, just rate limited
                    error = None
                    details["summary"] = "Authentication successful (rate limited)"
                else:
                    # Other error
                    try:
                        error_data = response.json()
                        error = f"API error: {error_data.get('message', response.text)}"
                    except:
                        error = f"HTTP {response.status_code}: {response.text}"
                    success = False
                
                self.log_test("API Authentication", success, details, error)
                return success
                
        except Exception as e:
            details = {
                "summary": "Authentication test failed",
                "api_key_format": f"sk-...{self.api_key[-4:]}" if self.api_key else "none",
                "metrics": {
                    "auth_time": f"{time.time() - start_time:.3f}s"
                }
            }
            
            self.log_test("API Authentication", False, details, str(e))
            return False

    async def test_ocr_api_call(self) -> bool:
        """Test 5: Simple OCR API Call"""
        print("πŸ“„ Testing OCR API Call...")
        
        if not self.api_key:
            details = {"summary": "Cannot test OCR - no API key"}
            self.log_test("OCR API Call", False, details, "MISTRAL_API_KEY not available")
            return False
        
        start_time = time.time()
        try:
            # Create a minimal test image (1x1 white pixel PNG)
            test_image_b64 = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg=="
            
            headers = {
                "Authorization": f"Bearer {self.api_key}",
                "Content-Type": "application/json"
            }
            
            payload = {
                "model": "pixtral-12b-2409",
                "messages": [
                    {
                        "role": "user",
                        "content": [
                            {
                                "type": "text",
                                "text": "Extract text from this image. If no text is found, respond with 'NO_TEXT_FOUND'."
                            },
                            {
                                "type": "image_url",
                                "image_url": {
                                    "url": f"data:image/png;base64,{test_image_b64}"
                                }
                            }
                        ]
                    }
                ],
                "max_tokens": 100
            }
            
            async with httpx.AsyncClient(timeout=60.0) as client:
                response = await client.post(
                    f"{self.api_base}/v1/chat/completions",
                    headers=headers,
                    json=payload
                )
                
                ocr_time = time.time() - start_time
                
                details = {
                    "summary": f"OCR API call completed ({ocr_time:.3f}s)",
                    "status_code": response.status_code,
                    "request_size": len(json.dumps(payload)),
                    "metrics": {
                        "ocr_time": f"{ocr_time:.3f}s",
                        "status_code": response.status_code,
                        "payload_size": f"{len(json.dumps(payload))} bytes"
                    }
                }
                
                if response.status_code == 200:
                    try:
                        result = response.json()
                        content = result.get("choices", [{}])[0].get("message", {}).get("content", "")
                        details["response_content"] = content[:200] + "..." if len(content) > 200 else content
                        details["summary"] = f"OCR successful ({ocr_time:.3f}s)"
                        success = True
                        error = None
                    except Exception as parse_error:
                        success = False
                        error = f"Failed to parse response: {parse_error}"
                else:
                    try:
                        error_data = response.json()
                        error = f"API error: {error_data.get('message', response.text)}"
                    except:
                        error = f"HTTP {response.status_code}: {response.text}"
                    success = False
                
                self.log_test("OCR API Call", success, details, error)
                return success
                
        except Exception as e:
            details = {
                "summary": "OCR API call failed",
                "metrics": {
                    "ocr_time": f"{time.time() - start_time:.3f}s"
                }
            }
            
            self.log_test("OCR API Call", False, details, str(e))
            return False

    async def run_all_tests(self) -> Dict[str, Any]:
        """Run all connectivity tests"""
        print("πŸ” Mistral API Connectivity Diagnostic Tool")
        print("=" * 50)
        
        # Run tests sequentially
        test_1 = self.test_environment_variables()
        test_2 = await self.test_dns_resolution()
        test_3 = await self.test_https_connectivity()
        test_4 = await self.test_api_authentication()
        test_5 = await self.test_ocr_api_call()
        
        # Summary
        total_tests = 5
        passed_tests = sum([test_1, test_2, test_3, test_4, test_5])
        
        print("=" * 50)
        print(f"πŸ“Š Test Summary: {passed_tests}/{total_tests} tests passed")
        
        if passed_tests == total_tests:
            print("βœ… All tests passed - Mistral OCR API is fully functional!")
        elif passed_tests >= 3:
            print("⚠️  Some tests failed - Mistral OCR may work with limitations")
        else:
            print("❌ Multiple tests failed - Mistral OCR likely won't work")
        
        # Add summary to results
        self.test_results["summary"] = {
            "total_tests": total_tests,
            "passed_tests": passed_tests,
            "success_rate": f"{(passed_tests/total_tests)*100:.1f}%",
            "overall_status": "success" if passed_tests == total_tests else "partial" if passed_tests >= 3 else "failure"
        }
        
        return self.test_results

    def save_results(self, filename: str = None):
        """Save test results to JSON file"""
        if not filename:
            timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
            env = self.test_results["environment"]
            filename = f"mistral_connectivity_test_{env}_{timestamp}.json"
        
        with open(filename, 'w') as f:
            json.dump(self.test_results, f, indent=2)
        
        print(f"πŸ“„ Test results saved to: {filename}")

async def main():
    """Main entry point"""
    print("Starting Mistral API connectivity diagnostics...")
    
    tester = MistralConnectivityTester()
    results = await tester.run_all_tests()
    
    # Save results
    tester.save_results()
    
    # Exit with appropriate code
    overall_status = results["summary"]["overall_status"]
    if overall_status == "success":
        sys.exit(0)
    elif overall_status == "partial":
        sys.exit(1)
    else:
        sys.exit(2)

if __name__ == "__main__":
    try:
        asyncio.run(main())
    except KeyboardInterrupt:
        print("\n❌ Test interrupted by user")
        sys.exit(3)
    except Exception as e:
        print(f"\n❌ Unexpected error: {e}")
        sys.exit(4)