DocUA commited on
Commit
eba9dcb
·
1 Parent(s): 98000f7

Edit Output

Browse files
Files changed (3) hide show
  1. config.py +1 -0
  2. main.py +119 -124
  3. prompts.py +42 -1
config.py CHANGED
@@ -8,3 +8,4 @@ OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
8
 
9
  if not OPENAI_API_KEY:
10
  raise ValueError("API ключ OpenAI не знайдено. Додайте його в .env файл.")
 
 
8
 
9
  if not OPENAI_API_KEY:
10
  raise ValueError("API ключ OpenAI не знайдено. Додайте його в .env файл.")
11
+
main.py CHANGED
@@ -33,7 +33,7 @@ from llama_index.core.schema import NodeWithScore
33
  from llama_index.core.prompts import PromptTemplate
34
  from llama_index.core.response_synthesizers import ResponseMode, get_response_synthesizer
35
 
36
- from prompts import PRECEDENT_ANALYSIS_TEMPLATE
37
 
38
 
39
  from dotenv import load_dotenv
@@ -115,7 +115,7 @@ state_lp_json = gr.State()
115
  state_nodes = gr.State()
116
 
117
 
118
- class CitationQueryEngineWorkflow(Workflow):
119
  @step
120
  async def analyze(self, ctx: Context, ev: StartEvent) -> StopEvent:
121
  query = ev.get("query") # нове рішення
@@ -125,11 +125,11 @@ class CitationQueryEngineWorkflow(Workflow):
125
  if not all([query, nodes]):
126
  return StopEvent(result="Недостатньо даних для аналізу. Необхідні нове рішення та правові позиції.")
127
 
128
- llm = OpenAI(model="gpt-4o-mini", temperature=0)
 
129
 
130
  # Підготовка контексту та збір ID правових позицій
131
  context_parts = []
132
- all_lp_ids = []
133
 
134
  for i, node in enumerate(nodes, 1):
135
  # Отримуємо текст з node.node якщо це NodeWithScore
@@ -138,9 +138,9 @@ class CitationQueryEngineWorkflow(Workflow):
138
  metadata = node.node.metadata if hasattr(node, 'node') else node.metadata
139
 
140
  lp_id = metadata.get('lp_id', f'unknown_{i}')
141
- all_lp_ids.append(lp_id)
142
 
143
- context_parts.append(f"Source {lp_id}:\n{node_text}")
144
 
145
  context_str = "\n\n".join(context_parts)
146
 
@@ -157,9 +157,10 @@ class CitationQueryEngineWorkflow(Workflow):
157
  "type": "object",
158
  "properties": {
159
  "lp_id": {"type": "string"},
 
160
  "description": {"type": "string"}
161
  },
162
- "required": ["lp_id", "description"]
163
  }
164
  }
165
  },
@@ -180,7 +181,7 @@ class CitationQueryEngineWorkflow(Workflow):
180
  ChatMessage(role="user", content=prompt)
181
  ]
182
 
183
- response = llm.chat(
184
  messages=messages,
185
  response_format=response_format
186
  )
@@ -188,18 +189,22 @@ class CitationQueryEngineWorkflow(Workflow):
188
  try:
189
  parsed_response = json.loads(response.message.content)
190
  if "relevant_positions" in parsed_response:
191
- return StopEvent(result=parsed_response)
 
 
 
 
 
 
 
 
 
 
192
  else:
193
- return StopEvent(result={
194
- "error": "Missing required fields in response",
195
- "content": response.message.content
196
- })
197
 
198
  except json.JSONDecodeError:
199
- return StopEvent(result={
200
- "error": "Error parsing response",
201
- "content": response.message.content
202
- })
203
 
204
 
205
  def parse_doc_ids(doc_ids):
@@ -295,92 +300,84 @@ def extract_court_decision_text(url):
295
  return decision_text.strip()
296
 
297
 
298
- def generate_legal_position(court_decision_text, user_question):
299
- # llm_lp = OpenAI(model="gpt-4o-mini", temperature=0)
300
- # llm_lp = OpenAI(model="ft:gpt-4o-mini-2024-07-18:personal:legal-position-100:ASPFc3vF", temperature=0)
301
- llm_lp = OpenAI(model="ft:gpt-4o-mini-2024-07-18:personal:legal-position-400:AT3wvKsU", temperature=0)
302
-
303
- response_format = {
304
- "type": "json_schema",
305
- "json_schema": {
306
- "name": "lp_schema",
307
- "schema": {
308
- "type": "object",
309
- "properties": {
310
- "title": {"type": "string", "description": "Title of the legal position"},
311
- "text": {"type": "string", "description": "Text of the legal position"},
312
- "proceeding": {"type": "string", "description": "Type of court proceedings"},
313
- "category": {"type": "string", "description": "Category of the legal position"},
314
- },
315
- "required": ["title", "text", "proceeding", "category"],
316
- "additionalProperties": False
317
  },
318
- "strict": True
319
- }
 
 
320
  }
 
