Views
No views yet
[doc:ID], and it refuses when the provided sources are
insufficient (instead of hallucinating). Built for the retrieval-augmented (RAG) leg of a
legal chat pipeline over the Ukrainian ЄДРСР state court-decision registry.Qwen/Qwen2.5-14B → domain continual-pretraining on ЄДРСР → SFT.overthelex/ua-legal-citation-grounded-sft (47,301 citation-grounded ChatML examples, 12.4% refusals, teacher-distilled + judge-filtered).[doc:ID]).
Output: a grounded Ukrainian legal answer citing only the [doc:ID] that appear in the
context, or an explicit refusal if the sources do not support an answer.| Metric | Value |
|---|---|
| citation coverage (answers that cite) | 95.7% |
| relevant citations | 96.1% |
| distractor citations | 2.35% |
| out-of-context (fabricated) citations | 3.9% |
| refusal correctness (refuses when sources insufficient) | 96.6% |
| avg citations / answer | 3.1 |
⚠️ Two things matter for correct behavior:
- Serve via HF
transformersgenerate(this is the tested path). Do not serve via vLLM without validation — a vLLM decoding bug degenerates this model.- Use
max_length ≥ 8192when tokenizing. Legal contexts run ~4-5k tokens; truncating the prompt silently drops context and collapses citation coverage.
1import torch
2from transformers import AutoModelForCausalLM, AutoTokenizer
3
4MODEL = "overthelex/ua-legal-citation-grounded-14b"
5tok = AutoTokenizer.from_pretrained(MODEL)
6model = AutoModelForCausalLM.from_pretrained(MODEL, torch_dtype=torch.bfloat16, device_map="auto").eval()
7
8SYSTEM = (
9 "Ти — український юридичний асистент. Відповідай ВИКЛЮЧНО на основі наданих витягів "
10 "із судових рішень ЄДРСР. Кожне фактичне твердження підкріплюй посиланням у форматі "
11 "[doc:ID], де ID — це edrsr_doc_id відповідного джерела. Якщо у наданих джерелах немає "
12 "достатньої підстави — прямо напиши, що наданих джерел недостатньо. Пиши українською."
13)
14
15context = [
16 {"doc_id": "114714815", "text": "...витяг із рішення суду..."},
17 {"doc_id": "77050963", "text": "...ще один витяг..."},
18]
19question = "Як суд розподіляє спільне майно подружжя при розлученні?"
20
21ctx = "\n\n".join(f"[doc:{c['doc_id']}] {c['text']}" for c in context)
22user = (f"Питання: {question}\n\nДжерела (витяги з рішень ЄДРСР):\n{ctx}\n\n"
23 "Дай обґрунтовану відповідь українською з посиланнями [doc:ID].")
24
25msgs = [{"role": "system", "content": SYSTEM}, {"role": "user", "content": user}]
26ids = tok.apply_chat_template(msgs, add_generation_prompt=True, return_tensors="pt",
27 truncation=True, max_length=8192).to(model.device) # <-- 8192, not 3500
28out = model.generate(ids, max_new_tokens=512, do_sample=False, pad_token_id=tok.pad_token_id)
29answer = tok.decode(out[0, ids.shape[1]:], skip_special_tokens=True)
30print(answer)[doc:ID] not present in the context you
provided (belt-and-suspenders against fabrication):1import re
2def strip_ungrounded(answer, context_doc_ids):
3 valid = {str(x) for x in context_doc_ids}
4 def repl(m):
5 kept = [i for i in re.findall(r"\d+", m.group(1)) if i in valid]
6 return f"[doc:{', doc:'.join(kept)}]" if kept else ""
7 return re.sub(r"\[doc:\s*([^\]]+?)\s*\]", repl, answer)edrsr_doc_id; the model learned the current (2026) corpus, so feed it current-corpus
contexts.Qwen2.5-14B continually pretrained on ЄДРСР) → LoRA SFT (r16, α32, 2 epochs,
lr 1e-4, max-len 4096, bf16, DeepSpeed ZeRO-2) on 47.3K citation-grounded ChatML examples.
Dataset regenerated on the current ЄДРСР vector store with a self-hosted Qwen2.5-72B teacher
and a faithfulness judge (programmatic citation check + LLM judge). Ukrainian legal-tech
project (SecondLayer / legal.org.ua).