Verdic
Verdic

Trust infrastructure for LLM applications. Deterministic guardrails for production AI systems.

Product

  • How It Works
  • Pricing
  • Documentation

Company

  • About
  • Blog
  • Contact

Legal

  • Privacy Policy
  • Terms of Service
  • Security

© 2025 Verdic. All rights reserved.

verdic.dev
Technical Deep Dive

Semantic Drift Detection: Using Vector Embeddings for LLM Validation

Learn how vector embeddings and semantic similarity measurements enable accurate drift detection in LLM outputs. Understand cosine similarity, angular distance, and advanced validation techniques.

Kundan Singh Rathore

Kundan Singh Rathore

Founder & CEO

December 10, 2024
9 min read
Embeddings
Vector Space
Semantic Similarity
ML
Validation
Semantic Drift Detection: Using Vector Embeddings for LLM Validation

Semantic Drift Detection: Using Vector Embeddings for LLM Validation

Vector embeddings transform text into high-dimensional vectors that capture semantic meaning. This enables precise drift detection by measuring how far LLM outputs deviate from intended meaning.

Understanding Vector Embeddings

Embeddings map text to points in a high-dimensional space where:

  • Similar meanings are close together
  • Different meanings are far apart
  • Semantic relationships are preserved

Example: "dog" and "puppy" are close; "dog" and "airplane" are far.

Cosine Similarity

The most common similarity measure:

function cosineSimilarity(vecA: number[], vecB: number[]): number {
  const dotProduct = vecA.reduce((sum, a, i) => sum + a * vecB[i], 0)
  const magnitudeA = Math.sqrt(vecA.reduce((sum, a) => sum + a * a, 0))
  const magnitudeB = Math.sqrt(vecB.reduce((sum, b) => sum + b * b, 0))
  
  return dotProduct / (magnitudeA * magnitudeB)
  // Returns value between -1 and 1
  // 1 = identical, 0 = orthogonal, -1 = opposite
}

Angular Distance

More intuitive than cosine similarity:

function angularDistance(vecA: number[], vecB: number[]): number {
  const similarity = cosineSimilarity(vecA, vecB)
  const angle = Math.acos(Math.max(-1, Math.min(1, similarity)))
  return angle // Returns radians (0 to π)
}

Interpretation:

  • 0 radians (0°) = Identical meaning
  • π/6 radians (30°) = Very similar
  • π/3 radians (60°) = Related but different
  • π/2 radians (90°) = Orthogonal (unrelated)
  • π radians (180°) = Opposite meaning

Drift Detection Workflow

Step 1: Generate Embeddings

import { OpenAIEmbeddingProvider } from '@verdic/sdk'

const embedder = new OpenAIEmbeddingProvider(apiKey)

// Embed the global intent (once, cache it)
const intentEmbedding = await embedder.embed(
  "Software development and code generation assistance"
)

// Embed each output (or batch for efficiency)
const outputEmbedding = await embedder.embed(llmOutput)

Step 2: Calculate Distance

function calculateDrift(
  intentEmbedding: number[],
  outputEmbedding: number[]
): number {
  // Calculate angular distance
  const angle = angularDistance(intentEmbedding, outputEmbedding)
  
  // Convert to drift score (0-1, higher = more drift)
  const driftScore = angle / Math.PI
  
  return driftScore
}

Step 3: Apply Threshold

const driftScore = calculateDrift(intentEmbedding, outputEmbedding)
const threshold = 0.76 // Configurable per use case

if (driftScore > threshold) {
  // Drift detected - block or warn
  return { decision: "BLOCK", driftScore, threshold }
} else {
  // Within acceptable range
  return { decision: "ALLOW", driftScore, threshold }
}

Threshold Selection

Choosing the right threshold is critical:

Too Strict (Low Threshold)

  • Blocks legitimate variations
  • High false positive rate
  • Poor user experience

Too Loose (High Threshold)

  • Allows drift and violations
  • High false negative rate
  • Safety/compliance issues

