Repo ini berisi contoh arsitektur yang dapat diskalakan untuk aplikasi bertenaga AI. Di permukaan, ini adalah aplikasi AI di mana pengguna dapat mengunggah PDF dan mengobrol dengan mereka. Namun, di bawah tenda, setiap pengguna mendapatkan instance database vektor khusus (Postgres on Neon dengan PGVector).
Anda dapat melihat versi langsung di https://db-per-tenant.up.railway.app/

Aplikasi ini dibangun menggunakan teknologi berikut:
Daripada memiliki semua embeddings vektor yang disimpan dalam satu basis data Postgres, Anda menyediakan masing -masing penyewa (pengguna, organisasi, ruang kerja, atau entitas lain yang membutuhkan isolasi) dengan instance basis data Postgres khusus di mana Anda dapat menyimpan dan menanyakan embeddingnya.
Bergantung pada aplikasi Anda, Anda akan menyediakan database vektor setelah acara tertentu (misalnya, pendaftaran pengguna, pembuatan organisasi, atau meningkatkan ke tingkat berbayar). Anda kemudian akan melacak penyewa dan database vektor terkait di database utama aplikasi Anda.
Pendekatan ini menawarkan beberapa manfaat:
Berikut diagram arsitektur basis data dari aplikasi demo yang ada di repo ini:

Basis data aplikasi utama terdiri dari tiga tabel: documents , users , dan vector_databases .
documents menyimpan informasi tentang file, termasuk judul, ukuran, dan cap waktu mereka, dan ditautkan ke pengguna melalui kunci asing.users memelihara profil pengguna, termasuk nama, email, dan URL avatar.vector_databases melacak basis data vektor mana yang menjadi milik pengguna. Kemudian, setiap database vektor yang disediakan memiliki tabel embeddings untuk menyimpan potongan dokumen untuk generasi pengambilan-augmented (RAG).
Untuk aplikasi ini, database vektor disediakan saat pengguna mendaftar. Setelah mereka mengunggah dokumen, itu akan dipotong dan disimpan dalam database vektor khusus mereka. Akhirnya, begitu pengguna mengobrol dengan dokumen mereka, pencarian kesamaan vektor berjalan dengan database mereka untuk mengambil informasi yang relevan untuk menjawab prompt mereka.

// 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 ) ;Meskipun pendekatan ini bermanfaat, itu juga bisa menjadi tantangan untuk diterapkan. Anda perlu mengelola siklus hidup setiap database, termasuk penyediaan, penskalaan, dan de-provisioning. Untungnya, Postgres on Neon diatur secara berbeda:

Ini membuat pola yang diusulkan untuk membuat database per penyewa tidak hanya mungkin tetapi juga hemat biaya.
Ketika Anda memiliki database per penyewa, Anda perlu mengelola migrasi untuk setiap database. Proyek ini menggunakan gerimis:
/app/lib/vector-db/schema.ts menggunakan naskah.bun run vector-db:generate , dan disimpan di /app/lib/vector-db/migrations .bun run vector-db:migrate . Perintah ini akan menjalankan skrip yang terhubung ke database masing -masing penyewa dan menerapkan migrasi.Penting untuk dicatat bahwa perubahan skema apa pun yang ingin Anda perkenalkan harus kompatibel ke belakang. Kalau tidak, Anda perlu menangani migrasi skema secara berbeda.
Meskipun pola ini berguna dalam membangun aplikasi AI, Anda dapat menggunakannya untuk memberikan masing -masing penyewa dengan basis data sendiri. Anda juga dapat menggunakan database selain Postgres untuk database aplikasi utama Anda (misalnya, MySQL, MongoDB, server MSSQL, dll.).
Jika Anda memiliki pertanyaan, jangan ragu untuk menghubungi Neon Discord atau hubungi tim penjualan neon. Kami ingin mendengar dari Anda.