Skip to main content
قراءة مستند أمر سهل. المهمة الحقيقية هي استخراج الحقول نفسها من كل مستند، بالشكل الذي يمكن لكودك أن يعتمد عليه. هناك طريقان من الملف إلى السجل. يمكنك استخراج النصّ وتسليمه إلى نموذج، أو يمكنك عرض الصفحة على نموذج قادر على الرؤية. الطريق الذي تحتاجه يتوقّف على كيفية إنشاء الملف، وملف PDF لا يُخبرك بأي نوعٍ منه هو بمجرّد النظر إليه. يبني هذا الدليل كلا الطريقين، ويترك لواجهة برمجة التطبيقات أن تختار بينهما:
على طول الطريق سنقوم بما يلي:
  1. استخراج النصّ من ملف PDF باستخدام /augment/text-parser
  2. وصف السجل المطلوب بوصفه مخطط JSON
  3. استخراجه، مع فرض المخطط لا مجرّد طلبه
  4. التعامل مع الملف الذي لا يحوي نصًّا أصلًا
  5. مقارنة ما يُنتجه الطريقان من الصفحة نفسها

الإعداد

تحتاج إلى Python 3.9 أو أحدث، وحزمة requests، ومفتاح Venice API. راجع إنشاء مفتاح API إن لم يكن لديك واحد.
سنستخدم ورقة بحثية عامّة كمستند عيّنة، لتتمكّن من المتابعة بالملف نفسه:
أنشئ ملف extract.py:
لاحظ أن AUTH وJSON_HEADERS منفصلان. المحلّل (parser) يستقبل رفعًا متعدّد الأجزاء، وتعيينك بنفسك لـ Content-Type في طلبٍ متعدّد الأجزاء يمنع requests من إضافة الحدّ الفاصل (boundary)، مما يُفشل الطلب بطريقة يصعب تشخيصها.

1. استخرج النصّ

تستقبل نقطة النهاية /augment/text-parser ملف PDF أو DOCX أو XLSX أو ملفًا نصيًّا صافيًا حجمه حتى 25 ميغابايت، وتُعيد النصّ مع عدد الرموز (tokens). تُعالَج المستندات في الذاكرة ولا يُحتفظ بمحتواها.
عدد tokens هو الجزء المفيد في هذه الاستجابة. فهو يُخبرك بما سيُكلّفك المستند في الطلب التالي قبل أن تُرسله، وهذا مهمّ لأن ملف PDF طويلًا قد يتجاوز بسهولة ما كنت تنوي إنفاقه.

2. صِف السجل الذي تريده

طلب JSON من نموذج يمنحك JSON بالشكل الذي طلبته تقريبًا. تمرير مخطط يمنحك JSON مطابقًا، لأن المخطط يُقيّد التوليد بدلًا من مجرّد إرشاده.
يستحقّ ضبط additionalProperties: False عند كل مستوى. بدونه يستطيع نموذج عثر على شيء مُلفت أن يُضيف مفتاحًا لم تخطّط له، فلا يتوقّعه الكود الذي يقرأ النتيجة.

3. الاستخراج

استدعاء واحد، مع response_format يحمل المخطط وstrict مُفعّل:
يمرّ كل استخراج في هذا الدليل عبر قارئ صغير واحد، لأن الطريقتين اللتين قد يفشل بهما هذا الاستدعاء تصلان كلتاهما برمز HTTP 200:
انظر إلى المؤلّف الأخير. لا تذكر الورقة أي جهة انتساب لـ Illia Polosukhin، والمخطط يقول إن affiliation مطلوب، فأعاد النموذج سلسلة فارغة بدلًا من حذفه. هذا هو المخطط يفعل ما طلبته منه تمامًا.
السلسلة الفارغة والقيمة المفقودة حقيقتان مختلفتان، وrequired يُوحّدهما. إن احتجت إلى التمييز بين “المستند لا يقول” و”المستند يقول لا شيء هنا”، فحدّد نوع الحقل بـ {"type": ["string", "null"]} واطلب null في موجّه النظام. يقبل الوضع الصارم الاتحاد، فتحصل على null بدلًا من "".

أطفئ التفكير

disable_thinking هو السطر في ذلك الطلب الذي يستحقّ الجدال، وإليك الحجّة. النموذج النصّي الافتراضي يُفكّر قبل أن يُجيب، والتفكير يُخصم من ميزانية الإكمال نفسها التي تُخصم منها الـ JSON. شغّل الاستخراج نفسه أربع مرات وراقب ما ينفقه النموذج: رفع الميزانية لا يُصلح المشكلة الأولى، بل يرفع السقف الذي يُسمح للنموذج ببلوغه. التشغيل الذي أنفق 4003 رموز عاد بـ finish_reason قيمته length وسلسلة فارغة. جعل إطفاء التفكير هذا الاستخراج أرخص خمس مرات، والأهم من ذلك جعله ثابتًا في كل مرة. المخطط يقوم فعلًا بالعمل الذي كان التفكير سيقوم به، وهو تحديد شكل الإجابة.
حين تنفد الميزانية، يكون النموذج قد كتب بعض الـ JSON عادة، فتحصل على كائن مبتور بدلًا من خطأ. عندئذٍ يفشل json.loads على سلسلة غير مغلقة في مكان ما في المنتصف، فيبدو الأمر وكأنه علّة تحليل وليس كذلك. يفحص read_record قيمة finish_reason أولًا لتقول الرسالة ما حدث فعلًا.

