DocUA commited on
Commit
3e63a13
·
1 Parent(s): 60a364a

Refactor httpx client usage for OpenAI and DeepSeek: implement context management to prevent socket leaks and improve error logging

Browse files
Files changed (2) hide show
  1. app.py +11 -2
  2. main.py +52 -35
app.py CHANGED
@@ -4,8 +4,14 @@ Hugging Face Spaces entry point for Legal Position AI Analyzer
4
  """
5
  import os
6
  import sys
 
 
7
  from pathlib import Path
8
 
 
 
 
 
9
  # Set environment for Hugging Face Spaces
10
  os.environ['GRADIO_SERVER_NAME'] = '0.0.0.0'
11
  os.environ['GRADIO_SERVER_PORT'] = '7860'
@@ -72,14 +78,17 @@ def run_network_diagnostics():
72
  except Exception as e:
73
  print(f" ❌ {name} ({url}) -> {type(e).__name__}: {e}")
74
 
75
- # Check httpx (used by anthropic SDK)
76
  print("\n🔧 httpx connectivity test:")
77
  try:
78
  import httpx
79
  print(f" httpx version: {httpx.__version__}")
80
  with httpx.Client(timeout=10) as client:
81
  resp = client.get("https://api.anthropic.com")
82
- print(f" ✅ httpx -> HTTP {resp.status_code}")
 
 
 
83
  except Exception as e:
84
  print(f" ❌ httpx -> {type(e).__name__}: {e}")
85
 
 
4
  """
5
  import os
6
  import sys
7
+ import warnings
8
+ import logging
9
  from pathlib import Path
10
 
11
+ # Suppress asyncio event loop __del__ warnings (known Python 3.11 issue in threaded envs)
12
+ warnings.filterwarnings("ignore", message=".*Invalid file descriptor.*")
13
+ logging.getLogger("asyncio").setLevel(logging.CRITICAL)
14
+
15
  # Set environment for Hugging Face Spaces
16
  os.environ['GRADIO_SERVER_NAME'] = '0.0.0.0'
17
  os.environ['GRADIO_SERVER_PORT'] = '7860'
 
78
  except Exception as e:
79
  print(f" ❌ {name} ({url}) -> {type(e).__name__}: {e}")
80
 
81
+ # Check httpx (used by anthropic/openai SDKs)
82
  print("\n🔧 httpx connectivity test:")
83
  try:
84
  import httpx
85
  print(f" httpx version: {httpx.__version__}")
86
  with httpx.Client(timeout=10) as client:
87
  resp = client.get("https://api.anthropic.com")
88
+ print(f" ✅ httpx -> Anthropic HTTP {resp.status_code}")
89
+ with httpx.Client(timeout=10) as client:
90
+ resp = client.get("https://api.openai.com")
91
+ print(f" ✅ httpx -> OpenAI HTTP {resp.status_code}")
92
  except Exception as e:
93
  print(f" ❌ httpx -> {type(e).__name__}: {e}")
94
 
