شرح Promptfoo: اختبار برومبتس الـ LLM في الـ

٢٢ مايو ٢٠٢٦

Promptfoo Tutorial: Test LLM Prompts in CI (2026)

Promptfoo هي أداة CLI مفتوحة المصدر تتيح لك اختبار برومبتات (prompts) نماذج اللغة الكبيرة (LLM) بنفس الطريقة التي تتحقق بها اختبارات الوحدة (unit tests) من الكود: حيث تحدد شكل المخرجات الجيدة، وتشغل تلك الفحوصات مقابل البرومبتات والنماذج الخاصة بك، وتكتشف التراجعات (regressions) قبل إطلاقها. يبني هذا البرنامج التعليمي تقييمًا (eval) فعالًا وبوابة جودة لـ GitHub Actions من الصفر.

ملخص

يوضح هذا البرنامج التعليمي العملي كيفية اختبار برومبتات LLM باستخدام فحوصات مؤتمتة وقابلة للتكرار بدلًا من معاينة المخرجات بالعين. ستقوم ببناء تقييم promptfoo لبرومبت مصنف تذاكر دعم فني، وإضافة تأكيدات (assertions) حتمية ومصنفة بواسطة النموذج، وتشغيله محليًا، وكسر البرومبت عمدًا لمشاهدة اكتشاف التراجع، وربط بوابة GitHub Actions التي تفشل عملية البناء (build) عندما تنخفض جودة البرومبت. يستخدم البرنامج إصدار promptfoo 0.121.121، ومفتاح OpenAI API، ويستغرق حوالي 20 دقيقة. كل أمر وكتلة إعداد قابلة للتشغيل عبر النسخ واللصق وتم التحقق منها مقابل وثائق promptfoo اعتبارًا من 22 مايو 2026.

ما ستتعلمه

  • إعداد مشروع promptfoo وكتابة ملف promptfooconfig.yaml من الصفر
  • كتابة تأكيدات حتمية، بما في ذلك التحقق من صحة JSON-schema باستخدام is-json
  • إضافة تأكيد llm-rubric مصنف بواسطة النموذج لجودة المخرجات التي لا يمكنك التحقق منها باستخدام regex
  • تشغيل تقييم ومقارنة نسختين من البرومبت جنبًا إلى جنب
  • اكتشاف تراجع حقيقي في البرومبت قبل وصوله إلى الإنتاج
  • وضع بوابة لجودة البرومبت في خط أنابيب CI الخاص بـ GitHub Actions

المتطلبات الأساسية

  • Node.js 20.20.0+ أو 22.22.0+ — يتطلب حقل engines في promptfoo إصدار ^20.20.0 || >=22.22.01. تحقق باستخدام node --version.
  • مفتاح OpenAI API — يستخدم هذا البرنامج التعليمي نماذج OpenAI. يدعم Promptfoo أكثر من 60 مزودًا، لذا يمكنك التبديل إلى Anthropic أو Google أو نموذج Ollama محلي لاحقًا.2
  • طرفية (terminal) ومستودع GitHub لخطوة CI.

لا تحتاج إلى تثبيت promptfoo عالميًا — يستخدم كل أمر npx promptfoo@latest، والذي يشغل الإصدار المنشور الحالي بدون تثبيت عالمي.2

ملاحظة سريعة حول المشروع: تأسست promptfoo في عام 2024، وأعلنت OpenAI عن استحواذها على Promptfoo في مارس 2026.3 تظل الأداة مفتوحة المصدر ومرخصة بموجب MIT.

الخطوة 1 — إعداد المشروع

أنشئ دليل مشروع مع مجلد prompts/ وقم بتصدير مفتاح API الخاص بك:

mkdir prompt-eval && cd prompt-eval
mkdir prompts
export OPENAI_API_KEY="sk-your-key-here"
node --version   # must be v20.20.0+ or v22.22.0+

