初始化项目,由ModelHub XC社区提供模型
Model: ratulsur/multi-format-finance-parser Source: Original Platform
This commit is contained in:
36
.gitattributes
vendored
Normal file
36
.gitattributes
vendored
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
*.7z filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.arrow filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.bin filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.bz2 filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.ckpt filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.ftz filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.gz filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.h5 filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.joblib filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.lfs.* filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.mlmodel filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.model filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.msgpack filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.npy filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.npz filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.onnx filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.ot filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.parquet filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.pb filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.pickle filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.pkl filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.pt filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.pth filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.rar filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
||||||
|
saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.tar.* filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.tar filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.tflite filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.tgz filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.wasm filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.xz filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.zip filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.zst filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
||||||
|
tokenizer.json filter=lfs diff=lfs merge=lfs -text
|
||||||
22
Dockerfile
Normal file
22
Dockerfile
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
FROM python:3.11-slim
|
||||||
|
|
||||||
|
ENV PYTHONDONTWRITEBYTECODE=1 \
|
||||||
|
PYTHONUNBUFFERED=1
|
||||||
|
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
|
curl \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY requirements.txt .
|
||||||
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
|
COPY app.py .
|
||||||
|
|
||||||
|
EXPOSE 7860
|
||||||
|
|
||||||
|
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
|
||||||
|
CMD curl -f http://localhost:7860/ || exit 1
|
||||||
|
|
||||||
|
CMD ["python", "app.py"]
|
||||||
306
README.md
Normal file
306
README.md
Normal file
@@ -0,0 +1,306 @@
|
|||||||
|
---
|
||||||
|
license: apache-2.0
|
||||||
|
language:
|
||||||
|
- en
|
||||||
|
base_model: Qwen/Qwen2.5-7B-Instruct
|
||||||
|
pipeline_tag: text-generation
|
||||||
|
tags:
|
||||||
|
- finance
|
||||||
|
- document-parsing
|
||||||
|
- qlora
|
||||||
|
- qwen2.5
|
||||||
|
- invoice
|
||||||
|
- sap
|
||||||
|
- structured-extraction
|
||||||
|
- json
|
||||||
|
---
|
||||||
|
|
||||||
|
# 🏦 Multi-Format Finance Document Parser
|
||||||
|
|
||||||
|
A production-grade financial document parser fine-tuned on **Qwen2.5-7B-Instruct** using **QLoRA (4-bit NF4 quantization)**. Given raw text from any financial document, it outputs structured JSON — ready for downstream processing, ERP integration, or analytics pipelines.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 Live Demo
|
||||||
|
|
||||||
|
👉 [Try it on HuggingFace Spaces](https://huggingface.co/spaces/ratulsur/finance-parser-demo)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📄 Supported Document Types
|
||||||
|
|
||||||
|
| Format | Examples |
|
||||||
|
|---|---|
|
||||||
|
| **Invoice** | Vendor invoices, GST bills, service bills |
|
||||||
|
| **SAP Report** | ALV exports, FI vendor payment reports |
|
||||||
|
| **Income Statement** | P&L statements, quarterly earnings |
|
||||||
|
| **Balance Sheet** | Assets, liabilities, equity statements |
|
||||||
|
| **Bank Statement** | Transaction records, account summaries |
|
||||||
|
| **Purchase Order** | PO documents, procurement records |
|
||||||
|
| **SQL Result** | Query outputs from finance databases |
|
||||||
|
| **CSV / Excel** | Tabular finance data |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🧠 Model Details
|
||||||
|
|
||||||
|
| Property | Value |
|
||||||
|
|---|---|
|
||||||
|
| **Base model** | Qwen/Qwen2.5-7B-Instruct |
|
||||||
|
| **Model size** | 8B parameters |
|
||||||
|
| **Fine-tuning method** | QLoRA (PEFT) |
|
||||||
|
| **Quantization** | 4-bit NF4 + double quantization |
|
||||||
|
| **Compute dtype** | bfloat16 |
|
||||||
|
| **LoRA rank** | r=8, alpha=16 |
|
||||||
|
| **Max sequence length** | 512 tokens |
|
||||||
|
| **Training hardware** | L40S 48GB GPU (Lightning AI) |
|
||||||
|
| **Training time** | ~1 hour |
|
||||||
|
| **License** | Apache 2.0 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📊 Training Data
|
||||||
|
|
||||||
|
| Dataset | Samples | Type |
|
||||||
|
|---|---|---|
|
||||||
|
| [CORD-v2](https://huggingface.co/datasets/naver-clova-ix/cord-v2) | 454 | Real receipt images + structured JSON |
|
||||||
|
| Synthetic invoices | 300 | Generated with realistic Indian/global vendors |
|
||||||
|
| Synthetic SAP reports | 100 | ALV-style pipe-delimited exports |
|
||||||
|
| Synthetic income statements | 100 | P&L with revenue, COGS, EBIT, net income |
|
||||||
|
| **Total** | **954** | Train: 812 · Eval: 95 · Test: 47 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ⚙️ Quantization Techniques
|
||||||
|
|
||||||
|
| Technique | Purpose |
|
||||||
|
|---|---|
|
||||||
|
| **NF4 4-bit quantization** | Stores weights in 4-bit NormalFloat format — ~4x model size reduction |
|
||||||
|
| **Double quantization** | Quantizes the quantization constants — additional ~0.4 bits/param saving |
|
||||||
|
| **bfloat16 compute** | Full precision operations, 4-bit storage |
|
||||||
|
| **LoRA adapters (r=8)** | Only 0.5% of parameters trained — 99.5% frozen |
|
||||||
|
| **Paged AdamW 8-bit** | Optimizer state memory reduction |
|
||||||
|
| **Gradient checkpointing** | ~40% activation memory reduction |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📤 Output Schema
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"document_type": "invoice|balance_sheet|income_stmt|sap_report|sql_result|bank_statement|purchase_order",
|
||||||
|
"vendor": "string or null",
|
||||||
|
"client": "string or null",
|
||||||
|
"date": "YYYY-MM-DD or null",
|
||||||
|
"due_date": "YYYY-MM-DD or null",
|
||||||
|
"document_id": "string or null",
|
||||||
|
"currency": "USD|EUR|INR|GBP|...",
|
||||||
|
"subtotal": "float or null",
|
||||||
|
"tax_amount": "float or null",
|
||||||
|
"tax_rate_pct": "float or null",
|
||||||
|
"total_amount": "float or null",
|
||||||
|
"line_items": [
|
||||||
|
{
|
||||||
|
"description": "string",
|
||||||
|
"quantity": "float or null",
|
||||||
|
"unit_price": "float or null",
|
||||||
|
"amount": "float"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"payment_terms": "string or null",
|
||||||
|
"notes": "string or null",
|
||||||
|
"metadata": {}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 💻 Usage
|
||||||
|
|
||||||
|
### Via HuggingFace Inference API
|
||||||
|
|
||||||
|
```python
|
||||||
|
import requests
|
||||||
|
import json
|
||||||
|
import re
|
||||||
|
|
||||||
|
API_URL = "https://api-inference.huggingface.co/models/ratulsur/multi-format-finance-parser"
|
||||||
|
HF_TOKEN = "hf_xxxxxxxxxxxx"
|
||||||
|
|
||||||
|
SYSTEM_PROMPT = """You are a production financial document parser.
|
||||||
|
Given raw text from any financial document, output ONLY a single valid JSON object.
|
||||||
|
Schema: {document_type, vendor, client, date (YYYY-MM-DD), due_date, document_id,
|
||||||
|
currency, subtotal, tax_amount, tax_rate_pct, total_amount,
|
||||||
|
line_items:[{description,quantity,unit_price,amount}], payment_terms, notes, metadata}.
|
||||||
|
All monetary values must be floats. Unknown fields → null. No explanation."""
|
||||||
|
|
||||||
|
def parse_document(text: str) -> dict:
|
||||||
|
prompt = (
|
||||||
|
f"<|im_start|>system\n{SYSTEM_PROMPT}<|im_end|>\n"
|
||||||
|
f"<|im_start|>user\nParse this financial document:\n\n{text}<|im_end|>\n"
|
||||||
|
f"<|im_start|>assistant\n"
|
||||||
|
)
|
||||||
|
headers = {"Authorization": f"Bearer {HF_TOKEN}"}
|
||||||
|
payload = {
|
||||||
|
"inputs": prompt,
|
||||||
|
"parameters": {
|
||||||
|
"max_new_tokens": 512,
|
||||||
|
"temperature": 0.05,
|
||||||
|
"return_full_text": False,
|
||||||
|
"do_sample": False,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resp = requests.post(API_URL, headers=headers, json=payload, timeout=120)
|
||||||
|
raw = resp.json()[0]["generated_text"].strip()
|
||||||
|
raw = re.sub(r"```json\s*|```\s*", "", raw).strip()
|
||||||
|
return json.loads(raw)
|
||||||
|
|
||||||
|
# Example
|
||||||
|
invoice = """
|
||||||
|
INVOICE
|
||||||
|
Vendor: Tata Consultancy Services Ltd.
|
||||||
|
Invoice No: TCS-2024-8821
|
||||||
|
Date: 2024-11-15
|
||||||
|
Service: Cloud Infrastructure Management INR 42,500.00
|
||||||
|
GST @ 18%: INR 7,650.00
|
||||||
|
TOTAL DUE: INR 50,150.00
|
||||||
|
Payment Terms: Net 30
|
||||||
|
"""
|
||||||
|
|
||||||
|
result = parse_document(invoice)
|
||||||
|
print(json.dumps(result, indent=2))
|
||||||
|
```
|
||||||
|
|
||||||
|
### Expected output
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"document_type": "invoice",
|
||||||
|
"vendor": "Tata Consultancy Services Ltd.",
|
||||||
|
"client": null,
|
||||||
|
"date": "2024-11-15",
|
||||||
|
"due_date": null,
|
||||||
|
"document_id": "TCS-2024-8821",
|
||||||
|
"currency": "INR",
|
||||||
|
"subtotal": 42500.0,
|
||||||
|
"tax_amount": 7650.0,
|
||||||
|
"tax_rate_pct": 18.0,
|
||||||
|
"total_amount": 50150.0,
|
||||||
|
"line_items": [
|
||||||
|
{
|
||||||
|
"description": "Cloud Infrastructure Management",
|
||||||
|
"quantity": 1,
|
||||||
|
"unit_price": 42500.0,
|
||||||
|
"amount": 42500.0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"payment_terms": "Net 30",
|
||||||
|
"notes": null,
|
||||||
|
"metadata": {}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Load locally with transformers
|
||||||
|
|
||||||
|
```python
|
||||||
|
from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig
|
||||||
|
import torch
|
||||||
|
|
||||||
|
bnb_config = BitsAndBytesConfig(
|
||||||
|
load_in_4bit=True,
|
||||||
|
bnb_4bit_quant_type="nf4",
|
||||||
|
bnb_4bit_use_double_quant=True,
|
||||||
|
bnb_4bit_compute_dtype=torch.bfloat16,
|
||||||
|
)
|
||||||
|
|
||||||
|
model = AutoModelForCausalLM.from_pretrained(
|
||||||
|
"ratulsur/multi-format-finance-parser",
|
||||||
|
quantization_config=bnb_config,
|
||||||
|
device_map="auto",
|
||||||
|
trust_remote_code=True,
|
||||||
|
)
|
||||||
|
tokenizer = AutoTokenizer.from_pretrained(
|
||||||
|
"ratulsur/multi-format-finance-parser",
|
||||||
|
trust_remote_code=True,
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🏗️ Training Setup
|
||||||
|
|
||||||
|
```python
|
||||||
|
# QLoRA config
|
||||||
|
bnb_config = BitsAndBytesConfig(
|
||||||
|
load_in_4bit=True,
|
||||||
|
bnb_4bit_quant_type="nf4",
|
||||||
|
bnb_4bit_use_double_quant=True,
|
||||||
|
bnb_4bit_compute_dtype=torch.bfloat16,
|
||||||
|
)
|
||||||
|
|
||||||
|
lora_config = LoraConfig(
|
||||||
|
r=8,
|
||||||
|
lora_alpha=16,
|
||||||
|
target_modules=["q_proj","k_proj","v_proj","o_proj",
|
||||||
|
"gate_proj","up_proj","down_proj"],
|
||||||
|
lora_dropout=0.05,
|
||||||
|
bias="none",
|
||||||
|
task_type=TaskType.CAUSAL_LM,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Training args
|
||||||
|
SFTConfig(
|
||||||
|
num_train_epochs=3,
|
||||||
|
per_device_train_batch_size=1,
|
||||||
|
gradient_accumulation_steps=8,
|
||||||
|
learning_rate=2e-4,
|
||||||
|
lr_scheduler_type="cosine",
|
||||||
|
optim="paged_adamw_8bit",
|
||||||
|
bf16=True,
|
||||||
|
gradient_checkpointing=True,
|
||||||
|
max_length=512,
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📁 Repository Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
ratulsur/multi-format-finance-parser/
|
||||||
|
├── model.safetensors # Merged model weights (15.2 GB)
|
||||||
|
├── config.json # Model configuration
|
||||||
|
├── tokenizer.json # Tokenizer
|
||||||
|
├── tokenizer_config.json # Tokenizer configuration
|
||||||
|
├── chat_template.jinja # Chat template
|
||||||
|
└── generation_config.json # Generation configuration
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ⚠️ Limitations
|
||||||
|
|
||||||
|
- Trained primarily on English financial documents
|
||||||
|
- Best performance on structured text (not handwritten documents)
|
||||||
|
- OCR quality affects accuracy for scanned documents
|
||||||
|
- SAP reports tested on ALV-style exports only
|
||||||
|
- 954 training samples — production use should involve more data
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔗 Links
|
||||||
|
|
||||||
|
- **Live Demo:** [HuggingFace Spaces](https://huggingface.co/spaces/ratulsur/finance-parser-demo)
|
||||||
|
- **Base Model:** [Qwen2.5-7B-Instruct](https://huggingface.co/Qwen/Qwen2.5-7B-Instruct)
|
||||||
|
- **Training Dataset:** [CORD-v2](https://huggingface.co/datasets/naver-clova-ix/cord-v2)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 👤 Author
|
||||||
|
|
||||||
|
**Ratul Sur**
|
||||||
|
- HuggingFace: [ratulsur](https://huggingface.co/ratulsur)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*If you find this model useful, please give it a ⭐ like on HuggingFace!*
|
||||||
233
app.py
Normal file
233
app.py
Normal file
@@ -0,0 +1,233 @@
|
|||||||
|
import gradio as gr
|
||||||
|
import requests
|
||||||
|
import json
|
||||||
|
import re
|
||||||
|
import os
|
||||||
|
|
||||||
|
# ── Config ─────────────────────────────────────────────────────
|
||||||
|
MODEL_ID = "ratulsur/multi-format-finance-parser"
|
||||||
|
API_URL = f"https://api-inference.huggingface.co/models/{MODEL_ID}"
|
||||||
|
HF_TOKEN = os.environ.get("HF_TOKEN", "")
|
||||||
|
|
||||||
|
SYSTEM_PROMPT = """You are a production financial document parser.
|
||||||
|
Given raw text from any financial document, output ONLY a single valid JSON object.
|
||||||
|
Schema: {document_type, vendor, client, date (YYYY-MM-DD), due_date, document_id,
|
||||||
|
currency, subtotal, tax_amount, tax_rate_pct, total_amount,
|
||||||
|
line_items:[{description,quantity,unit_price,amount}], payment_terms, notes, metadata}.
|
||||||
|
All monetary values must be floats. Unknown fields → null. No explanation."""
|
||||||
|
|
||||||
|
# ── Inference ──────────────────────────────────────────────────
|
||||||
|
def call_api(text: str) -> dict:
|
||||||
|
prompt = (
|
||||||
|
f"<|im_start|>system\n{SYSTEM_PROMPT}<|im_end|>\n"
|
||||||
|
f"<|im_start|>user\nParse this financial document:\n\n{text}<|im_end|>\n"
|
||||||
|
f"<|im_start|>assistant\n"
|
||||||
|
)
|
||||||
|
headers = {
|
||||||
|
"Authorization": f"Bearer {HF_TOKEN}",
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
}
|
||||||
|
payload = {
|
||||||
|
"inputs": prompt,
|
||||||
|
"parameters": {
|
||||||
|
"max_new_tokens": 512,
|
||||||
|
"temperature": 0.05,
|
||||||
|
"return_full_text": False,
|
||||||
|
"do_sample": False,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
resp = requests.post(API_URL, headers=headers, json=payload, timeout=120)
|
||||||
|
resp.raise_for_status()
|
||||||
|
raw = resp.json()[0]["generated_text"].strip()
|
||||||
|
raw = re.sub(r"```json\s*|```\s*", "", raw).strip()
|
||||||
|
|
||||||
|
# JSON repair heuristics
|
||||||
|
try:
|
||||||
|
return json.loads(raw)
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
raw = (raw
|
||||||
|
.replace("'", '"')
|
||||||
|
.replace("None", "null")
|
||||||
|
.replace("True", "true")
|
||||||
|
.replace("False", "false")
|
||||||
|
.replace(",\n}", "\n}")
|
||||||
|
.replace(",\n]", "\n]"))
|
||||||
|
match = re.search(r"\{.*\}", raw, re.DOTALL)
|
||||||
|
try:
|
||||||
|
return json.loads(match.group() if match else raw)
|
||||||
|
except Exception:
|
||||||
|
return {"error": "Could not parse model output", "raw": raw}
|
||||||
|
|
||||||
|
|
||||||
|
# ── Main processing function ───────────────────────────────────
|
||||||
|
def process(text_input: str, doc_hint: str):
|
||||||
|
if not text_input.strip():
|
||||||
|
return "⚠️ Please paste some document text.", ""
|
||||||
|
|
||||||
|
try:
|
||||||
|
text = text_input.strip()
|
||||||
|
if doc_hint and doc_hint != "Auto-detect":
|
||||||
|
text = f"[Document type: {doc_hint}]\n\n{text}"
|
||||||
|
|
||||||
|
result = call_api(text)
|
||||||
|
|
||||||
|
# Build summary
|
||||||
|
summary = []
|
||||||
|
if result.get("error"):
|
||||||
|
return f"❌ Error: {result['error']}", json.dumps(result, indent=2)
|
||||||
|
if result.get("document_type"):
|
||||||
|
summary.append(f"**Type:** {result['document_type']}")
|
||||||
|
if result.get("vendor"):
|
||||||
|
summary.append(f"**Vendor:** {result['vendor']}")
|
||||||
|
if result.get("client"):
|
||||||
|
summary.append(f"**Client:** {result['client']}")
|
||||||
|
if result.get("date"):
|
||||||
|
summary.append(f"**Date:** {result['date']}")
|
||||||
|
if result.get("due_date"):
|
||||||
|
summary.append(f"**Due date:** {result['due_date']}")
|
||||||
|
if result.get("document_id"):
|
||||||
|
summary.append(f"**Document ID:** {result['document_id']}")
|
||||||
|
if result.get("currency") and result.get("total_amount") is not None:
|
||||||
|
summary.append(f"**Total:** {result['currency']} {result['total_amount']:,.2f}")
|
||||||
|
if result.get("tax_amount") is not None:
|
||||||
|
summary.append(f"**Tax:** {result.get('currency','')} {result['tax_amount']:,.2f}")
|
||||||
|
if result.get("line_items"):
|
||||||
|
summary.append(f"**Line items:** {len(result['line_items'])}")
|
||||||
|
if result.get("payment_terms"):
|
||||||
|
summary.append(f"**Payment terms:** {result['payment_terms']}")
|
||||||
|
|
||||||
|
return "\n\n".join(summary), json.dumps(result, indent=2, ensure_ascii=False)
|
||||||
|
|
||||||
|
except requests.exceptions.Timeout:
|
||||||
|
return "⚠️ Model is loading, please wait 20 seconds and try again.", ""
|
||||||
|
except requests.exceptions.HTTPError as e:
|
||||||
|
return f"❌ API Error: {e}", ""
|
||||||
|
except Exception as e:
|
||||||
|
return f"❌ Error: {e}", ""
|
||||||
|
|
||||||
|
|
||||||
|
# ── Examples ───────────────────────────────────────────────────
|
||||||
|
EXAMPLES = [
|
||||||
|
["""INVOICE
|
||||||
|
Vendor: Tata Consultancy Services Ltd.
|
||||||
|
Invoice No: TCS-2024-8821
|
||||||
|
Date: 2024-11-15
|
||||||
|
Due Date: 2024-12-15
|
||||||
|
Bill To: Reliance Industries Ltd.
|
||||||
|
|
||||||
|
Service: Cloud Infrastructure Management (Oct 2024) INR 42,500.00
|
||||||
|
Service: SAP Integration Support INR 18,000.00
|
||||||
|
GST @ 18%: INR 10,890.00
|
||||||
|
TOTAL DUE: INR 71,390.00
|
||||||
|
Payment Terms: Net 30""", "Invoice"],
|
||||||
|
|
||||||
|
["""SAP FI - VENDOR PAYMENT REPORT
|
||||||
|
Company Code: 1000 | Fiscal Year: 2024
|
||||||
|
Run Date: 2024-09-30
|
||||||
|
|
||||||
|
|DocNo |Vendor |Amount |Curr|Status |
|
||||||
|
|----------|--------------------|--------------|----|--------|
|
||||||
|
|1900045621|Wipro Limited | 4,25,000.00 |INR |Open |
|
||||||
|
|1900045622|HCL Technologies | 2,10,500.00 |INR |Cleared |
|
||||||
|
|1900045623|Infosys BPO | 8,75,200.00 |INR |Open |
|
||||||
|
|
||||||
|
Total: 15,10,700.00 INR""", "SAP Report"],
|
||||||
|
|
||||||
|
["""INCOME STATEMENT
|
||||||
|
Reliance Industries Ltd.
|
||||||
|
Period ending: 2024-09-30
|
||||||
|
(in INR)
|
||||||
|
|
||||||
|
Revenue: 50,000,000.00
|
||||||
|
Cost of Revenue: (22,000,000.00)
|
||||||
|
Gross Profit: 28,000,000.00
|
||||||
|
Operating Expenses: (12,000,000.00)
|
||||||
|
EBIT: 16,000,000.00
|
||||||
|
Income Tax 25%: (4,000,000.00)
|
||||||
|
Net Income: 12,000,000.00""", "Income Statement"],
|
||||||
|
|
||||||
|
["""PURCHASE ORDER
|
||||||
|
PO Number: PO-2024-00456
|
||||||
|
Date: 2024-10-01
|
||||||
|
Vendor: Amazon Web Services India
|
||||||
|
Ship To: HDFC Bank Ltd., Mumbai
|
||||||
|
|
||||||
|
Item 1: EC2 Reserved Instances (1yr) USD 12,000.00
|
||||||
|
Item 2: S3 Storage 50TB USD 1,800.00
|
||||||
|
Item 3: RDS Multi-AZ USD 4,200.00
|
||||||
|
Subtotal: USD 18,000.00
|
||||||
|
Tax: USD 0.00
|
||||||
|
Total: USD 18,000.00
|
||||||
|
Payment Terms: Net 45""", "Purchase Order"],
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# ── UI ─────────────────────────────────────────────────────────
|
||||||
|
with gr.Blocks(
|
||||||
|
title="Multi-Format Finance Parser",
|
||||||
|
theme=gr.themes.Soft(),
|
||||||
|
css=".json-output { font-family: monospace; font-size: 13px; }"
|
||||||
|
) as demo:
|
||||||
|
|
||||||
|
gr.Markdown("""
|
||||||
|
# 🏦 Multi-Format Finance Document Parser
|
||||||
|
**Production-grade** financial document extraction → structured JSON.
|
||||||
|
|
||||||
|
Supports: **Invoice · SAP Report · Income Statement · Bank Statement · Purchase Order · SQL results**
|
||||||
|
|
||||||
|
*Fine-tuned Qwen2.5-7B-Instruct · QLoRA 4-bit NF4 · Trained on CORD-v2 + synthetic finance data*
|
||||||
|
""")
|
||||||
|
|
||||||
|
with gr.Row():
|
||||||
|
with gr.Column(scale=1):
|
||||||
|
text_in = gr.Textbox(
|
||||||
|
label="Paste document text",
|
||||||
|
lines=16,
|
||||||
|
placeholder="Paste your invoice, SAP export, income statement, or any financial document here...",
|
||||||
|
)
|
||||||
|
hint_in = gr.Dropdown(
|
||||||
|
choices=[
|
||||||
|
"Auto-detect",
|
||||||
|
"Invoice",
|
||||||
|
"SAP Report",
|
||||||
|
"Balance Sheet",
|
||||||
|
"Income Statement",
|
||||||
|
"Bank Statement",
|
||||||
|
"Purchase Order",
|
||||||
|
"SQL Result",
|
||||||
|
],
|
||||||
|
value="Auto-detect",
|
||||||
|
label="Document type hint (optional)",
|
||||||
|
)
|
||||||
|
parse_btn = gr.Button("Parse Document", variant="primary", size="lg")
|
||||||
|
|
||||||
|
with gr.Column(scale=1):
|
||||||
|
summary_out = gr.Markdown(label="Summary")
|
||||||
|
json_out = gr.Code(
|
||||||
|
label="Structured JSON output",
|
||||||
|
language="json",
|
||||||
|
lines=18,
|
||||||
|
)
|
||||||
|
|
||||||
|
gr.Markdown("### Try an example")
|
||||||
|
gr.Examples(
|
||||||
|
examples=EXAMPLES,
|
||||||
|
inputs=[text_in, hint_in],
|
||||||
|
label="Click any example to load it",
|
||||||
|
)
|
||||||
|
|
||||||
|
gr.Markdown("""
|
||||||
|
---
|
||||||
|
**Model:** [ratulsur/multi-format-finance-parser](https://huggingface.co/ratulsur/multi-format-finance-parser)
|
||||||
|
**Training:** QLoRA (4-bit NF4 double quantization) on Qwen2.5-7B-Instruct
|
||||||
|
**Dataset:** CORD-v2 receipts + synthetic invoices, SAP reports, income statements
|
||||||
|
""")
|
||||||
|
|
||||||
|
parse_btn.click(
|
||||||
|
fn=process,
|
||||||
|
inputs=[text_in, hint_in],
|
||||||
|
outputs=[summary_out, json_out],
|
||||||
|
)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
demo.launch()
|
||||||
54
chat_template.jinja
Normal file
54
chat_template.jinja
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
{%- if tools %}
|
||||||
|
{{- '<|im_start|>system\n' }}
|
||||||
|
{%- if messages[0]['role'] == 'system' %}
|
||||||
|
{{- messages[0]['content'] }}
|
||||||
|
{%- else %}
|
||||||
|
{{- 'You are Qwen, created by Alibaba Cloud. You are a helpful assistant.' }}
|
||||||
|
{%- endif %}
|
||||||
|
{{- "\n\n# Tools\n\nYou may call one or more functions to assist with the user query.\n\nYou are provided with function signatures within <tools></tools> XML tags:\n<tools>" }}
|
||||||
|
{%- for tool in tools %}
|
||||||
|
{{- "\n" }}
|
||||||
|
{{- tool | tojson }}
|
||||||
|
{%- endfor %}
|
||||||
|
{{- "\n</tools>\n\nFor each function call, return a json object with function name and arguments within <tool_call></tool_call> XML tags:\n<tool_call>\n{\"name\": <function-name>, \"arguments\": <args-json-object>}\n</tool_call><|im_end|>\n" }}
|
||||||
|
{%- else %}
|
||||||
|
{%- if messages[0]['role'] == 'system' %}
|
||||||
|
{{- '<|im_start|>system\n' + messages[0]['content'] + '<|im_end|>\n' }}
|
||||||
|
{%- else %}
|
||||||
|
{{- '<|im_start|>system\nYou are Qwen, created by Alibaba Cloud. You are a helpful assistant.<|im_end|>\n' }}
|
||||||
|
{%- endif %}
|
||||||
|
{%- endif %}
|
||||||
|
{%- for message in messages %}
|
||||||
|
{%- if (message.role == "user") or (message.role == "system" and not loop.first) or (message.role == "assistant" and not message.tool_calls) %}
|
||||||
|
{{- '<|im_start|>' + message.role + '\n' + message.content + '<|im_end|>' + '\n' }}
|
||||||
|
{%- elif message.role == "assistant" %}
|
||||||
|
{{- '<|im_start|>' + message.role }}
|
||||||
|
{%- if message.content %}
|
||||||
|
{{- '\n' + message.content }}
|
||||||
|
{%- endif %}
|
||||||
|
{%- for tool_call in message.tool_calls %}
|
||||||
|
{%- if tool_call.function is defined %}
|
||||||
|
{%- set tool_call = tool_call.function %}
|
||||||
|
{%- endif %}
|
||||||
|
{{- '\n<tool_call>\n{"name": "' }}
|
||||||
|
{{- tool_call.name }}
|
||||||
|
{{- '", "arguments": ' }}
|
||||||
|
{{- tool_call.arguments | tojson }}
|
||||||
|
{{- '}\n</tool_call>' }}
|
||||||
|
{%- endfor %}
|
||||||
|
{{- '<|im_end|>\n' }}
|
||||||
|
{%- elif message.role == "tool" %}
|
||||||
|
{%- if (loop.index0 == 0) or (messages[loop.index0 - 1].role != "tool") %}
|
||||||
|
{{- '<|im_start|>user' }}
|
||||||
|
{%- endif %}
|
||||||
|
{{- '\n<tool_response>\n' }}
|
||||||
|
{{- message.content }}
|
||||||
|
{{- '\n</tool_response>' }}
|
||||||
|
{%- if loop.last or (messages[loop.index0 + 1].role != "tool") %}
|
||||||
|
{{- '<|im_end|>\n' }}
|
||||||
|
{%- endif %}
|
||||||
|
{%- endif %}
|
||||||
|
{%- endfor %}
|
||||||
|
{%- if add_generation_prompt %}
|
||||||
|
{{- '<|im_start|>assistant\n' }}
|
||||||
|
{%- endif %}
|
||||||
61
config.json
Normal file
61
config.json
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
{
|
||||||
|
"architectures": [
|
||||||
|
"Qwen2ForCausalLM"
|
||||||
|
],
|
||||||
|
"attention_dropout": 0.0,
|
||||||
|
"bos_token_id": 151643,
|
||||||
|
"dtype": "float16",
|
||||||
|
"eos_token_id": 151645,
|
||||||
|
"hidden_act": "silu",
|
||||||
|
"hidden_size": 3584,
|
||||||
|
"initializer_range": 0.02,
|
||||||
|
"intermediate_size": 18944,
|
||||||
|
"layer_types": [
|
||||||
|
"full_attention",
|
||||||
|
"full_attention",
|
||||||
|
"full_attention",
|
||||||
|
"full_attention",
|
||||||
|
"full_attention",
|
||||||
|
"full_attention",
|
||||||
|
"full_attention",
|
||||||
|
"full_attention",
|
||||||
|
"full_attention",
|
||||||
|
"full_attention",
|
||||||
|
"full_attention",
|
||||||
|
"full_attention",
|
||||||
|
"full_attention",
|
||||||
|
"full_attention",
|
||||||
|
"full_attention",
|
||||||
|
"full_attention",
|
||||||
|
"full_attention",
|
||||||
|
"full_attention",
|
||||||
|
"full_attention",
|
||||||
|
"full_attention",
|
||||||
|
"full_attention",
|
||||||
|
"full_attention",
|
||||||
|
"full_attention",
|
||||||
|
"full_attention",
|
||||||
|
"full_attention",
|
||||||
|
"full_attention",
|
||||||
|
"full_attention",
|
||||||
|
"full_attention"
|
||||||
|
],
|
||||||
|
"max_position_embeddings": 32768,
|
||||||
|
"max_window_layers": 28,
|
||||||
|
"model_type": "qwen2",
|
||||||
|
"num_attention_heads": 28,
|
||||||
|
"num_hidden_layers": 28,
|
||||||
|
"num_key_value_heads": 4,
|
||||||
|
"pad_token_id": null,
|
||||||
|
"rms_norm_eps": 1e-06,
|
||||||
|
"rope_parameters": {
|
||||||
|
"rope_theta": 1000000.0,
|
||||||
|
"rope_type": "default"
|
||||||
|
},
|
||||||
|
"sliding_window": null,
|
||||||
|
"tie_word_embeddings": false,
|
||||||
|
"transformers_version": "5.10.2",
|
||||||
|
"use_cache": true,
|
||||||
|
"use_sliding_window": false,
|
||||||
|
"vocab_size": 152064
|
||||||
|
}
|
||||||
14
generation_config.json
Normal file
14
generation_config.json
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"bos_token_id": 151643,
|
||||||
|
"do_sample": true,
|
||||||
|
"eos_token_id": [
|
||||||
|
151645,
|
||||||
|
151643
|
||||||
|
],
|
||||||
|
"pad_token_id": 151643,
|
||||||
|
"repetition_penalty": 1.05,
|
||||||
|
"temperature": 0.7,
|
||||||
|
"top_k": 20,
|
||||||
|
"top_p": 0.8,
|
||||||
|
"transformers_version": "5.10.2"
|
||||||
|
}
|
||||||
3
model.safetensors
Normal file
3
model.safetensors
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:e8c36f07dea54d4b86453fcee15314516ccd721a781a8a741824d25e649977c5
|
||||||
|
size 15231271816
|
||||||
3
requirements.txt
Normal file
3
requirements.txt
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
gradio>=4.36.0
|
||||||
|
requests>=2.31.0
|
||||||
|
uvicorn>=0.30.0
|
||||||
3
tokenizer.json
Normal file
3
tokenizer.json
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:3fd169731d2cbde95e10bf356d66d5997fd885dd8dbb6fb4684da3f23b2585d8
|
||||||
|
size 11421892
|
||||||
30
tokenizer_config.json
Normal file
30
tokenizer_config.json
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"add_prefix_space": false,
|
||||||
|
"backend": "tokenizers",
|
||||||
|
"bos_token": null,
|
||||||
|
"clean_up_tokenization_spaces": false,
|
||||||
|
"eos_token": "<|im_end|>",
|
||||||
|
"errors": "replace",
|
||||||
|
"extra_special_tokens": [
|
||||||
|
"<|im_start|>",
|
||||||
|
"<|im_end|>",
|
||||||
|
"<|object_ref_start|>",
|
||||||
|
"<|object_ref_end|>",
|
||||||
|
"<|box_start|>",
|
||||||
|
"<|box_end|>",
|
||||||
|
"<|quad_start|>",
|
||||||
|
"<|quad_end|>",
|
||||||
|
"<|vision_start|>",
|
||||||
|
"<|vision_end|>",
|
||||||
|
"<|vision_pad|>",
|
||||||
|
"<|image_pad|>",
|
||||||
|
"<|video_pad|>"
|
||||||
|
],
|
||||||
|
"is_local": true,
|
||||||
|
"local_files_only": false,
|
||||||
|
"model_max_length": 131072,
|
||||||
|
"pad_token": "<|endoftext|>",
|
||||||
|
"split_special_tokens": false,
|
||||||
|
"tokenizer_class": "Qwen2Tokenizer",
|
||||||
|
"unk_token": null
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user