Skip to content

Commit 77d6396

Browse files
committed
fix(search): filter session logs from retrieval
1 parent 4b9d17d commit 77d6396

5 files changed

Lines changed: 803 additions & 19 deletions

File tree

openviking/retrieve/hierarchical_retriever.py

Lines changed: 203 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@
1414
import time
1515
from datetime import datetime
1616
from typing import Any, Dict, List, Optional, Tuple
17+
from urllib.parse import unquote
1718

19+
from openviking.core.namespace import is_session_uri, uri_parts
1820
from openviking.core.retrieval_targets import default_target_directories
1921
from openviking.models.embedder.base import EmbedResult, embed_compat
2022
from openviking.models.rerank import RerankClient
@@ -36,12 +38,163 @@
3638

3739
logger = get_logger(__name__)
3840

41+
_INTERNAL_SESSION_LOG_FILENAMES = frozenset({"messages.jsonl"})
42+
_INTERNAL_SESSION_LOG_SIDECAR_FILENAMES = frozenset({".abstract.md", ".overview.md"})
43+
_SESSION_LOG_FILTER_OVERFETCH_FACTOR = 4
44+
_SESSION_LOG_FILTER_MAX_DECODE_PASSES = 3
45+
3946

4047
class RetrieverMode(str):
4148
THINKING = "thinking"
4249
QUICK = "quick"
4350

4451

52+
def _is_internal_session_log_uri(uri: str) -> bool:
53+
"""Return True for internal session transcript files that should not surface in recall."""
54+
normalized = uri.split("?", 1)[0].split("#", 1)[0].rstrip("/")
55+
scheme = "viking://"
56+
if not normalized.casefold().startswith(scheme):
57+
return False
58+
59+
path = normalized[len(scheme) :]
60+
for _ in range(_SESSION_LOG_FILTER_MAX_DECODE_PASSES):
61+
decoded = unquote(path)
62+
if decoded == path:
63+
break
64+
path = decoded
65+
path = path.split("?", 1)[0].split("#", 1)[0]
66+
normalized_uri = f"{scheme}{path.rstrip('/').casefold()}"
67+
if not is_session_uri(normalized_uri):
68+
return False
69+
70+
parts = uri_parts(normalized_uri)
71+
root_depth = _session_root_depth(parts)
72+
if root_depth is None or len(parts) <= root_depth:
73+
return False
74+
75+
tail = parts[root_depth:]
76+
return _is_internal_session_log_tail(tail)
77+
78+
79+
def _session_root_depth(parts: List[str]) -> Optional[int]:
80+
if len(parts) >= 2 and parts[0] == "session":
81+
return 2
82+
if len(parts) >= 3 and parts[:2] == ["user", "sessions"]:
83+
return 3
84+
if len(parts) >= 4 and parts[0] == "user" and parts[2] == "sessions":
85+
return 4
86+
return None
87+
88+
89+
def _is_internal_session_log_tail(tail: List[str]) -> bool:
90+
if _is_session_transcript_tail(tail):
91+
return True
92+
if len(tail) >= 3 and tail[0] == "history" and tail[1].startswith("archive_"):
93+
return _is_session_transcript_tail(tail[2:])
94+
return False
95+
96+
97+
def _is_session_transcript_tail(tail: List[str]) -> bool:
98+
if tail == ["messages.jsonl"] or tail in (
99+
[".abstract.md"],
100+
[".overview.md"],
101+
):
102+
return True
103+
if (
104+
len(tail) == 2
105+
and tail[0] in _INTERNAL_SESSION_LOG_FILENAMES
106+
and tail[1] in _INTERNAL_SESSION_LOG_SIDECAR_FILENAMES
107+
):
108+
return True
109+
return False
110+
111+
112+
def _session_log_filtered_fetch_limit(limit: int, minimum: int) -> int:
113+
return max(limit * _SESSION_LOG_FILTER_OVERFETCH_FACTOR, minimum)
114+
115+
116+
async def _search_in_tenant_excluding_session_logs(
117+
vector_proxy: VikingDBManagerProxy,
118+
*,
119+
desired_limit: int,
120+
page_limit: int,
121+
query_vector: Optional[List[float]],
122+
sparse_query_vector: Optional[Dict[str, float]],
123+
context_type: Optional[str],
124+
target_directories: List[str],
125+
extra_filter: Optional[FilterExpr | Dict[str, Any]],
126+
level: Optional[List[int]],
127+
max_scan_pages: int,
128+
) -> tuple[List[Dict[str, Any]], int, int]:
129+
results: List[Dict[str, Any]] = []
130+
searches = 0
131+
scanned = 0
132+
offset = 0
133+
for _ in range(max_scan_pages):
134+
page = await vector_proxy.search_in_tenant(
135+
query_vector=query_vector,
136+
sparse_query_vector=sparse_query_vector,
137+
context_type=context_type,
138+
target_directories=target_directories,
139+
extra_filter=extra_filter,
140+
level=level,
141+
limit=page_limit,
142+
offset=offset,
143+
)
144+
searches += 1
145+
scanned += len(page)
146+
results.extend(
147+
result
148+
for result in page
149+
if not _is_internal_session_log_uri(str(result.get("uri", "")))
150+
)
151+
if len(results) >= desired_limit or len(page) < page_limit:
152+
break
153+
offset += page_limit
154+
return results[:desired_limit], searches, scanned
155+
156+
157+
async def _search_children_excluding_session_logs(
158+
vector_proxy: VikingDBManagerProxy,
159+
*,
160+
parent_uri: str,
161+
desired_limit: int,
162+
page_limit: int,
163+
query_vector: Optional[List[float]],
164+
sparse_query_vector: Optional[Dict[str, float]],
165+
context_type: Optional[str],
166+
target_directories: Optional[List[str]],
167+
extra_filter: Optional[FilterExpr | Dict[str, Any]],
168+
max_scan_pages: int,
169+
) -> tuple[List[Dict[str, Any]], int, int]:
170+
results: List[Dict[str, Any]] = []
171+
searches = 0
172+
scanned = 0
173+
offset = 0
174+
for _ in range(max_scan_pages):
175+
page = await vector_proxy.search_children_in_tenant(
176+
parent_uri=parent_uri,
177+
query_vector=query_vector,
178+
sparse_query_vector=sparse_query_vector,
179+
context_type=context_type,
180+
target_directories=target_directories,
181+
extra_filter=extra_filter,
182+
limit=page_limit,
183+
offset=offset,
184+
)
185+
searches += 1
186+
scanned += len(page)
187+
results.extend(
188+
result
189+
for result in page
190+
if not _is_internal_session_log_uri(str(result.get("uri", "")))
191+
)
192+
if len(results) >= desired_limit or len(page) < page_limit:
193+
break
194+
offset += page_limit
195+
return results[:desired_limit], searches, scanned
196+
197+
45198
class HierarchicalRetriever:
46199
"""Hierarchical retriever with dense and sparse vector support."""
47200

