يحتوي هذا الريبو على مثال على بنية قابلة للتطوير للتطبيقات التي تعمل بذو الثقة. على السطح ، إنه تطبيق منظمة العفو الدولية حيث يمكن للمستخدمين تحميل ملفات PDF والدردشة معهم. ومع ذلك ، تحت الغطاء ، يحصل كل مستخدم على مثيل قاعدة بيانات متجه مخصص (postgres على النيون مع PGVector).
يمكنك التحقق من الإصدار المباشر على https://db-per-enant.up.railway.app/

تم تصميم التطبيق باستخدام التقنيات التالية:
بدلاً من تخزين جميع التضمينات المتجهات المخزنة في قاعدة بيانات Postgres واحدة ، يمكنك توفير كل مستأجر (مستخدم أو مؤسسة أو مساحة عمل أو أي كيان آخر يتطلب العزلة) بمثيل قاعدة بيانات Postgres المخصص الخاص به حيث يمكنك تخزينها والاستعلام عنها.
اعتمادًا على التطبيق الخاص بك ، ستقوم بتوفير قاعدة بيانات متجه بعد حدث محدد (على سبيل المثال ، اشتراك المستخدم أو إنشاء المؤسسة أو الترقية إلى المستوى المدفوع). ستقوم بعد ذلك بتتبع المستأجرين وقواعد بيانات المتجهات المرتبطة بهم في قاعدة البيانات الرئيسية للتطبيق.
يوفر هذا النهج عدة فوائد:
إليك مخطط بنية قاعدة البيانات للتطبيق التجريبي الموجود في هذا الريبو:

تتكون قاعدة بيانات التطبيق الرئيسية من ثلاثة جداول: documents users و vector_databases .
documents بتخزين معلومات حول الملفات ، بما في ذلك عناوينها وأحجامها وجدولها الطابع ، ويرتبط بالمستخدمين عبر مفتاح خارجي.users بملفات تعريف المستخدم ، بما في ذلك الأسماء ورسائل البريد الإلكتروني وعناوين URL.vector_databases أي قاعدة بيانات المتجه ينتمي إلى المستخدم. بعد ذلك ، تحتوي كل قاعدة بيانات متجهة يتم توفيرها على جدول embeddings لتخزين أجزاء المستندات لتوليد الاسترداد (RAG).
بالنسبة لهذا التطبيق ، يتم توفير قواعد بيانات المتجهات عند تسجيل المستخدم. بمجرد تحميل المستند ، يتم قطعها وتخزينها في قاعدة بيانات المتجهات المخصصة الخاصة بهم. أخيرًا ، بمجرد أن يتحدث المستخدم مع مستندهم ، يعمل بحث تشابه المتجه مقابل قاعدة البيانات الخاصة بهم لاسترداد المعلومات ذات الصلة للإجابة على المطالبة.

