มีคนอัปโหลดรูปภาพลงในผลิตภัณฑ์ของคุณแล้วอ้างว่ารูปนั้นถ่ายจากกล้อง แบ็กเอนด์ของคุณสามารถพิสูจน์หรือหักล้างเรื่องนั้นได้หรือไม่? ตอนนี้โปรแกรมสร้างภาพสามารถสร้างผลลัพธ์ที่ดูสมจริงสำหรับผู้ตรวจสอบที่เป็นมนุษย์ได้แล้ว ดังนั้นการ "เชื่อสายตา" จึงใช้ไม่ได้ผลมานานแล้ว ข่าวดีก็คือคุณไม่จำเป็นต้องฝึกโมเดลของคุณเองเพื่อสร้างคำตอบที่เป็นประโยชน์ คุณสามารถรวมสัญญาณอิสระสองอย่างเข้าด้วยกัน ได้แก่ รายการระบุแหล่งที่มาทางวิทยาการเข้ารหัส และตัวแยกประเภทการเรียนรู้ของเครื่อง ให้เป็นคำตัดสินเดียวที่น่าเชื่อถือกว่าสัญญาณใดสัญญาณหนึ่งเพียงอย่างเดียว
บทช่วยสอนนี้จะแนะนำการสร้างแบ็กเอนด์ดังกล่าวให้เป็นบริการเดียวที่มีเอนด์พอยต์ POST /verify คุณจะส่งรูปภาพไปให้ แล้วมันจะส่งคำตัดสินในรูปแบบ JSON กลับมาพร้อมคะแนนความเชื่อมั่นและรายละเอียดแหล่งที่มาที่พบ เราจะใช้ Python และ FastAPI สำหรับเซิร์ฟเวอร์, เครื่องมือ C2PA แบบโอเพนซอร์สสำหรับสัญญาณแหล่งที่มา, และ API ตรวจจับแบบโฮสต์สำหรับสัญญาณตัวแยกประเภท เนื่องจากนี่เป็นโปรเจกต์ API เราจะออกแบบสัญญาเอนด์พอยต์ก่อนและใช้ Apidog เพื่อจำลองและทดสอบมัน เพื่อให้ทีมฟรอนต์เอนด์ของคุณสามารถเริ่มรวมระบบได้ก่อนที่โค้ดแบ็กเอนด์จะเสร็จสิ้น
สรุป (TL;DR)
คุณจะสร้างบริการ FastAPI ที่เปิดเผย POST /verify ซึ่งรับการอัปโหลดรูปภาพ, แยกและตรวจสอบ manifest Content Credentials ของ C2PA ด้วยไลบรารี c2pa-python, เรียกใช้ตัวแยกประเภทการตรวจจับ AI แบบโฮสต์เป็นสัญญาณอิสระที่สอง, และส่งคืนคำตัดสินในรูปแบบ JSON เดียว (likely_authentic, likely_ai, หรือ uncertain) พร้อมคะแนนความเชื่อมั่นและรายละเอียดแหล่งที่มาดิบ นอกจากนี้ คุณยังจะออกแบบสคีมา OpenAPI สำหรับเอนด์พอยต์และใช้ Apidog เพื่อสร้างเซิร์ฟเวอร์จำลองและรันการทดสอบเอนด์พอยต์กับมัน
ทำไมต้องสองสัญญาณแทนที่จะเป็นหนึ่งเดียว
ก่อนที่จะเขียนโค้ดใดๆ สิ่งสำคัญคือต้องเข้าใจอย่างชัดเจนว่าคุณกำลังตรวจจับอะไรอยู่ ไม่มีคุณสมบัติเดียวของไฟล์ที่บอกคุณว่า "มนุษย์สร้างสิ่งนี้" หรือ "AI สร้างสิ่งนี้" แต่มีเบาะแส และเบาะแสแต่ละอย่างจะจับภาพประเภทที่แตกต่างกันในขณะที่พลาดประเภทอื่นๆ
เบาะแสแรกคือแหล่งที่มา (provenance) C2PA (Coalition for Content Provenance and Authenticity) เป็นมาตรฐานเปิดที่แนบข้อมูลเมตาที่ป้องกันการปลอมแปลงและลงนามด้วยวิทยาการเข้ารหัสกับไฟล์สื่อ ข้อมูลเมตาที่รวมกันนี้เรียกว่า manifest และชื่อที่ผู้ใช้รู้จักคือ Content Credentials เมื่อเครื่องมือที่เข้าร่วม เช่น กล้อง, โปรแกรมแก้ไข, หรือโปรแกรมสร้างภาพ สร้างหรือเปลี่ยนแปลงภาพ มันสามารถเขียน manifest ที่บันทึกสิ่งที่เกิดขึ้นและลงนามด้วยใบรับรอง หากคุณสามารถอ่านและตรวจสอบ manifest นั้น คุณจะได้รับคำยืนยันที่แข็งแกร่งและตรวจสอบได้เกี่ยวกับประวัติของภาพ
ข้อเสียคือ C2PA เป็นแบบเลือกเข้าร่วม (opt-in) และ manifest นั้นเปราะบาง การจับภาพหน้าจอจะลบออก การเข้ารหัสใหม่ผ่านแอปส่งข้อความจะลบออก แพลตฟอร์มหลายแห่งจะลบข้อมูลเมตาเมื่ออัปโหลด ดังนั้นการไม่มี manifest จึงแทบไม่ได้บอกอะไรคุณเลย ไม่ได้หมายความว่าภาพนั้นเป็นของปลอม และไม่ได้หมายความว่าภาพนั้นเป็นของจริง
เบาะแสที่สองคือตัวแยกประเภททางสถิติ (statistical classifier) โมเดลการตรวจจับได้รับการฝึกฝนด้วยรูปภาพจริงและรูปภาพที่สร้างขึ้นนับล้านภาพ และเรียนรู้สิ่งผิดปกติทางสายตาที่โปรแกรมสร้างภาพมักจะทิ้งไว้เบื้องหลัง มันทำงานกับรูปภาพใดๆ ไม่ว่าจะมีข้อมูลเมตาหรือไม่ก็ตาม แต่มันเป็นแบบความน่าจะเป็น มันส่งคืนความน่าจะเป็น ไม่ใช่ข้อเท็จจริง และอาจผิดพลาดได้ โดยเฉพาะอย่างยิ่งกับรูปภาพที่อยู่นอกการกระจายการฝึกอบรมหรือรูปภาพที่ถูกบีบอัดอย่างมาก
สัญญาณใดสัญญาณหนึ่งไม่เพียงพอต่อการตัดสิน Provenance นั้นแม่นยำแต่แทบจะไม่มีอยู่จริง ตัวแยกประเภทสามารถใช้งานได้เสมอแต่ไม่เคยแน่นอน เมื่อรวมเข้าด้วยกัน คุณจะได้คำตัดสินที่พูดได้ว่า "นี่คือสิ่งที่วิทยาการเข้ารหัสพิสูจน์ นี่คือสิ่งที่โมเดลประมาณการ และนี่คือความมั่นใจที่การรวมกันทำให้เรา" นั่นคือเป้าหมายการออกแบบ หากคุณต้องการเจาะลึกว่าทำไมวิธีการแบบสัญญาณเดียวจึงล้มเหลว บทความของเราเกี่ยวกับ ทำไมการตรวจจับภาพ AI จึงล้มเหลว ครอบคลุมรูปแบบความล้มเหลวโดยละเอียด
ภาพรวมสถาปัตยกรรม
บริการนี้มีขนาดเล็กโดยตั้งใจ เอนด์พอยต์เดียว การเรียกใช้งานดาวน์สตรีมสองรายการ การตอบสนองที่รวมกันหนึ่งรายการ
┌─────────────────────────────┐
image ──▶ │ FastAPI POST /verify │
│ │
│ 1. ตรวจสอบการอัปโหลด │
│ 2. ┌──────────────────┐ │
│ │ C2PA manifest │ │ สัญญาณแหล่งที่มา
│ │ (c2pa-python) │ │
│ └──────────────────┘ │
│ 3. ┌──────────────────┐ │
│ │ classifier API │ │ สัญญาณทางสถิติ
│ │ (hosted detector) │ │
│ └──────────────────┘ │
│ 4. รวมเป็นคำตัดสิน │
└─────────────────────────────┘
│
▼
คำตัดสิน JSON + ความเชื่อมั่น
ขั้นตอนที่ 1 ตรวจสอบว่าไฟล์ที่อัปโหลดเป็นรูปภาพจริงในรูปแบบที่รองรับและอยู่ในขีดจำกัดขนาด ขั้นตอนที่ 2 อ่าน C2PA manifest ภายในเครื่อง ไม่มีการเรียกเครือข่าย เพียงแค่แยกวิเคราะห์และตรวจสอบใบรับรอง ขั้นตอนที่ 3 ส่งไบต์รูปภาพไปยังตัวแยกประเภทที่โฮสต์ผ่าน HTTPS ขั้นตอนที่ 4 รวมผลลัพธ์ทั้งสองด้วยฟังก์ชันกฎเล็กๆ และส่งคืนคำตัดสิน
ขั้นตอนสัญญาณทั้งสองเป็นอิสระต่อกัน สิ่งนี้มีความสำคัญต่อการจัดการข้อผิดพลาด: หากตัวแยกประเภทหมดเวลา คุณยังสามารถส่งคืนคำตัดสินบางส่วนจากสัญญาณแหล่งที่มาได้ และในทางกลับกัน เราจะกลับมาดูเรื่องนี้ในส่วนการเสริมความแข็งแกร่ง
สำหรับสแต็ก จำเป็นต้องใช้ Python 3.10 หรือใหม่กว่าเนื่องจากไลบรารี C2PA ต้องการสิ่งนี้ คุณจะใช้ FastAPI สำหรับเลเยอร์เว็บ, Uvicorn เพื่อรัน, python-multipart สำหรับการอัปโหลดไฟล์, httpx สำหรับการเรียกตัวแยกประเภทขาออก, และ c2pa-python สำหรับแหล่งที่มา
pip install fastapi "uvicorn[standard]" python-multipart httpx c2pa-python
สัญญาณ C2PA
Content Authenticity Initiative ภายใต้องค์กร GitHub contentauth เผยแพร่เครื่องมือ C2PA แบบโอเพนซอร์ส มีสองส่วนที่คุณจะได้ยิน:
c2patool, เครื่องมือบรรทัดคำสั่งสำหรับแสดงและเพิ่ม manifests มีประโยชน์สำหรับการตรวจสอบอย่างรวดเร็วจากเทอร์มินัล โปรดทราบว่าที่เก็บแบบสแตนด์อโลนถูกเก็บถาวรแล้ว และ CLI อยู่ในโปรเจกต์ Rustc2pa-rsc2pa-python, การผูก Python สำหรับไลบรารี Rust พื้นฐานเดียวกัน (c2pa-rs) นี่คือสิ่งที่บริการของคุณจะใช้ มันถูกเผยแพร่บน PyPI ในชื่อc2pa-pythonและติดตั้งด้วยpip install c2pa-python
เส้นทางการอ่านของไลบรารีมีศูนย์กลางอยู่ที่วัตถุ Reader คุณชี้ไปที่รูปภาพ จากนั้นขอ manifest store เป็น JSON นี่คือส่วนสำคัญของโมดูล provenance
# provenance.py
import json
import c2pa
def read_provenance(image_path: str) -> dict:
"""
อ่านและตรวจสอบ C2PA manifest จากรูปภาพ
ส่งคืน dict ที่ถูกทำให้เป็นมาตรฐานซึ่งอธิบายสิ่งที่พบ
"""
try:
with c2pa.Reader(image_path) as reader:
manifest_store = json.loads(reader.json())
except c2pa.C2paError as err:
# ManifestNotFound เป็นกรณีที่คาดไว้สำหรับรูปภาพส่วนใหญ่
if str(err).startswith("ManifestNotFound"):
return {
"has_manifest": False,
"validation": "none",
"detail": "ไม่พบ C2PA manifest ในรูปภาพนี้",
}
# C2paError อื่นๆ หมายความว่าไฟล์มีข้อมูล C2PA ที่เราไม่สามารถแยกวิเคราะห์ได้
return {
"has_manifest": True,
"validation": "error",
"detail": f"ไม่สามารถแยกวิเคราะห์ manifest: {err}",
}
active_label = manifest_store.get("active_manifest")
manifests = manifest_store.get("manifests", {})
active = manifests.get(active_label, {})
# validation_status จะปรากฏขึ้นเมื่อมีปัญหาในการตรวจสอบเท่านั้น
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": "อ่าน Manifest สำเร็จแล้ว",
}
ข้อสังเกตบางประการเกี่ยวกับสิ่งที่โค้ดทำ: Reader ถูกใช้เป็นตัวจัดการบริบทเพื่อให้ทรัพยากรพื้นฐานถูกปล่อย reader.json() ส่งคืน manifest store แบบเต็มในรูปแบบสตริง JSON; ไลบรารียังมี reader.detailed_json() หากคุณต้องการรายงานแบบยาวพร้อมกับการยืนยันและส่วนประกอบทุกอย่าง ผลลัพธ์ที่คาดหวังสำหรับการอัปโหลดส่วนใหญ่คือ C2paError ซึ่งข้อความเริ่มต้นด้วย ManifestNotFound เพราะรูปภาพส่วนใหญ่ไม่มี Content Credentials ถือว่าเป็นข้อมูล ไม่ใช่ความล้มเหลว
เมื่อมี manifest สองฟิลด์ที่สำคัญที่สุดสำหรับการตัดสินคือ claim_generator ซึ่งเป็นสตริงที่บอกคุณว่าเครื่องมือใดเขียน manifest ตัวอย่างเช่น สตริงเฟิร์มแวร์กล้องหรือชื่อเครื่องมือภาพ AI ส่วนอาร์เรย์ validation_status จะว่างเปล่าเมื่อลายเซ็นและแฮชตรวจสอบถูกต้อง และจะถูกเติมด้วยรหัสข้อผิดพลาดเมื่อไม่ถูกต้อง Manifest ที่ไม่ถูกต้องถือเป็นธงแดงที่ควรแจ้งเตือนอย่างชัดเจน ซึ่งหมายความว่าไฟล์อ้างอิงประวัติที่การเข้ารหัสไม่สามารถยืนยันได้
สิ่งที่สัญญาณนี้ไม่สามารถทำได้: ไม่สามารถให้คำตัดสินแก่คุณเมื่อไม่มี manifest ซึ่งเป็นกรณีส่วนใหญ่ นั่นคือเหตุผลที่คุณต้องการสัญญาณที่สอง
สัญญาณตัวจำแนก
ตัวจำแนกเป็น API ที่โฮสต์ซึ่งให้คะแนนความน่าจะเป็นที่ภาพจะถูกสร้างโดย AI มีผู้ให้บริการหลายรายเสนอสิ่งนี้ บทช่วยสอนนี้ใช้ Sightengine เนื่องจากโมเดลการตรวจจับ AI มี HTTP API ที่มีการบันทึกและรูปแบบการตอบสนองที่ชัดเจน แต่รูปแบบนี้เหมือนกันสำหรับผู้ให้บริการรายใดก็ได้ คุณเพียงแค่เปลี่ยน URL, พารามิเตอร์ และฟิลด์ที่คุณอ่าน หากคุณกำลังพิจารณาตัวเลือกต่างๆ บทสรุปของเราเกี่ยวกับ API การตรวจจับภาพ AI ที่ดีที่สุด เปรียบเทียบความแม่นยำ ราคา และขอบเขตการทำงานของผู้ให้บริการต่างๆ
เอนด์พอยต์ตรวจสอบของ Sightengine คือ https://api.sightengine.com/1.0/check.json คุณ POST รูปภาพเป็น media ตั้งค่า models เป็น genai และส่ง api_user และ api_secret ของคุณ การตอบสนองจะรวม type.ai_generated ซึ่งเป็นคะแนนจาก 0 ถึง 1 โดยที่ค่าที่สูงกว่าหมายถึงแนวโน้มที่จะถูกสร้างโดย AI มากขึ้น
# 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:
"""
ส่งรูปภาพไปยังตัวตรวจจับที่โฮสต์ไว้
ส่งคืน dict ที่ถูกทำให้เป็นมาตรฐานพร้อมคะแนนที่สร้างโดย AI
"""
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)}
ฟังก์ชันนี้เป็นแบบ async ดังนั้นตัวจำแนกที่ทำงานช้าจะไม่บล็อกอีเวนต์ลูป การหมดเวลานั้นชัดเจนและสั้น; แปดวินาทีเป็นค่าเริ่มต้นที่เหมาะสมสำหรับเอนด์พอยต์แบบโต้ตอบ และคุณควรปรับให้เข้ากับเวลาแฝงจริงของผู้ให้บริการของคุณ ทุกเส้นทางความล้มเหลวจะคืนค่า available: False พร้อมเหตุผลที่เครื่องอ่านได้แทนที่จะส่งข้อยกเว้น นั่นเป็นความตั้งใจ: การขัดข้องของตัวจำแนกควรทำให้คำตัดสินลดลง ไม่ใช่ทำให้คำขอขัดข้อง ตรรกะการตัดสินในส่วนถัดไปจะอ่าน available และตัดสินใจว่าจะทำอย่างไร
ให้ถือว่าคะแนนเป็นค่าประมาณ ค่า 0.92 คือ "โมเดลค่อนข้างแน่ใจ" ไม่ใช่ "สิ่งนี้ได้รับการพิสูจน์แล้วว่าเป็น AI" ผู้ขายจะอัปเดตโมเดลของตน และความแม่นยำจะแตกต่างกันไปตามเครื่องมือสร้างและตามปริมาณการบีบอัดภาพก่อนที่จะถึงคุณ สำหรับมุมมองที่กว้างขึ้นเกี่ยวกับพฤติกรรมของเครื่องมือเหล่านี้ในทางปฏิบัติ โปรดดูคู่มือของเราเกี่ยวกับ วิธีตรวจสอบว่าภาพถูกสร้างโดย AI หรือไม่
การออกแบบสัญญา /verify
นี่คือส่วนที่ Apidog ได้รับความนิยม ก่อนที่จะเขียนตัวจัดการเส้นทาง ให้ออกแบบคำขอและคำตอบเป็นสคีมา OpenAPI การทำสิ่งนี้ก่อนจะให้สามสิ่งแก่คุณ: แหล่งความจริงเดียวที่ทั้งสองทีมตกลงกัน, เซิร์ฟเวอร์จำลองที่ฟรอนต์เอนด์สามารถเรียกใช้ได้ทันที, และชุดการทดสอบที่คุณสามารถรันได้ทันทีที่แบ็กเอนด์มีอยู่จริง
คำขอ
POST /verify รับเนื้อหา multipart/form-data ที่มีฟิลด์เดียวคือ image ซึ่งเป็นไฟล์ที่จะตรวจสอบ ทำให้ง่ายเข้าไว้ พารามิเตอร์การสอบถามที่ไม่บังคับสามารถเพิ่มเข้ามาได้ภายหลัง
การตอบสนอง
การตอบสนองคือจุดที่งานออกแบบจะให้ผลลัพธ์ที่ดี มันจะต้องแสดงคำตัดสินสุดท้าย ความเชื่อมั่น และสัญญาณดิบทั้งสองเพื่อให้ผู้เรียกสามารถตรวจสอบการตัดสินใจได้ นี่คือรูปแบบ
{
"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": "A valid C2PA manifest names an AI image tool, and the classifier scored the image as likely AI-generated.",
"checked_at": "2026-05-21T09:30:00Z"
}
verdict คือค่าสตริงหนึ่งในสามค่า: likely_authentic, likely_ai, หรือ uncertain มีสามค่า ไม่ใช่สอง เพราะความซื่อสัตย์เป็นสิ่งสำคัญ; เมื่อสัญญาณขัดแย้งกันหรืออ่อนแอทั้งคู่ "uncertain" คือคำตอบที่ถูกต้อง confidence คือค่าทศนิยม 0 ถึง 1 ที่อธิบายว่าสัญญาณสนับสนุนคำตัดสินนั้นมากน้อยเพียงใด signals มีข้อมูลดิบทั้งสองเพื่อให้ผู้เรียกสามารถแสดง UI ของตนเองหรือใช้ policy ของตนเองได้ explanation คือประโยคที่มนุษย์อ่านเข้าใจได้สำหรับเจ้าหน้าที่สนับสนุนและบันทึก
การแสดงสิ่งนี้เป็นสคีมา OpenAPI นั้นตรงไปตรงมา นี่คือคอมโพเนนต์การตอบสนองที่คุณจะใส่ในสเปคของคุณ
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 }
คุณสามารถเขียนสคีมานี้ได้โดยตรงใน Apidog's visual designer หรือนำเข้าไฟล์ OpenAPI ที่มีอยู่ การออกแบบ API ก่อนการนำไปใช้งานเป็นเวิร์กโฟลว์ที่ควรนำมาใช้โดยทั่วไป; คู่มือการใช้งานโหมด spec-first ของเรา แสดงวิธีทำตั้งแต่ต้นจนจบใน Apidog
การแนะนำโค้ด
ตอนนี้ชิ้นส่วนต่างๆ มารวมกัน ด้านล่างนี้คือแอป FastAPI: การตรวจสอบอินพุต, การเรียกสัญญาณทั้งสอง, ฟังก์ชันการรวม, และเส้นทาง
การรวมสองสัญญาณเข้าด้วยกัน
ฟังก์ชันการตัดสิน (verdict function) เป็นหัวใจของบริการ มันเข้ารหัส policy ของคุณ แหล่งที่มา (provenance) เมื่อถูกต้องและมีอยู่ จะเป็นสัญญาณที่แข็งแกร่งกว่าเพราะเป็นแบบวิทยาการเข้ารหัส; ตัวจำแนก (classifier) เป็นตัวตัดสินในกรณีที่ผลลัพธ์ไม่ชัดเจน และเป็นทางเลือกสำรอง นี่คือเวอร์ชันที่ชัดเจนและอนุรักษ์นิยม
# verdict.py
def combine_signals(provenance: dict, classifier: dict) -> dict:
"""รวมสัญญาณแหล่งที่มาและตัวจำแนกเข้าเป็นคำตัดสินเดียว"""
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")
# Heuristic: เครื่องมือ AI ที่รู้จักมักจะระบุตัวเองใน manifest
ai_keywords = ("firefly", "dall-e", "dalle", "midjourney", "stable",
"gpt", "gemini", "imagen", "generat")
generator_looks_ai = any(k in generator for k in ai_keywords)
# กรณีที่ 1: manifest ที่ถูกต้องซึ่งระบุเครื่องมือสร้าง AI สัญญาณ AI แข็งแกร่ง
if has_manifest and validation == "valid" and generator_looks_ai:
return _verdict("likely_ai", 0.95,
"C2PA manifest ที่ถูกต้องระบุว่าเป็นเครื่องมือสร้างภาพ AI")
# กรณีที่ 2: manifest ที่ถูกต้องจากกล้องหรือโปรแกรมแก้ไขที่ไม่ใช่ AI สัญญาณแท้จริงแข็งแกร่ง
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,
"Manifest ดูเป็นของแท้แต่ตัวจำแนกไม่เห็นด้วย; สัญญาณขัดแย้งกัน")
return _verdict("likely_authentic", 0.9,
"พบ C2PA manifest ที่ถูกต้องจากเครื่องมือที่ไม่ใช่ AI")
# กรณีที่ 3: manifest ที่ตรวจสอบไม่ผ่าน ถือว่าน่าสงสัย
if has_manifest and validation in ("invalid", "error"):
return _verdict("uncertain", 0.6,
"ภาพมี C2PA manifest ที่ตรวจสอบไม่ผ่าน; ประวัติที่อ้างสิทธิ์ไม่ได้รับการยืนยัน")
# กรณีที่ 4: ไม่มี manifest กลับไปใช้ตัวจำแนกทั้งหมด
if classifier_ok and ai_score is not None:
if ai_score >= 0.7:
return _verdict("likely_ai", round(ai_score, 2),
"ไม่มีข้อมูลแหล่งที่มา; ตัวจำแนกให้คะแนนภาพว่าน่าจะสร้างโดย AI")
if ai_score <= 0.3:
return _verdict("likely_authentic", round(1 - ai_score, 2),
"ไม่มีข้อมูลแหล่งที่มา; ตัวจำแนกให้คะแนนภาพว่าน่าจะเป็นของแท้")
return _verdict("uncertain", 0.5,
"ไม่มีข้อมูลแหล่งที่มาและคะแนนตัวจำแนกไม่สามารถสรุปได้")
# กรณีที่ 5: ไม่มี manifest และไม่มีตัวจำแนก เราไม่สามารถบอกได้จริงๆ
return _verdict("uncertain", 0.0,
"ไม่มีข้อมูลแหล่งที่มาและตัวจำแนกไม่พร้อมใช้งาน")
def _verdict(verdict: str, confidence: float, explanation: str) -> dict:
return {"verdict": verdict, "confidence": confidence,
"explanation": explanation}
อ่านกรณีทั้งห้าแล้วคุณจะเห็น policy Manifest ที่ถูกต้องจะมีความสำคัญกว่า Manifest ที่ล้มเหลวเป็นคำเตือน ไม่ใช่หลักฐานของการปลอมแปลง ดังนั้นจึงถูกจัดอยู่ในประเภท "uncertain" การขัดแย้งกันระหว่าง manifest ที่สะอาดกับคะแนนตัวจำแนกที่สูงก็จัดอยู่ในประเภท "uncertain" เช่นกัน แทนที่จะเลือกข้าง และเมื่อไม่มีสัญญาณทั้งสอง บริการจะแจ้งอย่างตรงไปตรงมาด้วยความมั่นใจเป็นศูนย์แทนที่จะเดา คุณจะปรับค่า threshold เหล่านี้ตามระดับความเสี่ยงที่คุณยอมรับได้; แพลตฟอร์มเนื้อหาและห้องข่าวจะกำหนดเส้นแบ่งที่แตกต่างกัน
แอป 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. ตรวจสอบการอัปโหลด
if image.content_type not in ALLOWED_TYPES:
raise HTTPException(
status_code=415,
detail=f"ประเภท {image.content_type} ไม่รองรับ "
f"โปรดส่ง JPEG, PNG, หรือ WebP",
)
image_bytes = await image.read()
if len(image_bytes) == 0:
raise HTTPException(status_code=400, detail="ไฟล์ว่างเปล่า")
if len(image_bytes) > MAX_BYTES:
raise HTTPException(status_code=413, detail="ไฟล์เกินขีดจำกัด 12 MB")
# 2. สัญญาณแหล่งที่มา ตัวอ่าน C2PA ต้องการเส้นทางไฟล์
# ดังนั้นจึงเขียนลงในไฟล์ชั่วคราวแล้วลบออกในภายหลัง
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. สัญญาณตัวจำแนก ความล้มเหลวจะคืนค่า available: False ไม่ใช่ข้อยกเว้น
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. รวมและตอบกลับ
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(),
})
รันในเครื่องด้วย uvicorn main:app --reload แล้วเอนด์พอยต์จะพร้อมใช้งานที่ http://127.0.0.1:8000/verify ตัวอ่าน C2PA คาดหวังเส้นทางไฟล์ ดังนั้นตัวจัดการจะเขียนไฟล์ที่อัปโหลดลงในไฟล์ชั่วคราวและลบออกในบล็อก finally; ตัวจำแนกทำงานได้โดยตรงจากไบต์ สังเกตว่าคำขอไม่เคยล้มเหลวเนื่องจาก manifest หายไปหรือตัวจำแนกไม่ทำงาน; ทั้งสองเป็นสถานะปกติที่ฟังก์ชันการตัดสินจัดการ
แบ็กเอนด์ที่ออกแบบด้วยวิธีนี้ เป็นบริการที่เน้นงานเฉพาะที่มีสัญญาที่ชัดเจน เหมาะกับแนวโน้มที่กว้างขึ้นของผลิตภัณฑ์ที่เปิดเผยความสามารถหลักผ่าน API หากแนวคิดนี้ทำให้คุณสนใจ บทความของเราเกี่ยวกับ ซอฟต์แวร์ที่เปลี่ยนเป็นแบบ Headless คุ้มค่าที่จะอ่าน
การทดสอบและการจำลองด้วย Apidog
นี่คือปัญหาของเวิร์กโฟลว์: ทีมฟรอนต์เอนด์ของคุณต้องการสร้าง UI การอัปโหลดและแผงผลลัพธ์ตอนนี้ แต่แบ็กเอนด์ข้างต้นใช้เวลาหลายวันกว่าจะเสร็จ ได้คีย์ และปรับใช้ คุณไม่ต้องการให้พวกเขาติดขัด นี่คือสิ่งที่เซิร์ฟเวอร์จำลองมีไว้ใช้ และนี่คือจุดที่การออกแบบสคีมาตั้งแต่แรกเริ่มให้ผลตอบแทน
สร้างเซิร์ฟเวอร์จำลองจากสคีมา
นำเข้า OpenAPI schema เข้าสู่ Apidog หรือสร้าง endpoint /verify ใน visual designer ของ Apidog Apidog จะอ่าน response schema และสร้าง mock server โดยอัตโนมัติ เนื่องจาก schema กำหนดประเภทฟิลด์และ enum, mock จะส่งคืนข้อมูลที่มีโครงสร้างเหมือนกับ endpoint จริงทุกประการ: verdict ที่เป็นหนึ่งในสามค่า enum, confidence float ระหว่าง 0 ถึง 1, และออบเจ็กต์ signals ที่มีข้อมูลครบถ้วน Frontend จะชี้การเรียก fetch ไปที่ Apidog mock URL และรับ response ที่สมจริงได้ตั้งแต่วันแรก เมื่อ backend จริงถูกส่งมอบ พวกเขาก็แค่เปลี่ยน base URL เดียว
เซิร์ฟเวอร์จำลองยังเป็นที่ที่คุณสามารถทดสอบกรณีที่ยากลำบากก่อนที่โค้ดจริงจะมีอยู่ กำหนดตัวอย่างการตอบสนองสำหรับคำตัดสินที่สำคัญ:
- การตอบสนอง
likely_authenticพร้อม manifest กล้องที่ถูกต้อง - การตอบสนอง
likely_aiโดยมีเครื่องมือ AI ระบุไว้ใน manifest - การตอบสนอง
uncertainเมื่อตัวจำแนกไม่พร้อมใช้งาน - การตอบสนองข้อผิดพลาด
415และ413
ฟรอนต์เอนด์สามารถสร้างและจัดรูปแบบทุกสถานะ รวมถึงสถานะข้อผิดพลาด โดยใช้โมเดลจำลอง นี่คือวิธีที่คุณจัดส่ง UI และ API พร้อมกันแทนที่จะเรียงตามลำดับ
รันการทดสอบเอนด์พอยต์ใน Apidog
เมื่อแบ็กเอนด์ทำงานแล้ว ให้สร้างคำขอใน Apidog สำหรับ POST /verify กำหนด method ชี้ไปที่ URL โลคัลของคุณ และในแท็บ Body เลือก form-data เพิ่มฟิลด์ image กำหนดประเภทเป็น File และเลือกรูปภาพทดสอบจากดิสก์
ส่งไป แล้ว Apidog จะแสดงการตอบกลับในรูปแบบ JSON ตอนนี้เพิ่ม assertion เพื่อให้สิ่งนี้กลายเป็นการตรวจสอบที่ทำซ้ำได้ แทนที่จะเป็นเพียงการคลิกครั้งเดียว:
- ยืนยันว่าสถานะการตอบกลับคือ
200 - ยืนยันว่า
verdictมีอยู่และเป็นหนึ่งในสามสตริงที่อนุญาต - ยืนยันว่า
confidenceเป็นตัวเลขระหว่าง 0 ถึง 1 - ยืนยันว่า
signals.provenance.has_manifestเป็นค่าบูลีน
สร้างสถานการณ์การทดสอบขนาดเล็กที่รันการอัปโหลดหลายรายการต่อเนื่องกัน: รูปภาพที่มี Content Credentials, รูปภาพ JPEG ธรรมดาที่ไม่มี manifest, ไฟล์ขนาดใหญ่เกินไป, และไฟล์ที่ไม่ใช่รูปภาพที่เปลี่ยนชื่อด้วยนามสกุล .jpg แต่ละรายการจะตรวจสอบส่วนที่แตกต่างกันของตรรกะการตัดสินและการตรวจสอบอินพุตของคุณ บันทึกสถานการณ์และคุณสามารถรันชุดการทดสอบทั้งหมดซ้ำได้หลังจากการเปลี่ยนแปลงทุกครั้ง หรือเชื่อมโยงเข้ากับ CI เพื่อให้การถดถอยในฟังก์ชันการตัดสินทำให้ build ล้มเหลว การทดสอบเอนด์พอยต์การอัปโหลดด้วยตนเองด้วย curl นั้นจะล้าสมัยอย่างรวดเร็ว; สถานการณ์ที่บันทึกไว้ไม่เป็นเช่นนั้น
การเสริมความแข็งแกร่งและกรณีขอบ
เส้นทางปกติเป็น 80 เปอร์เซ็นต์ที่ง่าย บริการยืนยันตัวตนจะอยู่รอดหรือล้มเหลวที่ 20 เปอร์เซ็นต์ที่เหลือ เพราะข้อมูลอินพุตมีลักษณะที่เป็นปฏิปักษ์โดยธรรมชาติ มีคนพยายามปลอมแปลงภาพให้ดูเหมือนเป็นอย่างอื่น
ไฟล์เสียหายหรือไฟล์ที่ถูกตัด. ไฟล์อาจมีประเภท MIME ของรูปภาพ แต่ยังคงเป็นไฟล์ขยะ ตัวอ่าน C2PA จะส่ง C2paError หากข้อมูลไม่สามารถแยกวิเคราะห์ได้ และฟังก์ชัน read_provenance จะเปลี่ยนให้เป็นผลลัพธ์ที่สะอาดแทนที่จะเป็น 500 สำหรับการป้องกันสองชั้น คุณสามารถถอดรหัสรูปภาพด้วยไลบรารีอย่าง Pillow ก่อนการประมวลผล และปฏิเสธด้วย 400 หากการถอดรหัสล้มเหลว ซึ่งยังบล็อกเทคนิคการเปลี่ยนชื่อไฟล์ข้อความด้วย
Manifest หายไป. ได้รับการครอบคลุมแล้ว แต่ควรกล่าวซ้ำเพราะเป็นกรณีที่พบบ่อยที่สุดและผิดพลาดง่ายที่สุด การไม่มี manifest ไม่ใช่ข้อผิดพลาดและไม่ใช่คำตัดสิน ไลบรารีจะส่งสัญญาณด้วย C2paError ที่มีข้อความขึ้นต้นด้วย ManifestNotFound; บริการจะจับสิ่งนั้นโดยเฉพาะและดำเนินการต่อไปยังตัวจำแนก อย่าให้ manifest ที่หายไปทำให้เกิด 500 หรือคำตัดสินว่า "ของปลอม"
ตัวจำแนกหมดเวลาหรือหยุดทำงาน. ตัวจำแนกเป็นส่วนหนึ่งของเครือข่ายบุคคลที่สาม ดังนั้นสมมติว่าจะต้องมีข้อผิดพลาดบางครั้ง ฟังก์ชัน classify_image ใช้การหมดเวลา httpx ที่ชัดเจน และคืนค่า available: False เมื่อเกิดการหมดเวลาหรือข้อผิดพลาด HTTP ใดๆ จากนั้นฟังก์ชันการตัดสินจะย้อนกลับไปใช้แหล่งที่มาเพียงอย่างเดียว หรือคืนค่า uncertain ด้วยความมั่นใจเป็นศูนย์ หากไม่มี manifest เอนด์พอยต์ยังคงตอบกลับด้วย 200 และคำตัดสินที่ซื่อสัตย์; ผู้ขายที่ทำงานช้าไม่สามารถทำให้บริการของคุณหยุดทำงานได้
Manifest ปลอมแปลง. Manifest อาจมีอยู่แต่ไม่ถูกต้อง ลงนามด้วยใบรับรองที่ไม่ดี หรือมีค่าแฮชที่ไม่ตรงกับพิกเซล นี่คือกรณีที่คนมักลืม ตรวจสอบ validation_status เสมอ; อาร์เรย์ว่างเปล่าหมายถึง manifest ได้รับการตรวจสอบแล้ว ส่วนอาร์เรย์ที่มีข้อมูลหมายถึงไม่ผ่าน ฟังก์ชันการตัดสินจะถือว่า manifest ที่ล้มเหลวเป็นคำเตือนที่นำไปสู่ uncertain ไม่ใช่หลักฐาน การเชื่อ manifest ที่ไม่ได้รับการตรวจสอบนั้นแย่กว่าการไม่มี manifest เลย
ไฟล์ขนาดใหญ่และการใช้งานที่ไม่เหมาะสม. กำหนดขีดจำกัดขนาดการอัปโหลด ตัวอย่างใช้ 12 MB และปฏิเสธไฟล์ที่ใหญ่กว่าด้วย 413 ก่อนที่จะอ่านเนื้อหาทั้งหมดเข้าสู่หน่วยความจำ ใส่การจำกัดอัตรา (rate limit) ไว้หน้าเอนด์พอยต์; การตรวจสอบความถูกต้องด้วยการเข้ารหัสและการเรียก API ภายนอกต่อคำขอไม่ได้ไม่มีค่าใช้จ่าย และเอนด์พอยต์การยืนยันที่เปิดกว้างเป็นเป้าหมายที่น่าสนใจ
ความเป็นส่วนตัว. คุณกำลังรับรูปภาพจากผู้ใช้ ประมวลผลในหน่วยความจำหรือในไฟล์ชั่วคราวที่คุณลบออกทันทีตามตัวอย่าง และอย่าบันทึกไบต์รูปภาพ หากคุณกำลังส่งรูปภาพไปยังตัวจำแนกบุคคลที่สาม ให้ระบุในนโยบายความเป็นส่วนตัวของคุณและตรวจสอบให้แน่ใจว่าได้รับอนุญาตสำหรับการใช้งานของคุณ
แต่ละสัญญาณจับอะไรได้และพลาดอะไรไป
ตารางนี้คือโมเดลความคิดที่ต้องเก็บไว้ นั่นคือเหตุผลที่บริการใช้ทั้งสองอย่าง
| สถานการณ์ | สัญญาณแหล่งที่มา C2PA | สัญญาณตัวจำแนก |
|---|---|---|
| ภาพ AI จากเครื่องมือที่เขียน Content Credentials | จับได้: manifest ระบุชื่อเครื่องมือสร้าง | มักจะจับได้: มีสิ่งผิดปกติ |
| ภาพ AI ที่ข้อมูลเมตาถูกลบออก (จับภาพหน้าจอ, อัปโหลดซ้ำ) | พลาด: ไม่มี manifest ให้อ่าน | จับได้: ทำงานบนพิกเซล ไม่ต้องใช้ข้อมูลเมตา |
| ภาพจริงจากกล้องที่ลงนาม Content Credentials | ยืนยัน: manifest ถูกต้อง, ไม่ใช่เครื่องมือสร้าง AI | อาจผิดพลาดกับภาพที่มีการบีบอัดมากหรือถูกแก้ไข |
| ภาพจริงที่ไม่มีข้อมูลเมตาเลย | ไม่มีสัญญาณ: ไม่มีอะไรให้ตรวจสอบ | คาดเดาเท่านั้น: เป็นความน่าจะเป็น อาจผิดพลาดได้ |
| ภาพที่มี manifest ปลอมแปลงหรือถูกแก้ไข | จับได้: validation_status แจ้งความผิดพลาด |
อาจจะจับได้หรือไม่ก็ได้ ขึ้นอยู่กับพิกเซล |
| เครื่องมือสร้างใหม่ที่ตัวจำแนกไม่เคยถูกฝึก | จับได้เฉพาะเมื่อเครื่องมือเขียน manifest | มักจะพลาด: อยู่นอกขอบเขตการฝึก |
| ภาพจริงที่ถูกแก้ไขอย่างหนัก (รีทัช AI บนฐานจริง) | Manifest หากมีอยู่ จะบันทึกประวัติการแก้ไข | คลุมเครือ: สังเคราะห์บางส่วน, คะแนนอยู่กลางๆ |
อ่านในแต่ละแถวแล้วคุณจะเห็นเรื่องราวเดียวกัน: เมื่อสัญญาณหนึ่งตาบอด อีกสัญญาณหนึ่งมักจะไม่Provenence นั้นแม่นยำแต่มีน้อย; ตัวจำแนกนั้นเป็นสากลแต่คลุมเครือ คำตัดสินที่รวมกันนั้นน่าเชื่อถือกว่าคอลัมน์ใดคอลัมน์หนึ่งเพียงลำพัง และค่า uncertain ที่ซื่อสัตย์นั้นมีอยู่สำหรับแถวที่สัญญาณทั้งสองอ่อนแอ
กรณีการใช้งานในโลกจริง
รูปแบบนี้ไม่ใช่วิชาการ มีบางแห่งที่เหมาะสมโดยตรง:
- แพลตฟอร์มเนื้อหาที่ผู้ใช้สร้างขึ้น. ตลาดหรือแอปโซเชียลสามารถเรียกใช้การอัปโหลดผ่าน
/verifyและติดป้ายหรือจัดคิวเพื่อตรวจสอบสิ่งใดก็ตามที่ให้คะแนนว่าน่าจะเป็น AI หรือสิ่งใดก็ตามที่มี manifest ที่ตรวจสอบไม่ผ่าน คำตัดสินสามค่าจะสามารถแมปได้ง่ายกับ "อนุญาต," "แจ้งธง," และ "ส่งให้มนุษย์ตรวจสอบ" - ห้องข่าวและการตรวจสอบข้อเท็จจริง. บรรณาธิการที่ตรวจสอบรูปภาพที่เป็นไวรัลจะได้รับทั้งแหล่งที่มาทางวิทยาการเข้ารหัส (ถ้ามี) และการประเมินโมเดลอิสระในการเรียกครั้งเดียว พร้อมประโยคอธิบายที่พวกเขาสามารถอ้างอิงในบันทึกได้
- การรับประกันภัยและการรับเคลม. เมื่อลูกค้าส่งหลักฐานภาพถ่าย ขั้นตอนการตรวจสอบจะยกธงเตือนสำหรับภาพที่ดูเหมือนถูกสร้างขึ้นหรือมี manifest ที่ถูกดัดแปลง ก่อนที่ผู้ประเมินที่เป็นมนุษย์จะใช้เวลาตรวจสอบ
- ไปป์ไลน์สินทรัพย์ภายใน. ทีมที่ต้องการกันภาพที่สร้างโดย AI ออกไป หรือติดป้ายอย่างชัดเจนในคลังสต็อก สามารถใช้
/verifyเป็นด่านกั้นการนำเข้าได้ - การเผยแพร่ที่คำนึงถึงแหล่งที่มา. เมื่อกล้องและโปรแกรมแก้ไขจำนวนมากขึ้นนำ Content Credentials มาใช้ CMS สามารถอ่าน manifest เมื่ออัปโหลดและแสดงตราสัญลักษณ์ที่ได้รับการยืนยัน โดยจะย้อนกลับไปใช้ตัวจำแนกเมื่อไม่มี manifest
จุดร่วมคือ: คุณต้องการการตรวจสอบเบื้องต้นที่รวดเร็ว อัตโนมัติ และซื่อสัตย์เกี่ยวกับความไม่แน่นอนของตนเอง เพื่อให้ความสนใจของมนุษย์ไปในที่ที่จำเป็นจริงๆ
สรุป
การตรวจจับภาพที่สร้างโดย AI อย่างมีประสิทธิภาพไม่ได้เกี่ยวกับการหาการทดสอบที่สมบูรณ์แบบเพียงหนึ่งเดียว แต่เกี่ยวกับการรวมสัญญาณอิสระและมีความซื่อสัตย์เกี่ยวกับความเชื่อมั่น
- C2PA Content Credentials ให้สัญญาณแหล่งที่มาที่แข็งแกร่ง สามารถตรวจสอบได้ด้วยวิทยาการเข้ารหัส แต่ manifest เป็นแบบเลือกเข้าร่วมและถูกลบออกได้ง่าย ดังนั้นมักจะไม่มีอยู่
- ตัวจำแนกที่โฮสต์ให้สัญญาณแบบสากลแต่เป็นความน่าจะเป็นที่ใช้งานได้กับรูปภาพใดๆ ไม่ว่าจะมีข้อมูลเมตาหรือไม่ก็ตาม
- การรวมทั้งสองเข้าด้วยกันในบริการ FastAPI ขนาดเล็กจะสร้างคำตัดสินสามค่า:
likely_authentic,likely_ai, หรือuncertainพร้อมคะแนนความเชื่อมั่นและสัญญาณดิบทั้งสองแนบมาเพื่อการตรวจสอบ - การออกแบบสัญญา OpenAPI ก่อนช่วยให้คุณสามารถจำลองเอนด์พอยต์ใน Apidog เพื่อให้ฟรอนต์เอนด์สามารถสร้างไปพร้อมกันได้ จากนั้นเรียกใช้สถานการณ์การทดสอบที่บันทึกไว้กับแบ็กเอนด์จริง
- ไม่มีเครื่องมือตรวจจับใดที่สมบูรณ์แบบ สองสัญญาณช่วยเพิ่มความเชื่อมั่น แต่ไม่ได้ขจัดความไม่แน่นอน ซึ่งเป็นเหตุผลว่าทำไมคำตัดสิน
uncertainจึงเป็นคุณสมบัติ ไม่ใช่ช่องว่าง
ในการสร้างสิ่งนี้จริงๆ ให้คุณออกแบบ schema /verify, สร้าง mock server, และเรียกใช้การทดสอบเอนด์พอยต์ของคุณในที่เดียว ดาวน์โหลด Apidog เพื่อออกแบบ, จำลอง, และทดสอบ API ในขณะที่คุณสร้างมัน จากนั้นย้ายจาก mock ไปสู่แบ็กเอนด์จริงด้วยการเปลี่ยน base-URL เพียงครั้งเดียว
