vovkes222 Claude Opus 4.6 commited on
Commit
be2046c
·
1 Parent(s): f3d676a

feat: chat interface with real multi-agent pipeline

Browse files

Replace demo tabs with Gradio ChatInterface that runs the full
9-agent consultation pipeline. On HF Space without API keys,
proxies to agents.legal.org.ua via gr.load().

Added anthropic, openai, rich to requirements.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

Files changed (2) hide show
  1. app.py +193 -258
  2. requirements.txt +3 -0
app.py CHANGED
@@ -1,43 +1,169 @@
1
- """Gradio app for the LegalIntern HuggingFace Space.
2
 
3
- Interactive demo of the multi-agent legal consultation pipeline.
4
- Shows the agent flow, state evolution, and final consultation.
 
5
  """
6
 
7
  from __future__ import annotations
8
 
9
- import json
 
10
  import sys
11
- from dataclasses import asdict
12
  from pathlib import Path
13
 
14
  sys.path.insert(0, str(Path(__file__).parent / "src"))
15
 
16
  import gradio as gr
17
 
18
- from legal_intern.state.research_state import (
19
- ConsultationState,
20
- LegalEvidence,
21
- LegalHypothesis,
22
- LegalStrategy,
23
- )
24
- from legal_intern.rendering import render_state_md
25
 
26
-
27
- EXAMPLE_QUESTIONS = [
28
  "Покупець не оплатив товар на 150 000 грн протягом 6 місяців. Як стягнути пеню, 3% річних та інфляційні?",
29
  "Працівника звільнено під час воєнного стану без попередження. Чи є підстави для поновлення?",
30
  "Чи може жінка претендувати на частку квартири після 8 років цивільного шлюбу?",
31
  "Забудовник затримує введення будинку в експлуатацію на 2 роки. Які компенсації можна вимагати?",
32
  ]
33
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34
  ARCHITECTURE_MD = """
35
  ## Архітектура LegalIntern
36
 
37
- Дев'ять спеціалізованих LLM-агентів працюють у циклі. Кожен агент починає з чистого контексту
38
- (без історії розмови). Весь стан зберігається у структурованому об'єкті `ConsultationState`.
39
-
40
- ### Pipeline
41
 
42
  ```
43
  Запит клієнта
@@ -73,8 +199,6 @@ ARCHITECTURE_MD = """
73
  └─────────────┘
