diff --git a/CLAUDE.md b/CLAUDE.md
index 194a9cb..73b5ceb 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -15,13 +15,17 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
- **Use Python 3.10+ union syntax**: `str | None` instead of `Optional[str]`
- **Use lowercase generics**: `dict[str, Any]` instead of `Dict[str, Any]`
- **Type all function signatures** - Parameters and return types
-- **No explicit type checker configured** - Ruff handles linting only
+- **Type checker**: `ty` is configured for static type checking
+ ```bash
+ uv run ty check -- nextcloud_mcp_server
+ ```
### Code Quality
-- **Run ruff before committing**:
+- **Run ruff and ty before committing**:
```bash
uv run ruff check
uv run ruff format
+ uv run ty check -- nextcloud_mcp_server
```
- **Ruff configuration** in pyproject.toml (extends select: ["I"] for import sorting)
diff --git a/nextcloud_mcp_server/auth/viz_routes.py b/nextcloud_mcp_server/auth/viz_routes.py
index 20eaf1c..337c472 100644
--- a/nextcloud_mcp_server/auth/viz_routes.py
+++ b/nextcloud_mcp_server/auth/viz_routes.py
@@ -19,9 +19,7 @@ from starlette.responses import HTMLResponse, JSONResponse
from nextcloud_mcp_server.config import get_settings
from nextcloud_mcp_server.search import (
- FuzzySearchAlgorithm,
- HybridSearchAlgorithm,
- KeywordSearchAlgorithm,
+ BM25HybridSearchAlgorithm,
SemanticSearchAlgorithm,
)
from nextcloud_mcp_server.vector.pca import PCA
@@ -208,10 +206,8 @@ async def vector_visualization_html(request: Request) -> HTMLResponse:
@@ -259,25 +255,13 @@ async def vector_visualization_html(request: Request) -> HTMLResponse:
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+ BM25 Hybrid Search: Uses Qdrant's native Reciprocal Rank Fusion (RRF)
+ to automatically combine dense semantic vectors with sparse BM25 keyword vectors.
+ No manual weight tuning required.
+
@@ -364,12 +348,9 @@ async def vector_visualization_search(request: Request) -> JSONResponse:
# Parse query parameters
query = request.query_params.get("query", "")
- algorithm = request.query_params.get("algorithm", "hybrid")
+ algorithm = request.query_params.get("algorithm", "bm25_hybrid")
limit = int(request.query_params.get("limit", "50"))
- score_threshold = float(request.query_params.get("score_threshold", "0.7"))
- semantic_weight = float(request.query_params.get("semantic_weight", "0.5"))
- keyword_weight = float(request.query_params.get("keyword_weight", "0.3"))
- fuzzy_weight = float(request.query_params.get("fuzzy_weight", "0.2"))
+ score_threshold = float(request.query_params.get("score_threshold", "0.0"))
# Parse doc_types (comma-separated list, None = all types)
doc_types_param = request.query_params.get("doc_types", "")
@@ -433,16 +414,8 @@ async def vector_visualization_search(request: Request) -> JSONResponse:
# Create search algorithm
if algorithm == "semantic":
search_algo = SemanticSearchAlgorithm(score_threshold=score_threshold)
- elif algorithm == "keyword":
- search_algo = KeywordSearchAlgorithm()
- elif algorithm == "fuzzy":
- search_algo = FuzzySearchAlgorithm()
- elif algorithm == "hybrid":
- search_algo = HybridSearchAlgorithm(
- semantic_weight=semantic_weight,
- keyword_weight=keyword_weight,
- fuzzy_weight=fuzzy_weight,
- )
+ elif algorithm == "bm25_hybrid":
+ search_algo = BM25HybridSearchAlgorithm(score_threshold=score_threshold)
else:
return JSONResponse(
{"success": False, "error": f"Unknown algorithm: {algorithm}"},