zieglerd's picture
Upload app.py with huggingface_hub
b47b64e verified
Raw
History Blame Contribute Delete
6.78 kB
import os
import spaces
import gradio as gr
MODEL_ID = "zieglerd/RussianConcsExt"
MAX_INPUT_TOKENS = 16_384
IS_ZERO_GPU = os.getenv("SPACES_ZERO_GPU") == "1"
tokenizer = None
model = None
load_error = None
if IS_ZERO_GPU:
try:
import torch
from peft import AutoPeftModelForCausalLM
from transformers import AutoTokenizer, BitsAndBytesConfig
tokenizer = AutoTokenizer.from_pretrained(MODEL_ID)
quantization_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_quant_type="nf4",
bnb_4bit_use_double_quant=True,
bnb_4bit_compute_dtype=torch.bfloat16,
)
model = AutoPeftModelForCausalLM.from_pretrained(
MODEL_ID,
quantization_config=quantization_config,
device_map="cuda",
dtype=torch.bfloat16,
attn_implementation="sdpa",
).eval()
except Exception as exc:
load_error = f"{type(exc).__name__}: {exc}"
print(f"Model loading failed: {load_error}", flush=True)
EXAMPLE_CONTRACT = """ДОГОВОР ПОСТАВКИ №458/26
ООО «Поставщик» обязуется поставить ООО «Заказчик»
общехозяйственные товары согласно спецификации.
Стоимость договора составляет 34 132 686,08 рублей,
в том числе НДС 5%.
Оплата производится следующим образом:
85% — авансовый платеж.
15% — после поставки товара."""
def _extract_json(text: str):
"""Parse the first complete JSON object from model output when possible."""
import json
cleaned = text.strip()
if cleaned.startswith("```"):
lines = cleaned.splitlines()
if lines and lines[0].startswith("```"):
lines = lines[1:]
if lines and lines[-1].strip() == "```":
lines = lines[:-1]
cleaned = "\n".join(lines).strip()
try:
return json.loads(cleaned)
except json.JSONDecodeError:
pass
start = cleaned.find("{")
if start < 0:
return None
depth = 0
in_string = False
escaped = False
for index, char in enumerate(cleaned[start:], start=start):
if escaped:
escaped = False
continue
if char == "\\" and in_string:
escaped = True
continue
if char == '"':
in_string = not in_string
elif not in_string:
if char == "{":
depth += 1
elif char == "}":
depth -= 1
if depth == 0:
try:
return json.loads(cleaned[start : index + 1])
except json.JSONDecodeError:
return None
return None
@spaces.GPU(duration=180, size="large")
def extract_contract(contract_text: str, max_new_tokens: int):
"""Extract structured contract data from Russian text and return JSON."""
if not contract_text or not contract_text.strip():
return {}, "", "Введите текст договора."
if load_error:
return {}, "", f"Не удалось загрузить модель: {load_error}"
if model is None or tokenizer is None:
return (
{},
"",
"Демо ожидает предоставления ZeroGPU. Код и интерфейс уже развернуты.",
)
messages = [{"role": "user", "content": contract_text.strip()}]
prompt = tokenizer.apply_chat_template(
messages,
tokenize=False,
add_generation_prompt=True,
)
inputs = tokenizer(
prompt,
return_tensors="pt",
truncation=True,
max_length=MAX_INPUT_TOKENS,
).to("cuda")
with torch.inference_mode():
output_ids = model.generate(
**inputs,
max_new_tokens=int(max_new_tokens),
do_sample=False,
repetition_penalty=1.05,
pad_token_id=tokenizer.eos_token_id,
)
generated_ids = output_ids[0, inputs["input_ids"].shape[1] :]
raw_output = tokenizer.decode(generated_ids, skip_special_tokens=True).strip()
parsed = _extract_json(raw_output)
if parsed is None:
return (
{"raw_output": raw_output},
raw_output,
"Модель вернула ответ, но он не является корректным JSON.",
)
return parsed, raw_output, "Готово. Проверьте результат перед использованием."
with gr.Blocks(title="Russian Contract Extractor") as demo:
gr.Markdown(
"""
# Извлечение данных из российских договоров
Демо LoRA-адаптера
[zieglerd/RussianConcsExt](https://huggingface.co/zieglerd/RussianConcsExt)
для Qwen2.5-32B-Instruct. Вставьте текст договора, чтобы получить
структурированный JSON.
> Результат предназначен для информационного извлечения, не является
> юридической консультацией и требует проверки.
"""
)
with gr.Row():
with gr.Column(scale=3):
contract_input = gr.Textbox(
label="Текст договора",
value=EXAMPLE_CONTRACT,
lines=18,
placeholder="Вставьте распознанный или исходный текст договора…",
)
max_tokens = gr.Slider(
minimum=256,
maximum=2048,
value=1024,
step=128,
label="Максимум токенов ответа",
)
extract_button = gr.Button("Извлечь данные", variant="primary")
with gr.Column(scale=2):
json_output = gr.JSON(label="Структурированный результат")
status_output = gr.Markdown()
with gr.Accordion("Исходный ответ модели", open=False):
raw_output = gr.Code(label="Ответ", language="json")
extract_button.click(
fn=extract_contract,
inputs=[contract_input, max_tokens],
outputs=[json_output, raw_output, status_output],
api_name="extract_contract",
)
gr.Examples(
examples=[[EXAMPLE_CONTRACT, 1024]],
inputs=[contract_input, max_tokens],
cache_examples=True,
cache_mode="lazy",
)
if __name__ == "__main__":
demo.launch(mcp_server=True)