Initial commit: Portfolio Website
Vollständige Next.js 15 Portfolio-Website mit: - Blog-System mit 100+ Artikeln - Supabase-Integration - Responsive Design mit Tailwind CSS - TypeScript-Konfiguration - Testing-Setup mit Vitest und Playwright Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,500 @@
|
||||
# MongoDB Atlas Vector Search für AI-Anwendungen
|
||||
|
||||
**Meta-Description:** MongoDB Atlas Vector Search für semantische Suche und RAG. Embedding API, Hybrid Queries und Integration mit LLMs.
|
||||
|
||||
**Keywords:** MongoDB Atlas, Vector Search, Semantic Search, RAG, Embeddings, AI Database, LLM Integration
|
||||
|
||||
---
|
||||
|
||||
## Einführung
|
||||
|
||||
MongoDB Atlas bietet mit **Vector Search** eine native Lösung für AI-Anwendungen. Operative Daten und Vector Embeddings in einer Datenbank – ohne Sync-Probleme zwischen separaten Systemen.
|
||||
|
||||
---
|
||||
|
||||
## Vector Search Architecture
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ MONGODB ATLAS VECTOR SEARCH │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ Single Platform: │
|
||||
│ ├── Operative Daten (Documents) │
|
||||
│ ├── Vector Embeddings (1536+ Dimensions) │
|
||||
│ ├── Metadata (für Filtering) │
|
||||
│ └── Full-Text Search (Atlas Search) │
|
||||
│ │
|
||||
│ Vector Index Types: │
|
||||
│ ├── HNSW (Hierarchical Navigable Small World) │
|
||||
│ ├── IVF (Inverted File Index) │
|
||||
│ └── Vector Quantization (Cost Reduction) │
|
||||
│ │
|
||||
│ Use Cases: │
|
||||
│ ├── Semantic Search │
|
||||
│ ├── RAG (Retrieval-Augmented Generation) │
|
||||
│ ├── Recommendation Systems │
|
||||
│ └── Image/Audio Similarity │
|
||||
│ │
|
||||
│ Performance (Dedicated Search Nodes): │
|
||||
│ └── 40-60% schnellere Query Times │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Setup
|
||||
|
||||
```typescript
|
||||
// lib/mongodb.ts
|
||||
import { MongoClient } from 'mongodb';
|
||||
|
||||
const uri = process.env.MONGODB_URI!;
|
||||
const options = {};
|
||||
|
||||
let client: MongoClient;
|
||||
let clientPromise: Promise<MongoClient>;
|
||||
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
const globalWithMongo = global as typeof globalThis & {
|
||||
_mongoClientPromise?: Promise<MongoClient>;
|
||||
};
|
||||
|
||||
if (!globalWithMongo._mongoClientPromise) {
|
||||
client = new MongoClient(uri, options);
|
||||
globalWithMongo._mongoClientPromise = client.connect();
|
||||
}
|
||||
clientPromise = globalWithMongo._mongoClientPromise;
|
||||
} else {
|
||||
client = new MongoClient(uri, options);
|
||||
clientPromise = client.connect();
|
||||
}
|
||||
|
||||
export default clientPromise;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Vector Index erstellen
|
||||
|
||||
```javascript
|
||||
// Atlas Search Index Definition (JSON)
|
||||
{
|
||||
"name": "vector_index",
|
||||
"type": "vectorSearch",
|
||||
"definition": {
|
||||
"fields": [
|
||||
{
|
||||
"type": "vector",
|
||||
"path": "embedding",
|
||||
"numDimensions": 1536,
|
||||
"similarity": "cosine"
|
||||
},
|
||||
{
|
||||
"type": "filter",
|
||||
"path": "category"
|
||||
},
|
||||
{
|
||||
"type": "filter",
|
||||
"path": "createdAt"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```typescript
|
||||
// Programmatisch Index erstellen
|
||||
import { MongoClient } from 'mongodb';
|
||||
|
||||
async function createVectorIndex() {
|
||||
const client = await MongoClient.connect(process.env.MONGODB_URI!);
|
||||
const db = client.db('myapp');
|
||||
|
||||
await db.command({
|
||||
createSearchIndexes: 'documents',
|
||||
indexes: [
|
||||
{
|
||||
name: 'vector_index',
|
||||
type: 'vectorSearch',
|
||||
definition: {
|
||||
fields: [
|
||||
{
|
||||
type: 'vector',
|
||||
path: 'embedding',
|
||||
numDimensions: 1536,
|
||||
similarity: 'cosine'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
await client.close();
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Documents mit Embeddings speichern
|
||||
|
||||
```typescript
|
||||
import OpenAI from 'openai';
|
||||
import clientPromise from '@/lib/mongodb';
|
||||
|
||||
const openai = new OpenAI();
|
||||
|
||||
interface Document {
|
||||
_id?: string;
|
||||
title: string;
|
||||
content: string;
|
||||
embedding: number[];
|
||||
category: string;
|
||||
createdAt: Date;
|
||||
}
|
||||
|
||||
// Embedding generieren
|
||||
async function generateEmbedding(text: string): Promise<number[]> {
|
||||
const response = await openai.embeddings.create({
|
||||
model: 'text-embedding-ada-002',
|
||||
input: text
|
||||
});
|
||||
return response.data[0].embedding;
|
||||
}
|
||||
|
||||
// Document speichern
|
||||
async function saveDocument(
|
||||
title: string,
|
||||
content: string,
|
||||
category: string
|
||||
): Promise<Document> {
|
||||
const client = await clientPromise;
|
||||
const db = client.db('myapp');
|
||||
|
||||
// Embedding für Titel + Content
|
||||
const embedding = await generateEmbedding(`${title}\n\n${content}`);
|
||||
|
||||
const document: Document = {
|
||||
title,
|
||||
content,
|
||||
embedding,
|
||||
category,
|
||||
createdAt: new Date()
|
||||
};
|
||||
|
||||
const result = await db.collection<Document>('documents').insertOne(document);
|
||||
return { ...document, _id: result.insertedId.toString() };
|
||||
}
|
||||
|
||||
// Bulk Import
|
||||
async function bulkImportDocuments(docs: Array<{ title: string; content: string; category: string }>) {
|
||||
const client = await clientPromise;
|
||||
const db = client.db('myapp');
|
||||
|
||||
const documentsWithEmbeddings = await Promise.all(
|
||||
docs.map(async (doc) => ({
|
||||
...doc,
|
||||
embedding: await generateEmbedding(`${doc.title}\n\n${doc.content}`),
|
||||
createdAt: new Date()
|
||||
}))
|
||||
);
|
||||
|
||||
await db.collection('documents').insertMany(documentsWithEmbeddings);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Vector Search Queries
|
||||
|
||||
```typescript
|
||||
// Basic Vector Search
|
||||
async function searchDocuments(query: string, limit: number = 5) {
|
||||
const client = await clientPromise;
|
||||
const db = client.db('myapp');
|
||||
|
||||
const queryEmbedding = await generateEmbedding(query);
|
||||
|
||||
const results = await db.collection('documents').aggregate([
|
||||
{
|
||||
$vectorSearch: {
|
||||
index: 'vector_index',
|
||||
path: 'embedding',
|
||||
queryVector: queryEmbedding,
|
||||
numCandidates: 100,
|
||||
limit: limit
|
||||
}
|
||||
},
|
||||
{
|
||||
$project: {
|
||||
_id: 1,
|
||||
title: 1,
|
||||
content: 1,
|
||||
category: 1,
|
||||
score: { $meta: 'vectorSearchScore' }
|
||||
}
|
||||
}
|
||||
]).toArray();
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
// Vector Search mit Filter
|
||||
async function searchByCategory(
|
||||
query: string,
|
||||
category: string,
|
||||
limit: number = 5
|
||||
) {
|
||||
const client = await clientPromise;
|
||||
const db = client.db('myapp');
|
||||
|
||||
const queryEmbedding = await generateEmbedding(query);
|
||||
|
||||
const results = await db.collection('documents').aggregate([
|
||||
{
|
||||
$vectorSearch: {
|
||||
index: 'vector_index',
|
||||
path: 'embedding',
|
||||
queryVector: queryEmbedding,
|
||||
filter: {
|
||||
category: category
|
||||
},
|
||||
numCandidates: 100,
|
||||
limit: limit
|
||||
}
|
||||
},
|
||||
{
|
||||
$project: {
|
||||
title: 1,
|
||||
content: 1,
|
||||
score: { $meta: 'vectorSearchScore' }
|
||||
}
|
||||
}
|
||||
]).toArray();
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
// Hybrid Search (Vector + Full-Text)
|
||||
async function hybridSearch(query: string, limit: number = 10) {
|
||||
const client = await clientPromise;
|
||||
const db = client.db('myapp');
|
||||
|
||||
const queryEmbedding = await generateEmbedding(query);
|
||||
|
||||
const results = await db.collection('documents').aggregate([
|
||||
{
|
||||
$vectorSearch: {
|
||||
index: 'vector_index',
|
||||
path: 'embedding',
|
||||
queryVector: queryEmbedding,
|
||||
numCandidates: 150,
|
||||
limit: 50
|
||||
}
|
||||
},
|
||||
{
|
||||
$addFields: {
|
||||
vectorScore: { $meta: 'vectorSearchScore' }
|
||||
}
|
||||
},
|
||||
{
|
||||
$unionWith: {
|
||||
coll: 'documents',
|
||||
pipeline: [
|
||||
{
|
||||
$search: {
|
||||
index: 'text_index',
|
||||
text: {
|
||||
query: query,
|
||||
path: ['title', 'content']
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
$addFields: {
|
||||
textScore: { $meta: 'searchScore' }
|
||||
}
|
||||
},
|
||||
{ $limit: 50 }
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
$group: {
|
||||
_id: '$_id',
|
||||
title: { $first: '$title' },
|
||||
content: { $first: '$content' },
|
||||
vectorScore: { $max: '$vectorScore' },
|
||||
textScore: { $max: '$textScore' }
|
||||
}
|
||||
},
|
||||
{
|
||||
$addFields: {
|
||||
combinedScore: {
|
||||
$add: [
|
||||
{ $ifNull: ['$vectorScore', 0] },
|
||||
{ $multiply: [{ $ifNull: ['$textScore', 0] }, 0.5] }
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{ $sort: { combinedScore: -1 } },
|
||||
{ $limit: limit }
|
||||
]).toArray();
|
||||
|
||||
return results;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## RAG Implementation
|
||||
|
||||
```typescript
|
||||
import OpenAI from 'openai';
|
||||
|
||||
const openai = new OpenAI();
|
||||
|
||||
async function ragQuery(userQuestion: string) {
|
||||
// 1. Relevante Dokumente finden
|
||||
const relevantDocs = await searchDocuments(userQuestion, 5);
|
||||
|
||||
// 2. Context aufbauen
|
||||
const context = relevantDocs
|
||||
.map(doc => `Title: ${doc.title}\nContent: ${doc.content}`)
|
||||
.join('\n\n---\n\n');
|
||||
|
||||
// 3. LLM mit Context aufrufen
|
||||
const response = await openai.chat.completions.create({
|
||||
model: 'gpt-4-turbo-preview',
|
||||
messages: [
|
||||
{
|
||||
role: 'system',
|
||||
content: `Du bist ein hilfreicher Assistent. Beantworte Fragen basierend auf dem folgenden Kontext. Wenn der Kontext die Antwort nicht enthält, sage das ehrlich.
|
||||
|
||||
Kontext:
|
||||
${context}`
|
||||
},
|
||||
{
|
||||
role: 'user',
|
||||
content: userQuestion
|
||||
}
|
||||
],
|
||||
temperature: 0.7,
|
||||
max_tokens: 1000
|
||||
});
|
||||
|
||||
return {
|
||||
answer: response.choices[0].message.content,
|
||||
sources: relevantDocs.map(d => ({
|
||||
title: d.title,
|
||||
score: d.score
|
||||
}))
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Automated Embedding mit Voyage AI
|
||||
|
||||
```typescript
|
||||
// MongoDB Atlas Embedding API (Preview)
|
||||
// Automatisches Embedding ohne eigene OpenAI-Calls
|
||||
|
||||
// Index Definition mit Auto-Embedding
|
||||
{
|
||||
"name": "auto_vector_index",
|
||||
"type": "vectorSearch",
|
||||
"definition": {
|
||||
"fields": [
|
||||
{
|
||||
"type": "vector",
|
||||
"path": "embedding",
|
||||
"numDimensions": 1024,
|
||||
"similarity": "cosine"
|
||||
}
|
||||
],
|
||||
"embeddingModel": {
|
||||
"name": "voyage-3",
|
||||
"inputType": "document",
|
||||
"fieldMappings": [
|
||||
{
|
||||
"source": "content",
|
||||
"target": "embedding"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Documents ohne manuelles Embedding speichern
|
||||
async function saveDocumentAutoEmbed(title: string, content: string) {
|
||||
const client = await clientPromise;
|
||||
const db = client.db('myapp');
|
||||
|
||||
// Embedding wird automatisch generiert!
|
||||
await db.collection('documents').insertOne({
|
||||
title,
|
||||
content,
|
||||
createdAt: new Date()
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Vector Quantization (Kostenoptimierung)
|
||||
|
||||
```javascript
|
||||
// Index mit Quantization für große Datasets
|
||||
{
|
||||
"name": "quantized_vector_index",
|
||||
"type": "vectorSearch",
|
||||
"definition": {
|
||||
"fields": [
|
||||
{
|
||||
"type": "vector",
|
||||
"path": "embedding",
|
||||
"numDimensions": 1536,
|
||||
"similarity": "cosine",
|
||||
"quantization": {
|
||||
"type": "scalar" // Reduziert Speicher um ~75%
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Fazit
|
||||
|
||||
MongoDB Atlas Vector Search bietet:
|
||||
|
||||
1. **Unified Platform**: Operative Daten + Vectors in einer DB
|
||||
2. **Hybrid Search**: Kombination von Vector + Full-Text
|
||||
3. **Auto-Embedding**: Voyage AI Integration für automatische Embeddings
|
||||
4. **Skalierbarkeit**: Milliarden Vectors mit Quantization
|
||||
|
||||
Ideal für AI-Features in bestehenden MongoDB-Anwendungen.
|
||||
|
||||
---
|
||||
|
||||
## Bildprompts
|
||||
|
||||
1. "Vector embeddings flowing into search results, semantic similarity visualization"
|
||||
2. "MongoDB document with vector arrows connecting similar documents"
|
||||
3. "RAG pipeline showing document retrieval and LLM generation, AI workflow"
|
||||
|
||||
---
|
||||
|
||||
## Quellen
|
||||
|
||||
- [MongoDB Atlas Vector Search](https://www.mongodb.com/products/platform/atlas-vector-search)
|
||||
- [MongoDB Vector Search Overview](https://www.mongodb.com/docs/atlas/atlas-vector-search/vector-search-overview/)
|
||||
- [MongoDB Embedding API](https://www.mongodb.com/company/blog/product-release-announcements/introducing-the-embedding-and-reranking-api-on-mongodb-atlas)
|
||||
- [Vector Databases for LLM 2026](https://www.secondtalent.com/resources/top-vector-databases-for-llm-applications/)
|
||||
Reference in New Issue
Block a user