| """ |
| 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 |
|
|
| |
| app = FastAPI( |
| title="Real Estate Description Formatter", |
| description="API to format real estate descriptions with AI", |
| version="1.0.0" |
| ) |
|
|
| |
| app.add_middleware( |
| CORSMiddleware, |
| allow_origins=["*"], |
| allow_credentials=True, |
| allow_methods=["*"], |
| allow_headers=["*"], |
| ) |
|
|
| |
| |
| |
| MODEL_NAME = os.getenv("MODEL_NAME", "Qwen/Qwen2.5-7B-Instruct") |
| HF_TOKEN = os.getenv("HF_TOKEN", None) |
|
|
| client = InferenceClient(model=MODEL_NAME, token=HF_TOKEN) |
|
|
| |
| class RealEstateInput(BaseModel): |
| description: str |
|
|
| class RealEstateOutput(BaseModel): |
| original: str |
| formatted_html: str |
| success: bool |
| error: Optional[str] = None |
|
|
| |
| 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: |
| |
| messages = [ |
| { |
| "role": "system", |
| "content": SYSTEM_PROMPT |
| }, |
| { |
| "role": "user", |
| "content": USER_PROMPT_TEMPLATE.format(description=input_data.description) |
| } |
| ] |
|
|
| |
| response = client.chat_completion( |
| messages=messages, |
| max_tokens=2000, |
| temperature=0.3, |
| ) |
|
|
| |
| formatted_html = response.choices[0].message.content.strip() |
|
|
| |
| 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 |
| ) |
|
|