| | """ |
| | 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 |
| | ) |
| |
|