الانتقال إلى المحتوى

الأدلة الإرشادية

درس تعليمي: تطبيق خلف نقطة نهاية HTTP

قيّم خدمة تصل إليها عبر HTTP، دون استيراد شيفرتها: وجّه Oloproof إلى عنوان URL، وشغّل مجموعة الاختبار، واعثر على ما يفوتها، وانشر تغييرًا، وقارن. تحل خدمة محلية صغيرة محل خدمتك، فيعمل كل شيء دون اتصال.

ما الذي ستبنيه

خدمة لاستقبال الطلبات تقرأ رسالة عميل وتستخرج حقلين: intent (where_is_order أو cancel أو return أو other) وorder_id (أربعة أرقام، أو null). ستفحص تنسيق كل استجابة، وهو ما لا يحتاج إلى مرجع، وتقيس هل كل حقل صحيح، وهو ما يحتاج إليه. والمصطلحات مثل الحالة والتشغيل والمقياس والبوابة معرَّفة في المفاهيم.

المتطلبات المسبقة

  • Python 3.11 أو أحدث، وOloproof في بيئة افتراضية:
python3 -m venv .venv
. .venv/bin/activate
pip install oloproof
  • مشروع المثال، وهو يأتي مع الحزمة. انسخه إلى مجلد جديد واعمل هناك (httpx، الذي يستورده client.py، يُثبَّت مع Oloproof):
oloproof init --example http order-intake
cd order-intake
  • المنفذ 8765 شاغر على هذا الجهاز. وإن كان مشغولًا، فاختر غيره وغيّره في أمر الخادم وفي oloproof.yaml كليهما.

لا شيء هنا يستخدم مزوِّدًا أو مفتاح API أو الإنترنت: تستمع الخدمة على 127.0.0.1.

الملفات

order-intake/
  server.py             the stand-in service (Python standard library only)
  client.py             a callable that calls the service with a token (used near the end)
  oloproof.yaml         the suite: dataset, HTTP system, evaluators
  release.yaml          rules for a run
  compare.yaml          a rule for a comparison
  data/messages.jsonl   20 cases

شغّل الأوامر من order-intake/. شغّل الخدمة في طرفية ثانية واتركها تعمل:

python server.py --port 8765
intake service (v1) on http://127.0.0.1:8765/extract

عقد HTTP

لكل حالة يرسل Oloproof طلبًا واحدًا: input الحالة جسمًا بصيغة JSON، بالطريقة التي تعلنها (POST افتراضيًا). ويقرأ الاستجابة بوصفها JSON. والحالة 400 أو أعلى، أو انتهاء المهلة، أو رفض الاتصال، يُسجَّل خطأ تنفيذ لتلك الحالة، لا إجابة خاطئة أبدًا.

الطلب والاستجابة لحالة واحدة:

POST /extract
{"message": "Where is order 1042? It has not arrived."}

200 OK
{"result": {"intent": "where_is_order", "order_id": "1042"}, "service": {"rules": "v1"}}

يخبر output_path: result Oloproof بألّا يحتفظ إلا بـ result مُخرَجًا للحالة؛ ومن دونه يكون الجسم كله هو المُخرَج. ومسار منقوط مثل data.answer يصل إلى عمق أكبر.

version: 1
project: order-intake
dataset: data/messages.jsonl
system:
  name: order-intake
  http:
    url: http://127.0.0.1:8765/extract
    method: POST
    version: rules-v1
    output_path: result
    timeout_s: 30
evaluators:
  - type: json_schema
    criterion: format_valid
    field: null
    schema:
      type: object
      required: [intent, order_id]
      properties:
        intent: {enum: [where_is_order, cancel, return, other]}
        order_id: {type: [string, "null"], pattern: '^\d{4}$'}
      additionalProperties: false
  - type: exact_match
    criterion: intent_correct
    field: intent
  - type: exact_match
    criterion: order_id_correct
    field: order_id

يجب أن يعلن نظام HTTP version. لا يستطيع Oloproof رؤية عملية نشر: فهو يخزّن مُخرَج كل حالة مؤقتًا وفق عنوان URL والطريقة ومسار المُخرَج وتلك النسخة، لذا فالنسخة هي طريقتك لإخباره بأن الخدمة تغيّرت. وإن نسيت تغييرها فلن تُستدعى عملية النشر الجديدة أبدًا.

لا يمكن بعد تهيئة الترويسات والمصادقة على system.http. ويبيّن القسم الخاص بالرموز المميزة أدناه طريقة الالتفاف على ذلك.

مجموعة البيانات

{"id":"m04","input":{"message":"Has order #5120 shipped yet?"},"expected":{"intent":"where_is_order","order_id":"5120"}}
{"id":"m07","input":{"message":"Do you ship to Canada?"},"expected":{"intent":"other","order_id":null}}
{"id":"m16","input":{"message":"Please refund and take back the lamp from order #1560."},"expected":{"intent":"return","order_id":"1560"}}

