هذا تمرين اختياري بعد فهم Mapping والـ Dependencies وConfirmation. يستخدم بيانات ثابتة كي تختبر انتهاء Selection وتكرار Confirmation وحالات الخطأ دون إنشاء حجوزات حقيقية. لربط API لديك للمرة الأولى، ابدأ بـالدليل العملي الذي يرسل طلبًا فعليًا.
السيناريو صغير عن قصد: يقدّم المستخدم التاريخ، وتعطي API المواعيد المتاحة، ويختار المستخدم وقتًا، ثم يطلب التطبيق موافقته قبل الحجز.
متطلبات الـ API
- يعيد
GET /availability?date=2026-10-01الشكل{ slots: [{ slot_token, label }] }. - يقبل
POST /bookingsالشكل{ date, slot_token, notes? }. - يستقبل POST قيمة Bearer Token للمستخدم، ويعيد
{ booking_id, time }.
هذه Endpoints مخصصة للمثال، وليست خدمة تجريبية عامة. يعترض البرنامج الطلبات، لذلك لا ينشئ حجزًا عبر الشبكة.
١. احفظ الإعداد الكامل
نزّل config.yml. هذا ملف كامل؛ لا يحتاج إلى تجميع مقاطع من صفحات أخرى.
version: "2"
assistant:
language: auto
timezone: Asia/Hebron
tools:
- id: availability
tool:
description: Find available appointment times for a date. This tool does not create a booking.
input_schema:
type: object
properties:
date: { type: string, format: date }
required: [date]
request:
method: GET
url: https://api.example.com/availability
map:
query:
date: $.date
response:
private_fields: [slot_token]
output_schema:
type: object
properties:
slots:
type: array
items:
type: object
properties:
slot_token: { type: string }
label: { type: string }
required: [slot_token, label]
required: [slots]
- id: book
tool:
description: Book an appointment for a date after selecting an available time and confirming.
input_schema:
type: object
properties:
date: { type: string, format: date }
slot_token: { type: string, minLength: 1 }
notes: { type: string, maxLength: 500 }
required: [date, slot_token]
depends_on:
- tool: availability
when: always
arguments:
date: $.date
select:
items_path: $.slots
id_path: $.slot_token
label_path: $.label
id_sensitive: true
facts:
time: $.label
map:
$.slot_token: $.slot_token
ttl_ms: 30000
behavior:
effect: side-effect
confirmation:
required: true
ttl_ms: 30000
preview:
date: $.date
time: { source: selection, path: $.time }
notes: $.notes
recovery:
refresh_dependency: availability
statuses: [409, 410]
request:
method: POST
url: https://api.example.com/bookings
auth:
type: session
retry:
attempts: 1
map:
body:
date: $.date
slot_token: $.slot_token
notes: $.notes
تتبّع البيانات:
- يملك المستخدم
book.date، ويُمرّر إلىavailability.date. - تجلب
availabilityالمواعيد من الـ API، وتتحقق Output Schema من وجودslot_tokenوlabelلكل موعد. - يصف
selectطريقة عرض المواعيد، ويخفيid_sensitiveقيم Tokens الحقيقية باستخدام IDs للواجهة لا تكشف الـ Tokens الأصلية. - يكتب ربط الـ Dependency Token المختار في
book.slot_token. هذا الحقل غير موجود في Schema الحجز التي يراها الـ AI Model. - توفّر حقيقة Selection
timeمعاينة مفهومة للمستخدم، بينما يستخدم POST قيمة Token الحقيقية. - يوقف
confirmation.requiredالتدفق قبل الكتابة. ويمرّرauth.type: sessionقيمة Token من التطبيق، لتتحقق API من الصلاحية.
يحذف private_fields قيمة slot_token من بيانات العرض، لكنها تبقى متاحة لحسم الـ Dependencies داخل Runtime. حماية بيانات العرض لا ينبغي أن تزيل القيمة اللازمة للتنفيذ.
٢. شغّل برنامج الاختبار
ثبّت حزمة npm وفق دليل التثبيت، واحفظ البرنامج بجانب config.yml باسم demo.mjs. نزّل البرنامج.
import assert from "node:assert/strict";
import { AgentRuntime } from "agento-runtime";
const calls = [];
const runtime = await AgentRuntime.create({
configPath: "./config.yml",
presentation: "raw",
fetch: async (url, options) => {
const path = new URL(url).pathname;
calls.push({ path, method: options.method });
if (path === "/availability") {
return Response.json({
slots: [
{ slot_token: "slot-a", label: "10:00" },
{ slot_token: "slot-b", label: "11:00" },
],
});
}
assert.equal(path, "/bookings");
assert.equal(options.method, "POST");
assert.equal(options.headers.authorization, "Bearer demo-user-token");
assert.deepEqual(JSON.parse(options.body), {
date: "2026-10-01", slot_token: "slot-b", notes: "Window seat",
});
return Response.json({ booking_id: "booking-42", time: "11:00" });
},
});
const request = {
sessionId: "booking-demo",
auth: { token: "demo-user-token" },
};
try {
let result = await runtime.invoke({
...request, tool: "book",
arguments: { date: "2026-10-01", notes: "Window seat" },
});
assert.equal(result.status, "needs_selection");
assert.equal(calls.length, 1);
assert(!result.selection.options.some(option => option.id === "slot-b"));
console.log("1.", result.status, result.selection.options);
// Test fixture: simulate the user choosing 11:00.
const choice = result.selection.options.find(option => option.label === "11:00");
result = await runtime.select({
...request, selectionId: result.selection.id, choice: choice.id,
});
assert.equal(result.status, "needs_confirmation");
assert.equal(calls.length, 1);
assert.equal(result.confirmation.preview.time, "11:00");
assert(!JSON.stringify(result.confirmation.preview).includes("slot-b"));
console.log("2.", result.status, result.confirmation.preview);
// Test fixture only: a real UI waits for explicit user approval here.
result = await runtime.confirm({
...request, confirmationId: result.confirmation.id,
});
assert.equal(result.status, "completed");
assert.equal(calls.filter(call => call.method === "POST").length, 1);
console.log("3.", result.status, result.data);
console.log("HTTP calls:", calls);
} finally {
runtime.dispose();
}
node demo.mjs
يحاكي البرنامج Selection والموافقة داخل هذا الاختبار فقط. في منتجك، يجب أن ينتظر handler Selection إجراء المستخدم، ثم ينتظر handler Confirmation موافقته المنفصلة.
٣. اقرأ تسلسل النتائج
| الخطوة | النتيجة | طلبات HTTP حتى اللحظة |
|---|---|---|
استدعاء book بالتاريخ والملاحظات | needs_selection مع 10:00 و11:00 | GET واحد، ودون POST. |
| اختيار 11:00 | needs_confirmation مع التاريخ والوقت والملاحظات | ما زال GET واحدًا، ودون POST. |
استدعاء confirm() بقيمة confirmationId التي أعادها AGENTO | completed مع booking-42 | GET واحد وPOST واحد. |
يتحقق البرنامج من body النهائي بدقة، ومن عدم ظهور Token الحقيقي ضمن الخيارات أو المعاينة، ومن إرسال POST مرة واحدة فقط.
٤. غيّر خيارًا واحدًا في كل تجربة
احذف التاريخ. توقّع needs_input قبل أي GET. يميّز Runtime بين مدخل يقدّمه المستخدم وحقل تنتجه الـ Dependency.
أعد موعدًا واحدًا. إذا لم يحدد المستخدم تفضيلًا يخالف هذا الموعد، يُختار الموعد تلقائيًا. تصبح النتيجة الأولى needs_confirmation؛ عدّل assertions لتوافق التجربة. Selection التلقائي لا يعني الموافقة التلقائية.
أعد قائمة فارغة. توقّع needs_input مع رسالة تشرح غياب التوافر. لا تعرض نموذج إدخال بعنوان «التاريخ ناقص» لمجرد أن الحالة هي needs_input.
خفّض TTL الـ Dependency إلى مللي ثانية واحدة. قد ينتهي Selection قبل الطلب التالي. توقّع INPUT_SELECTION_STALE أو CONFIRMATION_STALE حسب لحظة الانتهاء. جدّد التدفق بدل إعادة إرسال ID قديم.
أعد 409 مرة واحدة من POST. يمسح recovery البيانات القديمة ويجلب مواعيد جديدة. تعامل مع Selection أو Confirmation الجديد؛ لا تؤكد البديل بصمت. يجب ألا يحدث POST الثاني إلا بعد موافقة جديدة.
غيّر when إلى missing ومرّر الـ Token مباشرةً. يمكن تجاوز الاستعلام. استخدم ذلك فقط لمسار موثوق من التطبيق أو Navigation، وليس للتحقق من Tokens التي يرسلها المستخدم.
٥. اتصل بـAPI الفعلية
استبدل URL للأداتين وoutput Schema، ثم احذف fetch المخصص. مرّر بيانات الاعتماد من backend بعد التحقق من المستخدم. طابق Schema ومسارات Selection والـ Mapping ومعاينة Confirmation مع Responses حقيقية.
أنشئ Runtime طويل العمر لكل process، واستخدم conversation ID ثابتًا ومصرّحًا به في كل متابعة. لا تسمح للعميل باختيار sessionId لمستخدم آخر.
عند تشغيل عدة instances، وفّر تخزين Sessions مشتركًا يسلسل المعاملات لكل Session، وrate limiter مشتركًا مناسبًا. وإذا دعمت API آلية idempotency، فاضبطها قبل السماح بإعادة محاولة الكتابة. راجع الجاهزية لـ Production.
٦. وسّع المثال إلى حجز خدمة
الخطوة التالية هي book → availability → services. تحتاج availability إلى ID لخدمة؛ لذلك عرّف اعتمادها على services. أضف اسم الخدمة إلى Selection facts للمعاينة، ثم أضف تفضيلات المستخدم بعد نجاح تدفق Selection الأساسي.
أضف سلوكًا واحدًا يمكن ملاحظته واختباره في كل مرة، ثم انتقل إلى السياسة التالية.