321
 
322
- system_prompt = """
323
- Дій як кваліфікований юрист. :
324
- """
325
-
326
- prompt = f"""Дотримуйся цих інструкцій.
327
-
328
- 1. Спочатку вам буде надано текст судового рішення:
329
-
330
- <court_decision>
331
- {court_decision_text}
332
- </court_decision>
333
-
334
- 2. Уважно прочитай та проаналізуй текст наданого судового рішення. Зверни увагу на:
335
- - Юридичну суть рішення
336
- - Основне правове обґрунтування
337
- - Головні юридичні міркування
338
 
339
- 3. На основі аналізу сформулюй короткий зміст позиції суду, дотримуючись таких вказівок:
340
- - Будь чіткими, точними та обґрунтованими
341
- - Використовуй відповідну юридичну термінологію
342
- - Зберігай стислість, але повністю передай суть судового рішення
343
- - Уникай додаткових пояснень чи коментарів
344
- - Спробуй узагальнювати та уникати специфічної інформації (наприклад, імен або назв) під час подачі результатів
345
- - Використовуйте лише українську мову
346
-
347
- 4. Створи короткий заголовок, який відображає основну суть судового рішення та зазнач його категорію.
348
-
349
- 5. Додатково визнач тип судочинства, до якої відноситься дане рішення.
350
- Використовуй лише один із цих типів: 'Адміністративне судочинство', 'Кримінальне судочинство', 'Цивільне судочинство', 'Господарське судочинство'
351
-
352
- 6. Відформатуй відповідь у форматі JSON:
353
 
354
- {{
355
- "title": "Заголовок судового рішення",
356
- "text": "Текст короткого змісту позиції суду",
357
- "proceeding": "Тип судочинства",
358
- "category": "Категорія судового рішення"
359
- }}
360
 
 
 
361
  """
 
 
 
 
 
 
362
 
363
- messages = [
364
- ChatMessage(role="system", content=system_prompt),
365
- ChatMessage(role="user", content=prompt),
366
- ]
 
 
 
 
367
 
368
- response = llm_lp.chat(messages, response_format=response_format)
 
369
 
370
- try:
371
  parsed_response = json.loads(response.message.content)
372
- if "title" in parsed_response and "text" in parsed_response:
 
 
373
  return parsed_response
374
- else:
375
- return {
376
- "title": "Error: Missing required fields in response",
377
- "text": response.message.content
378
- }
 
 
379
 
380
  except json.JSONDecodeError:
381
  return {
382
  "title": "Error parsing response",
383
- "text": response.message.content
 
 
 
 
 
 
 
 
 
384
  }
385
 
386
 
@@ -449,57 +446,55 @@ def create_gradio_interface():
449
  except Exception as e:
450
  return f"Error during search: {str(e)}", None
451
 
452
- import re
453
-
454
- import re
455
 
456
  async def analyze_action(legal_position_json, question, nodes):
457
  try:
458
- workflow = CitationQueryEngineWorkflow(timeout=600)
459
- # Запускаємо workflow і отримуємо об'єкт Response
460
- response = await workflow.run(
461
- query=legal_position_json["title"] + ': ' + legal_position_json["text"] + ': ' +
462
- legal_position_json["proceeding"] + ': ' + legal_position_json["category"],
463
- question=question,
464
- nodes=nodes # Передаємо nodes у workflow
 
465
  )
466
 
467
- # Отримуємо текст відповіді з об'єкта Response
468
- response_text = str(response)
469
-
470
- # Обробка цитат у тексті відповіді
471
- citations = re.findall(r'\[(\d+)\]', response_text)
472
- unique_citations = sorted(set(citations), key=int)
473
 
 
474
  output = f"**Аналіз Штучного Інтелекту:**\n{response_text}\n\n"
475
- output += "**Цитовані джерела існуючих правових позицій Верховного Суду:**\n"
476
 
477
- # Перевіряємо наявність source_nodes в об'єкті Response
478
- source_nodes = getattr(response, 'source_nodes', [])
479
 
