apex-rag 0.1.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.
- apex_rag-0.1.0/LICENSE +21 -0
- apex_rag-0.1.0/PKG-INFO +332 -0
- apex_rag-0.1.0/README.md +264 -0
- apex_rag-0.1.0/docker-compose.yml +70 -0
- apex_rag-0.1.0/examples/basic_usage.py +142 -0
- apex_rag-0.1.0/pyproject.toml +93 -0
- apex_rag-0.1.0/src/__init__.py +56 -0
- apex_rag-0.1.0/src/api.py +544 -0
- apex_rag-0.1.0/src/client.py +330 -0
- apex_rag-0.1.0/src/ingestion.py +518 -0
- apex_rag-0.1.0/src/navigation.py +487 -0
- apex_rag-0.1.0/src/storage.py +477 -0
- apex_rag-0.1.0/src/utils.py +234 -0
- apex_rag-0.1.0/tests/conftest.py +5 -0
- apex_rag-0.1.0/tests/test_search.py +310 -0
- apex_rag-0.1.0/tests/test_tree.py +251 -0
apex_rag-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 G S ABINIVAS
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
apex_rag-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: apex-rag
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A production-grade, local-first Agentic RAG library using structural document navigation.
|
|
5
|
+
Project-URL: Homepage, https://github.com/your-org/apex-rag
|
|
6
|
+
Project-URL: Repository, https://github.com/your-org/apex-rag
|
|
7
|
+
Project-URL: Issues, https://github.com/your-org/apex-rag/issues
|
|
8
|
+
Author: ApexRAG Contributors
|
|
9
|
+
License: MIT License
|
|
10
|
+
|
|
11
|
+
Copyright (c) 2026 G S ABINIVAS
|
|
12
|
+
|
|
13
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
14
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
15
|
+
in the Software without restriction, including without limitation the rights
|
|
16
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
17
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
18
|
+
furnished to do so, subject to the following conditions:
|
|
19
|
+
|
|
20
|
+
The above copyright notice and this permission notice shall be included in all
|
|
21
|
+
copies or substantial portions of the Software.
|
|
22
|
+
|
|
23
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
24
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
25
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
26
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
27
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
28
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
29
|
+
SOFTWARE.
|
|
30
|
+
License-File: LICENSE
|
|
31
|
+
Keywords: agentic,llm,nlp,ollama,rag,retrieval
|
|
32
|
+
Classifier: Development Status :: 3 - Alpha
|
|
33
|
+
Classifier: Intended Audience :: Developers
|
|
34
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
35
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
36
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
37
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
38
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
39
|
+
Requires-Python: >=3.10
|
|
40
|
+
Requires-Dist: aiosqlite>=0.20.0
|
|
41
|
+
Requires-Dist: alembic>=1.13.0
|
|
42
|
+
Requires-Dist: asyncpg>=0.29.0
|
|
43
|
+
Requires-Dist: fastapi>=0.111.0
|
|
44
|
+
Requires-Dist: httpx>=0.27.0
|
|
45
|
+
Requires-Dist: markitdown[all]>=0.0.1a2
|
|
46
|
+
Requires-Dist: ollama>=0.2.0
|
|
47
|
+
Requires-Dist: pydantic-settings>=2.3.0
|
|
48
|
+
Requires-Dist: pydantic>=2.7.0
|
|
49
|
+
Requires-Dist: python-dotenv>=1.0.1
|
|
50
|
+
Requires-Dist: python-multipart>=0.0.9
|
|
51
|
+
Requires-Dist: rich>=13.7.0
|
|
52
|
+
Requires-Dist: sqlalchemy>=2.0.0
|
|
53
|
+
Requires-Dist: tenacity>=8.3.0
|
|
54
|
+
Requires-Dist: uvicorn[standard]>=0.29.0
|
|
55
|
+
Provides-Extra: dev
|
|
56
|
+
Requires-Dist: httpx>=0.27.0; extra == 'dev'
|
|
57
|
+
Requires-Dist: mypy>=1.10.0; extra == 'dev'
|
|
58
|
+
Requires-Dist: pytest-asyncio>=0.23.0; extra == 'dev'
|
|
59
|
+
Requires-Dist: pytest-cov>=5.0.0; extra == 'dev'
|
|
60
|
+
Requires-Dist: pytest>=8.2.0; extra == 'dev'
|
|
61
|
+
Requires-Dist: ruff>=0.4.0; extra == 'dev'
|
|
62
|
+
Provides-Extra: docling
|
|
63
|
+
Requires-Dist: docling>=1.16.0; extra == 'docling'
|
|
64
|
+
Provides-Extra: postgres
|
|
65
|
+
Requires-Dist: asyncpg>=0.29.0; extra == 'postgres'
|
|
66
|
+
Requires-Dist: psycopg2-binary>=2.9.9; extra == 'postgres'
|
|
67
|
+
Description-Content-Type: text/markdown
|
|
68
|
+
|
|
69
|
+
# ApexRAG
|
|
70
|
+
|
|
71
|
+
> **Production-grade, local-first Agentic RAG Library.**
|
|
72
|
+
> Replaces vector similarity search with structural, agentic navigation of documents.
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## π§ The Core Idea
|
|
77
|
+
|
|
78
|
+
Traditional RAG embeds text into vectors and finds the "closest" chunks. This creates **retrieval hallucinations** β the model returns semantically-similar-but-wrong content because it has no understanding of document structure.
|
|
79
|
+
|
|
80
|
+
**ApexRAG takes a fundamentally different approach:**
|
|
81
|
+
|
|
82
|
+
1. **Parse** the document into a structural tree (based on headings) and extract page numbers.
|
|
83
|
+
2. **Synthesize** a 30-word *Semantic Map* for every node using a local LLM.
|
|
84
|
+
3. **Navigate** the tree with an LLM agent that reads summaries and decides which branch to enter β trying multiple candidates if necessary.
|
|
85
|
+
4. **Verify** the exact leaf node answers the query via a strict secondary LLM check (99.999% accuracy).
|
|
86
|
+
5. **Return** the exact leaf node content β not a blended, hallucinated average.
|
|
87
|
+
|
|
88
|
+
```
|
|
89
|
+
Query: "What were Q3 revenues?"
|
|
90
|
+
β
|
|
91
|
+
Root (Annual Report)
|
|
92
|
+
βββ Chapter 1: Executive Summary β LLM: "Not here"
|
|
93
|
+
βββ Chapter 2: Revenue Analysis β LLM: "Enter this"
|
|
94
|
+
βββ Q1 Revenue β LLM: "Not Q3"
|
|
95
|
+
βββ Q2 Revenue β LLM: "Not Q3"
|
|
96
|
+
βββ Q3 Revenue β LLM: "This is it!" β Return content
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## π Project Structure
|
|
102
|
+
|
|
103
|
+
```
|
|
104
|
+
apex_rag/
|
|
105
|
+
βββ src/
|
|
106
|
+
β βββ __init__.py # Public API exports
|
|
107
|
+
β βββ api.py # FastAPI App & UI dashboard
|
|
108
|
+
β βββ client.py # Thread-safe user-facing ApexIndex class
|
|
109
|
+
β βββ ingestion.py # Document parsing & tree synthesis
|
|
110
|
+
β βββ navigation.py # Recursive LLM navigation agent
|
|
111
|
+
β βββ storage.py # SQLAlchemy async ORM & PageIndexEntry
|
|
112
|
+
β βββ utils.py # ReasoningTrace, retry decorator, helpers
|
|
113
|
+
βββ tests/
|
|
114
|
+
β βββ test_tree.py # Parser & storage unit tests
|
|
115
|
+
β βββ test_search.py # Navigation agent unit tests (no Ollama needed)
|
|
116
|
+
βββ examples/
|
|
117
|
+
β βββ basic_usage.py # End-to-end demo
|
|
118
|
+
βββ pyproject.toml
|
|
119
|
+
βββ docker-compose.yml
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## β‘ Quick Start
|
|
125
|
+
|
|
126
|
+
### 1. Install
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
# Clone and set up
|
|
130
|
+
cd ApexRAG
|
|
131
|
+
pip install -e ".[dev]"
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### 2. Start Ollama
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
ollama serve
|
|
138
|
+
ollama pull llama3.1 # or phi3, mistral, etc.
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### 3. Ingest & Query
|
|
142
|
+
|
|
143
|
+
```python
|
|
144
|
+
import asyncio
|
|
145
|
+
from src.client import ApexIndex
|
|
146
|
+
|
|
147
|
+
async def main():
|
|
148
|
+
async with await ApexIndex.create(
|
|
149
|
+
db_url="sqlite+aiosqlite:///apex.db",
|
|
150
|
+
model="llama3.1",
|
|
151
|
+
) as index:
|
|
152
|
+
# Ingest a PDF
|
|
153
|
+
doc_id = await index.ingest("path/to/your/report.pdf")
|
|
154
|
+
|
|
155
|
+
# Query it
|
|
156
|
+
result = await index.query(
|
|
157
|
+
"What are the Q3 revenue figures?",
|
|
158
|
+
doc_id,
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
if result:
|
|
162
|
+
print(result.content)
|
|
163
|
+
print(f"Found at path: {result.path}")
|
|
164
|
+
print(f"Navigation trace: {result.trace}")
|
|
165
|
+
|
|
166
|
+
asyncio.run(main())
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### 4. Start the FastAPI Server & Visual Index Dashboard
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
uvicorn src.api:app --reload
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Open your browser to:
|
|
176
|
+
- **Dashboard:** [http://localhost:8000](http://localhost:8000)
|
|
177
|
+
- **API Docs:** [http://localhost:8000/docs](http://localhost:8000/docs)
|
|
178
|
+
|
|
179
|
+
From the dashboard, you can click on an ingested document to view its full structural tree and its book-style alphabetical page index!
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
## ποΈ Architecture Deep Dive
|
|
184
|
+
|
|
185
|
+
### Ingestion Engine (`ingestion.py`)
|
|
186
|
+
|
|
187
|
+
| Step | Description |
|
|
188
|
+
|------|-------------|
|
|
189
|
+
| **Convert** | `markitdown` or `docling` converts PDF/DOCX β Markdown |
|
|
190
|
+
| **Parse** | Regex walks ATX headings (`#`, `##`, `###`) to build `ParsedSection` tree |
|
|
191
|
+
| **Persist** | Nodes written to DB with LTree-style path (`1.2.3`) |
|
|
192
|
+
| **Synthesize** | Ollama generates 30-word summaries in parallel (bounded by semaphore) |
|
|
193
|
+
|
|
194
|
+
### Storage Layer (`storage.py`)
|
|
195
|
+
|
|
196
|
+
```
|
|
197
|
+
DocumentNode table:
|
|
198
|
+
id BIGINT PRIMARY KEY
|
|
199
|
+
doc_id VARCHAR(255) -- logical document identifier
|
|
200
|
+
parent_id BIGINT FK (self) -- NULL for root nodes
|
|
201
|
+
path VARCHAR(512) -- "1.2.3" LTree-style
|
|
202
|
+
title VARCHAR(512) -- section heading
|
|
203
|
+
summary TEXT -- 30-word Semantic Map
|
|
204
|
+
content TEXT -- leaf content (NULL for intermediate)
|
|
205
|
+
metadata TEXT (JSON) -- page numbers, char count, source file
|
|
206
|
+
depth INTEGER -- nesting level (0 = root)
|
|
207
|
+
position INTEGER -- sibling order
|
|
208
|
+
created_at TIMESTAMP
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
Supports both `sqlite+aiosqlite://` (local) and `postgresql+asyncpg://` (production).
|
|
212
|
+
|
|
213
|
+
### Navigation Agent (`navigation.py`)
|
|
214
|
+
|
|
215
|
+
```
|
|
216
|
+
find(query, doc_id)
|
|
217
|
+
βββ _navigate(current_node)
|
|
218
|
+
βββ [Leaf?] β return content immediately
|
|
219
|
+
βββ fetch children
|
|
220
|
+
βββ _ask_llm(query, child_summaries)
|
|
221
|
+
β βββ "Which child ID contains the answer?"
|
|
222
|
+
βββ [ID returned] β recurse into chosen child
|
|
223
|
+
β βββ [child returns None] β try siblings
|
|
224
|
+
βββ [NONE returned] β backtrack to parent
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
**LLM Response Parsing** is robust β 4-tier fallback:
|
|
228
|
+
1. Strict `json.loads()`
|
|
229
|
+
2. Regex extraction from prose-wrapped JSON
|
|
230
|
+
3. Explicit `"NONE"` keyword detection
|
|
231
|
+
4. Heuristic: scan for any valid child ID number in the response
|
|
232
|
+
|
|
233
|
+
**High Accuracy (99.999%) Verification:**
|
|
234
|
+
At the leaf level, a second LLM prompt strictly verifies if the leaf content answers the query. If it fails, the agent backtracks and explores the fallback candidates (second-best choices) up the tree.
|
|
235
|
+
|
|
236
|
+
### Reasoning Trace (`utils.py`)
|
|
237
|
+
|
|
238
|
+
Every navigation decision is printed with color-coded indicators:
|
|
239
|
+
|
|
240
|
+
```
|
|
241
|
+
βββ ApexRAG Navigation Start βββ
|
|
242
|
+
Query : What are the Q3 revenue figures?
|
|
243
|
+
Root : node_id=1
|
|
244
|
+
|
|
245
|
+
β³ ENTER node=1 path=1
|
|
246
|
+
Covers the full annual financial report for 2024β¦
|
|
247
|
+
β³ EXPLORE node=1 β evaluating 2 child summaries
|
|
248
|
+
β AGENT β node=3 reason: Revenue Analysis contains quarterly breakdown
|
|
249
|
+
β³ ENTER node=3 path=1.2
|
|
250
|
+
β³ EXPLORE node=3 β evaluating 4 child summaries
|
|
251
|
+
β AGENT β node=6 reason: Q3 Revenue section is exactly what's needed
|
|
252
|
+
β
LEAF REACHED node=6
|
|
253
|
+
preview: Q3 revenue was $165M. Growth slowed slightlyβ¦
|
|
254
|
+
|
|
255
|
+
βββ Navigation Complete βββ result=SUCCESS elapsed=3.41s
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
---
|
|
259
|
+
|
|
260
|
+
## π§ͺ Testing
|
|
261
|
+
|
|
262
|
+
```bash
|
|
263
|
+
# Run all tests (no Ollama required)
|
|
264
|
+
pytest
|
|
265
|
+
|
|
266
|
+
# With coverage
|
|
267
|
+
pytest --cov=src --cov-report=term-missing
|
|
268
|
+
|
|
269
|
+
# Specific test file
|
|
270
|
+
pytest tests/test_search.py -v
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
Tests use an in-memory SQLite database and mock LLM responses β zero external dependencies.
|
|
274
|
+
|
|
275
|
+
---
|
|
276
|
+
|
|
277
|
+
## π³ Production Deployment
|
|
278
|
+
|
|
279
|
+
```bash
|
|
280
|
+
# Copy and edit environment
|
|
281
|
+
cp .env.example .env
|
|
282
|
+
|
|
283
|
+
# Start everything (Ollama + PostgreSQL + API)
|
|
284
|
+
docker-compose up -d
|
|
285
|
+
|
|
286
|
+
# Pull the model inside the Ollama container
|
|
287
|
+
docker exec apex_ollama ollama pull llama3.1
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
**Environment variables:**
|
|
291
|
+
|
|
292
|
+
| Variable | Default | Description |
|
|
293
|
+
|----------|---------|-------------|
|
|
294
|
+
| `APEX_DB_URL` | `sqlite+aiosqlite:///apex.db` | SQLAlchemy async DB URL |
|
|
295
|
+
| `APEX_OLLAMA_HOST` | `http://localhost:11434` | Ollama server URL |
|
|
296
|
+
| `APEX_MODEL` | `llama3.1` | Ollama model for navigation |
|
|
297
|
+
| `APEX_LOG_LEVEL` | `INFO` | Logging verbosity |
|
|
298
|
+
|
|
299
|
+
---
|
|
300
|
+
|
|
301
|
+
## π§ Configuration Reference
|
|
302
|
+
|
|
303
|
+
```python
|
|
304
|
+
await ApexIndex.create(
|
|
305
|
+
db_url="postgresql+asyncpg://user:pass@host/db", # Production DB
|
|
306
|
+
ollama_host="http://localhost:11434",
|
|
307
|
+
model="llama3.1", # Navigation model
|
|
308
|
+
summariser_model="phi3", # Cheaper model for ingestion summaries
|
|
309
|
+
max_concurrent_summaries=8, # Parallelism (tune to your GPU VRAM)
|
|
310
|
+
parser_backend="markitdown", # "markitdown" | "docling" | "plaintext"
|
|
311
|
+
trace_enabled=True, # Color-coded console output
|
|
312
|
+
db_echo=False, # SQL query logging
|
|
313
|
+
)
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
---
|
|
317
|
+
|
|
318
|
+
## π Roadmap
|
|
319
|
+
|
|
320
|
+
- [x] FastAPI REST API wrapper (`/documents/ingest/file`, `/query`, `/documents`)
|
|
321
|
+
- [x] Book-style Page Index and Visual tree dashboard
|
|
322
|
+
- [x] Unlimited navigation depth with backtrack and verification
|
|
323
|
+
- [ ] Streaming query responses via SSE
|
|
324
|
+
- [ ] Multi-document cross-reference queries
|
|
325
|
+
- [ ] Alembic migrations for schema versioning
|
|
326
|
+
- [ ] Support for `docling` table extraction (structured data cells as leaf nodes)
|
|
327
|
+
|
|
328
|
+
---
|
|
329
|
+
|
|
330
|
+
## π License
|
|
331
|
+
|
|
332
|
+
MIT License β see [LICENSE](LICENSE).
|
apex_rag-0.1.0/README.md
ADDED
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
# ApexRAG
|
|
2
|
+
|
|
3
|
+
> **Production-grade, local-first Agentic RAG Library.**
|
|
4
|
+
> Replaces vector similarity search with structural, agentic navigation of documents.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## π§ The Core Idea
|
|
9
|
+
|
|
10
|
+
Traditional RAG embeds text into vectors and finds the "closest" chunks. This creates **retrieval hallucinations** β the model returns semantically-similar-but-wrong content because it has no understanding of document structure.
|
|
11
|
+
|
|
12
|
+
**ApexRAG takes a fundamentally different approach:**
|
|
13
|
+
|
|
14
|
+
1. **Parse** the document into a structural tree (based on headings) and extract page numbers.
|
|
15
|
+
2. **Synthesize** a 30-word *Semantic Map* for every node using a local LLM.
|
|
16
|
+
3. **Navigate** the tree with an LLM agent that reads summaries and decides which branch to enter β trying multiple candidates if necessary.
|
|
17
|
+
4. **Verify** the exact leaf node answers the query via a strict secondary LLM check (99.999% accuracy).
|
|
18
|
+
5. **Return** the exact leaf node content β not a blended, hallucinated average.
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
Query: "What were Q3 revenues?"
|
|
22
|
+
β
|
|
23
|
+
Root (Annual Report)
|
|
24
|
+
βββ Chapter 1: Executive Summary β LLM: "Not here"
|
|
25
|
+
βββ Chapter 2: Revenue Analysis β LLM: "Enter this"
|
|
26
|
+
βββ Q1 Revenue β LLM: "Not Q3"
|
|
27
|
+
βββ Q2 Revenue β LLM: "Not Q3"
|
|
28
|
+
βββ Q3 Revenue β LLM: "This is it!" β Return content
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## π Project Structure
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
apex_rag/
|
|
37
|
+
βββ src/
|
|
38
|
+
β βββ __init__.py # Public API exports
|
|
39
|
+
β βββ api.py # FastAPI App & UI dashboard
|
|
40
|
+
β βββ client.py # Thread-safe user-facing ApexIndex class
|
|
41
|
+
β βββ ingestion.py # Document parsing & tree synthesis
|
|
42
|
+
β βββ navigation.py # Recursive LLM navigation agent
|
|
43
|
+
β βββ storage.py # SQLAlchemy async ORM & PageIndexEntry
|
|
44
|
+
β βββ utils.py # ReasoningTrace, retry decorator, helpers
|
|
45
|
+
βββ tests/
|
|
46
|
+
β βββ test_tree.py # Parser & storage unit tests
|
|
47
|
+
β βββ test_search.py # Navigation agent unit tests (no Ollama needed)
|
|
48
|
+
βββ examples/
|
|
49
|
+
β βββ basic_usage.py # End-to-end demo
|
|
50
|
+
βββ pyproject.toml
|
|
51
|
+
βββ docker-compose.yml
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## β‘ Quick Start
|
|
57
|
+
|
|
58
|
+
### 1. Install
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
# Clone and set up
|
|
62
|
+
cd ApexRAG
|
|
63
|
+
pip install -e ".[dev]"
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### 2. Start Ollama
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
ollama serve
|
|
70
|
+
ollama pull llama3.1 # or phi3, mistral, etc.
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### 3. Ingest & Query
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
import asyncio
|
|
77
|
+
from src.client import ApexIndex
|
|
78
|
+
|
|
79
|
+
async def main():
|
|
80
|
+
async with await ApexIndex.create(
|
|
81
|
+
db_url="sqlite+aiosqlite:///apex.db",
|
|
82
|
+
model="llama3.1",
|
|
83
|
+
) as index:
|
|
84
|
+
# Ingest a PDF
|
|
85
|
+
doc_id = await index.ingest("path/to/your/report.pdf")
|
|
86
|
+
|
|
87
|
+
# Query it
|
|
88
|
+
result = await index.query(
|
|
89
|
+
"What are the Q3 revenue figures?",
|
|
90
|
+
doc_id,
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
if result:
|
|
94
|
+
print(result.content)
|
|
95
|
+
print(f"Found at path: {result.path}")
|
|
96
|
+
print(f"Navigation trace: {result.trace}")
|
|
97
|
+
|
|
98
|
+
asyncio.run(main())
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### 4. Start the FastAPI Server & Visual Index Dashboard
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
uvicorn src.api:app --reload
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Open your browser to:
|
|
108
|
+
- **Dashboard:** [http://localhost:8000](http://localhost:8000)
|
|
109
|
+
- **API Docs:** [http://localhost:8000/docs](http://localhost:8000/docs)
|
|
110
|
+
|
|
111
|
+
From the dashboard, you can click on an ingested document to view its full structural tree and its book-style alphabetical page index!
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## ποΈ Architecture Deep Dive
|
|
116
|
+
|
|
117
|
+
### Ingestion Engine (`ingestion.py`)
|
|
118
|
+
|
|
119
|
+
| Step | Description |
|
|
120
|
+
|------|-------------|
|
|
121
|
+
| **Convert** | `markitdown` or `docling` converts PDF/DOCX β Markdown |
|
|
122
|
+
| **Parse** | Regex walks ATX headings (`#`, `##`, `###`) to build `ParsedSection` tree |
|
|
123
|
+
| **Persist** | Nodes written to DB with LTree-style path (`1.2.3`) |
|
|
124
|
+
| **Synthesize** | Ollama generates 30-word summaries in parallel (bounded by semaphore) |
|
|
125
|
+
|
|
126
|
+
### Storage Layer (`storage.py`)
|
|
127
|
+
|
|
128
|
+
```
|
|
129
|
+
DocumentNode table:
|
|
130
|
+
id BIGINT PRIMARY KEY
|
|
131
|
+
doc_id VARCHAR(255) -- logical document identifier
|
|
132
|
+
parent_id BIGINT FK (self) -- NULL for root nodes
|
|
133
|
+
path VARCHAR(512) -- "1.2.3" LTree-style
|
|
134
|
+
title VARCHAR(512) -- section heading
|
|
135
|
+
summary TEXT -- 30-word Semantic Map
|
|
136
|
+
content TEXT -- leaf content (NULL for intermediate)
|
|
137
|
+
metadata TEXT (JSON) -- page numbers, char count, source file
|
|
138
|
+
depth INTEGER -- nesting level (0 = root)
|
|
139
|
+
position INTEGER -- sibling order
|
|
140
|
+
created_at TIMESTAMP
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Supports both `sqlite+aiosqlite://` (local) and `postgresql+asyncpg://` (production).
|
|
144
|
+
|
|
145
|
+
### Navigation Agent (`navigation.py`)
|
|
146
|
+
|
|
147
|
+
```
|
|
148
|
+
find(query, doc_id)
|
|
149
|
+
βββ _navigate(current_node)
|
|
150
|
+
βββ [Leaf?] β return content immediately
|
|
151
|
+
βββ fetch children
|
|
152
|
+
βββ _ask_llm(query, child_summaries)
|
|
153
|
+
β βββ "Which child ID contains the answer?"
|
|
154
|
+
βββ [ID returned] β recurse into chosen child
|
|
155
|
+
β βββ [child returns None] β try siblings
|
|
156
|
+
βββ [NONE returned] β backtrack to parent
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
**LLM Response Parsing** is robust β 4-tier fallback:
|
|
160
|
+
1. Strict `json.loads()`
|
|
161
|
+
2. Regex extraction from prose-wrapped JSON
|
|
162
|
+
3. Explicit `"NONE"` keyword detection
|
|
163
|
+
4. Heuristic: scan for any valid child ID number in the response
|
|
164
|
+
|
|
165
|
+
**High Accuracy (99.999%) Verification:**
|
|
166
|
+
At the leaf level, a second LLM prompt strictly verifies if the leaf content answers the query. If it fails, the agent backtracks and explores the fallback candidates (second-best choices) up the tree.
|
|
167
|
+
|
|
168
|
+
### Reasoning Trace (`utils.py`)
|
|
169
|
+
|
|
170
|
+
Every navigation decision is printed with color-coded indicators:
|
|
171
|
+
|
|
172
|
+
```
|
|
173
|
+
βββ ApexRAG Navigation Start βββ
|
|
174
|
+
Query : What are the Q3 revenue figures?
|
|
175
|
+
Root : node_id=1
|
|
176
|
+
|
|
177
|
+
β³ ENTER node=1 path=1
|
|
178
|
+
Covers the full annual financial report for 2024β¦
|
|
179
|
+
β³ EXPLORE node=1 β evaluating 2 child summaries
|
|
180
|
+
β AGENT β node=3 reason: Revenue Analysis contains quarterly breakdown
|
|
181
|
+
β³ ENTER node=3 path=1.2
|
|
182
|
+
β³ EXPLORE node=3 β evaluating 4 child summaries
|
|
183
|
+
β AGENT β node=6 reason: Q3 Revenue section is exactly what's needed
|
|
184
|
+
β
LEAF REACHED node=6
|
|
185
|
+
preview: Q3 revenue was $165M. Growth slowed slightlyβ¦
|
|
186
|
+
|
|
187
|
+
βββ Navigation Complete βββ result=SUCCESS elapsed=3.41s
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
## π§ͺ Testing
|
|
193
|
+
|
|
194
|
+
```bash
|
|
195
|
+
# Run all tests (no Ollama required)
|
|
196
|
+
pytest
|
|
197
|
+
|
|
198
|
+
# With coverage
|
|
199
|
+
pytest --cov=src --cov-report=term-missing
|
|
200
|
+
|
|
201
|
+
# Specific test file
|
|
202
|
+
pytest tests/test_search.py -v
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
Tests use an in-memory SQLite database and mock LLM responses β zero external dependencies.
|
|
206
|
+
|
|
207
|
+
---
|
|
208
|
+
|
|
209
|
+
## π³ Production Deployment
|
|
210
|
+
|
|
211
|
+
```bash
|
|
212
|
+
# Copy and edit environment
|
|
213
|
+
cp .env.example .env
|
|
214
|
+
|
|
215
|
+
# Start everything (Ollama + PostgreSQL + API)
|
|
216
|
+
docker-compose up -d
|
|
217
|
+
|
|
218
|
+
# Pull the model inside the Ollama container
|
|
219
|
+
docker exec apex_ollama ollama pull llama3.1
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
**Environment variables:**
|
|
223
|
+
|
|
224
|
+
| Variable | Default | Description |
|
|
225
|
+
|----------|---------|-------------|
|
|
226
|
+
| `APEX_DB_URL` | `sqlite+aiosqlite:///apex.db` | SQLAlchemy async DB URL |
|
|
227
|
+
| `APEX_OLLAMA_HOST` | `http://localhost:11434` | Ollama server URL |
|
|
228
|
+
| `APEX_MODEL` | `llama3.1` | Ollama model for navigation |
|
|
229
|
+
| `APEX_LOG_LEVEL` | `INFO` | Logging verbosity |
|
|
230
|
+
|
|
231
|
+
---
|
|
232
|
+
|
|
233
|
+
## π§ Configuration Reference
|
|
234
|
+
|
|
235
|
+
```python
|
|
236
|
+
await ApexIndex.create(
|
|
237
|
+
db_url="postgresql+asyncpg://user:pass@host/db", # Production DB
|
|
238
|
+
ollama_host="http://localhost:11434",
|
|
239
|
+
model="llama3.1", # Navigation model
|
|
240
|
+
summariser_model="phi3", # Cheaper model for ingestion summaries
|
|
241
|
+
max_concurrent_summaries=8, # Parallelism (tune to your GPU VRAM)
|
|
242
|
+
parser_backend="markitdown", # "markitdown" | "docling" | "plaintext"
|
|
243
|
+
trace_enabled=True, # Color-coded console output
|
|
244
|
+
db_echo=False, # SQL query logging
|
|
245
|
+
)
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
---
|
|
249
|
+
|
|
250
|
+
## π Roadmap
|
|
251
|
+
|
|
252
|
+
- [x] FastAPI REST API wrapper (`/documents/ingest/file`, `/query`, `/documents`)
|
|
253
|
+
- [x] Book-style Page Index and Visual tree dashboard
|
|
254
|
+
- [x] Unlimited navigation depth with backtrack and verification
|
|
255
|
+
- [ ] Streaming query responses via SSE
|
|
256
|
+
- [ ] Multi-document cross-reference queries
|
|
257
|
+
- [ ] Alembic migrations for schema versioning
|
|
258
|
+
- [ ] Support for `docling` table extraction (structured data cells as leaf nodes)
|
|
259
|
+
|
|
260
|
+
---
|
|
261
|
+
|
|
262
|
+
## π License
|
|
263
|
+
|
|
264
|
+
MIT License β see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
version: "3.9"
|
|
2
|
+
|
|
3
|
+
services:
|
|
4
|
+
# ββ Ollama βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
5
|
+
ollama:
|
|
6
|
+
image: ollama/ollama:latest
|
|
7
|
+
container_name: apex_ollama
|
|
8
|
+
ports:
|
|
9
|
+
- "11434:11434"
|
|
10
|
+
volumes:
|
|
11
|
+
- ollama_data:/root/.ollama
|
|
12
|
+
deploy:
|
|
13
|
+
resources:
|
|
14
|
+
reservations:
|
|
15
|
+
devices:
|
|
16
|
+
- driver: nvidia
|
|
17
|
+
count: all
|
|
18
|
+
capabilities: [gpu]
|
|
19
|
+
restart: unless-stopped
|
|
20
|
+
healthcheck:
|
|
21
|
+
test: ["CMD", "curl", "-f", "http://localhost:11434/api/tags"]
|
|
22
|
+
interval: 30s
|
|
23
|
+
timeout: 10s
|
|
24
|
+
retries: 5
|
|
25
|
+
start_period: 60s
|
|
26
|
+
|
|
27
|
+
# ββ PostgreSQL (production storage) ββββββββββββββββββββββββββββββββββββ
|
|
28
|
+
postgres:
|
|
29
|
+
image: postgres:16-alpine
|
|
30
|
+
container_name: apex_postgres
|
|
31
|
+
environment:
|
|
32
|
+
POSTGRES_USER: ${POSTGRES_USER:-apex}
|
|
33
|
+
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-apexsecret}
|
|
34
|
+
POSTGRES_DB: ${POSTGRES_DB:-apex_rag}
|
|
35
|
+
ports:
|
|
36
|
+
- "5432:5432"
|
|
37
|
+
volumes:
|
|
38
|
+
- postgres_data:/var/lib/postgresql/data
|
|
39
|
+
restart: unless-stopped
|
|
40
|
+
healthcheck:
|
|
41
|
+
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-apex} -d ${POSTGRES_DB:-apex_rag}"]
|
|
42
|
+
interval: 10s
|
|
43
|
+
timeout: 5s
|
|
44
|
+
retries: 5
|
|
45
|
+
|
|
46
|
+
# ββ ApexRAG FastAPI Service βββββββββββββββββββββββββββββββββββββββββββββ
|
|
47
|
+
apex_api:
|
|
48
|
+
build:
|
|
49
|
+
context: .
|
|
50
|
+
dockerfile: Dockerfile
|
|
51
|
+
container_name: apex_api
|
|
52
|
+
ports:
|
|
53
|
+
- "8000:8000"
|
|
54
|
+
environment:
|
|
55
|
+
APEX_DB_URL: postgresql+asyncpg://${POSTGRES_USER:-apex}:${POSTGRES_PASSWORD:-apexsecret}@postgres:5432/${POSTGRES_DB:-apex_rag}
|
|
56
|
+
APEX_OLLAMA_HOST: http://ollama:11434
|
|
57
|
+
APEX_MODEL: ${APEX_MODEL:-llama3.1}
|
|
58
|
+
APEX_LOG_LEVEL: ${APEX_LOG_LEVEL:-INFO}
|
|
59
|
+
depends_on:
|
|
60
|
+
postgres:
|
|
61
|
+
condition: service_healthy
|
|
62
|
+
ollama:
|
|
63
|
+
condition: service_healthy
|
|
64
|
+
volumes:
|
|
65
|
+
- ./uploads:/app/uploads
|
|
66
|
+
restart: unless-stopped
|
|
67
|
+
|
|
68
|
+
volumes:
|
|
69
|
+
ollama_data:
|
|
70
|
+
postgres_data:
|