search method

Future<RagSearchResult> search(
  1. String query, {
  2. int topK = 10,
  3. int tokenBudget = 2000,
  4. ContextStrategy strategy = ContextStrategy.relevanceFirst,
  5. int adjacentChunks = 0,
  6. bool singleSourceMode = false,
})

Search for relevant chunks and assemble context for LLM.

adjacentChunks - Number of adjacent chunks to include before/after each matched chunk (default: 0). Setting this to 1 will include the chunk before and after each matched chunk, helping with long articles. singleSourceMode - If true, only include chunks from the most relevant source.

Implementation

Future<RagSearchResult> search(
  String query, {
  int topK = 10,
  int tokenBudget = 2000,
  ContextStrategy strategy = ContextStrategy.relevanceFirst,
  int adjacentChunks = 0,
  bool singleSourceMode = false,
}) async {
  // 1. Generate query embedding
  final queryEmbedding = await EmbeddingService.embed(query);

  // DEBUG: Log embedding stats
  final embNorm = queryEmbedding.fold<double>(0, (sum, v) => sum + v * v);
  print('[DEBUG] Query: "$query"');
  print(
    '[DEBUG] Embedding norm: ${embNorm.toStringAsFixed(4)}, dims: ${queryEmbedding.length}',
  );
  print(
    '[DEBUG] First 5 values: ${queryEmbedding.take(5).map((v) => v.toStringAsFixed(4)).toList()}',
  );

  // 2. Search chunks
  var chunks = await searchChunks(
    dbPath: dbPath,
    queryEmbedding: queryEmbedding,
    topK: topK,
  );

  // 3. Filter to single source FIRST (before adjacent expansion)
  // Pass the original query for text matching
  if (singleSourceMode && chunks.isNotEmpty) {
    chunks = _filterToMostRelevantSource(chunks, query);
  }

  // 4. Expand with adjacent chunks (only for the selected source)
  if (adjacentChunks > 0 && chunks.isNotEmpty) {
    chunks = await _expandWithAdjacentChunks(chunks, adjacentChunks);
  }

  // 5. Assemble context (pass singleSourceMode to skip headers when single source)
  final context = ContextBuilder.build(
    searchResults: chunks,
    tokenBudget: tokenBudget,
    strategy: strategy,
    singleSourceMode: singleSourceMode, // Pass through to skip headers
  );

  return RagSearchResult(chunks: chunks, context: context);
}