knowledge-rag 4.3.1__tar.gz → 4.5.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {knowledge_rag-4.3.1 → knowledge_rag-4.5.0}/PKG-INFO +23 -1
- {knowledge_rag-4.3.1 → knowledge_rag-4.5.0}/README.md +22 -0
- {knowledge_rag-4.3.1 → knowledge_rag-4.5.0}/mcp_server/__init__.py +1 -1
- {knowledge_rag-4.3.1 → knowledge_rag-4.5.0}/mcp_server/server.py +61 -6
- {knowledge_rag-4.3.1 → knowledge_rag-4.5.0}/pyproject.toml +1 -1
- {knowledge_rag-4.3.1 → knowledge_rag-4.5.0}/.gitignore +0 -0
- {knowledge_rag-4.3.1 → knowledge_rag-4.5.0}/LICENSE +0 -0
- {knowledge_rag-4.3.1 → knowledge_rag-4.5.0}/config.example.yaml +0 -0
- {knowledge_rag-4.3.1 → knowledge_rag-4.5.0}/mcp_server/config.py +0 -0
- {knowledge_rag-4.3.1 → knowledge_rag-4.5.0}/mcp_server/guarded.py +0 -0
- {knowledge_rag-4.3.1 → knowledge_rag-4.5.0}/mcp_server/ingestion.py +0 -0
- {knowledge_rag-4.3.1 → knowledge_rag-4.5.0}/mcp_server/instance_lock.py +0 -0
- {knowledge_rag-4.3.1 → knowledge_rag-4.5.0}/mcp_server/metrics.py +0 -0
- {knowledge_rag-4.3.1 → knowledge_rag-4.5.0}/mcp_server/preflight.py +0 -0
- {knowledge_rag-4.3.1 → knowledge_rag-4.5.0}/mcp_server/ratelimit.py +0 -0
- {knowledge_rag-4.3.1 → knowledge_rag-4.5.0}/npm/README.md +0 -0
- {knowledge_rag-4.3.1 → knowledge_rag-4.5.0}/presets/cybersecurity.yaml +0 -0
- {knowledge_rag-4.3.1 → knowledge_rag-4.5.0}/presets/developer.yaml +0 -0
- {knowledge_rag-4.3.1 → knowledge_rag-4.5.0}/presets/general.yaml +0 -0
- {knowledge_rag-4.3.1 → knowledge_rag-4.5.0}/presets/research.yaml +0 -0
- {knowledge_rag-4.3.1 → knowledge_rag-4.5.0}/requirements.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: knowledge-rag
|
|
3
|
-
Version: 4.
|
|
3
|
+
Version: 4.5.0
|
|
4
4
|
Summary: Local RAG System for Claude Code — Hybrid search + Cross-encoder Reranking + 13 MCP Tools + 20 Format Parsers. Zero external servers.
|
|
5
5
|
Project-URL: Homepage, https://github.com/lyonzin/knowledge-rag
|
|
6
6
|
Project-URL: Repository, https://github.com/lyonzin/knowledge-rag
|
|
@@ -1449,6 +1449,28 @@ Common issues:
|
|
|
1449
1449
|
|
|
1450
1450
|
### Unreleased
|
|
1451
1451
|
|
|
1452
|
+
### v4.5.0 (2026-07-06) — Hybrid Search Ranking & Routing Fix
|
|
1453
|
+
|
|
1454
|
+
- **FIX**: `search_knowledge` now searches the entire index when the user omits `category_filter`. The internal `_route_by_keywords()` heuristic previously acted as a hard where-filter on both the semantic and BM25 branches, so queries whose terms happened to map to a sparsely-populated category could return two documents while thousands of relevant chunks in other categories were silently dropped. The router is now **informational only**: the `routed_by` field is still populated in every result for telemetry (public API unchanged), but the candidate set is never restricted. Users who want a hard filter still get it by passing `category_filter=...` explicitly — which continues to filter BM25 consistently with #109. (#112)
|
|
1455
|
+
- **NEW**: Hybrid search adds a small bounded ranking boost when query terms match indexed `source` or `filename` metadata, helping navigational queries surface the canonical file for a topic instead of adjacent files that only cross-reference it. The boost is capped at ~20% of typical RRF magnitudes, so it breaks near-ties without overriding semantic or BM25 signal. Public API unchanged. (#110, thanks @Hohlas)
|
|
1456
|
+
- **TEST**: New `TestKeywordRoutingBehavior` (3 tests) pins the routing fix — BM25 and semantic branches must both see the full corpus when no explicit filter is passed, and explicit `category_filter` still overrides the router. New `TestPathAwareRanking` covers the path-metadata boost. Baseline: 266 → 271.
|
|
1457
|
+
- **UPGRADE NOTE**: warm `query_cache` entries from before v4.5.0 should be invalidated by restarting the server so cached responses no longer reflect the pre-fix restrictive behavior.
|
|
1458
|
+
|
|
1459
|
+
### v4.4.0 (2026-07-06) — Cross-Platform Installer & Hybrid Search Category Filter
|
|
1460
|
+
|
|
1461
|
+
- **NEW**: Cross-platform, multi-LLM-client installer (`install.py`) driving both `install.sh` (Linux/macOS) and `install.ps1` (Windows) as thin wrappers. One codebase, one behavior across every OS.
|
|
1462
|
+
- **NEW**: Auto-detects and registers `knowledge-rag` in 8 LLM clients — Claude Code, Claude Desktop, Cursor, Windsurf, VS Code (Copilot Chat), Cline, Gemini CLI, Zed — writing to each tool's canonical config path with the correct JSON schema per client (VS Code uses `servers`, Zed uses `context_servers`, everyone else uses `mcpServers`).
|
|
1463
|
+
- **NEW**: `--for <clients>` / `--exclude <clients>` opt-in/opt-out selection, `--dry-run` preview, `--list-clients` registry inspection, `--pypi-version <ver>` pinning, `--skip-init` / `--skip-model` for fast reruns. See `python install.py --help`.
|
|
1464
|
+
- **FIX**: `install.ps1` no longer writes MCP config to `~/.claude/mcp.json` (a stale secondary path); it now targets `~/.claude.json` — the file Claude Code actually reads — via idempotent JSON merge that preserves every existing MCP server entry with an automatic `.knowledge-rag.bak` backup.
|
|
1465
|
+
- **FIX**: `install.ps1` gains PyPI mode (`pip install knowledge-rag`) and runs `mcp_server.server init` on install — feature parity with `install.sh`.
|
|
1466
|
+
- **FIX**: MCP server spec no longer uses the fragile `cmd /c cd /d ... && python ...` wrapper on Windows; it emits the standard `command` + `cwd` shape supported natively by every modern client.
|
|
1467
|
+
- **FIX**: `install.sh` guards against `sh install.sh` (bash-only features now emit a clear error instead of a cryptic syntax failure).
|
|
1468
|
+
- **FIX**: Both scripts now correctly advertise **13 MCP tools** (was outdated at 12; `get_reindex_status` shipped in v4.3.0).
|
|
1469
|
+
- **FIX**: Windows-side `install.ps1` prefers `winget install Python.Python.3.12 --scope user` (no admin), falls back to python.org 3.12.7 (was pinned to 3.12.0).
|
|
1470
|
+
- **FIX**: Hybrid search now applies the active category filter to BM25 results before RRF fusion, preventing keyword-only BM25 hits from other categories from leaking into filtered searches. Both leak paths are closed: BM25 candidates are metadata-filtered before RRF (with `top_k` widened to `max_results * 20` to compensate for post-filter drop), and the fallback fetch during fusion re-checks `category` before adding a chunk to `combined_scores`. Note: the `category_filter` guard now also applies to the keyword-routed category — `_route_by_keywords()`-inferred routing filters BM25 too, consistent with the semantic branch. Users with warm `query_cache` entries should restart the server to invalidate stale results. (#109, thanks @Hohlas)
|
|
1471
|
+
- **TEST**: New `tests/test_installer_no_data_loss.py` (22 tests) locks in the installer's zero-data-loss contract across all three JSON schemas (`mcpServers` / `servers` / `context_servers`): top-level keys preserved, sibling MCP servers byte-identical, `.knowledge-rag.bak` backup written before every mutation, idempotent second run, `--dry-run` writes nothing, atomic `os.replace` write. Baseline: 231 → 266.
|
|
1472
|
+
- **TEST**: New `TestHybridCategoryFilter::test_bm25_results_respect_category_filter` in `tests/test_search.py` — deterministic regression covering BM25-only hits with mixed categories. Baseline: 266 → 267.
|
|
1473
|
+
|
|
1452
1474
|
### v4.3.1 (2026-06-22) — Hybrid Search Fixes
|
|
1453
1475
|
|
|
1454
1476
|
- **FIX**: Accept `"general"` as a valid category in `search_knowledge`. The parser hardcodes `"general"` as the fallback in `_detect_category` (`ingestion.py`), but the validator only built `valid_categories` from `config.keyword_routes` + `config.category_mappings.values()` — so users who customized `config.yaml` and dropped the default `"general": "general"` mapping hit `Invalid category` even though the index contained `general` documents. Validator now always tolerates `"general"`. (#98, thanks @Hohlas)
|
|
@@ -1401,6 +1401,28 @@ Common issues:
|
|
|
1401
1401
|
|
|
1402
1402
|
### Unreleased
|
|
1403
1403
|
|
|
1404
|
+
### v4.5.0 (2026-07-06) — Hybrid Search Ranking & Routing Fix
|
|
1405
|
+
|
|
1406
|
+
- **FIX**: `search_knowledge` now searches the entire index when the user omits `category_filter`. The internal `_route_by_keywords()` heuristic previously acted as a hard where-filter on both the semantic and BM25 branches, so queries whose terms happened to map to a sparsely-populated category could return two documents while thousands of relevant chunks in other categories were silently dropped. The router is now **informational only**: the `routed_by` field is still populated in every result for telemetry (public API unchanged), but the candidate set is never restricted. Users who want a hard filter still get it by passing `category_filter=...` explicitly — which continues to filter BM25 consistently with #109. (#112)
|
|
1407
|
+
- **NEW**: Hybrid search adds a small bounded ranking boost when query terms match indexed `source` or `filename` metadata, helping navigational queries surface the canonical file for a topic instead of adjacent files that only cross-reference it. The boost is capped at ~20% of typical RRF magnitudes, so it breaks near-ties without overriding semantic or BM25 signal. Public API unchanged. (#110, thanks @Hohlas)
|
|
1408
|
+
- **TEST**: New `TestKeywordRoutingBehavior` (3 tests) pins the routing fix — BM25 and semantic branches must both see the full corpus when no explicit filter is passed, and explicit `category_filter` still overrides the router. New `TestPathAwareRanking` covers the path-metadata boost. Baseline: 266 → 271.
|
|
1409
|
+
- **UPGRADE NOTE**: warm `query_cache` entries from before v4.5.0 should be invalidated by restarting the server so cached responses no longer reflect the pre-fix restrictive behavior.
|
|
1410
|
+
|
|
1411
|
+
### v4.4.0 (2026-07-06) — Cross-Platform Installer & Hybrid Search Category Filter
|
|
1412
|
+
|
|
1413
|
+
- **NEW**: Cross-platform, multi-LLM-client installer (`install.py`) driving both `install.sh` (Linux/macOS) and `install.ps1` (Windows) as thin wrappers. One codebase, one behavior across every OS.
|
|
1414
|
+
- **NEW**: Auto-detects and registers `knowledge-rag` in 8 LLM clients — Claude Code, Claude Desktop, Cursor, Windsurf, VS Code (Copilot Chat), Cline, Gemini CLI, Zed — writing to each tool's canonical config path with the correct JSON schema per client (VS Code uses `servers`, Zed uses `context_servers`, everyone else uses `mcpServers`).
|
|
1415
|
+
- **NEW**: `--for <clients>` / `--exclude <clients>` opt-in/opt-out selection, `--dry-run` preview, `--list-clients` registry inspection, `--pypi-version <ver>` pinning, `--skip-init` / `--skip-model` for fast reruns. See `python install.py --help`.
|
|
1416
|
+
- **FIX**: `install.ps1` no longer writes MCP config to `~/.claude/mcp.json` (a stale secondary path); it now targets `~/.claude.json` — the file Claude Code actually reads — via idempotent JSON merge that preserves every existing MCP server entry with an automatic `.knowledge-rag.bak` backup.
|
|
1417
|
+
- **FIX**: `install.ps1` gains PyPI mode (`pip install knowledge-rag`) and runs `mcp_server.server init` on install — feature parity with `install.sh`.
|
|
1418
|
+
- **FIX**: MCP server spec no longer uses the fragile `cmd /c cd /d ... && python ...` wrapper on Windows; it emits the standard `command` + `cwd` shape supported natively by every modern client.
|
|
1419
|
+
- **FIX**: `install.sh` guards against `sh install.sh` (bash-only features now emit a clear error instead of a cryptic syntax failure).
|
|
1420
|
+
- **FIX**: Both scripts now correctly advertise **13 MCP tools** (was outdated at 12; `get_reindex_status` shipped in v4.3.0).
|
|
1421
|
+
- **FIX**: Windows-side `install.ps1` prefers `winget install Python.Python.3.12 --scope user` (no admin), falls back to python.org 3.12.7 (was pinned to 3.12.0).
|
|
1422
|
+
- **FIX**: Hybrid search now applies the active category filter to BM25 results before RRF fusion, preventing keyword-only BM25 hits from other categories from leaking into filtered searches. Both leak paths are closed: BM25 candidates are metadata-filtered before RRF (with `top_k` widened to `max_results * 20` to compensate for post-filter drop), and the fallback fetch during fusion re-checks `category` before adding a chunk to `combined_scores`. Note: the `category_filter` guard now also applies to the keyword-routed category — `_route_by_keywords()`-inferred routing filters BM25 too, consistent with the semantic branch. Users with warm `query_cache` entries should restart the server to invalidate stale results. (#109, thanks @Hohlas)
|
|
1423
|
+
- **TEST**: New `tests/test_installer_no_data_loss.py` (22 tests) locks in the installer's zero-data-loss contract across all three JSON schemas (`mcpServers` / `servers` / `context_servers`): top-level keys preserved, sibling MCP servers byte-identical, `.knowledge-rag.bak` backup written before every mutation, idempotent second run, `--dry-run` writes nothing, atomic `os.replace` write. Baseline: 231 → 266.
|
|
1424
|
+
- **TEST**: New `TestHybridCategoryFilter::test_bm25_results_respect_category_filter` in `tests/test_search.py` — deterministic regression covering BM25-only hits with mixed categories. Baseline: 266 → 267.
|
|
1425
|
+
|
|
1404
1426
|
### v4.3.1 (2026-06-22) — Hybrid Search Fixes
|
|
1405
1427
|
|
|
1406
1428
|
- **FIX**: Accept `"general"` as a valid category in `search_knowledge`. The parser hardcodes `"general"` as the fallback in `_detect_category` (`ingestion.py`), but the validator only built `valid_categories` from `config.keyword_routes` + `config.category_mappings.values()` — so users who customized `config.yaml` and dropped the default `"general": "general"` mapping hit `Invalid category` even though the index contained `general` documents. Validator now always tolerates `"general"`. (#98, thanks @Hohlas)
|
|
@@ -637,6 +637,33 @@ class CrossEncoderReranker:
|
|
|
637
637
|
return documents[:top_k]
|
|
638
638
|
|
|
639
639
|
|
|
640
|
+
def _metadata_path_score(query: str, metadata: Dict[str, Any]) -> float:
|
|
641
|
+
"""Return a small generic score boost when query terms match path metadata."""
|
|
642
|
+
query_terms = re.findall(r"[a-z0-9][-a-z0-9]*[a-z0-9]|[a-z0-9]", query.lower())
|
|
643
|
+
if not query_terms:
|
|
644
|
+
return 0.0
|
|
645
|
+
|
|
646
|
+
source = str(metadata.get("source", ""))
|
|
647
|
+
filename = str(metadata.get("filename", ""))
|
|
648
|
+
path_text = f"{source} {filename}".lower()
|
|
649
|
+
path_tokens = set(re.findall(r"[a-z0-9][-a-z0-9]*[a-z0-9]|[a-z0-9]", path_text))
|
|
650
|
+
if not path_tokens:
|
|
651
|
+
return 0.0
|
|
652
|
+
|
|
653
|
+
score = 0.0
|
|
654
|
+
for term in query_terms:
|
|
655
|
+
if term in path_tokens:
|
|
656
|
+
score += 0.0006
|
|
657
|
+
elif term in path_text:
|
|
658
|
+
score += 0.0003
|
|
659
|
+
|
|
660
|
+
query_phrase = query.strip().lower()
|
|
661
|
+
if query_phrase and query_phrase in path_text:
|
|
662
|
+
score += 0.0012
|
|
663
|
+
|
|
664
|
+
return min(score, 0.003)
|
|
665
|
+
|
|
666
|
+
|
|
640
667
|
# =============================================================================
|
|
641
668
|
# BM25 INDEX
|
|
642
669
|
# =============================================================================
|
|
@@ -1466,13 +1493,23 @@ class KnowledgeOrchestrator:
|
|
|
1466
1493
|
|
|
1467
1494
|
self._ensure_bm25_index()
|
|
1468
1495
|
|
|
1469
|
-
# Keyword routing
|
|
1496
|
+
# Keyword routing — informational only.
|
|
1497
|
+
# `routed_category` is surfaced via the `routed_by` field for telemetry,
|
|
1498
|
+
# but MUST NOT restrict the search when the user did not pass an explicit
|
|
1499
|
+
# `category_filter`. Auto-routing to a sparsely-populated category (e.g. one
|
|
1500
|
+
# with only a handful of docs) was hiding relevant material that lived under
|
|
1501
|
+
# the top-level `security` bucket. Users who want a hard filter still get it
|
|
1502
|
+
# by passing `category_filter=...` explicitly.
|
|
1470
1503
|
routed_category = self._route_by_keywords(query_text)
|
|
1471
1504
|
where_filter = None
|
|
1472
1505
|
if category_filter:
|
|
1473
1506
|
where_filter = {"category": category_filter}
|
|
1474
|
-
|
|
1475
|
-
|
|
1507
|
+
|
|
1508
|
+
def _matches_category(metadata: Dict[str, Any]) -> bool:
|
|
1509
|
+
if not where_filter:
|
|
1510
|
+
return True
|
|
1511
|
+
expected_category = where_filter.get("category")
|
|
1512
|
+
return not expected_category or metadata.get("category") == expected_category
|
|
1476
1513
|
|
|
1477
1514
|
# Parallel Semantic + BM25 search (threaded for latency reduction)
|
|
1478
1515
|
from concurrent.futures import ThreadPoolExecutor
|
|
@@ -1507,8 +1544,23 @@ class KnowledgeOrchestrator:
|
|
|
1507
1544
|
r = {}
|
|
1508
1545
|
if hybrid_alpha < 1.0:
|
|
1509
1546
|
try:
|
|
1510
|
-
|
|
1511
|
-
|
|
1547
|
+
bm25_top_k = max_results * (20 if where_filter else 3)
|
|
1548
|
+
bm25_hits = self.bm25_index.search(query_text, top_k=bm25_top_k)
|
|
1549
|
+
|
|
1550
|
+
if where_filter:
|
|
1551
|
+
chunk_ids = [chunk_id for chunk_id, _ in bm25_hits]
|
|
1552
|
+
metadata_by_id = {}
|
|
1553
|
+
if chunk_ids:
|
|
1554
|
+
fetched = self.collection.get(ids=chunk_ids, include=["metadatas"])
|
|
1555
|
+
metadata_by_id = dict(zip(fetched.get("ids", []), fetched.get("metadatas", [])))
|
|
1556
|
+
|
|
1557
|
+
bm25_hits = [
|
|
1558
|
+
(chunk_id, bm25_score)
|
|
1559
|
+
for chunk_id, bm25_score in bm25_hits
|
|
1560
|
+
if _matches_category(metadata_by_id.get(chunk_id, {}))
|
|
1561
|
+
]
|
|
1562
|
+
|
|
1563
|
+
for rank, (chunk_id, bm25_score) in enumerate(bm25_hits[: max_results * 3]):
|
|
1512
1564
|
r[chunk_id] = {"rank": rank + 1, "bm25_score": bm25_score}
|
|
1513
1565
|
except Exception as e:
|
|
1514
1566
|
print(f"[WARN] BM25 search failed: {e}")
|
|
@@ -1558,8 +1610,11 @@ class KnowledgeOrchestrator:
|
|
|
1558
1610
|
except Exception:
|
|
1559
1611
|
continue
|
|
1560
1612
|
|
|
1613
|
+
if not _matches_category(data.get("metadata", {})):
|
|
1614
|
+
continue
|
|
1615
|
+
|
|
1561
1616
|
combined_scores[chunk_id] = {
|
|
1562
|
-
"rrf_score": combined_rrf,
|
|
1617
|
+
"rrf_score": combined_rrf + _metadata_path_score(query_text, data.get("metadata", {})),
|
|
1563
1618
|
"semantic_rank": semantic_rank if chunk_id in semantic_results else None,
|
|
1564
1619
|
"bm25_rank": bm25_rank if chunk_id in bm25_results else None,
|
|
1565
1620
|
"document": data.get("document", ""),
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "knowledge-rag"
|
|
7
|
-
version = "4.
|
|
7
|
+
version = "4.5.0"
|
|
8
8
|
description = "Local RAG System for Claude Code — Hybrid search + Cross-encoder Reranking + 13 MCP Tools + 20 Format Parsers. Zero external servers."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = {text = "MIT"}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|