Design a Perplexity-Style AI Search Engine: The Complete Guide
A Senior+ Guide
1. Introduction
The search engine landscape has undergone a seismic transformation over the past several years. Traditional search engines like Google, Bing, and Yahoo have long served as the primary gateway to the internet, returning lists of blue links that users must manually scan, click, and synthesize. This model, while revolutionary in the late 1990s, has become increasingly inadequate for users who want direct, comprehensive answers rather than a scavenger hunt through dozens of web pages.
Perplexity AI emerged as a disruptor in this space by fundamentally reimagining what a search engine can be. Instead of returning ranked lists of links, Perplexity reads and synthesizes information from multiple sources, then presents a coherent, well-cited answer in natural language. Users receive the answer they seek along with precise inline citations that link directly to the source material. This approach has attracted millions of daily users and billions of dollars in valuation, proving that there is enormous demand for AI-native search experiences.
In this comprehensive guide, we will design a Perplexity-style AI search engine from the ground up. We will cover every critical subsystem — from web crawling and indexing, through Retrieval-Augmented Generation (RAG) pipelines, to citation verification and answer quality evaluation. We will examine the distributed systems challenges involved in serving millions of concurrent users with sub-second latency while maintaining answer accuracy and source integrity.
This guide is written for senior engineers and architects who need to understand not just the high-level concepts but also the implementation details: data models, API contracts, capacity planning, cost estimation, and operational considerations. We will include C# code examples throughout, along with Mermaid architecture diagrams, comparison tables, and interview preparation questions.
2. The AI Search Revolution
The transition from traditional keyword-based search to AI-powered answer engines represents one of the most significant shifts in information retrieval since the invention of the web search engine itself. Understanding this revolution requires examining several converging technological trends that have collectively made AI search both possible and practical.
The Limitations of Traditional Search
Traditional search engines operate on a fundamentally simple model: match user keywords against an inverted index of web pages, rank results using hundreds of signals (PageRank, freshness, click-through rate, dwell time), and present a ranked list. While this approach has served billions of users, it suffers from several structural limitations.
- Information synthesis burden: Users must manually read and combine information from multiple sources to answer complex questions.
- Keyword mismatch: Users often struggle to formulate the right keywords, and the search engine cannot understand the underlying intent.
- No follow-up capability: Traditional search treats every query as independent, ignoring conversational context.
- Ad-driven incentives: The advertising business model incentivizes showing more links rather than providing definitive answers.
- Spam and SEO manipulation: Rank-based systems are vulnerable to search engine optimization techniques that degrade result quality.
The Foundation: Large Language Models
The breakthrough enabling AI search is the combination of several LLM capabilities that matured rapidly between 2022 and 2025. Modern large language models can comprehend complex questions, synthesize information from retrieved passages, generate fluent natural language responses, and produce inline citations that reference specific source documents. These models have been trained on vast corpora of text data and fine-tuned for instruction following, making them capable of producing answers that are both accurate and readable.
The key insight that made AI search practical was Retrieval-Augmented Generation (RAG), a technique that grounds LLM outputs in real-time retrieved documents rather than relying solely on the model's parametric memory. RAG dramatically reduces hallucination and enables the system to provide up-to-date information with verifiable source citations.
The Competitive Landscape
| Product | Approach | Key Differentiator | Year Launched |
|---|---|---|---|
| Perplexity AI | RAG + LLM | Citations, conversational follow-up, Pro Search | 2022 |
| Google SGE / AI Overview | PaLM + Search | Integration with Google Search index | 2023 |
| Bing Chat / Copilot | GPT-4 + Bing | Microsoft/OpenAI partnership | 2023 |
| You.com | Custom LLM + Search | Developer-focused API | 2023 |
| Kagi | AI + traditional | Subscription-based, no ads | 2023 |
| Phind | Code-focused LLM | Developer-oriented AI search | 2023 |
3. System Requirements & Scope
Before diving into architecture, we must clearly define the functional and non-functional requirements. An AI search engine has a unique set of constraints that differ significantly from traditional web search or standalone LLM applications.
Functional Requirements
- Query processing: Accept natural language queries and return comprehensive, synthesized answers with inline citations.
- Real-time web search: Search the web in real-time to retrieve current and relevant source documents for every query.
- Citation generation: Provide numbered inline citations that link to the specific source documents used to generate each claim.
- Conversational follow-up: Support multi-turn conversations where users can ask clarifying questions and refine their search.
- Source transparency: Display the full list of sources used, including titles, URLs, snippets, and publication dates.
- Thread management: Allow users to create, share, and revisit search threads.
- Focus modes: Support different search scopes — All, Academic, YouTube, Reddit, and other specialized sources.
- Image and file analysis: Allow users to upload images and documents for analysis as part of their queries.
- Pro Search: Offer a deeper, multi-step reasoning mode for complex queries that requires more research and computation.
Non-Functional Requirements
| Requirement | Target | Rationale |
|---|---|---|
| Answer latency (P50) | < 2 seconds | Users expect near-instant answers |
| Answer latency (P99) | < 8 seconds | Complex queries may require deeper research |
| Citation accuracy | > 95% | Citations must be verifiable and relevant |
| Hallucination rate | < 2% | Grounded answers must not fabricate facts |
| Availability | 99.9% | Search is a critical daily tool for many users |
| Concurrent users | 10M+ daily active | Scale target for a competitive product |
| Query throughput | 50K QPS peak | Peak traffic during major news events |
| Index freshness | < 5 minutes for breaking news | Recency is a core value proposition |
| Data retention | 90 days threads, indefinite indexed | Balance storage cost with user experience |
Design Constraints
The system must operate within several critical constraints. LLM inference is the primary cost driver, so we must implement aggressive caching, batching, and model selection strategies. The citation pipeline must be reliable because incorrect citations destroy user trust faster than any other failure mode. The system must handle adversarial queries that attempt to produce harmful outputs while maintaining utility for legitimate use cases. And the architecture must support both free-tier users with basic search and paid users who expect deeper, more comprehensive analysis.
4. Capacity Estimation
Accurate capacity estimation is critical for an AI search engine because the cost structure is fundamentally different from traditional web applications. Each query triggers not just database lookups but also embedding generation, vector search, LLM inference, and potentially web crawling — operations that are orders of magnitude more expensive than serving static content.
Query Volume Projections
| Metric | Value | Calculation |
|---|---|---|
| Daily Active Users | 10,000,000 | Growth target for Year 2 |
| Queries per user per day | 5 | Average across free and paid |
| Total daily queries | 50,000,000 | 10M × 5 |
| Average QPS | ~580 | 50M / 86,400 seconds |
| Peak QPS (3× average) | ~1,750 | US morning peak concentration |
| Burst QPS (10× average) | ~5,800 | Breaking news events |
Storage Estimation
| Storage Type | Size per Query | Daily Total | Annual Total |
|---|---|---|---|
| Raw query logs | 2 KB | 100 GB | 36 TB |
| Retrieved documents | 50 KB | 2.5 TB | 912 TB |
| Embedding vectors | 4 KB | 200 GB | 73 TB |
| Generated answers | 8 KB | 400 GB | 146 TB |
| Thread history | 10 KB | 500 GB | 182 TB |
| User profiles | 1 KB | 10 GB | 3.6 TB |
Compute and Network
LLM inference is the dominant compute cost. Assuming an average answer requires processing approximately 4,000 input tokens and generating 800 output tokens, we need to estimate GPU requirements. A single NVIDIA A100 GPU can serve roughly 15-20 requests per second for a 7B parameter model using optimized inference. For 5,800 burst QPS, we need approximately 300-400 GPUs just for the generation step, plus additional GPUs for embedding computation and reranking.
Network bandwidth for web crawling must also be factored in. Crawling millions of pages per hour at an average of 500 KB per page requires approximately 2 Gbps of sustained bandwidth just for the crawl infrastructure, with additional bandwidth for the index update pipeline.
5. Data Model
The data model for an AI search engine is substantially more complex than a traditional search engine because we must track not just documents and indices but also conversation threads, generated answers, citations, user preferences, and feedback signals. The following data model captures the essential entities and their relationships.
Core Entities
public class IndexedDocument
{
public string DocumentId { get; set; } // SHA-256 of canonical URL
public string Url { get; set; }
public string Domain { get; set; }
public string Title { get; set; }
public string Content { get; set; } // Full extracted text
public string Summary { get; set; } // LLM-generated summary
public List<float> Embedding { get; set; } // 1536-dim vector
public List<string> Chunks { get; set; } // Split into chunks
public List<List<float>> ChunkEmbeddings { get; set; }
public DateTime CrawledAt { get; set; }
public DateTime ContentUpdatedAt { get; set; }
public float DomainAuthority { get; set; }
public string ContentType { get; set; } // article, forum, docs, etc.
public DocumentMetadata Metadata { get; set; }
public bool IsIndexed { get; set; }
public long Version { get; set; }
}
public class DocumentMetadata
{
public string Author { get; set; }
public DateTime? PublishedDate { get; set; }
public string Language { get; set; }
public List<string> Topics { get; set; }
public int WordCount { get; set; }
public float ReadabilityScore { get; set; }
public List<string> OutboundLinks { get; set; }
public int InboundLinkCount { get; set; }
}
Conversation and Answer Model
public class SearchThread
{
public string ThreadId { get; set; } // UUID
public string UserId { get; set; }
public string Title { get; set; } // Auto-generated
public List<SearchTurn> Turns { get; set; }
public string FocusMode { get; set; } // all, academic, youtube, etc.
public string ProSearchLevel { get; set; } // none, standard, deep
public DateTime CreatedAt { get; set; }
public DateTime UpdatedAt { get; set; }
public bool IsPublic { get; set; }
public int ViewCount { get; set; }
}
public class SearchTurn
{
public string TurnId { get; set; }
public string Query { get; set; }
public string RewrittenQuery { get; set; } // After intent classification
public List<RetrievedSource> Sources { get; set; }
public GeneratedAnswer Answer { get; set; }
public List<Citation> Citations { get; set; }
public List<FollowUpSuggestion> FollowUps { get; set; }
public QueryClassification Classification { get; set; }
public UserFeedback Feedback { get; set; }
public TimeSpan LatencyMs { get; set; }
public DateTime TimestampedAt { get; set; }
}
public class GeneratedAnswer
{
public string AnswerId { get; set; }
public string Content { get; set; } // Markdown formatted
public string ModelUsed { get; set; } // e.g., "sonar-large"
public int InputTokens { get; set; }
public int OutputTokens { get; set; }
public List<Citation> Citations { get; set; }
public AnswerQualityScores Quality { get; set; }
public bool IsStreamed { get; set; }
public TimeSpan GenerationTimeMs { get; set; }
}
public class Citation
{
public int CitationNumber { get; set; } // [1], [2], etc.
public string SourceDocumentId { get; set; }
public string Url { get; set; }
public string Title { get; set; }
public string Snippet { get; set; } // Excerpt used
public string Domain { get; set; }
public DateTime? PublishedDate { get; set; }
public float RelevanceScore { get; set; }
public bool IsVerified { get; set; }
public DateTime? VerifiedAt { get; set; }
}
Schema Relationships
6. High-Level Architecture
The architecture of a Perplexity-style AI search engine consists of several interconnected subsystems that work together to process a user query end-to-end. The system can be decomposed into four major layers: the ingestion layer (crawling, parsing, and indexing), the retrieval layer (query understanding, vector search, and reranking), the generation layer (RAG pipeline and answer synthesis), and the serving layer (API gateway, streaming, and caching).
Intent Classification] QueryRewrite[Query Rewriting
& Expansion] VectorSearch[Vector Search
pgvector / Qdrant] BM25Search[BM25 Search
Elasticsearch] HybridFusion[Hybrid Fusion
Reciprocal Rank] Reranker[Neural Reranker
Cross-Encoder] end subgraph Generation Layer ContextBuilder[Context Builder] CitationMapper[Citation Mapper] LLMInference[LLM Inference
Sonar / GPT-4] StreamProcessor[Stream Processor
SSE / WebSocket] QualityChecker[Quality Checker
Hallucination Detection] end subgraph Ingestion Layer Crawler[Web Crawler
Distributed Workers] Parser[HTML Parser
Content Extractor] Chunker[Document Chunker] Embedder[Embedding Service
text-embedding-3-large] IndexWriter[Index Writer] end subgraph Data Layer VectorDB[(Vector DB
pgvector)] DocStore[(Document Store
PostgreSQL)] Cache[(Cache Layer
Redis Cluster)] SearchIndex[(Search Index
Elasticsearch)] KG[(Knowledge Graph
Neo4j)] ObjectStore[(Object Store
S3 / R2)] end WebApp --> LB MobileApp --> LB API --> LB LB --> Auth --> RateLimit --> QueryRouter QueryRouter --> QueryUnderstanding QueryUnderstanding --> QueryRewrite QueryRewrite --> VectorSearch QueryRewrite --> BM25Search VectorSearch --> HybridFusion BM25Search --> HybridFusion HybridFusion --> Reranker Reranker --> ContextBuilder ContextBuilder --> CitationMapper CitationMapper --> LLMInference LLMInference --> StreamProcessor StreamProcessor --> QualityChecker Crawler --> Parser --> Chunker --> Embedder --> IndexWriter IndexWriter --> VectorDB IndexWriter --> DocStore IndexWriter --> SearchIndex QueryRouter --> Cache ContextBuilder --> KG
Request Flow Walkthrough
When a user submits a query like "What are the latest developments in quantum computing?", the following sequence unfolds. First, the query passes through the API gateway where authentication, rate limiting, and routing occur. The Query Understanding module classifies the intent as informational, determines the required depth (quick answer vs. deep research), and rewrites the query to optimize retrieval. The system then executes a hybrid search combining vector similarity search with BM25 keyword matching across the indexed document corpus.
The top 20-30 candidate documents are passed through a neural reranker that scores relevance using a cross-encoder model. The top 8-12 results are selected, chunked into relevant passages, and assembled into a context window along with the user's original query and any conversational history. The LLM generates a comprehensive answer with inline citations, streaming tokens to the client in real-time. A post-generation quality check verifies citation accuracy and screens for hallucinated content before the complete response is delivered.
public class SearchOrchestrator
{
private readonly IQueryUnderstanding _queryUnderstanding;
private readonly IHybridRetriever _retriever;
private readonly INeuralReranker _reranker;
private readonly IContextBuilder _contextBuilder;
private readonly ICitationMapper _citationMapper;
private readonly ILLMInference _llmInference;
private readonly IQualityChecker _qualityChecker;
private readonly ICacheService _cache;
public async IAsyncEnumerable<AnswerChunk> SearchAsync(
SearchRequest request,
[EnumeratorCancellation] CancellationToken ct)
{
var cached = await _cache.GetAsync<SearchResult>(request.Query);
if (cached != null)
{
foreach (var chunk in cached.Chunks)
yield return chunk;
yield break;
}
var classification = await _queryUnderstanding.ClassifyAsync(request.Query);
var rewrittenQuery = await _queryUnderstanding.RewriteAsync(
request.Query, classification, request.ThreadHistory);
var retrievalTask = Task.WhenAll(
_retriever.VectorSearchAsync(rewrittenQuery, k: 30),
_retriever.BM25SearchAsync(rewrittenQuery, k: 30));
var (vectorResults, bm25Results) = await retrievalTask;
var fusedResults = _retriever.ReciprocalRankFusion(
vectorResults, bm25Results, k: 60);
var reranked = await _reranker.RerankAsync(
rewrittenQuery, fusedResults.Take(20).ToList(), topK: 12);
var context = await _contextBuilder.BuildAsync(
request.Query, reranked, request.ThreadHistory);
var citationMap = _citationMapper.CreateMapping(reranked);
var answerBuffer = new StringBuilder();
await foreach (var token in _llmInference.GenerateAsync(
context, citationMap, ct))
{
answerBuffer.Append(token);
yield return new AnswerChunk
{
Content = token,
CitationUpdates = citationMap.GetNewlyReferenced(token)
};
}
var verification = await _qualityChecker.VerifyAsync(
answerBuffer.ToString(), citationMap);
if (verification.HasHallucination)
{
yield return new AnswerChunk
{
Content = "\n\n[Answer regenerated due to quality check]",
RequiresRegeneration = true
};
}
}
}
7. API Design
The API surface for an AI search engine must support streaming responses, conversation management, and various search modes. We define the primary endpoints below, using REST conventions with Server-Sent Events for streaming.
Primary Search Endpoint
// POST /api/v1/search
[ApiController]
[Route("api/v1")]
public class SearchController : ControllerBase
{
private readonly ISearchOrchestrator _orchestrator;
[HttpPost("search")]
[Authorize]
[RateLimit(QueriesPerMinute = 20)]
public async Task StreamSearch([FromBody] SearchRequest request)
{
Response.ContentType = "text/event-stream";
Response.Headers.Add("Cache-Control", "no-cache");
Response.Headers.Add("Connection", "keep-alive");
await foreach (var chunk in _orchestrator.SearchAsync(
request, HttpContext.RequestAborted))
{
var sseData = JsonSerializer.Serialize(new
{
type = chunk.CitationUpdates != null ? "citation" : "token",
content = chunk.Content,
citations = chunk.CitationUpdates,
done = chunk.IsComplete
});
await Response.WriteAsync($"data: {sseData}\n\n");
await Response.Body.FlushAsync();
}
}
}
public class SearchRequest
{
public string Query { get; set; } // Required
public string ThreadId { get; set; } // null for new thread
public string FocusMode { get; set; } = "all"; // all, academic, youtube
public string ProSearch { get; set; } = "off"; // off, standard, deep
public int MaxSources { get; set; } = 10;
public bool IncludeImages { get; set; } = false;
public List<string> SourceFilters { get; set; } // Domain allowlist
}
Thread Management Endpoints
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/v1/threads | List user's search threads |
| POST | /api/v1/threads | Create a new thread |
| GET | /api/v1/threads/{id} | Get thread with all turns |
| DELETE | /api/v1/threads/{id} | Delete a thread |
| PATCH | /api/v1/threads/{id} | Update thread title/settings |
| GET | /api/v1/threads/{id}/share | Generate share link |
Follow-Up Query Endpoint
// POST /api/v1/search/follow-up
[HttpPost("search/follow-up")]
public async Task StreamFollowUp([FromBody] FollowUpRequest request)
{
Response.ContentType = "text/event-stream";
var context = await _threadService.GetThreadContextAsync(request.ThreadId);
await foreach (var chunk in _orchestrator.FollowUpAsync(
request.Query, context, HttpContext.RequestAborted))
{
var sseData = JsonSerializer.Serialize(new
{
type = chunk.Type,
content = chunk.Content,
citations = chunk.Citations,
sources = chunk.NewSources,
suggestions = chunk.FollowUpSuggestions
});
await Response.WriteAsync($"data: {sseData}\n\n");
await Response.Body.FlushAsync();
}
}
public class FollowUpRequest
{
public string Query { get; set; }
public string ThreadId { get; set; }
public int PreviousTurnCount { get; set; } = 3
}
Rate Limiting Strategy
| Tier | Queries/Day | Pro Search/Day | Burst Limit |
|---|---|---|---|
| Free | 5 | 3 | 2 QPS |
| Pro ($20/mo) | 300 | 300 | 10 QPS |
| Enterprise | Unlimited | Unlimited | 100 QPS |
8. Web Crawling & Indexing
The foundation of any AI search engine is its web index. Unlike traditional search engines that index the entire web, an AI search engine can focus on a curated set of high-quality sources while maintaining the ability to fetch any specific URL on demand. The crawl system must handle billions of URLs, respect robots.txt directives, manage politeness constraints, and keep content fresh enough to support real-time queries about current events.
Crawl Architecture
500 Instances] CrawlerWorkers --> ContentExtractor[Content Extractor
Readability + Custom] ContentExtractor --> Dedup[Deduplication
SimHash + MinHash] Dedup --> QualityFilter[Quality Filter
Spam + Low Quality] QualityFilter --> Chunker[Chunking Service
Semantic Boundaries] Chunker --> Embedder[Embedding Service
Batch API] Embedder --> IndexWriter[Index Writer
Bulk Ingestion] IndexWriter --> VectorDB[(Vector DB)] IndexWriter --> DocStore[(PostgreSQL)] IndexWriter --> SearchIndex[(Elasticsearch)] CrawlerWorkers --> URLFrontier[URL Frontier
Priority Queues] URLFrontier --> CrawlerWorkers
Crawl Priority System
Not all URLs are equally valuable. The crawler uses a priority scoring system that determines which pages to crawl first and how frequently to re-crawl them. Breaking news sources receive the highest priority, followed by popular reference sites, academic databases, and general web content. The priority score is computed from multiple signals.
public class CrawlPriorityScorer
{
public float ComputePriority(URLRecord url)
{
float score = 0f;
// Domain authority (0.0 - 0.4)
score += url.DomainAuthority * 0.4f;
// Freshness need (0.0 - 0.3)
float freshnessFactor = url.AverageUpdateFrequency switch
{
< 1.0f => 0.1f, // Updates yearly
< 7.0f => 0.2f, // Updates weekly
< 1.0f => 0.3f, // Updates daily
_ => 0.4f // Updates hourly
};
score += freshnessFactor * 0.3f;
// Query relevance (0.0 - 0.2)
float queryDemand = url.HistoricalQueryHits / MaxHits;
score += Math.Min(queryDemand, 1.0f) * 0.2f;
// Content quality (0.0 - 0.1)
score += url.ContentQualityScore * 0.1f;
// Inbound links from high-authority domains (0.0 - 0.1)
float linkScore = url.HighAuthorityInboundLinks / 100f;
score += Math.Min(linkScore, 1.0f) * 0.1f;
return Math.Clamp(score, 0f, 1f);
}
public TimeSpan ComputeCrawlInterval(float priority, string contentType)
{
return contentType switch
{
"breaking_news" => TimeSpan.FromMinutes(1),
"news" => TimeSpan.FromHours(1),
"reference" => TimeSpan.FromHours(6),
"blog" => TimeSpan.FromDays(1),
"static" => TimeSpan.FromDays(7),
_ => TimeSpan.FromDays(1)
};
}
}
Content Extraction and Chunking
Raw HTML must be processed into clean, structured text suitable for embedding and LLM consumption. The extraction pipeline removes navigation elements, advertisements, cookie banners, and boilerplate content, preserving only the main article text. The extracted content is then split into semantically meaningful chunks that respect paragraph boundaries, heading sections, and logical topic shifts.
| Component | Technology | Purpose |
|---|---|---|
| HTML Parsing | AngleSharp + Custom Rules | Parse and clean HTML |
| Content Extraction | Readability algorithm + Custom | Extract main content |
| Language Detection | fastText lid.176 | Filter non-English content |
| Deduplication | SimHash + URL normalization | Remove near-duplicate pages |
| Chunking | Semantic splitter (512 tokens) | Split for embedding |
| Metadata Extraction | Custom parser | Extract dates, authors, topics |
public class ContentChunker
{
private const int MaxChunkTokens = 512;
private const int OverlapTokens = 50;
public List<DocumentChunk> ChunkDocument(IndexedDocument doc)
{
var chunks = new List<DocumentChunk>();
var sentences = TokenizeIntoSentences(doc.Content);
var currentChunk = new List<string>();
int currentTokens = 0;
foreach (var sentence in sentences)
{
int sentenceTokens = CountTokens(sentence);
if (currentTokens + sentenceTokens > MaxChunkTokens && currentChunk.Count > 0)
{
chunks.Add(new DocumentChunk
{
ChunkId = $"{doc.DocumentId}_chunk_{chunks.Count}",
DocumentId = doc.DocumentId,
Content = string.Join(" ", currentChunk),
TokenCount = currentTokens,
ChunkIndex = chunks.Count,
StartPosition = chunks.Count * (MaxChunkTokens - OverlapTokens)
});
// Keep overlap sentences for context continuity
int overlapCount = 0;
var overlapSentences = new List<string>();
for (int i = currentChunk.Count - 1; i >= 0 && overlapCount < OverlapTokens; i--)
{
overlapSentences.Insert(0, currentChunk[i]);
overlapCount += CountTokens(currentChunk[i]);
}
currentChunk = overlapSentences;
currentTokens = overlapCount;
}
currentChunk.Add(sentence);
currentTokens += sentenceTokens;
}
if (currentChunk.Count > 0)
{
chunks.Add(new DocumentChunk
{
ChunkId = $"{doc.DocumentId}_chunk_{chunks.Count}",
DocumentId = doc.DocumentId,
Content = string.Join(" ", currentChunk),
TokenCount = currentTokens,
ChunkIndex = chunks.Count
});
}
return chunks;
}
}
9. Query Understanding & Intent Classification
Query understanding is the brain of the AI search engine. Unlike traditional search where a keyword query is matched directly against an index, an AI search engine must deeply understand what the user is asking, determine the appropriate level of research depth, and rewrite the query to maximize retrieval quality. Poor query understanding cascades into poor retrieval, which cascades into poor answers — making this the single most impactful subsystem.
Intent Classification
Every incoming query is classified along multiple dimensions. The classification determines which retrieval strategy to use, how many sources to retrieve, whether to invoke Pro Search, and what level of citation density is expected.
| Intent Type | Example | Strategy | Depth |
|---|---|---|---|
| Factual Lookup | "What is the population of Tokyo?" | Direct answer, single authoritative source | Quick |
| Explanatory | "How does quantum entanglement work?" | Multi-source synthesis, educational tone | Standard |
| Comparison | "React vs Vue for a large enterprise app?" | Multi-perspective, structured comparison | Standard |
| How-To | "How to deploy a .NET app to AWS Lambda?" | Step-by-step, technical sources | Standard |
| Current Events | "What happened in the latest G7 summit?" | Real-time crawl, news sources priority | Standard |
| Opinion/Debate | "Is remote work better for productivity?" | Multi-perspective, balanced presentation | Deep |
| Code Generation | "Write a C# implementation of LRU cache" | Technical context, code-focused | Standard |
| Complex Research | "Compare the regulatory frameworks for AI across the EU, US, and China" | Deep research, multiple sub-queries | Pro Search |
Query Rewriting
Rewriting is crucial because users often ask questions ambiguously or with insufficient context. The query rewriting module uses a fine-tuned LLM to transform vague queries into precise retrieval queries while preserving the user's original intent.
public class QueryUnderstandingService
{
private readonly ILLMInference _llm;
private readonly IIntentClassifier _classifier;
public async Task<QueryClassification> ClassifyAsync(string query)
{
var prompt = $@"Classify this search query along multiple dimensions.
Query: ""{query}""
Return JSON with:
- intent: factual | explanatory | comparison | howto | current_events | opinion | code | research
- depth_needed: quick | standard | deep
- temporal_relevance: none | recent | breaking
- topic_domain: general | technical | academic | news | code
- complexity_score: 1-10
- suggested_focus_mode: all | academic | youtube | reddit | news";
var response = await _llm.CompleteAsync(prompt, maxTokens: 200);
return JsonSerializer.Deserialize<QueryClassification>(response);
}
public async Task<RewrittenQuery> RewriteAsync(
string originalQuery, QueryClassification classification,
List<SearchTurn> threadHistory)
{
var context = threadHistory.Count > 0
? $"\nConversation context:\n{string.Join("\n", threadHistory.TakeLast(3).Select(t => $"User: {t.Query}\nAssistant: {t.Answer.Content[..200]}"))}"
: "";
var prompt = $@"Rewrite this search query for optimal web retrieval.
Original: ""{originalQuery}""
Intent: {classification.Intent}
Domain: {classification.TopicDomain}
{context}
Provide:
1. A primary retrieval query optimized for vector search
2. 2-3 alternative phrasings for diversity
3. Key entities to extract
4. Boolean operators or domain restrictions if applicable
Return as JSON.";
var response = await _llm.CompleteAsync(prompt, maxTokens: 400);
return JsonSerializer.Deserialize<RewrittenQuery>(response);
}
}
Conversational Context Resolution
In multi-turn conversations, follow-up queries often contain pronouns and references that only make sense in context. "What about its performance?" after a discussion about React vs Vue requires resolving "its" to the previously discussed topic. The context resolution module uses coreference resolution and question reformulation to transform dependent queries into standalone retrieval queries.
public class ConversationalContextResolver
{
public async Task<string> ResolveFollowUpAsync(
string followUpQuery, List<SearchTurn> history)
{
if (history == null || history.Count == 0)
return followUpQuery;
var conversationSummary = string.Join("\n",
history.TakeLast(5).Select(t =>
$"Q: {t.Query}\nKey topics: {string.Join(", ", t.Classification.KeyEntities)}"));
var prompt = $@"Given this conversation history and a follow-up question,
rewrite the follow-up as a standalone search query.
Conversation:
{conversationSummary}
Follow-up: ""{followUpQuery}""
Return the rewritten standalone query. Be concise.";
return await _llm.CompleteAsync(prompt, maxTokens: 100);
}
}
10. RAG Pipeline (Retrieve + Generate)
The RAG pipeline is the core of the AI search engine, combining document retrieval with LLM-based answer generation. A well-designed RAG pipeline must balance several competing objectives: retrieving the most relevant documents, fitting the maximum useful context into the LLM's context window, generating accurate and well-cited answers, and doing all of this within latency and cost constraints.
Top 30] ParallelSearch --> BM25Search[BM25 Keyword
Top 30] ParallelSearch --> KnowledgeGraph[Knowledge Graph
Entities] VectorSearch --> Fusion[Reciprocal Rank
Fusion] BM25Search --> Fusion KnowledgeGraph --> Fusion Fusion --> Reranker[Cross-Encoder
Reranker] Reranker --> Selection[Top-K Selection
K=8-12] Selection --> ContextBuild[Context Assembly] ContextBuild --> PromptConstruct[Prompt Construction
System + Context + Query] PromptConstruct --> LLM[LLM Generation
Streaming] LLM --> CitationExtract[Citation Extraction
Regex + Embeddings] CitationExtract --> QualityCheck[Quality Check
Hallucination Detection] QualityCheck --> Response[Final Response
with Citations]
Hybrid Retrieval Strategy
Neither vector search alone nor BM25 search alone is sufficient for high-quality retrieval. Vector search excels at semantic similarity — finding passages that are conceptually related to the query even when they use different words. BM25 excels at exact keyword matching — finding passages that contain specific technical terms, names, or phrases. By combining both approaches with reciprocal rank fusion, we achieve retrieval quality that exceeds either method individually.
public class HybridRetriever
{
private readonly IVectorStore _vectorStore;
private readonly ISearchIndex _searchIndex;
private readonly IEmbeddingService _embedder;
public async Task<List<RetrievedDocument>> RetrieveAsync(
RewrittenQuery query, int topK = 12)
{
var embedding = await _embedder.EmbedAsync(query.PrimaryQuery);
var vectorTask = _vectorStore.SearchAsync(embedding, topK: 30,
filters: BuildFilters(query));
var bm25Task = _searchIndex.SearchAsync(query.PrimaryQuery, topK: 30,
filters: BuildFilters(query));
await Task.WhenAll(vectorTask, bm25Task);
var vectorResults = await vectorTask;
var bm25Results = await bm25Task;
// Reciprocal Rank Fusion
float k = 60; // RRF constant
var allDocs = new Dictionary<string, float>();
foreach (var (doc, rank) in vectorResults.Select((d, i) => (d, i)))
{
allDocs.TryAdd(doc.DocumentId, 0f);
allDocs[doc.DocumentId] += 1f / (k + rank + 1);
}
foreach (var (doc, rank) in bm25Results.Select((d, i) => (d, i)))
{
allDocs.TryAdd(doc.DocumentId, 0f);
allDocs[doc.DocumentId] += 1f / (k + rank + 1);
}
var fused = allDocs
.OrderByDescending(kvp => kvp.Value)
.Take(30)
.Select(kvp => new RetrievedDocument
{
DocumentId = kvp.Key,
RRF_Score = kvp.Value
})
.ToList();
return fused;
}
}
Context Window Assembly
Assembling the context window is an optimization problem. We must maximize the information content within the token budget while maintaining logical structure and citation traceability. The context builder prioritizes documents by relevance score, extracts the most informative passages, and formats them with clear source markers that the LLM can reference in its output.
public class ContextBuilder
{
private const int MaxContextTokens = 12000;
private const int SystemPromptTokens = 500;
private const int QueryTokens = 200;
private const int ReserveTokens = 1000;
public LLMContext BuildContext(
string originalQuery,
List<RerankedDocument> sources,
List<SearchTurn> history)
{
int availableTokens = MaxContextTokens
- SystemPromptTokens
- QueryTokens
- ReserveTokens;
var context = new LLMContext();
int usedTokens = 0;
// Add conversation history (limited to 3 turns)
if (history != null && history.Count > 0)
{
foreach (var turn in history.TakeLast(3))
{
var historyEntry = $"[Previous Q]: {turn.Query}\n" +
$"[Previous A]: {TruncateToTokens(turn.Answer.Content, 300)}";
int entryTokens = CountTokens(historyEntry);
if (usedTokens + entryTokens > availableTokens * 0.2f) break;
context.History.Add(historyEntry);
usedTokens += entryTokens;
}
}
// Add sources in relevance order
foreach (var source in sources)
{
var passage = $"[Source {source.CitationNumber}]: {source.Title}\n" +
$"URL: {source.Url}\n" +
$"Content: {source.BestPassage}\n" +
$"Published: {source.PublishedDate?.ToString("yyyy-MM-dd") ?? "Unknown"}";
int passageTokens = CountTokens(passage);
if (usedTokens + passageTokens > availableTokens) break;
context.Sources.Add(passage);
context.CitationMap.Add(source.CitationNumber, source);
usedTokens += passageTokens;
}
context.SystemPrompt = BuildSystemPrompt(originalQuery, context.Sources.Count);
context.UserQuery = originalQuery;
context.TokenBudget = usedTokens;
return context;
}
}
Streaming and Token-Level Citation Injection
One of the most distinctive features of Perplexity-style search is real-time streaming with live citation updates. As the LLM generates tokens, the system monitors for citation references (e.g., [1], [2]) and sends citation metadata to the client before the user encounters the reference number. This creates the illusion that the answer is being assembled in real-time with sources appearing as they become relevant.
11. Citation & Source Ranking
Citations are the trust mechanism of an AI search engine. Unlike traditional search where users can evaluate each result before clicking, AI search presents a synthesized answer that must be verifiable. The citation system must accurately attribute claims to sources, rank sources by reliability, and present citations in a way that builds user confidence.
Source Ranking Algorithm
Source quality is determined by a composite score that combines multiple signals. Each signal captures a different aspect of source reliability, and the weights are tuned based on the query type and domain.
| Signal | Weight | Description |
|---|---|---|
| Domain Authority | 0.25 | Computed from link graph analysis (similar to PageRank) |
| Content Freshness | 0.20 | Recency of publication or last update |
| Retrieval Score | 0.20 | Combined vector + BM25 relevance score |
| Citation Count | 0.10 | Number of academic/web citations pointing to this source |
| Source Type Bonus | 0.10 | Academic papers, government data, official docs get bonus |
| Readability Score | 0.05 | Clarity and structure of the content |
| Author Reputation | 0.05 | Known expert authors get higher scores |
| Engagement Signals | 0.05 | Share counts, comment quality (for forums) |
Citation Extraction from LLM Output
public class CitationExtractor
{
private static readonly Regex CitationPattern = new(
@"\[(\d+)\]", RegexOptions.Compiled);
public List<Citation> ExtractCitations(
string llmOutput, Dictionary<int, RetrievedDocument> citationMap)
{
var citations = new List<Citation>();
var matches = CitationPattern.Matches(llmOutput);
var seenNumbers = new HashSet<int>();
foreach (Match match in matches)
{
int citationNumber = int.Parse(match.Groups[1].Value);
if (!seenNumbers.Add(citationNumber)) continue;
if (!citationMap.ContainsKey(citationNumber)) continue;
var source = citationMap[citationNumber];
var snippet = ExtractSnippet(llmOutput, match.Index, 200);
citations.Add(new Citation
{
CitationNumber = citationNumber,
SourceDocumentId = source.DocumentId,
Url = source.Url,
Title = source.Title,
Snippet = snippet,
Domain = source.Domain,
PublishedDate = source.PublishedDate,
RelevanceScore = source.RerankScore,
IsVerified = false
});
}
return citations;
}
private string ExtractSnippet(string text, int position, int contextLength)
{
int start = Math.Max(0, position - contextLength / 2);
int length = Math.Min(contextLength, text.Length - start);
return text.Substring(start, length).Trim();
}
}
Citation Verification Pipeline
After generation, each citation undergoes verification. The system checks that the cited URL actually contains the information attributed to it by the LLM. This is done by extracting the relevant passage from the source document and computing semantic similarity with the generated claim. Citations below a similarity threshold are flagged for potential correction or removal.
12. Real-Time Web Search Integration
A key differentiator for AI search engines is the ability to incorporate real-time web information. While the pre-built index covers billions of pages, some queries require fetching content that was published minutes or hours ago. The real-time search system handles these cases by performing on-demand web searches, fetching and parsing the results, and incorporating them into the generation context.
When to Trigger Real-Time Search
| Trigger Condition | Example Query | Action |
|---|---|---|
| Temporal intent detected | "latest stock price of Tesla" | Live fetch from financial API |
| Breaking news classification | "earthquake today" | News search + recent article fetch |
| Low index coverage | Query for very niche topic | Web search fallback |
| Pro Search mode | "Comprehensive analysis of 2026 AI regulations" | Multi-step research with live search |
| User explicit request | "Search the web for..." | Forced real-time search |
Real-Time Search Implementation
public class RealTimeSearchService
{
private readonly ISearchApiClient _searchClient;
private readonly IContentFetcher _fetcher;
private readonly IContentParser _parser;
private readonly IEmbeddingService _embedder;
public async Task<List<RetrievedDocument>> SearchRealTimeAsync(
string query, int maxResults = 10)
{
// Step 1: Execute web search via search API
var searchResults = await _searchClient.SearchAsync(query, maxResults);
// Step 2: Fetch full page content for top results
var fetchTasks = searchResults
.Take(maxResults)
.Select(async result =>
{
try
{
var html = await _fetcher.FetchAsync(result.Url,
timeout: TimeSpan.FromSeconds(5));
var content = _parser.ExtractContent(html);
return new
{
result.Url,
result.Title,
Content = content,
result.Snippet,
PublishedDate = _parser.ExtractDate(html)
};
}
catch
{
return null; // Gracefully handle fetch failures
}
});
var fetchedResults = (await Task.WhenAll(fetchTasks))
.Where(r => r != null && r.Content.Length > 100)
.ToList();
// Step 3: Embed and score against original query
var queryEmbedding = await _embedder.EmbedAsync(query);
var scoredResults = new List<RetrievedDocument>();
foreach (var result in fetchedResults)
{
var contentEmbedding = await _embedder.EmbedAsync(
result.Content[..Math.Min(4000, result.Content.Length)]);
float similarity = CosineSimilarity(queryEmbedding, contentEmbedding);
scoredResults.Add(new RetrievedDocument
{
Url = result.Url,
Title = result.Title,
Content = result.Content,
RelevanceScore = similarity,
Source = "realtime_search",
PublishedDate = result.PublishedDate,
IsRealTime = true
});
}
return scoredResults
.OrderByDescending(r => r.RelevanceScore)
.Take(8)
.ToList();
}
}
13. Conversational Follow-Up
Conversational follow-up is what transforms an AI search engine from a glorified lookup tool into a genuine research assistant. Users should be able to ask a question, receive an answer, and then drill deeper into specific aspects without repeating context. The follow-up system must maintain coherent conversation state, resolve ambiguous references, and progressively refine answers as the user's information needs become clearer.
Conversation State Management
public class ConversationManager
{
private readonly IThreadStore _threadStore;
private readonly IContextResolver _contextResolver;
private readonly ISearchOrchestrator _orchestrator;
public async Task<ConversationState> ProcessTurnAsync(
string threadId, string query, string userId)
{
var thread = await _threadStore.GetThreadAsync(threadId);
if (thread == null)
{
thread = await _threadStore.CreateThreadAsync(
userId, await GenerateTitleAsync(query));
}
// Determine if this is a follow-up or standalone query
var contextType = await _contextResolver.ClassifyContextAsync(
query, thread.Turns);
string resolvedQuery = contextType switch
{
ContextType.Standalone => query,
ContextType.FollowUp => await _contextResolver.ResolveAsync(
query, thread.Turns),
ContextType.SpecificSource => await _contextResolver.ExpandSourceQueryAsync(
query, thread.Turns),
_ => query
};
// Execute search with appropriate context depth
int contextDepth = contextType switch
{
ContextType.FollowUp => 3,
ContextType.SpecificSource => 2,
_ => 0
};
var searchResult = await _orchestrator.SearchWithHistoryAsync(
resolvedQuery, thread.Turns.TakeLast(contextDepth).ToList());
var turn = new SearchTurn
{
TurnId = Guid.NewGuid().ToString(),
Query = query,
RewrittenQuery = resolvedQuery,
Classification = searchResult.Classification,
Sources = searchResult.Sources,
Answer = searchResult.Answer,
Citations = searchResult.Citations,
FollowUps = await GenerateFollowUpsAsync(searchResult, thread),
TimestampedAt = DateTime.UtcNow
};
thread.Turns.Add(turn);
await _threadStore.SaveThreadAsync(thread);
return new ConversationState
{
ThreadId = thread.ThreadId,
Turn = turn,
SuggestedFollowUps = turn.FollowUps
};
}
}
Follow-Up Suggestion Generation
After each answer, the system generates 3-4 suggested follow-up questions that help users explore the topic deeper. These suggestions are generated by the LLM based on the answer content, identified knowledge gaps, and common related questions. The suggestions are ranked by likely information gain — prioritizing questions that would add new information rather than rephrase the original query.
| Suggestion Type | Example | Purpose |
|---|---|---|
| Deep Dive | "What specific quantum computing breakthroughs occurred in 2026?" | Narrow focus on most relevant sub-topic |
| Comparison | "How does this compare to the previous approach?" | Add contrast and context |
| Counterpoint | "What are the limitations of this approach?" | Provide balanced perspective |
| Application | "How can I apply this in a production environment?" | Move from theory to practice |
| Related Topic | "What about its impact on the energy sector?" | Explore adjacent domains |
14. Knowledge Graph Integration
While vector search and BM25 form the backbone of retrieval, a knowledge graph adds structured reasoning capabilities that neither can provide. Knowledge graphs capture entities and their relationships — people, organizations, concepts, events, and how they connect. This structured knowledge enables the AI search engine to answer questions that require traversing relationships, performing entity disambiguation, and providing contextual information that pure text retrieval might miss.
Knowledge Graph Schema
Knowledge Graph Construction
public class KnowledgeGraphBuilder
{
private readonly INeo4jDriver _graphDb;
private readonly IEntityExtractor _entityExtractor;
private readonly IRelationClassifier _relationClassifier;
public async Task<GraphUpdateResult> UpdateFromDocumentAsync(
IndexedDocument document)
{
// Extract entities using NER model
var entities = await _entityExtractor.ExtractAsync(document.Content);
// Classify relationships between entities
var relationships = await _relationClassifier.ClassifyAsync(
entities, document.Content);
await using var session = _graphDb.AsyncSession();
await session.ExecuteWriteAsync(tx =>
{
// Merge document node
tx.Run(@"
MERGE (d:Document {id: $docId})
SET d.url = $url, d.title = $title,
d.crawledAt = $crawledAt",
new { docId = document.DocumentId, url = document.Url,
title = document.Title, crawledAt = document.CrawledAt });
// Merge entity nodes and relationships
foreach (var entity in entities)
{
tx.Run(@"
MERGE (e:Entity {name: $name, type: $type})
SET e.aliases = $aliases, e.lastSeen = $now
MERGE (d:Document {id: $docId})
MERGE (e)-[:MENTIONED_IN]->(d)",
new { name = entity.Name, type = entity.Type,
aliases = entity.Aliases, now = DateTimeOffset.UtcNow,
docId = document.DocumentId });
}
// Create relationship edges
foreach (var rel in relationships)
{
tx.Run($@"
MATCH (a:Entity {{name: $source}})
MATCH (b:Entity {{name: $target}})
MERGE (a)-[r:{rel.Type}]->(b)
SET r.confidence = $confidence,
r.evidence = $evidence",
new { source = rel.SourceEntity, target = rel.TargetEntity,
confidence = rel.Confidence, evidence = rel.Evidence });
}
});
return new GraphUpdateResult
{
EntitiesAdded = entities.Count,
RelationshipsAdded = relationships.Count
};
}
}
Knowledge Graph Query Integration
When a query requires structured reasoning, the knowledge graph is queried in parallel with vector and BM25 search. For example, a query like "Who are the founders of companies competing with Google in AI search?" requires traversing company-to-founder relationships and identifying AI search competitors — a task that knowledge graphs handle naturally but vector search handles poorly.
15. Answer Quality Evaluation
Answer quality is the defining metric for an AI search engine. A search engine that returns inaccurate answers or fabricated citations will lose user trust permanently. The quality evaluation system operates at multiple levels: pre-generation validation, post-generation verification, and ongoing quality monitoring through user feedback and automated evaluation.
Quality Dimensions
| Dimension | Measurement | Threshold | Impact |
|---|---|---|---|
| Factual Accuracy | Cross-reference with source content | > 95% claims supported | Critical |
| Citation Validity | Verify each citation points to relevant content | > 98% valid | Critical |
| Completeness | Coverage of query aspects | > 85% coverage | High |
| Relevance | Answer directly addresses the query | > 90% relevance | High |
| Readability | Clear, well-structured prose | Flesch-Kincaid > 60 | Medium |
| Balance | Presents multiple viewpoints when applicable | Score > 0.7 | Medium |
| Hallucination | Claims not in any source document | < 2% | Critical |
Hallucination Detection
public class HallucinationDetector
{
private readonly IEmbeddingService _embedder;
private readonly ILLMInference _llm;
public async Task<HallucinationReport> DetectAsync(
string answer, List<RetrievedDocument> sources)
{
// Step 1: Decompose answer into individual claims
var claims = await DecomposeClaimsAsync(answer);
var report = new HallucinationReport();
foreach (var claim in claims)
{
// Step 2: Embed the claim
var claimEmbedding = await _embedder.EmbedAsync(claim.Text);
// Step 3: Find most similar passage in sources
float maxSimilarity = 0f;
string bestMatchingSource = null;
foreach (var source in sources)
{
var sourceEmbedding = await _embedder.EmbedAsync(
source.BestPassage);
float similarity = CosineSimilarity(claimEmbedding, sourceEmbedding);
if (similarity > maxSimilarity)
{
maxSimilarity = similarity;
bestMatchingSource = source.DocumentId;
}
}
// Step 4: Use LLM for fine-grained entailment check
var entailment = await _llm.CompleteAsync($@"Does the following
claim follow from the source content?
Claim: ""{claim.Text}""
Source: ""{sources.First(s => s.DocumentId == bestMatchingSource).BestPassage}""
Answer with: SUPPORTED, PARTIALLY_SUPPORTED, or NOT_SUPPORTED",
maxTokens: 20);
report.Claims.Add(new ClaimVerification
{
Claim = claim.Text,
Verdict = entailment.Trim(),
SourceDocumentId = bestMatchingSource,
SimilarityScore = maxSimilarity
});
}
report.HallucinationRate = report.Claims
.Count(c => c.Verdict == "NOT_SUPPORTED") / (float)report.Claims.Count;
report.RequiresRegeneration = report.HallucinationRate > 0.15f;
return report;
}
}
16. Latency Optimization
Latency is a competitive moat in AI search. Users comparing Perplexity with Google expect similar or better response times, even though AI search performs dramatically more computation per query. Achieving sub-2-second time-to-first-token requires aggressive optimization across every layer of the stack.
Latency Budget Breakdown
| Stage | Target Latency | Optimization Strategy |
|---|---|---|
| Query Understanding | < 50ms | Cached classification model, small LLM |
| Query Rewriting | < 100ms | Distilled model, streaming response |
| Vector Search | < 80ms | HNSW index, GPU acceleration |
| BM25 Search | < 30ms | Sharded Elasticsearch, warm index |
| Reranking | < 150ms | ONNX Runtime, batched cross-encoder |
| Context Assembly | < 20ms | In-memory processing |
| LLM Time-to-First-Token | < 300ms | Speculative decoding, KV-cache |
| LLM Generation | < 1500ms | Streaming, optimized batch sizes |
| Citation Verification | < 100ms | Async post-generation check |
| Network Overhead | < 50ms | Edge deployment, CDN, WebSockets |
| Total (P50) | < 2s | Parallel execution, streaming |
Key Optimization Techniques
- KV-Cache Reuse: For conversations, the key-value cache from previous turns is preserved, so the LLM only needs to process new tokens rather than re-processing the entire context.
- Speculative Decoding: A smaller draft model generates candidate tokens that the larger model verifies in parallel, achieving 2-3× throughput improvement.
- Prefetching: While the LLM processes the query, the system prefetches likely follow-up sources based on the classified intent.
- Edge Caching: Popular queries (top 10% by volume) are cached at the edge for instant response.
- Model Routing: Simple factual queries are routed to smaller, faster models while complex queries use the full-size model.
public class LatencyOptimizedPipeline
{
private readonly IModelRouter _router;
private readonly IKVCacheManager _kvCache;
private readonly IEdgeCache _edgeCache;
public async IAsyncEnumerable<AnswerChunk> OptimizedSearchAsync(
SearchRequest request, [EnumeratorCancellation] CancellationToken ct)
{
// Check edge cache first
var cached = await _edgeCache.GetAsync(request.Query);
if (cached != null)
{
foreach (var chunk in cached)
yield return chunk;
yield break;
}
// Route to appropriate model based on complexity
var classification = await _router.ClassifyComplexityAsync(request.Query);
var model = classification.ComplexityScore switch
{
< 4 => "sonar-small", // Fast, simple queries
< 7 => "sonar-medium", // Balanced
_ => "sonar-large" // Deep reasoning
};
// Parallel retrieval and context preparation
var retrievalTask = RetrieveSourcesAsync(request);
var kvCacheTask = _kvCache.GetOrComputeAsync(
request.ThreadId, request.Query);
await Task.WhenAll(retrievalTask, kvCacheTask);
var sources = await retrievalTask;
var kvState = await kvCacheTask;
// Stream generation with KV-cache warm start
await foreach (var token in GenerateWithCacheAsync(
model, request, sources, kvState, ct))
{
yield return token;
}
}
}
17. Citation Verification
Citation verification goes beyond the hallucination detection covered in the quality evaluation section. While hallucination detection checks whether claims are supported by sources, citation verification ensures that the specific citation numbers in the answer actually correspond to the source documents they reference. This is a subtle but critical distinction — an answer might contain factually accurate information that is attributed to the wrong source.
Verification Pipeline
References] ExtractRefs --> MatchSource[Match to Source
Documents] MatchSource --> SnippetVerify[Verify Snippet
in Source] SnippetVerify --> SemanticCheck[Semantic
Similarity Check] SemanticCheck --> Verdict{Verdict} Verdict -->|Verified| FlagGreen[✓ Verified] Verdict -->|Mismatch| FlagRed[✗ Mismatch] Verdict -->|Partial| FlagYellow[~ Partial] FlagRed --> AutoCorrect[Auto-Correct
Citation Mapping]
public class CitationVerifier
{
private readonly IEmbeddingService _embedder;
private readonly IDocumentStore _docStore;
public async Task<VerificationResult> VerifyCitationAsync(
Citation citation, string surroundingContext)
{
// Fetch the full source document
var document = await _docStore.GetDocumentAsync(citation.SourceDocumentId);
if (document == null)
return new VerificationResult { Status = "DOCUMENT_NOT_FOUND" };
// Check if the cited URL still exists and is accessible
var isAccessible = await CheckUrlAccessibilityAsync(citation.Url);
if (!isAccessible)
return new VerificationResult { Status = "URL_INACCESSIBLE" };
// Find the snippet in the source document
var snippetFound = document.Content.Contains(citation.Snippet) ||
document.Chunks.Any(c =>
c.Contains(citation.Snippet[..100]));
if (!snippetFound)
{
// Try fuzzy matching
var bestMatch = FindBestFuzzyMatch(
citation.Snippet, document.Chunks);
if (bestMatch.Similarity < 0.7f)
return new VerificationResult
{
Status = "SNIPPET_NOT_FOUND",
Confidence = bestMatch.Similarity
};
}
// Semantic verification: does the claim match the source?
var contextEmbedding = await _embedder.EmbedAsync(surroundingContext);
var sourceEmbedding = await _embedder.EmbedAsync(citation.Snippet);
float semanticSimilarity = CosineSimilarity(contextEmbedding, sourceEmbedding);
return new VerificationResult
{
Status = semanticSimilarity > 0.8f ? "VERIFIED" :
semanticSimilarity > 0.6f ? "PARTIAL" : "MISMATCH",
Confidence = semanticSimilarity,
VerifiedAt = DateTimeOffset.UtcNow,
Suggestions = semanticSimilarity < 0.8f
? await FindBetterCitationAsync(surroundingContext, document)
: null
};
}
}
18. User Personalization
Personalization in AI search must be handled carefully. Unlike social media where engagement optimization justifies heavy personalization, search personalization should focus on understanding user expertise levels, preferred content formats, and topic interests without creating filter bubbles that limit information diversity. The goal is to present answers at the right level of complexity and from sources the user finds trustworthy.
Personalization Signals
| Signal | Source | Usage |
|---|---|---|
| Expertise level | Query history analysis | Adjust answer complexity |
| Topic interests | Browsing patterns | Prioritize relevant sources |
| Preferred source types | Click-through data | Rank familiar source types higher |
| Language preferences | Explicit setting + detection | Filter/generate in preferred language |
| Follow-up patterns | Thread analysis | Improve suggestion generation |
| Feedback history | Thumbs up/down, regenerate | Adjust generation style |
User Profile Model
public class UserProfile
{
public string UserId { get; set; }
public ExpertiseLevel Expertise { get; set; }
public Dictionary<string, float> TopicInterests { get; set; } // topic -> weight
public List<string> PreferredSources { get; set; }
public List<string> BlockedSources { get; set; }
public string PreferredLanguage { get; set; }
public string AnswerStyle { get; set; } // concise, detailed, academic
public int AverageQueryComplexity { get; set; }
public List<string> FrequentlySearchedDomains { get; set; }
public UserFeedbackProfile FeedbackProfile { get; set; }
public DateTime LastActiveAt { get; set; }
}
public enum ExpertiseLevel
{
Beginner, // Simple explanations, more context
Intermediate, // Balanced depth
Advanced, // Technical detail, minimal hand-holding
Expert // Assume deep domain knowledge
}
public class PersonalizationService
{
private readonly IUserProfileStore _profileStore;
private readonly IBehaviorAnalyzer _behaviorAnalyzer;
public async Task<PersonalizationContext> GetPersonalizationAsync(string userId)
{
var profile = await _profileStore.GetProfileAsync(userId);
if (profile == null)
return PersonalizationContext.Default();
// Update expertise level based on recent queries
profile.Expertise = await _behaviorAnalyzer.DetermineExpertiseAsync(
userId, recentDays: 30);
return new PersonalizationContext
{
ExpertiseLevel = profile.Expertise,
PreferredSources = profile.PreferredSources,
TopicBoosts = profile.TopicInterests
.OrderByDescending(kvp => kvp.Value)
.Take(5)
.ToDictionary(kvp => kvp.Key, kvp => kvp.Value),
AnswerStyle = profile.AnswerStyle,
SourcePenalties = profile.BlockedSources
};
}
}
19. Monetization & Pro
Building an AI search engine requires significant infrastructure investment, primarily driven by LLM inference costs and web crawling operations. A sustainable business model must balance free access for user acquisition with premium features that generate revenue. Perplexity's model provides a useful template, but there are several additional monetization opportunities unique to AI search.
Tier Structure
| Feature | Free | Pro ($20/mo) | Enterprise (Custom) |
|---|---|---|---|
| Daily Queries | 5 | 300 | Unlimited |
| Pro Search (Deep) | 3/day | 300/day | Unlimited |
| File Upload | 3/day | Unlimited | Unlimited |
| API Access | No | Limited (1000 QPM) | High (100K QPM) |
| Data Isolation | No | No | Yes, dedicated instance |
| Custom Knowledge Base | No | No | Yes, private RAG |
| SSO / SCIM | No | No | Yes |
| Analytics Dashboard | Basic | Advanced | Full + Export |
| Priority Support | No | Dedicated CSM |
Revenue Streams
- Subscriptions: Pro tier at $20/month is the primary revenue driver. At scale, this provides predictable recurring revenue.
- Enterprise Licensing: Annual contracts for enterprise search deployments, typically $50K-$500K per year depending on scale.
- API Monetization: Per-query pricing for developers building on the search API. $1-5 per 1000 queries depending on depth.
- Answer-Integrated Advertising: Sponsored content that appears as additional sources alongside organic answers. Must be clearly labeled to maintain trust.
- Data Licensing: Aggregated, anonymized search trend data licensed to market research firms.
- Shopping Integration: When queries have purchase intent, product recommendations with affiliate commissions.
Cost-Per-Query Economics
public class CostEstimator
{
public QueryCostEstimate EstimateCost(QueryComplexity complexity)
{
return complexity switch
{
QueryComplexity.Simple => new QueryCostEstimate
{
LLMCost = 0.001m, // Small model, short answer
EmbeddingCost = 0.0002m,
RetrievalCost = 0.0001m,
TotalCost = 0.0013m
},
QueryComplexity.Standard => new QueryCostEstimate
{
LLMCost = 0.005m, // Medium model
EmbeddingCost = 0.0005m,
RetrievalCost = 0.0003m,
CrawlCost = 0.001m, // Real-time search
TotalCost = 0.0068m
},
QueryComplexity.ProSearch => new QueryCostEstimate
{
LLMCost = 0.025m, // Large model, multi-step
EmbeddingCost = 0.002m,
RetrievalCost = 0.001m,
CrawlCost = 0.005m, // Multiple real-time searches
VerificationCost = 0.003m,
TotalCost = 0.036m
},
_ => throw new ArgumentException($"Unknown complexity: {complexity}")
};
}
}
20. Enterprise Search
Enterprise search represents a significant revenue opportunity and a fundamentally different technical challenge. While consumer search operates on public web data, enterprise search must index private documents, respect access controls, handle diverse file formats, and provide answers with organizational context. The architecture must support multi-tenant isolation, compliance requirements like SOC 2 and GDPR, and integration with enterprise identity providers.
Enterprise Architecture Differences
| Aspect | Consumer Search | Enterprise Search |
|---|---|---|
| Data Source | Public web | Private documents, wikis, Slack, email |
| Access Control | None | Role-based, document-level ACLs |
| LLM Deployment | Shared cloud | VPC-isolated or on-premise |
| Data Retention | Query logs (90 days) | Configurable per org policy |
| Compliance | Standard privacy | SOC 2, GDPR, HIPAA, FedRAMP |
| Multi-Tenancy | User-level | Organization-level isolation |
| Custom Models | No | Fine-tuned on org knowledge |
Enterprise RAG Pipeline
public class EnterpriseSearchPipeline
{
private readonly IAccessControlService _aclService;
private readonly IDocumentConnector[] _connectors;
private readonly IOrgKnowledgeBase _knowledgeBase;
public async Task<EnterpriseAnswer> SearchAsync(
EnterpriseSearchRequest request, ClaimsPrincipal user)
{
// Step 1: Determine user's access scope
var accessScope = await _aclService.GetAccessScopeAsync(user);
var accessibleOrgs = accessScope.AccessibleOrganizations;
// Step 2: Search across all connected data sources
var connectorTasks = _connectors
.Where(c => c.IsConfiguredForOrg(accessibleOrgs))
.Select(c => c.SearchAsync(request.Query, accessScope));
var allResults = (await Task.WhenAll(connectorTasks))
.SelectMany(r => r)
.ToList();
// Step 3: Filter by access control
var filteredResults = allResults
.Where(r => _aclService.HasAccess(user, r.DocumentId))
.ToList();
// Step 4: Apply org-specific boosting
var boostedResults = ApplyOrgBoosting(
filteredResults, request.OrganizationId);
// Step 5: Generate answer with enterprise context
var context = BuildEnterpriseContext(
request.Query, boostedResults, request.OrganizationId);
var answer = await GenerateAnswerAsync(context);
// Step 6: Audit logging for compliance
await AuditLogAsync(user, request, answer);
return answer;
}
}
21. Content Freshness Pipeline
Content freshness is a critical competitive advantage for AI search. Users increasingly turn to AI search for current events, breaking news, and rapidly evolving topics. The freshness pipeline must detect when content changes, prioritize re-crawling of time-sensitive sources, and ensure that the index reflects the most recent version of each page.
Freshness Strategy
RSS, Sitemaps, Monitoring] PriorityQueue[Priority Re-crawl Queue] FreshnessClassifier[Freshness Classifier
Breaking vs Evergreen] ReCrawl[Targeted Re-crawl] DiffEngine[Content Diff Engine
Semantic Change Detection] IndexUpdate[Index Update
Incremental Re-embedding] CacheInvalidation[Cache Invalidation
Edge + CDN] ChangeDetection --> PriorityQueue PriorityQueue --> FreshnessClassifier FreshnessClassifier -->|Breaking| ReCrawl FreshnessClassifier -->|Updated| ReCrawl FreshnessClassifier -->|Evergreen| IndexUpdate ReCrawl --> DiffEngine DiffEngine --> IndexUpdate IndexUpdate --> CacheInvalidation
Change Detection Sources
| Source | Latency | Coverage | Reliability |
|---|---|---|---|
| RSS/Atom Feeds | Real-time | ~30% of quality sources | High |
| Sitemap Index | Minutes | ~60% of major sites | Medium |
| Diff-based Polling | 1-6 hours | Targeted high-value pages | Medium |
| News API Aggregation | Real-time | Major news outlets | High |
| Social Media Signals | Minutes | Viral content detection | Low-Medium |
| Periodic Full Crawl | Daily-Weekly | Long tail content | High |
public class ContentFreshnessManager
{
private readonly IRssMonitor _rssMonitor;
private readonly ISitemapScanner _sitemapScanner;
private readonly IContentDiffEngine _diffEngine;
private readonly ICrawlPriorityQueue _priorityQueue;
public async Task<FreshnessReport> ProcessUpdatesAsync()
{
var updates = new List<ContentUpdate>();
// Collect changes from multiple sources
var rssChanges = await _rssMonitor.GetRecentChangesAsync(
since: TimeSpan.FromMinutes(15));
var sitemapChanges = await _sitemapScanner.GetChangesAsync(
since: TimeSpan.FromHours(1));
updates.AddRange(rssChanges.Select(u => new ContentUpdate
{
Url = u.Url,
DetectedAt = u.PublishedAt,
Source = "rss",
Priority = ClassifyUrgency(u)
}));
updates.AddRange(sitemapChanges.Select(u => new ContentUpdate
{
Url = u.Url,
DetectedAt = u.LastModified,
Source = "sitemap",
Priority = ClassifyUrgency(u)
}));
// Deduplicate and prioritize
var prioritized = updates
.GroupBy(u => u.Url)
.Select(g => g.OrderByDescending(u => u.Priority).First())
.OrderByDescending(u => u.Priority)
.ToList();
// Enqueue for re-crawl
foreach (var update in prioritized)
{
await _priorityQueue.EnqueueAsync(update);
}
return new FreshnessReport
{
ChangesDetected = updates.Count,
HighPriorityUpdates = updates.Count(u => u.Priority > 0.8f),
ProcessedAt = DateTimeOffset.UtcNow
};
}
}
22. Security & Privacy
An AI search engine handles extremely sensitive data — user queries reveal their interests, knowledge gaps, professional needs, and personal concerns. Security and privacy must be foundational, not afterthought. The system must protect user data at rest and in transit, prevent prompt injection attacks, and comply with global privacy regulations.
Threat Model
| Threat | Attack Vector | Mitigation |
|---|---|---|
| Prompt Injection | Injected instructions in retrieved web content | Content sanitization, input/output guards |
| Data Exfiltration | Query crafted to extract other users' data | Tenant isolation, query sanitization |
| Jailbreaking | Bypassing safety filters | Multi-layer safety classifiers |
| Search Poisoning | Gaming source ranking to inject misinformation | Source reputation, cross-reference verification |
| Session Hijacking | Stealing user session tokens | Secure cookies, token rotation, HTTPS-only |
| API Abuse | Automated scraping of generated answers | Rate limiting, bot detection, CAPTCHA |
Security Implementation
public class QuerySecurityGuard
{
private readonly ISafetyClassifier _safetyClassifier;
private readonly IPromptInjectionDetector _injectionDetector;
private readonly ILogger<QuerySecurityGuard> _logger;
public async Task<SecurityResult> ValidateQueryAsync(
string query, string userId, string sessionId)
{
// Layer 1: Input length validation
if (query.Length > 5000)
return SecurityResult.Reject("Query exceeds maximum length");
// Layer 2: Prompt injection detection
var injectionResult = await _injectionDetector.DetectAsync(query);
if (injectionResult.IsInjection)
{
_logger.LogWarning(
"Prompt injection detected from user {UserId}: {Score}",
userId, injectionResult.Confidence);
return SecurityResult.Reject("Query contains disallowed content");
}
// Layer 3: Safety classification
var safetyResult = await _safetyClassifier.ClassifyAsync(query);
if (safetyResult.Category == SafetyCategory.Harmful)
return SecurityResult.Reject("Query violates usage policy");
if (safetyResult.Category == SafetyCategory.Sensitive)
{
return SecurityResult.AllowWithModification(
new ContentFilter
{
FilterPII = true,
FilterMedical = true,
FilterLegal = true,
DisclaimerRequired = true
});
}
// Layer 4: Rate limiting per user
var rateCheck = await CheckRateLimitAsync(userId, sessionId);
if (!rateCheck.IsAllowed)
return SecurityResult.RateLimited(rateCheck.RetryAfter);
return SecurityResult.Allow();
}
public async Task<OutputGuardResult> GuardOutputAsync(
string generatedAnswer, List<Citation> citations)
{
// Check for PII leakage in generated content
var piiCheck = DetectPII(generatedAnswer);
if (piiCheck.HasPII)
{
generatedAnswer = RedactPII(generatedAnswer, piiCheck.Findings);
}
// Validate all citations point to safe domains
var unsafeCitations = citations
.Where(c => IsUnsafeDomain(c.Domain))
.ToList();
if (unsafeCitations.Any())
{
citations = citations.Except(unsafeCitations).ToList();
}
return new OutputGuardResult
{
SanitizedAnswer = generatedAnswer,
SanitizedCitations = citations,
WasModified = piiCheck.HasPII || unsafeCitations.Any()
};
}
}
Privacy Architecture
User queries are encrypted at rest using AES-256 with per-user keys managed through a cloud KMS. Query logs are automatically purged after 90 days for free users and configurable retention for enterprise. No query data is shared with third-party LLM providers — all inference runs on self-hosted models or through enterprise agreements with data processing agreements (DPAs). The system supports GDPR right-to-erasure through automated data deletion pipelines that remove user data within 24 hours of request.
23. Monitoring & Observability
Operating an AI search engine requires monitoring a complex distributed system where traditional metrics (latency, throughput, error rates) must be supplemented with AI-specific quality metrics. A degradation in answer quality may not manifest as errors but rather as user dissatisfaction — measured through thumbs-down rates, query reformulation frequency, and thread abandonment.
Key Metrics Dashboard
| Metric | Category | Alert Threshold |
|---|---|---|
| Time-to-First-Token (P95) | Performance | > 500ms |
| End-to-End Latency (P95) | Performance | > 8s |
| Answer Generation Success Rate | Reliability | < 99% |
| Citation Verification Rate | Quality | < 95% |
| Hallucination Rate (sampled) | Quality | > 5% |
| User Thumbs-Down Rate | User Experience | > 10% |
| Query Rejection Rate | Safety | > 2% |
| LLM Token Usage | Cost | > 120% of budget |
| Crawl Queue Depth | Operations | > 1M pending |
| Vector DB Index Lag | Freshness | > 1 hour |
Observability Stack
public class SearchTelemetry
{
private readonly ITelemetryClient _telemetry;
public void TrackSearchMetrics(SearchMetrics metrics)
{
// Standard performance metrics
_telemetry.TrackMetric("search.latency.total_ms", metrics.TotalLatencyMs);
_telemetry.TrackMetric("search.latency.ttft_ms", metrics.TimeToFirstTokenMs);
_telemetry.TrackMetric("search.latency.retrieval_ms", metrics.RetrievalLatencyMs);
_telemetry.TrackMetric("search.latency.reranking_ms", metrics.RerankingLatencyMs);
_telemetry.TrackMetric("search.latency.generation_ms", metrics.GenerationLatencyMs);
// Quality metrics
_telemetry.TrackMetric("search.citations.count", metrics.CitationCount);
_telemetry.TrackMetric("search.citations.verified_rate", metrics.CitationVerifiedRate);
_telemetry.TrackMetric("search.quality.similarity_score", metrics.SimilarityScore);
// Cost metrics
_telemetry.TrackMetric("search.cost.input_tokens", metrics.InputTokens);
_telemetry.TrackMetric("search.cost.output_tokens", metrics.OutputTokens);
_telemetry.TrackMetric("search.cost.total_usd", metrics.CostUSD);
// User engagement
_telemetry.TrackEvent("search.completed", new Dictionary<string, string>
{
["intent"] = metrics.QueryClassification,
["model_used"] = metrics.ModelUsed,
["has_follow_up"] = metrics.HasFollowUp.ToString(),
["user_feedback"] = metrics.UserFeedback ?? "none"
});
// Distributed tracing
using var span = _telemetry.StartSpan("search.request");
span.SetAttribute("query.length", metrics.QueryLength);
span.SetAttribute("sources.retrieved", metrics.SourcesRetrieved);
span.SetAttribute("generation.tokens", metrics.OutputTokens);
}
}
24. Cost Estimation
Cost estimation for an AI search engine is complex because the major cost drivers — LLM inference and embedding generation — scale non-linearly with usage. Understanding the cost structure is essential for pricing decisions, capacity planning, and identifying optimization opportunities.
Monthly Cost Breakdown (at 50M queries/month)
| Component | Unit Cost | Monthly Cost | % of Total |
|---|---|---|---|
| LLM Inference (self-hosted) | $0.003/query | $150,000 | 30% |
| Embedding Generation | $0.0003/query | $15,000 | 3% |
| Vector DB (GPU-accelerated) | $80K/mo cluster | $80,000 | 16% |
| Elasticsearch Cluster | $50K/mo cluster | $50,000 | 10% |
| PostgreSQL | $20K/mo cluster | $20,000 | 4% |
| Redis Cache | $10K/mo cluster | $10,000 | 2% |
| Crawler Infrastructure | $30K/mo | $30,000 | 6% |
| CDN / Edge | $15K/mo | $15,000 | 3% |
| Compute (API, workers) | $60K/mo | $60,000 | 12% |
| Monitoring & Logging | $10K/mo | $10,000 | 2% |
| Storage (S3, block) | $20K/mo | $20,000 | 4% |
| Bandwidth | $10K/mo | $10,000 | 2% |
| Engineering Team (20 people) | $25K/person | $500,000 | ~included |
| Total Infrastructure | $470,000 | 100% |
25. Testing Strategy
Testing an AI search engine is fundamentally different from testing traditional software. The non-deterministic nature of LLM outputs means that simple assertion-based tests are insufficient. Instead, we must use a combination of evaluation benchmarks, statistical quality checks, regression test suites, and continuous human evaluation.
Testing Levels
| Level | Type | What We Test | Frequency |
|---|---|---|---|
| Unit | Component tests | Query parser, citation extractor, chunker | Every commit |
| Integration | Pipeline tests | Retrieval + generation end-to-end | Daily |
| Evaluation | Benchmark tests | Answer quality on curated Q&A dataset | Weekly |
| Regression | Golden set | Known-good answers for reference queries | Pre-deploy |
| A/B Testing | Live experiments | User satisfaction metrics | Continuous |
| Adversarial | Red team tests | Prompt injection, jailbreaking attempts | Weekly |
Answer Quality Evaluation Framework
public class AnswerQualityEvaluator
{
private readonly ILLMInference _judgeModel;
private readonly ICitationVerifier _citationVerifier;
public async Task<QualityReport> EvaluateAnswerAsync(
string query, GeneratedAnswer answer, List<RetrievedDocument> sources)
{
var report = new QualityReport();
// 1. Relevance scoring (1-5 scale)
report.RelevanceScore = await _judgeModel.CompleteAsync($@"
Rate the relevance of this answer to the query (1-5):
Query: ""{query}""
Answer: ""{answer.Content}""
Respond with just the number.", maxTokens: 5);
// 2. Citation accuracy
var citationResults = new List<CitationCheck>();
foreach (var citation in answer.Citations)
{
var verification = await _citationVerifier.VerifyCitationAsync(
citation, answer.Content);
citationResults.Add(new CitationCheck
{
CitationNumber = citation.CitationNumber,
IsVerified = verification.Status == "VERIFIED",
Confidence = verification.Confidence
});
}
report.CitationAccuracy = citationResults
.Count(c => c.IsVerified) / (float) citationResults.Count;
// 3. Hallucination check
report.HallucinationRate = await DetectHallucinationRateAsync(
answer.Content, sources);
// 4. Completeness
report.CompletenessScore = await _judgeModel.CompleteAsync($@"
Rate how completely this answer addresses all aspects of the query (1-5):
Query: ""{query}""
Answer: ""{answer.Content}""
Respond with just the number.", maxTokens: 5);
// 5. Overall quality composite
report.OverallScore =
(report.RelevanceScore * 0.3f) +
(report.CitationAccuracy * 0.25f) +
((1 - report.HallucinationRate) * 0.25f) +
(report.CompletenessScore * 0.2f);
return report;
}
}
26. Interview Q&A
System design interviews for AI search roles focus on the unique challenges of building RAG systems at scale. Below are the most frequently asked questions along with structured answer frameworks that demonstrate senior-level understanding.
Question 1: How would you design a Perplexity-style search engine from scratch?
Framework: Start by clarifying the scope (consumer vs. enterprise), then define the core pipeline: query understanding → retrieval → reranking → generation → citation mapping → quality verification. Discuss the hybrid retrieval strategy (vector + BM25), the importance of reranking, and the streaming architecture for real-time answers. Address the unique challenges: citation accuracy, hallucination prevention, and latency optimization.
Question 2: How do you handle citation accuracy and prevent hallucinations?
Framework: Explain the multi-layer approach. First, the retrieval step ensures relevant context is provided to the LLM. Second, the prompt engineering explicitly instructs the model to cite sources. Third, post-generation verification cross-references each citation claim against source content using semantic similarity. Fourth, a quality gate can reject or regenerate answers with high hallucination scores. Mention that citation accuracy degrades with context length, so strategic context truncation is important.
Question 3: How do you achieve sub-second latency for LLM-based search?
Framework: Emphasize parallel execution — retrieval, query understanding, and context preparation happen concurrently. Discuss KV-cache reuse for conversations, model routing (small models for simple queries), speculative decoding for throughput, edge caching for popular queries, and streaming to hide generation latency behind time-to-first-token. Mention that the P99 target is more relaxed (5-8 seconds) for deep research queries.
Question 4: How would you handle a breaking news event with 100× traffic spike?
Framework: Discuss auto-scaling for the API layer, priority re-crawling of news sources, edge caching of common news queries, and graceful degradation (falling back to cached or slightly stale answers). Mention the importance of the freshness pipeline — detecting breaking events via RSS and news APIs, triggering immediate re-crawls, and updating the index within minutes. Address the LLM inference scaling challenge — GPU capacity pre-provisioning with spot instances for burst handling.
Question 5: How do you evaluate the quality of generated answers?
Framework: Explain the multi-dimensional evaluation approach. Automated metrics include citation verification rate, hallucination detection (claim-level entailment checks), answer relevance scoring (using a judge model), and completeness assessment. User-facing metrics include thumbs-up/down ratios, query reformulation rates (users rephrasing suggests the first answer missed the mark), and thread continuation rates. Human evaluation provides ground truth calibration. A/B testing on answer variations helps identify which generation strategies produce the most satisfying results.
Question 6: Design the conversational follow-up system.
Framework: Start with conversation state management — maintaining thread history and using it to resolve ambiguous follow-up queries. Discuss the query rewriting pipeline that transforms context-dependent questions ("What about its performance?") into standalone retrieval queries. Explain the KV-cache optimization for conversations, where the system reuses cached key-value states from previous turns. Address follow-up suggestion generation and the importance of information gain scoring to ensure suggestions explore new dimensions of the topic.
Question 7: How would you handle adversarial prompts and prompt injection through retrieved web content?
Framework: This is a critical security concern. Explain the defense-in-depth approach. First, web content is sanitized before being included in the LLM context — HTML stripped, suspicious instruction patterns removed. Second, the system prompt explicitly instructs the LLM to ignore any instructions found within the source content. Third, an output guard checks for common prompt injection success patterns. Fourth, rate limiting and behavioral analysis detect automated probing. Mention that this is an arms race requiring continuous monitoring and adaptation.
Question 8: Compare vector search vs. BM25 for AI search. Why use both?
Framework: Vector search excels at semantic understanding — matching queries to passages that use different words but express the same concept. BM25 excels at exact keyword matching — critical for technical terms, proper nouns, code, and specific phrases. Neither alone is sufficient. Vector search misses exact matches; BM25 misses semantic similarity. Reciprocal rank fusion combines both result lists, and the cross-encoder reranker further refines the combined ranking. This hybrid approach consistently outperforms either method alone on retrieval benchmarks.
Question 9: How do you design the content freshness pipeline for a search engine?
Framework: Describe the multi-source change detection system (RSS feeds, sitemap monitoring, diff-based polling, news API aggregation). Explain the priority queue that ensures breaking news sources are re-crawled within minutes while evergreen content is refreshed daily or weekly. Discuss semantic change detection that avoids re-indexing pages with trivial changes (ad updates, timestamp shifts). Mention the cascade: change detection → priority queue → targeted re-crawl → content diff → selective re-embedding → index update → cache invalidation.
Question 10: How would you build enterprise search on top of this architecture?
Framework: Enterprise search adds access control, multi-tenancy, and compliance requirements. Explain document-level ACLs that filter search results based on user permissions. Discuss tenant isolation for data, LLM inference, and vector storage. Address compliance requirements (SOC 2 audit logging, GDPR data deletion, encryption at rest). Describe the connector architecture for integrating with enterprise data sources (SharePoint, Confluence, Slack, Google Drive). Mention custom knowledge base fine-tuning and the private deployment option for sensitive industries.
Question 11: Estimate the infrastructure cost for serving 10 million daily active users.
Framework: Work through the calculation: 10M DAU × 5 queries/day = 50M queries/day ≈ 580 average QPS, 5,800 burst QPS. LLM inference is the dominant cost — self-hosted 7B models on A100 GPUs need ~400 GPUs for burst capacity. Vector database (pgvector or Qdrant) needs GPU acceleration for the embedding search at this scale. Elasticsearch cluster for BM25 search, PostgreSQL for metadata, Redis for caching, and S3 for document storage. Total infrastructure cost: approximately $470K/month. Revenue needed to break even: approximately 500K Pro subscribers at $20/month.
Question 12: How do you handle multilingual queries?
Framework: Start with language detection on the query using fastText. For multilingual retrieval, use multilingual embedding models (e.g., multilingual-e5-large) that map queries and documents from different languages into the same vector space. The index stores documents in their original language with language metadata. For generation, the LLM must be multilingual or queries should be routed to language-specific models. Answer in the user's detected language. Cross-lingual retrieval is important — a Spanish query should find relevant English academic papers if no Spanish sources exist.