Ayona commited on
Commit
22df219
·
1 Parent(s): 601f5a4

handoff: harden config and align MVP surface

Browse files
Files changed (4) hide show
  1. README.md +14 -4
  2. app.py +7 -91
  3. config.py +3 -13
  4. interface.py +3 -3
README.md CHANGED
@@ -33,17 +33,19 @@ python_version: "3.11"
33
  - Можливість додавання коментарів для уточнення контексту
34
  - Автоматична класифікація за типом судочинства та категорією
35
 
36
- ### 🔍 Пошук схожих позицій
37
  - Інтелектуальний пошук релевантних правових позицій у базі даних
38
  - Пошук на основі згенерованої позиції або вхідного тексту
39
  - Гібридний підхід: векторний пошук + BM25
40
  - Відображення результатів з посиланнями на джерела
 
41
 
42
- ### ⚖️ Порівняльний аналіз
43
  - Детальний аналіз релевантності знайдених позицій
44
  - Виявлення спільних правових аспектів
45
  - Оцінка можливості застосування існуючих позицій
46
  - Обґрунтовані висновки щодо необхідності створення нової позиції
 
47
 
48
  ### ⚙️ Редагування промптів
49
  - Персональне налаштування промптів для AI
@@ -153,8 +155,10 @@ REDIS_PASSWORD=your_redis_password
153
 
154
  ### 4. Запуск додатку
155
 
 
 
156
  ```bash
157
- python main.py
158
  ```
159
 
160
  Додаток буде доступний за адресою: `http://localhost:7860`
@@ -320,6 +324,12 @@ redis:
320
 
321
  ## 🚀 Deployment
322
 
 
 
 
 
 
 
323
  ### Hugging Face Spaces
324
 
