-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsearch.py
More file actions
124 lines (94 loc) · 3.17 KB
/
Copy pathsearch.py
File metadata and controls
124 lines (94 loc) · 3.17 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
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
68
69
70
71
72
73
74
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
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
# search.py
#
# Semantic search over embedded chunks.
# Uses cosine similarity to find the most relevant code/text for a query.
import math
from typing import List, Tuple
from dataclasses import dataclass
from config import CANDIDATE_CHUNK_LIMIT
from indexer import Chunk
from embedding import EmbeddedChunk, embed_query
@dataclass
class SearchResult:
"""A search result with relevance score."""
chunk: Chunk
score: float # cosine similarity (0 to 1, higher is better)
def cosine_similarity(vec_a: List[float], vec_b: List[float]) -> float:
"""
Compute cosine similarity between two vectors.
Args:
vec_a: First vector.
vec_b: Second vector.
Returns:
Cosine similarity score between -1 and 1.
"""
if len(vec_a) != len(vec_b):
return 0.0
dot_product = sum(a * b for a, b in zip(vec_a, vec_b))
magnitude_a = math.sqrt(sum(a * a for a in vec_a))
magnitude_b = math.sqrt(sum(b * b for b in vec_b))
if magnitude_a == 0 or magnitude_b == 0:
return 0.0
return dot_product / (magnitude_a * magnitude_b)
def search(
query: str,
embedded_chunks: List[EmbeddedChunk],
top_k: int = CANDIDATE_CHUNK_LIMIT,
) -> List[SearchResult]:
"""
Search for chunks most semantically similar to the query.
Args:
query: Natural language search query.
embedded_chunks: List of chunks with their embeddings.
top_k: Number of top results to return.
Returns:
List of SearchResult objects sorted by relevance (highest first).
"""
# Get embedding for the query
query_embedding = embed_query(query)
if query_embedding is None:
print("[search] Failed to embed query.")
return []
# Score all chunks
scored: List[Tuple[float, EmbeddedChunk]] = []
for ec in embedded_chunks:
score = cosine_similarity(query_embedding, ec.embedding)
scored.append((score, ec))
# Sort by score (descending) and take top_k
scored.sort(key=lambda x: x[0], reverse=True)
top_results = scored[:top_k]
# Convert to SearchResult objects
results = [
SearchResult(chunk=ec.chunk, score=score)
for score, ec in top_results
]
return results
def format_results(results: List[SearchResult]) -> str:
"""
Format search results for display.
Args:
results: List of SearchResult objects.
Returns:
Formatted string showing the results.
"""
if not results:
return "No results found."
lines = []
lines.append(f"Found {len(results)} relevant chunks:\n")
lines.append("-" * 60)
for i, result in enumerate(results, 1):
chunk = result.chunk
lines.append(
f"\n[{i}] {chunk.file_path} "
f"(lines {chunk.start_line}-{chunk.end_line}) "
f"[score: {result.score:.4f}]"
)
lines.append("-" * 60)
# Show a preview of the chunk (first 5 lines)
preview_lines = chunk.text.split("\n")[:5]
preview = "\n".join(preview_lines)
if len(chunk.text.split("\n")) > 5:
preview += "\n..."
lines.append(preview)
lines.append("-" * 60)
return "\n".join(lines)