يقرأ Promptfoo مفتاح OPENAI_API_KEY من البيئة، لذا لا يوجد شيء آخر للمصادقة.2 يمكنك أيضًا تشغيل npx promptfoo@latest init للحصول على جولة تفاعلية للإعداد، ولكن كتابة الإعداد يدويًا تعلمك الهيكل بشكل أسرع — وهو ما تفعله الخطوة التالية.

الخطوة 2 — كتابة أول ملف promptfooconfig.yaml

تعيش تقييمات Promptfoo في ملف YAML واحد. أنشئ prompts/classifier_v1.txt بالبرومبت الذي تريد اختباره — مصنف تذاكر دعم يجب أن يعيد JSON قابل للقراءة آليًا:

You are a support-ticket classifier. Read the customer message and respond with
ONLY a JSON object, with no prose and no markdown fences, in exactly this shape:
{"category": "<billing|technical|account|other>", "priority": "<low|normal|high|urgent>"}

Customer message:
{{ticket}}

العنصر النائب {{ticket}} هو متغير promptfoo — تحدد الأقواس المتعرجة المزدوجة القيم التي يتم استبدالها لكل حالة اختبار.2 الآن أنشئ promptfooconfig.yaml في جذر المشروع:

# yaml-language-server: $schema=https://promptfoo.dev/config-schema.json
description: Support-ticket classifier eval

prompts:
  - file://prompts/classifier_v1.txt

providers:
  - openai:chat:gpt-5.4-mini

tests:
  - vars:
      ticket: 'I was charged twice for my subscription this month.'
  - vars:
      ticket: 'The mobile app crashes every time I open the reports tab.'
  - vars:
      ticket: 'How do I change the email address on my account?'

تقوم ثلاثة مفاتيح بالعمل هنا. يشير prompts إلى ملف البرومبت، ويسمي providers النموذج (سلسلة openai:chat:gpt-5.4-mini هي تعريف المزود في promptfoo لنموذج GPT-5.4 السريع ومنخفض التكلفة من OpenAI2)، ويسرد tests المدخلات. قم بتشغيله:

npx promptfoo@latest eval

يستدعي Promptfoo النموذج مرة واحدة لكل حالة اختبار ويطبع جدول نتائج — صف واحد لكل اختبار، وعمود واحد لكل برومبت. لا توجد تأكيدات بعد، لذا يظهر كل صف المخرجات الخام فقط لتعاينها. هذه هي مرحلة "تبدو جيدة بالنسبة لي". الخطوات التالية تستبدلها بفحوصات تعمل من تلقاء نفسها.

الخطوة 3 — إضافة تأكيدات حتمية

التأكيدات الحتمية هي فحوصات برمجية — لا يشارك فيها LLM — تنجح أو تفشل بنفس الطريقة في كل تشغيل.4 بالنسبة للمصنف، العقد صارم: يجب أن تكون المخرجات JSON صالحًا مع الحقول الصحيحة والقيم المسموح بها. استبدل ملف promptfooconfig.yaml بهذا الإصدار:

# yaml-language-server: $schema=https://promptfoo.dev/config-schema.json
description: Support-ticket classifier eval

prompts:
  - file://prompts/classifier_v1.txt

providers:
  - openai:chat:gpt-5.4-mini

defaultTest:
  assert:
    - type: is-json
      value:
        type: object
        required: [category, priority]
        additionalProperties: false
        properties:
          category:
            type: string
            enum: [billing, technical, account, other]
          priority:
            type: string
            enum: [low, normal, high, urgent]
    - type: cost
      threshold: 0.01

tests:
  - vars:
      ticket: 'I was charged twice for my subscription this month.'
    assert:
      - type: javascript
        value: '(() => { try { return JSON.parse(output).category === "billing"; } catch { return false; } })()'
  - vars:
      ticket: 'The mobile app crashes every time I open the reports tab.'
    assert:
      - type: javascript
        value: '(() => { try { return JSON.parse(output).category === "technical"; } catch { return false; } })()'
  - vars:
      ticket: 'How do I change the email address on my account?'
    assert:
      - type: javascript
        value: '(() => { try { return JSON.parse(output).category === "account"; } catch { return false; } })()'

