# Vector Search Explained

## Content: Vector Search Explained

### User Intent

"How does semantic/vector search work in Graphlit?"

### Operation

* **SDK Method**: `queryContents()` with `searchType: SearchVector`
* **GraphQL**: `queryContents` query
* **Common Use Cases**: Semantic similarity, concept search, "find similar documents"

### How Vector Search Works

Vector search finds content based on semantic meaning, not just keyword matches. Content is converted to high-dimensional vectors (embeddings), and similarity is measured by distance between vectors.

### TypeScript (Canonical)

```typescript
import { Graphlit } from 'graphlit-client';
import { ContentTypes, FileTypes, ModelServiceTypes, SearchTypes, SpecificationTypes } from 'graphlit-client/dist/generated/graphql-types';

const graphlit = new Graphlit();

// Pure vector search
const results = await graphlit.queryContents({
  search: "machine learning research papers",
  searchType: SearchTypes.Vector,
  limit: 10
});

console.log(`Found ${results.contents.results.length} semantically similar results`);

results.contents.results.forEach((content, index) => {
  console.log(`\n${index + 1}. ${content.name}`);
  console.log(`   Relevance: ${content.relevance}`);
  console.log(`   Type: ${content.type}`);
  
  // Show matching text chunk
  if (content.pages && content.pages.length > 0) {
    const topChunk = content.pages[0].chunks?.[0];
    if (topChunk) {
      console.log(`   Match: "${topChunk.text.substring(0, 100)}..."`);
    }
  }
});

// Find similar documents to a specific one
const similar = await graphlit.queryContents({
  similarContents: [{ id: 'content-id' }],
  limit: 5
});

console.log(`\nDocuments similar to content-id:`);
similar.contents.results.forEach(content => {
  console.log(`- ${content.name} (relevance: ${content.relevance})`);
});
```

### The Vector Search Pipeline

#### 1. Ingestion Time (Embedding Creation)

```typescript
// When content is ingested:
const content = await graphlit.ingestUri(
  'https://example.com/document.pdf',
  undefined,
  undefined,
  undefined,
  undefined,
  { id: 'workflow-id' }
);

// Behind the scenes:
// 1. Document is chunked (default: 512 tokens per chunk)
// 2. Each chunk → embedding model (e.g., text-embedding-3-large)
// 3. Model returns 3072-dimensional vector per chunk
// 4. Vectors stored in Azure AI Search
// 5. Ready for similarity search
```

#### 2. Query Time (Semantic Search)

```typescript
// User searches:
const results = await graphlit.queryContents({
  search: "climate change impact on agriculture",
  searchType: SearchTypes.Vector
});

// Behind the scenes:
// 1. Query text → embedding model (same model as ingestion)
// 2. Get query vector (3072 dimensions)
// 3. Cosine similarity against all content vectors
// 4. Return top-K most similar chunks
// 5. Aggregate by parent content
// 6. Rank and return results
```

### When to Use Vector Search

\*\* Good For\*\*:

* Conceptual queries ("AI safety concerns")
* Semantic similarity ("find similar documents")
* Cross-language concepts
* Synonyms and paraphrases
* Question answering
* Vague or exploratory queries

\*\* Not Good For\*\*:

* Exact phrases ("Project Alpha v2.3")
* Names and IDs ("PROJ-1234")
* Codes and identifiers
* Very short queries (< 3 words)
* Spelling-sensitive searches

### Examples: Vector vs Keyword

```typescript
// Vector search finds semantic meaning
const vector = await graphlit.queryContents({
  search: "reducing carbon emissions",
  searchType: SearchTypes.Vector
});
// Matches: "lowering CO2 output", "decreasing greenhouse gases",
//          "climate change mitigation", "sustainability efforts"

// Keyword search finds exact tokens
const keyword = await graphlit.queryContents({
  search: "reducing carbon emissions",
  searchType: SearchTypes.Keyword
});
// Matches: Only documents with words "reducing", "carbon", "emissions"
```

### Embedding Models

**Current Default**: OpenAI `text-embedding-3-large`

* **Dimensions**: 3072
* **Quality**: Highest
* **Cost**: Higher
* **Use**: Best results, production recommended

**Alternative**: OpenAI `text-embedding-3-small`

* **Dimensions**: 1536
* **Quality**: Good
* **Cost**: Lower
* **Use**: Cost-sensitive applications

**Legacy**: OpenAI `text-embedding-ada-002`

* **Dimensions**: 1536
* **Quality**: Baseline
* **Cost**: Lower
* **Use**: Not recommended for new projects

