searchSemantic method

Future<KoolbaseSemanticSearchResult> searchSemantic({
  1. required String field,
  2. List<double>? queryVector,
  3. String? queryText,
  4. int limit = 20,
  5. Map<String, dynamic>? where,
  6. KoolbaseSearchMode mode = KoolbaseSearchMode.semantic,
  7. double? minSimilarity,
})

final result = await Koolbase.db.collection('articles').searchSemantic( field: 'content_embedding', queryText: 'how do I configure CI/CD?', limit: 10, );

// Client-side embedding (when you've already encoded the query): final result = await Koolbase.db.collection('articles').searchSemantic( field: 'content_embedding', queryVector: precomputed, limit: 10, );

// Hybrid search (vector + BM25, RRF-fused): final result = await Koolbase.db.collection('articles').searchSemantic( field: 'content_embedding', queryText: 'how do I configure CI/CD?', mode: KoolbaseSearchMode.hybrid, minSimilarity: 70, );


[mode] selects the retrieval strategy:
- [KoolbaseSearchMode.semantic] (default) — pure vector search via HNSW
- [KoolbaseSearchMode.lexical] — pure BM25 over the field's source text
- [KoolbaseSearchMode.hybrid] — vector + lexical fused with reciprocal
  rank fusion (k=60). Generally the strongest default for production.

[minSimilarity], if set (0..100), filters out results below the given
similarity percentage. Server-side filter — saves bandwidth on weak
matches. Only valid for semantic and hybrid; setting it on lexical
throws a server-side validation error (BM25 ranks aren't comparable
to cosine similarity).

Throws [KoolbaseNotFoundException] if [field] is not declared on
this collection. Throws [KoolbaseVectorDimensionMismatchException]
if [queryVector]'s length does not match the field's dimension.
Throws [ArgumentError] if both or neither of [queryVector] / [queryText]
are supplied, or if [minSimilarity] is outside 0..100.

Implementation

Future<KoolbaseSemanticSearchResult> searchSemantic({
  required String field,
  List<double>? queryVector,
  String? queryText,
  int limit = 20,
  Map<String, dynamic>? where,
  KoolbaseSearchMode mode = KoolbaseSearchMode.semantic,
  double? minSimilarity,
}) async {
  final hasVector = queryVector != null && queryVector.isNotEmpty;
  final hasText = queryText != null && queryText.trim().isNotEmpty;
  if (!hasVector && !hasText) {
    throw ArgumentError('Provide either queryVector or queryText.');
  }
  if (hasVector && hasText) {
    throw ArgumentError('Provide only one of queryVector or queryText.');
  }
  if (minSimilarity != null && (minSimilarity < 0 || minSimilarity > 100)) {
    throw ArgumentError(
        'minSimilarity must be between 0 and 100, got $minSimilarity.');
  }
  final body = <String, dynamic>{
    'collection': collectionName,
    'field': field,
    'limit': limit,
    if (hasVector) 'query_vector': queryVector,
    if (hasText) 'query_text': queryText,
    if (where != null && where.isNotEmpty) 'where': where,
    // Always send mode so the server uses the SDK's intent rather than
    // its own default. Omitting for `semantic` would also work (server
    // defaults to semantic) but explicit is safer if defaults ever shift.
    'mode': mode.wireValue,
    if (minSimilarity != null) 'min_similarity': minSimilarity,
  };
  final res = await _client
      .post(
        Uri.parse('$baseUrl/v1/sdk/db/search-semantic'),
        headers: await _headers(),
        body: jsonEncode(body),
      )
      .timeout(const Duration(seconds: 10));
  if (res.statusCode != 200) {
    throw await koolbaseDataErrorNotifying(res,
        onSessionExpired: _onSessionExpired,
        fallbackMessage: 'Semantic search failed');
  }
  final data = jsonDecode(res.body) as Map<String, dynamic>;
  final hits = (data['results'] as List<dynamic>? ?? [])
      .map((e) => KoolbaseSemanticHit.fromJson(e as Map<String, dynamic>))
      .toList(growable: false);
  return KoolbaseSemanticSearchResult(hits: hits, total: hits.length);
}