يطبق defaultTest تأكيداته على كل حالة اختبار.4 يقوم تأكيد is-json بمهمة مزدوجة: فهو يؤكد أن المخرجات يتم تحليلها كـ JSON و يتحقق من صحتها مقابل JSON Schema في value — لذا فإن أي category غير موجود في القائمة أو حقل priority مفقود سيفشل تلقائيًا.4 تأكيد cost يفشل الاختبار إذا كلف استنتاج واحد أكثر من سنت واحد بالدولار الأمريكي، وهو حاجز حماية رخيص ضد تبديل النموذج العرضي الذي قد يستهلك ميزانيتك.4

ثم يضيف كل اختبار تأكيد javascript يتحقق من الفئة المحددة التي يجب أن تنتجها تلك التذكرة. يتم تغليف الفحص في try/catch بحيث تفشل المخرجات المشوهة وغير القابلة للتحليل بشكل نظيف بدلًا من إلقاء خطأ — وهي عادة تستحق الحفاظ عليها في أي تأكيد يستدعي JSON.parse. قم بتشغيل npx promptfoo@latest eval مرة أخرى — الآن يبلغ كل صف عن نجاح أو فشل حقيقي بدلًا من جدار من النص.

الخطوة 4 — إضافة تأكيد مصنف بواسطة النموذج (llm-rubric)

بعض جوانب جودة المخرجات لا يمكن التحقق منها باستخدام regex. "هل الأولوية المحددة منطقية؟" هو قرار تقديري. هذا هو الغرض من التأكيدات المصنفة بواسطة النموذج: فهي ترسل المخرجات إلى قاضٍ LLM وتقيمها مقابل معيار (rubric) تكتبه بلغة إنجليزية بسيطة.4 أضف تأكيدًا واحدًا إضافيًا إلى قائمة defaultTest.assert:

النوع llm-rubric هو فحص "LLM-as-a-judge" الخاص بـ promptfoo.5 تحتاج التأكيدات المصنفة بواسطة النموذج (Model-graded assertions) إلى مزود LLM للقيام بعملية التقييم. يختار promptfoo المقيم من أي مفتاح مزود يجده في بيئتك — مع تعيين OPENAI_API_KEY فإنه يقوم بالتقييم باستخدام نموذج OpenAI، لذا فإن مفتاحك الحالي يغطي ذلك بالفعل — ويمكنك توجيه المقيم إلى مكان آخر باستخدام حقل provider على مستوى التأكيد.5 استخدم التأكيدات الحتمية (deterministic assertions) أينما استطعت — فهي مجانية وفورية ولا تخطئ أبدًا — واحتفظ بـ llm-rubric للفحوصات الذاتية البحتة.

الخطوة 5 — تشغيل التقييم وقراءة النتائج

قم بتشغيل التقييم الكامل:

npx promptfoo@latest eval

يقوم promptfoo بطباعة ملخص مع أعداد النجاح/الفشل وجدول لكل اختبار. لاستكشاف النتائج بشكل تفاعلي — تصفية الإخفاقات، مقارنة المخرجات، قراءة منطق المقيم — افتح عارض الويب:

npx promptfoo@latest view

يؤدي هذا إلى فتح عارض نتائج محلي في متصفحك. يقوم promptfoo بتخزين استجابات LLM الناجحة مؤقتًا على القرص (الافتراضي هو ~/.promptfoo/cache، مع عمر تخزين 14 يومًا)، لذا فإن إعادة تشغيل نفس التقييم تكون سريعة ومجانية؛ لا يتم تخزين استجابات الأخطاء أبدًا.6 إذا كنت تريد مخرجات قابلة للقراءة آليًا للبرمجة النصية، فاكتب ملف JSON:

npx promptfoo@latest eval -o results.json