**Configure Embedding Model**:

```typescript
// Create embedding specification
const embeddingSpec = await graphlit.createSpecification({
  name: "High Quality Embeddings",
  type: SpecificationTypes.TextEmbedding,
  serviceType: ModelServiceTypes.OpenAi,
  openAI: {
    model: OpenAiModels.Embedding_3Large,
    chunkTokenLimit: 512  // Tokens per chunk
  }
});

// Assign as the project default text embedding strategy
await graphlit.updateProject({
  embeddings: {
    textSpecification: { id: embeddingSpec.createSpecification.id },
  },
});
```

### Similarity Scoring

**Relevance Score** (`content.relevance`):

* Range: 0.0 (no match) to 1.0 (perfect match)
* Based on cosine similarity
* Higher = more semantically similar
* Typically use threshold (e.g., > 0.7)

```typescript
const results = await graphlit.queryContents({
  search: "query",
  searchType: SearchTypes.Vector
});

// Filter by relevance
const highRelevance = results.contents.results.filter(
  content => content.relevance > 0.7
);

console.log(`High relevance matches: ${highRelevance.length}`);
```

## Vector search (snake\_case)

results = await graphlit.client.query\_contents( search="machine learning research", search\_type=SearchTypes.Vector, limit=10 )

for content in results.contents.results: print(f"{content.name} - Relevance: {content.relevance}")

## Find similar documents

similar = await graphlit.client.query\_contents( filter=ContentFilterInput( similar\_contents=\[ EntityReferenceInput(id='content-id') ] ) )

````

**C#**:
```csharp
using Graphlit;

var client = new Graphlit();

// Vector search (PascalCase)
var results = await graphlit.QueryContents(new ContentFilter
{
    Search = "machine learning research",
    SearchType = SearchVector,
    Limit = 10
});

foreach (var content in results.Contents.Results)
{
    Console.WriteLine($"{content.Name} - Relevance: {content.Relevance}");
}

// Find similar documents
var similar = await graphlit.QueryContents(new ContentFilter
{
    SimilarContents = new[]
    {
        new EntityReference { Id = "content-id" }
    }
});
````

### Developer Hints

#### Vector Search is Expensive

```typescript
// Each query:
// 1. Embeds query text (LLM API call)
// 2. Compares against millions of vectors
// 3. Aggregates and ranks results

// Cost factors:
// - Embedding API calls
// - Vector index size
// - Query frequency

// Optimization:
// - Cache query embeddings for common queries
// - Use hybrid search (better results, similar cost)
// - Limit result count
```

#### Chunk Size Matters

```typescript
// Small chunks (256 tokens):
// - More precise matching
// - More chunks = more embeddings = higher cost
// - Better for specific queries

// Large chunks (1024 tokens):
// - More context per chunk
// - Fewer chunks = lower cost
// - Better for broad queries

// Default (512 tokens):
// - Balanced approach
// - Recommended for most use cases
```

#### Query Quality Tips

```typescript
// ✓ Good queries (natural language)
"How does machine learning improve healthcare?"
"Impact of remote work on productivity"
"Best practices for API security"

// ✗ Poor queries (too short/vague)
"AI"
"docs"
"help"

// ✓ Better versions
"Artificial intelligence applications"
"Documentation about features"
"Help with authentication setup"
```

### Variations

#### 1. Basic Vector Search

```typescript
const results = await graphlit.queryContents({
  search: "quantum computing applications",
  searchType: SearchTypes.Vector
});
```

#### 2. Vector Search with Filters

```typescript
// Combine semantic search with metadata filters
const filtered = await graphlit.queryContents({
  search: "machine learning",
  searchType: SearchTypes.Vector,
  
    types: [ContentTypes.File],
    fileTypes: [FileTypes.Document],
    creationDateRange: {
      from: '2024-01-01'
    }
  });
```

#### 3. Find Similar Documents

```typescript
// "More like this" functionality
const similar = await graphlit.queryContents({
  
    similarContents: [{ id: 'original-content-id' }]
  
  limit: 10
});
```

#### 4. Vector Search with Relevance Threshold

```typescript
const results = await graphlit.queryContents({
  search: "query",
  searchType: SearchTypes.Vector,
  limit: 50
});

// Client-side filtering by relevance
const relevant = results.contents.results.filter(
  content => content.relevance >= 0.75
);
```

#### 5. Multi-Collection Vector Search

```typescript
// Search across specific collections
const results = await graphlit.queryContents({
  search: "product roadmap",
  searchType: SearchTypes.Vector,
  
    collections: [
      { id: 'engineering-docs' },
      { id: 'product-docs' }
    ]
  });
