ragtools 0.1.0__py3-none-any.whl
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.
- ragtools/__init__.py +44 -0
- ragtools/embedders.py +34 -0
- ragtools/fusers.py +31 -0
- ragtools/generators.py +28 -0
- ragtools/indexes.py +153 -0
- ragtools/parsers.py +75 -0
- ragtools/py.typed +0 -0
- ragtools/scorers.py +21 -0
- ragtools/stores.py +118 -0
- ragtools-0.1.0.dist-info/METADATA +29 -0
- ragtools-0.1.0.dist-info/RECORD +12 -0
- ragtools-0.1.0.dist-info/WHEEL +4 -0
ragtools/__init__.py
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
from ragtools.embedders import Embedder, SentenceTransformerEmbedder, SpladeEmbedder
|
|
2
|
+
from ragtools.fusers import BordaCountFuser, Fuser, ReciprocalRankFuser
|
|
3
|
+
from ragtools.generators import Generator, TransformersGenerator
|
|
4
|
+
from ragtools.indexes import FaissEmbeddingIndex, Index, SparseEmbeddingIndex
|
|
5
|
+
from ragtools.parsers import (
|
|
6
|
+
ChunkParser,
|
|
7
|
+
CsvFileElementParser,
|
|
8
|
+
ElementPageIndexParser,
|
|
9
|
+
ElementTextParser,
|
|
10
|
+
MdFileElementParser,
|
|
11
|
+
Parser,
|
|
12
|
+
PdfFileElementParser,
|
|
13
|
+
TextFileElementParser,
|
|
14
|
+
)
|
|
15
|
+
from ragtools.scorers import CrossEncoderScorer, Scorer
|
|
16
|
+
from ragtools.stores import DirectoryStore, FileStore, MemoryStore, Store
|
|
17
|
+
|
|
18
|
+
__all__ = [
|
|
19
|
+
"BordaCountFuser",
|
|
20
|
+
"ChunkParser",
|
|
21
|
+
"CrossEncoderScorer",
|
|
22
|
+
"CsvFileElementParser",
|
|
23
|
+
"DirectoryStore",
|
|
24
|
+
"ElementPageIndexParser",
|
|
25
|
+
"ElementTextParser",
|
|
26
|
+
"Embedder",
|
|
27
|
+
"FaissEmbeddingIndex",
|
|
28
|
+
"FileStore",
|
|
29
|
+
"Fuser",
|
|
30
|
+
"Generator",
|
|
31
|
+
"Index",
|
|
32
|
+
"MdFileElementParser",
|
|
33
|
+
"MemoryStore",
|
|
34
|
+
"Parser",
|
|
35
|
+
"PdfFileElementParser",
|
|
36
|
+
"ReciprocalRankFuser",
|
|
37
|
+
"Scorer",
|
|
38
|
+
"SentenceTransformerEmbedder",
|
|
39
|
+
"SparseEmbeddingIndex",
|
|
40
|
+
"SpladeEmbedder",
|
|
41
|
+
"Store",
|
|
42
|
+
"TextFileElementParser",
|
|
43
|
+
"TransformersGenerator",
|
|
44
|
+
]
|
ragtools/embedders.py
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from functools import cached_property
|
|
3
|
+
from typing import Protocol, cast
|
|
4
|
+
|
|
5
|
+
from sentence_transformers import SentenceTransformer, SparseEncoder
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Embedder[E](Protocol):
|
|
9
|
+
def embed(self, chunk: str) -> E: ...
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class SentenceTransformerEmbedder:
|
|
14
|
+
model_name: str
|
|
15
|
+
|
|
16
|
+
@cached_property
|
|
17
|
+
def model(self) -> SentenceTransformer:
|
|
18
|
+
return SentenceTransformer(self.model_name)
|
|
19
|
+
|
|
20
|
+
def embed(self, chunk: str) -> list[float]:
|
|
21
|
+
return self.model.encode(chunk, normalize_embeddings=True).tolist()
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass
|
|
25
|
+
class SpladeEmbedder:
|
|
26
|
+
model_name: str
|
|
27
|
+
|
|
28
|
+
@cached_property
|
|
29
|
+
def model(self) -> SparseEncoder:
|
|
30
|
+
return SparseEncoder(self.model_name)
|
|
31
|
+
|
|
32
|
+
def embed(self, chunk: str) -> dict[str, float]:
|
|
33
|
+
embeddings = self.model.encode(chunk)
|
|
34
|
+
return dict(cast(list[tuple[str, float]], [*self.model.decode(embeddings)]))
|
ragtools/fusers.py
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from collections.abc import Hashable, Sequence
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from typing import Protocol
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Fuser[V](Protocol):
|
|
7
|
+
def fuse(self, *values: Sequence[V]) -> list[V]: ...
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass(slots=True)
|
|
11
|
+
class ReciprocalRankFuser[V: Hashable]:
|
|
12
|
+
k: int = 60
|
|
13
|
+
|
|
14
|
+
def fuse(self, *values: Sequence[V]) -> list[V]:
|
|
15
|
+
scores: dict[V, float] = {}
|
|
16
|
+
for ranking in values:
|
|
17
|
+
for rank, value in enumerate(ranking, start=1):
|
|
18
|
+
weight = 1 / (self.k + rank)
|
|
19
|
+
scores[value] = scores.get(value, 0.0) + weight
|
|
20
|
+
return sorted(scores, key=lambda value: scores[value], reverse=True)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass(slots=True)
|
|
24
|
+
class BordaCountFuser[V: Hashable]:
|
|
25
|
+
def fuse(self, *values: Sequence[V]) -> list[V]:
|
|
26
|
+
scores: dict[V, float] = {}
|
|
27
|
+
for ranking in values:
|
|
28
|
+
for rank, value in enumerate(ranking, start=1):
|
|
29
|
+
weight = len(ranking) - rank + 1
|
|
30
|
+
scores[value] = scores.get(value, 0.0) + weight
|
|
31
|
+
return sorted(scores, key=lambda value: scores[value], reverse=True)
|
ragtools/generators.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from functools import cached_property
|
|
3
|
+
from typing import Protocol
|
|
4
|
+
|
|
5
|
+
from transformers import TextGenerationPipeline, pipeline
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Generator(Protocol):
|
|
9
|
+
def prompt(self, query: str) -> str: ...
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class TransformersGenerator:
|
|
14
|
+
model_name: str
|
|
15
|
+
max_new_tokens: int
|
|
16
|
+
|
|
17
|
+
@cached_property
|
|
18
|
+
def model(self) -> TextGenerationPipeline:
|
|
19
|
+
return pipeline("text-generation", model=self.model_name)
|
|
20
|
+
|
|
21
|
+
def prompt(self, query: str) -> str:
|
|
22
|
+
chat = [{"role": "user", "content": query}]
|
|
23
|
+
outputs = self.model(
|
|
24
|
+
chat,
|
|
25
|
+
max_new_tokens=self.max_new_tokens,
|
|
26
|
+
do_sample=True,
|
|
27
|
+
)
|
|
28
|
+
return outputs[0]["generated_text"][-1]["content"]
|
ragtools/indexes.py
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import builtins
|
|
2
|
+
from collections.abc import Hashable
|
|
3
|
+
from typing import Protocol
|
|
4
|
+
|
|
5
|
+
import faiss
|
|
6
|
+
import numpy as np
|
|
7
|
+
from scipy.sparse import csr_matrix
|
|
8
|
+
|
|
9
|
+
from ragtools.stores import Store
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Index[K, V](Store[K, V], Protocol):
|
|
13
|
+
def closest(self, key: K, k: int) -> tuple[K, ...]: ...
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class FaissEmbeddingIndex:
|
|
17
|
+
def __init__(self, dimensions: int) -> None:
|
|
18
|
+
self._index = faiss.IndexIDMap2(faiss.IndexFlatL2(dimensions))
|
|
19
|
+
self._ids: builtins.set[int] = builtins.set()
|
|
20
|
+
|
|
21
|
+
def set(self, key: int, value: list[float]) -> None:
|
|
22
|
+
self.delete(key)
|
|
23
|
+
self._index.add_with_ids(
|
|
24
|
+
np.ascontiguousarray([value], dtype=np.float32),
|
|
25
|
+
np.array([key], dtype=np.int64),
|
|
26
|
+
)
|
|
27
|
+
self._ids.add(key)
|
|
28
|
+
|
|
29
|
+
def get(self, key: int) -> list[float]:
|
|
30
|
+
if key not in self._ids:
|
|
31
|
+
raise KeyError(key)
|
|
32
|
+
return self._index.reconstruct(key).tolist()
|
|
33
|
+
|
|
34
|
+
def delete(self, key: int) -> None:
|
|
35
|
+
if key in self._ids:
|
|
36
|
+
ids = np.array([key], dtype=np.int64)
|
|
37
|
+
self._index.remove_ids(faiss.IDSelectorBatch(len(ids), faiss.swig_ptr(ids)))
|
|
38
|
+
self._ids.discard(key)
|
|
39
|
+
|
|
40
|
+
def contains(self, key: int) -> bool:
|
|
41
|
+
return key in self._ids
|
|
42
|
+
|
|
43
|
+
def keys(self) -> builtins.set[int]:
|
|
44
|
+
return builtins.set(self._ids)
|
|
45
|
+
|
|
46
|
+
def closest(self, key: int, k: int) -> tuple[int, ...]:
|
|
47
|
+
if k <= 0 or self._index.ntotal == 0:
|
|
48
|
+
return ()
|
|
49
|
+
if key not in self._ids:
|
|
50
|
+
raise KeyError(key)
|
|
51
|
+
vec = self._index.reconstruct(key)[None]
|
|
52
|
+
_, ids = self._index.search(vec, min(k + 1, self._index.ntotal))
|
|
53
|
+
return tuple(int(fid) for fid in ids[0] if fid != -1 and int(fid) != key)[:k]
|
|
54
|
+
|
|
55
|
+
def __len__(self) -> int:
|
|
56
|
+
return self._index.ntotal
|
|
57
|
+
|
|
58
|
+
def __contains__(self, key: int) -> bool:
|
|
59
|
+
return self.contains(key)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class SparseEmbeddingIndex[K: Hashable]:
|
|
63
|
+
def __init__(self) -> None:
|
|
64
|
+
self._data = np.empty(0, dtype=np.float32)
|
|
65
|
+
self._indices = np.empty(0, dtype=np.int32)
|
|
66
|
+
self._indptr = np.zeros(1, dtype=np.int64)
|
|
67
|
+
self._rows: list[K] = []
|
|
68
|
+
self._row_of: dict[K, int] = {}
|
|
69
|
+
self._vocab: dict[str, int] = {}
|
|
70
|
+
self._tokens: list[str] = []
|
|
71
|
+
|
|
72
|
+
def set(self, key: K, value: dict[str, float]) -> None:
|
|
73
|
+
self.delete(key)
|
|
74
|
+
clean = {t: w for t, w in value.items() if w}
|
|
75
|
+
cols = []
|
|
76
|
+
for t in clean:
|
|
77
|
+
if t not in self._vocab:
|
|
78
|
+
self._vocab[t] = len(self._tokens)
|
|
79
|
+
self._tokens.append(t)
|
|
80
|
+
cols.append(self._vocab[t])
|
|
81
|
+
|
|
82
|
+
self._data = np.concatenate(
|
|
83
|
+
[self._data, np.fromiter(clean.values(), np.float32, len(clean))]
|
|
84
|
+
)
|
|
85
|
+
self._indices = np.concatenate(
|
|
86
|
+
[self._indices, np.fromiter(cols, np.int32, len(clean))]
|
|
87
|
+
)
|
|
88
|
+
self._indptr = np.append(self._indptr, len(self._data))
|
|
89
|
+
self._row_of[key] = len(self._rows)
|
|
90
|
+
self._rows.append(key)
|
|
91
|
+
|
|
92
|
+
def get(self, key: K) -> dict[str, float]:
|
|
93
|
+
if key not in self._row_of:
|
|
94
|
+
raise KeyError(key)
|
|
95
|
+
r = self._row_of[key]
|
|
96
|
+
start, end = int(self._indptr[r]), int(self._indptr[r + 1])
|
|
97
|
+
return {
|
|
98
|
+
self._tokens[c]: float(w)
|
|
99
|
+
for c, w in zip(
|
|
100
|
+
self._indices[start:end], self._data[start:end], strict=True
|
|
101
|
+
)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
def delete(self, key: K) -> None:
|
|
105
|
+
if (r := self._row_of.pop(key, None)) is None:
|
|
106
|
+
return
|
|
107
|
+
start, end = int(self._indptr[r]), int(self._indptr[r + 1])
|
|
108
|
+
self._data = np.delete(self._data, slice(start, end))
|
|
109
|
+
self._indices = np.delete(self._indices, slice(start, end))
|
|
110
|
+
counts = np.diff(self._indptr)
|
|
111
|
+
keep = np.ones(counts.size, dtype=bool)
|
|
112
|
+
keep[r] = False
|
|
113
|
+
self._indptr = np.concatenate(
|
|
114
|
+
[np.zeros(1, dtype=np.int64), np.cumsum(counts[keep], dtype=np.int64)]
|
|
115
|
+
)
|
|
116
|
+
del self._rows[r]
|
|
117
|
+
self._row_of = {k: i for i, k in enumerate(self._rows)}
|
|
118
|
+
|
|
119
|
+
def contains(self, key: K) -> bool:
|
|
120
|
+
return key in self._row_of
|
|
121
|
+
|
|
122
|
+
def keys(self) -> builtins.set[K]:
|
|
123
|
+
return builtins.set(self._row_of)
|
|
124
|
+
|
|
125
|
+
def closest(self, key: K, k: int) -> tuple[K, ...]:
|
|
126
|
+
if key not in self._row_of:
|
|
127
|
+
raise KeyError(key)
|
|
128
|
+
if k <= 0 or len(self._rows) <= 1 or len(self._vocab) == 0:
|
|
129
|
+
return ()
|
|
130
|
+
r = self._row_of[key]
|
|
131
|
+
start, end = int(self._indptr[r]), int(self._indptr[r + 1])
|
|
132
|
+
q = np.zeros(len(self._vocab), dtype=np.float32)
|
|
133
|
+
q[self._indices[start:end]] = self._data[start:end]
|
|
134
|
+
scores = self._matrix @ q
|
|
135
|
+
scores[r] = -np.inf
|
|
136
|
+
scores[scores == 0.0] = -np.inf
|
|
137
|
+
k_eff = min(k, len(self._rows) - 1)
|
|
138
|
+
top = np.argpartition(-scores, k_eff - 1)[:k_eff]
|
|
139
|
+
top = top[np.argsort(-scores[top], kind="stable")]
|
|
140
|
+
return tuple(self._rows[i] for i in top if scores[i] != -np.inf)
|
|
141
|
+
|
|
142
|
+
@property
|
|
143
|
+
def _matrix(self) -> csr_matrix:
|
|
144
|
+
return csr_matrix(
|
|
145
|
+
(self._data, self._indices, self._indptr),
|
|
146
|
+
shape=(len(self._rows), max(len(self._vocab), 1)),
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
def __len__(self) -> int:
|
|
150
|
+
return len(self._rows)
|
|
151
|
+
|
|
152
|
+
def __contains__(self, key: K) -> bool:
|
|
153
|
+
return self.contains(key)
|
ragtools/parsers.py
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from functools import cached_property
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Protocol, cast
|
|
5
|
+
|
|
6
|
+
from semantic_chunker import TextSplitter, get_chunker
|
|
7
|
+
from unstructured.documents.elements import Element
|
|
8
|
+
from unstructured.partition.csv import partition_csv
|
|
9
|
+
from unstructured.partition.md import partition_md
|
|
10
|
+
from unstructured.partition.pdf import partition_pdf
|
|
11
|
+
from unstructured.partition.text import partition_text
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Parser[D, V](Protocol):
|
|
15
|
+
def units(self, data: D) -> V: ...
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass(slots=True)
|
|
19
|
+
class CsvFileElementParser:
|
|
20
|
+
def units(self, data: Path) -> tuple[Element, ...]:
|
|
21
|
+
return tuple(partition_csv(str(data)))
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass(slots=True)
|
|
25
|
+
class MdFileElementParser:
|
|
26
|
+
def units(self, data: Path) -> tuple[Element, ...]:
|
|
27
|
+
return tuple(partition_md(str(data)))
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass(slots=True)
|
|
31
|
+
class PdfFileElementParser:
|
|
32
|
+
def units(self, data: Path) -> tuple[Element, ...]:
|
|
33
|
+
return tuple(partition_pdf(str(data)))
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass(slots=True)
|
|
37
|
+
class TextFileElementParser:
|
|
38
|
+
def units(self, data: Path) -> tuple[Element, ...]:
|
|
39
|
+
return tuple(partition_text(str(data)))
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclass(slots=True)
|
|
43
|
+
class ElementTextParser:
|
|
44
|
+
def units(self, data: Element) -> str:
|
|
45
|
+
return data.text
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@dataclass(slots=True)
|
|
49
|
+
class ElementPageIndexParser:
|
|
50
|
+
def units(self, data: Element) -> int:
|
|
51
|
+
page_index = data.metadata.page_number
|
|
52
|
+
return page_index if page_index is not None else -1
|
|
53
|
+
|
|
54
|
+
@dataclass
|
|
55
|
+
class ChunkParser:
|
|
56
|
+
model_name: str
|
|
57
|
+
chunk_size: int
|
|
58
|
+
overlap: int
|
|
59
|
+
|
|
60
|
+
@cached_property
|
|
61
|
+
def model(self) -> TextSplitter:
|
|
62
|
+
return cast(
|
|
63
|
+
"TextSplitter",
|
|
64
|
+
get_chunker(
|
|
65
|
+
self.model_name,
|
|
66
|
+
chunking_type="text",
|
|
67
|
+
tree_sitter_language=None,
|
|
68
|
+
max_tokens=self.chunk_size,
|
|
69
|
+
overlap=self.overlap,
|
|
70
|
+
trim=True,
|
|
71
|
+
),
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
def units(self, data: str) -> tuple[str, ...]:
|
|
75
|
+
return tuple(self.model.chunks(data))
|
ragtools/py.typed
ADDED
|
File without changes
|
ragtools/scorers.py
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from functools import cached_property
|
|
3
|
+
from typing import Protocol
|
|
4
|
+
|
|
5
|
+
from sentence_transformers import CrossEncoder
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Scorer(Protocol):
|
|
9
|
+
def score(self, query: str, chunk: str) -> float: ...
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class CrossEncoderScorer:
|
|
14
|
+
model_name: str
|
|
15
|
+
|
|
16
|
+
@cached_property
|
|
17
|
+
def model(self) -> CrossEncoder:
|
|
18
|
+
return CrossEncoder(self.model_name)
|
|
19
|
+
|
|
20
|
+
def score(self, query: str, chunk: str) -> float:
|
|
21
|
+
return float(self.model.predict((query, chunk)).tolist())
|
ragtools/stores.py
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import builtins
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Protocol
|
|
5
|
+
|
|
6
|
+
import blake3
|
|
7
|
+
import dill
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Store[K, V](Protocol):
|
|
11
|
+
def set(self, key: K, value: V) -> None: ...
|
|
12
|
+
def get(self, key: K) -> V: ...
|
|
13
|
+
def delete(self, key: K) -> None: ...
|
|
14
|
+
def contains(self, key: K) -> bool: ...
|
|
15
|
+
def keys(self) -> builtins.set[K]: ...
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass(slots=True)
|
|
19
|
+
class MemoryStore[K, V]:
|
|
20
|
+
items: dict[K, V]
|
|
21
|
+
|
|
22
|
+
def __init__(self) -> None:
|
|
23
|
+
self.items = {}
|
|
24
|
+
|
|
25
|
+
def set(self, key: K, value: V) -> None:
|
|
26
|
+
self.items[key] = value
|
|
27
|
+
|
|
28
|
+
def get(self, key: K) -> V:
|
|
29
|
+
return self.items[key]
|
|
30
|
+
|
|
31
|
+
def delete(self, key: K) -> None:
|
|
32
|
+
del self.items[key]
|
|
33
|
+
|
|
34
|
+
def contains(self, key: K) -> bool:
|
|
35
|
+
return key in self.items
|
|
36
|
+
|
|
37
|
+
def keys(self) -> builtins.set[K]:
|
|
38
|
+
return set(self.items.keys())
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass(slots=True)
|
|
42
|
+
class FileStore[K, V]:
|
|
43
|
+
file: Path
|
|
44
|
+
|
|
45
|
+
def _load_items(self) -> dict[K, V]:
|
|
46
|
+
if not self.file.is_file():
|
|
47
|
+
return {}
|
|
48
|
+
with self.file.open("rb") as f:
|
|
49
|
+
return dill.load(f)
|
|
50
|
+
|
|
51
|
+
def _save_items(self, items: dict[K, V]) -> None:
|
|
52
|
+
self.file.parent.mkdir(parents=True, exist_ok=True)
|
|
53
|
+
with self.file.open("wb") as f:
|
|
54
|
+
dill.dump(items, f)
|
|
55
|
+
|
|
56
|
+
def set(self, key: K, value: V) -> None:
|
|
57
|
+
items = self._load_items()
|
|
58
|
+
items[key] = value
|
|
59
|
+
self._save_items(items)
|
|
60
|
+
|
|
61
|
+
def get(self, key: K) -> V:
|
|
62
|
+
items = self._load_items()
|
|
63
|
+
return items[key]
|
|
64
|
+
|
|
65
|
+
def delete(self, key: K) -> None:
|
|
66
|
+
items = self._load_items()
|
|
67
|
+
del items[key]
|
|
68
|
+
self._save_items(items)
|
|
69
|
+
|
|
70
|
+
def contains(self, key: K) -> bool:
|
|
71
|
+
items = self._load_items()
|
|
72
|
+
return key in items
|
|
73
|
+
|
|
74
|
+
def keys(self) -> builtins.set[K]:
|
|
75
|
+
items = self._load_items()
|
|
76
|
+
return set(items.keys())
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@dataclass(slots=True)
|
|
80
|
+
class DirectoryStore[K, V]:
|
|
81
|
+
directory: Path
|
|
82
|
+
|
|
83
|
+
def _file_path(self, key: K) -> Path:
|
|
84
|
+
return self.directory / blake3.blake3(dill.dumps(key)).hexdigest()
|
|
85
|
+
|
|
86
|
+
def set(self, key: K, value: V) -> None:
|
|
87
|
+
path = self._file_path(key)
|
|
88
|
+
self.directory.mkdir(parents=True, exist_ok=True)
|
|
89
|
+
with path.open("wb") as f:
|
|
90
|
+
dill.dump((key, value), f)
|
|
91
|
+
|
|
92
|
+
def get(self, key: K) -> V:
|
|
93
|
+
path = self._file_path(key)
|
|
94
|
+
if not path.is_file():
|
|
95
|
+
raise KeyError(key)
|
|
96
|
+
with path.open("rb") as f:
|
|
97
|
+
_, value = dill.load(f)
|
|
98
|
+
return value
|
|
99
|
+
|
|
100
|
+
def delete(self, key: K) -> None:
|
|
101
|
+
path = self._file_path(key)
|
|
102
|
+
if not path.is_file():
|
|
103
|
+
raise KeyError(key)
|
|
104
|
+
path.unlink()
|
|
105
|
+
|
|
106
|
+
def contains(self, key: K) -> bool:
|
|
107
|
+
return self._file_path(key).is_file()
|
|
108
|
+
|
|
109
|
+
def keys(self) -> builtins.set[K]:
|
|
110
|
+
if not self.directory.is_dir():
|
|
111
|
+
return set()
|
|
112
|
+
keys_set: set[K] = set()
|
|
113
|
+
for file in self.directory.iterdir():
|
|
114
|
+
if file.is_file():
|
|
115
|
+
with file.open("rb") as f:
|
|
116
|
+
stored_key, _ = dill.load(f)
|
|
117
|
+
keys_set.add(stored_key)
|
|
118
|
+
return keys_set
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: ragtools
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Add your description here
|
|
5
|
+
Author: Wannes Vantorre
|
|
6
|
+
Author-email: Wannes Vantorre <vantorrewannes@gmail.com>
|
|
7
|
+
Requires-Dist: blake3>=1.0.9
|
|
8
|
+
Requires-Dist: dill>=0.4.1
|
|
9
|
+
Requires-Dist: faiss-cpu>=1.15.1
|
|
10
|
+
Requires-Dist: scipy>=1.18.1
|
|
11
|
+
Requires-Dist: semantic-chunker>=0.2.0
|
|
12
|
+
Requires-Dist: sentence-transformers>=6.0.1
|
|
13
|
+
Requires-Dist: tokenizers>=0.23.2
|
|
14
|
+
Requires-Dist: transformers>=5.17.0
|
|
15
|
+
Requires-Dist: unstructured-inference>=1.6.13
|
|
16
|
+
Requires-Dist: unstructured[csv,md,pdf]>=0.27.5
|
|
17
|
+
Requires-Dist: torch>=2.13.0 ; extra == 'cpu'
|
|
18
|
+
Requires-Dist: torchvision>=0.28.0 ; extra == 'cpu'
|
|
19
|
+
Requires-Python: >=3.14
|
|
20
|
+
Project-URL: Homepage, https://github.com/VantorreWannes/ragtools
|
|
21
|
+
Project-URL: Repository, https://github.com/VantorreWannes/ragtools
|
|
22
|
+
Project-URL: Issues, https://github.com/VantorreWannes/ragtools/issues
|
|
23
|
+
Project-URL: Changelog, https://github.com/VantorreWannes/ragtools/releases
|
|
24
|
+
Provides-Extra: cpu
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
|
|
27
|
+
# ragtools
|
|
28
|
+
|
|
29
|
+
Simple cache based RAG primitives
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
ragtools/__init__.py,sha256=O0wmKZc-m8YTNlmZ9nZF3kwTM0dYcRvmYjFUmK2E3Lg,1265
|
|
2
|
+
ragtools/embedders.py,sha256=PqXcJtAbivONA-xfzgA4wQsocCbjdzeGVq7ziFxgrq8,938
|
|
3
|
+
ragtools/fusers.py,sha256=aOU5T0-NDmcuG3JB9HmSTeARc9b_7AvMgbW_7y4Y2y4,1104
|
|
4
|
+
ragtools/generators.py,sha256=wgEuufhMSxR6Y5BqaniIzsxTUuo3FxO0AnMFURdGzoo,767
|
|
5
|
+
ragtools/indexes.py,sha256=ElZE4NN7X_aesm2Vh8YNbyT2EgoGBkNUkJa7Pxa7LkE,5442
|
|
6
|
+
ragtools/parsers.py,sha256=3iZ5pu6ECkZKW8MbjNd8YKUJGt0s1pmC4fJylk8_K2s,2109
|
|
7
|
+
ragtools/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
+
ragtools/scorers.py,sha256=2Wsl1DT3VJu0vVDqVke8PxwuPl2Pa9HukqxA7IJBmlA,537
|
|
9
|
+
ragtools/stores.py,sha256=bg-i-f2LVCI1qfTxUYZ-N5QPC77gRF3nLP6AQh5daSw,3236
|
|
10
|
+
ragtools-0.1.0.dist-info/WHEEL,sha256=-i9oRNYVXXZJUIYl5zclLIg6onEb0NLibTX34uln84w,81
|
|
11
|
+
ragtools-0.1.0.dist-info/METADATA,sha256=nV9BAZ-zRnKW6nGnbRUto4Fmo_h0bv8DTTDi8z2QXrg,1046
|
|
12
|
+
ragtools-0.1.0.dist-info/RECORD,,
|