يحتوي ملف JSON على كائن results.stats مع أعداد successes و failures و errors — وهي الأرقام التي ستقرأها بوابة CI الخاصة بك في الخطوة 7.7

الخطوة 6 — اكتشاف تراجع في البرومبت

إليك السيناريو الذي بني عليه هذا البرنامج التعليمي بالكامل. يقرر زميل في الفريق أن برومبت المصنف يبدو "آليًا" ويعيد كتابته ليكون أكثر ودية. أنشئ prompts/classifier_v2.txt:

You are a friendly support assistant. Read the customer message below, briefly
explain in a sentence which team should handle it, and then provide a JSON
object with the category and priority.

Customer message:
{{ticket}}

يبدو أفضل للإنسان — لكنه يكسر العقد بصمت، لأن المخرجات أصبحت الآن جملة نصية متبوعة بـ JSON بدلاً من JSON نقي. أضفه بجانب الأصلي حتى يقوم promptfoo بتقييم كليهما:

prompts:
  - file://prompts/classifier_v1.txt
  - file://prompts/classifier_v2.txt

قم بتشغيل npx promptfoo@latest eval مرة أخرى. يعرض promptfoo البرومبتين جنبًا إلى جنب، عمود لكل منهما. يظل عمود v1 باللون الأخضر. يفشل عمود v2 في اختبار is-json في كل صف — المخرجات الكاملة لم تعد JSON صالحًا — وتفشل فحوصات JavaScript أيضًا، لأن المخرجات المغلفة بالنص لن يتم تحليلها. هذا تراجع تم اكتشافه في ثوانٍ بدلاً من حدوثه في بيئة الإنتاج عندما يبدأ محلل لاحق في إلقاء أخطاء.4

هذه هي القيمة الجوهرية لـ promptfoo: أي تغيير في البرومبت لا يمكنك الحكم عليه بالنظر يحصل على حكم موضوعي وقابل للتكرار. إذا كنت تحتاج فقط إلى العثور على JSON في مكان ما داخل مخرجات أكثر ضجيجًا، فإن تأكيد contains-json هو البديل الأكثر مرونة لـ is-json.4

الخطوة 7 — ضبط بوابة CI باستخدام GitHub Actions

الخطوة الأخيرة تجعل التقييم إلزاميًا: فحص CI يفشل البناء عندما تنخفض جودة البرومبت. احفظ هذا كـ .github/workflows/prompt-eval.yml:

name: LLM Prompt Eval

on:
  pull_request:
    paths:
      - 'prompts/**'
      - 'promptfooconfig.yaml'

jobs:
  eval:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6

      - uses: actions/setup-node@v6
        with:
          node-version: '22'

      - name: Cache promptfoo responses
        uses: actions/cache@v5
        with:
          path: ~/.cache/promptfoo
          key: promptfoo-${{ hashFiles('prompts/**', 'promptfooconfig.yaml') }}
          restore-keys: promptfoo-

      - name: Run prompt eval
        env:
          OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
          PROMPTFOO_CACHE_PATH: ~/.cache/promptfoo
        run: npx promptfoo@latest eval -c promptfooconfig.yaml -o results.json

      - name: Quality gate
        run: |
          FAILURES=$(jq '.results.stats.failures' results.json)
          if [ "$FAILURES" -gt 0 ]; then
            echo "Prompt eval failed: $FAILURES test(s) did not pass."
            exit 1
          fi
          echo "All prompt tests passed."

      - name: Upload report
        if: always()
        uses: actions/upload-artifact@v7
        with:
          name: promptfoo-results
          path: results.json

يتم تشغيل سير العمل فقط عند تغيير ملف برومبت أو التكوين. يقوم بتشغيل التقييم، وكتابة results.json، وتقرأ خطوة "Quality gate" قيمة results.stats.failures باستخدام jq: أي فشل يؤدي إلى الخروج بقيمة غير صفرية ويتحول الفحص إلى اللون الأحمر، مما يمنع الدمج.7 تعيد خطوة التخزين المؤقت استخدام ذاكرة تخزين استجابات promptfoo عبر عمليات التشغيل للحفاظ على سرعة ورخص سير العمل. أضف مفتاحك ضمن Settings → Secrets and variables → Actions في المستودع كـ OPENAI_API_KEY.8

