قد تكون بنية JSON في نتيجة الوكيل صحيحة مع سوء فهم المصدر. يعطي هذا الدليل مطور Python مسارًا كاملًا من ملف ملاحظات مأذون به إلى سجل إجراءات محلي: نتيجة منتج محددة النوع، وفحص المصدر والمعنى، وموافقة بشرية على البصمات الدقيقة، ومستهلك يكتب تقرير Markdown مجمّعًا ويعيد قراءته. استخدم ملاحظاتك التي تحوي التزامات فعل صريحة. لم تُستخدم ملاحظات حقيقية أو نتيجة نموذج في إعداد الدليل.
التغيير الكبير
- ما الذي تغيّر: تقبل OpenAI Agents SDK نوع Pydantic
output_type، فيستطيع التطبيق طلب نتيجة محددة وفحصها قبل استخدامها. يضيف المثال اقتباسات المصدر وعقدًا ذا إصدار حول النتيجة. - لماذا يهم: يستطيع المطور الذي يصل الوكيل بخطوة عمل أخرى إيقاف المخرجات المشوهة أو الناقصة أو غير المدعومة قبل أن تصبح قائمة إجراءات. ويبقى على المراجع الحكم على دعم الملاحظات المقتبسة لكل مهمة ومسؤول وتاريخ، وعلى أي إغفال من النموذج.
- ما ينبغي مراقبته: نقطة القرار عند حدود المستهلك. تنتج الوصفة سجل فرز محليًا فقط بعد الموافقة على التشغيل والمصدر والمرشح الدقيقين. والاستيراد اللاحق إلى نظام مهام يحتاج إلى سلطة وسياسة إعادة محاولة وقراءة تحقق في الوجهة خاصة به.
جهّز مصدرًا واحدًا مأذونًا به
تحتاج إلى Python 3.12 وحساب OpenAI API مأذون للملاحظات، ومراجع يقرأ الأصل، ونموذج تأكدت من دعمه الحالي للمخرجات المنظمة بمخطط JSON في حسابك. افحص شروط البيانات والأسعار الحالية قبل تشغيل المنتج. لا تضع بيانات اعتماد في الملاحظات أو ملف الموافقة. استدعاء النموذج الوحيد أدناه هو produce؛ والباقي معالجة محلية.
احفظ الملاحظات الأصلية بترميز UTF-8 في notes.txt. يجب أن تتضمن التزامات فعل حقيقية وصريحة من عملك. عيّن source_id ثابتًا، مثل معرّف سجلك، وأبقِ كل بايت في الملف دون تغيير حتى المراجعة والاستهلاك. أنشئ candidate/ قبل الإنتاج؛ يجب أن يكون ملف المرشح جديدًا. وفّر دليل إخراج منفصلًا للتقرير المحلي لاحقًا.
سجّل بصمة بايتات المصدر قبل التشغيل:
python -c "from pathlib import Path; import hashlib; print(hashlib.sha256(Path('notes.txt').read_bytes()).hexdigest())"في بيئة Python 3.12 معزولة، أنشئ recipe-requirements.txt بهذه الأسطر الدقيقة وثبّت الحزم:
openai-agents==0.24.0
pydantic==2.14.1python -m pip install -r recipe-requirements.txt
mkdir candidateثُبّتت هذه الإصدارات معًا في بيئة Python 3.12.7 محلية في 20 أكتوبر 2026. أكّد التوافق وتوفر النموذج عند استخدام الدليل. احفظ النص البرمجي الكامل أدناه باسم action_register_recipe.py في الدليل نفسه. له أمران: produce ينشئ مرشحًا وإيصال تشغيل؛ consume يبني التقرير بعد التحقق والموافقة فقط.
شغّل المنتج محدد النوع
العقد Extraction يطلب الإصدار action-register/v1 والحالة ready أو partial أو blocked ومعرّف المصدر وSHA-256 لبايتات الملف الدقيقة والإجراءات و gap_note. يحتاج كل إجراء إلى نص واقتباس دقيق ونطاق أسطر مصدر يبدأ من 1 ومسؤول وتاريخ يسمحان بـ null، وحالة دليل complete أو needs_review. تعني قيمة null للمسؤول أو التاريخ أن الملاحظات لم تذكرهما. يُطلب من النموذج عدم استنتاجهما. كل الحقول إلزامية والإضافية مرفوضة.
اختر نموذج المخرجات المنظمة صراحةً. قدّم مسارك المسموح ومعرّفك:
python action_register_recipe.py produce --source-id OWNED-ID --source notes.txt --candidate candidate/actions.json --model YOUR-STRUCTURED-OUTPUT-MODELاقرأ candidate/actions.json بجانب candidate/actions.run.json. يسجل الأخير UUID التشغيل وبصمات المصدر والمرشح والوكيل الأخير والنموذج المطلوب. عند الفشل يُنشأ بدلًا من ذلك سجل تشخيص .failure.json بلاحقة معرّف التشغيل. لا يُعد الانقطاع أو None أو مخرج نهائي كسلسلة نصية بسيطة أو وكيل أخير غير متوقع نجاحًا. لا يحفظ هذا الأمر حالة SDK القابلة للاستئناف RunState؛ ولا يستطيع إيصال الفشل استئناف التشغيل. افحص الطلب والمرشح وأي حالة لاحقة قبل تقرير مناسبة تشغيل جديد.
راجع المعنى ووافق على المرشح الدقيق
يفحص التطبيق الأنواع الصارمة والحقول المطلوبة وهوية المصدر وحالة الجاهزية وإجراءً كاملًا واحدًا على الأقل ونطاقات الأسطر الصالحة ووجود الاقتباس فيها وعدم فراغ المهمة أو المسؤول والتواريخ القياسية YYYY-MM-DD. هذا يثبت البنية وموضع الاقتباس، لا أن الجملة التزام حقيقي أو أن الاسم والتاريخ يعنيان ما يقوله المرشح أو أن كل الالتزامات وُجدت.
اقرأ الملاحظات الأصلية وكل إجراء مقترح. قارن المعنى والمسؤول والتاريخ والاقتباس والإغفالات. عالج الغموض بتشغيل جديد مُراجع؛ واترك المرشحين غير المدعومين أو الجزئيين أو المحجوبين دون موافقة. بعد التأكد من تطابق الملف غير المتغير والمرشح مع actions.run.json، اكتب approval.json بهذه المفاتيح السبعة فقط. انسخ معرّف التشغيل والبصمات من الإيصال المفحوص. استخدم معرّف المراجع الحقيقي ووصفًا جوهريًا للفحوص التي أُجريت فعلًا:
{
"approved": true,
"run_id": "COPY-RUN-ID",
"source_id": "OWNED-ID",
"source_sha256": "COPY-SOURCE-SHA256",
"payload_sha256": "COPY-PAYLOAD-SHA256",
"reviewer": "YOUR-REVIEWER-ID",
"review_note": "Describe the source, task, owner, date, quotation and omission checks actually performed."
}approved يجب أن يكون قيمة JSON المنطقية true، لا نصًا أو رقمًا. القيم الأخرى كلها سلاسل نصية؛ ولا يجوز فراغ المراجع أو الملاحظة. تفشل الحقول الإضافية. تربط الموافقة مرشحًا ومراجعة مصدر واحدة، لا أي إعادة كتابة مستقبلية.
استهلك التقرير وأعد قراءته
python action_register_recipe.py consume --source-id OWNED-ID --source notes.txt --candidate candidate/actions.json --approval approval.json --out private-outputيجمع المستهلك الإجراءات المقبولة في Assigned with dates و Needs an owner و Needs a date. يحوي كل إدخال المهمة وحالة المسؤول والتاريخ والاقتباس وأسطر المصدر. يكتب action-register-<payload-hash-prefix>.md وملف .receipt.json مجاورًا ببصمات التشغيل والمصدر والمرشح والتقرير. افتح الملفين وقارن الإجراءات المقتبسة بالملاحظات وتحقق من report_sha256 مقابل بايتات Markdown. لا يعني الإيصال الصالح إلا أن الملف المحلي أُنشئ وأُعيدت قراءته؛ ولا يوافق على الكتابة إلى نظام مهام خارجي.
يطبع أمر الاستهلاك مساري التقرير والإيصال الفعليين. استخدمهما لفحص البايتات:
python -c "from pathlib import Path; import hashlib,json,sys; report=Path(sys.argv[1]); receipt=json.loads(Path(sys.argv[2]).read_text(encoding='utf-8')); assert hashlib.sha256(report.read_bytes()).hexdigest()==receipt['report_sha256']; print('report hash matches receipt')" PATH-PRINTED-FOR-REPORT PATH-PRINTED-FOR-RECEIPTبعد وجود مرشح، افحص مسار الرفض بمثال بنيوي اصطناعي واضح. انسخ JSON لملف منفصل واحذف أي حقل مطلوب من أول إجراء ثم استدعِ consume بالملف المشوه ودليل إخراج جديد فارغ. يجب أن يفشل قبل إنشاء الملف. لا تعتبر المثال استخراجًا واقعيًا ولا توافق عليه. مثلًا يحذف الفحص المحلي التالي evidence_quote من نسخة:
python -c "import json; from pathlib import Path; p=json.loads(Path('candidate/actions.json').read_text(encoding='utf-8')); p['actions'][0].pop('evidence_quote'); Path('candidate/SYNTHETIC-malformed.json').write_text(json.dumps(p), encoding='utf-8')"
python action_register_recipe.py consume --source-id OWNED-ID --source notes.txt --candidate candidate/SYNTHETIC-malformed.json --approval approval.json --out synthetic-rejection-outputتوقع فشل الأمر الثاني وبقاء دليل الإخراج غير موجود. لا تستخدم المثال إن لم يكن للمرشح الحقيقي إجراء أول؛ فهو ناقص أصلًا لهذا المستهلك. عند كتابة غير مؤكدة، افحص التقرير الحتمي والإيصال قبل الإعادة. يمكن إعادة استخدام البايتات الموجودة المتطابقة؛ وتوقف البايتات المختلفة العملية للمطابقة. تغيير المصدر أو إصدار المخطط يحتاج مراجعة جديدة. لا تعِد استدعاء النموذج لمجرد غموض رد المستهلك المحلي.
وصفة Python الكاملة
انسخ هذا النص البرمجي حرفيًا إلى action_register_recipe.py:
"""Private, bounded recipe. No model call occurs unless `produce` is explicitly run."""
import argparse
import hashlib
import json
import uuid
from datetime import date
from pathlib import Path
from typing import Literal
from pydantic import BaseModel, ConfigDict, StrictInt, StrictStr, ValidationError
class StrictModel(BaseModel):
model_config = ConfigDict(extra="forbid", strict=True)
class Action(StrictModel):
action_text: StrictStr
evidence_quote: StrictStr
line_start: StrictInt
line_end: StrictInt
owner: StrictStr | None
due_date: StrictStr | None
evidence_status: Literal["complete", "needs_review"]
class Extraction(StrictModel):
schema_version: Literal["action-register/v1"]
status: Literal["ready", "partial", "blocked"]
source_id: StrictStr
source_sha256: StrictStr
actions: list[Action]
gap_note: StrictStr | None
def digest(data: bytes) -> str:
return hashlib.sha256(data).hexdigest()
def canonical(payload: Extraction) -> bytes:
return json.dumps(payload.model_dump(mode="json"), sort_keys=True, ensure_ascii=False, separators=(",", ":")).encode("utf-8")
def check(payload: Extraction, source_id: str, source: bytes) -> None:
if payload.source_id != source_id or payload.source_sha256 != digest(source):
raise ValueError("source identity or revision mismatch")
lines = source.decode("utf-8").splitlines()
if payload.status == "ready" and (not payload.actions or payload.gap_note is not None):
raise ValueError("ready requires actions and no gap")
if payload.status != "ready":
raise ValueError("partial or blocked result cannot reach consumer")
for index, item in enumerate(payload.actions, 1):
if item.evidence_status != "complete":
raise ValueError(f"action {index}: evidence needs review")
if not item.action_text.strip() or not item.evidence_quote.strip():
raise ValueError(f"action {index}: empty task or evidence")
if not 1 <= item.line_start <= item.line_end <= len(lines):
raise ValueError(f"action {index}: invalid source line range")
segment = "\n".join(lines[item.line_start - 1:item.line_end])
if item.evidence_quote not in segment:
raise ValueError(f"action {index}: quotation absent from source span")
if item.owner is not None and not item.owner.strip():
raise ValueError(f"action {index}: blank owner")
if item.due_date is not None:
try:
if date.fromisoformat(item.due_date).isoformat() != item.due_date:
raise ValueError()
except ValueError as exc:
raise ValueError(f"action {index}: due date must be YYYY-MM-DD") from exc
def report(payload: Extraction) -> str:
"""Application consumer: a triage register grouped by missing ownership/date."""
groups = [
("Assigned with dates", lambda x: x.owner is not None and x.due_date is not None),
("Needs an owner", lambda x: x.owner is None),
("Needs a date", lambda x: x.owner is not None and x.due_date is None),
]
rows = ["# Action register for " + payload.source_id, "", "Review artifact; source meaning was approved separately.", ""]
for heading, predicate in groups:
rows += ["## " + heading, ""]
matches = [(n, a) for n, a in enumerate(payload.actions, 1) if predicate(a)]
for number, action in matches:
rows += [f"{number}. {action.action_text}", f" Owner: {action.owner or 'Unspecified'}; due: {action.due_date or 'Unspecified'}", f" Source lines {action.line_start}-{action.line_end}: {action.evidence_quote}", ""]
if not matches:
rows += ["None.", ""]
return "\n".join(rows)
def load_payload(path: Path) -> Extraction:
return Extraction.model_validate_json(path.read_bytes(), strict=True)
def consume(source_id: str, source_path: Path, payload_path: Path, approval_path: Path, out_dir: Path) -> tuple[Path, Path]:
source = source_path.read_bytes()
payload = load_payload(payload_path)
check(payload, source_id, source)
run = json.loads(payload_path.with_suffix(".run.json").read_text(encoding="utf-8"))
payload_hash = digest(canonical(payload))
if run.get("source_id") != source_id or run.get("source_sha256") != digest(source) or run.get("payload_sha256") != payload_hash or run.get("last_agent") != "Owned-source action extractor" or not str(run.get("run_id", "")).strip():
raise ValueError("producer receipt does not match accepted candidate")
approval = json.loads(approval_path.read_text(encoding="utf-8"))
if (type(approval) is not dict
or set(approval) != {"approved", "run_id", "source_id", "source_sha256", "payload_sha256", "reviewer", "review_note"}
or approval["approved"] is not True
or any(type(approval[key]) is not str for key in ("run_id", "source_id", "source_sha256", "payload_sha256", "reviewer", "review_note"))
or not approval["reviewer"].strip() or not approval["review_note"].strip()
or approval["run_id"] != run["run_id"]
or approval["source_id"] != source_id
or approval["source_sha256"] != digest(source)
or approval["payload_sha256"] != payload_hash):
raise ValueError("human source/meaning approval must bind exact source and payload hashes")
body = report(payload).encode("utf-8")
stem = "action-register-" + payload_hash[:16]
out_dir.mkdir(parents=True, exist_ok=True)
target = out_dir / (stem + ".md")
receipt = out_dir / (stem + ".receipt.json")
record = {"run_id": run["run_id"], "source_id": source_id, "source_sha256": digest(source), "payload_sha256": payload_hash, "report_sha256": digest(body), "reviewer": approval["reviewer"], "operation": "local action-triage register"}
encoded = (json.dumps(record, indent=2) + "\n").encode("utf-8")
for path, data in [(target, body), (receipt, encoded)]:
if path.exists():
if path.read_bytes() != data:
raise ValueError(f"uncertain prior output differs: {path}; inspect before retry")
else:
with path.open("xb") as handle:
handle.write(data)
if path.read_bytes() != data:
raise IOError(f"readback failed: {path}")
return target, receipt
def produce(source_id: str, source_path: Path, candidate_path: Path, model: str) -> None:
from agents import Agent, Runner
source = source_path.read_bytes()
source_text = source.decode("utf-8")
if not source_text.strip() or candidate_path.exists() or not candidate_path.parent.is_dir():
raise ValueError("source must contain text; candidate must be new and its parent directory must already exist")
run_id = str(uuid.uuid4())
agent = Agent(
name="Owned-source action extractor",
model=model,
instructions=("Extract only explicit action commitments from the supplied numbered source. "
"Return action-register/v1. Copy an exact quote with matching 1-based line span. "
"Use null for an unstated owner or due date; never infer them. "
"Use partial and needs_review for ambiguous evidence; blocked for unusable input. "
"Do not use tools or hand off."),
output_type=Extraction,
)
numbered = "\n".join(f"{n}: {line}" for n, line in enumerate(source_text.splitlines(), 1))
try:
result = Runner.run_sync(agent, f"Source ID: {source_id}\nSHA-256: {digest(source)}\nNumbered source:\n{numbered}")
if result.interruptions or result.last_agent is not agent or not isinstance(result.final_output, Extraction):
raise ValueError("run interrupted, unfinished, or finished by unexpected agent; this CLI does not persist resumable RunState, so do not resume from its failure receipt")
output = result.final_output
if output.source_id != source_id or output.source_sha256 != digest(source):
raise ValueError("producer returned wrong source identity")
candidate = canonical(output)
with candidate_path.open("xb") as handle:
handle.write(candidate) # Candidate only; consumer still requires review.
receipt = {"run_id": run_id, "source_id": source_id, "source_sha256": digest(source), "payload_sha256": digest(candidate), "last_agent": result.last_agent.name, "model": model}
with candidate_path.with_suffix(".run.json").open("x", encoding="utf-8") as handle:
handle.write(json.dumps(receipt, indent=2) + "\n")
except Exception as exc:
failure = {"run_id": run_id, "source_id": source_id, "source_sha256": digest(source), "error_type": type(exc).__name__, "error": str(exc), "state": "unknown_or_blocked; inspect run and destination before retry"}
with candidate_path.with_name(candidate_path.stem + "." + run_id + ".failure.json").open("x", encoding="utf-8") as handle:
handle.write(json.dumps(failure, indent=2) + "\n")
raise
def main() -> None:
parser = argparse.ArgumentParser()
sub = parser.add_subparsers(dest="command", required=True)
for name in ("produce", "consume"):
p = sub.add_parser(name)
p.add_argument("--source-id", required=True)
p.add_argument("--source", type=Path, required=True)
if name == "produce":
p.add_argument("--candidate", type=Path, required=True)
p.add_argument("--model", required=True)
else:
p.add_argument("--candidate", type=Path, required=True)
p.add_argument("--approval", type=Path, required=True)
p.add_argument("--out", type=Path, required=True)
args = parser.parse_args()
if args.command == "produce":
produce(args.source_id, args.source, args.candidate, args.model)
else:
print(*consume(args.source_id, args.source, args.candidate, args.approval, args.out), sep="\n")
if __name__ == "__main__":
main()الحدود والمصادر
فُحص العقد والمستهلك المحلي دون اتصال بخمسة اختبارات وسبع حالات اصطناعية لمنتج محاكى أو إعادة محاولة. شملت توليد المخطط الصارم والرفض قبل إنشاء الملف والتجميع والبصمات ومعالجة الفشل. لم تُرسل مخططات إلى مزود ولم يُستدع نموذج ولم تُستخرج ملاحظات حقيقية ولم تُؤخذ موافقة بشرية واقعية ولم يُنشأ تقرير من مصدر حقيقي ولم تُقَس فاتورة أو جودة. تعتمد رسوم API على الحساب؛ افحص التوافق والأسعار وإذن البيانات قبل إرسال المصدر.
للسياق ذي الصلة، فإن دليل BIG CHANGE لاختيار مهمة ذكاء اصطناعي من عملك يغطي القرار السابق بشأن العمل الذي تختبره. يبدأ هذا الدليل باستخراج مختار ومأذون به، ويمرر نتيجة واحدة محددة النوع إلى مستهلك محلي مفحوص.
المصادر وقراءات إضافية
- OpenAI Agents SDK: Agents يوثق Pydantic
output_type. لا يوفر الموافقة البشرية أو المستهلك المحلي في الوصفة. - OpenAI Agents SDK: Results يوثق
final_outputوlast_agentوالانقطاعات وto_state(). لا يحفظ CLI هذا حالة قابلة للاستئناف. - OpenAI Agents SDK: Quickstart يغطي التثبيت وإعداد مفتاح API. أبقِ المفتاح خارج المصدر والإيصالات.
- OpenAI Structured Outputs يشرح قواعد المخطط الصارم ودعم النماذج. أكّد دعم نموذجك المختار.
- Pydantic ConfigDict يوثق التحقق الصارم ومنع الحقول الإضافية.
- OpenAI Agents SDK 0.24.0 و Pydantic 2.14.1 يحددان الإصدارات المثبتة محليًا بدقة. لم يُختبر قبول المزود.



