Xây Dựng API Phát Hiện Ảnh AI với C2PA và Bộ Phân Loại

Ashley Innocent

Ashley Innocent

21 tháng 5 2026

Xây Dựng API Phát Hiện Ảnh AI với C2PA và Bộ Phân Loại

Apidog cho doanh nghiệp

Triển khai tại chỗ

SSO & RBAC

Tuân thủ SOC 2

Khám phá Apidog Enterprise

Một người tải ảnh lên sản phẩm của bạn và khẳng định đó là ảnh chụp từ máy ảnh. Hệ thống phụ trợ của bạn có thể chứng minh hay bác bỏ điều đó không? Các trình tạo ảnh hiện nay tạo ra những kết quả trông thật đối với người đánh giá là con người, vì vậy việc "tin vào mắt mình" đã không còn hiệu quả từ lâu. Tin tốt là bạn không cần phải tự đào tạo mô hình của riêng mình để đưa ra một câu trả lời hữu ích. Bạn có thể kết hợp hai tín hiệu độc lập, một bản kê khai nguồn gốc mật mã và một bộ phân loại học máy, thành một phán quyết duy nhất đáng tin cậy hơn so với từng tín hiệu riêng lẻ. Hướng dẫn này sẽ chỉ cho bạn cách xây dựng hệ thống phụ trợ đó dưới dạng một dịch vụ duy nhất với một điểm cuối `POST /verify`. Bạn cung cấp cho nó một hình ảnh, nó sẽ trả về một phán quyết JSON với điểm tin cậy và các chi tiết nguồn gốc mà nó tìm thấy. Chúng ta sẽ sử dụng Python và FastAPI cho máy chủ, công cụ C2PA mã nguồn mở cho tín hiệu nguồn gốc, và một API phát hiện được lưu trữ cho tín hiệu bộ phân loại. Bởi vì đây là một dự án API, chúng ta cũng sẽ thiết kế hợp đồng điểm cuối trước và sử dụng [Apidog](https://apidog.com) để mô phỏng và kiểm thử nó, để đội ngũ giao diện người dùng của bạn có thể bắt đầu tích hợp trước khi mã phụ trợ được hoàn thành.

TÓM TẮT

Bạn sẽ xây dựng một dịch vụ FastAPI hiển thị điểm cuối POST /verify chấp nhận tải lên hình ảnh, trích xuất và xác thực bản kê khai Thông tin xác thực nội dung C2PA của nó bằng thư viện c2pa-python, gọi một bộ phân loại phát hiện AI được lưu trữ làm tín hiệu độc lập thứ hai, và trả về một phán quyết JSON duy nhất (likely_authentic, likely_ai, hoặc uncertain) với điểm tin cậy và các chi tiết nguồn gốc thô. Bạn cũng sẽ thiết kế lược đồ OpenAPI cho điểm cuối và sử dụng Apidog để tạo một máy chủ mô phỏng và chạy các kiểm thử điểm cuối đối với nó.

Tải xuống ứng dụng

Tại sao hai tín hiệu thay vì một

Trước khi viết bất kỳ mã nào, điều quan trọng là phải hiểu rõ bạn đang phát hiện điều gì. Không có một thuộc tính duy nhất nào của một tệp có thể cho bạn biết "con người đã tạo ra cái này" hay "AI đã tạo ra cái này." Thay vào đó, có những manh mối, và mỗi manh mối bắt được một loại hình ảnh khác nhau trong khi bỏ lỡ những loại khác.

Manh mối đầu tiên là nguồn gốc. C2PA, Liên minh Xác thực và Nguồn gốc Nội dung, là một tiêu chuẩn mở gắn siêu dữ liệu chống giả mạo, được ký mật mã vào một tệp phương tiện. Gói siêu dữ liệu đó được gọi là bản kê khai, và tên dễ hiểu cho người dùng là Thông tin xác thực nội dung. Khi một công cụ tham gia, một máy ảnh, một trình chỉnh sửa hoặc một trình tạo hình ảnh, tạo hoặc thay đổi một hình ảnh, nó có thể ghi một bản kê khai ghi lại những gì đã xảy ra và ký nó bằng một chứng chỉ. Nếu bạn có thể đọc và xác thực bản kê khai đó, bạn sẽ nhận được một tuyên bố mạnh mẽ, có thể kiểm chứng về lịch sử của hình ảnh.

Vấn đề là: C2PA là tùy chọn tham gia, và bản kê khai rất dễ bị tổn thương. Một ảnh chụp màn hình sẽ loại bỏ nó. Việc mã hóa lại thông qua một ứng dụng nhắn tin sẽ loại bỏ nó. Nhiều nền tảng xóa siêu dữ liệu khi tải lên. Vì vậy, một bản kê khai bị thiếu hầu như không cho bạn biết điều gì; nó không có nghĩa là hình ảnh là giả, và nó không có nghĩa là nó là thật.

Manh mối thứ hai là một bộ phân loại thống kê. Một mô hình phát hiện được đào tạo trên hàng triệu hình ảnh thật và hình ảnh được tạo ra và học được các tạo tác hình ảnh mà các trình tạo thường để lại. Nó hoạt động trên bất kỳ hình ảnh nào, có hoặc không có siêu dữ liệu, nhưng nó có tính xác suất. Nó trả về một khả năng, không phải là một sự thật, và nó có thể sai, đặc biệt là trên các hình ảnh nằm ngoài phân phối đào tạo của nó hoặc các hình ảnh đã bị nén nặng.

Không tín hiệu nào đủ một mình. Nguồn gốc chính xác nhưng hiếm khi có mặt. Bộ phân loại luôn có sẵn nhưng không bao giờ chắc chắn. Kết hợp chúng và bạn nhận được một phán quyết nói rằng, thực tế là "đây là những gì mật mã chứng minh, đây là những gì mô hình ước tính, và đây là mức độ tự tin mà sự kết hợp mang lại cho chúng ta." Đó là mục tiêu thiết kế. Nếu bạn muốn tìm hiểu sâu hơn về lý do tại sao các phương pháp tiếp cận một tín hiệu không hiệu quả, bài viết của chúng tôi về [lý do phát hiện hình ảnh AI thất bại](http://apidog.com/blog/why-ai-image-detection-fails) trình bày chi tiết các chế độ lỗi.

Tổng quan kiến trúc

Dịch vụ này nhỏ một cách có chủ đích. Một điểm cuối, hai lệnh gọi xuống cấp, một phản hồi kết hợp.

                ┌─────────────────────────────┐
   hình ảnh  ──▶   │   FastAPI  POST /verify      │
                │                              │
                │   1. xác thực tải lên         │
                │   2. ┌──────────────────┐    │
                │      │ Bản kê khai C2PA    │    │  tín hiệu nguồn gốc
                │      │ (c2pa-python)     │    │
                │      └──────────────────┘    │
                │   3. ┌──────────────────┐    │
                │      │ API bộ phân loại    │    │  tín hiệu thống kê
                │      │ (bộ phát hiện được lưu trữ) │    │
                │      └──────────────────┘    │
                │   4. kết hợp thành phán quyết    │
                └─────────────────────────────┘
                              │
                              ▼
                   Phán quyết JSON + độ tin cậy

Bước 1 kiểm tra xem tệp tải lên có phải là hình ảnh thực thuộc loại được hỗ trợ và nằm trong giới hạn kích thước hay không. Bước 2 đọc bản kê khai C2PA cục bộ; không có lệnh gọi mạng, chỉ phân tích cú pháp và xác thực chứng chỉ. Bước 3 gửi các byte hình ảnh đến một bộ phân loại được lưu trữ qua HTTPS. Bước 4 hợp nhất hai kết quả bằng một hàm quy tắc nhỏ và trả về phán quyết.

Hai bước tín hiệu độc lập. Điều này quan trọng đối với việc xử lý lỗi: nếu bộ phân loại hết thời gian chờ, bạn vẫn có thể trả về một phán quyết một phần từ tín hiệu nguồn gốc, và ngược lại. Chúng ta sẽ quay lại vấn đề đó trong phần tăng cường.

Đối với ngăn xếp, Python 3.10 trở lên được yêu cầu vì thư viện C2PA cần nó. Bạn sẽ sử dụng FastAPI cho lớp web, Uvicorn để chạy nó, python-multipart cho tải lên tệp, httpx cho lệnh gọi bộ phân loại đi, và c2pa-python cho nguồn gốc.

pip install fastapi "uvicorn[standard]" python-multipart httpx c2pa-python

Tín hiệu C2PA

Sáng kiến Xác thực Nội dung (Content Authenticity Initiative), dưới tổ chức GitHub contentauth, xuất bản công cụ C2PA mã nguồn mở. Có hai phần bạn sẽ nghe nói đến:

Đường dẫn đọc của thư viện tập trung vào đối tượng Reader. Bạn trỏ nó vào một hình ảnh, sau đó yêu cầu kho bản kê khai dưới dạng JSON. Dưới đây là phần cốt lõi của mô-đun nguồn gốc.

# provenance.py
import json
import c2pa


def read_provenance(image_path: str) -> dict:
    """
    Đọc và xác thực bản kê khai C2PA từ một hình ảnh.
    Trả về một từ điển chuẩn hóa mô tả những gì đã tìm thấy.
    """
    try:
        with c2pa.Reader(image_path) as reader:
            manifest_store = json.loads(reader.json())
    except c2pa.C2paError as err:
        # ManifestNotFound là trường hợp dự kiến đối với hầu hết các hình ảnh.
        if str(err).startswith("ManifestNotFound"):
            return {
                "has_manifest": False,
                "validation": "none",
                "detail": "Không có bản kê khai C2PA nào trong hình ảnh này.",
            }
        # Bất kỳ C2paError nào khác có nghĩa là tệp có dữ liệu C2PA mà chúng tôi không thể phân tích cú pháp.
        return {
            "has_manifest": True,
            "validation": "error",
            "detail": f"Không thể phân tích cú pháp bản kê khai: {err}",
        }

    active_label = manifest_store.get("active_manifest")
    manifests = manifest_store.get("manifests", {})
    active = manifests.get(active_label, {})

    # validation_status chỉ xuất hiện khi có vấn đề xác thực.
    validation_status = manifest_store.get("validation_status", [])
    validation = "valid" if not validation_status else "invalid"

    claim_generator = active.get("claim_generator", "unknown")
    signature_issuer = active.get("signature_info", {}).get("issuer", "unknown")

    return {
        "has_manifest": True,
        "validation": validation,
        "claim_generator": claim_generator,
        "signature_issuer": signature_issuer,
        "validation_status": validation_status,
        "detail": "Đã đọc bản kê khai thành công.",
    }

Một vài lưu ý về những gì mã làm. Reader được sử dụng như một trình quản lý ngữ cảnh để các tài nguyên cơ bản được giải phóng. reader.json() trả về toàn bộ kho bản kê khai dưới dạng chuỗi JSON; thư viện cũng cung cấp reader.detailed_json() nếu bạn muốn báo cáo dạng dài với mọi xác nhận và thành phần. Kết quả dự kiến cho hầu hết các lần tải lên là một C2paError có thông báo bắt đầu bằng ManifestNotFound, vì hầu hết các hình ảnh đơn giản không có Thông tin xác thực nội dung. Coi đó là dữ liệu, không phải là một lỗi.

Khi một bản kê khai có mặt, hai trường quan trọng nhất đối với một phán quyết. Chuỗi claim_generator cho bạn biết công cụ nào đã ghi bản kê khai, ví dụ: chuỗi chương trình cơ sở máy ảnh hoặc tên công cụ hình ảnh AI. Mảng validation_status trống khi chữ ký và hàm băm được kiểm tra, và được điền bằng mã lỗi khi chúng không khớp. Một bản kê khai không hợp lệ là một dấu hiệu đỏ đáng được nêu bật; nó có nghĩa là tệp tuyên bố một lịch sử mà mật mã không xác nhận.

Điều mà tín hiệu này không thể làm: nó không thể cung cấp cho bạn một phán quyết khi không có bản kê khai, đó là hầu hết thời gian. Đó chính xác là lý do bạn cần tín hiệu thứ hai.

Tín hiệu bộ phân loại

Bộ phân loại là một API được lưu trữ để chấm điểm khả năng một hình ảnh được tạo bởi AI. Một số nhà cung cấp cung cấp dịch vụ này. Hướng dẫn này sử dụng Sightengine vì mô hình phát hiện AI của họ có API HTTP được ghi lại rõ ràng và hình dạng phản hồi minh bạch, nhưng mẫu tương tự áp dụng cho bất kỳ nhà cung cấp nào; bạn thay đổi URL, các tham số và trường bạn đọc. Nếu bạn đang cân nhắc các lựa chọn, tổng hợp của chúng tôi về [các API phát hiện hình ảnh AI tốt nhất](http://apidog.com/blog/best-ai-image-detection-apis) so sánh độ chính xác, giá cả và phạm vi phủ sóng giữa các nhà cung cấp.

Điểm cuối kiểm tra của Sightengine là https://api.sightengine.com/1.0/check.json. Bạn POST hình ảnh dưới dạng media, đặt models thành genai, và chuyển api_userapi_secret của bạn. Phản hồi bao gồm type.ai_generated, một điểm số từ 0 đến 1, trong đó điểm cao hơn có nghĩa là khả năng được tạo bởi AI cao hơn.

# classifier.py
import httpx

SIGHTENGINE_URL = "https://api.sightengine.com/1.0/check.json"


async def classify_image(
    image_bytes: bytes,
    filename: str,
    api_user: str,
    api_secret: str,
    timeout_seconds: float = 8.0,
) -> dict:
    """
    Gửi hình ảnh đến bộ phát hiện được lưu trữ.
    Trả về một từ điển chuẩn hóa với điểm AI-generated.
    """
    data = {
        "models": "genai",
        "api_user": api_user,
        "api_secret": api_secret,
    }
    files = {"media": (filename, image_bytes)}

    try:
        async with httpx.AsyncClient(timeout=timeout_seconds) as client:
            response = await client.post(SIGHTENGINE_URL, data=data, files=files)
            response.raise_for_status()
            payload = response.json()
    except httpx.TimeoutException:
        return {"available": False, "reason": "classifier_timeout"}
    except httpx.HTTPStatusError as err:
        return {
            "available": False,
            "reason": f"classifier_http_{err.response.status_code}",
        }
    except httpx.HTTPError as err:
        return {"available": False, "reason": f"classifier_error: {err}"}

    if payload.get("status") != "success":
        return {
            "available": False,
            "reason": payload.get("error", {}).get("message", "unknown_error"),
        }

    ai_score = payload.get("type", {}).get("ai_generated")
    if ai_score is None:
        return {"available": False, "reason": "missing_score_in_response"}

    return {"available": True, "ai_score": float(ai_score)}

Hàm là bất đồng bộ (async) để bộ phân loại chậm không làm chặn vòng lặp sự kiện. Thời gian chờ được chỉ định rõ ràng và ngắn; tám giây là một giá trị mặc định hợp lý cho một điểm cuối tương tác, và bạn nên điều chỉnh nó theo độ trễ thực tế của nhà cung cấp của bạn. Mọi đường dẫn thất bại đều trả về available: False với một lý do có thể đọc được bằng máy thay vì gây ra ngoại lệ. Điều này là có chủ ý: một sự cố bộ phân loại nên làm suy giảm phán quyết, không phải làm hỏng yêu cầu. Logic phán quyết trong phần tiếp theo đọc available và quyết định phải làm gì.

Coi điểm số là một ước tính. 0.92 là "mô hình khá chắc chắn," không phải "điều này đã được chứng minh là AI." Các nhà cung cấp cập nhật mô hình của họ, và độ chính xác thay đổi tùy theo trình tạo và mức độ nén của hình ảnh trước khi đến tay bạn. Để có cái nhìn rộng hơn về cách các công cụ này hoạt động trong thực tế, hãy xem hướng dẫn của chúng tôi về [cách kiểm tra xem một hình ảnh có phải do AI tạo ra hay không](http://apidog.com/blog/how-to-check-ai-generated-images).

Thiết kế hợp đồng /verify

Đây là lúc [Apidog](https://apidog.com) thể hiện giá trị của mình. Trước khi viết trình xử lý tuyến đường, hãy thiết kế yêu cầu và phản hồi dưới dạng lược đồ OpenAPI. Làm điều này trước mang lại cho bạn ba điều: một nguồn thông tin duy nhất mà cả hai đội đều đồng ý, một máy chủ mô phỏng mà giao diện người dùng có thể gọi ngay lập tức, và một bộ kiểm thử mà bạn có thể chạy ngay khi phần phụ trợ tồn tại.

Yêu cầu

POST /verify nhận một body multipart/form-data với một trường, image, là tệp để kiểm tra. Hãy giữ nó đơn giản như vậy. Các tham số truy vấn tùy chọn có thể được thêm vào sau.

Phản hồi

Phản hồi là nơi công việc thiết kế được đền đáp. Nó phải hiển thị phán quyết cuối cùng, độ tin cậy, và cả hai tín hiệu thô để người gọi có thể kiểm tra quyết định. Dưới đây là hình dạng.

{
  "verdict": "likely_ai",
  "confidence": 0.86,
  "signals": {
    "provenance": {
      "has_manifest": true,
      "validation": "valid",
      "claim_generator": "SomeImageTool/2.1",
      "signature_issuer": "Some Issuing CA"
    },
    "classifier": {
      "available": true,
      "ai_score": 0.91
    }
  },
  "explanation": "Một bản kê khai C2PA hợp lệ đặt tên một công cụ hình ảnh AI, và bộ phân loại đã chấm điểm hình ảnh là có khả năng được tạo bởi AI.",
  "checked_at": "2026-05-21T09:30:00Z"
}

verdict là một trong ba giá trị chuỗi: likely_authentic, likely_ai, hoặc uncertain. Ba giá trị, không phải hai, vì sự trung thực quan trọng; khi các tín hiệu mâu thuẫn hoặc cả hai đều yếu, "uncertain" là câu trả lời đúng. confidence là một số thập phân từ 0 đến 1 mô tả mức độ mạnh mẽ mà các tín hiệu hỗ trợ phán quyết đó. signals mang cả hai đầu vào thô để người gọi có thể hiển thị giao diện người dùng của riêng họ hoặc áp dụng chính sách của riêng họ. explanation là một câu văn dễ đọc cho nhân viên hỗ trợ và nhật ký.

Diễn đạt điều này dưới dạng lược đồ OpenAPI rất đơn giản. Dưới đây là thành phần phản hồi mà bạn sẽ đặt vào đặc tả của mình.

components:
  schemas:
    VerifyResponse:
      type: object
      required: [verdict, confidence, signals, checked_at]
      properties:
        verdict:
          type: string
          enum: [likely_authentic, likely_ai, uncertain]
        confidence:
          type: number
          format: float
          minimum: 0
          maximum: 1
        signals:
          type: object
          properties:
            provenance:
              type: object
              properties:
                has_manifest: { type: boolean }
                validation:
                  type: string
                  enum: [valid, invalid, error, none]
                claim_generator: { type: string }
                signature_issuer: { type: string }
            classifier:
              type: object
              properties:
                available: { type: boolean }
                ai_score:
                  type: number
                  format: float
        explanation: { type: string }
        checked_at: { type: string, format: date-time }

Bạn có thể tạo lược đồ này trực tiếp trong trình thiết kế trực quan của Apidog hoặc nhập một tệp OpenAPI hiện có. Thiết kế API trước khi triển khai là một quy trình làm việc đáng được áp dụng nói chung; bài viết [hướng dẫn chế độ ưu tiên đặc tả của Apidog](http://apidog.com/blog/spec-first-mode-apidog-beta-walkthrough) của chúng tôi trình bày cách thực hiện điều đó từ đầu đến cuối trong Apidog.

Hướng dẫn chi tiết về mã

Bây giờ các mảnh ghép lại với nhau. Dưới đây là ứng dụng FastAPI: xác thực đầu vào, cả hai lệnh gọi tín hiệu, hàm kết hợp và tuyến đường.

Kết hợp hai tín hiệu

Hàm phán quyết là trái tim của dịch vụ. Nó mã hóa chính sách của bạn. Nguồn gốc, khi hợp lệ và hiện diện, là tín hiệu mạnh hơn vì nó là mật mã; bộ phân loại là yếu tố phá vỡ thế bế tắc và là phương án dự phòng. Dưới đây là một phiên bản rõ ràng, thận trọng.

# verdict.py


def combine_signals(provenance: dict, classifier: dict) -> dict:
    """Hợp nhất các tín hiệu nguồn gốc và bộ phân loại thành một phán quyết."""
    has_manifest = provenance.get("has_manifest", False)
    validation = provenance.get("validation", "none")
    generator = (provenance.get("claim_generator") or "").lower()

    classifier_ok = classifier.get("available", False)
    ai_score = classifier.get("ai_score")

    # Kinh nghiệm: các công cụ AI được biết đến thường tự nhận dạng trong bản kê khai.
    ai_keywords = ("firefly", "dall-e", "dalle", "midjourney", "stable",
                   "gpt", "gemini", "imagen", "generat")
    generator_looks_ai = any(k in generator for k in ai_keywords)

    # Trường hợp 1: bản kê khai hợp lệ đặt tên một trình tạo AI. Tín hiệu AI mạnh.
    if has_manifest and validation == "valid" and generator_looks_ai:
        return _verdict("likely_ai", 0.95,
                        "Bản kê khai C2PA hợp lệ đặt tên một công cụ hình ảnh AI.")

    # Trường hợp 2: bản kê khai hợp lệ từ máy ảnh hoặc trình chỉnh sửa không phải AI. Tín hiệu xác thực mạnh.
    if has_manifest and validation == "valid" and not generator_looks_ai:
        if classifier_ok and ai_score is not None and ai_score > 0.85:
            return _verdict("uncertain", 0.55,
                            "Bản kê khai trông có vẻ xác thực nhưng bộ phân loại "
                            "không đồng ý; các tín hiệu mâu thuẫn.")
        return _verdict("likely_authentic", 0.9,
                        "Một bản kê khai C2PA hợp lệ từ một công cụ không phải AI đã có mặt.")

    # Trường hợp 3: bản kê khai không vượt qua xác thực. Coi là đáng ngờ.
    if has_manifest and validation in ("invalid", "error"):
        return _verdict("uncertain", 0.6,
                        "Hình ảnh có một bản kê khai C2PA không vượt qua "
                        "xác thực; lịch sử được tuyên bố của nó chưa được xác minh.")

    # Trường hợp 4: không có bản kê khai. Hoàn toàn dựa vào bộ phân loại.
    if classifier_ok and ai_score is not None:
        if ai_score >= 0.7:
            return _verdict("likely_ai", round(ai_score, 2),
                            "Không có dữ liệu nguồn gốc; bộ phân loại đã chấm điểm "
                            "hình ảnh là có khả năng được tạo bởi AI.")
        if ai_score <= 0.3:
            return _verdict("likely_authentic", round(1 - ai_score, 2),
                            "Không có dữ liệu nguồn gốc; bộ phân loại đã chấm điểm "
                            "hình ảnh là có khả năng xác thực.")
        return _verdict("uncertain", 0.5,
                        "Không có dữ liệu nguồn gốc và điểm bộ phân loại "
                        "không có kết luận rõ ràng.")

    # Trường hợp 5: không có bản kê khai và không có bộ phân loại. Chúng tôi thực sự không thể nói.
    return _verdict("uncertain", 0.0,
                    "Không có dữ liệu nguồn gốc và bộ phân loại không khả dụng.")


def _verdict(verdict: str, confidence: float, explanation: str) -> dict:
    return {"verdict": verdict, "confidence": confidence,
            "explanation": explanation}

Đọc qua năm trường hợp và bạn có thể thấy chính sách. Một bản kê khai hợp lệ chiếm ưu thế. Một bản kê khai thất bại là một cảnh báo, không phải bằng chứng về sự giả mạo, vì vậy nó rơi vào "không chắc chắn". Xung đột giữa một bản kê khai sạch và điểm bộ phân loại cao cũng rơi vào "không chắc chắn" thay vì chọn một bên. Và khi cả hai tín hiệu đều bị thiếu, dịch vụ sẽ nói thật với độ tin cậy bằng 0 thay vì đoán mò. Bạn sẽ điều chỉnh các ngưỡng này theo khả năng chấp nhận rủi ro của riêng mình; một nền tảng nội dung và một tòa soạn báo sẽ vẽ ra các đường ranh giới khác nhau.

Ứng dụng FastAPI

# main.py
import os
import tempfile
from datetime import datetime, timezone

from fastapi import FastAPI, UploadFile, File, HTTPException
from fastapi.responses import JSONResponse

from provenance import read_provenance
from classifier import classify_image
from verdict import combine_signals

app = FastAPI(title="AI Image Detector API", version="1.0.0")

ALLOWED_TYPES = {"image/jpeg", "image/png", "image/webp"}
MAX_BYTES = 12 * 1024 * 1024  # 12 MB

SIGHTENGINE_USER = os.environ.get("SIGHTENGINE_API_USER", "")
SIGHTENGINE_SECRET = os.environ.get("SIGHTENGINE_API_SECRET", "")


@app.post("/verify")
async def verify(image: UploadFile = File(...)):
    # 1. Xác thực tệp tải lên.
    if image.content_type not in ALLOWED_TYPES:
        raise HTTPException(
            status_code=415,
            detail=f"Loại không được hỗ trợ {image.content_type}. "
                   f"Gửi JPEG, PNG, hoặc WebP.",
        )

    image_bytes = await image.read()
    if len(image_bytes) == 0:
        raise HTTPException(status_code=400, detail="Tệp trống.")
    if len(image_bytes) > MAX_BYTES:
        raise HTTPException(status_code=413, detail="Tệp vượt quá giới hạn 12 MB.")

    # 2. Tín hiệu nguồn gốc. Trình đọc C2PA cần đường dẫn tệp,
    #    vì vậy hãy ghi vào một tệp tạm thời và dọn dẹp sau đó.
    suffix = os.path.splitext(image.filename or "")[1] or ".img"
    with tempfile.NamedTemporaryFile(suffix=suffix, delete=False) as tmp:
        tmp.write(image_bytes)
        tmp_path = tmp.name
    try:
        provenance = read_provenance(tmp_path)
    finally:
        os.unlink(tmp_path)

    # 3. Tín hiệu bộ phân loại. Các lỗi sẽ trả về available: False, không phải ngoại lệ.
    if SIGHTENGINE_USER and SIGHTENGINE_SECRET:
        classifier = await classify_image(
            image_bytes, image.filename or "upload",
            SIGHTENGINE_USER, SIGHTENGINE_SECRET,
        )
    else:
        classifier = {"available": False, "reason": "classifier_not_configured"}

    # 4. Kết hợp và phản hồi.
    result = combine_signals(provenance, classifier)
    return JSONResponse({
        "verdict": result["verdict"],
        "confidence": result["confidence"],
        "signals": {
            "provenance": {
                k: provenance.get(k) for k in
                ("has_manifest", "validation", "claim_generator",
                 "signature_issuer")
            },
            "classifier": {
                "available": classifier.get("available", False),
                "ai_score": classifier.get("ai_score"),
            },
        },
        "explanation": result["explanation"],
        "checked_at": datetime.now(timezone.utc).isoformat(),
    })

Chạy cục bộ với uvicorn main:app --reload và điểm cuối sẽ hoạt động tại http://127.0.0.1:8000/verify. Trình đọc C2PA mong đợi một đường dẫn tệp, vì vậy trình xử lý sẽ ghi tệp tải lên vào một tệp tạm thời và xóa nó trong một khối finally; bộ phân loại hoạt động trực tiếp từ các byte. Lưu ý rằng yêu cầu không bao giờ gặp sự cố khi thiếu bản kê khai hoặc bộ phân loại không có mặt; cả hai đều là trạng thái bình thường mà hàm phán quyết xử lý.

Một hệ thống phụ trợ được thiết kế theo cách này, một dịch vụ tập trung với một hợp đồng rõ ràng, phù hợp với xu hướng chung của các sản phẩm hiển thị khả năng cốt lõi của chúng thông qua một API. Nếu ý tưởng đó làm bạn quan tâm, bài luận của chúng tôi về [phần mềm không giao diện](http://apidog.com/blog/software-going-headless-api-is-product) rất đáng đọc.

Kiểm thử và mô phỏng với Apidog

Đây là vấn đề trong quy trình làm việc: đội ngũ giao diện người dùng của bạn muốn xây dựng giao diện người dùng tải lên và bảng kết quả ngay bây giờ, nhưng phần phụ trợ ở trên phải mất vài ngày để hoàn thành, lấy khóa và triển khai. Bạn không muốn họ bị chặn. Đây là lúc các máy chủ mô phỏng phát huy tác dụng, và đây là lúc việc thiết kế lược đồ trước được đền đáp.

Tạo máy chủ mô phỏng từ lược đồ

Nhập lược đồ OpenAPI vào Apidog, hoặc xây dựng điểm cuối /verify trong trình thiết kế trực quan. Apidog đọc lược đồ phản hồi và tự động tạo một máy chủ mô phỏng. Bởi vì lược đồ định nghĩa các loại trường và enum, bản mô phỏng trả về dữ liệu có hình dạng chính xác như điểm cuối thực: một verdict là một trong ba giá trị enum, một số thập phân confidence từ 0 đến 1, một đối tượng signals đã được điền. Giao diện người dùng trỏ lệnh gọi fetch của nó vào URL mô phỏng của Apidog và nhận được các phản hồi thực tế ngay từ ngày đầu tiên. Khi phần phụ trợ thực sự được triển khai, họ chỉ cần thay đổi một URL cơ sở.

Bản mô phỏng cũng là nơi bạn thử nghiệm các trường hợp khó trước khi bất kỳ mã thực nào tồn tại. Định nghĩa các phản hồi ví dụ cho các phán quyết quan trọng:

Giao diện người dùng có thể xây dựng và tạo kiểu cho mọi trạng thái, bao gồm cả trạng thái lỗi, dựa trên bản mô phỏng. Đó là cách bạn triển khai giao diện người dùng và API song song thay vì tuần tự.

Chạy kiểm thử điểm cuối trong Apidog

Khi phần phụ trợ đang chạy, hãy tạo một yêu cầu trong Apidog cho POST /verify. Đặt phương thức, trỏ nó đến URL cục bộ của bạn, và trong tab Body chọn form-data, thêm trường image, đặt loại của nó thành File, và chọn một hình ảnh kiểm tra từ ổ đĩa.

Gửi nó đi, và Apidog hiển thị phản hồi JSON. Bây giờ hãy thêm các khẳng định để điều này trở thành một kiểm tra lặp lại được thay vì một lần duy nhất:

Xây dựng một kịch bản kiểm thử nhỏ chạy nhiều lần tải lên liên tiếp: một hình ảnh có Thông tin xác thực nội dung, một tệp JPEG thông thường không có bản kê khai, một tệp quá lớn và một tệp không phải hình ảnh được đổi tên với phần mở rộng .jpg. Mỗi trường hợp kiểm tra một nhánh khác nhau trong logic phán quyết và xác thực đầu vào của bạn. Lưu kịch bản và bạn có thể chạy lại toàn bộ bộ kiểm thử sau mỗi thay đổi, hoặc kết nối nó vào CI để một lỗi hồi quy trong hàm phán quyết làm lỗi quá trình xây dựng. Kiểm thử một điểm cuối tải lên bằng tay với curl sẽ nhanh chóng trở nên nhàm chán; một kịch bản đã lưu thì không.

Tăng cường bảo mật và các trường hợp ngoại lệ

Đường dẫn hạnh phúc là 80 phần trăm dễ dàng. Một dịch vụ xác minh sống hay chết dựa vào 20 phần trăm còn lại, bởi vì các đầu vào có tính chất đối địch; ai đó đang cố gắng biến một hình ảnh thành thứ không phải nó.

Tệp bị hỏng hoặc bị cắt bớt. Một tệp có thể có loại MIME là hình ảnh nhưng vẫn là rác. Trình đọc C2PA sẽ báo lỗi C2paError đối với dữ liệu mà nó không thể phân tích cú pháp, và hàm read_provenance đã chuyển điều đó thành một kết quả sạch thay vì lỗi 500. Để đảm bảo an toàn, bạn có thể giải mã hình ảnh bằng một thư viện như Pillow trước khi xử lý và từ chối nó với lỗi 400 nếu quá trình giải mã thất bại. Điều đó cũng chặn thủ thuật đổi tên tệp văn bản.

Bản kê khai bị thiếu. Đã được đề cập, nhưng đáng để nhắc lại vì đây là trường hợp phổ biến nhất và dễ mắc lỗi nhất. Không có bản kê khai không phải là lỗi và không phải là phán quyết. Thư viện báo hiệu điều đó bằng một C2paError có thông báo bắt đầu bằng ManifestNotFound; dịch vụ bắt lỗi đó một cách cụ thể và chuyển sang bộ phân loại. Đừng bao giờ để việc thiếu bản kê khai dẫn đến lỗi 500 hoặc phán quyết "giả".

Bộ phân loại hết thời gian chờ hoặc ngừng hoạt động. Bộ phân loại là một phụ thuộc mạng của bên thứ ba, vì vậy hãy giả định rằng đôi khi nó sẽ thất bại. Hàm classify_image sử dụng thời gian chờ httpx rõ ràng và trả về available: False khi có bất kỳ thời gian chờ hoặc lỗi HTTP nào. Hàm phán quyết sau đó sẽ chỉ dựa vào nguồn gốc, hoặc trả về uncertain với độ tin cậy bằng không nếu cũng không có bản kê khai. Điểm cuối vẫn phản hồi với 200 và một phán quyết trung thực; nhà cung cấp chậm không thể làm sập dịch vụ của bạn.

Bản kê khai giả mạo. Một bản kê khai có thể có mặt nhưng không hợp lệ, được ký bằng chứng chỉ xấu hoặc với các hàm băm không khớp với các pixel. Đây là trường hợp mà mọi người thường quên. Luôn kiểm tra validation_status; một mảng trống có nghĩa là bản kê khai đã được xác minh, một mảng được điền có nghĩa là nó không được xác minh. Hàm phán quyết coi một bản kê khai thất bại là một cảnh báo dẫn đến uncertain, không bao giờ là bằng chứng. Tin tưởng một bản kê khai chưa được xác thực còn tệ hơn là không có bản kê khai nào cả.

Tệp lớn và lạm dụng. Giới hạn kích thước tải lên, ví dụ sử dụng 12 MB, và từ chối bất kỳ thứ gì lớn hơn bằng lỗi 413 trước khi đọc toàn bộ nội dung vào bộ nhớ nơi bạn có thể. Đặt giới hạn tốc độ trước điểm cuối; xác thực mật mã và một lệnh gọi API đi cho mỗi yêu cầu không miễn phí, và một điểm cuối xác minh mở là một mục tiêu hấp dẫn.

Quyền riêng tư. Bạn đang nhận hình ảnh người dùng. Xử lý chúng trong bộ nhớ hoặc trong một tệp tạm thời mà bạn xóa ngay lập tức, như ví dụ đã làm, và không ghi nhật ký các byte hình ảnh. Nếu bạn đang gửi hình ảnh đến một bộ phân loại của bên thứ ba, hãy nói rõ điều đó trong chính sách quyền riêng tư của bạn và đảm bảo điều đó được phép cho trường hợp sử dụng của bạn.

Mỗi tín hiệu bắt và bỏ lỡ điều gì

Bảng này là mô hình tinh thần cần giữ. Đó là lý do tại sao dịch vụ sử dụng cả hai.

Kịch bản Tín hiệu nguồn gốc C2PA Tín hiệu bộ phân loại
Hình ảnh AI từ công cụ viết Thông tin xác thực nội dung Bắt được: bản kê khai đặt tên trình tạo Thường bắt được: có tạo tác
Hình ảnh AI bị xóa siêu dữ liệu (ảnh chụp màn hình, tải lại) Bỏ lỡ: không có bản kê khai để đọc Bắt được: hoạt động trên pixel, không cần siêu dữ liệu
Ảnh thật từ máy ảnh có ký Thông tin xác thực nội dung Xác nhận: bản kê khai hợp lệ, trình tạo không phải AI Có thể dương tính giả khi nén nặng hoặc chỉnh sửa
Ảnh thật không có siêu dữ liệu nào cả Không có tín hiệu: không có gì để xác thực Chỉ đoán tốt nhất: có tính xác suất, có thể sai
Hình ảnh có bản kê khai giả mạo hoặc bị chỉnh sửa Bắt được: validation_status đánh dấu lỗi Có thể bắt được hoặc không, tùy thuộc vào pixel
Trình tạo mới mà bộ phân loại chưa được đào tạo trên Chỉ bắt được nếu công cụ viết bản kê khai Thường bỏ lỡ: nằm ngoài phân phối đào tạo
Ảnh thật đã chỉnh sửa nhiều (chỉnh sửa AI trên nền thật) Bản kê khai, nếu có, ghi lại lịch sử chỉnh sửa Mơ hồ: tổng hợp một phần, điểm số nằm ở mức trung bình

Đọc qua bất kỳ hàng nào và bạn sẽ thấy cùng một câu chuyện: nơi một tín hiệu bị mù, tín hiệu kia thường không. Nguồn gốc chính xác nhưng hiếm; bộ phân loại phổ quát nhưng mơ hồ. Phán quyết kết hợp đáng tin cậy hơn bất kỳ cột nào một mình, và giá trị uncertain trung thực tồn tại cho những hàng mà cả hai tín hiệu đều yếu.

Các trường hợp sử dụng trong thế giới thực

Mô hình này không mang tính lý thuyết. Một vài trường hợp nó phù hợp trực tiếp:

Điểm chung: bạn muốn một quy trình tự động, nhanh chóng đầu tiên trung thực về sự không chắc chắn của chính nó, để sự chú ý của con người được tập trung vào nơi thực sự cần thiết.

Kết luận

Phát hiện hình ảnh do AI tạo ra một cách hiệu quả không phải là việc tìm ra một bài kiểm tra hoàn hảo. Đó là việc kết hợp các tín hiệu độc lập và trung thực về độ tin cậy.

Để xây dựng điều này trong thực tế, hãy thiết kế lược đồ /verify, tạo máy chủ mô phỏng và chạy các kiểm thử điểm cuối của bạn ở một nơi. Tải xuống Apidog để thiết kế, mô phỏng và kiểm thử API khi bạn xây dựng nó, sau đó chuyển từ bản mô phỏng sang phần phụ trợ trực tiếp chỉ với một thay đổi URL cơ sở duy nhất.

Tải xuống ứng dụng

Thực hành thiết kế API trong Apidog

Khám phá cách dễ dàng hơn để xây dựng và sử dụng API