```

#### 6. Vector Search Pagination

```typescript
// First page
const page1 = await graphlit.queryContents({
  search: "query",
  searchType: SearchTypes.Vector,
  limit: 20,
  offset: 0
});

// Second page
const page2 = await graphlit.queryContents({
  search: "query",
  searchType: SearchTypes.Vector,
  limit: 20,
  offset: 20
});
```

### Common Issues & Solutions

**Issue**: No results returned **Solution**: Query might be too specific or embeddings not created

```typescript
// Check if content has embeddings
const content = await graphlit.getContent('content-id');
if (!content.content.pages || content.content.pages.length === 0) {
  console.log('Content not embedded yet');
}

// Try broader query
const results = await graphlit.queryContents({
  search: "machine learning",  // Broader
  searchType: SearchTypes.Vector
});
```

**Issue**: Results not semantically relevant **Solution**: Try hybrid search or keyword search

```typescript
// Hybrid often works better
const results = await graphlit.queryContents({
  search: "query",
  searchType: SearchTypes.Hybrid  // Default and recommended
});
```

**Issue**: Want to change embedding model for existing content **Solution**: Must re-ingest content with new specification

```typescript
// Create new specification
const newSpec = await graphlit.createSpecification({
  type: SpecificationTypes.TextEmbedding,
  serviceType: ModelServiceTypes.OpenAi,
  openAI: {
    model: OpenAiModels.Embedding_3Small  // Smaller, cheaper
  }
});

await graphlit.updateProject({
  embeddings: {
    textSpecification: { id: newSpec.createSpecification.id },
  },
});

// Re-ingest content with new spec
// (embeddings are immutable once created)
```

**Issue**: Slow query performance **Solution**: Vector search is computationally expensive

```typescript
// Optimizations:
// 1. Use filters to reduce search space
const results = await graphlit.queryContents({
  search: "query",
  searchType: SearchTypes.Vector,
  
    collections: [{ id: 'small-collection' }]  // Narrow scope
  });

// 2. Reduce limit
const results = await graphlit.queryContents({
  search: "query",
  searchType: SearchTypes.Vector,
  limit: 10  // Fewer results = faster
});

// 3. Consider hybrid search (often faster)
const results = await graphlit.queryContents({
  search: "query",
  searchType: SearchTypes.Hybrid
});
```

### Production Example

```typescript
async function semanticSearch(query: string) {
  console.log(`\n=== SEMANTIC SEARCH ===`);
  console.log(`Query: "${query}"`);
  
  const startTime = Date.now();
  
  const results = await graphlit.queryContents({
    search: query,
    searchType: SearchTypes.Vector,
    limit: 10
  });
  
  const elapsed = Date.now() - startTime;
  
  console.log(`\nFound ${results.contents.results.length} results in ${elapsed}ms`);
  
  results.contents.results.forEach((content, index) => {
    console.log(`\n${index + 1}. ${content.name}`);
    console.log(`   Relevance: ${(content.relevance * 100).toFixed(1)}%`);
    console.log(`   Type: ${content.type}`);
    console.log(`   Created: ${new Date(content.creationDate).toLocaleDateString()}`);
    
    // Show best matching chunk
    if (content.pages && content.pages.length > 0) {
      const topPage = content.pages.sort((a, b) => 
        (b.relevance || 0) - (a.relevance || 0)
      )[0];
      
      if (topPage.chunks && topPage.chunks.length > 0) {
        const topChunk = topPage.chunks.sort((a, b) =>
          (b.relevance || 0) - (a.relevance || 0)
        )[0];
        
        console.log(`   Matching text: "${topChunk.text.substring(0, 150)}..."`);
      }
    }
  });
  
  // Relevance distribution
  const highRelevance = results.contents.results.filter(c => c.relevance >= 0.8).length;
  const mediumRelevance = results.contents.results.filter(c => c.relevance >= 0.6 && c.relevance < 0.8).length;
  const lowRelevance = results.contents.results.filter(c => c.relevance < 0.6).length;
  
  console.log(`\n Relevance Distribution:`);
  console.log(`   High (≥80%): ${highRelevance}`);
  console.log(`   Medium (60-80%): ${mediumRelevance}`);
  console.log(`   Low (<60%): ${lowRelevance}`);
}

// Usage
await semanticSearch("impact of artificial intelligence on healthcare");
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.graphlit.dev/api-guides/use-cases/content/content-search-vector-explained.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
