From d8db2c53c7b20a58cc523ea23609aae5228b9c17 Mon Sep 17 00:00:00 2001 From: iamvirul Date: Tue, 19 May 2026 10:05:20 +0530 Subject: [PATCH 1/3] feat: add min_score param to search_code --- src/vecgrep/server.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/vecgrep/server.py b/src/vecgrep/server.py index 95db51d..79d180d 100644 --- a/src/vecgrep/server.py +++ b/src/vecgrep/server.py @@ -726,7 +726,7 @@ def index_codebase( @mcp.tool() -def search_code(query: str, path: str, top_k: int = 8) -> str: +def search_code(query: str, path: str, top_k: int = 8, min_score: float = 0.35) -> str: """ Semantically search an indexed codebase for code relevant to a query. @@ -741,6 +741,9 @@ def search_code(query: str, path: str, top_k: int = 8) -> str: E.g. "how does authentication work", "database connection setup" path: Absolute path to the codebase root directory. top_k: Number of results to return (default 8, max 20). + min_score: Minimum cosine similarity score to include a result (default 0.35). + Results below this threshold are filtered out as noise. Set to 0.0 + to disable filtering. Returns: Formatted list of matching code chunks with file:line references and @@ -753,6 +756,7 @@ def search_code(query: str, path: str, top_k: int = 8) -> str: return "Error: query must not be empty" top_k = max(1, min(top_k, 20)) + min_score = max(0.0, min(min_score, 1.0)) root = Path(path).resolve() # Check if index has data @@ -782,6 +786,8 @@ def search_code(query: str, path: str, top_k: int = 8) -> str: query_vec = emb_provider.embed([query])[0] results = store.search(query_vec, top_k=top_k) + results = [r for r in results if r["score"] >= min_score] + if not results: return "No results found. Try re-indexing with index_codebase()." @@ -832,7 +838,8 @@ def get_index_status(path: str) -> str: f" Provider: {s['provider']}\n" f" Model: {s['model']}\n" f" Dimensions: {s['dims']}\n" - f" Compute device: {device_label}" + f" Compute device: {device_label}\n" + f" Min score: 0.35 (default, override via search_code min_score param)" ) except Exception as e: return f"Error: {e}" From 01d46c0cb8cd879df61fe0a67e32e733bb6d9ad5 Mon Sep 17 00:00:00 2001 From: iamvirul Date: Tue, 19 May 2026 10:05:24 +0530 Subject: [PATCH 2/3] test: add min_score filter tests --- tests/test_server.py | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/tests/test_server.py b/tests/test_server.py index c699c13..421c52d 100644 --- a/tests/test_server.py +++ b/tests/test_server.py @@ -137,6 +137,47 @@ def test_auto_indexes_on_first_search(self, tmp_path): result = search_code("compute function", str(tmp_path), top_k=5) assert "Error" not in result + def test_min_score_filters_low_scoring_results(self, tmp_path): + _write_py(tmp_path, "a.py", "def authenticate_user(username, password):\n pass\n") + _do_index(str(tmp_path)) + # Inject a result below threshold and one above + low = {"file_path": str(tmp_path / "a.py"), "start_line": 1, "end_line": 2, + "content": "def authenticate_user(username, password):\n pass", "score": 0.20} + high = {"file_path": str(tmp_path / "a.py"), "start_line": 1, "end_line": 2, + "content": "def authenticate_user(username, password):\n pass", "score": 0.80} + with patch("vecgrep.server.VectorStore.search", return_value=[high, low]): + result = search_code("auth", str(tmp_path), top_k=5, min_score=0.35) + assert "0.80" in result + assert "0.20" not in result + + def test_min_score_zero_disables_filtering(self, tmp_path): + _write_py(tmp_path, "a.py", "def foo(): pass\n") + _do_index(str(tmp_path)) + low = {"file_path": str(tmp_path / "a.py"), "start_line": 1, "end_line": 1, + "content": "def foo(): pass", "score": 0.10} + with patch("vecgrep.server.VectorStore.search", return_value=[low]): + result = search_code("foo", str(tmp_path), top_k=5, min_score=0.0) + assert "0.10" in result + + def test_min_score_clamped_above_one(self, tmp_path): + _write_py(tmp_path, "a.py", "def foo(): pass\n") + _do_index(str(tmp_path)) + perfect = {"file_path": str(tmp_path / "a.py"), "start_line": 1, "end_line": 1, + "content": "def foo(): pass", "score": 1.0} + with patch("vecgrep.server.VectorStore.search", return_value=[perfect]): + result = search_code("foo", str(tmp_path), top_k=5, min_score=2.0) + # Clamped to 1.0 — only a perfect score passes + assert "1.00" in result + + def test_min_score_all_filtered_returns_no_results(self, tmp_path): + _write_py(tmp_path, "a.py", "def foo(): pass\n") + _do_index(str(tmp_path)) + low = {"file_path": str(tmp_path / "a.py"), "start_line": 1, "end_line": 1, + "content": "def foo(): pass", "score": 0.10} + with patch("vecgrep.server.VectorStore.search", return_value=[low]): + result = search_code("foo", str(tmp_path), top_k=5, min_score=0.35) + assert "No results found" in result + # --------------------------------------------------------------------------- # Concurrency @@ -179,6 +220,7 @@ def test_returns_expected_fields(self, tmp_path): assert "Total chunks" in result assert "Last indexed" in result assert "Index size" in result + assert "Min score" in result def test_nonexistent_path_shows_zero_files(self, tmp_path): # No indexing — status should still return without raising From e98827ccce0776f262fb2e7837b496b746c24d49 Mon Sep 17 00:00:00 2001 From: iamvirul Date: Tue, 19 May 2026 10:06:11 +0530 Subject: [PATCH 3/3] docs: update changelog for min_score --- CHANGELOG.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b32f5e9..87d7a23 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,19 @@ All notable changes to VecGrep are documented here. --- +## [Unreleased] + +### Added + +- **`min_score` parameter on `search_code`** — filters out results below a + cosine similarity threshold before returning them. Default is `0.35`, which + cuts noise without affecting relevant results. Set to `0.0` to disable. + Values are clamped to `[0.0, 1.0]`. +- **`Min score` field in `get_index_status`** — surfaces the default threshold + so users know filtering is active and how to override it. + +--- + ## [1.7.0] — 2026-03-04 ### Added