ننصح العملاء بمراجعة المحادثات التي يتولاها وكيل الذكاء الاصطناعي بعد تمكينه، لكن فحص الجودة اليدوي يصبح مستهلكًا للوقت عند التوسع. يوضح هذا الدليل كيفية أتمتة مراجعة جودة المحادثات باستخدام n8n + نموذج ذكاء اصطناعي، بحيث يمكن مراجعة كل محادثة مُغلقة تلقائيًا وتسجيلها في Google Sheets للتقارير.
باختصار، يتم تشغيل سير العمل هذا في n8n في كل مرة تُغلق فيها محادثة. عند التشغيل، يستدعي List Messages API لاسترجاع آخر 50 رسالة (مع خيار استرجاع حتى 100 عبر الترقيم)، ثم يمرر النص إلى نموذج ذكاء اصطناعي لمعالجة مراجعة الجودة — مثل تحليل المشاعر، تقييم التفاعل، والتحقق من الحل. تُسجَّل النتائج في Google Sheets حتى تتمكن من البحث بسهولة عن أي محادثة باستخدام معرف جهة الاتصال.
ما الذي ستبنيه
أتمتة تقوم بما يلي:
تُشغل عند إغلاق المحادثة
تسترجع آخر 50 رسالة باستخدام List Messages API (اختياريًا حتى 100 عبر الترقيم)
تنظف وتنسق الرسائل لتشكيل نص المحادثة
ترسل النص إلى نموذج ذكاء اصطناعي لتقييم جودة المحادثة
تحلل مخرجات الذكاء الاصطناعي بأمان (عقد JSON فقط)
تضيف النتائج إلى Google Sheets (صف واحد لكل محادثة)
متطلبات
الأدوات
منصة الأتمتة: n8n
Respond.io Developer API — نقطة نهاية List Messages
AI API — OpenAI, Anthropic, Gemini، أو أي مزود تدعمه n8n
التقارير / التخزين — Google Sheets (أو أداة التخزين المفضلة لديك)
بيانات الاعتماد
مفتاح Respond.io Developer API
مفتاح API لمزود الذكاء الاصطناعي (OpenAI, Anthropic, Gemini، إلخ)
بيانات اعتماد Google Sheets في n8n (OAuth أو حساب خدمة)
قبل أن تبدأ
1) أنشئ جدولًا في Google Sheets لنتائج مراجعة الجودة
أنشئ جدول بيانات وأضف ورقة (مثل QA_Results) مع العناوين التالية:
timestampcontact_idopened_atchanneloverall_scoreresolvedcustomer_sentimentreasoningengagement_score
2) تأكد من حقول الحمولة في المشغل
من مخرجات Conversation closed trigger، حدد أسماء الحقول الدقيقة لـ:
معرّف المحادثة
معرّف جهة الاتصال
الطابع الزمني لفتح المحادثة
الطابع الزمني لإغلاق المحادثة (اختياري)
تختلف أسماء الحقول باختلاف التنفيذ. في عقد الكود أدناه، سترى عناصر نائبة مثل
trigger.conversationId. قم بتحديث تعيينات الحقول لتطابق مخرجات المشغل لديك.
دليل خطوة بخطوة
1. المشغل: Conversation Closed
عندما تُغلق محادثة في respond.io، نريد تشغيل سير العمل في n8n تلقائيًا. هذا يضمن مرور كل محادثة مكتملة عبر QA — دون الحاجة لمراجعة يدوية.
n8n node:Conversation closed trigger
في n8n، أضف عقدة مشغل respond.io.
اختر Conversation Closed.
قم بتوصيل بيانات اعتماد مفتاح API الخاص بـ respond.io. تعلم كيفية إعداد ذلك في n8n عبر دليل التكامل الخاص بنا دليل التكامل.
يضمن هذا المشغل أن تقوم بمراجعة المحادثات فقط بعد انتهائها.
2. جلب رسائل المحادثة
في هذه الخطوة، ستجلب آخر 50 رسالة واردة وصادرة باستخدام List Messages API. هذا عادةً ما يكون كافيًا لمراجعة QA شاملة. إذا كنت بحاجة لمزيد من السياق، يمكنك اختياريًا جلب 50 رسالة إضافية (حتى 100 إجمالًا) باستخدام الترقيم.
عقد n8n:
get 1st 50 messages(HTTP Request)Is there a second page?(IF) — اختياريget 2nd 50 messages(HTTP Request) — اختياري
2.1 جلب أول 50 رسالة
Node:get 1st 50 messages
اختر Core > HTTP Request
الطريقة:
GETURL: نقطة نهاية List Messages الخاصة بك
مثال URL (استبدلها بقاعدة API الفعلية + صيغة النهاية):
https://api.respond.io/v2/contact/{{identifier}}/message/list
حيث يجب استبدال identifier بإحدى الصيغ التالية: id:<contactID>، phone:+<countryCodeAndPhone>، أو email:<contactEmail>
شغّل Send Query Parameters > Using Fields Below، ثم أضف:
الاسم:
limitالقيمة:
50
إذا كان API الخاص بك يدعم ذلك، اضبط الفرز من الأقدم → الأحدث. مثال: sort=asc.
قم بتمكين Send Headers > Using Fields Below، ثم أضف:
الاسم:
Acceptالقيمة:
application/json
الاسم:
Authorizationالقيمة:
Bearer <your_respond_api_key>
إذا تضمن استجابة List Messages لديك
pagination.next، يمكنك استخدامه مباشرة لجلب الصفحة الثانية.
2.2 التحقق وجلب أكثر من 50 رسالة (اختياري)
إذا لم تكن 50 رسالة كافية لاحتياجات QA الخاصة بك، يمكنك جلب صفحة ثانية من 50 رسالة. أضف عقدة IF للتحقق ما إذا كانت هناك رسائل إضافية متاحة.
Node:Is there a second page? (IF)
القيمة اليسرى (تعبير):
{{ $json.pagination.next }}المشغل: is not empty
إذا كانت صحيحة، فهذا يعني أن هناك المزيد من الرسائل ليتم جلبها. إذا كانت خاطئة، يستمر سير العمل دون جلب المزيد — وهذا يمنع فشل سير العمل عندما لا توجد صفحة ثانية. ستنتظر عقدة Merge التالية اكتمال المسارين، لذلك سيستمر سير العمل بسلاسة في كلتا الحالتين.
2.3 جلب الـ 50 رسالة التالية (اختياري)
إذا نجحت عقدة IF (هناك صفحة ثانية)، فاجلب الدفعة التالية.
Node:get 2nd 50 messages
أضف عقدة HTTP Request أخرى.
اضبط:
الطريقة:
GETURL:
{{ $json.pagination.next }}
أضف نفس الرؤوس:
Accept:
application/jsonAuthorization:
Bearer <your_respond_api_key>
3. دمج صفحات الرسائل (اختياري)
هذه الخطوة مطلوبة فقط إذا كنت تجلب أكثر من 50 رسالة. إذا اخترت عدم جلب صفحة ثانية، يمكنك تخطي هذه العقدة وربط
get 1st 50 messagesمباشرة بالخطوة التالية (Clean + keep messages since last open).
تجمع عقدة Merge أول 50 رسالة مع الـ50 التالية لتشكيل قائمة واحدة. بدونها، لا يستطيع سير العمل معالجة استجابتين API منفصلتين معًا.
n8n node:Merge
أضف Flow > Merge.
اضبط الوضع إلى Append.
اضبط عدد المدخلات:
2اتصل بـ:
المدخل 1:
get 1st 50 messagesالمدخل 2:
get 2nd 50 messages
4. تنظيف وتصفيّة الرسائل
لا تزال استجابة الـ API خامًا وتحتوي على الكثير من المعلومات الزائدة التي لا يحتاجها الذكاء الاصطناعي. هذه الخطوة تنظفها — حذف الرسائل القديمة، توحيد تسميات المرسل، وهيكلة كل شيء في قائمة بسيطة جاهزة لمعالجة الذكاء الاصطناعي. كل ما عليك هو إضافة عقدة Code ولصق JavaScript أدناه.
n8n node:Clean + keep messages since last convo open
للقيام بذلك، أضف عقدة → اختر Core → Code → Code in JavaScript.
سيقوم هذا الكود بـ:
دمج الرسائل من كلا الصفحتين
استبعاد الرسائل المرسلة قبل فتح المحادثة
فرز الرسائل من الأقدم → الأحدث
تطبيع المرسلين إلى تسميات متسقة:
Contact,AI Agent,Human Agent,Workflowإضافة أرقام فهرس الرسائل
إرجاع قائمة نظيفة ومهيكلة لخطوة النص
ملاحظات: هذا المثال يفلتر باستخدام
messageIdمقارنة بالطابع الزمني لفتح المحادثة المحول إلى ميكروثانية. إذا كان الـ API لديك يوفر طوابع زمنيةcreatedAtبدلاً من ذلك، فلتر باستخدامcreatedAtبدلًا منmessageId. تأكد أنconversation_open_timestampيتضمن المنطقة الزمنية. إذا لم يتضمنها، اضبطها على منطقة عملك الزمنية قبل التحليل.
الصق ذلك في عقدة الكود:
// n8n Code node AFTER Merge (Run Once for All Items)
// Incoming items are the API responses from page1 and (optionally) page2.
function unwrapRespondList(json) {
if (Array.isArray(json) && json.length && json[0]?.items) return json[0];
if (json?.items) return json;
if (Array.isArray(json)) return { items: json, pagination: {} };
return { items: [], pagination: {} };
}
function toMicroseconds(ts) {
if (!ts || typeof ts !== "string") return null;
// Prefer timestamps that already contain timezone info.
// If your timestamp lacks timezone, add it upstream (recommended) rather than hardcoding here.
const iso = ts.includes("T") ? ts : ts.replace(" ", "T");
const ms = Date.parse(iso);
if (Number.isNaN(ms)) return null;
return ms * 1000; // microseconds
}
function normalizeSender(source) {
if (!source) return "Unknown";
const s = String(source).toLowerCase();
if (s === "contact") return "Contact";
if (s.includes("ai")) return "AI Agent";
if (s.includes("workflow") || s.includes("automation")) return "Workflow";
if (s.includes("user") || s.includes("agent")) return "Human Agent";
return source;
}
// Read from the Conversation closed trigger
const trigger = $("Conversation closed trigger").first().json;
const body = trigger.body ?? trigger;
const openedTs =
body.conversation_open_timestamp ||
body.conversation_opened_timestamp ||
body.conversationOpenedAt ||
body["conversation_open_timestamp "] ||
null;
const openMicro = toMicroseconds(openedTs);
// Merge items from all incoming API payloads (1 or 2 pages)
let mergedItems = [];
for (const item of $input.all()) {
const unwrapped = unwrapRespondList(item.json);
if (Array.isArray(unwrapped.items)) mergedItems.push(...unwrapped.items);
}
// Filter messages after conversation opened timestamp
if (openMicro !== null) {
mergedItems = mergedItems.filter((m) => {
const idNum = Number(m?.messageId);
return Number.isFinite(idNum) && idNum > openMicro;
});
}
// Sort oldest -> newest
mergedItems.sort((a, b) => Number(a?.messageId ?? 0) - Number(b?.messageId ?? 0));
// Index + shape
const indexed = mergedItems.map((m, i) => {
const traffic = m?.traffic ?? null;
const sender = m?.sender || {};
// Incoming traffic is always from Contact
if (traffic === "incoming") {
return {
Index: i + 1,
traffic,
message: m?.message ?? null,
Sender: { source: "Contact" },
};
}
return {
Index: i + 1,
traffic,
message: m?.message ?? null,
Sender: {
source: normalizeSender(sender?.source),
userId: sender?.userId ?? null,
teamId: sender?.teamId ?? null,
},
};
});
return [
{
json: {
message_count: indexed.length,
items: indexed,
meta: {
conversation_open_timestamp: openedTs,
conversation_open_microseconds: openMicro,
pages_received: $input.all().length,
},
},
},
];تأكد من ضبط الوضع = تشغيل مرة واحدة لجميع العناصر واللغة = JavaScript.
النص البرمجي أعلاه هو مرجع. قد تُرجع مساحة العمل لديك حقولًا أو تراكيب مختلفة. لبناء منطق تحليل JSON المناسب لإعدادك، انسخ المخرجات من العقدة السابقة، والصقها في أداة ذكاء اصطناعي (مثل ChatGPT أو Claude) مع البرنامج النصي المرجعي أعلاه، ووصف شكل المخرجات الذي تحتاجه. يمكن للذكاء الاصطناعي عندها تعديل النص البرمجي ليتوافق مع بياناتك الفعلية.
5. إنشاء نص المحادثة
تحوّل هذه الخطوة قائمة الرسائل المنقّحة إلى نص بتنسيق Markdown لمعالجة الذكاء الاصطناعي. النصوص المنظمة تحسّن فهم الذكاء الاصطناعي وتقلل من الاستنتاجات الخاطئة.
عقدة n8n:Build markdown transcript (Core → Code → JavaScript)
الصق هذا في عقدة الكود:
// Minimal parse of possibly-escaped JSON string
function parseMaybeEscapedJSON(raw) {
if (raw == null) return null;
if (typeof raw === 'object') return raw;
let s = String(raw).trim();
if ((s.startsWith('"') && s.endsWith('"')) || (s.startsWith("'") && s.endsWith("'"))) s = s.slice(1, -1);
s = s.replace(/\\"/g, '"').replace(/\\n/g, '\n').replace(/\\t/g, '\t').replace(/\\r/g, '\r').replace(/\\\\/g, '\\');
return JSON.parse(s);
}
// Flatten nested objects/arrays into k1_k2_0_k3 style keys
// Primitive-only arrays are joined as comma-delimited strings
function flatten(obj, prefix = '', out = {}) {
if (obj == null) return out;
const makeKey = (k) => (prefix ? `${prefix}_${k}` : String(k));
if (Array.isArray(obj)) {
// If every element is a primitive, join as comma-delimited string
if (obj.every(v => v == null || typeof v !== 'object')) {
out[prefix || 'value'] = obj.join(', ');
return out;
}
obj.forEach((v, i) => {
const k = makeKey(i);
(v && typeof v === 'object') ? flatten(v, k, out) : out[k] = v;
});
return out;
}
if (typeof obj === 'object') {
for (const [k, v] of Object.entries(obj)) {
const key = makeKey(k);
(v && typeof v === 'object') ? flatten(v, key, out) : out[key] = v;
}
return out;
}
out[prefix || 'value'] = obj;
return out;
}
const parsed = [];
for (const item of items) {
try {
const obj = parseMaybeEscapedJSON(item.json?.Output);
if (!obj || typeof obj !== 'object') {
parsed.push({ json: { error: 'Parsed Output is not an object', raw: item.json?.Output ?? null } });
continue;
}
const out = flatten(obj);
parsed.push({ json: out });
} catch (e) {
parsed.push({ json: { error: 'Failed to parse Output', message: e?.message || String(e), raw: item.json?.Output ?? null } });
}
}
return parsed;تأكد من ضبط الوضع = تشغيل مرة واحدة لجميع العناصر واللغة = JavaScript.
6. مراجعات المحادثات باستخدام الذكاء الاصطناعي
تُرسِل هذه العقدة النص إلى نموذج ذكاء اصطناعي (مثل OpenAI 5.4) وتُعيد استجابة JSON مُنظمة.
كيفية الإعداد
اختر بيانات اعتماد مزود الذكاء الاصطناعي الخاص بك. في هذا المثال سنستخدم OpenAI.
المورد: Message a Model
العملية: Message an Assistant
الرسائل:
النوع: نص
الدور: المستخدم
الموجه:
{{ $json.transcript }}
تبسيط المخرجات: تشغيل
إضافة خيار:
تعليمات - هذه مجرد قالب نموذجي، ويمكنك إدخاله في أداة ذكاء اصطناعي أخرى (مثل ChatGPT أو Claude أو غيرها) لتحريره وفق احتياجاتك:
أنت محلل جودة المحادثات لمنصة respond.io، منصة رسائل العملاء. عملك هو مراجعة محادثات الدعم أو المبيعات وإنتاج درجات جودة دقيقة ومتسقة.
ستتلقى نص محادثة كقائمة مرقمة من الرسائل. قيّمها باستخدام عملية التفكير المهيكلة أدناه قبل إنتاج مخرجاتك النهائية.
---
## صيغة الإدخال
تحتوي كل رسالة في النص على:
- رقم الرسالة (مثل `1`، `2`، `3`)
- `traffic:incoming` — رسالة من العميل
- `traffic:outgoing` — رسالة إلى العميل
- `text:` — محتوى الرسالة
- `sender source:` — مرسلها:
- `Contact` → العميل
- `ai_agent` → وكيل الذكاء الاصطناعي (مؤتمت — قيّم للجودة)
- `workflow` → نظام آلي (لا تُحسب كتفاعل وكيل)
- `user` → وكيل بشري (أشر إلى نقطة التسليم)
عند التقييم، عالج رسائل `ai_agent` و`user` معًا كـ "جانب الوكيل". تتبّع وقت التسليم من الذكاء الاصطناعي إلى الإنسان لأن ذلك يؤثر على التسجيل ودرجة التفاعل. تجاهل رسائل `workflow` عند تقييم التفاعل — فهي استجابات نظامية آلية وليست عناصر للحكم قد تكون المحادثة بأي لغة. قيّم المشاعر والمحتوى بدقة بغض النظر عن اللغة
---
## الخطوة 1 — سلسلة التفكير (التفكير الداخلي)
قبل إصدار أي درجات، قم بتحليل المحادثة خطوة بخطوة بالترتيب التالي:
**1. حدّد نية العميل الأساسية. ما الذي أراد العميل الوصول إليه؟ صِفها في جملة واحدة.
**2. تتبّع هيكل المحادثة. من تولّى إدارة المحادثة — وكيل ذكاء اصطناعي فقط أم تم تحويلها إلى إنسان؟ في أي نقطة؟ هل شارك أكثر من وكيل بشري؟ هل كانت هناك رسائل من `workflow` قد تسبّب ارتباكًا للعميل؟
**3. تتبّع الحل. هل عالج الوكيل نية العميل الأساسية فعليًا؟ هل أكّد العميل أن المشكلة قد تم حلها؟ هل انتهت المحادثة قبل حل المشكلة، أم بانحراف/تسوية، أم مع تحويل مناسب للمتابعة؟
**4. تتبّع المشاعر. كيف بدأ العميل عاطفيًا؟ كيف كانت نهايتهم؟ ابحث عن:
- علامات الإحباط: تكرار الأسئلة، التصحيحات، ردود قصيرة/قاسية
- علامات الرضا: "thank you"، "got it"، "perfect"، عبارات انتهاء إيجابية
- علامات الحياد: معاملاتية، متعاونة، لا إشارة قوية لأي جهة
اعطِ وزنًا أكبر لنبرة العميل عند النهاية مقارنةً ببدايته.
**5. قيّم تفاعل الوكيل. قيّم جانب الوكيل بناءً على:
- هل أقرّ بحالة العميل قبل الانتقال إلى الحل؟
- هل خصّصوا الرد باستخدام سياق المحادثة السابق؟
- هل طرحوا أسئلة توضيحية ذكية أم افترضوا أمورًا؟
- إذا تولّى إنسان المحادثة، هل حافظ على استمرارية السياق من الذكاء الاصطناعي أم بدأ من الصفر؟
- هل كانت الردود بنفس لغة العميل؟
- لا تقم بتقييم رسائل `workflow` كمؤشر تفاعل.
**6. تحديد الأخطاء الجسيمة.**
تحقّق صراحةً من كل مما يلي:
- هل كرّر العميل سؤاله الرئيسي دون أن يتم الاعتراف به؟
- هل قدّم الوكيل معلومات غير صحيحة؟
- هل تجاهل الوكيل جزءًا هامًا من رسالة العميل؟
- هل انتهت المحادثة بدون حل أو بدون خطوات تالية واضحة؟
- هل كانت هناك نبرة غير مهنية أو غير لائقة؟
- هل كانت هناك تأخيرات مفرطة في الاستجابة؟
- هل كان مسار التصعيد خاطئًا أم غير ضروري؟
- هل فشل الوكيل البشري في التقاط سياق التسليم من الذكاء الاصطناعي؟
- هل ردّ الوكيل بلغة مختلفة عن لغة العميل؟
**7. اكتب مبرراتك.**
في 2–4 جمل، لخّص: ماذا حدث، ما الذي فعله الوكيل بشكل جيد، وما الذي فشل. كن محددًا — اذكر لحظات فعلية أو أرقام الرسائل.
---
## الخطوة 2 — التقييم المبدئي
استنادًا إلى استدلالك في الخطوة 1، أعد مسودة أولية لجميع الدرجات:
- **`overall_score`** (عدد صحيح، 1–10): الجودة الشاملة للمحادثة. Weights resolution, sentiment, and engagement together. الإخفاقات الحرجة تقيد الدرجة تلقائيًا عند 5.
- **`resolved`** (enum): ما إذا تم إغلاق مشكلة العميل. لا تعيّن `resolved` لمجرد أن الوكيل أرسل رسالة نهائية. يتطلب ذلك تأكيدًا صريحًا من العميل أو تسليمًا واضحًا ومكتملًا.
- **`customer_sentiment`** (قيمة): النبرة العاطفية السائدة لدى العميل، مع إعطاء وزن لكيفية انتهائها.
- **`engagement_score`** (عدد صحيح، 1–10): مدى جودة تواصل الوكيل — التخصيص، التعاطف، الوضوح، تطابق اللغة، واستمرارية التسليم عبر عمليات التسليم.
- **`reasoning`** (نص): الملخص من 2–4 جمل من الخطوة 1.
- **`flags`** (array): قائمة بالإخفاقات الحرجة المحددة. مصفوفة فارغة إذا لم توجد.
---
## الخطوة 3 — سلسلة التحقق
قبل الانتهاء، نفّذ كل فحص مما يلي وأجب عنه صراحةً: **التحقق 1 — الحل:**
هل هناك دليل فعلي على أن مشكلة العميل حُلت — أم أنني افترضت ذلك؟
إذا كان استنتاجيًا، هل يجب أن تكون قيمة `resolved` = "unresolved" بدلاً من ذلك؟ **التحقق 2 — الانطباع:**
هل أحكم على الانطباع بناءً على نبرة العميل عند النهاية، وليس فقط على استيائه في البداية؟
هل تحسّن مزاجهم، أم بقي نفسه، أم ساء؟ **التحقق 3 — التفاعل:**
هل سيصمد هذا التقييم `engagement_score` لو راجعه مدير مراقبة الجودة؟
**التحقق 4 — جودة التسليم:**
إذا تولى وكيل بشري المهمة، هل حافظ على الاستمرارية — أم بدأ من الصفر، تجاهل السياق، أو رد بلغة خاطئة؟ قم بتعديل `engagement_score` وفقًا لذلك.
**التحقق 5 — اتساق الدرجات:**
- `resolved: unresolved` + `customer_sentiment: negative` → يجب أن تكون `overall_score` ≤ 4
- `resolved: resolved` + `customer_sentiment: positive` → يجب أن تكون `overall_score` ≥ 6
- وجود أي علمة → يجب أن تكون `overall_score` ≤ 5
**التحقق 6 — اكتمال العلامات:**
راجع قائمة الأخطاء الجسيمة كاملة مرة أخرى. **Check 5 — Score consistency:**
- `resolved: unresolved` + `customer_sentiment: negative` → `overall_score` must be ≤ 4
- `resolved: resolved` + `customer_sentiment: positive` → `overall_score` must be ≥ 6
- Any flag present → `overall_score` must be ≤ 5
**Check 6 — Flags completeness:**
Review the full hard failure list again.
Did I miss anything?
---
الخطوة 4 — الإخراج النهائي
أخرج درجاتك النهائية في صيغة JSON المطلوبة فقط. ---
## STEP 4 — FINAL OUTPUT
Output your final scores in the required JSON format only. Do not include your internal Step 1–3 reasoning.
---
## مرجع التقييم
### overall_score
| الدرجة | المعنى |
|-------|---------|
| 9–10 | ممتاز — تم الحل بكفاءة، انتهى العميل برضا، وكان الوكيل متعاطفًا ومخصصًا |
| 7–8 | جيد — تم الحل مع احتكاك طفيف، انطباع محايد إلى إيجابي |
| 5–6 | متوسط — تم الحل جزئيًا، أو تفاعل عالي لكن نتيجة ضعيفة |
| 3–4 | ضعيف — غير محلول، انطباع سلبي، أو إخفاقات واضحة في التفاعل |
| 1–2 | فشل حرج — معلومات خاطئة، غادر العميل غاضبًا، أو تُركت المحادثة بدون متابعة |
### engagement_score
| الدرجة | المعنى |
|-------|---------|
| 9–10 | شديد التخصيص، متعاطف، فعال، مفيد بشكل استباقي، ومطابق للغة المطلوبة |
| 7–8 | جيد — عالج المشكلة مع بعض اللمسات الشخصية |
| 5–6 | وظيفي لكن آلي — صحيح تقنيًا، دون دفء أو تخصيص |
| 3–4 | تواصل ضعيف — افتراضات، تجاهل أسئلة، أو فقدان استمرارية التسليم |
| 1–2 | ضار — وقح، متجاهل، استخدام لغة خاطئة، أو الانحراف تمامًا عن الموضوع |### ## SCORING REFERENCE ### overall_score | Score | Meaning | |-------|---------| | 9–10 | Excellent — resolved efficiently, customer ended positive, agent was empathetic and personalized | | 7–8 | Good — resolved with minor friction, neutral-to-positive sentiment | | 5–6 | Average — partially resolved, or high engagement but poor outcome | | 3–4 | Poor — unresolved, negative sentiment, or clear engagement failures | | 1–2 | Critical failure — wrong info given, customer left angry, or conversation abandoned | ### engagement_score | Score | Meaning | |-------|---------| | 9–10 | Highly personalized, empathetic, efficient, proactively helpful, correct language throughout | | 7–8 | Solid — addressed the issue well with some personal touch | | 5–6 | Functional but robotic — technically correct, no warmth or personalization | | 3–4 | Poor communication — assumptions made, questions ignored, or handover lacked continuity | | 1–2 | Harmful — rude, dismissive, wrong language used, or completely off-topic |
مطلوب: إخراج JSON صارم
يجب أن تتطلب تعليمات المساعد لديك مخطط JSON.
التنسيق الموصى به:
النوع: JSON Schema (موصى به)
الاسم: Conversation QA Schema
صارم: تشغيل المخطط: هذا أيضًا مخطط نموذجي لإخراج درجة جودة المحادثة، حالة المشكلة، شعور العميل، درجة التفاعل، أسباب النتيجة، وعلامات الفشل في المحادثة.
{
"type": "json_schema",
"json_schema": {
"name": "conversation_review",
"strict": true,
"schema": {
"type": "object",
"properties": {
"overall_score": {
"type": "integer",
"description": "Holistic quality score of the conversation. 1 = critical failure, 10 = excellent. Any flag present caps this at 5."
},
"resolved": {
"type": "string",
"enum": ["resolved", "unresolved", "escalated"],
"description": "Whether the customer's core issue was closed. 'resolved' requires explicit customer confirmation. 'escalated' means a successful intentional handover to a human agent. 'unresolved' means the conversation ended without closure."
},
"customer_sentiment": {
"type": "string",
"enum": ["positive", "neutral", "negative"],
"description": "The customer's dominant emotional tone, weighted toward how they ended the conversation. 'positive' = satisfied or grateful. 'neutral' = cooperative and transactional. 'negative' = frustrated, repetitive, or left dissatisfied."
},
"engagement_score": {
"type": "integer",
"description": "How well the agent side communicated — personalization, empathy, clarity, language match, and handover continuity. Excludes workflow messages. 1 = harmful or dismissive, 10 = highly personalized and proactive."
},
"reasoning": {
"type": "string",
"description": "2–4 sentences explaining the scores. Must reference specific moments in the conversation. Cover what the agent did well, what failed, and what drove the final scores. لا تعمّم. Empty array if none apply.",
"items": {
"type": "string",
"enum": [
"customer_repeated_question",
"incorrect_information_given",
"agent_ignored_customer_message",
"conversation_ended_abruptly",
"unprofessional_tone",
"excessive_response_delay",
"wrong_escalation_path",
"handover_lacked_continuity",
"wrong_language_used"
]
}
}
},
"required": [
"overall_score",
"resolved",
"customer_sentiment",
"engagement_score",
"reasoning",
"flags"
],
"additionalProperties": false
}
}
}بعد هذه العقدة، اربطها بعقدة Map Output واستمر إلى الخطوة 7.
7. تحليل وتطبيع مخرجات الذكاء الاصطناعي
تمنع هذه الخطوة استجابات الذكاء الاصطناعي المشوّهة أو الجزئية من تعطيل سير العمل.
عقد n8n:
Map Output(Data Transformation → Edit Fields / Set)Parse Output(Core → Code → JavaScript)
7.1 Map Output
Node:Map Output
الوضع: تعيين يدوي
أنشئ حقلًا باسم
Outputواربطه باستجابة الذكاء الاصطناعي.
يضمن ذلك اتساق العقدة التالية (يمكنها دائمًا قراءة $json.Output).
7.2 تحليل الإخراج بأمان
Node:Parse Output
الصق هذا في عقدة الكود:
// Minimal parse of possibly-escaped JSON string
function parseMaybeEscapedJSON(raw) {
if (raw == null) return null;
if (typeof raw === 'object') return raw;
let s = String(raw).trim();
if ((s.startsWith('"') && s.endsWith('"')) || (s.startsWith("'") && s.endsWith("'"))) s = s.slice(1, -1);
s = s.replace(/\\"/g, '"').replace(/\\n/g, '\n').replace(/\\t/g, '\t').replace(/\\r/g, '\r').replace(/\\\\/g, '\\');
return JSON.parse(s);
}
// Flatten nested objects/arrays into k1_k2_0_k3 style keys
function flatten(obj, prefix = '', out = {}) {
if (obj == null) return out;
const makeKey = (k) => (prefix ? `${prefix}_${k}` : String(k));
if (Array.isArray(obj)) {
obj.forEach((v, i) => {
const k = makeKey(i);
(v && typeof v === 'object') ? flatten(v, k, out) : out[k] = v;
});
return out;
}
if (typeof obj === 'object') {
for (const [k, v] of Object.entries(obj)) {
const key = makeKey(k);
(v && typeof v === 'object') ? flatten(v, key, out) : out[key] = v;
}
return out;
}
out[prefix || 'value'] = obj;
return out;
}
const parsed = [];
for (const item of items) {
try {
const obj = parseMaybeEscapedJSON(item.json?.Output);
if (!obj || typeof obj !== 'object') {
parsed.push({ json: { error: 'Parsed Output is not an object', raw: item.json?.Output ?? null } });
continue;
}
const out = flatten(obj);
parsed.push({ json: out });
} catch (e) {
parsed.push({ json: { error: 'Failed to parse Output', message: e?.message || String(e), raw: item.json?.Output ?? null } });
}
}
return parsed;
ملاحظة حول التسطيح: ستتحول المصفوفات والكائنات المتداخلة إلى أعمدة مثل issues_0_type وissues_0_severity، إلخ. يُسهّل ذلك تخزينها في Google Sheets.
8. تخزين النتائج
الخطوة النهائية تخزن نتائج مراقبة الجودة في Google Sheets للتتبع والتقارير.
عقدة n8n: Google Sheets (append)
بيانات الاعتماد للاتصال: اربط حساب Google Sheets الخاص بك
المورد: ورقة داخل المستند
العملية: إضافة صف
اختر المستند: من القائمة → اختر اسم جدول البيانات
اختر الورقة: من القائمة → اختر اسم ورقة العمل
وضع تعيين الأعمدة: قم بتعيين كل عمود يدويًا
بعد ذلك، قم بتعيين كل قيمة إلى اسم العمود الذي أعددته في جدولك، ويفضل اتباع أسماء الأعمدة الموضحة في القسم 1.
من أين يأتي كل حقل
من مشغّل webhook (بيانات تعريف المحادثة):
قم بتعيين هذه باستخدام مرجع عقدة Webhook، مثلاً:
{{ $('Webhook').first().json.body.contact_id }}
contact_idconversation_opened_timestampconversation_closed_timestamp
من مخرجات الذكاء الاصطناعي المحللة (القسم 7):
قم بتعيين هذه باستخدام حقول الإخراج المحللة، مثلاً:
{{ $json.overall_score }}
overall_scoreresolvedsentimentreasoningengagement_score
لأغراض التصحيح (موصى به أثناء النشر):
raw_ai_output— خزّن الاستجابة الكاملة لنموذج الذكاء الاصطناعي حتى تتمكن من مراجعة النتائج وتحسين الموجه
💡 نصيحة: تحقق من المخرجات الفعلية لعقدة Webhook للتأكد من أسماء الحقول الدقيقة — فقد تختلف اعتمادًا على تكوين المشغّل في مساحة عملك.
تحسينات اختيارية
إخفاء المعلومات الشخصية قبل مراجعة الذكاء الاصطناعي (البريد الإلكتروني، أرقام الهاتف، معرفات الطلب)
معالجة أكثر من 100 رسالة عن طريق التكرار خلال الصفحات حتى تصبح
pagination.nextفارغةنماذج المحادثات (مثلاً، راجع 10% فقط من المحادثات المغلقة للتحكم في التكاليف)
أرسل القضايا شديدة الخطورة إلى Slack/Teams لمتابعة أسرع