325
  1. Створіть новий Space на [Hugging Face](https://huggingface.co/spaces)
@@ -443,4 +453,4 @@ gunicorn main:app --workers 4 --bind 0.0.0.0:7860
443
 
444
  **Останнє оновлення:** 2026-01-03
445
 
446
- **Статус:** Production Ready
 
33
  - Можливість додавання коментарів для уточнення контексту
34
  - Автоматична класифікація за типом судочинства та категорією
35
 
36
+ ### 🔍 Пошук схожих позицій *(опціонально)*
37
  - Інтелектуальний пошук релевантних правових позицій у базі даних
38
  - Пошук на основі згенерованої позиції або вхідного тексту
39
  - Гібридний підхід: векторний пошук + BM25
40
  - Відображення результатів з посиланнями на джерела
41
+ - Якщо customer build поставляється без retrieval-інфраструктури, цей модуль може бути вимкнений
42
 
43
+ ### ⚖️ Порівняльний аналіз *(опціонально)*
44
  - Детальний аналіз релевантності знайдених позицій
45
  - Виявлення спільних правових аспектів
46
  - Оцінка можливості застосування існуючих позицій
47
  - Обґрунтовані висновки щодо необхідності створення нової позиції
48
+ - Не є критерієм приймання MVP, якщо це не зафіксовано окремо в ТЗ
49
 
50
  ### ⚙️ Редагування промптів
51
  - Персональне налаштування промптів для AI
 
155
 
156
  ### 4. Запуск додатку
157
 
158
+ Локальний запуск:
159
+
160
  ```bash
161
+ python app.py
162
  ```
163
 
164
  Додаток буде доступний за адресою: `http://localhost:7860`
 
324
 
325
  ## 🚀 Deployment
326
 
327
+ ### Deployment profiles
328
+
329
+ - **Customer MVP / handoff:** Generation + model/thinking/comment controls are the primary supported flow.
330
+ - **Optional modules:** Search and comparative analysis may be enabled only when retrieval indexes are available and accepted as in-scope.
331
+ - **Known limitation:** if retrieval uses `DocSA/legal-position-indexes`, that corpus is an older snapshot and may be stale.
332
+
333
  ### Hugging Face Spaces
334
 
335
  1. Створіть новий Space на [Hugging Face](https://huggingface.co/spaces)
 
453
 
454
  **Останнє оновлення:** 2026-01-03
455
 
456
+ **Статус:** 🟡 Handoff Candidate (generation-first MVP; optional retrieval modules require separate validation)
app.py CHANGED
@@ -24,98 +24,17 @@ def _suppress_asyncio_fd_errors(unraisable):
24
 
25
  sys.unraisablehook = _suppress_asyncio_fd_errors
26
 
27
- # Set environment for Hugging Face Spaces
28
- os.environ['GRADIO_SERVER_NAME'] = '0.0.0.0'
29
- os.environ['GRADIO_SERVER_PORT'] = '7860'
30
- # Avoid uvloop shutdown warnings on HF Spaces
31
- os.environ.setdefault('UVICORN_LOOP', 'asyncio')
32
 
33
- # Apply nest_asyncio only if needed (some Python versions have conflicts)
34
- # try:
35
- # import nest_asyncio
36
- # nest_asyncio.apply()
37
- # except Exception as e:
38
- # print(f"[WARNING] Could not apply nest_asyncio: {e}")
39
 
40
  # Add project root to Python path
41
  project_root = Path(__file__).parent
42
  sys.path.insert(0, str(project_root))
43
-
44
- # ============ Network Diagnostics ============
45
- def run_network_diagnostics():
46
- """Check outbound network connectivity from HF Spaces container."""
47
- import urllib.request
48
- import socket
49
-
50
- print("=" * 50)
51
- print("🔍 NETWORK DIAGNOSTICS")
52
- print("=" * 50)
53
-
54
- # Check proxy env vars
55
- proxy_vars = ['HTTP_PROXY', 'HTTPS_PROXY', 'http_proxy', 'https_proxy', 'NO_PROXY', 'no_proxy', 'ALL_PROXY']
56
- print("\n📡 Proxy environment variables:")
57
- for var in proxy_vars:
58
- val = os.environ.get(var)
59
- if val:
60
- print(f" {var} = {val}")
61
- if not any(os.environ.get(v) for v in proxy_vars):
62
- print(" (none set)")
63
-
64
- # Check DNS resolution
65
- hosts = ['api.anthropic.com', 'api.openai.com', 'generativelanguage.googleapis.com']
66
- print("\n🌐 DNS resolution:")
67
- for host in hosts:
68
- try:
69
- ip = socket.gethostbyname(host)
70
- print(f" ✅ {host} -> {ip}")
71
- except socket.gaierror as e:
72
- print(f" ❌ {host} -> DNS FAILED: {e}")
73
-
74
- # Check actual HTTP(S) connectivity
75
- print("\n🔌 HTTPS connectivity:")
76
- test_urls = [
77
- ('https://api.anthropic.com', 'Anthropic API'),
78
- ('https://api.openai.com', 'OpenAI API'),
79
- ('https://httpbin.org/get', 'httpbin (general internet)'),
80
- ]
81
- for url, name in test_urls:
82
- try:
83
- req = urllib.request.Request(url, method='HEAD')
84
- req.add_header('User-Agent', 'connectivity-test/1.0')
85
- resp = urllib.request.urlopen(req, timeout=10)
86
- print(f" ✅ {name} ({url}) -> HTTP {resp.status}")
87
- except urllib.error.HTTPError as e:
88
- # HTTP error means we CAN connect (just got an error response)
89
- print(f" ✅ {name} ({url}) -> HTTP {e.code} (connection OK, auth expected)")
90
- except Exception as e:
91
- print(f" ❌ {name} ({url}) -> {type(e).__name__}: {e}")
92
-
93
- # Check httpx (used by anthropic/openai SDKs)
94
- print("\n🔧 httpx connectivity test:")
95
- try:
96
- import httpx
97
- print(f" httpx version: {httpx.__version__}")
98
- # Test with default settings
99
- with httpx.Client(timeout=10) as client:
100
- resp = client.get("https://api.anthropic.com")
101
- print(f" ✅ httpx (default) -> Anthropic HTTP {resp.status_code}")
102
- # Test OpenAI with HTTP/2 disabled (avoids 421 Misdirected Request)
103
- with httpx.Client(timeout=10, http2=False) as client:
104
- resp = client.get("https://api.openai.com")
105
- print(f" ✅ httpx (http1.1) -> OpenAI HTTP {resp.status_code}")
106
- # Also test with default HTTP/2 to compare
107
- try:
108
- with httpx.Client(timeout=10) as client:
109
- resp = client.get("https://api.openai.com")
110
- print(f" ✅ httpx (default) -> OpenAI HTTP {resp.status_code}")
111
- except Exception as e2:
112
- print(f" ⚠️ httpx (default/http2) -> OpenAI FAILED: {type(e2).__name__}: {e2}")
113
- except Exception as e:
114
- print(f" ❌ httpx -> {type(e).__name__}: {e}")
115
-
116
- print("=" * 50)
117
-
118
- # ============ End Diagnostics ============
119
 
120
  # Import and launch interface
121
  from interface import create_gradio_interface
@@ -134,11 +53,8 @@ else:
134
  demo = create_gradio_interface()
135
 
136
  if __name__ == "__main__":
137
- # Run diagnostics only when executed directly
138
- run_network_diagnostics()
139
-
140
  print("🚀 Starting Legal Position AI Analyzer...")
141
-
142
  # Detect if running on HF Spaces or locally
143
  is_hf_space = os.environ.get('SPACE_ID') is not None
144
 
 
24
 
25
  sys.unraisablehook = _suppress_asyncio_fd_errors
26
 
27
+ def configure_runtime_environment() -> None:
28
+ """Apply runtime defaults for Hugging Face Spaces / Gradio deployment."""
29
+ os.environ.setdefault('GRADIO_SERVER_NAME', '0.0.0.0')
30
+ os.environ.setdefault('GRADIO_SERVER_PORT', '7860')
31
+ os.environ.setdefault('UVICORN_LOOP', 'asyncio')
32
 
 
 
 
 
 
 
33
 
34
  # Add project root to Python path
35
  project_root = Path(__file__).parent
36
  sys.path.insert(0, str(project_root))
37
+ configure_runtime_environment()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38
 
39
  # Import and launch interface
40
  from interface import create_gradio_interface
 
53
  demo = create_gradio_interface()
54
 
55
  if __name__ == "__main__":
 
 
 
56
  print("🚀 Starting Legal Position AI Analyzer...")
57
+
58
  # Detect if running on HF Spaces or locally
59
  is_hf_space = os.environ.get('SPACE_ID') is not None
60
 
config.py CHANGED
@@ -1,8 +1,11 @@
 
1
  import os
2
  from enum import Enum
3
  from pathlib import Path
4
  from dotenv import load_dotenv
5
 
 
 
6
  # Load environment variables
7
  load_dotenv()
8
 
@@ -13,19 +16,6 @@ OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
13
  ANTHROPIC_API_KEY = os.getenv("ANTHROPIC_API_KEY")
14
  DEEPSEEK_API_KEY = os.getenv("DEEPSEEK_API_KEY")
15
 
16
- # Debug: Print key status (masked)
17
- print("="*30)
18
- print("🔧 CONFIGURATION DEBUG")
19
- print(f"OPENAI_API_KEY: {'✅ Found' if OPENAI_API_KEY else '❌ Missing'}")
20
- if OPENAI_API_KEY:
21
- print(f" Length: {len(OPENAI_API_KEY)}")
22
- print(f" Prefix: {OPENAI_API_KEY[:5]}...")
23
-
24
- print(f"ANTHROPIC_API_KEY: {'✅ Found' if ANTHROPIC_API_KEY else '❌ Missing'}")
25
- print(f"DEEPSEEK_API_KEY: {'✅ Found' if DEEPSEEK_API_KEY else '❌ Missing'}")
26
- print(f"GEMINI_API_KEY: {'✅ Found' if os.getenv('GEMINI_API_KEY') else '❌ Missing'}")
27
- print("="*30)
28
-
29
  # Конфігурація Gemini
30
  GEMINI_API_KEY = os.getenv("GEMINI_API_KEY")
31
  if GEMINI_API_KEY:
 
1
+ import logging
2
  import os
3
  from enum import Enum
4
  from pathlib import Path
5
  from dotenv import load_dotenv
6
 
7
+ logger = logging.getLogger(__name__)
8
+
9
  # Load environment variables
10
  load_dotenv()
11
 
 
16
  ANTHROPIC_API_KEY = os.getenv("ANTHROPIC_API_KEY")
17
  DEEPSEEK_API_KEY = os.getenv("DEEPSEEK_API_KEY")
18
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  # Конфігурація Gemini
20
  GEMINI_API_KEY = os.getenv("GEMINI_API_KEY")
21
  if GEMINI_API_KEY:
interface.py CHANGED
@@ -730,8 +730,8 @@ def create_gradio_interface() -> gr.Blocks:
730
 
731
  with gr.TabItem("📂 Завантаження файлу", id="file_tab"):
732
  file_input = gr.File(
733
- label="Перетягніть файл або натисніть для вибору",
734
- file_types=[".txt", ".docx", ".pdf"], # Added docx/pdf just for UI (backend needs support)
735
  file_count="single"
736
  )
737
 
@@ -752,7 +752,7 @@ def create_gradio_interface() -> gr.Blocks:
752
  )
753
 
754
  generate_position_button = gr.Button(
755
- " Згенерувати правову позицію",
756
  variant="primary",
757
  size="lg"
758
  )
 
730
 
731
  with gr.TabItem("📂 Завантаження файлу", id="file_tab"):
732
  file_input = gr.File(
733
+ label="Перетягніть TXT-файл або натисніть для вибору",
734
+ file_types=[".txt"],
735
  file_count="single"
736
  )
737
 
 
752
  )
753
 
754
  generate_position_button = gr.Button(
755
+ "📝 Згенерувати правову позицію",
756
  variant="primary",
757
  size="lg"
758
  )