وش يغطي هذا المخطط؟
يركز المرجع على النمط الظاهر للزائر: يفهم الاحتياج، يحفظ السياق المفيد، يبحث في كتالوج معتمد، يشرح التطابق، ويوجّه للخطوة التالية.
يشمل
- واجهة المحادثة وعقد الرسالة
- هيكلة كتالوج خيالي
- قواعد تصحيح السياق
- اختيار السؤال حسب الاحتياج
- مطابقة مرتبطة بالكتالوج
- استجابات منتجات منظمة
- مسار حتمي احتياطي
- حدود العزل والتحقق
ما يشمل
- مساعد تينغ الحقيقي
- تعليمات أو ترتيب إنتاجي
- ربط خاص
- بيانات عملاء أو CRM حقيقية
- اختيارات تينغ الإنتاجية للمزود
- تفاصيل أمان إنتاجية قابلة للاستغلال
البنية المرجعية
خل واجهة المتصفح للعرض والتفاعل. خادم التطبيق هو المسؤول عن فهم السياق، الوصول للكتالوج، التحقق، وهيكلة الاستجابة.

عرض مصدر Mermaid
flowchart LR
A["واجهة المحادثة"] --> B["خادم التطبيق"]
B --> C["سياق المحادثة"]
B --> D["استخراج النية والمتطلبات"]
D --> E["كتالوج محلي معتمد"]
E --> F["طبقة الاسترجاع والمطابقة"]
F --> G["طبقة التحقق"]
G --> H["استجابة منظمة"]
H --> Aحزمة عملية واحدة لبناء المرجع
يستخدم هذا المخطط OpenRouter وLangChain وLangGraph وLangSmith عشان يكون التنفيذ واضحاً. هذه خيارات تعليمية مرجعية، وليست وصفاً لحزمة تينغ الإنتاجية.
OpenRouter
بوابة نماذج لواجهة النموذج المرجعية. اختر نموذجاً يناسب لغة مشروعك وسرعته واحتياجه للمخرجات المنظمة.
LangChain
يوفر موصلات النماذج والأدوات وتركيب التعليمات والمخرجات المنظمة المتحقق منها بالمخطط.
LangGraph
يشغّل سير العمل ذي الحالة: تحديث الحقائق، استرجاع الخيارات، اختيار السؤال، التوصية، ثم التحقق.
LangSmith
يضيف التتبّع ومجموعات الاختبار والتقييمات وفحوصات الانحدار، بدون ما يصير مصدر حقيقة المنتجات.
كتالوج معتمد / مخزن بيانات
ملف JSON له إصدارات يكفي محلياً. في الإنتاج تقدر تستخدم قاعدة بيانات معتمدة أو مصدر بيانات أعمال خلف نفس واجهة المستودع المتحقق منها.
استضافة ويب / بنية تحتية سحابية
تشغّل واجهة الخادم، وتحقن الأسرار بأمان، وتوفر فحوصات الصحة والتسجيل والمراقبة والتراجع.
عرض مصدر Mermaid
flowchart LR
UI["واجهة المحادثة"] --> API["واجهة التطبيق"]
API --> GRAPH["سير عمل LangGraph"]
GRAPH --> CHAIN["أدوات LangChain والمخرجات المنظمة"]
CHAIN --> ROUTER["OpenRouter"]
GRAPH --> CATALOG["كتالوج منتجات معتمد"]
GRAPH --> TRACE["تتبّع وتقييم LangSmith"]
API --> HOST["استضافة ويب / بنية تحتية سحابية"]افصل تنسيق سير العمل عن حقيقة المنتجات
خل الحالة وعقد سير العمل والوصول للكتالوج والواجهة العامة وحالات التقييم في وحدات منفصلة. كذا تقدر تختبر حدود الكتالوج بدون استدعاء نموذج.
src/
agent/
graph.ts
state.ts
nodes/
extract-facts.ts
retrieve-catalog.ts
choose-question.ts
recommend.ts
validate-response.ts
catalog/
products.json
schema.ts
retrieve.ts
api/
product-discovery.ts
evals/
dataset.json
recommendation.test.tsثبّت الحزم المرجعية واضبط الأسرار داخل الخادم فقط
ابدأ بمشروع خادم JavaScript أو TypeScript حديث. إصدارات الحزم تتغير، لذلك استخدم الإصدارات المدعومة لبيئة تشغيلك وراجع ملاحظات الترحيل لكل إطار.
npm install @langchain/core @langchain/langgraph @openrouter/ai-sdk-provider ai langsmith zod
# Add your application framework and test runner separately.
# Keep credentials server-side.# .env.example — placeholders only
OPENROUTER_API_KEY=replace_with_server_side_secret
OPENROUTER_BASE_URL=https://openrouter.ai/api/v1
REFERENCE_MODEL_ID=choose_for_your_own_requirements
LANGSMITH_TRACING=true
LANGSMITH_API_KEY=replace_with_server_side_secret
LANGSMITH_PROJECT=product-discovery-referenceاضبط OpenRouter قبل ما تربط سير العمل
- أنشئ مفتاح API للمشروع واحفظه داخل بيئة الخادم فقط.
- اختر نموذجاً حالياً من كتالوج النماذج وانسخ معرّفه الدقيق إلى REFERENCE_MODEL_ID.
- اضبط رابط OpenRouter الأساسي، وتحقق من المتغيرات المطلوبة عند تشغيل الخادم، ثم جهّز موصل LangChain الموضح تحت.
- شغّل اختبار المخرجات المنظمة داخل الخادم قبل تفعيل سير LangGraph أو تتبع LangSmith.
import { createOpenRouter } from "@openrouter/ai-sdk-provider";
import { generateObject } from "ai";
import { z } from "zod";
const ExtractedFactsSchema = z.object({
facts: z.array(z.object({
key: z.string().min(1),
value: z.string().min(1),
certainty: z.enum(["explicit", "uncertain", "inferred"])
}))
});
const ModelEnvironment = z.object({
OPENROUTER_API_KEY: z.string().min(1),
OPENROUTER_BASE_URL: z.string().url(),
REFERENCE_MODEL_ID: z.string().min(1)
}).parse(process.env);
export const openRouter = createOpenRouter({
apiKey: ModelEnvironment.OPENROUTER_API_KEY,
baseURL: ModelEnvironment.OPENROUTER_BASE_URL
});
// Use structured output for interpretation and explanation only.
// Deterministic retrieval and catalog validation remain authoritative.
export async function extractFacts(prompt: string) {
return generateObject({
model: openRouter(ModelEnvironment.REFERENCE_MODEL_ID),
schema: ExtractedFactsSchema,
prompt,
temperature: 0
});
}
export async function smokeTestReferenceModel() {
const result = await generateObject({
model: openRouter(ModelEnvironment.REFERENCE_MODEL_ID),
schema: z.object({ status: z.literal("ready") }),
prompt: "Return status=ready. Do not include any other field."
});
return result.object.status === "ready";
}- لا تكشف مفاتيح API داخل كود المتصفح.
- ارفع .env.example للمستودع، مو ملف بيئة يحتوي قيماً حقيقية.
- اختر نموذجك حسب اللغة والمخرجات المنظمة والسرعة والخصوصية والتكلفة.
- حافظ على المسار الحتمي المحلي إذا تعطلت واجهة النموذج.
أعطِ كل منتج معلومات كافية لاتخاذ قرار
الكتالوج المفيد يحتاج أكثر من اسم ووصف. لازم يوضح متى يناسب المنتج، متى ما يناسب، وش يقدر يسوي، وش يحتاج.
idمعرّف ثابت للنظام
nameاسم العرض لكل لغة
summaryشرح قصير للعميل
problemsSolvedالمشاكل اللي صُمم الخيار عشان يحلها
bestFitحالات التطابق الإيجابية
unsuitableحالات عدم الملاءمة بوضوح
capabilitiesالقدرات المسموح يذكرها
requirementsمتطلبات البيانات أو سير العمل أو الملكية
relatedبدائل معتمدة للمقارنة
{
"id": "customer-support-agent",
"name": "Customer Support Agent",
"summary": "Answers repeat customer questions",
"problemsSolved": ["Repeated enquiries"],
"bestFit": ["Approved support content"],
"unsuitable": ["No reliable answer source"],
"capabilities": ["Approved-answer retrieval"],
"requirements": ["Escalation rules"],
"related": ["internal-knowledge-agent"]
}احفظ الحقائق، مو ملخص مبهم للمحادثة
تعامل مع المحادثة كدليل. استخرج مجموعة صغيرة من حقائق الزائر مع درجة التأكد والمصدر، وقدم التصحيح الواضح على أي افتراض سابق.
الواضح يتقدم على المستنتج
كلام الزائر الصريح يستبدل الافتراض السابق.
التصحيح له أولوية
“قصدي للموظفين” يغيّر الجمهور بدون ما يحتفظ بحقيقة قديمة عن العملاء.
عدم التأكد يبقى واضح
“يمكن” و“مو متأكد” ما تتحول إلى متطلبات مؤكدة.
النص غير المرتبط ما يغيّر السياق
السؤال الجانبي ما يعيد كتابة متطلبات المنتج بصمت.
الأسئلة لها ذاكرة
سجّل وش انطلب عشان ما يتكرر سؤال قديم.
اسأل عن أهم معلومة ناقصة فقط
اختيار السؤال التالي يعتمد على الاحتياج الحالي، الحقائق اللي تجمعت، والمتطلبات اللي تفرق بين أقوى الخيارات.
- 01
استخرج الحقائق الواضحة وغير المؤكدة
- 02
قيّم خيارات الكتالوج المحتملة
- 03
حدد المعلومة اللي تفرق بينهم أكثر
- 04
تجاوز أي شيء تجاوب أو انسأل من قبل
- 05
اسأل سؤال واحد مختصر
- 06
أعد التقييم بعد رد الزائر
عرض مصدر Mermaid
flowchart TD
A["رسالة الزائر"] --> B["تحديث الحقائق الواضحة"]
B --> C["تصحيح السياق السابق"]
C --> D["تقييم خيارات الكتالوج"]
D --> E{"هل الدليل كافي؟"}
E -- "لا" --> F["اسأل عن أهم معلومة ناقصة"]
E -- "نعم" --> G["تحقق من ادعاءات الكتالوج"]
G --> H["أرجع الإجابة وبطاقات المنتجات والخطوات التالية"]اربط كل توصية بدليل معتمد من الكتالوج
استخدم فلاتر حتمية للحدود الصارمة، وبعدها طبقة مطابقة واضحة للترتيب. تحقق من كل ادعاء مقابل سجلات الكتالوج المختارة.
فلاتر صارمة
استبعد الخيارات اللي تخالف الجمهور أو البيانات أو السياسات أو حدود سير العمل.
تقييم الخيارات
قيّم تطابق الاحتياج والمصدر والجمهور والقناة والخطوة التالية.
تجميع الدليل
اربط كل تطابق بحقول الملاءمة والمتطلبات من السجل نفسه.
التحقق من الادعاءات
ارفض أي اسم أو قدرة أو ربط مو موجود في السجل المعتمد.
أرجع هيكل تقدر الواجهة تثق فيه
خادم التطبيق يرجع عقد صغير ومتحقق منه، مو تنسيق عرض مفتوح بدون حدود.
typequestion | recommendation | comparison | no_matchmessageشرح واضح للعميلproducts[]حقول المنتجات العامة المعتمدةmatchReasons[]أسباب مرتبطة بالدليلsuggestions[]أسئلة تالية آمنة
{
"type": "recommendation",
"message": "The strongest match is…",
"products": [{ "id": "customer-support-agent" }],
"matchReasons": ["Repeated customer questions"],
"suggestions": ["What data would we need?"]
}ابنِ المساعد كوحدات صغيرة ومتحقق منها
مرجع TypeScript التالي يوضح العقود الأساسية. أضف الاستيرادات والمساعدات الخاصة بمشروعك في أماكنها، واختبر كل عقدة لحالها قبل تجميع الرسم.
import { z } from "zod";
export const ProductSchema = z.object({
id: z.string().min(1),
name: z.string().min(1),
summary: z.string().min(1),
problemsSolved: z.array(z.string()),
bestFit: z.array(z.string()),
unsuitable: z.array(z.string()),
capabilities: z.array(z.string()),
requirements: z.array(z.string()),
related: z.array(z.string()),
cta: z.object({ label: z.string(), href: z.string() })
});
export const CatalogSchema = z.array(ProductSchema).min(1);
export type Product = z.infer<typeof ProductSchema>;import { Annotation } from "@langchain/langgraph";
export type Fact = {
key: string;
value: string;
certainty: "explicit" | "uncertain" | "inferred";
sourceTurn: number;
};
export const DiscoveryState = Annotation.Root({
messages: Annotation<Array<{ role: "user" | "assistant"; content: string }>>({
reducer: (left, right) => left.concat(right), default: () => []
}),
facts: Annotation<Record<string, Fact>>({
reducer: (current, updates) => ({ ...current, ...updates }), default: () => ({})
}),
askedFactKeys: Annotation<string[]>({
reducer: (left, right) => [...new Set([...left, ...right])], default: () => []
}),
candidateIds: Annotation<string[]>({ default: () => [] }),
response: Annotation<DiscoveryResponse | null>({ default: () => null })
});export function mergeFacts(
current: Record<string, Fact>,
extracted: Fact[]
) {
const next = { ...current };
for (const fact of extracted) {
const previous = next[fact.key];
const visitorCorrectedIt = fact.certainty === "explicit" &&
previous && previous.value !== fact.value;
if (!previous || visitorCorrectedIt ||
(previous.certainty === "inferred" && fact.certainty !== "inferred")) {
next[fact.key] = fact;
}
}
return next;
}import catalogJson from "./products.json";
import { CatalogSchema, type Product } from "./schema";
const catalog = CatalogSchema.parse(catalogJson);
export function retrieveProducts(facts: Record<string, Fact>): Product[] {
const terms = Object.values(facts)
.filter((fact) => fact.certainty !== "inferred")
.flatMap((fact) => fact.value.toLowerCase().split(/\s+/));
return catalog
.filter((product) => !violatesHardBoundary(product, facts))
.map((product) => ({
product,
score: [...product.problemsSolved, ...product.bestFit, ...product.capabilities]
.join(" ").toLowerCase().split(/\s+/)
.filter((word) => terms.includes(word)).length
}))
.filter(({ score }) => score > 0)
.sort((a, b) => b.score - a.score)
.slice(0, 3)
.map(({ product }) => product);
}export function chooseQuestion(
candidates: Product[],
facts: Record<string, Fact>,
askedFactKeys: string[]
) {
const missing = candidateRequirements(candidates)
.filter((requirement) => !facts[requirement.key])
.filter((requirement) => !askedFactKeys.includes(requirement.key))
.map((requirement) => ({
...requirement,
value: separationScore(requirement, candidates) * requirement.decisionWeight
}))
.sort((a, b) => b.value - a.value);
return missing[0] ?? null; // Ask one question, or move to recommendation.
}export const DiscoveryResponseSchema = z.object({
type: z.enum(["question", "recommendation", "comparison", "no_match", "error"]),
message: z.string().min(1).max(1200),
products: z.array(ProductSchema.pick({
id: true, name: true, summary: true, capabilities: true, cta: true
})).max(3),
matchReasons: z.array(z.string()).max(6),
suggestions: z.array(z.string()).max(3)
});
export function validateCatalogClaims(response: DiscoveryResponse, catalog: Product[]) {
const allowed = new Map(catalog.map((product) => [product.id, product]));
for (const product of response.products) {
const source = allowed.get(product.id);
if (!source || product.name !== source.name) throw new Error("Unsupported product");
if (product.capabilities.some((item) => !source.capabilities.includes(item))) {
throw new Error("Unsupported capability");
}
}
return DiscoveryResponseSchema.parse(response);
}import { END, START, StateGraph } from "@langchain/langgraph";
export const discoveryGraph = new StateGraph(DiscoveryState)
.addNode("extractFacts", extractFacts)
.addNode("retrieveCatalog", retrieveCatalog)
.addNode("chooseQuestion", chooseQuestionNode)
.addNode("recommend", recommend)
.addNode("validate", validateResponse)
.addEdge(START, "extractFacts")
.addEdge("extractFacts", "retrieveCatalog")
.addConditionalEdges("retrieveCatalog", routeAfterRetrieval, {
ask: "chooseQuestion", recommend: "recommend", noMatch: "validate"
})
.addEdge("chooseQuestion", "validate")
.addEdge("recommend", "validate")
.addEdge("validate", END)
.compile();export async function POST(request: Request) {
try {
const input = RequestSchema.parse(await request.json());
const result = await discoveryGraph.invoke({
messages: [{ role: "user", content: input.message }],
facts: input.state.facts,
askedFactKeys: input.state.askedFactKeys
});
return Response.json(PublicResultSchema.parse({
response: result.response,
state: { facts: result.facts, askedFactKeys: result.askedFactKeys }
}));
} catch (error) {
console.error("product_discovery_request_failed", safeErrorCode(error));
return Response.json({
response: deterministicFallback(),
error: "The assisted response is temporarily unavailable."
}, { status: 503 });
}
}مسار عملي لتطبيق المرجع
حدد القرار
اختر رحلة عميل محددة وخطوات تالية مسموحة.
هيكل الكتالوج
أضف الملاءمة وعدم الملاءمة والقدرات والمتطلبات والبدائل.
ابنِ الاسترجاع الحتمي
اثبت حدود الكتالوج قبل إضافة فهم اللغة الطبيعية.
أضف سياق المحادثة
تتبع الحقائق الواضحة والمترددة والمصححة واللي انسأل عنها.
أضف واجهة النموذج
استخدمها لفهم اللغة والشرح داخل عقود ثابتة.
تحقق من كل استجابة
امنع المنتجات والادعاءات والإجراءات غير المدعومة والهياكل الخاطئة.
صمم التحويل
انقل السياق المؤكد إلى استفسار أو حجز أو شراء أو موظف—بعد موافقة الزائر.
اختبر سيناريوهات حقيقية
استخدم تصحيحات وطلبات مبهمة وحالات بدون تطابق وهجمات تعليمات وتعطل الخدمة.
أثبت الرحلة المحددة قبل النشر
استخدم الكتالوج الخيالي لين يستقر السلوك. المفروض يشتغل المسار المحلي برد حتمي حتى لو كانت واجهة النموذج الخارجي معطلة.
- 01
انسخ .env.example إلى ملف بيئة محلي متجاهل وأضف بيانات التطوير.
- 02
حمّل products.json عبر CatalogSchema.parse عشان تفشل السجلات غير الصالحة عند بدء التشغيل.
- 03
شغّل الخادم وأرسل رسالة واحدة إلى واجهة اكتشاف المنتجات.
- 04
اختبر طلباً مبهماً، وتصحيحاً واضحاً، ومقارنة، وحالة بدون تطابق، وتعطلاً مصطنعاً لواجهة النموذج.
- 05
افحص الاستجابة العامة وتأكد أنها ما تحتوي تعليمات أو حالة مخفية أو رابط تتبع أو بيانات وصفية خاصة.
افصل التطبيق المرجعي عن النظام الإنتاجي
عزل الكتالوج
استخدم كتالوج خيالي أو عام ومعتمد؛ ولا تبحث في بيانات الإنتاج بشكل افتراضي.
ملكية الخادم
خل واجهات النماذج والتحقق والوصول لأنظمة العمل داخل خادم التطبيق.
عدم كشف الحالة الداخلية
أرجع حقول عامة فقط؛ لا ترسل تعليمات أو بيانات وصفية خاصة أو استدلال داخلي.
حدود الإدخال
حدد طول الرسالة وحجم السجل والأدوار المقبولة ومصادر الطلب.
فشل واضح
استخدم مسار حتمي أو اعرض خطأ مؤقت. لا تعرض رد ناجح مخترع.
تحويل بموافقة
لا تحول محادثة تعليمية إلى فرصة مبيعات بدون إجراء واضح وموافقة من الزائر.
اختبر القرارات، مو بس سلاسة الإجابة
استخدم تتبعات ومجموعات LangSmith لمراجعة سلوك سير العمل، واحتفظ بتأكيدات حتمية داخل الكود عشان حدود الكتالوج المهمة ما تعتمد على المراجعة اليدوية.
كل منتج وقدرة موجودة في السجل المعتمد.
تظهر التطابقات القوية المتوقعة للاحتياجات الممثلة.
يُطلب أهم شيء ناقص فقط، بدون تكرار.
التصحيحات الواضحة تستبدل الحقائق القديمة أو المستنتجة.
الاحتياج غير المدعوم يرجع no_match بدل اختراع منتج.
المهلة والمخرجات غير الصالحة تنتج مساراً حتمياً أو خطأ واضحاً.
const cases = [
{ input: "We need answers from approved support articles", expected: ["customer-support-agent"] },
{ input: "Actually, this is for employees", correction: { audience: "employees" } },
{ input: "Recommend a payroll system", expectedType: "no_match" }
];
for (const testCase of cases) {
const result = await discoveryGraph.invoke(toInitialState(testCase.input));
assertCatalogGrounded(result.response, catalog);
assertNoRepeatedQuestion(result.askedFactKeys);
assertExpectedOutcome(result.response, testCase);
// Record the run in a LangSmith dataset for trace review and regression trends.
}وش يتغير قبل الإطلاق الحقيقي؟
انقل سير العمل المختبر إلى استضافة ويب / بنية تحتية سحابية
انشر واجهة التطبيق وسير العمل على بيئة خادم تناسب حجم الطلبات والمنطقة ومتطلبات البيانات. افصل الواجهة العامة عن الأسرار وبيانات دخول أنظمة الأعمال.
بيئة التشغيل
شغّل واجهة API وLangGraph داخل الخادم. خل الطلبات بلا حالة أو حمّل حالة المحادثة من مخزن معتمد.
الحالة
استخدم حالة عميل موقعة للعروض منخفضة المخاطر فقط. في الإنتاج احفظ الحد الأدنى مع قواعد احتفاظ ووصول.
الكتالوج
عيّن مالكاً، وأصدر نسخاً من السجلات، وتحقق من التحديثات، ووفر مسار تراجع.
الأسرار
احقن بيانات الدخول من بيئة الاستضافة. لا تضمّنها في JavaScript المتصفح أو السجلات.
الضوابط
أضف فحص المصدر والمصادقة عند الحاجة وحدود المعدل وحجم الطلب والمهلة.
المراقبة
اجمع سجلات تشغيل آمنة وتتبعات LangSmith مع ضوابط المحتوى الحساس والاحتفاظ.
الاعتمادية
وفر فحوصات صحة وتنبيهات لنسبة الأعطال وحدد المسار البديل واختبر التراجع.
الربط
اربط CRM أو الحجز أو التجارة أو التحويل لموظف بعد موافقة واضحة والتحقق من قواعد العمل.
وش ما يحله هذا المرجع عن قصد؟
هذا المخطط يسمّي حزمة مرجعية تعليمية واحدة، لكنه ما يكشف أو يدّعي مزودي تينغ أو نموذجها الإنتاجي. وما يشمل تعليمات تينغ أو ترتيبها أو ربطها الخاص أو تفاصيل أمانها. تصميمك الإنتاجي يظل معتمداً على كتالوج الشركة والمخاطر وسير العمل والحجم واللغات والأنظمة.