input هو جسم الطلب بالضبط. وexpected يحمل المرجع لكل حقل؛ وnull قيمة مرجعية حقيقية، تعني "لا يوجد معرّف طلب في هذه الرسالة".

اختيار المُقيِّمات

المعيارالمُقيِّميحتاج إلى expectedيقيس
format_validjson_schema على المُخرَجلاالتنسيق: نية معروفة ومعرّف سليم الشكل
intent_correctexact_match على intentنعمنجاح المهمة للحقل الأول
order_id_correctexact_match على order_idنعمنجاح المهمة للحقل الثاني

كان المخطط سيُنجح {"intent": "other", "order_id": null} لكل رسالة: سليم الشكل وعديم الفائدة. وفحوص المرجع وحدها تقول هل أدّت الخدمة عملها. وتقييم الحقلين منفصلين يُظهر أيهما يُخفق، وهو ما كان فحص مجمَّع واحد سيخفيه.

لا يسمح release.yaml بأي إخفاق في التنسيق ويطلب أن يكون كل حقل صحيحًا في 70% من المرات على الأقل، محكومًا عليه بفترة 95%:

version: 1
confidence_level: 0.95
block_on: [FAIL, INSUFFICIENT_EVIDENCE, MANUAL_REVIEW]
rules:
  - id: valid-format
    metric: format_valid
    kind: observed_count
    max_failures: 0
  - id: intent-floor
    metric: intent_correct
    min: 0.70
  - id: order-id-floor
    metric: order_id_correct
    min: 0.70

شغّله

oloproof run
Run run_01M4... [DECIDED/COMPLETE]
Gate: BLOCK (exit 3)
│ valid-format   │ format_valid     │ PASS                  │ observed_failures_within_limit │
│ intent-floor   │ intent_correct   │ INSUFFICIENT_EVIDENCE │ interval_overlaps_threshold    │
│ order-id-floor │ order_id_correct │ INSUFFICIENT_EVIDENCE │ interval_overlaps_threshold    │
│ format_valid     │ 100.0%   │ [83.1%, 100.0%] │ 20 / 20 observed · 0 missing · 0 excluded │
│ intent_correct   │ 75.0%    │ [50.8%, 91.4%]  │ 15 / 20 observed · 0 missing · 0 excluded │
│ order_id_correct │ 75.0%    │ [50.8%, 91.4%]  │ 15 / 20 observed · 0 missing · 0 excluded │
Cache: execution 0 hit/20 miss; judgment 0 hit/60 miss

كل استجابة سليمة الشكل. وكل حقل صحيح 15 مرة من 20، لكن 20 حالة تترك فترة تنزل حتى 50.8%، فلا يثبت أيٌّ من الحدين الأدنيين 70%: INSUFFICIENT_EVIDENCE، وتحجب البوابة برمز الخروج 3.

افحص الإخفاقات

oloproof inspect RUN_ID --failures
8 of 20 cases failed, errored or did not finish

m04
  output: {"intent": "where_is_order", "order_id": null}
  order_id_correct: failed

m06
  output: {"intent": "other", "order_id": "7011"}
  intent_correct: failed
...
m18
  output: {"intent": "other", "order_id": null}
  intent_correct: failed
  order_id_correct: failed

اقرأ المدخلات بجانبها (oloproof inspect RUN_ID --case m04) فيظهر عيبان: نمط المعرّف لا يطابق إلا "order 1234"، لا "order #5120" ولا "order no. 8123" ولا "#1673" وحدها؛ وصيغ مثل "send back" و"stop order" و"where's my parcel" لا تقابل أي نية. ذلك هو الإجراء التالي: وسّع القاعدتين.

انشر تغييرًا

أوقف الخدمة وشغّل المرشَّح، الذي يحمل تلك الإصلاحات:

python server.py --port 8765 --rules v2

غيّر version: rules-v1 إلى version: rules-v2 في oloproof.yaml، لأن عنوان URL لم يتغير وإلا لأعاد Oloproof استخدام المُخرَجات القديمة. ثم:

oloproof run
Gate: ALLOW (exit 0)
│ intent_correct   │ 100.0%   │ [83.1%, 100.0%] │ 20 / 20 observed · 0 missing · 0 excluded │
│ order_id_correct │ 100.0%   │ [83.1%, 100.0%] │ 20 / 20 observed · 0 missing · 0 excluded │

ينجح الحدّان الأدنيان كلاهما ويخرج الأمر بالرمز 0.

قارن عمليتي النشر

يسأل compare.yaml هل نوايا المرشَّح أفضل من نوايا خط الأساس:

version: 1
confidence_level: 0.95
block_on: [FAIL, INSUFFICIENT_EVIDENCE, MANUAL_REVIEW]
rules:
  - id: intent-better
    kind: superiority
    metric: intent_correct