480
- # Проходимо по унікальних цитатах та зіставляємо з `lp_id` у source_nodes
481
- for citation in unique_citations:
482
- found = False # Змінна для відстеження, чи знайдено джерело для lp_id
483
- for index, source_node_with_score in enumerate(source_nodes, start=1):
484
- source_node = source_node_with_score.node
485
- lp_id = source_node.metadata.get('lp_id') # Отримуємо lp_id із метаданих джерела
486
 
487
- # Якщо lp_id збігається з цитатою
488
- if str(lp_id) == citation:
489
- found = True
490
- source_title = source_node.metadata.get('title', 'Невідомий заголовок')
491
- doc_ids = source_node.metadata.get('doc_id')
492
- links = get_links_html(doc_ids)
493
- links_lp = get_links_html_lp(lp_id)
494
 
495
- # Використовуємо `index` як номер джерела на початку рядка
496
- output += f"[{index}]: *{source_title}* {links_lp} 👉 Score: {source_node_with_score.score} {links}\n"
497
- break # Вихід із циклу при знайденому відповідному джерелі
 
498
 
499
- if not found:
500
- output += f"[{citation}]: Немає відповідного джерела для lp_id {citation}\n"
 
 
501
 
502
  return output
 
503
  except Exception as e:
504
  return f"Error during analysis: {str(e)}"
505
 
 
33
  from llama_index.core.prompts import PromptTemplate
34
  from llama_index.core.response_synthesizers import ResponseMode, get_response_synthesizer
35
 
36
+ from prompts import SYSTEM_PROMPT, LEGAL_POSITION_PROMPT, PRECEDENT_ANALYSIS_TEMPLATE
37
 
38
 
39
  from dotenv import load_dotenv
 
115
  state_nodes = gr.State()
116
 
117
 
118
+ class PrecedentAnalysisWorkflow(Workflow):
119
  @step
120
  async def analyze(self, ctx: Context, ev: StartEvent) -> StopEvent:
121
  query = ev.get("query") # нове рішення
 
125
  if not all([query, nodes]):
126
  return StopEvent(result="Недостатньо даних для аналізу. Необхідні нове рішення та правові позиції.")
127
 
128
+ llm_analyse = OpenAI(model="gpt-4o", temperature=0)
129
+ # llm_analyse = OpenAI(model="gpt-4o-mini", temperature=0)
130
 
131
  # Підготовка контексту та збір ID правових позицій
132
  context_parts = []
 
133
 
134
  for i, node in enumerate(nodes, 1):
135
  # Отримуємо текст з node.node якщо це NodeWithScore
 
138
  metadata = node.node.metadata if hasattr(node, 'node') else node.metadata
139
 
140
  lp_id = metadata.get('lp_id', f'unknown_{i}')
141
+ source_index = str(i)
142
 
143
+ context_parts.append(f"Source {source_index} (ID: {lp_id}):\n{node_text}")
144
 
145
  context_str = "\n\n".join(context_parts)
146
 
 
157
  "type": "object",
158
  "properties": {
159
  "lp_id": {"type": "string"},
160
+ "source_index": {"type": "string"},
161
  "description": {"type": "string"}
162
  },
163
+ "required": ["lp_id", "source_index", "description"]
164
  }
165
  }
166
  },
 
181
  ChatMessage(role="user", content=prompt)
182
  ]
183
 
184
+ response = llm_analyse.chat(
185
  messages=messages,
186
  response_format=response_format
187
  )
 
189
  try:
190
  parsed_response = json.loads(response.message.content)
191
  if "relevant_positions" in parsed_response:
192
+ # Форматуємо результат
193
+ response_lines = []
194
+
195
+ for position in parsed_response["relevant_positions"]:
196
+ position_text = (
197
+ f"* [{position['source_index']}] | Висновок: {position['description']} | Правова позиція [{position['lp_id']}]: "
198
+ )
199
+ response_lines.append(position_text)
200
+
201
+ response_text = "\n".join(response_lines)
202
+ return StopEvent(result=response_text)
203
  else:
204
+ return StopEvent(result="Помилка: відповідь не містить аналізу правових позицій")
 
 
 
205
 
206
  except json.JSONDecodeError:
207
+ return StopEvent(result="Помилка обробки відповіді від AI")
 
 
 
208
 
209
 
210
  def parse_doc_ids(doc_ids):
 
300
  return decision_text.strip()
301
 
302
 
