Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion openviking/retrieve/hierarchical_retriever.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
from openviking.storage.semantic_sidecar import body_for_preview
from openviking.storage.vikingdb_manager import VikingDBManager, VikingDBManagerProxy
from openviking.telemetry import get_current_telemetry
from openviking.utils.tags import normalize_search_tags
from openviking.utils.time_utils import parse_iso_datetime
from openviking.utils.token_estimation import (
estimate_text_tokens,
Expand Down Expand Up @@ -621,7 +622,9 @@ async def _convert_to_matched_contexts(
abstract=abstract,
category=c.get("category", ""),
score=final_score,
search_tags=list(c.get("search_tags") or []),
search_tags=normalize_search_tags(
c.get("search_tags"), discard_invalid=True
),
)
)

Expand Down
4 changes: 3 additions & 1 deletion openviking_cli/retrieve/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
from enum import Enum
from typing import Any, Dict, List, Optional

from openviking.utils.tags import normalize_search_tags


class ContextType(str, Enum):
"""Context type for retrieval."""
Expand Down Expand Up @@ -377,7 +379,7 @@ def _context_to_dict(self, ctx: MatchedContext) -> Dict[str, Any]:
"level": ctx.level,
"score": ctx.score,
"abstract": ctx.abstract,
"tags": ctx.search_tags,
"tags": normalize_search_tags(ctx.search_tags, discard_invalid=True),
}

def _query_to_dict(self, q: TypedQuery) -> Dict[str, Any]:
Expand Down
2 changes: 1 addition & 1 deletion tests/retrieve/test_hierarchical_retriever_rerank.py
Original file line number Diff line number Diff line change
Expand Up @@ -639,7 +639,7 @@ async def test_convert_to_matched_contexts_propagates_search_tags():
"viking://resources/file-a",
1.0,
abstract="child A",
search_tags=["team=infra", "project=viking"],
search_tags=["default", "team=infra", "bad=", "project=viking"],
)
],
ctx=_ctx(),
Expand Down
15 changes: 15 additions & 0 deletions tests/retrieve/test_provenance.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,21 @@ def test_context_to_dict_exposes_search_tags_as_tags(self):
assert d["resources"][0]["tags"] == ["team=infra", "project=viking"]
assert "search_tags" not in d["resources"][0]

def test_context_to_dict_discards_legacy_invalid_tags(self):
ctx = MatchedContext(
uri="viking://resources/docs/arch.md",
context_type=ContextType.RESOURCE,
level=2,
search_tags=["default", "team=infra", "bad=", "project=viking"],
)

result = FindResult(memories=[], resources=[ctx], skills=[])

assert result.to_dict()["resources"][0]["tags"] == [
"team=infra",
"project=viking",
]

def test_context_to_dict_defaults_empty_tags(self):
ctx = MatchedContext(
uri="viking://resources/docs/arch.md",
Expand Down
36 changes: 36 additions & 0 deletions tests/server/test_api_search.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from openviking.storage.viking_fs import VikingFS
from openviking.utils.time_utils import parse_iso_datetime
from openviking_cli.exceptions import InvalidArgumentError
from openviking_cli.retrieve import ContextType, FindResult, MatchedContext
from openviking_cli.session.user_id import UserIdentifier


Expand Down Expand Up @@ -55,6 +56,41 @@ async def test_find_basic(client_with_resource):
assert "telemetry" not in body


@pytest.mark.parametrize(
("endpoint", "service_method"),
[
("/api/v1/search/find", "find"),
("/api/v1/search/search", "search"),
],
)
async def test_search_endpoints_filter_invalid_result_tags(
client: httpx.AsyncClient, service, monkeypatch, endpoint: str, service_method: str
):
async def fake_search(**kwargs):
del kwargs
return FindResult(
memories=[],
resources=[
MatchedContext(
uri="viking://resources/legacy-tags.md",
context_type=ContextType.RESOURCE,
search_tags=["default", "team=infra", "bad=", "project=viking"],
)
],
skills=[],
)

monkeypatch.setattr(service.search, service_method, fake_search)

response = await client.post(endpoint, json={"query": "legacy tags"})

assert response.status_code == 200
assert response.json()["result"]["resources"][0]["tags"] == [
"team=infra",
"project=viking",
]


@pytest.mark.parametrize("endpoint", ["/api/v1/search/find", "/api/v1/search/search"])
async def test_search_endpoints_reject_unknown_request_fields(
client: httpx.AsyncClient,
Expand Down