Spaces:
Sleeping
Sleeping
| from fastapi import FastAPI, UploadFile, File, Form | |
| from fastapi.middleware.cors import CORSMiddleware | |
| from PIL import Image | |
| from pdf2image import convert_from_bytes | |
| from typing import List | |
| import base64 | |
| from io import BytesIO | |
| import requests | |
| import os | |
| import json | |
| app = FastAPI() | |
| # Optional: Allow CORS for testing | |
| app.add_middleware( | |
| CORSMiddleware, | |
| allow_origins=["*"], | |
| allow_methods=["*"], | |
| allow_headers=["*"], | |
| ) | |
| FIREWORKS_API_KEY = os.getenv("FIREWORKS_API_KEY") | |
| FIREWORKS_MODEL = "accounts/fireworks/models/llama4-scout-instruct-basic" | |
| FIREWORKS_ENDPOINT = "https://api.fireworks.ai/inference/v1/chat/completions" | |
| def encode_image(img: Image.Image) -> str: | |
| buffered = BytesIO() | |
| img.save(buffered, format="PNG") | |
| return base64.b64encode(buffered.getvalue()).decode("utf-8") | |
| async def generate(image: UploadFile = File(...)): | |
| image_data = await image.read() | |
| # Handle image or PDF conversion | |
| if image.filename.lower().endswith(".pdf"): | |
| pages = convert_from_bytes(image_data, dpi=150) | |
| if not pages: | |
| return {"error": "No pages found in PDF"} | |
| pil_img = pages[0].convert("RGB") # Use first page of PDF | |
| else: | |
| pil_img = Image.open(BytesIO(image_data)).convert("RGB") | |
| encoded_img = encode_image(pil_img) | |
| prompt = f""" | |
| You are an intelligent invoice data extractor. Given image of invoice (in English or other languages), extract key business fields into the specified JSON format. Return each field along with an estimated accuracy score between 0 and 1. | |
| - Accuracy reflects your confidence in the correctness of each field. | |
| - Handle synonyms (e.g., 'total' = 'net', 'tax' = 'GST'/'TDS'). | |
| - Detect currency from symbols ($, ₹, €) or keywords (USD, INR, EUR); default to USD if unclear. | |
| - The 'items' list may have multiple entries, each with detailed attributes. | |
| - If a field is missing or not found, return an empty value (`""` or `0`) and set `accuracy` to `0.0`. | |
| - I just want json response in below format nothing else in response | |
| {{ | |
| "invoice": {{ | |
| "invoice_number": {{"value": "", "accuracy": 0.0}}, | |
| "invoice_date": {{"value": "YYYY-MM-DD", "accuracy": 0.0}}, | |
| "due_date": {{"value": "YYYY-MM-DD", "accuracy": 0.0}}, | |
| "purchase_order_number": {{"value": "", "accuracy": 0.0}}, | |
| "vendor": {{ | |
| "vendor_id": {{"value": "", "accuracy": 0.0}}, | |
| "name": {{"value": "", "accuracy": 0.0}}, | |
| "address": {{ | |
| "line1": {{"value": "", "accuracy": 0.0}}, | |
| "line2": {{"value": "", "accuracy": 0.0}}, | |
| "city": {{"value": "", "accuracy": 0.0}}, | |
| "state": {{"value": "", "accuracy": 0.0}}, | |
| "postal_code": {{"value": "", "accuracy": 0.0}}, | |
| "country": {{"value": "", "accuracy": 0.0}} | |
| }}, | |
| "contact": {{ | |
| "email": {{"value": "", "accuracy": 0.0}}, | |
| "phone": {{"value": "", "accuracy": 0.0}} | |
| }}, | |
| "tax_id": {{"value": "", "accuracy": 0.0}} | |
| }}, | |
| "buyer": {{ | |
| "buyer_id": {{"value": "", "accuracy": 0.0}}, | |
| "name": {{"value": "", "accuracy": 0.0}}, | |
| "address": {{ | |
| "line1": {{"value": "", "accuracy": 0.0}}, | |
| "line2": {{"value": "", "accuracy": 0.0}}, | |
| "city": {{"value": "", "accuracy": 0.0}}, | |
| "state": {{"value": "", "accuracy": 0.0}}, | |
| "postal_code": {{"value": "", "accuracy": 0.0}}, | |
| "country": {{"value": "", "accuracy": 0.0}} | |
| }}, | |
| "contact": {{ | |
| "email": {{"value": "", "accuracy": 0.0}}, | |
| "phone": {{"value": "", "accuracy": 0.0}} | |
| }}, | |
| "tax_id": {{"value": "", "accuracy": 0.0}} | |
| }}, | |
| "items": [ | |
| {{ | |
| "item_id": {{"value": "", "accuracy": 0.0}}, | |
| "description": {{"value": "", "accuracy": 0.0}}, | |
| "quantity": {{"value": 0, "accuracy": 0.0}}, | |
| "unit_of_measure": {{"value": "", "accuracy": 0.0}}, | |
| "unit_price": {{"value": 0, "accuracy": 0.0}}, | |
| "total_price": {{"value": 0, "accuracy": 0.0}}, | |
| "tax_rate": {{"value": 0, "accuracy": 0.0}}, | |
| "tax_amount": {{"value": 0, "accuracy": 0.0}}, | |
| "discount": {{"value": 0, "accuracy": 0.0}}, | |
| "net_amount": {{"value": 0, "accuracy": 0.0}} | |
| }} | |
| ], | |
| "sub_total": {{"value": 0, "accuracy": 0.0}}, | |
| "tax_total": {{"value": 0, "accuracy": 0.0}}, | |
| "discount_total": {{"value": 0, "accuracy": 0.0}}, | |
| "total_amount": {{"value": 0, "accuracy": 0.0}}, | |
| "currency": {{"value": "", "accuracy": 0.0}} | |
| }} | |
| }} | |
| """ | |
| payload = { | |
| "model": FIREWORKS_MODEL, | |
| "messages": [ | |
| { | |
| "role": "user", | |
| "content": [ | |
| { "type": "text", "text": prompt }, | |
| { | |
| "type": "image_url", | |
| "image_url": { | |
| "url": f"data:image/png;base64,{encoded_img}" | |
| } | |
| } | |
| ] | |
| } | |
| ], | |
| "max_tokens": 1024, | |
| "temperature": 0.2, | |
| "top_p": 0.9 | |
| } | |
| headers = { | |
| "Authorization": f"Bearer {FIREWORKS_API_KEY}", | |
| "Content-Type": "application/json" | |
| } | |
| response = requests.post(FIREWORKS_ENDPOINT, headers=headers, json=payload) | |
| try: | |
| llm_output = response.json()["choices"][0]["message"]["content"] | |
| json_start = llm_output.find("{") | |
| json_end = llm_output.rfind("}") + 1 | |
| json_str = llm_output[json_start:json_end] | |
| structured_data = json.loads(json_str) | |
| return structured_data | |
| except Exception as e: | |
| output = f"Error: {response.text}" | |
| return {"error": f"{output}"} |