oloproof compare CANDIDATE_RUN_ID BASELINE_RUN_ID --policy compare.yaml
format_valid: +0.0 points [-23.6, +23.6] · 20 paired · 0 missing · 0 excluded
intent_correct: +25.0 points [-8.5, +58.3] · 20 paired · 0 missing · 0 excluded
order_id_correct: +25.0 points [-8.5, +58.3] · 20 paired · 0 missing · 0 excluded
Decisions
  intent-better  intent_correct  superiority  INSUFFICIENT_EVIDENCE  interval_overlaps_zero
Gate: BLOCK (exit 3)

يجتاز المرشَّح حدّيه الأدنيين، لكن المقارنة لا تستطيع أن تُظهر أنه أفضل: تغيّرت خمس حالات، وعلى 20 حالة مزدوجة ما زالت فترة المكسب تتضمن الصفر. والعبارتان صحيحتان معًا. "يلبّي المتطلب" و"يتفوق على خط الأساس" سؤالان منفصلان، ومجموعة اختبار بهذا الصغر لا تجيب عن الثاني إلا للآثار الكبيرة. والعلاج مزيد من الرسائل الحقيقية.

خدمة تحتاج إلى رمز مميز

شغّل الخدمة بحيث تطلب رمزًا مميزًا من نوع bearer:

INTAKE_TOKEN=s3cret python server.py --port 8765 --rules v2 --require-token

لا يرسل system.http ترويسات مخصّصة، لذا مع version جديدة (لنقل rules-v2-auth) يُرفض كل استدعاء:

Gate: BLOCK (exit 3)
│ valid-format   │ format_valid     │ INSUFFICIENT_EVIDENCE │ no_observations │
│ intent_correct   │          │ [0.0%, 100.0%] │ 0 / 0 observed · 20 missing · 0 excluded │

ويُظهر oloproof inspect RUN_ID --failures الرسالة execution ERROR: TransientError: system returned HTTP 401 في كل حالة. وأخطاء التنفيذ أدلة مفقودة، لا إخفاقات: لم يُرصد شيء، فتكون كل قاعدة INSUFFICIENT_EVIDENCE.

والالتفاف هو كائن Python قابل للاستدعاء يرسل الطلب بنفسه. يضيف client.py الترويسة من متغير بيئة، فلا يدخل الرمز المميز أبدًا في oloproof.yaml ولا في أي سجل مخزَّن:

URL = os.environ.get("INTAKE_URL", "http://127.0.0.1:8765/extract")


def extract(case: dict[str, Any]) -> dict[str, Any]:
    headers = {"Authorization": f"Bearer {os.environ['INTAKE_TOKEN']}"}
    response = httpx.post(URL, json=case, headers=headers, timeout=30)
    response.raise_for_status()
    return response.json()["result"]

استبدل كتلة http: في oloproof.yaml بـ:

system:
  name: order-intake
  version: rules-v2
  callable: client:extract

وشغّل مع الرمز المميز في البيئة:

INTAKE_TOKEN=s3cret oloproof run

تُرصد الحالات العشرون كلها من جديد وتسمح البوابة. ويتبع التخزين المؤقت للكائن القابل للاستدعاء مصدره وversion الخاصة به، لا الخدمة التي خلفه، فتنطبق القاعدة نفسها: غيّر version حين تنشر.

استكشاف الأخطاء وإصلاحها

العَرَضالسبب والإصلاح
system connection failed (ConnectError) في كل حالةالخدمة لا تعمل، أو تستمع على منفذ آخر.
system connection failed (RemoteProtocolError)شيء آخر يجيب على ذلك المنفذ. اختر منفذًا شاغرًا.
KeyError: "missing output path 'results'"يسمّي output_path حقلًا ليس في الاستجابة.
system returned HTTP 401 أو 403تحتاج نقطة النهاية إلى بيانات اعتماد: استخدم التفاف الكائن القابل للاستدعاء.
نشرت تغييرًا وسطر التخزين المؤقت كله إصاباتلم تتغير version، فأُعيد استخدام المُخرَجات المخزّنة.
an HTTP system needs a declared versionأضف version تحت system أو system.http.

القيود

  • لا ترويسات مخصّصة ولا مصادقة ولا معاملات استعلام ولا قوالب طلبات على system.http: input الحالة هو جسم JSON كما هو. استخدم كائنًا قابلًا للاستدعاء لأي شيء آخر.
  • يجب أن تكون الاستجابات JSON. ولا تُقرأ الاستجابات المتدفقة بوصفها تدفقًا.
  • لا يستطيع Oloproof اكتشاف عملية نشر؛ وversion المُعلَنة هي الهوية الكاملة لما أجاب.
  • تملك خدمتك حالتها: يرسل Oloproof الطلبات ويسجّل الإجابات؛ ولا يعيد ضبط أي شيء يغيّره طلب، ولا يعزله ولا يتراجع عنه.