Danielfonseca1212 commited on
Commit
e24d656
·
verified ·
1 Parent(s): 3c70859

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +113 -306
app.py CHANGED
@@ -1,327 +1,134 @@
1
- """
2
- lex-mcp Assistente Jurídico via MCP + Hugging Face
3
- Expõe ferramentas especializadas em direito para qualquer LLM host compatível com MCP.
4
- """
5
-
6
- from __future__ import annotations
7
-
8
  import os
9
- import re
10
- from typing import Any
11
-
12
  import httpx
13
- from fastmcp import FastMCP
14
- from huggingface_hub import HfApi, ModelFilter, list_models
15
- from huggingface_hub.utils import RepositoryNotFoundError
16
- from datasets import load_dataset, get_dataset_config_names, get_dataset_split_names
17
-
18
- # ── Bootstrap ─────────────────────────────────────────────────────────────────
19
-
20
- mcp = FastMCP(
21
- name="lex-mcp",
22
- instructions="""
23
- Você é LEX, um assistente jurídico especializado alimentado por modelos e datasets
24
- do Hugging Face Hub. Você possui quatro ferramentas:
25
-
26
- • search_legal_models — encontra modelos de NLP treinados em domínio jurídico
27
- • explore_legal_dataset — inspeciona datasets jurídicos (jurisprudência, leis, contratos)
28
- • analyze_legal_text — roda inferência NLP em texto jurídico (classificação, NER, resumo)
29
- • find_jurisprudence — busca decisões e ementas em datasets de jurisprudência
30
-
31
- IMPORTANTE: Sempre use as ferramentas para buscar dados atuais do Hub.
32
- Nunca invente modelos ou citações. Indique limitações quando relevante.
33
- Responda em português quando o usuário escrever em português.
34
- """,
35
- )
36
 
37
  HF_TOKEN = os.getenv("HF_TOKEN")
38
- api = HfApi(token=HF_TOKEN)
39
-
40
- # Modelos jurídicos de referência no HF Hub (curados)
41
- LEGAL_MODEL_HINTS = [
42
- "legal", "juridico", "jurídico", "law", "legislation",
43
- "bert-legal", "legalbert", "law-bert", "contracts", "court",
44
- "nlp-laval", "legal-xlm", "legalbench", "saul", "brazilianLegal",
45
- ]
46
-
47
- LEGAL_DATASET_HINTS = [
48
- "legal", "law", "court", "jurisprudence", "legislation",
49
- "contracts", "case-law", "oab", "stf", "stj", "tjsp",
50
- ]
51
-
52
-
53
- # ── Tool 1 — search_legal_models ──────────────────────────────────────────────
54
-
55
- @mcp.tool(
56
- description=(
57
- "Busca modelos de NLP especializados em domínio jurídico no Hugging Face Hub. "
58
- "Filtre por língua (ex: 'pt' para português), tarefa (ex: 'text-classification', "
59
- "'token-classification', 'summarization') e palavras-chave. "
60
- "Retorna os modelos mais baixados com metadados completos."
61
- )
62
- )
63
- def search_legal_models(
64
- query: str = "legal",
65
- language: str = "pt",
66
- task: str = "",
67
- limit: int = 8,
68
- ) -> list[dict[str, Any]]:
69
- """Retorna modelos jurídicos ordenados por downloads."""
70
-
71
- # Enriquecer query com termos jurídicos se necessário
72
- legal_query = query if any(h in query.lower() for h in LEGAL_MODEL_HINTS) else f"legal {query}"
73
-
74
- filters = ModelFilter(
75
- task=task or None,
76
- language=language or None,
77
- )
78
-
79
- results = list(
80
- list_models(
81
- filter=filters,
82
- search=legal_query,
83
- sort="downloads",
84
- direction=-1,
85
- limit=limit,
86
- token=HF_TOKEN,
87
- cardData=True,
88
- )
89
- )
90
-
91
- return [
92
- {
93
- "id": m.modelId,
94
- "task": m.pipeline_tag,
95
- "downloads": m.downloads,
96
- "likes": m.likes,
97
- "last_modified": str(m.lastModified)[:10],
98
- "tags": [t for t in (m.tags or []) if len(t) < 40][:8],
99
- "language": getattr(m, "language", None),
100
- "hf_url": f"https://huggingface.co/{m.modelId}",
101
- }
102
- for m in results
103
- ]
104
-
105
-
106
- # ── Tool 2 — explore_legal_dataset ───────────────────────────────────────────
107
-
108
- @mcp.tool(
109
- description=(
110
- "Inspeciona um dataset jurídico no Hugging Face Hub. "
111
- "Retorna configs disponíveis, splits, schema de colunas e exemplos de registros. "
112
- "Ideal para entender datasets de jurisprudência, legislação e contratos. "
113
- "Use dataset_id como 'joelniklaus/MultiLegalPile', 'lexlms/lex_glue', etc."
114
- )
115
- )
116
- def explore_legal_dataset(
117
- dataset_id: str,
118
- config: str = "default",
119
- split: str = "train",
120
- n_samples: int = 3,
121
- ) -> dict[str, Any]:
122
- """Retorna schema + amostras de um dataset jurídico."""
123
-
124
- try:
125
- configs = get_dataset_config_names(dataset_id, token=HF_TOKEN)
126
- except Exception:
127
- configs = [config]
128
-
129
- resolved_config = config if config in configs else (configs[0] if configs else None)
130
-
131
- try:
132
- splits = get_dataset_split_names(dataset_id, config_name=resolved_config, token=HF_TOKEN)
133
- except Exception:
134
- splits = [split]
135
-
136
- resolved_split = split if split in splits else (splits[0] if splits else "train")
137
 
 
 