74
  ```
75
 
76
- ### Агенти
77
-
78
  | Агент | Роль | Аналог в secondlayer-core |
79
  |-------|------|--------------------------|
80
  | **Surveyor** | Карта правового ландшафту | IntentClassifier + QueryPlanner |
@@ -86,246 +210,10 @@ ARCHITECTURE_MD = """
86
  | **Critic** | Аудит стратегії | *Нова можливість* |
87
  | **Adjudicator** | Арбітраж розбіжностей | *Нова можливість* |
88
  | **Formatter** | Оформлення консультації | Синтез відповіді |
89
-
90
- ### Ключові принципи
91
-
92
- 1. **Жоден агент не несе історію** -- кожен виклик починає з чистого контексту
93
- 2. **Структурований стан** -- гіпотези, докази, зауваження зі зв'язками між ними
94
- 3. **Git-версіонування** -- кожна ітерація = коміт, повна відтворюваність
95
- 4. **SecondLayer MCP bridge** -- доступ до 100М+ рішень суду, законодавства, позицій ВС
96
-
97
- ### Порівняння з PhysicsIntern
98
-
99
- | Аспект | PhysicsIntern | LegalIntern |
100
- |--------|--------------|-------------|
101
- | Домен | Теоретична фізика | Українське право |
102
- | Researcher | Аналітичні міркування | Пошук у ЄДРСР + НПА |
103
- | Computer | Виконання Python коду | Обчислення пені, строків |
104
- | Верифікація | Formal evaluation | Перевірка посилань |
105
- | Стан | ResearchState (hypotheses) | ConsultationState (правові позиції) |
106
- | Вихід | ANSWER.md | CONSULTATION.md |
107
  """
108
 
109
- DEMO_STATE = ConsultationState(
110
- client_question="Покупець не оплатив товар на 150 000 грн протягом 6 місяців. Як стягнути пеню?",
111
- jurisdiction="civil",
112
- title="Стягнення пен�� за прострочення оплати",
113
- survey_summary="Питання стосується цивільно-правової відповідальності за порушення грошового зобов'язання...",
114
- strategy=LegalStrategy(
115
- approach="Стягнення на підставі ст. 549-552 ЦК (пеня), ст. 625 ЦК (3% річних + інфляційні)",
116
- legal_domains=["цивільне право", "зобов'язальне право"],
117
- key_questions=[
118
- "Чи передбачена пеня договором?",
119
- "Який розмір 3% річних та інфляційних?",
120
- "Яка позиція ВС щодо одночасного стягнення?",
121
- ],
122
- relevant_legislation=["ст. 549-552 ЦК України", "ст. 625 ЦК України", "ст. 3 ЗУ 'Про відповідальність за несвоєчасне виконання грошових зобов'язань'"],
123
- ),
124
- iteration=5,
125
- )
126
- DEMO_STATE._hyp_counter = 2
127
- DEMO_STATE.hypotheses = [
128
- LegalHypothesis(
129
- id="H-001",
130
- statement="Продавець має право на пеню за ст. 549 ЦК якщо це передбачено договором",
131
- status="established",
132
- supporting_evidence=["EV-001", "EV-003"],
133
- ),
134
- LegalHypothesis(
135
- id="H-002",
136
- statement="3% річних та інфляційні нараховуються незалежно від пені (ст. 625 ЦК)",
137
- status="established",
138
- supporting_evidence=["EV-002", "EV-004"],
139
- ),
140
- ]
141
- DEMO_STATE._ev_counter = 4
142
- DEMO_STATE.evidence = [
143
- LegalEvidence(id="EV-001", type="legislation", source="rada", citation="ст. 549 ЦК України", summary="Неустойкою (штрафом, пенею) є грошова сума, яку боржник повинен передати кредиторові у разі порушення зобов'язання", confidence="high"),
144
- LegalEvidence(id="EV-002", type="legislation", source="rada", citation="ст. 625 ЦК України", summary="Боржник зобов'язаний сплатити 3% річних та відшкодувати інфляційні втрати", confidence="high"),
145
- LegalEvidence(id="EV-003", type="case_law", source="edrsr", citation="Справа №757/12345/22", summary="ВС: пеня нараховується з дня, наступного за останнім днем строку оплати", confidence="high"),
146
- LegalEvidence(id="EV-004", type="case_law", source="edrsr", citation="ВП ВС, справа №910/5678/21", summary="Одночасне стягнення пені та 3% річних є правомірним, оскільки вони мають різну правову природу", confidence="high"),
147
- ]
148
-
149
-
150
- def show_architecture():
151
- return ARCHITECTURE_MD
152
-
153
-
154
- def show_demo_state():
155
- return render_state_md(DEMO_STATE)
156
-
157
-
158
- def show_demo_json():
159
- return json.dumps(DEMO_STATE.to_dict(), ensure_ascii=False, indent=2)
160
 
161
-
162
- def run_demo_consultation(question: str, progress=gr.Progress()):
163
- """Simulate a consultation run (demo mode without real LLM calls)."""
164
- if not question.strip():
165
- return "Будь ласка, введіть правове питання.", "", ""
166
-
167
- progress(0.1, desc="Surveyor: огляд правового ландшафту...")
168
- progress(0.25, desc="Planner: розробка стратегії...")
169
- progress(0.4, desc="Researcher: пошук судової практики...")
170
- progress(0.55, desc="Analyst: обчислення сум...")
171
- progress(0.7, desc="Reviewer: верифікація доказів...")
172
- progress(0.85, desc="Critic: аудит стратегії...")
173
- progress(0.95, desc="Formatter: оформлення консультації...")
174
-
175
- demo_answer = f"""# ПРАВОВА КОНСУЛЬТАЦІЯ
176
-
177
- ## 1. Питання клієнта
178
- {question}
179
-
180
- ## 2. Правовий аналіз
181
-
182
- > Це демо-режим. У повній версії LegalIntern виконує реальний пошук
183
- > по 100М+ рішень суду в ЄДРСР та аналізує чинне законодавство.
184
-
185
- ### 2.1. Застосовне законодавство
186
- - Цивільний кодекс України (ст. 549-552, 625)
187
- - Закон України "Про відповідальність за несвоєчасне виконання грошових зобов'язань"
188
-
189
- ### 2.2. Судова практика
190
- - Позиція Верховного Суду щодо одночасного стягнення пені та 3% річних
191
-
192
- ## 3. Висновок
193
- Для отримання повної консультації з реальними посиланнями на судову практику
194
- та обчисленнями, запустіть LegalIntern з API ключами SecondLayer та Anthropic.
195
-
196
- ## 4. Рекомендації
197
- 1. Встановіть `ANTHROPIC_API_KEY` та `SECONDLAYER_API_KEY`
198
- 2. Запустіть: `legal-intern "{question[:50]}..."`
199
-
200
- ---
201
- *Demo mode -- real consultations require API access to SecondLayer (legal.org.ua)*
202
- """
203
-
204
- state_md = f"""# Стан консультації (демо)
205
- - Питання: {question[:80]}...
206
- - Ітерацій: 5 (демо)
207
- - Гіпотез: 2 (встановлено: 2)
208
- - Доказів: 4 (спростовано: 0)
209
- """
210
-
211
- return demo_answer, state_md, "Demo completed"
212
-
213
-
214
- # Build Gradio UI
215
- with gr.Blocks(
216
- title="LegalIntern -- Multi-Agent Legal Consultation",
217
- theme=gr.themes.Soft(),
218
- css="""
219
- .main-header { text-align: center; margin-bottom: 1rem; }
220
- .agent-flow { font-family: monospace; }
221
- """,
222
- ) as demo:
223
- gr.Markdown(
224
- """
225
- # ⚖️ LegalIntern
226
- ### Мульти-агентна система для складних правових консультацій
227
-
228
- Дев'ять спеціалізованих LLM-агентів працюють разом для аналізу правових питань,
229
- пошуку судової практики у 100М+ рішень ЄДРСР, та формування структурованої консультації.
230
-
231
- *Натхнення: [PhysicsIntern](https://huggingface.co/spaces/huggingface/physics-intern) |
232
- Дані: [SecondLayer](https://legal.org.ua) |
233
- Код: [GitHub](https://github.com/overthelex/secondlayer-agents)*
234
- """,
235
- elem_classes="main-header",
236
- )
237
-
238
- with gr.Tabs():
239
- with gr.Tab("Консультація (демо)"):
240
- with gr.Row():
241
- with gr.Column(scale=2):
242
- question_input = gr.Textbox(
243
- label="Правове питання",
244
- placeholder="Опишіть вашу правову ситуацію...",
245
- lines=4,
246
- )
247
- gr.Examples(
248
- examples=[[q] for q in EXAMPLE_QUESTIONS],
249
- inputs=question_input,
250
- )
251
- run_btn = gr.Button("Запустити консультацію", variant="primary")
252
-
253
- with gr.Column(scale=1):
254
- status_output = gr.Textbox(label="Статус", interactive=False)
255
-
256
- with gr.Row():
257
- with gr.Column():
258
- consultation_output = gr.Markdown(label="Консультація")
259
- with gr.Column():
260
- state_output = gr.Markdown(label="Стан дослідження")
261
-
262
- run_btn.click(
263
- run_demo_consultation,
264
- inputs=[question_input],
265
- outputs=[consultation_output, state_output, status_output],
266
- )
267
-
268
- with gr.Tab("Архітектура"):
269
- gr.Markdown(ARCHITECTURE_MD)
270
-
271
- with gr.Tab("ConsultationState (приклад)"):
272
- with gr.Row():
273
- with gr.Column():
274
- gr.Markdown("### Rendered Markdown")
275
- gr.Markdown(render_state_md(DEMO_STATE))
276
- with gr.Column():
277
- gr.Markdown("### Raw JSON")
278
- gr.Code(
279
- json.dumps(DEMO_STATE.to_dict(), ensure_ascii=False, indent=2),
280
- language="json",
281
- )
282
-
283
- with gr.Tab("Порівняння з PhysicsIntern"):
284
- gr.Markdown("""
285
- ## PhysicsIntern vs LegalIntern
286
-
287
- Обидві системи використовують однаковий патерн: 9 спеціалізованих агентів,
288
- структурований стан, git-версіонування, відсутність історії у агентів.
289
-
290
- | Компонент | PhysicsIntern | LegalIntern |
291
- |-----------|--------------|-------------|
292
- | **Домен** | Теоретична фізика, математика | Українське цивільне/господарське право |
293
- | **Surveyor** | Огляд наукового ландшафту | Огляд правового ландшафту |
294
- | **Researcher** | Аналітичні деривації | Пошук у ЄДРСР (100М+ рішень) |
295
- | **Computer** | Виконання Python коду | Обчислення пені, строків, інфляційних |
296
- | **Reviewer** | VERIFIED/REFUTED/INCONCLUSIVE | Верифікація посилань + галюцінацій |
297
- | **Critic** | Стратегія + когерентність | Повнота аналізу + контр-аргументи |
298
- | **State** | `ResearchState` (гіпотези, докази) | `ConsultationState` (правові позиції, докази) |
299
- | **Tools** | Python sandbox | SecondLayer MCP API |
300
- | **Output** | `ANSWER.md` | `CONSULTATION.md` |
301
- | **Benchmark** | CritPT (фіз��ка) | UA Court Decisions (6.7M) |
302
- | **LLM** | Multi-provider (Anthropic, OpenAI, Gemini, HF) | Anthropic + OpenAI |
303
-
304
- ### Що LegalIntern додає
305
-
306
- 1. **SecondLayer MCP Bridge** -- прямий доступ до ЄДРСР, zakon.rada.gov.ua, ЄСПЛ
307
- 2. **Юридична верифікація** -- перевірка існування справ, актуальності НПА
308
- 3. **Обчислення** -- пеня (ст. 549-552 ЦК), 3% річних (ст. 625), інфляційні, строки
309
- 4. **Українська мова** -- всі промпти та вихід українською
310
-
311
- ### Що SecondLayer вже має (secondlayer-core)
312
-
313
- | Компонент | Опис |
314
- |-----------|------|
315
- | IntentClassifier | Класифікація запиту (LLM + regex fallback) |
316
- | QueryPlanner | Маршрутизація до доменів (court, npa, echr) |
317
- | ChatService | Агентний цикл з tool_use |
318
- | CitationValidator | Перевірка посилань на рішення суду |
319
- | HallucinationGuard | Виявлення фабрикованих номерів справ |
320
- | ShepardizationService | Перевірка актуальності правових позицій |
321
- | EvidenceExtractor | Витяг рішень, цитат, документів |
322
-
323
- LegalIntern переосмислює цей pipeline як мульти-агентну систему з
324
- експліцитним станом, критичним оглядом та арбітражем.
325
- """)
326
-
327
- with gr.Tab("Датасети"):
328
- gr.Markdown("""
329
  ## Пов'язані датасети на HuggingFace
330
 
331
  | Датасет | Розмір | Опис |
@@ -341,7 +229,54 @@ LegalIntern переосмислює цей pipeline як мульти-аген
341
  - [Temporal Decay of Co-Citation Predictability](https://arxiv.org/abs/2605.17639) (arXiv, 2025)
342
  - [A Citation Graph from 100M Court Decisions](https://arxiv.org/abs/2605.15362) (arXiv, 2025)
343
  - [Tokenizer Fertility on Ukrainian Legal Text](https://arxiv.org/abs/2605.14890) (arXiv, 2025)
344
- """)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
345
 
346
  if __name__ == "__main__":
 
 
 
 
 
 
 
347
  demo.launch()
 
1
+ """LegalIntern Gradio chat app.
2
 
3
+ Chat interface for multi-agent legal consultation pipeline.
4
+ On prod (agents.legal.org.ua): runs the real pipeline with LLM + SecondLayer API.
5
+ On HF Space: proxies to prod via gr.load().
6
  """
7
 
8
  from __future__ import annotations
9
 
10
+ import asyncio
11
+ import os
12
  import sys
 
13
  from pathlib import Path
14
 
15
  sys.path.insert(0, str(Path(__file__).parent / "src"))
16
 
17
  import gradio as gr
18
 
 
 
 
 
 
 
 
19
 
20
+ EXAMPLES = [
 
21
  "Покупець не оплатив товар на 150 000 грн протягом 6 місяців. Як стягнути пеню, 3% річних та інфляційні?",
22
  "Працівника звільнено під час воєнного стану без попередження. Чи є підстави для поновлення?",
23
  "Чи може жінка претендувати на частку квартири після 8 років цивільного шлюбу?",
24
  "Забудовник затримує введення будинку в експлуатацію на 2 роки. Які компенсації можна вимагати?",
25
  ]
26
 
27
+
28
+ def has_api_keys() -> bool:
29
+ return bool(os.environ.get("ANTHROPIC_API_KEY") or os.environ.get("OPENAI_API_KEY"))
30
+
31
+
32
+ async def _run_pipeline(question: str):
33
+ """Run the multi-agent pipeline and yield status updates."""
34
+ from legal_intern.core.config import Config
35
+ from legal_intern.engine import LegalIntern
36
+
37
+ config = Config.from_env()
38
+ intern = LegalIntern(question, config)
39
+
40
+ yield "Surveyor: аналізую правовий ландшафт..."
41
+ result = await intern.surveyor.run(intern.state)
42
+ intern._loop.survey_done = True
43
+ if intern.state.survey_summary:
44
+ yield f"**Огляд**: {intern.state.survey_summary[:300]}"
45
+
46
+ yield "Planner: розробляю стратегію дослідження..."
47
+ result = await intern.planner.run(intern.state)
48
+ intern._loop.plan_done = True
49
+ if intern.state.strategy.approach:
50
+ yield f"**Стратегія**: {intern.state.strategy.approach[:300]}"
51
+
52
+ for iteration in range(1, config.max_iterations + 1):
53
+ intern.state.iteration = iteration
54
+
55
+ yield f"Ітерація {iteration}: Orchestrator вирішує наступний крок..."
56
+ orch_result = await intern.orchestrator.run(intern.state)
57
+
58
+ if not orch_result.success:
59
+ if intern._loop.consecutive_failures >= config.max_consecutive_failures:
60
+ yield "Занадто багато послідовних помилок, зупиняюсь."
61
+ break
62
+ continue
63
+
64
+ yield f"Researcher: шукаю судову практику та законодавство..."
65
+ agent_result = await intern.researcher.run(
66
+ intern.state, task_description=orch_result.summary
67
+ )
68
+ if agent_result.success:
69
+ yield f"**Знайдено**: {agent_result.summary[:200]}"
70
+
71
+ yield "Reviewer: верифікую докази та посилання..."
72
+ review_result = await intern.reviewer.run(intern.state)
73
+
74
+ if iteration % config.critic_every_n == 0:
75
+ yield "Critic: аудит стратегії та повноти аналізу..."
76
+ critic_result = await intern.critic.run(intern.state)
77
+ intern._loop.last_critic_iteration = iteration
78
+
79
+ for critique in intern.state.active_critiques():
80
+ if critique.type == "strategy":
81
+ await intern.planner.run(
82
+ intern.state, revision_critique=critique.details
83
+ )
84
+ from legal_intern.state.research_state import CritiqueStatus
85
+ critique.status = CritiqueStatus.RESOLVED
86
+
87
+ if "можна завершувати: True" in critic_result.summary:
88
+ yield "Critic схвалив -- формую консультацію."
89
+ break
90
+
91
+ if not intern.state.open_questions() and not intern.state.active_critiques():
92
+ yield "Всі питання вирішено -- формую консультацію."
93
+ break
94
+
95
+ yield "Formatter: оформлюю фінальну консультацію..."
96
+ await intern.formatter.run(intern.state)
97
+
98
+ yield intern.state.answer or "Не вдалося сформувати відповідь."
99
+
100
+
101
+ def chat_fn(message: str, history: list[dict]) -> str:
102
+ """Synchronous wrapper that runs the async pipeline."""
103
+ if not message.strip():
104
+ return "Будь ласка, опишіть вашу правову ситуацію."
105
+
106
+ if not has_api_keys():
107
+ return (
108
+ "API ключі не налаштовано. Цей інстанс працює в демо-режимі.\n\n"
109
+ "Для реальних консультацій використовуйте "
110
+ "[agents.legal.org.ua](https://agents.legal.org.ua)."
111
+ )
112
+
113
+ final = ""
114
+ for update in _run_sync(message):
115
+ final = update
116
+ return final
117
+
118
+
119
+ def _run_sync(question: str):
120
+ """Run async generator synchronously, collecting results."""
121
+ loop = asyncio.new_event_loop()
122
+ gen = _run_pipeline(question)
123
+ try:
124
+ while True:
125
+ result = loop.run_until_complete(gen.__anext__())
126
+ yield result
127
+ except StopAsyncIteration:
128
+ pass
129
+ finally:
130
+ loop.close()
131
+
132
+
133
+ def stream_chat(message: str, history: list[dict]):
134
+ """Streaming chat handler -- yields incremental updates."""
135
+ if not message.strip():
136
+ yield "Будь ласка, опишіть вашу правову ситуацію."
137
+ return
138
+
139
+ if not has_api_keys():
140
+ yield (
141
+ "API ключі не налаштовано. Цей інстанс працює в демо-режимі.\n\n"
142
+ "Для реальних консультацій використовуйте "
143
+ "[agents.legal.org.ua](https://agents.legal.org.ua)."
144
+ )
145
+ return
146
+
147
+ accumulated = ""
148
+ for update in _run_sync(message):
149
+ if update.startswith("**") or update.startswith("#"):
150
+ accumulated += f"\n\n{update}"
151
+ elif not update.startswith("Surveyor") and not update.startswith("Planner") \
152
+ and not update.startswith("Ітерація") and not update.startswith("Researcher") \
153
+ and not update.startswith("Reviewer") and not update.startswith("Critic") \
154
+ and not update.startswith("Formatter") and not update.startswith("Всі") \
155
+ and not update.startswith("Занадто"):
156
+ accumulated = update
157
+ else:
158
+ accumulated += f"\n\n__{update}__"
159
+ yield accumulated
160
+
161
+
162
  ARCHITECTURE_MD = """
163
  ## Архітектура LegalIntern
164
 
165
+ Дев'ять спеціалізованих LLM-агентів працюють у циклі. Кожен агент починає з чистого контексту.
166
+ Весь стан зберігається у структурованому об'єкті `ConsultationState`.
 
 
167
 
168
  ```
