1# ─────────────────────────────────────────────────────────────────────────────
2# QUICK-START
3# Structured-data extraction with reasoning + JSON output
4# ─────────────────────────────────────────────────────────────────────────────
5from transformers import AutoTokenizer, AutoModelForCausalLM, pipeline
6import torch, json, textwrap, inspect
7from pydantic import BaseModel
8from typing import List, Optional
9
10MODEL = "MasterControlAIML/DeepSeek-R1-Qwen2.5-3b-LLM-Judge-Reward-JSON-Unstructured-To-Structured-Lora"
11
12# 1️⃣ Inline schema (keeps the LLM on-rails) ─────────────────────────────────
13class MultipleChoice(BaseModel):
14 question: str
15 options: List[str]
16 selected: str
17
18class FormField(BaseModel):
19 fieldName: str
20 value: str
21 notes: Optional[str] = ""
22
23class Calculation(BaseModel):
24 formula: str
25 result: str
26 notes: Optional[str] = ""
27
28class Metadata(BaseModel):
29 reportDate: str
30 auditorId: Optional[str] = None
31 comments: Optional[str] = None
32
33class Content(BaseModel):
34 paragraphs: List[str]
35 tables: List["Table"] # assume Table defined elsewhere
36 checkboxes: List["Checkbox"] # 〃
37 multipleChoice: List[MultipleChoice]
38 formFields: List[FormField]
39 calculations: List[Calculation]
40 metadata: Optional[Metadata] = Metadata(reportDate="")
41
42class Section(BaseModel):
43 id: str
44 title: str
45 content: Content
46
47class Document(BaseModel):
48 documentTitle: str
49 documentDate: str
50 sections: List[Section]
51
52SCHEMA_TEXT = inspect.getsource(Document)
53
54# 2️⃣ Build prompts ──────────────────────────────────────────────────────────
55SYSTEM_PROMPT = textwrap.dedent(f"""
56 You are an expert **data-extraction assistant**.
57 Extract structured info from unstructured text **exactly** following the Pydantic schema.
58
59 ── Schema ──
60 {SCHEMA_TEXT}
61 ─────────────
62
63 Rules:
64 1. Follow the schema for keys & nesting.
65 2. Copy values verbatim when possible.
66 3. If a field is missing, return null.
67 4. Output your step-by-step reasoning first.
68 5. Then return ONLY the JSON inside this wrapper:
69 final answer[ json object: {{ ... }} ]
70
71 Format:
72 <reasoning>…</reasoning>
73 <answer>
74 final answer[ json object: {{ … }} ]
75 </answer>
76""").strip()
77
78UNSTRUCTURED_TEXT = """
79 12 April 2025 – Onsite audit performed by Jane Smith.
80 Observations: Two fire extinguishers past expiry; emergency lights functional.
81 Calculations: Total extinguishers = 8, expired = 2 → 25 % overdue.
82"""
83
84USER_PROMPT = textwrap.dedent(f"""
85 ### Task
86 Convert the following *hier* text to the schema.
87
88 ### hier
89 {UNSTRUCTURED_TEXT}
90""").strip()
91
92# 3️⃣ Generate ───────────────────────────────────────────────────────────────
93tok = AutoTokenizer.from_pretrained(MODEL, use_fast=True)
94model = AutoModelForCausalLM.from_pretrained(
95 MODEL,
96 device_map="auto",
97 torch_dtype=torch.bfloat16
98)
99gen = pipeline("text-generation", model=model, tokenizer=tok,
100 max_new_tokens=512, do_sample=False)
101
102prompt = f"<|system|>\n{SYSTEM_PROMPT}\n<|user|>\n{USER_PROMPT}"
103raw_out = gen(prompt)[0]["generated_text"]
104
105# 4️⃣ Slice out the JSON ─────────────────────────────────────────────────────
106start = raw_out.find("final answer[")
107end = raw_out.rfind("]") + 1
108json_text = raw_out[start:].split("json object:")[-1].strip(" []\n")
109data = json.loads(json_text) # ✅ Raises if malformed
110
111print(raw_out) # reasoning + JSON
112print("\n✅ Parsed object:\n", data)
1@misc{bhaviktheslider_2025_unsloth_qwen2.5_3b_grpo,
2 title = {An Unsloth-accelerated GRPO-trained Qwen 2.5-3B for JSON structuring},
3 author = {MasterControlAIML},
4 year = {2025},
5 howpublished = {\url{https://huggingface.co/MasterControlAIML/DeepSeek-R1-Qwen2.5-3b-LLM-Judge-Reward-JSON-Unstructured-To-Structured-Lora}}
6}