138
  try:
139
- ds = load_dataset(
140
- dataset_id,
141
- name=resolved_config,
142
- split=f"{resolved_split}[:{n_samples}]",
143
- token=HF_TOKEN,
144
- trust_remote_code=False,
 
 
 
145
  )
146
- features = {k: str(v) for k, v in ds.features.items()}
147
- samples = ds.to_list()
148
- # Truncar textos longos para não explodir o contexto
149
- for sample in samples:
150
- for key, val in sample.items():
151
- if isinstance(val, str) and len(val) > 600:
152
- sample[key] = val[:600] + "…"
 
 
153
  except Exception as e:
154
- features = {}
155
- samples = []
156
- return {
157
- "dataset_id": dataset_id,
158
- "error": str(e),
159
- "configs_available": configs,
160
- "splits_available": splits,
161
- }
162
-
163
- return {
164
- "dataset_id": dataset_id,
165
- "hf_url": f"https://huggingface.co/datasets/{dataset_id}",
166
- "configs_available": configs,
167
- "splits_available": splits,
168
- "resolved": {"config": resolved_config, "split": resolved_split},
169
- "total_features": len(features),
170
- "features": features,
171
- "samples": samples,
172
- }
173
-
174
-
175
- # ── Tool 3 — analyze_legal_text ──────────────────────────────────────────────
176
-
177
- @mcp.tool(
178
- description=(
179
- "Roda inferência NLP em texto jurídico usando a Hugging Face Inference API. "
180
- "Tarefas suportadas: 'summarization' (resumo de decisões), "
181
- "'text-classification' (classificação de matéria/área do direito), "
182
- "'token-classification' (NER: partes, datas, valores), "
183
- "'question-answering' (responde perguntas sobre o texto). "
184
- "Se model_id não for fornecido, usa modelos jurídicos recomendados."
185
- )
186
- )
187
- def analyze_legal_text(
188
- text: str,
189
- task: str = "summarization",
190
- model_id: str = "",
191
- context: str = "",
192
- ) -> dict[str, Any]:
193
- """Executa análise NLP jurídica via Inference API."""
194
-
195
- # Modelos padrão por tarefa (jurídicos ou multilíngues de qualidade)
196
- DEFAULT_MODELS: dict[str, str] = {
197
- "summarization": "facebook/bart-large-cnn",
198
- "text-classification": "nlpaueb/legal-bert-base-uncased",
199
- "token-classification": "nlpaueb/legal-bert-base-uncased",
200
- "question-answering": "deepset/roberta-base-squad2",
201
- "fill-mask": "nlpaueb/legal-bert-base-uncased",
202
- }
203
-
204
- resolved_model = model_id or DEFAULT_MODELS.get(task, "facebook/bart-large-cnn")
205
 
206
- url = f"https://api-inference.huggingface.co/models/{resolved_model}"
207
- headers = {"Content-Type": "application/json"}
208
- if HF_TOKEN:
209
- headers["Authorization"] = f"Bearer {HF_TOKEN}"
210
- if task:
211
- headers["X-Task"] = task
212
-
213
- # Montar payload conforme a tarefa
214
- if task == "question-answering" and context:
215
- payload: dict[str, Any] = {"inputs": {"question": text, "context": context}}
216
- elif task == "summarization":
217
- # Truncar para evitar erros de tamanho máximo
218
- payload = {
219
- "inputs": text[:1024],
220
- "parameters": {"max_length": 200, "min_length": 40, "do_sample": False},
221
  }
222
- else:
 
 
 
 
 
 
 
223
  payload = {"inputs": text[:512]}
 
 
 
 
 
 
 
 
 
 
224
 