يوجد أيضًا إجراء رسمي promptfoo/promptfoo-action@v1. يقوم بإجراء مقارنة قبل/بعد للبرومبتات المحررة وينشر النتائج كتعليق على طلب السحب (pull-request)، وهو أمر ممتاز للمراجعة.8 إنه مكمل وليس بديلاً: يوضح التعليق للمراجعين ما تغير، بينما بوابة jq أعلاه هي ما يفشل البناء فعليًا. قم بتشغيل كليهما للحصول على الصورة الكاملة.

التحقق

تأكد من أن خط الأنابيب بالكامل يعمل محليًا قبل الاعتماد على CI:

npx promptfoo@latest eval -c promptfooconfig.yaml -o results.json
jq '.results.stats' results.json

يجب أن ترى كائن results.stats حيث تكون failures أكبر من صفر: يقوم promptfoo بتشغيل جميع الاختبارات الثلاثة ضد كلا البرومبتين، ويفشل برومبت v2 المعطل في is-json في كل مرة. عدد failures غير الصفري هذا هو بالضبط ما تعتمد عليه بوابة CI. الآن تحقق من منطق البوابة نفسه:

FAILURES=$(jq '.results.stats.failures' results.json); echo "exit would be: $([ "$FAILURES" -gt 0 ] && echo 1 || echo 0)"

مع وجود برومبت v2 المعطل، سيطبع هذا exit would be: 1 — دليل على أن خطوة CI ستمنع الدمج. احذف classifier_v2.txt، وأزله من قائمة prompts، وأعد التشغيل، وسيطبع نفس الأمر 0.

الأخطاء الشائعة

  • No OpenAI API key / خطأ في مصادقة المزود. يقرأ promptfoo مفتاح OPENAI_API_KEY من البيئة. قم بتصديره في نفس الصدفة (shell) التي تشغل فيها التقييم، كما هو موضح في الخطوة 1، وفي CI قم بتعيينه كسر (secret) للمستودع — لا تقم أبدًا بإرساله (commit).
  • فشل npx أو رفض promptfoo البدء. يتطلب promptfoo إصدار Node.js 20.20.0+ أو 22.22.0+.1 قم بتشغيل node --version؛ إذا كنت تستخدم إصدارًا أقدم، فقم بالترقية قبل إعادة المحاولة.
  • تريد مخرجات نموذج جديدة حتى لو لم تتغير المدخلات. يقوم promptfoo بتخزين الاستجابات الناجحة مؤقتًا على القرص لمدة 14 يومًا، مفهرسة بالبرومبت والمزود وتكوين المزود والمتغيرات — لذا فإن تحرير أي منها يؤدي إلى استدعاء جديد تلقائيًا.6 لفرض استدعاءات جديدة بمدخلات متطابقة (على سبيل المثال، لإعادة أخذ عينات من نموذج غير حتمي)، قم بتشغيل npx promptfoo@latest eval --no-cache، أو امسح ذاكرة التخزين المؤقت باستخدام npx promptfoo@latest cache clear.
  • فشل is-json في مخرجات "تبدو مثل JSON". يجب أن تكون المخرجات بالكامل JSON صالحًا حتى ينجح is-json. النموذج الذي يضيف جملة نصية أو يغلف JSON في سياج كود Markdown سيفشل في هذا الاختبار — وهذا مقصود. استخدم contains-json إذا كنت تحتاج فقط إلى JSON صالح في مكان ما في الاستجابة.4
  • أخطاء HTTP 429 (تجاوز حد المعدل) في مجموعة اختبار كبيرة. قلل التزامن باستخدام -j 1 (على سبيل المثال npx promptfoo@latest eval -j 1). يقوم promptfoo بالفعل بإعادة محاولة أخطاء 429 مع تراجع أسي، ولكن تقليل المكالمات المتوازية يقلل الضغط.6

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