303
+ # Constants for JSON schema
304
+ LEGAL_POSITION_SCHEMA = {
305
+ "type": "json_schema",
306
+ "json_schema": {
307
+ "name": "lp_schema",
308
+ "schema": {
309
+ "type": "object",
310
+ "properties": {
311
+ "title": {"type": "string", "description": "Title of the legal position"},
312
+ "text": {"type": "string", "description": "Text of the legal position"},
313
+ "proceeding": {"type": "string", "description": "Type of court proceedings"},
314
+ "category": {"type": "string", "description": "Category of the legal position"},
 
 
 
 
 
 
 
315
  },
316
+ "required": ["title", "text", "proceeding", "category"],
317
+ "additionalProperties": False
318
+ },
319
+ "strict": True
320
  }
321
+ }
322
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
323
 
324
+ def generate_legal_position(court_decision_text, user_question):
325
+ """
326
+ Генерує правову позицію на основі тексту судового рішення.
 
 
 
 
 
 
 
 
 
 
 
327
 
328
+ Args:
329
+ court_decision_text (str): Текст судового рішення для аналізу
330
+ user_question (str): Питання користувача (наразі не використовується)
 
 
 
331
 
332
+ Returns:
333
+ dict: Словник з правовою позицією або повідомленням про помилку
334
  """
335
+ try:
336
+ # Ініціалізація моделі
337
+ llm_lp = OpenAI(
338
+ model="ft:gpt-4o-mini-2024-07-18:personal:legal-position-400:AT3wvKsU",
339
+ temperature=0
340
+ )
341
 
342
+ # Формування повідомлень для чату
343
+ messages = [
344
+ ChatMessage(role="system", content=SYSTEM_PROMPT),
345
+ ChatMessage(
346
+ role="user",
347
+ content=LEGAL_POSITION_PROMPT.format(court_decision_text=court_decision_text)
348
+ ),
349
+ ]
350
 
351
+ # Отримання відповіді від моделі
352
+ response = llm_lp.chat(messages, response_format=LEGAL_POSITION_SCHEMA)
353
 
354
+ # Обробка відповіді
355
  parsed_response = json.loads(response.message.content)
356
+
357
+ # Перевірка наявності обов'язкових полів
358
+ if all(field in parsed_response for field in ["title", "text", "proceeding", "category"]):
359
  return parsed_response
360
+
361
+ return {
362
+ "title": "Error: Missing required fields in response",
363
+ "text": response.message.content,
364
+ "proceeding": "Unknown",
365
+ "category": "Error"
366
+ }
367
 
368
  except json.JSONDecodeError:
369
  return {
370
  "title": "Error parsing response",
371
+ "text": response.message.content,
372
+ "proceeding": "Unknown",
373
+ "category": "Error"
374
+ }
375
+ except Exception as e:
376
+ return {
377
+ "title": "Unexpected error",
378
+ "text": str(e),
379
+ "proceeding": "Unknown",
380
+ "category": "Error"
381
  }
382
 
383
 
 
446
  except Exception as e:
447
  return f"Error during search: {str(e)}", None
448
 
 
 
 
449
 
450
  async def analyze_action(legal_position_json, question, nodes):
451
  try:
452
+ workflow = PrecedentAnalysisWorkflow(timeout=600)
453
+
454
+ # Формуємо єдиний текст запиту з legal_position_json
455
+ query = (
456
+ f"{legal_position_json['title']}: "
457
+ f"{legal_position_json['text']}: "
458
+ f"{legal_position_json['proceeding']}: "
459
+ f"{legal_position_json['category']}"
460
  )
461
 
462
+ # Запускаємо workflow і отримуємо текст аналізу
463
+ response_text = await workflow.run(
464
+ query=query,
465
+ question=question,
466
+ nodes=nodes
467
+ )
468
 
469
+ # Формуємо вивід
470
  output = f"**Аналіз Штучного Інтелекту:**\n{response_text}\n\n"
471
+ output += "**Цитовані джерела існуючих правових позицій Верховного Суду:**\n\n"
472
 
473
+ # Розбиваємо текст відповіді на рядки
474
+ analysis_lines = response_text.split('\n')
475
 
476
+ # Проходимо по кожному рядку аналізу
477
+ for line in analysis_lines:
478
+ if line.startswith('* ['):
479
+ # З кожного рядка отримуємо індекс
480
+ index = line[3:line.index(']')] # Витягуємо індекс з "* [X]"
 
481
 
482
+ # Знаходимо відповідний node за індексом
483
+ node = nodes[int(index) - 1]
484
+ source_node = node.node
 
 
 
 
485
 
486
+ source_title = source_node.metadata.get('title', 'Невідомий заголовок')
487
+ source_text_lp = node.text
488
+ doc_ids = source_node.metadata.get('doc_id')
489
+ lp_id = source_node.metadata.get('lp_id')
490
 
