from pydantic import BaseModel, Field, HttpUrl, ConfigDict from typing import List from openai import AsyncOpenAI from fastapi import FastAPI, HTTPException, Security from fastapi.security import APIKeyHeader import os api_keys = [os.getenv('FASTAPI_KEY')] api_key_header = APIKeyHeader(name="X-API-Key") def get_api_key(api_key_header: str = Security(api_key_header)) -> str: if api_key_header in api_keys: return api_key_header raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid or missing API Key", ) class BusinessCardResponse(BaseModel): is_valid_business_card: bool = Field(title = "Is This a Valid Business Card", description="To flag whether the user inputted image is a business card") name: str job: str company: str phone_number: List[int] country_code : List[int] email: List[str] address: List[str] postal_code: List[str] website: List[str] client = AsyncOpenAI(api_key = os.getenv('OPENAI_API_KEY')) app = FastAPI(title="Business Card Scanner API", description = "A FastAPI app to do physical business card scanning & information extraction", version = "0.1.0") class ImageRequest(BaseModel): url: str model_config = ConfigDict( json_schema_extra={ "examples": [ { "url" : "https://marketplace.canva.com/EAFIF52_A0Y/1/0/1600w/canva-putih-biru-profesional-minimalis-kartu-nama-bisnis-AmVxO2cXfvA.jpg" } ] } ) prompt = """ ### CONTEXT ### I want to create a physical-to-digital business card scanner application. This app allows the user to take a picture of a physical business card, extract the information inside the picture, and create a digital business card. ### OBJECTIVE ### The user will give you an image of a business card. Your first task is to check whether it is a business card. If it is a business card, you must extract these information from the card: 1. The cardholder's name (including the salutation & degrees, if any) 2. Job/occupation of the cardholder 3. The company name 2. Phone Number(s) (list of integer, remove plus/+ sign and any other symbols) 3. Email address (list) 4. Home/office address (list) 5. Personal/company Website (list) ### OUTPUT ### You must only return a JSON output in a snippet with this schema: ```json { "is_valid_business_card": bool, "name": str, "job": str, "company": str, "phone_number": List[int], "country_code": List[int], "email": List[str], "address": List[str], "postal_code": List[str], "website": List[str] } ``` You must only return the JSON output only, without any additional comments! ### SAMPLE RESPONSE ### ```json { "is_valid_business_card": True, "name": "Matthew Farant, BSc.", "job": "Business Intelligence Analyst", "company": "TipTip", "phone_number": [62812345678, 62312345678], "country_code": [62, 62], "email":["matthewfarant@gmail.com", "matthew.farant@tiptip.tv"], "address":["Jalan Kepang Lima No. 8"], "postal_code": ["60216"], "website":["www.matthew.com"] } """ @app.post("/api/scan", response_model=BusinessCardResponse) async def extract_card(image_request: ImageRequest, api_key: str = Security(get_api_key)) -> BusinessCardResponse: response = await client.chat.completions.create( model="gpt-4o-mini", messages=[ { "role": "system", "content": [ { "text": prompt, "type": "text" } ] }, { "role": "user", "content": [ { "type": "text", "text": """Extract the business card information from the image and put them in a JSON format""" }, { "type": "image_url", "image_url": {"url": image_request.url} } ] }, { "role": "assistant", "content": "```json" } ], temperature=1, max_tokens=300, top_p=1, frequency_penalty=0, presence_penalty=0 ) return BusinessCardResponse.model_validate_json(response.choices[0].message.content.replace('```json','').replace('```','').strip())