knowledge-rag 3.3.1__tar.gz → 3.4.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-3.3.1 → knowledge_rag-3.4.0}/.gitignore +1 -0
- {knowledge_rag-3.3.1 → knowledge_rag-3.4.0}/PKG-INFO +64 -6
- {knowledge_rag-3.3.1 → knowledge_rag-3.4.0}/README.md +63 -5
- {knowledge_rag-3.3.1 → knowledge_rag-3.4.0}/config.example.yaml +24 -0
- {knowledge_rag-3.3.1 → knowledge_rag-3.4.0}/mcp_server/__init__.py +1 -1
- {knowledge_rag-3.3.1 → knowledge_rag-3.4.0}/mcp_server/config.py +69 -7
- {knowledge_rag-3.3.1 → knowledge_rag-3.4.0}/mcp_server/ingestion.py +93 -1
- {knowledge_rag-3.3.1 → knowledge_rag-3.4.0}/mcp_server/server.py +49 -35
- {knowledge_rag-3.3.1 → knowledge_rag-3.4.0}/presets/cybersecurity.yaml +4 -0
- {knowledge_rag-3.3.1 → knowledge_rag-3.4.0}/presets/developer.yaml +11 -0
- {knowledge_rag-3.3.1 → knowledge_rag-3.4.0}/presets/general.yaml +3 -0
- {knowledge_rag-3.3.1 → knowledge_rag-3.4.0}/presets/research.yaml +4 -0
- {knowledge_rag-3.3.1 → knowledge_rag-3.4.0}/pyproject.toml +1 -1
- {knowledge_rag-3.3.1 → knowledge_rag-3.4.0}/LICENSE +0 -0
- {knowledge_rag-3.3.1 → knowledge_rag-3.4.0}/documents/examples/sample-document.md +0 -0
- {knowledge_rag-3.3.1 → knowledge_rag-3.4.0}/requirements.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: knowledge-rag
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.4.0
|
|
4
4
|
Summary: Local RAG System for Claude Code — Hybrid search + Cross-encoder Reranking + 12 MCP Tools. 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
|
|
@@ -38,7 +38,7 @@ Description-Content-Type: text/markdown
|
|
|
38
38
|
|
|
39
39
|
<div align="center">
|
|
40
40
|
|
|
41
|
-

|
|
42
42
|

|
|
43
43
|

|
|
44
44
|