@@ -73,6 +226,9 @@ def __init__(
73226
self.retrieval_config = retrieval_config or RetrievalConfig()
74227
self.hotness_alpha = self.retrieval_config.hotness_alpha
75228
self.score_propagation_alpha = self.retrieval_config.score_propagation_alpha
229+
self.session_log_filter_max_scan_pages = (
230+
self.retrieval_config.session_log_filter_max_scan_pages
231+
)
76232

77233
# Use rerank threshold if available, otherwise use a default
78234
self.threshold = rerank_config.threshold if rerank_config else 0
@@ -151,19 +307,27 @@ async def retrieve(
151307
context_type = query.context_type.value if query.context_type else None
152308

153309
if mode == RetrieverMode.QUICK:
310+
quick_fetch_limit = _session_log_filtered_fetch_limit(limit, self.GLOBAL_SEARCH_TOPK)
154311
with telemetry.measure("search.vector_retrieval"):
155-
quick_results = await vector_proxy.search_in_tenant(
312+
(
313+
quick_results,
314+
search_count,
315+
scanned_count,
316+
) = await _search_in_tenant_excluding_session_logs(
317+
vector_proxy,
318+
desired_limit=max(limit, self.GLOBAL_SEARCH_TOPK),
319+
page_limit=quick_fetch_limit,
156320
query_vector=query_vector,
157321
sparse_query_vector=sparse_query_vector,
158322
context_type=context_type,
159323
target_directories=target_dirs,
160324
extra_filter=scope_dsl,
161325
level=level,
162-
limit=max(limit, self.GLOBAL_SEARCH_TOPK),
326+
max_scan_pages=self.session_log_filter_max_scan_pages,
163327
)
164-
telemetry.count("vector.searches", 1)
165-
telemetry.count("vector.scored", len(quick_results))
166-
telemetry.count("vector.scanned", len(quick_results))
328+
telemetry.count("vector.searches", search_count)
329+
telemetry.count("vector.scored", scanned_count)
330+
telemetry.count("vector.scanned", scanned_count)
167331

168332
collected_by_uri: Dict[str, Dict[str, Any]] = {}
169333
for result in quick_results:
@@ -192,19 +356,27 @@ async def retrieve(
192356
rerank_used = False
193357
else:
194358
# Step 2: Global vector search to supplement starting points
359+
global_fetch_limit = max(limit, self.GLOBAL_SEARCH_TOPK)
195360
with telemetry.measure("search.vector_retrieval"):
196-
global_results = await vector_proxy.search_in_tenant(
361+
(
362+
global_results,
363+
search_count,
364+
scanned_count,
365+
) = await _search_in_tenant_excluding_session_logs(
366+
vector_proxy,
367+
desired_limit=global_fetch_limit,
368+
page_limit=global_fetch_limit,
197369
query_vector=query_vector,
198370
sparse_query_vector=sparse_query_vector,
199371
context_type=context_type,
200372
target_directories=target_dirs,
201373
extra_filter=scope_dsl,
202374
level=[0, 1],
203-
limit=max(limit, self.GLOBAL_SEARCH_TOPK),
375+
max_scan_pages=self.session_log_filter_max_scan_pages,
204376
)
205-
telemetry.count("vector.searches", 1)
206-
telemetry.count("vector.scored", len(global_results))
207-
telemetry.count("vector.scanned", len(global_results))
377+
telemetry.count("vector.searches", search_count)
378+
telemetry.count("vector.scored", scanned_count)
379+
telemetry.count("vector.scanned", scanned_count)
208380

209381
# Debug: Print all URIs in global_results
210382
if logger.isEnabledFor(logging.DEBUG):
@@ -389,7 +561,7 @@ async def _recursive_search(
389561
if initial_candidates:
390562
for r in initial_candidates:
391563
uri = r.get("uri", "")
392-
if not uri:
564+
if not uri or _is_internal_session_log_uri(uri):
393565
continue
394566
if level is None or r.get("level", 2) in level:
395567
score = self._finite_score(r.get("_score", 0.0))
@@ -408,17 +580,25 @@ async def _recursive_search(
408580

409581
# Initialize: process starting points
410582
for uri, score in starting_points:
583+
if _is_internal_session_log_uri(uri):
584+
continue
411585
heapq.heappush(dir_queue, (-score, uri))
412586

413-
async def search_children(current_uri: str) -> List[Dict[str, Any]]:
414-
return await vector_proxy.search_children_in_tenant(
587+
child_desired_limit = max(limit * 2, 20)
588+
child_fetch_limit = _session_log_filtered_fetch_limit(limit, 20)
589+
590+
async def search_children(current_uri: str) -> tuple[List[Dict[str, Any]], int, int]:
591+
return await _search_children_excluding_session_logs(
592+
vector_proxy,
415593
parent_uri=current_uri,
594+
desired_limit=child_desired_limit,
595+
page_limit=child_fetch_limit,
416596
query_vector=query_vector,
417-
sparse_query_vector=sparse_query_vector, # Pass sparse vector
597+
sparse_query_vector=sparse_query_vector,
418598
context_type=context_type,
419599
target_directories=target_dirs,
420600
extra_filter=scope_dsl,
421-
limit=max(limit * 2, 20),
601+
max_scan_pages=self.session_log_filter_max_scan_pages,
422602
)
423603

424604
parallelism = max(1, self.MAX_PARALLEL_CHILD_SEARCHES)
@@ -442,10 +622,12 @@ async def search_children(current_uri: str) -> List[Dict[str, Any]]:
442622
)
443623

444624
telemetry = get_current_telemetry()
445-
for (_, current_score), results in zip(batch, batch_results, strict=True):
446-
telemetry.count("vector.searches", 1)
447-
telemetry.count("vector.scored", len(results))
448-
telemetry.count("vector.scanned", len(results))
625+
for (_, current_score), (results, search_count, scanned_count) in zip(
626+
batch, batch_results, strict=True
627+
):
628+
telemetry.count("vector.searches", search_count)
629+
telemetry.count("vector.scored", scanned_count)
630+
telemetry.count("vector.scanned", scanned_count)
449631

450632
if not results:
451633
continue
@@ -457,6 +639,8 @@ async def search_children(current_uri: str) -> List[Dict[str, Any]]:
457639

458640
for r, score in zip(results, query_scores, strict=True):
459641
uri = r.get("uri", "")
642+
if not uri or _is_internal_session_log_uri(uri):
643+
continue
460644
final_score = (
461645
alpha * score + (1 - alpha) * current_score if current_score else score
462646
)

openviking/storage/viking_vector_index_backend.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1125,6 +1125,7 @@ async def search_children_in_tenant(
11251125
target_directories: Optional[List[str]] = None,
11261126
extra_filter: Optional[FilterExpr | Dict[str, Any]] = None,
11271127
limit: int = 10,
1128+
offset: int = 0,
11281129
) -> List[Dict[str, Any]]:
11291130
# TODO:Better Alternative to Current Temporary Fix
11301131

@@ -1156,6 +1157,7 @@ async def search_children_in_tenant(
11561157
sparse_query_vector=sparse_query_vector,
11571158
filter=merged_filter,
11581159
limit=limit,
1160+
offset=offset,
11591161
output_fields=RETRIEVAL_OUTPUT_FIELDS,
11601162
ctx=ctx,
11611163
)

0 commit comments

Comments
 (0)