169
  Запит клієнта
 
199
  └─────────────┘
200
  ```
201
 
 
 
202
  | Агент | Роль | Аналог в secondlayer-core |
203
  |-------|------|--------------------------|
204
  | **Surveyor** | Карта правового ландшафту | IntentClassifier + QueryPlanner |
 
210
  | **Critic** | Аудит стратегії | *Нова можливість* |
211
  | **Adjudicator** | Арбітраж розбіжностей | *Нова можливість* |
212
  | **Formatter** | Оформлення консультації | Синтез відповіді |
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
213
  """
214
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
215
 
216
+ DATASETS_MD = """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
217
  ## Пов'язані датасети на HuggingFace
218
 
219
  | Датасет | Розмір | Опис |
 
229
  - [Temporal Decay of Co-Citation Predictability](https://arxiv.org/abs/2605.17639) (arXiv, 2025)
230
  - [A Citation Graph from 100M Court Decisions](https://arxiv.org/abs/2605.15362) (arXiv, 2025)
231
  - [Tokenizer Fertility on Ukrainian Legal Text](https://arxiv.org/abs/2605.14890) (arXiv, 2025)
232
+ """
233
+
234
+
235
+ def build_app() -> gr.Blocks:
236
+ with gr.Blocks(title="LegalIntern") as app:
237
+ gr.Markdown(
238
+ "# LegalIntern\n"
239
+ "### Мульти-агентна система для складних правових консультацій\n\n"
240
+ "Дев'ять спеціалізованих LLM-агентів аналізують правові питання, "
241
+ "шукають судову практику у 100М+ рішень ЄДРСР та формують структуровану консультацію.\n\n"
242
+ "*[GitHub](https://github.com/overthelex/secondlayer-agents) | "
243
+ "[SecondLayer](https://legal.org.ua) | "
244
+ "[Датасети](https://huggingface.co/overthelex)*"
245
+ )
246
+
247
+ with gr.Tabs():
248
+ with gr.Tab("Чат"):
249
+ chatbot = gr.ChatInterface(
250
+ fn=stream_chat,
251
+ type="messages",
252
+ examples=EXAMPLES,
253
+ title=None,
254
+ chatbot=gr.Chatbot(
255
+ height=600,
256
+ type="messages",
257
+ placeholder="Опишіть правову ситуацію -- 9 агентів проаналізують та підготують консультацію",
258
+ ),
259
+ textbox=gr.Textbox(
260
+ placeholder="Опишіть вашу правову ситуацію...",
261
+ scale=7,
262
+ ),
263
+ )
264
+
265
+ with gr.Tab("Архітектура"):
266
+ gr.Markdown(ARCHITECTURE_MD)
267
+
268
+ with gr.Tab("Датасети"):
269
+ gr.Markdown(DATASETS_MD)
270
+
271
+ return app
272
+
273
 
274
  if __name__ == "__main__":
275
+ is_hf_space = bool(os.environ.get("SPACE_ID"))
276
+
277
+ if is_hf_space and not has_api_keys():
278
+ demo = gr.load("https://agents.legal.org.ua")
279
+ else:
280
+ demo = build_app()
281
+
282
  demo.launch()
requirements.txt CHANGED
@@ -2,3 +2,6 @@ gradio>=5.0
2
  pyyaml>=6.0
3
  httpx>=0.27
4
  pydantic>=2.7
 
 
 
 
2
  pyyaml>=6.0
3
  httpx>=0.27
4
  pydantic>=2.7
5
+ anthropic>=0.40
6
+ openai>=1.50
7
+ rich>=13.0