يقوم شخص ما بتحميل صورة إلى منتجك ويدّعي أن الكاميرا التقطتها. هل يمكن لخدمتك الخلفية (backend) إثبات أو دحض ذلك؟ تُنتج مولّدات الصور الآن نتائج تبدو حقيقية للمراجع البشري، لذا فإن "الثقة بالعيون" توقفت عن العمل منذ فترة. الخبر السار هو أنك لست بحاجة إلى تدريب نموذجك الخاص لتقديم إجابة مفيدة. يمكنك الجمع بين إشارتين مستقلتين، وهما بيان إثبات مصدر تشفيري ومصنف تعلم آلي، في حكم واحد يكون أكثر صدقًا من أي إشارة بمفردها.
يشرح هذا البرنامج التعليمي كيفية بناء تلك الخدمة الخلفية كخدمة واحدة تحتوي على نقطة نهاية POST /verify. تُقدّم لها صورة، فتعيد حكمًا بتنسيق JSON مع درجة ثقة وتفاصيل المصدر التي عثرت عليها. سنستخدم Python و FastAPI للخادم، وأدوات C2PA مفتوحة المصدر لإشارة المصدر، وواجهة برمجة تطبيقات (API) للكشف المستضافة لإشارة المصنف. نظرًا لأن هذا مشروع واجهة برمجة تطبيقات، فسنقوم أيضًا بتصميم عقد نقطة النهاية أولاً واستخدام Apidog لمحاكاتها واختبارها، حتى يتمكن فريق الواجهة الأمامية من البدء في التكامل قبل الانتهاء من التعليمات البرمجية للخدمة الخلفية.
باختصار
ستقوم ببناء خدمة FastAPI تعرض نقطة نهاية POST /verify التي تقبل تحميل صورة، وتستخرج وتتحقق من بيان بيانات اعتماد المحتوى (C2PA Content Credentials) الخاص بها باستخدام مكتبة c2pa-python، وتستدعي مصنفًا مستضافًا للكشف عن الذكاء الاصطناعي كإشارة مستقلة ثانية، وتُعيد حكمًا واحدًا بتنسيق JSON (likely_authentic، likely_ai، أو uncertain) مع درجة ثقة وتفاصيل المصدر الخام. ستقوم أيضًا بتصميم مخطط OpenAPI لنقطة النهاية واستخدام Apidog لتوليد خادم وهمي وتشغيل اختبارات نقطة النهاية عليه.
لماذا إشارتان بدلاً من واحدة
قبل كتابة أي تعليمات برمجية، من المفيد أن تكون واضحًا بشأن ما تكتشفه. لا توجد خاصية واحدة للملف تخبرك "صنعها إنسان" أو "صنعها ذكاء اصطناعي". بدلاً من ذلك، هناك دلائل، وكل دليل يلتقط نوعًا مختلفًا من الصور بينما يغفل أنواعًا أخرى.
الدليل الأول هو المصدر. C2PA، الائتلاف من أجل مصدر المحتوى وأصالته، هو معيار مفتوح يرفق بيانات وصفية (metadata) مشفرة وموقعة وقابلة للكشف عن التلاعب بها بملف وسائط. تُسمى حزمة البيانات الوصفية هذه "بيانًا" (manifest)، والاسم الموجه للمستخدم لها هو "بيانات اعتماد المحتوى" (Content Credentials). عندما تقوم أداة مشاركة، كاميرا، محرر، أو مولّد صور، بإنشاء أو تغيير صورة، يمكنها كتابة بيان يسجل ما حدث ويوقعه بشهادة. إذا تمكنت من قراءة هذا البيان والتحقق منه، فستحصل على بيان قوي وقابل للتحقق حول تاريخ الصورة.
المشكلة: C2PA هو خيار للمشاركة (opt-in)، والبيان هش. لقطة الشاشة تزيله. إعادة الترميز عبر تطبيق مراسلة تزيله. العديد من المنصات تزيل البيانات الوصفية عند التحميل. لذا فإن البيان المفقود لا يخبرك بشيء تقريبًا؛ لا يعني أن الصورة مزيفة، ولا يعني أنها حقيقية.
الدليل الثاني هو مصنف إحصائي. يتم تدريب نموذج الكشف على ملايين الصور الحقيقية والمولدة ويتعلم العلامات البصرية التي تميل المولدات إلى تركها. يعمل على أي صورة، ببيانات وصفية أو بدونها، ولكنه احتمالي. يعيد احتمالًا، لا حقيقة، ويمكن أن يكون خاطئًا، خاصة على الصور خارج توزيع تدريبه أو الصور التي تم ضغطها بشدة.
لا تكفي أي إشارة بمفردها. المصدر دقيق ولكنه نادر الوجود. المصنف متاح دائمًا ولكنه ليس مؤكدًا أبدًا. اجمعهما وستحصل على حكم يقول، في جوهره، "هذا ما يثبته التشفير، وهذا ما يقدره النموذج، وهذا هو مدى ثقة التركيبة بنا". هذا هو الهدف التصميمي. إذا كنت تريد نظرة أعمق حول سبب قصور الأساليب ذات الإشارة الواحدة، فإن مقالنا حول لماذا تفشل أنظمة الكشف عن صور الذكاء الاصطناعي يغطي أنماط الفشل بالتفصيل.
نظرة عامة على البنية
الخدمة صغيرة عن قصد. نقطة نهاية واحدة، استدعاءان فرعيان، استجابة مجمعة واحدة.
┌─────────────────────────────┐
image ──▶ │ FastAPI POST /verify │
│ │
│ 1. validate upload │
│ 2. ┌──────────────────┐ │
│ │ C2PA manifest │ │ provenance signal
│ │ (c2pa-python) │ │
│ └──────────────────┘ │
│ 3. ┌──────────────────┐ │
│ │ classifier API │ │ statistical signal
│ │ (hosted detector) │ │
│ └──────────────────┘ │
│ 4. combine into verdict │
└─────────────────────────────┘
│
▼
JSON verdict + confidence
تتحقق الخطوة 1 من أن الملف المحمّل هو صورة حقيقية من نوع مدعوم وضمن حد معين للحجم. تقرأ الخطوة 2 بيان C2PA محليًا؛ لا يوجد استدعاء شبكي، فقط تحليل البيانات والتحقق من الشهادة. ترسل الخطوة 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)، تحت مظلة منظمة contentauth على GitHub، أدوات C2PA مفتوحة المصدر. هناك جزءان ستسمع عنهما:
c2patool، أداة سطر أوامر لعرض وإضافة البيانات. إنها مفيدة للفحص السريع من الطرفية. لاحظ أن مستودعها المستقل أصبح مؤرشفًا الآن، وواجهة سطر الأوامر (CLI) موجودة داخل مشروع Rustc2pa-rs.c2pa-python، الرابط (binding) بلغة بايثون لنفس مكتبة Rust الأساسية (c2pa-rs). هذا ما ستستخدمه خدمتك. يتم نشرها على PyPI باسمc2pa-pythonويتم تثبيتها باستخدامpip install c2pa-python.
يركز مسار القراءة في المكتبة على كائن Reader. توجهه إلى صورة، ثم تطلب تخزين البيان بتنسيق JSON. إليك جوهر وحدة المصدر.
# provenance.py
import json
import c2pa
def read_provenance(image_path: str) -> dict:
"""
Read and validate the C2PA manifest from an image.
Returns a normalized dict describing what was found.
"""
try:
with c2pa.Reader(image_path) as reader:
manifest_store = json.loads(reader.json())
except c2pa.C2paError as err:
# ManifestNotFound is the expected case for most images.
if str(err).startswith("ManifestNotFound"):
return {
"has_manifest": False,
"validation": "none",
"detail": "No C2PA manifest present in this image.",
}
# Any other C2paError means the file had C2PA data we could not parse.
return {
"has_manifest": True,
"validation": "error",
"detail": f"Could not parse manifest: {err}",
}
active_label = manifest_store.get("active_manifest")
manifests = manifest_store.get("manifests", {})
active = manifests.get(active_label, {})
# validation_status appears only when there are validation problems.
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 read successfully.",
}
بعض الملاحظات حول ما يفعله الكود. يتم استخدام Reader كمدير سياق (context manager) بحيث يتم تحرير الموارد الأساسية. تعيد reader.json() مخزن البيان الكامل كسلسلة JSON؛ توفر المكتبة أيضًا reader.detailed_json() إذا كنت تريد التقرير المفصل مع كل تأكيد ومكون. النتيجة المتوقعة لمعظم التحميلات هي C2paError التي تبدأ رسالتها بـ ManifestNotFound، لأن معظم الصور ببساطة لا تحتوي على بيانات اعتماد المحتوى. تعامل مع ذلك كبيانات، وليس فشلًا.
عندما يكون البيان موجودًا، هناك حقلان يهمان أكثر للحكم. تخبرك سلسلة claim_generator الأداة التي كتبت البيان، على سبيل المثال سلسلة البرامج الثابتة للكاميرا أو اسم أداة صور الذكاء الاصطناعي. تكون مصفوفة validation_status فارغة عندما تكون التوقيعات والتجزئة صحيحة، وتُملأ برموز الخطأ عندما لا تكون كذلك. البيان غير الصالح هو علامة حمراء تستحق الإبلاغ عنها بصوت عالٍ؛ فهذا يعني أن الملف يدعي تاريخًا لا يدعمه التشفير.
ما لا تستطيع هذه الإشارة فعله: لا يمكنها أن تمنحك حكمًا عندما لا يكون هناك بيان، وهو ما يحدث في معظم الأوقات. وهذا هو بالضبط سبب حاجتك إلى الإشارة الثانية.
إشارة المصنف
المصنف هو واجهة برمجة تطبيقات (API) مستضافة تقيّم احتمالية أن تكون الصورة مولّدة بواسطة الذكاء الاصطناعي. يقدم العديد من البائعين هذه الخدمة. يستخدم هذا البرنامج التعليمي Sightengine لأن نموذج الكشف عن الذكاء الاصطناعي الخاص به لديه واجهة برمجة تطبيقات HTTP موثقة وشكل استجابة واضح، ولكن النمط هو نفسه لأي مزود؛ يمكنك تبديل عنوان URL، والمعاملات، والحقل الذي تقرأه. إذا كنت توازن بين الخيارات، فإن مجموعتنا لأفضل واجهات برمجة تطبيقات الكشف عن صور الذكاء الاصطناعي تقارن الدقة والتسعير والتغطية عبر البائعين.
نقطة نهاية التحقق في Sightengine هي https://api.sightengine.com/1.0/check.json. تقوم بإرسال الصورة كـ media، وتعيين models إلى genai، وتمرير api_user و api_secret الخاصين بك. تتضمن الاستجابة type.ai_generated، وهي درجة تتراوح من 0 إلى 1 حيث تعني الدرجة الأعلى احتمالية أكبر لتكون الصورة مولّدة بالذكاء الاصطناعي.
# 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:
"""
Send the image to the hosted detector.
Returns a normalized dict with the AI-generated score.
"""
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) حتى لا يعيق المصنف البطيء حلقة الأحداث (event loop). المهلة واضحة وقصيرة؛ ثماني ثوانٍ هي قيمة افتراضية معقولة لنقطة نهاية تفاعلية، ويجب عليك ضبطها لتناسب زمن استجابة مزودك الفعلي. يعيد كل مسار فشل available: False مع سبب يمكن قراءته آليًا بدلاً من إثارة استثناء. وهذا متعمد: يجب أن يؤدي انقطاع المصنف إلى تدهور الحكم، لا إلى تعطل الطلب. تقرأ منطق الحكم في القسم التالي available وتقرر ما يجب فعله.تعامل مع النتيجة كتقدير. 0.92 تعني "النموذج متأكد إلى حد ما"، وليس "هذا ذكاء اصطناعي مثبت". يقوم البائعون بتحديث نماذجهم، وتختلف الدقة حسب المولد ومدى ضغط الصورة قبل وصولها إليك. للحصول على نظرة أوسع حول كيفية عمل هذه الأدوات في الممارسة العملية، راجع دليلنا حول كيفية التحقق مما إذا كانت الصورة مولّدة بالذكاء الاصطناعي.تصميم عقد /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 (غير مؤكد). ثلاث قيم، لا اثنتان، لأن الصدق مهم؛ عندما تختلف الإشارات أو تكون كلاهما ضعيفة، فإن "غير مؤكد" هو الإجابة الصحيحة. confidence هي قيمة عائمة تتراوح من 0 إلى 1 تصف مدى قوة دعم الإشارات لهذا الحكم. تحمل signals كلا المدخلين الخام حتى يتمكن المتصل من عرض واجهة المستخدم الخاصة به أو تطبيق سياسته الخاصة. 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 أو استيراد ملف OpenAPI موجود. إن تصميم واجهة برمجة التطبيقات (API) قبل التنفيذ هو سير عمل يستحق التبني بشكل عام؛ يوضح دليل وضع "spec-first" الخاص بنا كيفية القيام بذلك من البداية إلى النهاية في Apidog.شرح التعليمات البرمجيةالآن تتجمع الأجزاء معًا. أدناه هو تطبيق FastAPI: التحقق من صحة المدخلات، وكلا استدعاءات الإشارة، ووظيفة الدمج، والمسار.دمج الإشارتيندالة الحكم هي قلب الخدمة. إنها تُشفّر سياستك. المصدر، عندما يكون صالحًا وموجودًا، هو الإشارة الأقوى لأنه تشفيري؛ أما المصنف فهو حاسم التعادل وملاذ احتياطي. إليك نسخة واضحة ومحافظة.# verdict.py
def combine_signals(provenance: dict, classifier: dict) -> dict:
"""Merge the provenance and classifier signals into one verdict."""
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: known AI tools tend to identify themselves in the 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)
# Case 1: a valid manifest naming an AI generator. Strong AI signal.
if has_manifest and validation == "valid" and generator_looks_ai:
return _verdict("likely_ai", 0.95,
"A valid C2PA manifest names an AI image tool.")
# Case 2: a valid manifest from a camera or non-AI editor. Strong authentic signal.
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 looks authentic but the classifier "
"disagrees; signals conflict.")
return _verdict("likely_authentic", 0.9,
"A valid C2PA manifest from a non-AI tool is present.")
# Case 3: a manifest that fails validation. Treat as suspicious.
if has_manifest and validation in ("invalid", "error"):
return _verdict("uncertain", 0.6,
"The image carries a C2PA manifest that failed "
"validation; its claimed history is unverified.")
# Case 4: no manifest. Fall back entirely to the classifier.
if classifier_ok and ai_score is not None:
if ai_score >= 0.7:
return _verdict("likely_ai", round(ai_score, 2),
"No provenance data; the classifier scored the "
"image as likely AI-generated.")
if ai_score <= 0.3:
return _verdict("likely_authentic", round(1 - ai_score, 2),
"No provenance data; the classifier scored the "
"image as likely authentic.")
return _verdict("uncertain", 0.5,
"No provenance data and the classifier score is "
"inconclusive.")
# Case 5: no manifest and no classifier. We genuinely cannot say.
return _verdict("uncertain", 0.0,
"No provenance data and the classifier was unavailable.")
def _verdict(verdict: str, confidence: float, explanation: str) -> dict:
return {"verdict": verdict, "confidence": confidence,
"explanation": explanation}
```اقرأ الحالات الخمس وسترى السياسة. البيان الصالح يسود. البيان الفاشل هو تحذير، وليس دليلاً على التزييف، لذا ينتهي به الأمر إلى "غير مؤكد". النزاع بين بيان نظيف ودرجة مصنف عالية يؤدي أيضًا إلى "غير مؤكد" بدلاً من اختيار جانب. وعندما تكون كلتا الإشارتين مفقودتين، تُعلن الخدمة ذلك بصدق بثقة صفرية بدلاً من التخمين. ستقوم بضبط هذه العتبات لتناسب مدى تحملك للمخاطر؛ فمنصة محتوى وغرفة أخبار ستضع الخطوط بشكل مختلف.تطبيق 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. Validate the upload.
if image.content_type not in ALLOWED_TYPES:
raise HTTPException(
status_code=415,
detail=f"Unsupported type {image.content_type}. "
f"Send JPEG, PNG, or WebP.",
)
image_bytes = await image.read()
if len(image_bytes) == 0:
raise HTTPException(status_code=400, detail="Empty file.")
if len(image_bytes) > MAX_BYTES:
raise HTTPException(status_code=413, detail="File exceeds 12 MB limit.")
# 2. Provenance signal. The C2PA reader needs a file path,
# so write to a temp file and clean up afterward.
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. Classifier signal. Failures return available: False, not exceptions.
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. Combine and respond.
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؛ بينما يعمل المصنف مباشرة من البايتات. لاحظ أن الطلب لا يتعطل أبدًا بسبب بيان مفقود أو مصنف غير موجود؛ كلاهما حالتان طبيعيتان تتعامل معهما دالة الحكم.تتناسب الخدمة الخلفية المصممة بهذه الطريقة، كخدمة مركزة ذات عقد واضح، مع الاتجاه الأوسع للمنتجات التي تعرض قدرتها الأساسية عبر واجهة برمجة التطبيقات (API). إذا كانت هذه الفكرة تثير اهتمامك، فإن مقالنا حول تطبيقات البرامج عديمة الرأس (headless software) يستحق القراءة.الاختبار والمحاكاة باستخدام Apidogهنا تكمن مشكلة سير العمل: يريد فريق الواجهة الأمامية (frontend) بناء واجهة المستخدم الخاصة بالتحميل ولوحة النتائج الآن، لكن الخدمة الخلفية (backend) المذكورة أعلاه تستغرق بضعة أيام للانتهاء منها والحصول على مفاتيحها ونشرها. لا تريد أن يتم إعاقتهم. هذا هو الغرض من الخوادم الوهمية (mock servers)، وهنا يؤتي تصميم المخطط أولاً ثماره.إنشاء خادم وهمي من المخططاستورد مخطط OpenAPI إلى Apidog، أو أنشئ نقطة النهاية /verify في المصمم المرئي. يقرأ Apidog مخطط الاستجابة وينشئ خادمًا وهميًا تلقائيًا. نظرًا لأن المخطط يحدد أنواع الحقول والتعدادات، فإن الخادم الوهمي يُعيد بيانات مطابقة تمامًا لنقطة النهاية الحقيقية: verdict يكون إحدى القيم الثلاث للتعداد، confidence وهو رقم عشري بين 0 و 1، وكائن signals ممتلئ. توجه الواجهة الأمامية استدعاء fetch الخاص بها إلى عنوان URL الوهمي لـ Apidog وتحصل على استجابات واقعية في اليوم الأول. عندما يتم إطلاق الواجهة الخلفية الحقيقية، يقومون بتغيير عنوان URL أساسي واحد.الخادم الوهمي هو أيضًا المكان الذي تمارس فيه الحالات الصعبة قبل وجود أي تعليمات برمجية حقيقية. حدد أمثلة للاستجابات للأحكام المهمة:استجابة likely_authentic مع بيان كاميرا صالح،استجابة likely_ai مع أداة ذكاء اصطناعي مُسماة في البيان،استجابة uncertain عندما يكون المصنف غير متاح،استجابات الخطأ 415 و413.يمكن للواجهة الأمامية بناء وتصميم كل حالة، بما في ذلك حالات الخطأ، بالاعتماد على الخادم الوهمي. هذه هي الطريقة التي تقوم بها بإطلاق واجهة مستخدم (UI) وواجهة برمجة تطبيقات (API) بشكل متوازٍ بدلاً من التسلسل.تشغيل اختبارات نقطة النهاية في Apidogبمجرد تشغيل الخدمة الخلفية، أنشئ طلبًا في Apidog لنقطة النهاية POST /verify. قم بتعيين الطريقة (method)، ووجهها إلى عنوان URL المحلي الخاص بك، وفي علامة التبويب Body اختر form-data، وأضف حقل image، واضبط نوعه على File، واختر صورة اختبار من القرص.أرسله، وستعرض Apidog استجابة JSON. الآن أضف تأكيدات ليصبح هذا فحصًا قابلاً للتكرار بدلاً من نقرة واحدة لمرة واحدة:تأكد أن حالة الاستجابة هي 200،تأكد أن verdict موجود وهو أحد السلاسل النصية الثلاث المسموح بها،تأكد أن confidence هو رقم بين 0 و 1،تأكد أن signals.provenance.has_manifest هو قيمة منطقية (boolean).قم ببناء سيناريو اختبار صغير يقوم بتشغيل عدة عمليات تحميل بالتسلسل: صورة تحتوي على بيانات اعتماد المحتوى، وصورة JPEG عادية بدون بيان، وملف كبير الحجم، وملف غير صورة تم تغيير اسمه بامتداد .jpg. يتحقق كل منها من فرع مختلف من منطق حكمك والتحقق من صحة مدخلاتك. احفظ السيناريو ويمكنك إعادة تشغيل المجموعة بأكملها بعد كل تغيير، أو ربطها بنظام التكامل المستمر (CI) بحيث يؤدي تراجع في دالة الحكم إلى فشل البناء. اختبار نقطة نهاية تحميل يدويًا باستخدام curl يصبح قديمًا بسرعة؛ السيناريو المحفوظ لا يفعل ذلك.التعزيز والحالات الحديةالمسار السعيد هو الـ 80 بالمائة السهلة. خدمة التحقق تعيش أو تموت على الـ 20 بالمائة الأخرى، لأن المدخلات بطبيعتها معادية؛ يحاول شخص ما تمرير صورة على أنها شيء ليس كذلك.الملفات التالفة أو المقطوعة.يمكن أن يحتوي الملف على نوع MIME للصورة ومع ذلك يكون غير صالح. يثير قارئ C2PA خطأ C2paError على البيانات التي لا يمكنه تحليلها، وتقوم دالة read_provenance بالفعل بتحويل ذلك إلى نتيجة نظيفة بدلاً من خطأ 500. لمزيد من الأمان، يمكنك فك تشفير الصورة باستخدام مكتبة مثل Pillow قبل المعالجة ورفضها برمز 400 إذا فشل فك التشفير. وهذا أيضًا يمنع خدعة ملف النص المُعاد تسميته.بيان مفقود.تمت تغطيته، ولكن يستحق إعادة التأكيد لأنه الحالة الأكثر شيوعًا والأسهل للخطأ فيها. عدم وجود بيان ليس خطأ ولا حكمًا. تُشير المكتبة إلى ذلك بـ C2paError التي تبدأ رسالتها بـ ManifestNotFound؛ تلتقط الخدمة ذلك تحديدًا وتنتقل إلى المصنف. لا تدع أبدًا بيانًا مفقودًا ينتج خطأ 500 أو حكمًا "مزيفًا".انتهاء مهلة المصنف أو انقطاعه.المصنف هو تبعية شبكة لجهة خارجية، لذا افترض أنه سيفشل أحيانًا. تستخدم دالة classify_image مهلة صريحة من httpx وتُعيد available: False عند أي انتهاء مهلة أو خطأ HTTP. ثم تعود دالة الحكم إلى المصدر وحده، أو تُعيد uncertain بثقة صفرية إذا لم يكن هناك بيان أيضًا. لا تزال نقطة النهاية تستجيب برمز 200 وحكم صادق؛ لا يمكن لمزود بطيء أن يعطل خدمتك.بيانات مزورة.يمكن أن يكون البيان موجودًا ولكنه غير صالح، موقعًا بشهادة سيئة أو بتجزئات لا تتطابق مع البكسلات. هذه هي الحالة التي ينساها الناس. تحقق دائمًا من validation_status؛ مصفوفة فارغة تعني أن البيان قد تم التحقق منه، بينما مصفوفة ممتلئة تعني أنه لم يتم التحقق منه. تتعامل دالة الحكم مع البيان الفاشل كتحذير يؤدي إلى "غير مؤكد"، وليس كدليل أبدًا. الثقة ببيان غير صالح أسوأ من عدم وجود بيان على الإطلاق.الملفات الكبيرة وسوء الاستخدام.ضع حدًا لحجم التحميل، يستخدم المثال 12 ميغابايت، وارفض أي شيء أكبر برمز 413 قبل قراءة الجسم بأكمله في الذاكرة حيث يمكنك. ضع حدًا لمعدل الطلبات أمام نقطة النهاية؛ فالتحقق التشفيري واستدعاء واجهة برمجة تطبيقات خارجية لكل طلب ليسا مجانيين، ونقطة نهاية التحقق المفتوحة هي هدف مغرٍ.الخصوصية.أنت تستقبل صور المستخدمين. عالجها في الذاكرة أو في ملف مؤقت تحذفه فورًا، كما يفعل المثال، ولا تسجل بايتات الصور. إذا كنت ترسل الصور إلى مصنف تابع لجهة خارجية، فاذكر ذلك في سياسة الخصوصية الخاصة بك وتأكد من أن ذلك مسموح به لحالة استخدامك.ما تلتقطه وتفوته كل إشارةهذا الجدول هو النموذج الذهني الذي يجب الاحتفاظ به. وهذا هو السبب في أن الخدمة تستخدم كليهما.
السيناريو
إشارة مصدر C2PA
إشارة المصنف
صورة ذكاء اصطناعي من أداة تكتب بيانات اعتماد المحتوى
تلتقطها: يحدد البيان المولد
عادة ما تلتقطها: القطع الأثرية موجودة
صورة ذكاء اصطناعي مع بيانات وصفية مجردة (لقطة شاشة، إعادة تحميل)
تفوتها: لا يوجد بيان للقراءة
تلتقطها: تعمل على وحدات البكسل، لا حاجة لبيانات وصفية
صورة حقيقية من كاميرا توقع بيانات اعتماد المحتوى
تؤكدها: بيان صالح، مولد غير ذكاء اصطناعي
قد تعطي نتيجة إيجابية خاطئة عند الضغط الشديد أو التعديلات
صورة حقيقية بدون بيانات وصفية على الإطلاق
لا توجد إشارة: لا شيء للتحقق منه
أفضل تخمين فقط: احتمالي، يمكن أن يكون خاطئًا
صورة ببيان مزور أو معدّل
تلتقطها: validation_status يشير إلى الفشل
قد تلتقطها أو لا تلتقطها، يعتمد على وحدات البكسل
مولد جديد لم يتم تدريب المصنف عليه
يلتقطها فقط إذا كانت الأداة تكتب بيانًا
غالبًا ما تفوتها: خارج توزيع التدريب
صورة حقيقية تم تحريرها بشدة (تنقيح بالذكاء الاصطناعي على أساس حقيقي)
البيان، إذا كان موجودًا، يسجل تاريخ التحرير
غامضة: تركيبية جزئيًا، الدرجة تقع في النطاق المتوسط
اقرأ عبر أي صف وسترى نفس القصة: حيث تكون إشارة واحدة عمياء، غالبًا ما تكون الأخرى ليست كذلك. المصدر دقيق ولكنه نادر؛ المصنف عالمي ولكنه غير واضح. الحكم المجمع أكثر جدارة بالثقة من أي عمود بمفرده، والقيمة الصادقة "غير مؤكد" موجودة للصفوف التي تكون فيها كلتا الإشارتين ضعيفة.حالات الاستخدام في العالم الحقيقيهذا النمط ليس أكاديميًا. إليك بعض الأماكن التي يناسبها مباشرةً:منصات المحتوى الذي ينشئه المستخدمون. يمكن لسوق أو تطبيق اجتماعي تمرير التحميلات عبر /verify ووضع علامة أو إرسال أي شيء يُصنف على أنه من المحتمل أن يكون ذكاء اصطناعيًا، أو أي شيء يحمل بيانًا فشل التحقق منه، للمراجعة. الحكم ذو القيم الثلاث يتوافق بوضوح مع "السماح"، "وضع علامة"، و"الإرسال إلى إنسان".غرف الأخبار ومراجعة الحقائق. يحصل المحرر الذي يتحقق من صورة فيروسية على كل من مصدر التشفير، إن وجد، وتقدير نموذج مستقل في استدعاء واحد، مع جملة تفسيرية يمكنه اقتباسها في ملاحظاته.استقبال التأمين والمطالبات. عندما يقدم العميل دليلًا مصورًا، ترفع خطوة التحقق علامة على الصور التي تبدو مولّدة أو تحمل بيانًا تم التلاعب به، قبل أن يقضي المُعدِّل البشري وقتًا عليها.مسارات أصول داخلية. يمكن لفريق يحتاج إلى إبقاء صور الذكاء الاصطناعي خارج، أو مميزة بوضوح في، مكتبة صور، أن يحكم عملية الاستيعاب على /verify.النشر المدرك للمصدر. مع تبني المزيد من الكاميرات والمحررين لبيانات اعتماد المحتوى، يمكن لنظام إدارة المحتوى (CMS) قراءة البيان عند التحميل وعرض شارة تم التحقق منها، والعودة إلى المصنف عندما لا يكون هناك بيان موجود.الخيط المشترك: تريد مرورًا أوليًا سريعًا ومؤتمتًا، يكون صادقًا بشأن عدم يقينه الخاص، بحيث يذهب الاهتمام البشري حيث يكون مطلوبًا بالفعل.الخلاصةالكشف الجيد عن الصور المولّدة بالذكاء الاصطناعي لا يتعلق بإيجاد اختبار مثالي واحد. بل يتعلق بدمج إشارات مستقلة والتحلي بالصدق بشأن الثقة.تمنحك بيانات اعتماد المحتوى (C2PA Content Credentials) إشارة مصدر قوية وقابلة للتحقق تشفيريًا، ولكن البيان اختياري ويمكن إزالته بسهولة، لذا غالبًا ما يكون غائبًا.يمنحك المصنف المستضاف إشارة عالمية ولكن احتمالية تعمل على أي صورة، ببيانات وصفية أو بدونها.يؤدي دمجها في خدمة FastAPI صغيرة إلى إنتاج حكم ذي ثلاث قيم، likely_authentic (على الأرجح أصلي)، likely_ai (على الأرجح ذكاء اصطناعي)، أو uncertain (غير مؤكد)، مع درجة ثقة وإرفاق كلتا الإشارتين الخام للمراجعة.يتيح لك تصميم عقد OpenAPI أولاً محاكاة نقطة النهاية في Apidog بحيث يتم بناء الواجهة الأمامية بالتوازي، ثم تشغيل سيناريوهات الاختبار المحفوظة مقابل الخدمة الخلفية الحقيقية.لا يوجد كاشف مثالي. إشارتان تزيدان الثقة؛ لا تقضيان على عدم اليقين، ولهذا السبب فإن حكم uncertain هو ميزة، وليس نقصًا.لبناء هذا في الواقع، صمم مخطط /verify، أنشئ خادمًا وهميًا، وقم بتشغيل اختبارات نقطة النهاية الخاصة بك في مكان واحد. حمل Apidog لتصميم واجهة برمجة التطبيقات ومحاكاتها واختبارها أثناء بنائها، ثم انتقل من الخادم الوهمي إلى الخدمة الخلفية الحية بتغيير واحد فقط لعنوان URL الأساسي.
زر