Recommended Thresholds

| Use Case | Threshold | Rationale | |----------|-----------|-----------| | Customer Support | 0.70-0.75 | Balance flexibility with consistency | | Code Generation | 0.75-0.80 | Strict to prevent off-topic code | | Content Moderation | 0.65-0.70 | Strict to catch violations | | Creative Writing | 0.80-0.85 | Allow more creative variation |

Advanced Techniques

Rotation-Based Detection

Measure not just distance, but rotation:

// Rotation angle indicates direction of drift
const rotationAngle = calculateRotation(intentEmbedding, outputEmbedding)

// High rotation = significant semantic shift
if (rotationAngle > Math.PI / 4) { // 45 degrees
  // Major drift detected
}

Multi-Intent Validation

Compare against multiple reference intents:

const intents = [
  "customer support",
  "technical documentation",
  "product information"
]

const distances = await Promise.all(
  intents.map(intent => 
    calculateDrift(
      await embedder.embed(intent),
      outputEmbedding
    )
  )
)

// Output must be close to at least one intent
const minDistance = Math.min(...distances)
if (minDistance > threshold) {
  // Doesn't match any intent
  return { decision: "BLOCK" }
}

Batch Processing

Process multiple outputs efficiently:

// Batch embedding generation
const outputs = ["output1", "output2", "output3"]
const embeddings = await embedder.embedBatch(outputs)

// Compare all against intent
const driftScores = embeddings.map(emb => 
  calculateDrift(intentEmbedding, emb)
)

Performance Optimization

Caching Embeddings

Cache intent embeddings (they don't change):

const embeddingCache = new Map()

async function getCachedEmbedding(text: string): Promise<number[]> {
  if (embeddingCache.has(text)) {
    return embeddingCache.get(text)
  }
  
  const embedding = await embedder.embed(text)
  embeddingCache.set(text, embedding)
  
  return embedding
}

Approximate Similarity

For high-throughput systems, use approximate methods:

// Use dimensionality reduction for faster comparison
import { PCA } from 'ml-pca'

const pca = new PCA(embeddings)
const reduced = pca.predict(embedding) // Reduce from 1536 to 128 dims

// Faster comparison on reduced dimensions

Limitations and Considerations

Embedding Quality

Not all embeddings are equal:

  • OpenAI text-embedding-3-small: 1536 dims, high quality
  • Custom embeddings: May need fine-tuning
  • Local embeddings: Trade quality for privacy

Language Support

Most embeddings work best for English:

  • Non-English text may have lower accuracy
  • Consider language-specific models
  • Multilingual embeddings available but less precise

Context Length

Embeddings have context limits:

  • OpenAI: 8192 tokens
  • Longer texts need chunking
  • Consider average pooling for long texts

Real-World Example

import { verdic } from '@verdic/sdk'

async function validateOutput(output: string, intent: string) {
  const validation = await verdic.validate({
    projectId: "my-project",
    output: output,
    config: {
      globalIntent: intent,
      threshold: 0.76,
      enableV5: true, // Enable semantic drift detection
      enableV8: true  // Enable rotation-based detection
    }
  })
  
  console.log(`Drift Score: ${validation.drift}`)
  console.log(`Decision: ${validation.decision}`)
  console.log(`Rotation Angle: ${validation.rotationMetrics?.angle}`)
  
  return validation
}

// Usage
const result = await validateOutput(
  "Here's how to implement authentication in React...",
  "Software development and code generation assistance"
)

// Result: { decision: "ALLOW", drift: 0.15, ... }

Conclusion

Vector embeddings provide a powerful foundation for semantic drift detection. By measuring angular distance between intent and output embeddings, you can accurately detect when LLM outputs deviate from expected meaning.

Combined with multi-dimensional analysis and proper threshold tuning, this approach enables production-ready LLM validation that balances accuracy with performance.

Ready to Build Safer AI?

Get your API key and start implementing enterprise-grade guardrails in minutes.