225
- with httpx.Client(timeout=60.0) as client:
226
- resp = client.post(url, headers=headers, json=payload)
227
-
228
- if resp.status_code == 503:
229
- return {
230
- "status": "model_loading",
231
- "model_id": resolved_model,
232
- "message": "Modelo está carregando. Tente novamente em 20-30 segundos.",
233
- }
234
-
235
- if resp.status_code != 200:
236
- return {
237
- "error": f"HTTP {resp.status_code}",
238
- "model_id": resolved_model,
239
- "detail": resp.text[:400],
240
- }
241
-
242
- try:
243
- result = resp.json()
244
- except Exception:
245
- result = resp.text
246
-
247
- return {
248
- "model_id": resolved_model,
249
- "task": task,
250
- "hf_url": f"https://huggingface.co/{resolved_model}",
251
- "result": result,
252
- }
253
-
254
-
255
- # ── Tool 4 — find_jurisprudence ───────────────────────────────────────────────
256
-
257
- @mcp.tool(
258
- description=(
259
- "Busca decisões judiciais e ementas em datasets de jurisprudência disponíveis "
260
- "no Hugging Face Hub. Pesquisa por palavras-chave no texto das decisões. "
261
- "Retorna ementas, tribunal, data e número do processo quando disponíveis. "
262
- "Datasets suportados: 'joelniklaus/brazilian_court_decisions', "
263
- "'lagepaul/jurisprudencia-brasil' e outros datasets jurídicos brasileiros."
264
- )
265
- )
266
- def find_jurisprudence(
267
- keywords: str,
268
- dataset_id: str = "joelniklaus/brazilian_court_decisions",
269
- max_results: int = 5,
270
- split: str = "train",
271
- ) -> dict[str, Any]:
272
- """Busca decisões judiciais por palavras-chave."""
273
-
274
- try:
275
- configs = get_dataset_config_names(dataset_id, token=HF_TOKEN)
276
- resolved_config = configs[0] if configs else None
277
- except Exception:
278
- resolved_config = None
279
-
280
  try:
281
- # Carregar slice generoso para fazer busca textual
282
  ds = load_dataset(
283
  dataset_id,
284
- name=resolved_config,
285
- split=f"{split}[:500]",
286
  token=HF_TOKEN,
287
  trust_remote_code=False,
288
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
289
  except Exception as e:
290
- return {"error": str(e), "dataset_id": dataset_id}
291
-
292
- # Identificar coluna de texto principal
293
- text_cols = [
294
- col for col in ds.column_names
295
- if any(kw in col.lower() for kw in ["text", "ementa", "decision", "body", "content", "acordao"])
296
- ]
297
- text_col = text_cols[0] if text_cols else ds.column_names[0]
298
-
299
- # Busca por keywords (case-insensitive)
300
- kw_pattern = re.compile("|".join(re.escape(k.strip()) for k in keywords.split(",")), re.IGNORECASE)
301
-
302
- matches = []
303
- for row in ds:
304
- haystack = str(row.get(text_col, ""))
305
- if kw_pattern.search(haystack):
306
- snippet = haystack[:500] + ("…" if len(haystack) > 500 else "")
307
- matches.append({
308
- "snippet": snippet,
309
- "columns": {k: str(v)[:200] for k, v in row.items() if k != text_col},
310
- })
311
- if len(matches) >= max_results:
312
- break
313
-
314
- return {
315
- "dataset_id": dataset_id,
316
- "hf_url": f"https://huggingface.co/datasets/{dataset_id}",
317
- "keywords_searched": keywords,
318
- "text_column_used": text_col,
319
- "total_matches": len(matches),
320
- "results": matches,
321
- }
322
-
323
-
324
- # ── Entry point ───────────────────────────────────────────────────────────────
 
 
 
 
 
 
 
 
 
 
 
 
325
 
326
  if __name__ == "__main__":
327
- mcp.run()
 
1
+ # app.py
2
+ import gradio as gr
 
 
 
 
 
3
  import os
 
 
 
4
  import httpx
5
+ from huggingface_hub import list_models # ← CORREÇÃO: sem ModelFilter
6
+ from datasets import load_dataset
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7
 
8
  HF_TOKEN = os.getenv("HF_TOKEN")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
 
10
+ def search_models(query="legal", language="pt", limit=5):
11
+ """Busca modelos no HF Hub"""
12
  try:
13
+ results = list(
14
+ list_models(
15
+ search=query,
16
+ language=language or None,
17
+ sort="downloads",
18
+ direction=-1,
19
+ limit=int(limit),
20
+ token=HF_TOKEN,
21
+ )
22
  )
23
+
24
+ output = ""
25
+ for m in results[:int(limit)]:
26
+ output += f"**{m.modelId}**\n"
27
+ output += f"- Task: {m.pipeline_tag}\n"
28
+ output += f"- Downloads: {m.downloads:,}\n"
29
+ output += f"- URL: https://huggingface.co/{m.modelId}\n\n"
30
+
31
+ return output if output else "Nenhum modelo encontrado."
32
  except Exception as e:
33
+ return f"Erro: {str(e)}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34
 
35
+ def analyze_text(text, task="summarization"):
36
+ """Analisa texto via Inference API"""
37
+ try:
38
+ models = {
39
+ "summarization": "facebook/bart-large-cnn",
40
+ "text-classification": "nlpaueb/legal-bert-base-uncased",
 
 
 
 
 
 
 
 
 
41
  }
42
+
43
+ model_id = models.get(task, "facebook/bart-large-cnn")
44
+ url = f"https://api-inference.huggingface.co/models/{model_id}"
45
+ headers = {"Content-Type": "application/json"}
46
+
47
+ if HF_TOKEN:
48
+ headers["Authorization"] = f"Bearer {os.getenv('HF_TOKEN')}"
49
+
50
  payload = {"inputs": text[:512]}
51
+
52
+ with httpx.Client(timeout=60.0) as client:
53
+ resp = client.post(url, headers=headers, json=payload)
54
+
55
+ if resp.status_code == 200:
56
+ return str(resp.json())
57
+ else:
58
+ return f"Erro HTTP {resp.status_code}: {resp.text[:200]}"
59
+ except Exception as e:
60
+ return f"Erro: {str(e)}"
61
 
62
+ def explore_dataset(dataset_id, n_samples=3):
63
+ """Explora dataset do HF"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
64
  try:
 
65
  ds = load_dataset(
66
  dataset_id,
67
+ split=f"train[:{n_samples}]",
 
68
  token=HF_TOKEN,
69
  trust_remote_code=False,
70
  )
71
+
72
+ output = f"**Dataset:** {dataset_id}\n\n"
73
+ output += f"**Colunas:** {', '.join(ds.column_names)}\n\n"
74
+
75
+ for i, sample in enumerate(ds.to_list()[:int(n_samples)]):
76
+ output += f"--- Amostra {i+1} ---\n"
77
+ for key, val in sample.items():
78
+ if isinstance(val, str) and len(val) > 300:
79
+ val = val[:300] + "..."
80
+ output += f"{key}: {val}\n"
81
+ output += "\n"
82
+
83
+ return output
84
  except Exception as e:
85
+ return f"Erro: {str(e)}"
86
+
87
+ # Interface Gradio
88
+ with gr.Blocks(title="LEX - Assistente Jurídico", theme=gr.themes.Soft()) as demo:
89
+ gr.Markdown("# ⚖️ LEX - Assistente Jurídico MCP")
90
+ gr.Markdown("Powered by Hugging Face Hub + FastMCP")
91
+
92
+ with gr.Tab("🔍 Buscar Modelos"):
93
+ with gr.Row():
94
+ with gr.Column():
95
+ model_query = gr.Textbox(label="Query", value="legal")
96
+ model_lang = gr.Textbox(label="Idioma", value="pt")
97
+ model_limit = gr.Slider(1, 20, value=5, step=1, label="Limite")
98
+ model_btn = gr.Button("Buscar", variant="primary")
99
+ with gr.Column():
100
+ model_output = gr.Textbox(label="Resultados", lines=15, interactive=False)
101
+
102
+ model_btn.click(fn=search_models, inputs=[model_query, model_lang, model_limit], outputs=model_output)
103
+
104
+ with gr.Tab("📝 Analisar Texto"):
105
+ with gr.Row():
106
+ with gr.Column():
107
+ text_input = gr.Textbox(label="Texto Jurídico", lines=5)
108
+ task_type = gr.Dropdown(
109
+ choices=["summarization", "text-classification"],
110
+ value="summarization",
111
+ label="Tarefa"
112
+ )
113
+ analyze_btn = gr.Button("Analisar", variant="primary")
114
+ with gr.Column():
115
+ analyze_output = gr.Textbox(label="Resultado", lines=10, interactive=False)
116
+
117
+ analyze_btn.click(fn=analyze_text, inputs=[text_input, task_type], outputs=analyze_output)
118
+
119
+ with gr.Tab("📊 Explorar Dataset"):
120
+ with gr.Row():
121
+ with gr.Column():
122
+ dataset_input = gr.Textbox(
123
+ label="Dataset ID",
124
+ value="joelniklaus/brazilian_court_decisions"
125
+ )
126
+ sample_count = gr.Slider(1, 10, value=3, step=1, label="Amostras")
127
+ dataset_btn = gr.Button("Explorar", variant="primary")
128
+ with gr.Column():
129
+ dataset_output = gr.Textbox(label="Dataset Info", lines=15, interactive=False)
130
+
131
+ dataset_btn.click(fn=explore_dataset, inputs=[dataset_input, sample_count], outputs=dataset_output)
132
 
133
  if __name__ == "__main__":
134
+ demo.launch()