// Code from app/lib/auth.ts
authenticator . use (
new GoogleStrategy (
{
clientID : process . env . GOOGLE_CLIENT_ID ,
clientSecret : process . env . GOOGLE_CLIENT_SECRET ,
callbackURL : process . env . GOOGLE_CALLBACK_URL ,
} ,
async ( { profile } ) => {
const email = profile . emails [ 0 ] . value ;
try {
const userData = await db
. select ( {
user : users ,
vectorDatabase : vectorDatabases ,
} )
. from ( users )
. leftJoin ( vectorDatabases , eq ( users . id , vectorDatabases . userId ) )
. where ( eq ( users . email , email ) ) ;
if (
userData . length === 0 ||
! userData [ 0 ] . vectorDatabase ||
! userData [ 0 ] . user
) {
const { data , error } = await neonApiClient . POST ( "/projects" , {
body : {
project : { } ,
} ,
} ) ;
if ( error ) {
throw new Error ( `Failed to create Neon project, ${ error } ` ) ;
}
const vectorDbId = data ?. project . id ;
const vectorDbConnectionUri = data . connection_uris [ 0 ] ?. connection_uri ;
const sql = postgres ( vectorDbConnectionUri ) ;
await sql `CREATE EXTENSION IF NOT EXISTS vector;` ;
await migrate ( drizzle ( sql ) , { migrationsFolder : "./drizzle" } ) ;
const newUser = await db
. insert ( users )
. values ( {
email ,
name : profile . displayName ,
avatarUrl : profile . photos [ 0 ] . value ,
userId : generateId ( { object : "user" } ) ,
} )
. onConflictDoNothing ( )
. returning ( ) ;
await db
. insert ( vectorDatabases )
. values ( {
vectorDbId ,
userId : newUser [ 0 ] . id ,
} )
. returning ( ) ;
const result = {
... newUser [ 0 ] ,
vectorDbId ,
} ;
return result ;
}
return {
... userData [ 0 ] . user ,
vectorDbId : userData [ 0 ] . vectorDatabase . vectorDbId ,
} ;
} catch ( error ) {
console . error ( "User creation error:" , error ) ;
throw new Error ( getErrorMessage ( error ) ) ;
}
} ,
) ,
) ;
// Code from app/routes/api/document/chat
// Get the user's messages and the document ID from the request body.
const {
messages ,
documentId ,
} : {
messages : Message [ ] ;
documentId : string ;
} = await request . json ( ) ;
const { content : prompt } = messages [ messages . length - 1 ] ;
const { data , error } = await neonApiClient . GET (
"/projects/{project_id}/connection_uri" ,
{
params : {
path : {
project_id : user . vectorDbId ,
} ,
query : {
role_name : "neondb_owner" ,
database_name : "neondb" ,
} ,
} ,
} ,
) ;
if ( error ) {
return json ( {
error : error ,
} ) ;
}
const embeddings = new OpenAIEmbeddings ( {
apiKey : process . env . OPENAI_API_KEY ,
dimensions : 1536 ,
model : "text-embedding-3-small" ,
} ) ;
const vectorStore = await NeonPostgres . initialize ( embeddings , {
connectionString : data . uri ,
tableName : "embeddings" ,
columns : {
contentColumnName : "content" ,
metadataColumnName : "metadata" ,
vectorColumnName : "embedding" ,
} ,
} ) ;
const result = await vectorStore . similaritySearch ( prompt , 2 , {
documentId ,
} ) ;
const model = new ChatOpenAI ( {
apiKey : process . env . OPENAI_API_KEY ,
model : "gpt-4o-mini" ,
temperature : 0 ,
} ) ;
const allMessages = messages . map ( ( message ) =>
message . role === "user"
? new HumanMessage ( message . content )
: new AIMessage ( message . content ) ,
) ;
const systemMessage = new SystemMessage (
`You are a helpful assistant, here's some extra additional context that you can use to answer questions. Only use this information if it's relevant:
${ result . map ( ( r ) => r . pageContent ) . join ( " " ) } ` ,
) ;
allMessages . push ( systemMessage ) ;
const stream = await model . stream ( allMessages ) ;
return LangChainAdapter . toDataStreamResponse ( stream ) ;على الرغم من أن هذا النهج مفيد ، إلا أنه قد يكون من الصعب تنفيذه. تحتاج إلى إدارة دورة حياة كل قاعدة بيانات ، بما في ذلك توفير ، التوسع ، وإزالة التسجيل. لحسن الحظ ، تم إعداد Postgres على النيون بشكل مختلف:

هذا يجعل النمط المقترح لإنشاء قاعدة بيانات لكل مستأجر غير ممكن فحسب ، بل أيضًا فعال من حيث التكلفة.
عندما يكون لديك قاعدة بيانات لكل مستأجر ، تحتاج إلى إدارة الترحيل لكل قاعدة بيانات. يستخدم هذا المشروع الرذاذ:
/app/lib/vector-db/schema.ts vector-db/schema.ts باستخدام TypeScript.bun run vector-db:generate وتخزينه في /app/lib/vector-db/migrations .bun run vector-db:migrate . سيقوم هذا الأمر بتشغيل برنامج نصي يتصل بقاعدة بيانات كل مستأجر ويطبق الترحيل.من المهم أن نلاحظ أن أي تغييرات مخطط تود تقديمها يجب أن تكون متوافقة مع المتتالية. خلاف ذلك ، ستحتاج إلى التعامل مع هجرات المخطط بشكل مختلف.
على الرغم من أن هذا النمط مفيد في بناء تطبيقات الذكاء الاصطناعي ، يمكنك ببساطة استخدامه لتزويد كل مستأجر بقاعدة البيانات الخاصة به. يمكنك أيضًا استخدام قاعدة بيانات أخرى غير Postgres لقاعدة بيانات التطبيق الرئيسية الخاصة بك (على سبيل المثال ، MySQL ، MongoDB ، MSSQL Server ، إلخ).
إذا كان لديك أي أسئلة ، فلا تتردد في التواصل مع Neon Discord أو الاتصال بفريق مبيعات النيون. نود أن نسمع منك.