4. حين لا يوجد نصّ لاستخراجه

ملف PDF الناتج من ماسح ضوئي يحتوي على صور للصفحات، لا نصًّا. لا شيء في اسم الملف يقول ذلك، ولا شيء في حجمه يكشفه أيضًا. لا تحتاج إلى اكتشاف ذلك بنفسك، فالمحلّل يفعل:
يصل ذلك بحالة HTTP 400، وهو إشارة توجيه لا فشل. طريق النصّ غير متاح لهذا الملف، فخذ الطريق الآخر: عيّن الصفحة صورةً ودَع نموذجًا ينظر إليها.
الآن يمكن ربط الطريقين معًا، مع اختيار خطأ المحلّل نفسه بينهما:
البديل يقرأ صفحة واحدة. هذا مناسب لنموذج، أو فاتورة، أو صفحة عنوان، وخاطئ لأي شيء أطول من ذلك، لأن بقية المستند تختفي بصمت. ارسم كل صفحة وأرسلها بوصفها عدة صور حين يكون من المحتمل ألا تكون الإجابة على الصفحة الأولى.

5. فيمَ يختلف الطريقان

شغّل كليهما على الصفحة الأولى نفسها فتعود السجلات متطابقة تقريبًا. التقريب هو الجزء المثير: حافظ طريق النصّ على حرف Ł. أعاد طريق الرؤية حرف L بترميز ASCII، لأنه يقرأ أشكال الحروف لا شفرات الأحرف، والعلامات التشكيلية تفاصيل بصرية دقيقة تنجو بصعوبة. إن كنت تُطابق الأسماء المستخرَجة مع قاعدة بيانات، فذلك الفارق يُقرّر ما إذا كان الصفّ سيُعثر عليه. المؤلف الثامن أهمّ. لا تذكر الصفحة أي انتساب لـ Illia Polosukhin، ويُبلّغ طريق النصّ عن ذلك بأمانة بوصفه سلسلة فارغة في كل مرة. أما طريق الرؤية فقد ملأ الحقل في بعض التشغيلات بجار محتمل من الصفحة نفسها. تُتيح قراءة البكسلات مجالًا للاستنتاج أكبر مما تتيحه قراءة الأحرف، والحقل المطلوب دعوةٌ لملئه. حين لا يمكنك التحقّق من الإخراج يدويًا، فذلك سبب لتفضيل النصّ المُحلَّل حيثما وفّره المستند. الفارق في التكلفة أقلّ ممّا يبدو. مع إطفاء التفكير على الجانبين، شغّل الطريقان حجم موجّه متقاربًا على هذه الصفحة: بلغت الصورة 923,732 حرفًا من ترميز base64، ولا شيء من ذلك مما تدفع مقابله. تُقاس الصور بالرموز حسب الحجم لا حسب طول ترميزها، فملف PNG كبير لا يُكلّف بقدر ما يبدو. فضّل النصّ المُحلَّل حين يحوي المستند نصًّا. فهو يحفظ الأحرف الدقيقة، ولا يُكلّف شيئًا إضافيًا لتجاوز الصفحة الأولى، ولا يُبالي بكيفية تنسيق الصفحة. لجأ إلى الرؤية حين يقول المحلّل إنه لا شيء ليقرأه، أو حين يكون المعنى في التخطيط، كما في مخطط بياني أو ختم أو توقيع.

استخراج شيء آخر

لا شيء أعلاه خاصّ بالأوراق البحثية. استبدل المخطط وموجّه النظام، فتستخرج خطّة العمل الفواتير:
حقول description تقوم بعمل حقيقي. لا يصبح التاريخ بلا لبس إلا حين تُحدّد الصيغة التي تريدها، و03/04/2026 تعني يومين مختلفين تبعًا لمن كتبها.

الخطوات التالية

  • تحقّق من صحّة النتيجة مقابل المخطط باستخدام pydantic أو jsonschema، لكي يفشل السجل المشوّه عند الحدود لا بعد ثلاث دوال.
  • خزّن النصّ المستخرَج باستخدام التضمينات للبحث عبر المستندات بدلًا من إعادة استخراجها.
  • ألحق المستندات مباشرةً بإكمال محادثة عبر مدخلات الملفات حين تريد إجابات لا سجلات.
  • امنح المستخرِج لوكيل بوصفه أداةً باستخدام بناء وكيل يستخدم الأدوات عبر استدعاء الدوال.

معالجة المستندات

مرجع لنقطة نهاية text-parser.

الاستجابات المُهيكلة

كيف يقيّد json_schema إكمال المحادثة.

الرؤية

إرسال الصور إلى نموذج محادثة.

مدخلات الملفات

ألحق مستندًا دون تحليله بنفسك.