Yasal Yapay Zeka Asistanı Oluşturma: RAG, Korkuluk ve Profesyonel Arayüz
2025'in başından bu yana yapay zeka tarafından oluşturulan içerikle ilgili 518 vaka belgelendi Halüsinasyonla ilgili görüntüler ABD mahkeme duruşmalarında sunuldu. Aynı dönemde bağımsız değerlendirmeler Westlaw AI ve LexisNexis'in Hukuk dünyasındaki en popüler iki sistem olan Lexis+, doğru yanıtlar üretir belirli yasa sorgularıyla ilgili vakaların yalnızca %65-83'ünde. Sorun yapay zeka değil: nasıl olduğu kullanıldığı ve nasıl yapıldığı.
Bu yazıda bir inşa ediyoruz Yasal Yapay Zeka Asistanı (Yasal Yardımcı Pilot) Halüsinasyon sorununu doğrudan ele alan profesyonel: Derlemde RAG desteklenmeyen yanıtları yakalamak için tescilli yasal, çok seviyeli korkuluk kaynaklardan, doğrulanabilir alıntılardan ve akış açısından optimize edilmiş Angular arayüzden avukatların çalışmaları.
Ne Öğreneceksiniz
- Yasal alan için Almayla Artırılmış Nesil (RAG) mimarisi
- Yasal bir külliyatın oluşturulması: düzenlemeler, cümleler, doktrin
- Çok seviyeli korkuluk: alıntı temellendirme, güven puanlama, ret mantığı
- Doğru ve yanıltıcı olmayan yasal yanıtlar için hızlı mühendislik
- Yanıt akışına sahip açısal avukat dostu arayüz
- Sistem kalitesini ölçmek için değerlendirme çerçevesi
Yasal Alan için RAG Mimarisi
Genel bir sohbet robotu ile profesyonel bir Yasal Yardımcı Pilot arasındaki fark şudur: RAG mimarisinde: her yanıtın belirli belgelere dayalı Modelin parametrik hafızası tarafından üretilmeyen, yasal külliyattan kurtarılan. Bu, bir sorundan kaynaklanan halüsinasyonları azaltan temel mekanizmadır. yönetilebilir bir risk altında sistemik.
from dataclasses import dataclass, field
from typing import List, Optional
from datetime import datetime
@dataclass
class LegalSource:
"""Documento sorgente recuperato per fondare la risposta."""
doc_id: str
doc_type: str # "sentenza", "legge", "regolamento", "dottrina"
title: str
citation: str # citazione formale (es. "Cass. civ. n. 12345/2024")
content_chunk: str # estratto rilevante
relevance_score: float # score di rilevanza [0, 1]
source_url: Optional[str] = None
@dataclass
class LegalQueryResult:
"""
Risultato strutturato di una query al Legal Copilot.
Ogni affermazione deve essere tracciabile a una fonte specifica.
"""
query: str
answer: str
sources: List[LegalSource]
confidence: float # confidence score complessivo [0, 1]
grounding_ratio: float # % di affermazioni supportate da fonti
uncertainty_disclaimer: str # disclaimer quando la confidenza e bassa
generated_at: datetime
model_version: str
warnings: List[str] = field(default_factory=list)
Yasal Corpus'un İnşası
Hukuki Yardımcı Pilot için külliyatın kalitesi en kritik faktördür. Yasal bir külliyat İyi yapılandırılmış İtalyanca şunları içermelidir:
- Birincil mevzuat: Medeni Kanun, Ceza Kanunu, Deneme Kanunu Medeni, özel kanunlar – mevcut (birleştirilmiş) versiyonda
- Hukuk: Yargıtay, Anayasa Mahkemesi kararları, TAR ve Danıştay, AB Adalet Divanı (AİHM)
- Yönetmelik ve genelgeler: İtalya Bankası, Consob, Gizlilik Garantörü, AGCM, INPS
- Güncellenen doktrin: Yetkili hukuk dergilerinden makaleler
import asyncio
import aiohttp
from bs4 import BeautifulSoup
from dataclasses import dataclass
from typing import List, AsyncIterator
import re
@dataclass
class RawLegalDocument:
source_id: str
doc_type: str
raw_text: str
metadata: dict
class LegalCorpusBuilder:
"""
Builder per il corpus giuridico.
Scarica e normalizza documenti da fonti ufficiali.
"""
# Normativa italiana via Normattiva (fonte ufficiale)
NORMATTIVA_API = "https://www.normattiva.it/uri-res/N2Ls"
async def fetch_normativa(self, uri: str) -> Optional[RawLegalDocument]:
"""
Scarica una norma da Normattiva (il portale ufficiale delle leggi italiane).
URI format: urn:nir:stato:legge:2023-12-31;234
"""
async with aiohttp.ClientSession() as session:
try:
async with session.get(
f"{self.NORMATTIVA_API}?urn={uri}&mimetype=text/plain",
timeout=aiohttp.ClientTimeout(total=30)
) as resp:
if resp.status == 200:
text = await resp.text()
return RawLegalDocument(
source_id=uri,
doc_type="legge",
raw_text=self._clean_normativa_text(text),
metadata={'source': 'normattiva', 'uri': uri}
)
except Exception as e:
print(f"Errore fetch {uri}: {e}")
return None
def _clean_normativa_text(self, text: str) -> str:
"""Rimuove markup e normalizza il testo normativo."""
# Rimuovi intestazioni burocratiche
text = re.sub(r'^.*?CAPO I', 'CAPO I', text, flags=re.DOTALL)
# Normalizza a capo
text = re.sub(r'\n{3,}', '\n\n', text)
# Rimuovi numeri di pagina
text = re.sub(r'\n\d+\n', '\n', text)
return text.strip()
def chunk_legal_text(
self,
doc: RawLegalDocument,
max_chars: int = 1500,
overlap_chars: int = 200
) -> List[dict]:
"""
Chunking strutturato per testi normativi.
Divide per articoli mantenendo la struttura legislativa.
"""
chunks = []
# Pattern per articoli: "Art. 1" o "Articolo 1" con varianti
article_pattern = re.compile(
r'(?:Art(?:icolo)?\.?\s+(\d+(?:\s*-\s*(?:bis|ter|quater|quinquies))?)'
r'|(\d+\.\s+))',
re.IGNORECASE
)
articles = list(article_pattern.finditer(doc.raw_text))
if not articles:
# Nessuna struttura: chunk fisso con overlap
for i in range(0, len(doc.raw_text), max_chars - overlap_chars):
chunk_text = doc.raw_text[i:i + max_chars]
chunks.append({
'content': chunk_text,
'doc_id': doc.source_id,
'doc_type': doc.doc_type,
'metadata': doc.metadata
})
else:
for idx, match in enumerate(articles):
start = match.start()
end = articles[idx + 1].start() if idx + 1 < len(articles) else len(doc.raw_text)
chunk_text = doc.raw_text[start:end].strip()
if len(chunk_text) > max_chars:
# Articolo molto lungo: suddividi ulteriormente
for j in range(0, len(chunk_text), max_chars - overlap_chars):
chunks.append({
'content': chunk_text[j:j + max_chars],
'doc_id': doc.source_id,
'article_ref': match.group(0),
'doc_type': doc.doc_type,
'metadata': doc.metadata
})
else:
chunks.append({
'content': chunk_text,
'doc_id': doc.source_id,
'article_ref': match.group(0),
'doc_type': doc.doc_type,
'metadata': doc.metadata
})
return chunks
Çok Seviyeli Korkuluklu RAG Sistemi
Legal Copilot'un ve korkuluklu RAG sisteminin kalbi: tüm sorular zorunlu değildir. bir yanıt alın. Ulaşılan kaynaklar soruyu yeterince kapsamıyorsa, sistem spekülatif bir yanıt oluşturmak yerine bunu açıkça belirtmelidir.
from langchain_openai import ChatOpenAI
from langchain.schema import SystemMessage, HumanMessage
from sentence_transformers import SentenceTransformer, util
import json
import re
from typing import List, Tuple
class LegalGuardrailSystem:
"""
Sistema di guardrail multi-livello per il Legal Copilot.
Impedisce la generazione di risposte non supportate dalle fonti.
"""
# Prompt di sistema con istruzioni esplicite anti-hallucination
SYSTEM_PROMPT = """Sei un assistente legale AI altamente specializzato.
REGOLE ASSOLUTE:
1. Rispondi SOLO basandoti sui documenti forniti nel contesto.
2. Se le fonti non coprono adeguatamente la domanda, di' esplicitamente
"Le fonti disponibili non sono sufficienti per rispondere a questa domanda."
3. Cita sempre la fonte specifica per ogni affermazione (art. X, sentenza Y).
4. Non interpretare o speculare oltre quanto dichiarato nelle fonti.
5. Usa linguaggio giuridico preciso, non parafrasare formule legali standard.
6. Segnala quando una norma potrebbe essere stata modificata di recente.
FORMATO RISPOSTA:
- Risposta strutturata con paragrafi
- Ogni affermazione seguita da [Fonte: ...]
- Conclusione con disclaimer se appropriato"""
def __init__(self, llm_model: str = "gpt-4o", embedding_model: str = "nlpaueb/legal-bert-base-uncased"):
self.llm = ChatOpenAI(model=llm_model, temperature=0.1, max_tokens=2000)
self.embedding_model = SentenceTransformer(embedding_model)
def _compute_grounding_score(
self,
answer: str,
sources: List[LegalSource]
) -> Tuple[float, List[str]]:
"""
Calcola quanto la risposta e 'fondata' nelle sorgenti.
Usa similarity semantica tra frasi della risposta e chunk delle fonti.
"""
if not sources:
return 0.0, ["Nessuna fonte disponibile"]
# Estrai frasi dalla risposta
sentences = [s.strip() for s in re.split(r'[.!?]', answer) if len(s.strip()) > 20]
if not sentences:
return 0.0, []
source_texts = [s.content_chunk for s in sources]
ungrounded = []
sentence_embeds = self.embedding_model.encode(sentences, convert_to_tensor=True)
source_embeds = self.embedding_model.encode(source_texts, convert_to_tensor=True)
grounded_count = 0
for i, sent_embed in enumerate(sentence_embeds):
max_sim = float(util.cos_sim(sent_embed, source_embeds).max())
if max_sim >= 0.65:
grounded_count += 1
else:
ungrounded.append(sentences[i])
grounding_ratio = grounded_count / len(sentences) if sentences else 0.0
return grounding_ratio, ungrounded
def _check_refusal_conditions(self, query: str, sources: List[LegalSource]) -> Optional[str]:
"""
Verifica se il sistema deve rifiutare di rispondere.
Restituisce il motivo del rifiuto o None se si può procedere.
"""
# Nessuna fonte trovata
if not sources:
return "Non ho trovato documenti rilevanti nel corpus per rispondere a questa domanda."
# Tutte le fonti hanno rilevanza molto bassa
max_relevance = max(s.relevance_score for s in sources)
if max_relevance < 0.4:
return (
f"Le fonti disponibili hanno una rilevanza troppo bassa (max: {max_relevance:.2f}) "
"per rispondere con sufficiente affidabilità."
)
# Query su consulenza legale personale specifica
advice_patterns = [
r'cosa devo fare (io|noi)', r'ho torto o ragione',
r'posso vincere la causa', r'devo (firmare|accettare|rifiutare)'
]
for pattern in advice_patterns:
if re.search(pattern, query, re.IGNORECASE):
return (
"Non posso fornire consulenza legale personalizzata. "
"Rivolgiti a un avvocato abilitato per il tuo caso specifico."
)
return None # Nessun rifiuto: procedi
async def generate_legal_answer(
self,
query: str,
retrieved_sources: List[LegalSource]
) -> LegalQueryResult:
"""
Genera una risposta legale fondata sulle sorgenti.
"""
from datetime import datetime
# Step 1: Verifica condizioni di rifiuto
refusal_reason = self._check_refusal_conditions(query, retrieved_sources)
if refusal_reason:
return LegalQueryResult(
query=query,
answer=refusal_reason,
sources=[],
confidence=0.0,
grounding_ratio=0.0,
uncertainty_disclaimer=refusal_reason,
generated_at=datetime.utcnow(),
model_version="gpt-4o-guardrailed-v1",
warnings=["RIFIUTO: " + refusal_reason]
)
# Step 2: Costruisci contesto dalle fonti
context = "\n\n---\n\n".join([
f"[{s.doc_type.upper()}] {s.citation}\n{s.content_chunk}"
for s in retrieved_sources
])
messages = [
SystemMessage(content=self.SYSTEM_PROMPT),
HumanMessage(content=f"CONTESTO NORMATIVO:\n{context}\n\nDOMANDA: {query}")
]
# Step 3: Genera risposta con LLM
response = await self.llm.ainvoke(messages)
answer = response.content
# Step 4: Calcola grounding score
grounding_ratio, ungrounded = self._compute_grounding_score(answer, retrieved_sources)
# Step 5: Genera disclaimer se grounding insufficiente
disclaimer = ""
warnings = []
if grounding_ratio < 0.7:
disclaimer = (
f"ATTENZIONE: Il {(1-grounding_ratio)*100:.0f}% delle affermazioni potrebbe "
"non essere direttamente supportato dalle fonti citate. Verificare sempre "
"con il testo normativo originale."
)
warnings.append(f"Grounding score basso: {grounding_ratio:.2%}")
confidence = (
grounding_ratio * 0.6 +
(max(s.relevance_score for s in retrieved_sources) if retrieved_sources else 0) * 0.4
)
return LegalQueryResult(
query=query,
answer=answer,
sources=retrieved_sources,
confidence=confidence,
grounding_ratio=grounding_ratio,
uncertainty_disclaimer=disclaimer,
generated_at=datetime.utcnow(),
model_version="gpt-4o-guardrailed-v1",
warnings=warnings
)
Akış ile açısal arayüz
Profesyonel bir Hukuk Yardımcı Pilotu, avukatlar için en uygun kullanıcı deneyimini sunmalıdır ve avukat yardımcıları. Arayüz kaynakları gerçek zamanlı olarak göstermeli ve genişletmenize olanak sağlamalıdır. Her bir normatif alıntı yapın ve yanıtın güven düzeyini netleştirin.
// legal-copilot.service.ts
import { Injectable, inject, signal } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable, Subject } from 'rxjs';
export interface LegalSource {
docId: string;
docType: string;
citation: string;
contentChunk: string;
relevanceScore: number;
sourceUrl?: string;
}
export interface CopilotResponse {
query: string;
answer: string;
sources: LegalSource[];
confidence: number;
groundingRatio: number;
uncertaintyDisclaimer: string;
warnings: string[];
generatedAt: string;
}
@Injectable({ providedIn: 'root' })
export class LegalCopilotService {
private http = inject(HttpClient);
private readonly API_BASE = '/api/v1/legal-copilot';
isLoading = signal(false);
currentResponse = signal<CopilotResponse | null>(null);
streamingText = signal('');
ask(query: string): Observable<CopilotResponse> {
this.isLoading.set(true);
this.streamingText.set('');
return new Observable(observer => {
// SSE per streaming della risposta
const eventSource = new EventSource(
`{this.API_BASE}/stream?query={encodeURIComponent(query)}`
);
eventSource.addEventListener('token', (e: MessageEvent) => {
this.streamingText.update(prev => prev + e.data);
});
eventSource.addEventListener('complete', (e: MessageEvent) => {
const result = JSON.parse(e.data) as CopilotResponse;
this.currentResponse.set(result);
this.isLoading.set(false);
observer.next(result);
observer.complete();
eventSource.close();
});
eventSource.addEventListener('error', () => {
this.isLoading.set(false);
observer.error(new Error('Errore nella comunicazione con il Legal Copilot'));
eventSource.close();
});
return () => eventSource.close();
});
}
}
Değerlendirme Çerçevesi
Yasal Yardımcı Pilotun kalitesini ölçmek, yasal alana özgü ölçümler gerektirir. Kullanıcı memnuniyetini ölçmek yeterli değildir: Yasal doğruluğun değerlendirilmesi gerekir.
from dataclasses import dataclass
from typing import List
import statistics
@dataclass
class EvaluationCase:
"""Un caso di valutazione con risposta attesa verificata da un esperto legale."""
query: str
reference_answer: str # risposta attesa da avvocato senior
required_citations: List[str] # citazioni che devono comparire nella risposta
forbidden_claims: List[str] # affermazioni false che non devono apparire
class LegalCopilotEvaluator:
"""
Framework di evaluation per il Legal Copilot.
Combina metriche automatiche e expert review.
"""
def evaluate_response(
self,
response: LegalQueryResult,
eval_case: EvaluationCase
) -> dict:
"""Valuta una singola risposta su più dimensioni."""
# 1. Citation recall: % delle citazioni attese presenti nella risposta
answer_lower = response.answer.lower()
found_citations = sum(
1 for cit in eval_case.required_citations
if cit.lower() in answer_lower
)
citation_recall = found_citations / len(eval_case.required_citations) if eval_case.required_citations else 1.0
# 2. Hallucination rate: % delle affermazioni false rilevate
hallucinations_found = sum(
1 for claim in eval_case.forbidden_claims
if claim.lower() in answer_lower
)
hallucination_rate = hallucinations_found / len(eval_case.forbidden_claims) if eval_case.forbidden_claims else 0.0
# 3. Refusal appropriateness (solo per query senza copertura nel corpus)
# Valutazione manuale: 1 se il rifiuto era corretto, 0 se errato
return {
'citation_recall': citation_recall,
'hallucination_rate': hallucination_rate,
'grounding_ratio': response.grounding_ratio,
'confidence': response.confidence,
'answer_length': len(response.answer),
'sources_count': len(response.sources)
}
def aggregate_evaluation(self, results: List[dict]) -> dict:
"""Aggrega i risultati di più casi di valutazione."""
return {
'avg_citation_recall': statistics.mean(r['citation_recall'] for r in results),
'avg_hallucination_rate': statistics.mean(r['hallucination_rate'] for r in results),
'avg_grounding_ratio': statistics.mean(r['grounding_ratio'] for r in results),
'avg_confidence': statistics.mean(r['confidence'] for r in results),
'total_cases': len(results)
}
Dağıtım ve Uyumluluk Konuları
Zorunlu Sorumluluk Reddi Beyanları ve Sistem Sınırlamaları
- Bu yasal tavsiye değildir: her cevaba eşlik etmelidir sistemin yasal bilgi sağladığına dair açık bir sorumluluk reddi beyanı ile kişiselleştirilmiş hukuki danışmanlık. Avukatlık mesleğinin uygulanması gizlidir nitelikli avukatlara.
- Corpus güncellemesi: yönetmelik değişir. Korpus gerekir en az haftada bir, son tarih belirtilerek güncellenecektir güncelleme kullanıcılar tarafından görülebilir.
- Denetimler için günlüğe kaydetme: tüm soru ve yanıtlar Yasal denetim ve sistemin sürekli iyileştirilmesi amacıyla oturum açılmıştır.
- Kullanıcı Sorumlulukları: sözleşmeyle şunu tanımlayın: Sistem yanıtlarına dayalı olarak alınan kararların sorumluluğu kullanıcının avukatında kalır.
Sonuçlar
Yasal Yapay Zeka Asistanı yalnızca "yasal belgelere bağlı ChatGPT" değildir. Özel RAG mimarisi, korkuluklar gerektiren karmaşık bir sistemdir. halüsinasyonlar için çok seviyeli, güncellenmiş yasal külliyat ve bir arayüz Özellikle yasal iş akışı için tasarlanmıştır.
Sektör rakamları açık: yeterli korkuluk olmadan inşa edilen sistemler Belirli hukuki sorularla ilgili vakaların %17-33'ünde halüsinasyonlara neden olurlar. ile bu makalede sunulan mimari — RAG + alıntı temellendirme + ret mantık — bu oranı önemli ölçüde azaltmak ve bir sistem oluşturmak mümkündür avukatların araştırma ve analiz aracı olarak güvenle kullanabileceği bir araçtır.
LegalTech ve AI serisi
- Sözleşme Analizi için NLP: OCR'den Anlamaya
- e-Keşif Platformu Mimarisi
- Dinamik Kural Motoru ile Uyumluluk Otomasyonu
- Yasal Anlaşmalar için Akıllı Sözleşme: Sağlamlık ve Vyper
- Üretken Yapay Zeka ile Yasal Belgelerin Özetlenmesi
- Arama Motoru Yasası: Vektör Yerleştirmeleri
- Scala'da Dijital İmza ve Belge Kimlik Doğrulaması
- Veri Gizliliği ve GDPR Uyumluluk Sistemleri
- Yasal Yapay Zeka Asistanı Oluşturma - Hukuki Yardımcı Pilot (bu makale)
- LegalTech Veri Entegrasyon Modeli