491
+ links = get_links_html(doc_ids)
492
+ links_lp = get_links_html_lp(lp_id)
493
+
494
+ output += f"[{index}]: *{source_title}* | {source_text_lp} | {links_lp} | {links}\n\n"
495
 
496
  return output
497
+
498
  except Exception as e:
499
  return f"Error during analysis: {str(e)}"
500
 
prompts.py CHANGED
@@ -1,8 +1,47 @@
1
  from llama_index.core.prompts import PromptTemplate
2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
 
4
  PRECEDENT_ANALYSIS_TEMPLATE = PromptTemplate(
5
- "Дій як кваліфікований юрист. Проаналізуй правові позиції Верховного Суду та порівняй їх з новим рішенням.\n\n"
6
  "1. Нове рішення:\n{query}\n\n"
7
  "2. Уточнююче питання:\n{question}\n\n"
8
  "3. Правові позиції для аналізу:\n"
@@ -11,11 +50,13 @@ PRECEDENT_ANALYSIS_TEMPLATE = PromptTemplate(
11
  "\n------\n"
12
  "На основі аналізу створи список релевантних правових позицій. "
13
  "Кожна позиція повинна містити ID та короткий опис її суті. "
 
14
  "Результат надай у такому форматі JSON:\n\n"
15
  "{{\n"
16
  " \"relevant_positions\": [\n"
17
  " {{\n"
18
  " \"lp_id\": \"ID позиції\",\n"
 
19
  " \"description\": \"Короткий опис суті правової позиції\"\n"
20
  " }}\n"
21
  " ]\n"
 
1
  from llama_index.core.prompts import PromptTemplate
2
 
3
+ # System prompt
4
+ SYSTEM_PROMPT = """Дій як кваліфікований юрист."""
5
+
6
+ # Main prompt template
7
+ LEGAL_POSITION_PROMPT = """Дотримуйся цих інструкцій.
8
+
9
+ 1. Спочатку вам буде надано текст судового рішення:
10
+
11
+ <court_decision>
12
+ {court_decision_text}
13
+ </court_decision>
14
+
15
+ 2. Уважно прочитай та проаналізуй текст наданого судового рішення. Зверни увагу на:
16
+ - Юридичну суть рішення
17
+ - Основне правове обґрунтування
18
+ - Головні юридичні міркування
19
+
20
+ 3. На основі аналізу сформулюй короткий зміст позиції суду, дотримуючись таких вказівок:
21
+ - Будь чіткими, точними та обґрунтованими
22
+ - Використовуй відповідну юридичну термінологію
23
+ - Зберігай стислість, але повністю передай суть судового рішення
24
+ - Уникай додаткових пояснень чи коментарів
25
+ - Спробуй узагальнювати та уникати специфічної інформації (наприклад, імен або назв) під час подачі результатів
26
+ - Використовуйте лише українську мову
27
+
28
+ 4. Створи короткий заголовок, який відображає основну суть судового рішення та зазнач його категорію.
29
+
30
+ 5. Додатково визнач тип судочинства, до якої відноситься дане рішення.
31
+ Використовуй лише один із цих типів: 'Адміністративне судочинство', 'Кримінальне судочинство', 'Цивільне судочинство', 'Господарське судочинство'
32
+
33
+ 6. Відформатуй відповідь у форматі JSON:
34
+
35
+ {{
36
+ "title": "Заголовок судового рішення",
37
+ "text": "Текст короткого змісту позиції суду",
38
+ "proceeding": "Тип судочинства",
39
+ "category": "Категорія судового рішення"
40
+ }}
41
+ """
42
 
43
  PRECEDENT_ANALYSIS_TEMPLATE = PromptTemplate(
44
+ "Проаналізуй правові позиції Верховного Суду та порівняй їх з новим рішенням.\n\n"
45
  "1. Нове рішення:\n{query}\n\n"
46
  "2. Уточнююче питання:\n{question}\n\n"
47
  "3. Правові позиції для аналізу:\n"
 
50
  "\n------\n"
51
  "На основі аналізу створи список релевантних правових позицій. "
52
  "Кожна позиція повинна містити ID та короткий опис її суті. "
53
+ "В описі також ОБОВ'ЯЗКОВО вкажи порядковий номер позиції зі списку наданих правових позицій. "
54
  "Результат надай у такому форматі JSON:\n\n"
55
  "{{\n"
56
  " \"relevant_positions\": [\n"
57
  " {{\n"
58
  " \"lp_id\": \"ID позиції\",\n"
59
+ " \"source_index\": \"Номер позиції у списку\",\n"
60
  " \"description\": \"Короткий опис суті правової позиції\"\n"
61
  " }}\n"
62
  " ]\n"