Skip to content

Commit 66d7dad

Browse files
authored
Merge pull request #2 from Orbifold/v2
Tremendous amount of work and time went into this one.
2 parents 167755b + befc9f9 commit 66d7dad

216 files changed

Lines changed: 19965 additions & 12244 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/copilot-instructions.md

Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
1+
# KNWL AI Agent Instructions
2+
3+
**v2 branch is under active development** - major architectural changes from v1.
4+
5+
## Architecture Overview
6+
7+
KNWL is a Graph RAG Python package with pluggable components orchestrated through three core systems:
8+
9+
### 1. Configuration System (`knwl/config.py`)
10+
11+
Hierarchical dictionary with **service variants** enabling runtime component swapping:
12+
13+
```python
14+
"llm": {
15+
"default": "ollama", # specifies default variant
16+
"ollama": {"class": "knwl.llm.ollama.OllamaClient", "model": "o14", ...},
17+
"openai": {"class": "knwl.llm.openai.OpenAIClient", "model": "gpt-4o-mini", ...}
18+
}
19+
```
20+
21+
**Key features:**
22+
- **Cross-references**: `"@/llm/ollama"` resolves to config at `llm.ollama`
23+
- **Path placeholders**: `$root` (project root), `$/tests` (tests/data), `$/data` expand dynamically
24+
- **Deep merge**: `override` parameter merges recursively, doesn't replace entire sections
25+
- Access via `get_config("llm", "model", override={...})`
26+
27+
### 2. Dependency Injection (`knwl/di.py`)
28+
29+
Decorator-based DI eliminates manual service instantiation:
30+
31+
```python
32+
@service("llm", variant="ollama", param_name="ai")
33+
@singleton_service("graph", variant="nx") # reuses same instance
34+
@inject_config("api.host", "api.port") # pulls config values
35+
@defaults("json") # injects service's default config
36+
async def process(text: str, ai=None, graph=None, host=None, port=None):
37+
# All params automatically injected from config
38+
pass
39+
```
40+
41+
All decorators accept `override` dict for context-specific config without modifying defaults.
42+
43+
### 3. Services Registry (`knwl/services.py`)
44+
45+
Dynamic class loading via config:
46+
- Service definitions must include `"class": "full.module.path.ClassName"`
47+
- Parse names: `services.get_service("vector/chroma")` or `("vector", "chroma")`
48+
- Singletons cached by service+variant+override hash
49+
50+
### 4. Framework Base (`knwl/framework_base.py`)
51+
52+
All components inherit from `FrameworkBase` (ABC) providing:
53+
- `get_service(name, variant)` - fetch any configured service
54+
- `get_llm(variant)` - shorthand for LLM service
55+
- `ensure_path_exists(path)` - cross-platform path handling
56+
- `id` - unique UUID per instance
57+
58+
## Core Data Flow
59+
60+
```
61+
Input Text → Documents → Chunks → Graph Extraction → SemanticGraph → Vector Storage
62+
63+
KnwlExtraction (nodes+edges)
64+
65+
Merge & Summarize → Storage (JSON/Chroma/NetworkX)
66+
```
67+
68+
**Main orchestration** in `GraphRAG` class (`knwl/semantic/graph_rag/graph_rag.py`):
69+
70+
1. **`ingest(inputs)`** - Primary ingestion pipeline:
71+
- Processes `KnwlInput` → chunks text → extracts graph → stores in semantic graph
72+
- Returns `KnwlIngestion` with metadata
73+
- Optionally chunks and stores text if `ragger` is provided
74+
75+
2. **`augment(inputs, params)`** - Query/augmentation with multiple strategies:
76+
- **local**: Keywords → nodes → retrieve neighborhood + chunks
77+
- **global**: Keywords → edges → retrieve endpoints + edge chunks
78+
- **naive**: Direct semantic chunk retrieval (no graph)
79+
- **hybrid**: Combines local + global contexts
80+
- Returns `KnwlContext` with augmented context and references
81+
82+
## Models (`knwl/models/`)
83+
84+
**All models are immutable Pydantic BaseModels** (`model_config = {"frozen": True}`):
85+
86+
- Auto-generated `id` via hash of key fields (e.g., `KnwlNode.hash_keys(name, type)`)
87+
- Serialize with `model_dump(mode="json")`
88+
- Update via `model.model_copy(update={"field": new_value})`
89+
90+
**Key models:**
91+
- `KnwlNode` - graph vertices (name, type, description, chunk_ids)
92+
- `KnwlEdge` - graph edges (source_id, targetId, description, keywords, weight)
93+
- `KnwlExtraction` - raw LLM extraction (dicts of nodes/edges keyed by name)
94+
- `KnwlGraph` - final graph (lists of `KnwlNode`/`KnwlEdge`)
95+
- `KnwlChunk`, `KnwlDocument`, `KnwlInput` - pipeline data structures
96+
97+
## Component Architecture
98+
99+
**Base classes define pluggable interfaces:**
100+
- `LLMBase``OllamaClient`, `OpenAIClient`
101+
- `ChunkingBase``TiktokenChunking`
102+
- `StorageBase``JsonStorage`, `SqliteStorage`
103+
- `VectorStorageBase``ChromaStorage`
104+
- `GraphStorageBase``NetworkXGraphStorage`
105+
- `GraphExtractionBase``BasicGraphExtraction`, `GleanGraphExtraction`
106+
- `GraphRAGBase``GraphRAG`
107+
- `SemanticGraphBase``SemanticGraph`
108+
- `RagBase``RagStore`
109+
- `GragStrategyBase``LocalGragStrategy`, `GlobalGragStrategy`, `NaiveGragStrategy`, `HybridGragStrategy`
110+
- `EntityExtractionBase`, `KeywordsExtractionBase`, `FormatterBase`, etc.
111+
112+
**Storage namespaces** isolate data:
113+
```python
114+
JsonStorage(namespace="documents") # → {path}/documents.json
115+
ChromaStorage(namespace="nodes") # → Chroma collection "nodes"
116+
NetworkXGraphStorage(namespace="kg") # → {path}/kg.graphml
117+
```
118+
119+
## Testing
120+
121+
**Fast tests** (skip LLM integration):
122+
```bash
123+
uv run pytest -m "not llm"
124+
```
125+
126+
**Full suite** (requires Ollama running):
127+
```bash
128+
uv run pytest
129+
```
130+
131+
**Test markers** (`pytest.ini`):
132+
- `@pytest.mark.llm` - needs Ollama/LLM
133+
- `@pytest.mark.asyncio` - async test
134+
- `@pytest.mark.integration` - external services
135+
- `@pytest.mark.slow` - long-running
136+
- `@pytest.mark.basic` - basic tests that don't require external services
137+
138+
**Important**: DI container persists between tests - clear state in `setup_method()` if tests interfere.
139+
140+
## Development Patterns
141+
142+
### Adding a Service Variant
143+
144+
1. **Create class** inheriting from base (e.g., `LLMBase`)
145+
2. **Add to config** in `knwl/config.py`:
146+
```python
147+
"llm": {
148+
"default": "ollama",
149+
"my_llm": {
150+
"class": "knwl.llm.my_llm.MyLLMClient",
151+
"api_key": "...",
152+
"model": "..."
153+
}
154+
}
155+
```
156+
3. **Use via DI**: `@service("llm", variant="my_llm")`
157+
158+
### Working with Immutable Models
159+
160+
```python
161+
# ❌ WRONG - models are frozen
162+
node.description = "new desc"
163+
164+
# ✅ CORRECT
165+
updated_node = node.model_copy(update={"description": "new desc"})
166+
```
167+
168+
### Async Parallelism
169+
170+
Prefer `asyncio.gather()` for concurrent operations:
171+
```python
172+
nodes = await asyncio.gather(*[
173+
self.merge_nodes_into_graph(k, v)
174+
for k, v in extraction.nodes.items()
175+
])
176+
```
177+
178+
### Config Overrides
179+
180+
```python
181+
# Override specific config without modifying defaults
182+
override = {"llm": {"temperature": 0.5}}
183+
184+
@service("llm", override=override)
185+
async def my_func(llm=None):
186+
# llm uses temperature=0.5 instead of default
187+
pass
188+
```
189+
190+
## Common Pitfalls
191+
192+
1. **Frozen models**: Use `.model_copy(update={...})` not direct assignment
193+
2. **Config references**: `@/path/to/service` only works in config dict, not arbitrary strings
194+
3. **Service class paths**: Must be importable Python paths (e.g., `knwl.llm.ollama.OllamaClient`)
195+
4. **DI container state**: Persists across tests - may need cleanup
196+
5. **Path handling**: Always use `ensure_path_exists()` or `get_full_path()` from `FrameworkBase`
197+
6. **Namespace confusion**: Storage instances with same class but different namespaces are distinct
198+
199+
## Entry Points
200+
201+
- **Library**: `from knwl.semantic.graph_rag.graph_rag import GraphRAG; rag = GraphRAG(...); await rag.ingest(input)`
202+
- **API**: `api/main.py` - FastAPI REST service (uvicorn, configurable workers)
203+
204+
**Running API**:
205+
```bash
206+
# Development (auto-reload)
207+
python api/main.py # reads config from api.host, api.port, api.development
208+
209+
# Production
210+
uvicorn api.main:app --host 0.0.0.0 --port 9000 --workers 8
211+
```
212+
213+
## Project Management
214+
215+
**Package manager**: `uv` (not pip/poetry)
216+
- Dependencies in `pyproject.toml`
217+
- Install: `uv sync`
218+
- Run scripts: `uv run pytest`, `uv run python cli.py`
219+
220+
**Design journal**: `journal/*.md` explains architectural decisions
221+
- `DependencyInjection.md` - DI framework rationale
222+
- `GraphRAG.md`, `GraphExtraction.md` - Graph RAG strategy
223+
- `Models.md` - Data model design
224+
225+
## Key Files for Reference
226+
227+
- `knwl/semantic/graph_rag/graph_rag.py` - Main `GraphRAG` orchestration class
228+
- `knwl/di.py` - DI framework (~930 lines, see `tests/test_di.py`)
229+
- `knwl/config.py` - Config structure, `get_config()`, merge logic
230+
- `knwl/services.py` - Service registry, dynamic class loading
231+
- `knwl/framework_base.py` - Base class for all components
232+
- `knwl/prompts/extraction_prompts.py` - LLM prompts for entity extraction
233+
- `knwl/semantic/graph/semantic_graph.py` - Semantic graph implementation
234+
- `knwl/semantic/rag/rag_store.py` - RAG store for chunk management
235+
- `tests/fixtures.py` - Test data and shared fixtures

.gitignore

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,23 @@
11
__pycache__
22
*.egg-info
3-
dickens/
4-
book.txt
3+
54
.idea/
65
dist/
7-
venv
8-
tests/workdir/**/*
6+
.venv
7+
tests/data/**/*
98
.coverage
109
**/.DS_Store
1110
workdir/
1211
.idea
1312
.vscode
13+
**/*.log
14+
.pytest_cache/
15+
/data/
16+
17+
# Environment variables
18+
.env
19+
.env.local
20+
.env.*.local
21+
tests/library/**/*.md
22+
benchmarks/results/
23+
benchmarks/knwl_data/

LICENSE

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
MIT License
2+
3+
Copyright (c) 2025 Orbifold Consulting (Francois Vanderseypen)
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.
22+
23+
Project: Kwnl — https://knwl.ai
24+
Repository: https://github.com/Orbifold/knwl
25+
Contact: info@orbifold.net

0 commit comments

Comments
 (0)