""" Real Estate Description Formatter API Uses lightweight AI models from HuggingFace to format real estate descriptions """ from fastapi import FastAPI, HTTPException from fastapi.middleware.cors import CORSMiddleware from pydantic import BaseModel from typing import Optional import os from huggingface_hub import InferenceClient # FastAPI app app = FastAPI( title="Real Estate Description Formatter", description="API to format real estate descriptions with AI", version="1.0.0" ) # CORS app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) # Initialize HuggingFace Inference Client # Sử dụng model mạnh: Qwen2.5-7B-Instruct (7B params, hỗ trợ tiếng Việt) # Alternatives: mistralai/Mistral-7B-Instruct-v0.3, google/gemma-2-7b-it MODEL_NAME = os.getenv("MODEL_NAME", "Qwen/Qwen2.5-7B-Instruct") HF_TOKEN = os.getenv("HF_TOKEN", None) # Optional: for private models or faster inference client = InferenceClient(model=MODEL_NAME, token=HF_TOKEN) # Request/Response Models class RealEstateInput(BaseModel): description: str class RealEstateOutput(BaseModel): original: str formatted_html: str success: bool error: Optional[str] = None # Prompt phức tạp để xử lý form description bất động sản SYSTEM_PROMPT = """Bạn là một bộ máy chuẩn hoá nội dung mô tả bất động sản cho CMS. INPUT: - Một chuỗi text thô (plain text) - Không có HTML - Có thể dính liền câu, thiếu xuống dòng, thiếu dấu câu chuẩn NHIỆM VỤ DUY NHẤT: - Tách nội dung thành các dòng ngắn, dễ đọc - Mỗi dòng là một ý/câu độc lập QUY TẮC BẮT BUỘC: 1. TUYỆT ĐỐI KHÔNG: - Không thêm, bịa, suy đoán, diễn giải hay làm rõ nội dung - Không sửa nghĩa, không viết lại câu - Không thêm từ mới, không thêm tiền tố - Không phân loại ngữ nghĩa (không suy luận đâu là giá, đâu là vị trí, đâu là liên hệ) 2. CHỈ ĐƯỢC PHÉP: - Giữ nguyên 100% nội dung text gốc - Chỉ tách câu dựa trên: - Dấu . ! ? : - Hoặc khi phát hiện các cụm bắt đầu rõ ràng như: "Diện tích", "Vị trí", "Giá", "Liên hệ", "Email" - Giữ nguyên thứ tự xuất hiện 3. OUTPUT FORMAT: - Trả về JSON duy nhất - Key: description_lines - Value: mảng string - Mỗi phần tử là một dòng text nguyên văn từ input 4. KHÔNG: - Không HTML - Không markdown - Không text giải thích - Không key nào khác ngoài description_lines """ USER_PROMPT_TEMPLATE = """Hãy định dạng mô tả bất động sản sau thành HTML có cấu trúc rõ ràng theo đúng quy tắc đã nêu: {description} LƯU Ý: - Không thêm nội dung - Không suy đoán thông tin thiếu - Chỉ bọc HTML + CSS inline""" @app.get("/") async def root(): """API root""" return { "name": "Real Estate Description Formatter", "version": "1.0.0", "model": MODEL_NAME, "endpoint": "/format" } @app.post("/format", response_model=RealEstateOutput) async def format_description(input_data: RealEstateInput): """ Format real estate description with AI Example input: { "description": "NHÀ 2 TẦNG HẺM OTO LIÊN HOA VĨNH NGỌC - TÂY NHA TRANG- Diện tích 92m² full ONT- Hướng Đông Bắc- Pháp lý sổ hồng hoàn công..." } """ try: # Prepare messages for chat model messages = [ { "role": "system", "content": SYSTEM_PROMPT }, { "role": "user", "content": USER_PROMPT_TEMPLATE.format(description=input_data.description) } ] # Call HuggingFace Inference API response = client.chat_completion( messages=messages, max_tokens=2000, temperature=0.3, # Low temperature for consistent formatting ) # Extract formatted HTML formatted_html = response.choices[0].message.content.strip() # Clean up markdown if model added it if formatted_html.startswith("```html"): formatted_html = formatted_html.replace("```html", "").replace("```", "").strip() return RealEstateOutput( original=input_data.description, formatted_html=formatted_html, success=True ) except Exception as e: return RealEstateOutput( original=input_data.description, formatted_html="", success=False, error=str(e) ) @app.get("/health") async def health_check(): """Health check endpoint""" return { "status": "healthy", "model": MODEL_NAME, "service": "Real Estate Formatter" } if __name__ == "__main__": import uvicorn uvicorn.run( "app:app", host="0.0.0.0", port=7860, reload=False )