يمكنك الآن اختبار برومبتات LLM تلقائيًا — مجموعة اختبار تعمل محليًا وتمنع عمليات الدمج السيئة في CI. من هنا:

  • أضف مزودًا ثانيًا واجعل promptfoo يقيم كلا العمودين في وقت واحد — وهي طريقة سريعة لإجراء اختبار A/B عند نقل النماذج. توجيه عدة مزودين عبر نقطة نهاية واحدة يتناسب جيدًا مع بوابة LLM مثل LiteLLM Proxy.
  • وجه المزود إلى نموذج محلي للاختبار دون اتصال بالإنترنت؛ راجع دليلنا لتشغيل الذكاء الاصطناعي المحلي باستخدام Ollama.
  • بالنسبة لتطبيقات الاسترجاع، تعمل تأكيدات promptfoo المسماة context-faithfulness و answer-relevance على تقييم الالتزام بالسياق ومدى الصلة — وهي مفيدة إلى جانب نظام RAG قوي.

تعامل مع الأوامر (Prompts) كأنها كود: قم بإصدارها، واختبارها، ولا تسمح أبدًا لأي "تحسين" بالوصول إلى بيئة الإنتاج دون اختبار.

Footnotes

  1. promptfoo npm package — version 0.121.12 (latest dist-tag) and engines.node: "^20.20.0 || >=22.22.0", verified via npm view promptfoo on 2026-05-22. https://www.npmjs.com/package/promptfoo 2 3

  2. "Getting started," Promptfoo docs (last updated 2026-05-21) — config structure, provider strings (openai:chat:gpt-5.4-mini), {{variable}} syntax, npx promptfoo@latest commands, 60+ providers. https://www.promptfoo.dev/docs/getting-started/ 2 3 4 5

  3. "OpenAI to acquire Promptfoo," OpenAI, announced 2026-03-09. Promptfoo remains open source and MIT licensed. https://openai.com/index/openai-to-acquire-promptfoo/

  4. "Assertions & metrics," Promptfoo docs (last updated 2026-05-19) — defaultTest, is-json with JSON-schema validation, cost, JavaScript, contains-json, and the llm-rubric model-graded assertion. https://www.promptfoo.dev/docs/configuration/expected-outputs/ 2 3 4 5 6 7 8

  5. "LLM Rubric," Promptfoo docs (last updated 2026-05-12) — llm-rubric is the LLM-as-a-judge grader; the grading model is chosen from the available provider key (gpt-5 when an OpenAI key is set) and can be overridden with the assertion provider field. https://www.promptfoo.dev/docs/configuration/expected-outputs/model-graded/llm-rubric/ 2

  6. "Caching," Promptfoo docs (last updated 2026-05-18) — disk cache default ~/.promptfoo/cache, 14-day TTL, --no-cache, error responses not cached, 429 backoff. https://www.promptfoo.dev/docs/configuration/caching/ 2 3

  7. "CI/CD Integration for LLM Evaluation and Security," Promptfoo docs (last updated 2026-05-01) — JSON output schema (results.stats.failures) and the jq-based quality gate. https://www.promptfoo.dev/docs/integrations/ci-cd/ 2

  8. "Testing Prompts with GitHub Actions," Promptfoo docs (last updated 2026-05-18) — the official promptfoo/promptfoo-action@v1 action and its before/after PR comment. https://www.promptfoo.dev/docs/integrations/GitHub-action/ 2


نشرة أسبوعية مجانية

ابقَ على مسار النيرد

بريد واحد أسبوعياً — دورات، مقالات معمّقة، أدوات، وتجارب ذكاء اصطناعي.

بدون إزعاج. إلغاء الاشتراك في أي وقت.