main.py CHANGED
@@ -682,23 +682,23 @@ def generate_legal_position(
682
  raise Exception(f"Текст судового рішення занадто короткий або відсутній (довжина: {len(court_decision_text) if court_decision_text else 0} символів). Будь ласка, перевірте вхідні дані.")
683
 
684
  if provider == ModelProvider.OPENAI.value:
685
- # Use custom httpx client to avoid async loop issues on HF Spaces
686
- # This is critical for stable connection in threaded environments like Gradio
687
- http_client = httpx.Client(
688
- timeout=120.0,
689
- transport=httpx.HTTPTransport(retries=3)
690
- )
691
- client = OpenAI(
692
- api_key=OPENAI_API_KEY,
693
- http_client=http_client
694
- )
695
-
696
- # Retry logic for connection errors
697
- max_retries = 3
698
- last_error = None
699
- response = None
700
-
701
  try:
 
 
 
 
 
 
 
 
 
 
 
 
 
702
  print(f"[DEBUG] OpenAI Generation - Model: {model_name}")
703
 
704
  # Check for reasoning models (gpt-4.1, o1, etc.)
@@ -739,7 +739,11 @@ def generate_legal_position(
739
  except Exception as api_err:
740
  last_error = api_err
741
  error_type = type(api_err).__name__
742
- print(f"[ERROR] OpenAI API attempt {attempt + 1} failed: {error_type}: {str(api_err)}")
 
 
 
 
743
  if attempt < max_retries - 1:
744
  wait_time = 2 ** attempt # 1, 2, 4 seconds
745
  print(f"[DEBUG] Retrying in {wait_time}s...")
@@ -763,29 +767,36 @@ def generate_legal_position(
763
  print(f"[ERROR] OpenAI generation/parsing failed: {e}")
764
  return {
765
  "title": "Автоматично сформований заголовок (OpenAI)",
766
- "text": response_text.strip() if 'response_text' in locals() and response_text else f"Помилка при отриманні відповіді: {str(e)}",
767
  "proceeding": "Не визначено",
768
  "category": "Помилка парсингу"
769
  }
 
 
 
 
 
 
770
 
771
  if provider == ModelProvider.DEEPSEEK.value:
772
- # Use custom httpx client for DeepSeek on HF Spaces
773
- http_client = httpx.Client(
774
- timeout=120.0,
775
- transport=httpx.HTTPTransport(retries=3)
776
- )
777
- client = OpenAI(
778
- api_key=DEEPSEEK_API_KEY,
779
- base_url="https://api.deepseek.com",
780
- http_client=http_client
781
- )
782
-
783
- # Retry logic for DeepSeek
784
- max_retries = 3
785
- last_error = None
786
- response = None
787
-
788
  try:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
789
  print(f"[DEBUG] DeepSeek Generation - Model: {model_name}")
790
 
791
  # Check for reasoning model (DeepSeek R1)
@@ -842,10 +853,16 @@ def generate_legal_position(
842
  print(f"[ERROR] DeepSeek generation/parsing failed: {e}")
843
  return {
844
  "title": "Автоматично сформований заголовок (DeepSeek)",
845
- "text": response_text.strip() if 'response_text' in locals() and response_text else f"Помилка при отриманні відповіді від DeepSeek: {str(e)}",
846
  "proceeding": "Не визначено",
847
  "category": "Помилка API/Парсингу"
848
  }
 
 
 
 
 
 
849
 
850
  elif provider == ModelProvider.ANTHROPIC.value:
851
  client = Anthropic(api_key=ANTHROPIC_API_KEY)
 
682
  raise Exception(f"Текст судового рішення занадто короткий або відсутній (довжина: {len(court_decision_text) if court_decision_text else 0} символів). Будь ласка, перевірте вхідні дані.")
683
 
684
  if provider == ModelProvider.OPENAI.value:
685
+ # Use context-managed httpx client to avoid socket/fd leaks in threaded Gradio env
686
+ http_client = None
687
+ response_text = None
 
 
 
 
 
 
 
 
 
 
 
 
 
688
  try:
689
+ http_client = httpx.Client(
690
+ timeout=httpx.Timeout(120.0, connect=30.0),
691
+ )
692
+ client = OpenAI(
693
+ api_key=OPENAI_API_KEY,
694
+ http_client=http_client
695
+ )
696
+
697
+ # Retry logic for connection errors
698
+ max_retries = 3
699
+ last_error = None
700
+ response = None
701
+
702
  print(f"[DEBUG] OpenAI Generation - Model: {model_name}")
703
 
704
  # Check for reasoning models (gpt-4.1, o1, etc.)
 
739
  except Exception as api_err:
740
  last_error = api_err
741
  error_type = type(api_err).__name__
742
+ error_detail = str(api_err)
743
+ # Log underlying cause for connection errors
744
+ if hasattr(api_err, '__cause__') and api_err.__cause__:
745
+ error_detail += f" | Cause: {type(api_err.__cause__).__name__}: {api_err.__cause__}"
746
+ print(f"[ERROR] OpenAI API attempt {attempt + 1} failed: {error_type}: {error_detail}")
747
  if attempt < max_retries - 1:
748
  wait_time = 2 ** attempt # 1, 2, 4 seconds
749
  print(f"[DEBUG] Retrying in {wait_time}s...")
 
767
  print(f"[ERROR] OpenAI generation/parsing failed: {e}")
768
  return {
769
  "title": "Автоматично сформований заголовок (OpenAI)",
770
+ "text": response_text.strip() if response_text else f"Помилка при отриманні відповіді: {str(e)}",
771
  "proceeding": "Не визначено",
772
  "category": "Помилка парсингу"
773
  }
774
+ finally:
775
+ if http_client:
776
+ try:
777
+ http_client.close()
778
+ except Exception:
779
+ pass
780
 
781
  if provider == ModelProvider.DEEPSEEK.value:
782
+ # Use context-managed httpx client for DeepSeek to avoid socket/fd leaks
783
+ http_client = None
784
+ response_text = None
 
 
 
 
 
 
 
 
 
 
 
 
 
785
  try:
786
+ http_client = httpx.Client(
787
+ timeout=httpx.Timeout(120.0, connect=30.0),
788
+ )
789
+ client = OpenAI(
790
+ api_key=DEEPSEEK_API_KEY,
791
+ base_url="https://api.deepseek.com",
792
+ http_client=http_client
793
+ )
794
+
795
+ # Retry logic for DeepSeek
796
+ max_retries = 3
797
+ last_error = None
798
+ response = None
799
+
800
  print(f"[DEBUG] DeepSeek Generation - Model: {model_name}")
801
 
802
  # Check for reasoning model (DeepSeek R1)
 
853
  print(f"[ERROR] DeepSeek generation/parsing failed: {e}")
854
  return {
855
  "title": "Автоматично сформований заголовок (DeepSeek)",
856
+ "text": response_text.strip() if response_text else f"Помилка при отриманні відповіді від DeepSeek: {str(e)}",
857
  "proceeding": "Не визначено",
858
  "category": "Помилка API/Парсингу"
859
  }
860
+ finally:
861
+ if http_client:
862
+ try:
863
+ http_client.close()
864
+ except Exception:
865
+ pass
866
 
867
  elif provider == ModelProvider.ANTHROPIC.value:
868
  client = Anthropic(api_key=ANTHROPIC_API_KEY)