|
|
@@ -61,7 +61,7 @@ Your documents become instantly searchable inside Claude Code — with reranking
|
|
|
61
61
|
|
|
62
62
|
**12 MCP Tools** | **Hybrid Search + Cross-Encoder Reranking** | **Markdown-Aware Chunking** | **100% Local, Zero Cloud**
|
|
63
63
|
|
|
64
|
-
[What's New](#whats-new-in-
|
|
64
|
+
[What's New](#whats-new-in-v340) | [Installation](#installation) | [Configuration](#configuration) | [API Reference](#api-reference) | [Architecture](#architecture)
|
|
65
65
|
|
|
66
66
|
</div>
|
|
67
67
|
|
|
@@ -108,6 +108,40 @@ After the initial rebuild, startup and queries are faster than v2.x because ther
|
|
|
108
108
|
|
|
109
109
|
---
|
|
110
110
|
|
|
111
|
+
## What's New in v3.4.0
|
|
112
|
+
|
|
113
|
+
### Persistent Model Cache
|
|
114
|
+
Embedding models (~250MB) are now stored in `models_cache/` instead of `/tmp`. No more re-downloading after system reboots on Linux. Configurable via `paths.models_cache_dir` in config.yaml.
|
|
115
|
+
|
|
116
|
+
### Exclude Patterns
|
|
117
|
+
Skip files and directories during indexing with glob patterns. Configure under `documents.exclude_patterns` in config.yaml:
|
|
118
|
+
```yaml
|
|
119
|
+
documents:
|
|
120
|
+
exclude_patterns:
|
|
121
|
+
- "node_modules"
|
|
122
|
+
- ".venv"
|
|
123
|
+
- "__pycache__"
|
|
124
|
+
- ".git"
|
|
125
|
+
- "*.tmp"
|
|
126
|
+
```
|
|
127
|
+
Patterns match against both full relative paths and individual path components — `"node_modules"` excludes it at any depth.
|
|
128
|
+
|
|
129
|
+
### Jupyter Notebook Support (.ipynb)
|
|
130
|
+
Native parser extracts only markdown and code cell sources — no outputs, no execution counts, no base64-encoded images. Clean content for high-quality retrieval. Added to default supported formats.
|
|
131
|
+
|
|
132
|
+
### MCP Protocol Stability
|
|
133
|
+
`stdout` is redirected to `stderr` before the MCP server starts. Prevents random library `print()` calls from corrupting the JSON-RPC stream — fixes "Failed to connect" errors in VS Code and IDE integrations.
|
|
134
|
+
|
|
135
|
+
### File Watcher Resilience (Linux)
|
|
136
|
+
Server no longer crashes when system inotify limits are reached. Falls back gracefully to manual indexing with a clear warning message.
|
|
137
|
+
|
|
138
|
+
### MetaTrader Support (.mq4, .mqh)
|
|
139
|
+
MQL4/MQL5 source files can now be indexed as code. Not enabled by default — add `.mq4` and `.mqh` to your `supported_formats` list to activate.
|
|
140
|
+
|
|
141
|
+
*Community credit: Features inspired by ideas from [@Hohlas](https://github.com/Hohlas) in [PR #18](https://github.com/lyonzin/knowledge-rag/pull/18).*
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
111
145
|
## What's New in v3.3.0
|
|
112
146
|
|
|
113
147
|
### YAML Configuration System
|
|
@@ -966,6 +1000,7 @@ Pre-built configurations for common use cases. Each preset is a complete `config
|
|
|
966
1000
|
|-------|---------|-------------|
|
|
967
1001
|
| `paths.documents_dir` | `./documents` | Root folder scanned recursively for documents |
|
|
968
1002
|
| `paths.data_dir` | `./data` | Internal storage for ChromaDB and index metadata |
|
|
1003
|
+
| `paths.models_cache_dir` | `./models_cache` | Persistent cache for embedding models (~250MB). Prevents re-download after reboots |
|
|
969
1004
|
|
|
970
1005
|
Relative paths resolve from the project root. Absolute paths work too. The `KNOWLEDGE_RAG_DIR` environment variable overrides the project root.
|
|
971
1006
|
|
|
@@ -973,7 +1008,8 @@ Relative paths resolve from the project root. Absolute paths work too. The `KNOW
|
|
|
973
1008
|
|
|
974
1009
|
| Field | Default | Description |
|
|
975
1010
|
|-------|---------|-------------|
|
|
976
|
-
| `documents.supported_formats` | .md .txt .pdf .py .json .docx .xlsx .pptx .csv | File extensions to index |
|
|
1011
|
+
| `documents.supported_formats` | .md .txt .pdf .py .json .docx .xlsx .pptx .csv .ipynb | File extensions to index |
|
|
1012
|
+
| `documents.exclude_patterns` | `[]` (empty) | Glob patterns for files/dirs to skip during indexing |
|
|
977
1013
|
| `documents.chunking.chunk_size` | 1000 | Max characters per chunk |
|
|
978
1014
|
| `documents.chunking.chunk_overlap` | 200 | Characters shared between consecutive chunks |
|
|
979
1015
|
|
|
@@ -1090,7 +1126,8 @@ knowledge-rag/
|
|
|
1090
1126
|
├── data/
|
|
1091
1127
|
│ ├── chroma_db/ # ChromaDB vector database
|
|
1092
1128
|
│ └── index_metadata.json # Incremental indexing state
|
|
1093
|
-
├──
|
|
1129
|
+
├── models_cache/ # Persistent embedding model cache
|
|
1130
|
+
├── tests/ # Test suite (70+ tests)
|
|
1094
1131
|
├── venv/ # Python virtual environment
|
|
1095
1132
|
├── requirements.txt # Python dependencies
|
|
1096
1133
|
├── LICENSE # MIT License
|
|
@@ -1163,7 +1200,7 @@ pip install rank-bm25
|
|
|
1163
1200
|
|
|
1164
1201
|
### "ModuleNotFoundError: No module named 'mcp_server'"
|
|
1165
1202
|
|
|
1166
|
-
This occurs when Claude Code doesn't set the working directory correctly. Use the `cmd /c "cd /d ... && python"` wrapper in your MCP config (see [Installation](#
|
|
1203
|
+
This occurs when Claude Code doesn't set the working directory correctly. Use the `cmd /c "cd /d ... && python"` wrapper in your MCP config (see [Installation](#installation)).
|
|
1167
1204
|
|
|
1168
1205
|
### Dimension mismatch after upgrade
|
|
1169
1206
|
|
|
@@ -1195,6 +1232,27 @@ With ~200 documents, expect ~300-500MB RAM. The embedding model (~50MB) and rera
|
|
|
1195
1232
|
|
|
1196
1233
|
## Changelog
|
|
1197
1234
|
|
|
1235
|
+
### v3.4.0 (2026-04-16)
|
|
1236
|
+
|
|
1237
|
+
- **NEW**: `models_cache_dir` — persistent embedding model cache, prevents re-download after reboots
|
|
1238
|
+
- **NEW**: `exclude_patterns` — glob-based file/directory exclusion during indexing (under `documents:` section)
|
|
1239
|
+
- **NEW**: Jupyter Notebook (.ipynb) parser — extracts markdown and code cell sources only, ignores outputs and base64
|
|
1240
|
+
- **NEW**: MCP stdout protection — redirects stdout to stderr before server start, fixes "Failed to connect" errors
|
|
1241
|
+
- **NEW**: File watcher resilience — graceful fallback when Linux inotify limits are reached
|
|
1242
|
+
- **NEW**: MetaTrader (.mq4, .mqh) support — opt-in code parsing for MQL4/MQL5 files
|
|
1243
|
+
- **NEW**: 23 new tests (exclude patterns, ipynb parser, stdout protection)
|
|
1244
|
+
- **IMPROVED**: All 4 presets updated with new config fields
|
|
1245
|
+
- Community credit: [@Hohlas](https://github.com/Hohlas) ([PR #18](https://github.com/lyonzin/knowledge-rag/pull/18))
|
|
1246
|
+
|
|
1247
|
+
### v3.3.2 (2026-04-06)
|
|
1248
|
+
|
|
1249
|
+
- **FIX**: Full type validation on all YAML config values — wrong types warn and fall back to defaults
|
|
1250
|
+
- **FIX**: Bounds validation for chunk_size, chunk_overlap, default_results, max_results, embedding_dim
|
|
1251
|
+
- **FIX**: `keyword_routes` with string values instead of lists detected and removed with warning
|
|
1252
|
+
- **FIX**: `reranker_enabled: "yes"` (string) corrected to boolean with warning
|
|
1253
|
+
- **FIX**: Synced version strings across all source files
|
|
1254
|
+
- **FIX**: Broken README anchor, duplicate keyword, error handling in `knowledge-rag init`
|
|
1255
|
+
|
|
1198
1256
|
### v3.3.1 (2026-04-06)
|
|
1199
1257
|
|
|
1200
1258
|
- **FIX**: YAML null values (`category_mappings:` without value) no longer crash the server — falls back to defaults
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
<div align="center">
|
|
4
4
|
|
|
5
|
-

|
|
6
6
|

|
|
7
7
|

|
|
8
8
|

|
|
@@ -25,7 +25,7 @@ Your documents become instantly searchable inside Claude Code — with reranking
|
|
|
25
25
|
|
|
26
26
|
**12 MCP Tools** | **Hybrid Search + Cross-Encoder Reranking** | **Markdown-Aware Chunking** | **100% Local, Zero Cloud**
|
|
27
27
|
|
|
28
|
-
[What's New](#whats-new-in-
|
|
28
|
+
[What's New](#whats-new-in-v340) | [Installation](#installation) | [Configuration](#configuration) | [API Reference](#api-reference) | [Architecture](#architecture)
|
|
29
29
|
|
|
30
30
|
</div>
|
|
31
31
|
|
|
@@ -72,6 +72,40 @@ After the initial rebuild, startup and queries are faster than v2.x because ther
|
|
|
72
72
|
|
|
73
73
|
---
|
|
74
74
|
|
|
75
|
+
## What's New in v3.4.0
|
|
76
|
+
|
|
77
|
+
### Persistent Model Cache
|
|
78
|
+
Embedding models (~250MB) are now stored in `models_cache/` instead of `/tmp`. No more re-downloading after system reboots on Linux. Configurable via `paths.models_cache_dir` in config.yaml.
|
|
79
|
+
|
|
80
|
+
### Exclude Patterns
|
|
81
|
+
Skip files and directories during indexing with glob patterns. Configure under `documents.exclude_patterns` in config.yaml:
|
|
82
|
+
```yaml
|
|
83
|
+
documents:
|
|
84
|
+
exclude_patterns:
|
|
85
|
+
- "node_modules"
|
|
86
|
+
- ".venv"
|
|
87
|
+
- "__pycache__"
|
|
88
|
+
- ".git"
|
|
89
|
+
- "*.tmp"
|
|
90
|
+
```
|
|
91
|
+
Patterns match against both full relative paths and individual path components — `"node_modules"` excludes it at any depth.
|
|
92
|
+
|
|
93
|
+
### Jupyter Notebook Support (.ipynb)
|
|
94
|
+
Native parser extracts only markdown and code cell sources — no outputs, no execution counts, no base64-encoded images. Clean content for high-quality retrieval. Added to default supported formats.
|
|
95
|
+
|
|
96
|
+
### MCP Protocol Stability
|
|
97
|
+
`stdout` is redirected to `stderr` before the MCP server starts. Prevents random library `print()` calls from corrupting the JSON-RPC stream — fixes "Failed to connect" errors in VS Code and IDE integrations.
|
|
98
|
+
|
|
99
|
+
### File Watcher Resilience (Linux)
|
|
100
|
+
Server no longer crashes when system inotify limits are reached. Falls back gracefully to manual indexing with a clear warning message.
|
|
101
|
+
|
|
102
|
+
### MetaTrader Support (.mq4, .mqh)
|
|
103
|
+
MQL4/MQL5 source files can now be indexed as code. Not enabled by default — add `.mq4` and `.mqh` to your `supported_formats` list to activate.
|
|
104
|
+
|
|
105
|
+
*Community credit: Features inspired by ideas from [@Hohlas](https://github.com/Hohlas) in [PR #18](https://github.com/lyonzin/knowledge-rag/pull/18).*
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
75
109
|
## What's New in v3.3.0
|
|
76
110
|
|
|
77
111
|
### YAML Configuration System
|
|
@@ -930,6 +964,7 @@ Pre-built configurations for common use cases. Each preset is a complete `config
|
|
|
930
964
|
|-------|---------|-------------|
|
|
931
965
|
| `paths.documents_dir` | `./documents` | Root folder scanned recursively for documents |
|
|
932
966
|
| `paths.data_dir` | `./data` | Internal storage for ChromaDB and index metadata |
|
|
967
|
+
| `paths.models_cache_dir` | `./models_cache` | Persistent cache for embedding models (~250MB). Prevents re-download after reboots |
|
|
933
968
|
|
|
934
969
|
Relative paths resolve from the project root. Absolute paths work too. The `KNOWLEDGE_RAG_DIR` environment variable overrides the project root.
|
|
935
970
|
|
|
@@ -937,7 +972,8 @@ Relative paths resolve from the project root. Absolute paths work too. The `KNOW
|
|
|
937
972
|
|
|
938
973
|
| Field | Default | Description |
|
|
939
974
|
|-------|---------|-------------|
|
|
940
|
-
| `documents.supported_formats` | .md .txt .pdf .py .json .docx .xlsx .pptx .csv | File extensions to index |
|
|
975
|
+
| `documents.supported_formats` | .md .txt .pdf .py .json .docx .xlsx .pptx .csv .ipynb | File extensions to index |
|
|
976
|
+
| `documents.exclude_patterns` | `[]` (empty) | Glob patterns for files/dirs to skip during indexing |
|
|
941
977
|
| `documents.chunking.chunk_size` | 1000 | Max characters per chunk |
|
|
942
978
|
| `documents.chunking.chunk_overlap` | 200 | Characters shared between consecutive chunks |
|
|
943
979
|
|
|
@@ -1054,7 +1090,8 @@ knowledge-rag/
|
|
|
1054
1090
|
├── data/
|
|
1055
1091
|
│ ├── chroma_db/ # ChromaDB vector database
|
|
1056
1092
|
│ └── index_metadata.json # Incremental indexing state
|
|
1057
|
-
├──
|
|
1093
|
+
├── models_cache/ # Persistent embedding model cache
|
|
1094
|
+
├── tests/ # Test suite (70+ tests)
|
|
1058
1095
|
├── venv/ # Python virtual environment
|
|
1059
1096
|
├── requirements.txt # Python dependencies
|
|
1060
1097
|
├── LICENSE # MIT License
|
|
@@ -1127,7 +1164,7 @@ pip install rank-bm25
|
|
|
1127
1164
|
|
|
1128
1165
|
### "ModuleNotFoundError: No module named 'mcp_server'"
|
|
1129
1166
|
|
|
1130
|
-
This occurs when Claude Code doesn't set the working directory correctly. Use the `cmd /c "cd /d ... && python"` wrapper in your MCP config (see [Installation](#
|
|
1167
|
+
This occurs when Claude Code doesn't set the working directory correctly. Use the `cmd /c "cd /d ... && python"` wrapper in your MCP config (see [Installation](#installation)).
|
|
1131
1168
|
|
|
1132
1169
|
### Dimension mismatch after upgrade
|
|
1133
1170
|
|
|
@@ -1159,6 +1196,27 @@ With ~200 documents, expect ~300-500MB RAM. The embedding model (~50MB) and rera
|
|
|
1159
1196
|
|
|
1160
1197
|
## Changelog
|
|
1161
1198
|
|
|
1199
|
+
### v3.4.0 (2026-04-16)
|
|
1200
|
+
|
|
1201
|
+
- **NEW**: `models_cache_dir` — persistent embedding model cache, prevents re-download after reboots
|
|
1202
|
+
- **NEW**: `exclude_patterns` — glob-based file/directory exclusion during indexing (under `documents:` section)
|
|
1203
|
+
- **NEW**: Jupyter Notebook (.ipynb) parser — extracts markdown and code cell sources only, ignores outputs and base64
|
|
1204
|
+
- **NEW**: MCP stdout protection — redirects stdout to stderr before server start, fixes "Failed to connect" errors
|
|
1205
|
+
- **NEW**: File watcher resilience — graceful fallback when Linux inotify limits are reached
|
|
1206
|
+
- **NEW**: MetaTrader (.mq4, .mqh) support — opt-in code parsing for MQL4/MQL5 files
|
|
1207
|
+
- **NEW**: 23 new tests (exclude patterns, ipynb parser, stdout protection)
|
|
1208
|
+
- **IMPROVED**: All 4 presets updated with new config fields
|
|
1209
|
+
- Community credit: [@Hohlas](https://github.com/Hohlas) ([PR #18](https://github.com/lyonzin/knowledge-rag/pull/18))
|
|
1210
|
+
|
|
1211
|
+
### v3.3.2 (2026-04-06)
|
|
1212
|
+
|
|
1213
|
+
- **FIX**: Full type validation on all YAML config values — wrong types warn and fall back to defaults
|
|
1214
|
+
- **FIX**: Bounds validation for chunk_size, chunk_overlap, default_results, max_results, embedding_dim
|
|
1215
|
+
- **FIX**: `keyword_routes` with string values instead of lists detected and removed with warning
|
|
1216
|
+
- **FIX**: `reranker_enabled: "yes"` (string) corrected to boolean with warning
|
|
1217
|
+
- **FIX**: Synced version strings across all source files
|
|
1218
|
+
- **FIX**: Broken README anchor, duplicate keyword, error handling in `knowledge-rag init`
|
|
1219
|
+
|
|
1162
1220
|
### v3.3.1 (2026-04-06)
|
|
1163
1221
|
|
|
1164
1222
|
- **FIX**: YAML null values (`category_mappings:` without value) no longer crash the server — falls back to defaults
|
|
@@ -39,6 +39,11 @@ paths:
|
|
|
39
39
|
# You generally don't need to touch this.
|
|
40
40
|
data_dir: "./data"
|
|
41
41
|
|
|
42
|
+
# Persistent cache for embedding models (~250MB).
|
|
43
|
+
# Prevents re-downloading after reboots (especially on Linux where /tmp is cleared).
|
|
44
|
+
# Default: ./models_cache (relative to project root)
|
|
45
|
+
models_cache_dir: "./models_cache"
|
|
46
|
+
|
|
42
47
|
|
|
43
48
|
# ============================================================================
|
|
44
49
|
# DOCUMENTS
|
|
@@ -58,6 +63,25 @@ documents:
|
|
|
58
63
|
# - .csv # CSV data files
|
|
59
64
|
# - .py # Python source code
|
|
60
65
|
# - .json # JSON files
|
|
66
|
+
# - .ipynb # Jupyter Notebooks (extracts markdown + code cells)
|
|
67
|
+
# - .mq4 # MetaTrader MQL4 source (opt-in, add to enable)
|
|
68
|
+
# - .mqh # MetaTrader MQL4/5 headers (opt-in, add to enable)
|
|
69
|
+
|
|
70
|
+
# Exclude patterns — skip files/directories matching these patterns.
|
|
71
|
+
# Uses fnmatch glob syntax. Patterns match against relative paths AND
|
|
72
|
+
# individual path components (so "node_modules" matches at any depth).
|
|
73
|
+
#
|
|
74
|
+
# Examples:
|
|
75
|
+
# exclude_patterns:
|
|
76
|
+
# - "node_modules" # Skip node_modules at any depth
|
|
77
|
+
# - ".git" # Skip .git directories
|
|
78
|
+
# - "__pycache__" # Skip Python cache
|
|
79
|
+
# - ".venv" # Skip virtual environments
|
|
80
|
+
# - "*.tmp" # Skip .tmp files anywhere
|
|
81
|
+
# - "drafts/*" # Skip everything in top-level drafts/
|
|
82
|
+
#
|
|
83
|
+
# Default: [] (nothing excluded — all supported files are indexed)
|
|
84
|
+
exclude_patterns: []
|
|
61
85
|
|
|
62
86
|
# How documents are split into searchable chunks.
|
|
63
87
|
#
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""Configuration for Knowledge RAG System v3.
|
|
1
|
+
"""Configuration for Knowledge RAG System v3.4.0 — YAML-configurable"""
|
|
2
2
|
|
|
3
3
|
import os
|
|
4
4
|
from dataclasses import dataclass, field
|
|
@@ -15,7 +15,7 @@ import yaml
|
|
|
15
15
|
_source_dir = Path(__file__).parent.parent
|
|
16
16
|
|
|
17
17
|
|
|
18
|
-
_SUPPORTED_SUFFIXES = frozenset([".md", ".txt", ".pdf", ".py", ".json", ".docx", ".xlsx", ".pptx", ".csv"])
|
|
18
|
+
_SUPPORTED_SUFFIXES = frozenset([".md", ".txt", ".pdf", ".py", ".json", ".docx", ".xlsx", ".pptx", ".csv", ".ipynb"])
|
|
19
19
|
|
|
20
20
|
|
|
21
21
|
def _has_documents(path: Path) -> bool:
|
|
@@ -73,7 +73,19 @@ def _get(section: str, key: str, default):
|
|
|
73
73
|
if not isinstance(s, dict):
|
|
74
74
|
return default
|
|
75
75
|
val = s.get(key)
|
|
76
|
-
|
|
76
|
+
if val is None:
|
|
77
|
+
return default
|
|
78
|
+
# Skip type check when default is None (caller handles validation)
|
|
79
|
+
if default is None:
|
|
80
|
+
return val
|
|
81
|
+
# YAML parses "yes"/"no" as bool, but explicit string "yes" stays str
|
|
82
|
+
if not isinstance(val, type(default)):
|
|
83
|
+
print(
|
|
84
|
+
f"[WARN] config.yaml: {section}.{key} has wrong type "
|
|
85
|
+
f"(expected {type(default).__name__}, got {type(val).__name__}), using default"
|
|
86
|
+
)
|
|
87
|
+
return default
|
|
88
|
+
return val
|
|
77
89
|
|
|
78
90
|
|
|
79
91
|
def _get_top(key: str, default):
|
|
@@ -81,7 +93,8 @@ def _get_top(key: str, default):
|
|
|
81
93
|
val = _yaml.get(key)
|
|
82
94
|
if val is None:
|
|
83
95
|
return default
|
|
84
|
-
if not isinstance(val,
|
|
96
|
+
if not isinstance(val, dict):
|
|
97
|
+
print(f"[WARN] config.yaml: {key} has wrong type (expected dict, got {type(val).__name__}), using default")
|
|
85
98
|
return default
|
|
86
99
|
return val
|
|
87
100
|
|
|
@@ -155,7 +168,6 @@ _DEFAULT_KEYWORD_ROUTES = {
|
|
|
155
168
|
"deserialization",
|
|
156
169
|
"ysoserial",
|
|
157
170
|
"upload bypass",
|
|
158
|
-
"reverse shell",
|
|
159
171
|
"web shell",
|
|
160
172
|
"hash cracking",
|
|
161
173
|
"hashcat",
|
|
@@ -345,6 +357,9 @@ class Config:
|
|
|
345
357
|
documents_dir: Path = field(
|
|
346
358
|
default_factory=lambda: _resolve_path(_get("paths", "documents_dir", None), BASE_DIR / "documents")
|
|
347
359
|
)
|
|
360
|
+
models_cache_dir: Path = field(
|
|
361
|
+
default_factory=lambda: _resolve_path(_get("paths", "models_cache_dir", None), BASE_DIR / "models_cache")
|
|
362
|
+
)
|
|
348
363
|
|
|
349
364
|
# Chunking
|
|
350
365
|
chunk_size: int = field(
|
|
@@ -407,10 +422,15 @@ class Config:
|
|
|
407
422
|
# Supported formats
|
|
408
423
|
supported_formats: List[str] = field(
|
|
409
424
|
default_factory=lambda: _get(
|
|
410
|
-
"documents",
|
|
425
|
+
"documents",
|
|
426
|
+
"supported_formats",
|
|
427
|
+
[".md", ".txt", ".pdf", ".py", ".json", ".docx", ".xlsx", ".pptx", ".csv", ".ipynb"],
|
|
411
428
|
)
|
|
412
429
|
)
|
|
413
430
|
|
|
431
|
+
# Exclude patterns for directory traversal
|
|
432
|
+
exclude_patterns: List[str] = field(default_factory=lambda: _get("documents", "exclude_patterns", []))
|
|
433
|
+
|
|
414
434
|
# Category mappings
|
|
415
435
|
category_mappings: Dict[str, str] = field(
|
|
416
436
|
default_factory=lambda: _get_top("category_mappings", _DEFAULT_CATEGORY_MAPPINGS)
|
|
@@ -431,10 +451,52 @@ class Config:
|
|
|
431
451
|
max_results: int = field(default_factory=lambda: _get("search", "max_results", 20))
|
|
432
452
|
|
|
433
453
|
def __post_init__(self):
|
|
434
|
-
"""
|
|
454
|
+
"""Validate config values and ensure directories exist."""
|
|
455
|
+
# Bounds validation
|
|
456
|
+
if not isinstance(self.chunk_size, int) or self.chunk_size < 100:
|
|
457
|
+
print(f"[WARN] chunk_size={self.chunk_size} invalid, using 1000")
|
|
458
|
+
self.chunk_size = 1000
|
|
459
|
+
if not isinstance(self.chunk_overlap, int) or self.chunk_overlap < 0:
|
|
460
|
+
print(f"[WARN] chunk_overlap={self.chunk_overlap} invalid, using 200")
|
|
461
|
+
self.chunk_overlap = 200
|
|
462
|
+
if self.chunk_overlap >= self.chunk_size:
|
|
463
|
+
print(
|
|
464
|
+
f"[WARN] chunk_overlap ({self.chunk_overlap}) >= chunk_size ({self.chunk_size}), using {self.chunk_size // 5}"
|
|
465
|
+
)
|
|
466
|
+
self.chunk_overlap = self.chunk_size // 5
|
|
467
|
+
if not isinstance(self.default_results, int) or self.default_results < 1:
|
|
468
|
+
self.default_results = 5
|
|
469
|
+
if not isinstance(self.max_results, int) or self.max_results < 1:
|
|
470
|
+
self.max_results = 20
|
|
471
|
+
if not isinstance(self.embedding_dim, int) or self.embedding_dim < 1:
|
|
472
|
+
self.embedding_dim = 384
|
|
473
|
+
if not isinstance(self.reranker_enabled, bool):
|
|
474
|
+
print(f"[WARN] reranker_enabled={self.reranker_enabled!r} invalid, using True")
|
|
475
|
+
self.reranker_enabled = True
|
|
476
|
+
if not isinstance(self.reranker_top_k_multiplier, int) or self.reranker_top_k_multiplier < 1:
|
|
477
|
+
self.reranker_top_k_multiplier = 3
|
|
478
|
+
if not isinstance(self.supported_formats, list) or not self.supported_formats:
|
|
479
|
+
print("[WARN] supported_formats is empty or invalid, using defaults")
|
|
480
|
+
self.supported_formats = [".md", ".txt", ".pdf", ".py", ".json", ".docx", ".xlsx", ".pptx", ".csv"]
|
|
481
|
+
|
|
482
|
+
# Validate exclude_patterns is a list of strings
|
|
483
|
+
if not isinstance(self.exclude_patterns, list):
|
|
484
|
+
print(f"[WARN] exclude_patterns={self.exclude_patterns!r} invalid, using []")
|
|
485
|
+
self.exclude_patterns = []
|
|
486
|
+
else:
|
|
487
|
+
self.exclude_patterns = [p for p in self.exclude_patterns if isinstance(p, str)]
|
|
488
|
+
|
|
489
|
+
# Validate keyword_routes values are lists (not strings)
|
|
490
|
+
for cat, keywords in list(self.keyword_routes.items()):
|
|
491
|
+
if not isinstance(keywords, list):
|
|
492
|
+
print(f"[WARN] keyword_routes.{cat} is not a list, removing")
|
|
493
|
+
del self.keyword_routes[cat]
|
|
494
|
+
|
|
495
|
+
# Ensure directories exist
|
|
435
496
|
self.data_dir.mkdir(parents=True, exist_ok=True)
|
|
436
497
|
self.chroma_dir.mkdir(parents=True, exist_ok=True)
|
|
437
498
|
self.documents_dir.mkdir(parents=True, exist_ok=True)
|
|
499
|
+
self.models_cache_dir.mkdir(parents=True, exist_ok=True)
|
|
438
500
|
|
|
439
501
|
|
|
440
502
|
# Global config instance
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
"""Document Ingestion System for Knowledge RAG
|
|
2
2
|
|
|
3
3
|
Multi-format document parsing, chunking, and metadata extraction.
|
|
4
|
-
Supports: MD, PDF, TXT, PY, JSON, DOCX, XLSX, PPTX, CSV
|
|
4
|
+
Supports: MD, PDF, TXT, PY, JSON, DOCX, XLSX, PPTX, CSV, IPYNB, MQH, MQ4
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
+
import fnmatch
|
|
7
8
|
import hashlib
|
|
8
9
|
import json
|
|
9
10
|
import os
|
|
@@ -103,6 +104,9 @@ class DocumentParser:
|
|
|
103
104
|
".xlsx": self._parse_xlsx,
|
|
104
105
|
".pptx": self._parse_pptx,
|
|
105
106
|
".csv": self._parse_csv,
|
|
107
|
+
".ipynb": self._parse_ipynb,
|
|
108
|
+
".mqh": self._parse_code,
|
|
109
|
+
".mq4": self._parse_code,
|
|
106
110
|
}
|
|
107
111
|
|
|
108
112
|
def parse_file(self, filepath: Path) -> Optional[Document]:
|
|
@@ -151,12 +155,41 @@ class DocumentParser:
|
|
|
151
155
|
|
|
152
156
|
return doc
|
|
153
157
|
|
|
158
|
+
@staticmethod
|
|
159
|
+
def _should_exclude(path: Path, base_dir: Path, patterns: List[str]) -> bool:
|
|
160
|
+
"""Check if a path matches any exclude pattern.
|
|
161
|
+
|
|
162
|
+
Uses fnmatch on the relative path (forward-slash normalized) and
|
|
163
|
+
also checks each path component individually for simple name patterns.
|
|
164
|
+
"""
|
|
165
|
+
if not patterns:
|
|
166
|
+
return False
|
|
167
|
+
|
|
168
|
+
try:
|
|
169
|
+
rel = path.relative_to(base_dir)
|
|
170
|
+
except ValueError:
|
|
171
|
+
rel = path
|
|
172
|
+
|
|
173
|
+
rel_str = str(rel).replace("\\", "/")
|
|
174
|
+
|
|
175
|
+
for pattern in patterns:
|
|
176
|
+
# Full relative path match (e.g., "docs/drafts/*.tmp")
|
|
177
|
+
if fnmatch.fnmatch(rel_str, pattern):
|
|
178
|
+
return True
|
|
179
|
+
# Check each component (e.g., "node_modules" matches any/node_modules/deep)
|
|
180
|
+
for part in rel.parts:
|
|
181
|
+
if fnmatch.fnmatch(part, pattern):
|
|
182
|
+
return True
|
|
183
|
+
|
|
184
|
+
return False
|
|
185
|
+
|
|
154
186
|
def parse_directory(self, directory: Path = None) -> List[Document]:
|
|
155
187
|
"""Parse all supported files in a directory recursively (follows symlinks)."""
|
|
156
188
|
directory = Path(directory) if directory else config.documents_dir
|
|
157
189
|
documents = []
|
|
158
190
|
seen_dirs = set()
|
|
159
191
|
supported = set(config.supported_formats)
|
|
192
|
+
exclude = config.exclude_patterns
|
|
160
193
|
|
|
161
194
|
for root, dirs, files in os.walk(directory, followlinks=True):
|
|
162
195
|
real_root = os.path.realpath(root)
|
|
@@ -165,10 +198,16 @@ class DocumentParser:
|
|
|
165
198
|
continue
|
|
166
199
|
seen_dirs.add(real_root)
|
|
167
200
|
|
|
201
|
+
# Filter out excluded directories in-place (prevents os.walk from descending)
|
|
202
|
+
if exclude:
|
|
203
|
+
dirs[:] = [d for d in dirs if not self._should_exclude(Path(root) / d, directory, exclude)]
|
|
204
|
+
|
|
168
205
|
for fname in files:
|
|
169
206
|
filepath = Path(root) / fname
|
|
170
207
|
if filepath.suffix.lower() not in supported:
|
|
171
208
|
continue
|
|
209
|
+
if exclude and self._should_exclude(filepath, directory, exclude):
|
|
210
|
+
continue
|
|
172
211
|
try:
|
|
173
212
|
doc = self.parse_file(filepath)
|
|
174
213
|
if doc:
|
|
@@ -437,6 +476,59 @@ class DocumentParser:
|
|
|
437
476
|
content = "\n".join(parts)
|
|
438
477
|
return content, metadata
|
|
439
478
|
|
|
479
|
+
def _parse_ipynb(self, filepath: Path) -> tuple[str, Dict]:
|
|
480
|
+
"""Parse Jupyter Notebook, extracting only markdown and code cell sources.
|
|
481
|
+
|
|
482
|
+
Ignores outputs, execution counts, cell metadata, and base64 images.
|
|
483
|
+
"""
|
|
484
|
+
raw = filepath.read_text(encoding="utf-8", errors="ignore")
|
|
485
|
+
metadata = {
|
|
486
|
+
"type": "jupyter_notebook",
|
|
487
|
+
"title": filepath.stem,
|
|
488
|
+
"file_size": filepath.stat().st_size,
|
|
489
|
+
"modified": datetime.fromtimestamp(filepath.stat().st_mtime).isoformat(),
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
try:
|
|
493
|
+
nb = json.loads(raw)
|
|
494
|
+
except json.JSONDecodeError:
|
|
495
|
+
metadata["is_valid_json"] = False
|
|
496
|
+
return raw, metadata
|
|
497
|
+
|
|
498
|
+
metadata["is_valid_json"] = True
|
|
499
|
+
metadata["nbformat"] = nb.get("nbformat", 0)
|
|
500
|
+
kernel = nb.get("metadata", {}).get("kernelspec", {})
|
|
501
|
+
metadata["kernel"] = kernel.get("display_name", kernel.get("name", "unknown"))
|
|
502
|
+
|
|
503
|
+
cells = nb.get("cells", [])
|
|
504
|
+
metadata["cells"] = len(cells)
|
|
505
|
+
code_cells = 0
|
|
506
|
+
markdown_cells = 0
|
|
507
|
+
|
|
508
|
+
parts = []
|
|
509
|
+
for cell in cells:
|
|
510
|
+
cell_type = cell.get("cell_type", "")
|
|
511
|
+
source = cell.get("source", "")
|
|
512
|
+
|
|
513
|
+
if isinstance(source, list):
|
|
514
|
+
source = "".join(source)
|
|
515
|
+
|
|
516
|
+
if not source or not source.strip():
|
|
517
|
+
continue
|
|
518
|
+
|
|
519
|
+
if cell_type == "markdown":
|
|
520
|
+
parts.append(source)
|
|
521
|
+
markdown_cells += 1
|
|
522
|
+
elif cell_type == "code":
|
|
523
|
+
parts.append(f"```python\n{source}\n```")
|
|
524
|
+
code_cells += 1
|
|
525
|
+
|
|
526
|
+
metadata["code_cells"] = code_cells
|
|
527
|
+
metadata["markdown_cells"] = markdown_cells
|
|
528
|
+
|
|
529
|
+
content = "\n\n".join(parts)
|
|
530
|
+
return content, metadata
|
|
531
|
+
|
|
440
532
|
# =========================================================================
|
|
441
533
|
# Chunking
|
|
442
534
|
# =========================================================================
|
|
@@ -19,8 +19,8 @@ Features:
|
|
|
19
19
|
- CRUD operations via MCP tools (add, update, remove docs)
|
|
20
20
|
|
|
21
21
|
Autor: Lyon (Ailton Rocha)
|
|
22
|
-
Versao: 3.
|
|
23
|
-
Data: 2026-
|
|
22
|
+
Versao: 3.4.0
|
|
23
|
+
Data: 2026-04-16
|
|
24
24
|
"""
|
|
25
25
|
|
|
26
26
|
import hashlib
|
|
@@ -141,7 +141,7 @@ class FastEmbedEmbeddings:
|
|
|
141
141
|
self.model_name = model or config.embedding_model
|
|
142
142
|
self._dim = config.embedding_dim
|
|
143
143
|
print(f"[INFO] Loading embedding model: {self.model_name} ({self._dim}D)...")
|
|
144
|
-
self._model = TextEmbedding(model_name=self.model_name)
|
|
144
|
+
self._model = TextEmbedding(model_name=self.model_name, cache_dir=str(config.models_cache_dir))
|
|
145
145
|
print("[INFO] Embedding model loaded successfully")
|
|
146
146
|
|
|
147
147
|
def __call__(self, input: List[str]) -> List[List[float]]:
|
|
@@ -1836,32 +1836,37 @@ def _handle_init():
|
|
|
1836
1836
|
|
|
1837
1837
|
cwd = Path.cwd()
|
|
1838
1838
|
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1839
|
+
try:
|
|
1840
|
+
# Copy config.example.yaml
|
|
1841
|
+
src = data_dir / "config.example.yaml"
|
|
1842
|
+
if src.exists():
|
|
1843
|
+
dst = cwd / "config.example.yaml"
|
|
1844
|
+
shutil.copy2(src, dst)
|
|
1845
|
+
print(f"[OK] {dst}")
|
|
1846
|
+
|
|
1847
|
+
# Copy presets
|
|
1848
|
+
presets_dir = cwd / "presets"
|
|
1849
|
+
presets_dir.mkdir(exist_ok=True)
|
|
1850
|
+
for f in data_dir.glob("*.yaml"):
|
|
1851
|
+
if f.name == "config.example.yaml":
|
|
1852
|
+
continue
|
|
1853
|
+
dst = presets_dir / f.name
|
|
1854
|
+
shutil.copy2(f, dst)
|
|
1855
|
+
print(f"[OK] {dst}")
|
|
1856
|
+
|
|
1857
|
+
# Create documents dir
|
|
1858
|
+
docs_dir = cwd / "documents"
|
|
1859
|
+
docs_dir.mkdir(exist_ok=True)
|
|
1860
|
+
print(f"[OK] {docs_dir}/")
|
|
1861
|
+
|
|
1862
|
+
print("\nDone. Quick start:")
|
|
1863
|
+
print(" cp presets/general.yaml config.yaml # or cybersecurity, developer, research")
|
|
1864
|
+
print(" # Add your documents to documents/")
|
|
1865
|
+
print(" # Restart Claude Code")
|
|
1866
|
+
except PermissionError:
|
|
1867
|
+
print("[ERROR] Permission denied. Run from a writable directory.")
|
|
1868
|
+
except OSError as e:
|
|
1869
|
+
print(f"[ERROR] Failed to write files: {e}")
|
|
1865
1870
|
|
|
1866
1871
|
|
|
1867
1872
|
def main():
|
|
@@ -1894,12 +1899,21 @@ def main():
|
|
|
1894
1899
|
print(f"[INFO] Indexed {stats['indexed']} documents with {stats['chunks_added']} chunks")
|
|
1895
1900
|
|
|
1896
1901
|
# Start file watcher for auto-reindex on document changes
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1902
|
+
try:
|
|
1903
|
+
watcher = DocumentWatcher(get_orchestrator, debounce_seconds=5.0)
|
|
1904
|
+
observer = Observer()
|
|
1905
|
+
observer.schedule(watcher, str(config.documents_dir), recursive=True)
|
|
1906
|
+
observer.daemon = True
|
|
1907
|
+
observer.start()
|
|
1908
|
+
print(f"[WATCHER] Monitoring {config.documents_dir} for changes")
|
|
1909
|
+
except Exception as e:
|
|
1910
|
+
print(f"[WARN] Failed to start file watcher: {e}")
|
|
1911
|
+
print("[WARN] Auto-reindexing disabled. Use reindex_documents tool manually.")
|
|
1912
|
+
|
|
1913
|
+
# Redirect stdout to stderr to protect MCP stdio JSON-RPC stream.
|
|
1914
|
+
# Libraries (fastembed, chromadb, watchdog) may print() at runtime,
|
|
1915
|
+
# which would corrupt the protocol. This ONLY affects the server process.
|
|
1916
|
+
sys.stdout = sys.stderr
|
|
1903
1917
|
|
|
1904
1918
|
mcp.run()
|
|
1905
1919
|
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
paths:
|
|
23
23
|
documents_dir: "./documents"
|
|
24
24
|
data_dir: "./data"
|
|
25
|
+
models_cache_dir: "./models_cache"
|
|
25
26
|
|
|
26
27
|
|
|
27
28
|
# ============================================================================
|
|
@@ -39,6 +40,9 @@ documents:
|
|
|
39
40
|
- .xlsx
|
|
40
41
|
- .pptx
|
|
41
42
|
- .csv
|
|
43
|
+
- .ipynb # Jupyter Notebooks (exploit dev, research)
|
|
44
|
+
|
|
45
|
+
exclude_patterns: []
|
|
42
46
|
|
|
43
47
|
chunking:
|
|
44
48
|
chunk_size: 1000
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
paths:
|
|
18
18
|
documents_dir: "./documents"
|
|
19
19
|
data_dir: "./data"
|
|
20
|
+
models_cache_dir: "./models_cache"
|
|
20
21
|
|
|
21
22
|
|
|
22
23
|
# ============================================================================
|
|
@@ -32,6 +33,16 @@ documents:
|
|
|
32
33
|
- .py # Python source code
|
|
33
34
|
- .json # API schemas, configs
|
|
34
35
|
- .csv # Data files, logs
|
|
36
|
+
- .ipynb # Jupyter Notebooks
|
|
37
|
+
|
|
38
|
+
exclude_patterns:
|
|
39
|
+
- "node_modules"
|
|
40
|
+
- ".venv"
|
|
41
|
+
- "__pycache__"
|
|
42
|
+
- ".git"
|
|
43
|
+
- "dist"
|
|
44
|
+
- "build"
|
|
45
|
+
- ".next"
|
|
35
46
|
|
|
36
47
|
chunking:
|
|
37
48
|
chunk_size: 1200 # Slightly larger for code + prose docs
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
paths:
|
|
27
27
|
documents_dir: "./documents"
|
|
28
28
|
data_dir: "./data"
|
|
29
|
+
models_cache_dir: "./models_cache"
|
|
29
30
|
|
|
30
31
|
|
|
31
32
|
# ============================================================================
|
|
@@ -39,6 +40,8 @@ documents:
|
|
|
39
40
|
- .pdf
|
|
40
41
|
- .docx
|
|
41
42
|
|
|
43
|
+
exclude_patterns: []
|
|
44
|
+
|
|
42
45
|
chunking:
|
|
43
46
|
chunk_size: 1000
|
|
44
47
|
chunk_overlap: 200
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
paths:
|
|
18
18
|
documents_dir: "./documents"
|
|
19
19
|
data_dir: "./data"
|
|
20
|
+
models_cache_dir: "./models_cache"
|
|
20
21
|
|
|
21
22
|
|
|
22
23
|
# ============================================================================
|
|
@@ -32,6 +33,9 @@ documents:
|
|
|
32
33
|
- .xlsx # Data tables, survey results
|
|
33
34
|
- .pptx # Presentations, lecture slides
|
|
34
35
|
- .csv # Experiment data, datasets
|
|
36
|
+
- .ipynb # Jupyter Notebooks (analysis, experiments)
|
|
37
|
+
|
|
38
|
+
exclude_patterns: []
|
|
35
39
|
|
|
36
40
|
chunking:
|
|
37
41
|
chunk_size: 1500 # Larger chunks — academic text needs more context
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "knowledge-rag"
|
|
7
|
-
version = "3.
|
|
7
|
+
version = "3.4.0"
|
|
8
8
|
description = "Local RAG System for Claude Code — Hybrid search + Cross-encoder Reranking + 12 MCP Tools. Zero external servers."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = {text = "MIT"}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|