Skip to main content

Understanding Vector Search in LanOnasis

Vector search enables semantic understanding of your queries, finding relevant memories based on meaning rather than exact keyword matches.

How It Works

graph LR
A[Your Text] --> B[Embedding Model]
B --> C[Vector<br/>1536 dimensions]
C --> D[Vector Database]
D --> E[Similarity Search]
E --> F[Relevant Memories]

Implementation Example

Step 1: Store Memories with Semantic Context

// These will be semantically related despite different wording
await client.upsert({
text: "The quarterly revenue exceeded projections by 15%"
})

await client.upsert({
text: "Q4 financial performance surpassed expectations"
})

await client.upsert({
text: "Sales figures came in higher than anticipated"
})

Step 2: Search Semantically

// This query will find all three memories above
const results = await client.search({
query: "How did we do financially?",
topK: 10
})

// Results ranked by semantic similarity
results.forEach(result => {
console.log(`Score: ${result.score} - ${result.memory.text}`)
})

Advanced Vector Search Techniques

1. Hybrid Search (Semantic + Keyword)

const hybridResults = await client.hybridSearch({
query: "Q4 revenue projections",
semanticWeight: 0.7, // 70% semantic
keywordWeight: 0.3, // 30% keyword matching
topK: 10
})
const filteredResults = await client.search({
query: "financial performance",
filters: {
type: "financial_report",
year: 2024,
department: { $in: ["sales", "marketing"] }
},
topK: 5
})
// Search using multiple query vectors
const multiResults = await client.multiVectorSearch({
queries: [
"revenue growth",
"customer acquisition",
"market expansion"
],
aggregation: "mean", // or "max", "min"
topK: 10
})

Optimizing Search Quality

1. Metadata Enhancement

// Rich metadata improves filtering and ranking
await client.upsert({
text: "Q4 revenue was $2.5M, up 15% YoY",
metadata: {
type: "financial",
quarter: "Q4",
year: 2024,
metrics: {
revenue: 2500000,
growth_rate: 0.15
},
tags: ["revenue", "growth", "financial"],
department: "finance",
confidence: 0.95
}
})

2. Query Expansion

// Automatically expand queries for better recall
const expandedResults = await client.search({
query: "revenue",
queryExpansion: {
enabled: true,
synonyms: ["income", "earnings", "sales"],
related_terms: ["profit", "growth", "performance"]
}
})

3. Re-ranking

// Re-rank results using additional signals
const rerankedResults = await client.searchWithReranking({
query: "important decisions",
topK: 20, // Get more candidates
rerank: {
enabled: true,
model: "cross-encoder",
topK: 5, // Return top 5 after reranking
factors: {
recency: 0.2,
user_relevance: 0.3,
semantic_similarity: 0.5
}
}
})

Performance Considerations

Embedding Cache

// Cache frequently used embeddings
const cachedClient = new MemoryClient({
apiKey: process.env.LANONASIS_API_KEY,
cache: {
enabled: true,
ttl: 3600, // 1 hour
maxSize: 1000 // Cache up to 1000 embeddings
}
})

Batch Processing

// Process multiple searches efficiently
const batchResults = await client.batchSearch([
{ query: "Q4 planning", topK: 5 },
{ query: "budget decisions", topK: 5 },
{ query: "team updates", topK: 5 }
])

Troubleshooting Common Issues

Low Relevance Scores

Problem: Search results have consistently low scores (< 0.5)

Solutions:

  1. Ensure sufficient text content (minimum 20 words)
  2. Add more context to stored memories
  3. Use more specific search queries
  4. Consider adjusting similarity threshold
// Diagnostic query to check embedding quality
const diagnostic = await client.diagnoseSearch({
query: "your search query",
includeEmbedding: true,
explainScores: true
})

console.log(diagnostic.explanation)

Missing Expected Results

Problem: Known memories aren't appearing in search

Solutions:

// 1. Verify memory exists
const memory = await client.get("memory-id")

// 2. Check if embedding was generated
if (!memory.embedding) {
await client.refreshEmbedding("memory-id")
}

// 3. Use lower similarity threshold
const results = await client.search({
query: "your query",
threshold: 0.3, // Lower threshold
topK: 20 // Get more results
})

Best Practices

  1. Chunk Long Documents: Split documents > 1000 words for better search precision
  2. Use Descriptive Text: Include context and details in stored memories
  3. Leverage Metadata: Use structured metadata for filtering and organization
  4. Monitor Performance: Track search latency and relevance metrics
  5. Regular Reindexing: Refresh embeddings when updating the embedding model

Example: Building a Knowledge Base

class KnowledgeBase {
private client: MemoryClient

async addDocument(doc: Document) {
// Chunk document into sections
const chunks = this.chunkDocument(doc)

// Store each chunk with metadata
const memories = await this.client.batchUpsert(
chunks.map((chunk, index) => ({
text: chunk.text,
metadata: {
documentId: doc.id,
documentTitle: doc.title,
section: chunk.section,
chunkIndex: index,
totalChunks: chunks.length,
author: doc.author,
createdAt: doc.createdAt,
tags: doc.tags
}
}))
)

return memories
}

async searchKnowledge(query: string, options?: SearchOptions) {
// Search with smart defaults
const results = await this.client.search({
query,
topK: options?.topK || 10,
filters: options?.filters,
includeMetadata: true
})

// Group results by document
const grouped = this.groupByDocument(results)

// Return formatted response
return grouped.map(doc => ({
documentId: doc.id,
title: doc.title,
relevantSections: doc.chunks,
overallScore: doc.maxScore
}))
}
}