legalmind-ai 1.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of legalmind-ai might be problematic. Click here for more details.
- legalmind/__init__.py +1 -0
- legalmind/ai/__init__.py +7 -0
- legalmind/ai/legal_ai.py +232 -0
- legalmind/analyzers/__init__.py +0 -0
- legalmind/api/__init__.py +0 -0
- legalmind/api/server.py +288 -0
- legalmind/config.py +41 -0
- legalmind/core.py +92 -0
- legalmind/core_enhanced.py +206 -0
- legalmind/enhanced_search.py +148 -0
- legalmind/prompt_templates.py +284 -0
- legalmind/providers/__init__.py +0 -0
- legalmind/providers/fallback/__init__.py +11 -0
- legalmind/providers/fallback/config.py +66 -0
- legalmind/providers/fallback/data_loader.py +308 -0
- legalmind/providers/fallback/enhanced_system.py +151 -0
- legalmind/providers/fallback/system.py +456 -0
- legalmind/providers/fallback/versalaw2_core/__init__.py +11 -0
- legalmind/providers/fallback/versalaw2_core/config.py +66 -0
- legalmind/providers/fallback/versalaw2_core/data_loader.py +308 -0
- legalmind/providers/fallback/versalaw2_core/enhanced_system.py +151 -0
- legalmind/providers/fallback/versalaw2_core/system.py +456 -0
- legalmind/providers/qodo.py +139 -0
- legalmind/providers/qodo_ai.py +85 -0
- legalmind/study_cases/CROSS_PROJECT_INTEGRATION_ANALYSIS.md +411 -0
- legalmind/study_cases/DAFTAR_KASUS_PRIORITAS_ANALISIS.md +779 -0
- legalmind/study_cases/JAWABAN_ANALISIS_3_KASUS_MENANTANG.md +393 -0
- legalmind/study_cases/JAWABAN_TERBAIK_KONTRAK_REAL.md +854 -0
- legalmind/study_cases/LEGAL_PROJECTS_ANALYSIS_REPORT.md +442 -0
- legalmind/study_cases/PORTFOLIO_11_KASUS_LENGKAP.md +458 -0
- legalmind/study_cases/RINGKASAN_3_KASUS_TECH_INTERNASIONAL.md +565 -0
- legalmind/study_cases/RINGKASAN_HASIL_PENGUJIAN.md +112 -0
- legalmind/study_cases/RINGKASAN_IDE_MONETISASI.md +464 -0
- legalmind/study_cases/RINGKASAN_LENGKAP.md +419 -0
- legalmind/study_cases/RINGKASAN_VISUAL_HASIL_ANALISIS.md +331 -0
- legalmind/study_cases/Real_Studycase_Law_International_Edition.md +434 -0
- legalmind/study_cases/analyze_5_additional_cases.py +905 -0
- legalmind/study_cases/analyze_5_additional_cases_part2.py +461 -0
- legalmind/study_cases/analyze_challenging_cases.py +963 -0
- legalmind/study_cases/analyze_international_tech_cases.py +1706 -0
- legalmind/study_cases/analyze_real_problematic_contracts.py +603 -0
- legalmind/study_cases/kuhp_baru_2026/analisis_perbandingan/ANALISIS_PERUBAHAN_SISTEM_PEMIDANAAN.md +16 -0
- legalmind/study_cases/kuhp_baru_2026/analisis_perbandingan/PERBANDINGAN_KOMPREHENSIF_KUHP_LAMA_BARU.md +27 -0
- legalmind/study_cases/kuhp_baru_2026/analisis_perbandingan/STUDI_KASUS_TRANSISI_KUHP_BARU.md +16 -0
- legalmind/study_cases/kuhp_baru_2026/implementasi_praktis/ANALISIS_DAMPAK_BISNIS_KUHP_BARU.md +16 -0
- legalmind/study_cases/kuhp_baru_2026/implementasi_praktis/CHECKLIST_KOMPLIANCE_KUHP_BARU.md +16 -0
- legalmind/study_cases/kuhp_baru_2026/implementasi_praktis/PANDUAN_TRANSISI_KUHP_BARU_2026.md +28 -0
- legalmind/study_cases/kuhp_baru_2026/studi_kasus/KASUS_KEKERASAN_SEKSUAL_BARU.md +16 -0
- legalmind/study_cases/kuhp_baru_2026/studi_kasus/KASUS_KORUPSI_DAN_GRATIFIKASI.md +16 -0
- legalmind/study_cases/kuhp_baru_2026/studi_kasus/KASUS_TINDAK_PIDANA_SIBER_KUHP_BARU.md +16 -0
- legalmind/study_cases/kuhp_baru_2026/topik_khusus/HUKUM_YANG_HIDUP_DI_MASYARAKAT.md +16 -0
- legalmind/study_cases/kuhp_baru_2026/topik_khusus/PIDANA_TAMBAHAN_DAN_TINDAKAN.md +16 -0
- legalmind/study_cases/kuhp_baru_2026/topik_khusus/TINDAK_PIDANA_SIBER_KUHP_BARU.md +16 -0
- legalmind_ai-1.1.0.dist-info/METADATA +93 -0
- legalmind_ai-1.1.0.dist-info/RECORD +58 -0
- legalmind_ai-1.1.0.dist-info/WHEEL +5 -0
- legalmind_ai-1.1.0.dist-info/entry_points.txt +4 -0
- legalmind_ai-1.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,456 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
VersaLaw2 Integrated System
|
|
4
|
+
Complete production-ready implementation
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
from typing import Dict, List, Optional
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
import json
|
|
11
|
+
import hashlib
|
|
12
|
+
from datetime import datetime
|
|
13
|
+
|
|
14
|
+
from .data_loader import MayaLawDataLoader
|
|
15
|
+
from .config import Config
|
|
16
|
+
|
|
17
|
+
# Setup logging
|
|
18
|
+
logging.basicConfig(
|
|
19
|
+
level=logging.INFO,
|
|
20
|
+
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
|
21
|
+
)
|
|
22
|
+
logger = logging.getLogger(__name__)
|
|
23
|
+
|
|
24
|
+
class VersaLaw2Classifier:
|
|
25
|
+
"""Enhanced classifier"""
|
|
26
|
+
|
|
27
|
+
def __init__(self):
|
|
28
|
+
self.categories = {
|
|
29
|
+
'hukum_pidana': {
|
|
30
|
+
'keywords': ['pidana', 'pencurian', 'pembunuhan', 'korupsi', 'narkotika',
|
|
31
|
+
'hakim', 'terdakwa', 'minimum', 'maksimum', 'penjara', 'hukuman'],
|
|
32
|
+
'weight': 1.0
|
|
33
|
+
},
|
|
34
|
+
'hukum_perdata': {
|
|
35
|
+
'keywords': ['perdata', 'gugatan', 'wanprestasi', 'kontrak', 'perjanjian',
|
|
36
|
+
'ganti rugi', 'sengketa'],
|
|
37
|
+
'weight': 1.0
|
|
38
|
+
},
|
|
39
|
+
'hukum_keluarga': {
|
|
40
|
+
'keywords': ['perceraian', 'cerai', 'nafkah', 'waris', 'anak', 'nikah',
|
|
41
|
+
'perkawinan'],
|
|
42
|
+
'weight': 1.0
|
|
43
|
+
},
|
|
44
|
+
'hukum_bisnis': {
|
|
45
|
+
'keywords': ['perusahaan', 'pt', 'cv', 'saham', 'ipo', 'merger', 'akuisisi'],
|
|
46
|
+
'weight': 1.0
|
|
47
|
+
},
|
|
48
|
+
'hukum_properti': {
|
|
49
|
+
'keywords': ['tanah', 'sertifikat', 'properti', 'bangunan', 'hak tanggungan'],
|
|
50
|
+
'weight': 1.0
|
|
51
|
+
},
|
|
52
|
+
'hukum_tata_negara': {
|
|
53
|
+
'keywords': ['konstitusi', 'uud', 'mahkamah konstitusi', 'pemilu', 'dpr'],
|
|
54
|
+
'weight': 1.0
|
|
55
|
+
},
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
def classify(self, question: str) -> Dict:
|
|
59
|
+
"""Classify legal question"""
|
|
60
|
+
question_lower = question.lower()
|
|
61
|
+
|
|
62
|
+
scores = {}
|
|
63
|
+
for category, data in self.categories.items():
|
|
64
|
+
score = sum(1 for kw in data['keywords'] if kw in question_lower)
|
|
65
|
+
if score > 0:
|
|
66
|
+
scores[category] = score * data['weight']
|
|
67
|
+
|
|
68
|
+
if scores:
|
|
69
|
+
best_category = max(scores, key=scores.get)
|
|
70
|
+
total_words = len(question.split())
|
|
71
|
+
confidence = min(scores[best_category] / max(total_words, 1), 0.95)
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
'category': best_category,
|
|
75
|
+
'confidence': confidence,
|
|
76
|
+
'all_scores': scores
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
'category': 'umum',
|
|
81
|
+
'confidence': 0.3,
|
|
82
|
+
'all_scores': {}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
class AIProcessor:
|
|
86
|
+
"""AI processor with support for multiple providers"""
|
|
87
|
+
|
|
88
|
+
def __init__(self, provider: str = "mock", api_key: Optional[str] = None, config: Optional[Config] = None):
|
|
89
|
+
self.provider = provider
|
|
90
|
+
self.api_key = api_key
|
|
91
|
+
self.config = config or Config()
|
|
92
|
+
|
|
93
|
+
if provider == "openai" and api_key:
|
|
94
|
+
try:
|
|
95
|
+
from openai import OpenAI
|
|
96
|
+
self.client = OpenAI(api_key=api_key)
|
|
97
|
+
self.model = "gpt-4-turbo-preview"
|
|
98
|
+
logger.info("OpenAI client initialized")
|
|
99
|
+
except ImportError:
|
|
100
|
+
logger.warning("OpenAI package not installed, falling back to mock")
|
|
101
|
+
self.provider = "mock"
|
|
102
|
+
|
|
103
|
+
elif provider == "deepseek" and api_key:
|
|
104
|
+
try:
|
|
105
|
+
from openai import OpenAI
|
|
106
|
+
self.client = OpenAI(
|
|
107
|
+
api_key=api_key,
|
|
108
|
+
base_url="https://api.deepseek.com/v1"
|
|
109
|
+
)
|
|
110
|
+
self.model = "deepseek-chat"
|
|
111
|
+
logger.info("DeepSeek client initialized")
|
|
112
|
+
except ImportError:
|
|
113
|
+
logger.warning("OpenAI package not installed, falling back to mock")
|
|
114
|
+
self.provider = "mock"
|
|
115
|
+
|
|
116
|
+
elif provider == "qodo" and api_key:
|
|
117
|
+
try:
|
|
118
|
+
from openai import OpenAI
|
|
119
|
+
base_url = self.config.get('qodo_base_url', 'https://api.qodo.ai/v1')
|
|
120
|
+
self.client = OpenAI(
|
|
121
|
+
api_key=api_key,
|
|
122
|
+
base_url=base_url
|
|
123
|
+
)
|
|
124
|
+
self.model = "qodo-chat" # Adjust based on Qodo.ai's actual model name
|
|
125
|
+
logger.info(f"Qodo.ai client initialized (250 free calls available!)")
|
|
126
|
+
print("ā
Qodo.ai initialized - 250 free calls available!")
|
|
127
|
+
except ImportError:
|
|
128
|
+
logger.warning("OpenAI package not installed, falling back to mock")
|
|
129
|
+
self.provider = "mock"
|
|
130
|
+
except Exception as e:
|
|
131
|
+
logger.warning(f"Qodo.ai initialization failed: {e}, falling back to mock")
|
|
132
|
+
self.provider = "mock"
|
|
133
|
+
|
|
134
|
+
else:
|
|
135
|
+
self.provider = "mock"
|
|
136
|
+
logger.info("Using mock AI processor")
|
|
137
|
+
|
|
138
|
+
def generate_answer(self, question: str, context: Dict) -> Dict:
|
|
139
|
+
"""Generate answer using AI"""
|
|
140
|
+
|
|
141
|
+
if self.provider == "mock":
|
|
142
|
+
return self._mock_answer(question, context)
|
|
143
|
+
else:
|
|
144
|
+
return self._real_ai_answer(question, context)
|
|
145
|
+
|
|
146
|
+
def _real_ai_answer(self, question: str, context: Dict) -> Dict:
|
|
147
|
+
"""Generate answer using real AI"""
|
|
148
|
+
try:
|
|
149
|
+
prompt = self._build_prompt(question, context)
|
|
150
|
+
|
|
151
|
+
response = self.client.chat.completions.create(
|
|
152
|
+
model=self.model,
|
|
153
|
+
messages=[
|
|
154
|
+
{
|
|
155
|
+
"role": "system",
|
|
156
|
+
"content": "Anda adalah ahli hukum Indonesia yang sangat berpengalaman. "
|
|
157
|
+
"Jawab pertanyaan berdasarkan konteks yang diberikan dengan akurat."
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
"role": "user",
|
|
161
|
+
"content": prompt
|
|
162
|
+
}
|
|
163
|
+
],
|
|
164
|
+
temperature=self.config.get('ai_temperature', 0.3),
|
|
165
|
+
max_tokens=self.config.get('ai_max_tokens', 2000)
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
return {
|
|
169
|
+
'answer': response.choices[0].message.content,
|
|
170
|
+
'model': self.model,
|
|
171
|
+
'usage': response.usage._asdict()
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
except Exception as e:
|
|
175
|
+
logger.error(f"AI generation error: {e}")
|
|
176
|
+
return self._mock_answer(question, context)
|
|
177
|
+
|
|
178
|
+
def _build_prompt(self, question: str, context: Dict) -> str:
|
|
179
|
+
"""Build prompt for AI"""
|
|
180
|
+
cases = context.get('cases', [])
|
|
181
|
+
|
|
182
|
+
context_text = ""
|
|
183
|
+
for case in cases[:2]: # Use top 2 cases
|
|
184
|
+
context_text += f"""
|
|
185
|
+
KASUS #{case['number']}:
|
|
186
|
+
{case.get('kasus', '')}
|
|
187
|
+
|
|
188
|
+
PERTANYAAN:
|
|
189
|
+
{case.get('pertanyaan', '')}
|
|
190
|
+
|
|
191
|
+
JAWABAN:
|
|
192
|
+
{case.get('jawaban', '')[:400]}
|
|
193
|
+
|
|
194
|
+
DASAR HUKUM:
|
|
195
|
+
{', '.join(case.get('pasal', [])[:3])}
|
|
196
|
+
{', '.join(case.get('uu', [])[:2])}
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
"""
|
|
200
|
+
|
|
201
|
+
prompt = f"""
|
|
202
|
+
Berdasarkan konteks kasus hukum berikut dari database MayaLaw,
|
|
203
|
+
jawab pertanyaan dengan akurat dan detail.
|
|
204
|
+
|
|
205
|
+
KONTEKS DARI MAYALAW:
|
|
206
|
+
{context_text}
|
|
207
|
+
|
|
208
|
+
PERTANYAAN USER:
|
|
209
|
+
{question}
|
|
210
|
+
|
|
211
|
+
Berikan jawaban yang:
|
|
212
|
+
1. ā
Akurat berdasarkan hukum Indonesia
|
|
213
|
+
2. ā
Merujuk pada Pasal dan UU yang spesifik dari konteks
|
|
214
|
+
3. ā
Menjelaskan dengan bahasa yang mudah dipahami
|
|
215
|
+
4. ā
Menyertakan analisis hukum yang mendalam
|
|
216
|
+
5. ā
Memberikan tingkat keyakinan (confidence level)
|
|
217
|
+
|
|
218
|
+
Format jawaban:
|
|
219
|
+
## āļø JAWABAN:
|
|
220
|
+
[Jawaban singkat dan jelas]
|
|
221
|
+
|
|
222
|
+
## š DASAR HUKUM:
|
|
223
|
+
[Pasal dan UU yang relevan]
|
|
224
|
+
|
|
225
|
+
## š ANALISIS:
|
|
226
|
+
[Penjelasan detail]
|
|
227
|
+
|
|
228
|
+
## šÆ TINGKAT KEYAKINAN:
|
|
229
|
+
[Persentase dan alasan]
|
|
230
|
+
"""
|
|
231
|
+
return prompt
|
|
232
|
+
|
|
233
|
+
def _mock_answer(self, question: str, context: Dict) -> Dict:
|
|
234
|
+
"""Mock answer for testing"""
|
|
235
|
+
cases = context.get('cases', [])
|
|
236
|
+
|
|
237
|
+
if not cases:
|
|
238
|
+
answer = f"""## ā ļø INFORMASI
|
|
239
|
+
|
|
240
|
+
Pertanyaan: "{question}"
|
|
241
|
+
|
|
242
|
+
Saat ini tidak ditemukan kasus yang relevan di database MayaLaw untuk pertanyaan ini.
|
|
243
|
+
|
|
244
|
+
## š” SARAN
|
|
245
|
+
|
|
246
|
+
1. Coba rumuskan pertanyaan dengan kata kunci yang lebih spesifik
|
|
247
|
+
2. Konsultasikan dengan ahli hukum untuk analisis mendalam
|
|
248
|
+
3. Database sedang dikembangkan untuk mencakup lebih banyak kasus
|
|
249
|
+
"""
|
|
250
|
+
return {
|
|
251
|
+
'answer': answer,
|
|
252
|
+
'model': 'mock',
|
|
253
|
+
'usage': {'total_tokens': 50}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
case = cases[0]
|
|
257
|
+
|
|
258
|
+
answer = f"""## āļø JAWABAN
|
|
259
|
+
|
|
260
|
+
{case.get('jawaban', 'Berdasarkan analisis hukum...')[:400]}
|
|
261
|
+
|
|
262
|
+
## š DASAR HUKUM
|
|
263
|
+
|
|
264
|
+
"""
|
|
265
|
+
|
|
266
|
+
if case.get('pasal'):
|
|
267
|
+
answer += "**Pasal yang Relevan:**\n"
|
|
268
|
+
for pasal in case['pasal'][:5]:
|
|
269
|
+
answer += f"- {pasal}\n"
|
|
270
|
+
answer += "\n"
|
|
271
|
+
|
|
272
|
+
if case.get('uu'):
|
|
273
|
+
answer += "**Undang-Undang:**\n"
|
|
274
|
+
for uu in case['uu'][:3]:
|
|
275
|
+
answer += f"- {uu}\n"
|
|
276
|
+
answer += "\n"
|
|
277
|
+
|
|
278
|
+
if case.get('dasar_hukum'):
|
|
279
|
+
answer += f"{case['dasar_hukum'][:300]}\n\n"
|
|
280
|
+
|
|
281
|
+
answer += f"""## š ANALISIS
|
|
282
|
+
|
|
283
|
+
{case.get('analisis', '')[:600]}
|
|
284
|
+
|
|
285
|
+
## š REFERENSI
|
|
286
|
+
|
|
287
|
+
Berdasarkan Kasus #{case['number']} dari database MayaLaw ({case['file']})
|
|
288
|
+
|
|
289
|
+
## šÆ TINGKAT KEYAKINAN
|
|
290
|
+
|
|
291
|
+
95% - Jawaban berdasarkan studi kasus yang relevan dan terverifikasi
|
|
292
|
+
"""
|
|
293
|
+
|
|
294
|
+
return {
|
|
295
|
+
'answer': answer,
|
|
296
|
+
'model': 'mock',
|
|
297
|
+
'usage': {'total_tokens': len(answer.split())}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
class CacheManager:
|
|
301
|
+
"""Simple cache manager"""
|
|
302
|
+
|
|
303
|
+
def __init__(self, cache_dir: str = "/root/dragon/global/lab/.cache"):
|
|
304
|
+
self.cache_dir = Path(cache_dir)
|
|
305
|
+
self.cache_dir.mkdir(parents=True, exist_ok=True)
|
|
306
|
+
self.enabled = True
|
|
307
|
+
|
|
308
|
+
def get_cache_key(self, question: str) -> str:
|
|
309
|
+
"""Generate cache key"""
|
|
310
|
+
return hashlib.md5(question.encode()).hexdigest()
|
|
311
|
+
|
|
312
|
+
def get(self, question: str) -> Optional[Dict]:
|
|
313
|
+
"""Get cached result"""
|
|
314
|
+
if not self.enabled:
|
|
315
|
+
return None
|
|
316
|
+
|
|
317
|
+
cache_key = self.get_cache_key(question)
|
|
318
|
+
cache_file = self.cache_dir / f"{cache_key}.json"
|
|
319
|
+
|
|
320
|
+
if cache_file.exists():
|
|
321
|
+
try:
|
|
322
|
+
with open(cache_file, 'r', encoding='utf-8') as f:
|
|
323
|
+
return json.load(f)
|
|
324
|
+
except:
|
|
325
|
+
return None
|
|
326
|
+
return None
|
|
327
|
+
|
|
328
|
+
def set(self, question: str, result: Dict):
|
|
329
|
+
"""Cache result"""
|
|
330
|
+
if not self.enabled:
|
|
331
|
+
return
|
|
332
|
+
|
|
333
|
+
cache_key = self.get_cache_key(question)
|
|
334
|
+
cache_file = self.cache_dir / f"{cache_key}.json"
|
|
335
|
+
|
|
336
|
+
try:
|
|
337
|
+
with open(cache_file, 'w', encoding='utf-8') as f:
|
|
338
|
+
json.dump(result, f, ensure_ascii=False, indent=2)
|
|
339
|
+
except Exception as e:
|
|
340
|
+
logger.warning(f"Cache write error: {e}")
|
|
341
|
+
|
|
342
|
+
class VersaLaw2System:
|
|
343
|
+
"""Complete VersaLaw2 integrated system"""
|
|
344
|
+
|
|
345
|
+
def __init__(self, config: Optional[Config] = None):
|
|
346
|
+
self.config = config or Config()
|
|
347
|
+
|
|
348
|
+
print("š Initializing VersaLaw2 System...")
|
|
349
|
+
print("="*60)
|
|
350
|
+
|
|
351
|
+
# Initialize components
|
|
352
|
+
self.data_loader = MayaLawDataLoader(self.config['mayalaw_path'])
|
|
353
|
+
self.classifier = VersaLaw2Classifier()
|
|
354
|
+
print("ā
Classifier ready")
|
|
355
|
+
|
|
356
|
+
self.ai_processor = AIProcessor(
|
|
357
|
+
provider=self.config['ai_provider'],
|
|
358
|
+
api_key=self.config.get('ai_api_key') or self.config.get('qodo_api_key') or self.config.get('deepseek_api_key') or self.config.get('openai_api_key'),
|
|
359
|
+
config=self.config
|
|
360
|
+
)
|
|
361
|
+
print(f"ā
AI processor ready (mode: {self.ai_processor.provider})")
|
|
362
|
+
|
|
363
|
+
self.cache = CacheManager(self.config['cache_dir'])
|
|
364
|
+
print(f"ā
Cache {'enabled' if self.cache.enabled else 'disabled'}")
|
|
365
|
+
|
|
366
|
+
print("="*60)
|
|
367
|
+
print("š System ready!\n")
|
|
368
|
+
|
|
369
|
+
logger.info("VersaLaw2 System initialized successfully")
|
|
370
|
+
|
|
371
|
+
def ask(self, question: str, use_cache: bool = True) -> Dict:
|
|
372
|
+
"""Answer legal question"""
|
|
373
|
+
|
|
374
|
+
# Check cache
|
|
375
|
+
if use_cache:
|
|
376
|
+
cached = self.cache.get(question)
|
|
377
|
+
if cached:
|
|
378
|
+
logger.info(f"Cache hit for question: {question[:50]}")
|
|
379
|
+
print("š¾ Using cached result")
|
|
380
|
+
return cached
|
|
381
|
+
|
|
382
|
+
print(f"\n{'='*60}")
|
|
383
|
+
print(f"š PERTANYAAN: {question}")
|
|
384
|
+
print(f"{'='*60}\n")
|
|
385
|
+
|
|
386
|
+
# Step 1: Classify
|
|
387
|
+
print("1ļøā£ Mengklasifikasi...")
|
|
388
|
+
classification = self.classifier.classify(question)
|
|
389
|
+
print(f" ā
Kategori: {classification['category']}")
|
|
390
|
+
print(f" ā
Confidence: {classification['confidence']:.0%}\n")
|
|
391
|
+
|
|
392
|
+
# Step 2: Search
|
|
393
|
+
print("2ļøā£ Mencari di MayaLaw...")
|
|
394
|
+
relevant_cases = self.data_loader.search(
|
|
395
|
+
question,
|
|
396
|
+
top_k=self.config['max_search_results']
|
|
397
|
+
)
|
|
398
|
+
print(f" ā
Ditemukan: {len(relevant_cases)} kasus\n")
|
|
399
|
+
|
|
400
|
+
if relevant_cases:
|
|
401
|
+
for i, case in enumerate(relevant_cases, 1):
|
|
402
|
+
print(f" {i}. Kasus #{case['number']}: {case['pertanyaan'][:60]}...")
|
|
403
|
+
print()
|
|
404
|
+
|
|
405
|
+
# Step 3: Generate answer
|
|
406
|
+
print("3ļøā£ Memproses dengan AI...")
|
|
407
|
+
context = {'cases': relevant_cases}
|
|
408
|
+
ai_response = self.ai_processor.generate_answer(question, context)
|
|
409
|
+
print(f" ā
Generated\n")
|
|
410
|
+
|
|
411
|
+
# Build result
|
|
412
|
+
result = {
|
|
413
|
+
'question': question,
|
|
414
|
+
'classification': classification,
|
|
415
|
+
'cases_found': len(relevant_cases),
|
|
416
|
+
'cases': relevant_cases,
|
|
417
|
+
'answer': ai_response['answer'],
|
|
418
|
+
'metadata': {
|
|
419
|
+
'ai_model': ai_response['model'],
|
|
420
|
+
'tokens': ai_response['usage']['total_tokens'],
|
|
421
|
+
'confidence': 0.95 if relevant_cases else 0.5,
|
|
422
|
+
'timestamp': datetime.now().isoformat()
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
# Cache result
|
|
427
|
+
if use_cache:
|
|
428
|
+
self.cache.set(question, result)
|
|
429
|
+
|
|
430
|
+
logger.info(f"Question answered: {question[:50]}")
|
|
431
|
+
|
|
432
|
+
return result
|
|
433
|
+
|
|
434
|
+
def print_answer(self, result: Dict):
|
|
435
|
+
"""Pretty print answer"""
|
|
436
|
+
print(f"{'='*60}")
|
|
437
|
+
print("š HASIL ANALISIS")
|
|
438
|
+
print(f"{'='*60}\n")
|
|
439
|
+
|
|
440
|
+
print(f"šÆ Kategori: {result['classification']['category']}")
|
|
441
|
+
print(f"š Kasus: {result['cases_found']}")
|
|
442
|
+
print(f"šÆ Confidence: {result['metadata']['confidence']:.0%}\n")
|
|
443
|
+
|
|
444
|
+
print(f"{'='*60}")
|
|
445
|
+
print(result['answer'])
|
|
446
|
+
print(f"{'='*60}\n")
|
|
447
|
+
|
|
448
|
+
def get_stats(self) -> Dict:
|
|
449
|
+
"""Get system statistics"""
|
|
450
|
+
return {
|
|
451
|
+
'system': {
|
|
452
|
+
'ai_provider': self.ai_processor.provider,
|
|
453
|
+
'cache_enabled': self.cache.enabled,
|
|
454
|
+
},
|
|
455
|
+
'data': self.data_loader.get_stats()
|
|
456
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Demo VersaLaw2 with Qodo.ai
|
|
4
|
+
250 Free Calls Available!
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import sys
|
|
8
|
+
import os
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
sys.path.insert(0, str(Path(__file__).parent))
|
|
12
|
+
|
|
13
|
+
from versalaw2_core.enhanced_system import create_enhanced_system
|
|
14
|
+
|
|
15
|
+
def main():
|
|
16
|
+
"""Run Qodo.ai demo"""
|
|
17
|
+
|
|
18
|
+
print("\n" + "="*60)
|
|
19
|
+
print("āļø VERSALAW2 WITH QODO.AI")
|
|
20
|
+
print("="*60)
|
|
21
|
+
print("š 250 FREE CALLS AVAILABLE!")
|
|
22
|
+
print("="*60)
|
|
23
|
+
print("\nPowered by:")
|
|
24
|
+
print(" ⢠VersaLaw2 Framework (21 analyzers)")
|
|
25
|
+
print(" ⢠MayaLaw Database (126 study cases)")
|
|
26
|
+
print(" ⢠Maya Wisdom Processor (legal knowledge)")
|
|
27
|
+
print(" ⢠Qodo.ai API (250 free calls!)")
|
|
28
|
+
print("="*60 + "\n")
|
|
29
|
+
|
|
30
|
+
# Get API key
|
|
31
|
+
api_key = os.getenv('QODO_API_KEY')
|
|
32
|
+
|
|
33
|
+
if not api_key:
|
|
34
|
+
print("ā ļø QODO_API_KEY not found in environment variables")
|
|
35
|
+
print("\nš To use Qodo.ai:")
|
|
36
|
+
print(" 1. Get free API key from: https://qodo.ai")
|
|
37
|
+
print(" 2. Set environment variable:")
|
|
38
|
+
print(" export QODO_API_KEY='your-api-key-here'")
|
|
39
|
+
print(" 3. Run this script again")
|
|
40
|
+
print("\nš” Or run with mock AI for testing:")
|
|
41
|
+
print(" python demo_versalaw2.py")
|
|
42
|
+
print()
|
|
43
|
+
return
|
|
44
|
+
|
|
45
|
+
# Initialize with Qodo.ai
|
|
46
|
+
print("š Initializing with Qodo.ai...")
|
|
47
|
+
try:
|
|
48
|
+
system = create_enhanced_system(
|
|
49
|
+
ai_provider='qodo',
|
|
50
|
+
api_key=api_key
|
|
51
|
+
)
|
|
52
|
+
except Exception as e:
|
|
53
|
+
print(f"ā Error initializing Qodo.ai: {e}")
|
|
54
|
+
print("\nš” Falling back to mock AI...")
|
|
55
|
+
system = create_enhanced_system(
|
|
56
|
+
ai_provider='mock',
|
|
57
|
+
api_key=None
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
# Demo questions
|
|
61
|
+
demo_questions = [
|
|
62
|
+
"Apa syarat sah perjanjian?",
|
|
63
|
+
"Apakah hakim boleh menjatuhkan pidana di bawah minimum?",
|
|
64
|
+
"Bagaimana prosedur mengajukan gugatan perceraian?",
|
|
65
|
+
]
|
|
66
|
+
|
|
67
|
+
print("\nš Demo Questions:\n")
|
|
68
|
+
for i, q in enumerate(demo_questions, 1):
|
|
69
|
+
print(f"{i}. {q}")
|
|
70
|
+
|
|
71
|
+
print("\n" + "="*60)
|
|
72
|
+
print("Starting analysis with Qodo.ai...")
|
|
73
|
+
print("="*60 + "\n")
|
|
74
|
+
|
|
75
|
+
for i, question in enumerate(demo_questions, 1):
|
|
76
|
+
print(f"\n{'#'*60}")
|
|
77
|
+
print(f"QODO.AI DEMO {i}/{len(demo_questions)}")
|
|
78
|
+
print(f"{'#'*60}\n")
|
|
79
|
+
|
|
80
|
+
# Ask question
|
|
81
|
+
result = system.ask(question, include_wisdom=True)
|
|
82
|
+
|
|
83
|
+
# Print answer
|
|
84
|
+
system.print_answer(result)
|
|
85
|
+
|
|
86
|
+
# Show API info
|
|
87
|
+
print(f"\nš” API Info:")
|
|
88
|
+
print(f" Provider: {result['metadata']['ai_model']}")
|
|
89
|
+
print(f" Tokens used: {result['metadata']['tokens']}")
|
|
90
|
+
|
|
91
|
+
if system.ai_processor.provider == 'qodo':
|
|
92
|
+
remaining = 250 - i # Approximate
|
|
93
|
+
print(f" š Estimated free calls remaining: ~{remaining}")
|
|
94
|
+
|
|
95
|
+
print()
|
|
96
|
+
|
|
97
|
+
# Show statistics
|
|
98
|
+
print("\n" + "="*60)
|
|
99
|
+
print("š SESSION STATISTICS")
|
|
100
|
+
print("="*60)
|
|
101
|
+
|
|
102
|
+
stats = system.get_stats()
|
|
103
|
+
print(f"\nš¤ AI Provider: {stats['system']['ai_provider']}")
|
|
104
|
+
print(f"š¾ Cache: {'Enabled' if stats['system']['cache_enabled'] else 'Disabled'}")
|
|
105
|
+
print(f"š MayaLaw Cases: {stats['data']['total_cases']}")
|
|
106
|
+
print(f"š Files Loaded: {stats['data']['files_loaded']}")
|
|
107
|
+
print(f"š§ Maya Wisdom: {'Available' if stats['wisdom']['available'] else 'Not Available'}")
|
|
108
|
+
|
|
109
|
+
if system.ai_processor.provider == 'qodo':
|
|
110
|
+
print(f"\nš Qodo.ai Free Calls:")
|
|
111
|
+
print(f" Total available: 250")
|
|
112
|
+
print(f" Used in this demo: {len(demo_questions)}")
|
|
113
|
+
print(f" Estimated remaining: ~{250 - len(demo_questions)}")
|
|
114
|
+
|
|
115
|
+
print("\n" + "="*60)
|
|
116
|
+
print("ā
QODO.AI DEMO COMPLETED!")
|
|
117
|
+
print("="*60)
|
|
118
|
+
|
|
119
|
+
print("\nš” Benefits of Qodo.ai:")
|
|
120
|
+
print(" ā
250 free calls for testing")
|
|
121
|
+
print(" ā
Real AI responses (not mock)")
|
|
122
|
+
print(" ā
No credit card required")
|
|
123
|
+
print(" ā
Perfect for evaluation")
|
|
124
|
+
|
|
125
|
+
print("\nš Next Steps:")
|
|
126
|
+
print(" 1. Continue testing with remaining free calls")
|
|
127
|
+
print(" 2. Evaluate quality vs DeepSeek/OpenAI")
|
|
128
|
+
print(" 3. Choose best provider for production")
|
|
129
|
+
|
|
130
|
+
print("\nš Cost Comparison:")
|
|
131
|
+
print(" ⢠Qodo.ai: 250 free calls, then check pricing")
|
|
132
|
+
print(" ⢠DeepSeek: $0.28 per 1,000 calls (CHEAPEST!)")
|
|
133
|
+
print(" ⢠OpenAI: $20 per 1,000 calls")
|
|
134
|
+
print(" ⢠Mock: Free (testing only)")
|
|
135
|
+
|
|
136
|
+
print()
|
|
137
|
+
|
|
138
|
+
if __name__ == "__main__":
|
|
139
|
+
main()
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Qodo AI Provider for LegalMind
|
|
3
|
+
"""
|
|
4
|
+
import os
|
|
5
|
+
import requests
|
|
6
|
+
from typing import Dict, Any, Optional
|
|
7
|
+
|
|
8
|
+
class QodoAIAnalyzer:
|
|
9
|
+
"""Qodo AI integration for legal analysis"""
|
|
10
|
+
|
|
11
|
+
def __init__(self, api_key: str):
|
|
12
|
+
self.api_key = api_key
|
|
13
|
+
self.base_url = "https://api.qodo.ai/v1/completions"
|
|
14
|
+
self.headers = {
|
|
15
|
+
"Authorization": f"Bearer {api_key}",
|
|
16
|
+
"Content-Type": "application/json"
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
def analyze(self, question: str, context: Optional[Dict] = None) -> Dict[str, Any]:
|
|
20
|
+
"""Analyze legal question using Qodo AI"""
|
|
21
|
+
|
|
22
|
+
# Build prompt with legal context
|
|
23
|
+
prompt = self._build_legal_prompt(question, context)
|
|
24
|
+
|
|
25
|
+
payload = {
|
|
26
|
+
"prompt": prompt,
|
|
27
|
+
"max_tokens": 800,
|
|
28
|
+
"temperature": 0.3
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
try:
|
|
32
|
+
response = requests.post(
|
|
33
|
+
self.base_url,
|
|
34
|
+
headers=self.headers,
|
|
35
|
+
json=payload,
|
|
36
|
+
timeout=30
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
if response.status_code == 200:
|
|
40
|
+
result = response.json()
|
|
41
|
+
return self._parse_response(result, question)
|
|
42
|
+
else:
|
|
43
|
+
return {
|
|
44
|
+
"success": False,
|
|
45
|
+
"error": f"Qodo AI API error: {response.status_code}",
|
|
46
|
+
"provider": "qodo_ai"
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
except Exception as e:
|
|
50
|
+
return {
|
|
51
|
+
"success": False,
|
|
52
|
+
"error": str(e),
|
|
53
|
+
"provider": "qodo_ai"
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
def _build_legal_prompt(self, question: str, context: Optional[Dict] = None) -> str:
|
|
57
|
+
"""Build legal analysis prompt"""
|
|
58
|
+
base_prompt = f"""Anda adalah asisten hukum profesional Indonesia. Berikan analisis hukum yang akurat berdasarkan UU yang berlaku.
|
|
59
|
+
|
|
60
|
+
PERTANYAAN: {question}
|
|
61
|
+
|
|
62
|
+
JAWABAN HUKUM:"""
|
|
63
|
+
|
|
64
|
+
if context:
|
|
65
|
+
context_str = "\n".join([f"{k}: {v}" for k, v in context.items()])
|
|
66
|
+
base_prompt = f"KONTEKS:\n{context_str}\n\n{base_prompt}"
|
|
67
|
+
|
|
68
|
+
return base_prompt
|
|
69
|
+
|
|
70
|
+
def _parse_response(self, result: Dict, question: str) -> Dict[str, Any]:
|
|
71
|
+
"""Parse Qodo AI response"""
|
|
72
|
+
if 'choices' in result and len(result['choices']) > 0:
|
|
73
|
+
answer = result['choices'][0].get('text', '').strip()
|
|
74
|
+
elif 'text' in result:
|
|
75
|
+
answer = result['text'].strip()
|
|
76
|
+
else:
|
|
77
|
+
answer = "Tidak dapat memproses jawaban."
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
"success": True,
|
|
81
|
+
"question": question,
|
|
82
|
+
"answer": answer,
|
|
83
|
+
"provider": "qodo_ai",
|
|
84
|
+
"raw_response": result
|
|
85
|
+
}
|