Class: SwarmMemory::Search::SemanticSearch

Inherits:
Object
  • Object
show all
Defined in:
lib/swarm_memory/search/semantic_search.rb

Overview

Semantic search using embedding similarity

Finds entries similar to a query based on embedding vectors rather than exact text matching.

Instance Method Summary collapse

Constructor Details

#initialize(adapter:, embedder:) ⇒ SemanticSearch

Initialize semantic search

Parameters:



14
15
16
17
# File 'lib/swarm_memory/search/semantic_search.rb', line 14

def initialize(adapter:, embedder:)
  @adapter = adapter
  @embedder = embedder
end

Instance Method Details

#find_similar(query:, top_k: 5, threshold: 0.7) ⇒ Array<Hash>

Search for entries similar to query

Examples:

results = search.find_similar(
  query: "How do I test Ruby code?",
  top_k: 5,
  threshold: 0.7
)
# => [
#   { path: "skills/testing/minitest", similarity: 0.92, title: "..." },
#   { path: "concepts/ruby/testing", similarity: 0.85, title: "..." }
# ]

Parameters:

  • query (String)

    Search query

  • top_k (Integer) (defaults to: 5)

    Number of results to return

  • threshold (Float) (defaults to: 0.7)

    Minimum similarity threshold (0.0-1.0)

Returns:

  • (Array<Hash>)

    Ranked results with similarity scores

Raises:

  • (ArgumentError)


36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# File 'lib/swarm_memory/search/semantic_search.rb', line 36

def find_similar(query:, top_k: 5, threshold: 0.7)
  raise ArgumentError, "query is required" if query.nil? || query.to_s.strip.empty?

  # Generate query embedding
  query_embedding = @embedder.embed(query)

  # Get all entries with embeddings
  all_entries = @adapter.all_entries
  entries_with_embeddings = all_entries.select { |_, entry| entry.embedded? }

  return [] if entries_with_embeddings.empty?

  # Calculate similarities
  similarities = entries_with_embeddings.map do |path, entry|
    similarity = TextSimilarity.cosine(query_embedding, entry.embedding)

    {
      path: path,
      title: entry.title,
      similarity: similarity,
      updated_at: entry.updated_at,
    }
  end

  # Filter by threshold and sort by similarity (descending)
  results = similarities
    .select { |r| r[:similarity] >= threshold }
    .sort_by { |r| -r[:similarity] }
    .take(top_k)

  results
end

#find_similar_to_entry(file_path:, top_k: 5, threshold: 0.7) ⇒ Array<Hash>

Find entries similar to a given entry

Parameters:

  • file_path (String)

    Path to reference entry

  • top_k (Integer) (defaults to: 5)

    Number of results to return

  • threshold (Float) (defaults to: 0.7)

    Minimum similarity threshold

Returns:

  • (Array<Hash>)

    Ranked results (excluding the reference entry)



75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
# File 'lib/swarm_memory/search/semantic_search.rb', line 75

def find_similar_to_entry(file_path:, top_k: 5, threshold: 0.7)
  # Get reference entry
  reference_entry = @adapter.read_entry(file_path: file_path)

  unless reference_entry.embedded?
    raise SearchError, "Entry #{file_path} has no embedding. Cannot perform semantic search."
  end

  # Get all entries with embeddings (excluding reference)
  all_entries = @adapter.all_entries
  entries_with_embeddings = all_entries
    .select { |path, entry| path != file_path && entry.embedded? }

  return [] if entries_with_embeddings.empty?

  # Calculate similarities
  similarities = entries_with_embeddings.map do |path, entry|
    similarity = TextSimilarity.cosine(reference_entry.embedding, entry.embedding)

    {
      path: path,
      title: entry.title,
      similarity: similarity,
      updated_at: entry.updated_at,
    }
  end

  # Filter by threshold and sort by similarity (descending)
  results = similarities
    .select { |r| r[:similarity] >= threshold }
    .sort_by { |r| -r[:similarity] }
    .take(top_k)

  results
end