right-rag 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.
right_rag/__init__.py ADDED
@@ -0,0 +1,2 @@
1
+ """right-rag package."""
2
+
right_rag/__main__.py ADDED
@@ -0,0 +1,6 @@
1
+ from .cli import main
2
+
3
+
4
+ if __name__ == "__main__":
5
+ main()
6
+
right_rag/cli.py ADDED
@@ -0,0 +1,130 @@
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+ import sys
5
+ from pathlib import Path
6
+
7
+ from .pipeline import cli as pipeline_cli
8
+
9
+
10
+ SPEC_PACKAGES = [
11
+ "document_loaders",
12
+ "unit_parsers",
13
+ "chunk_parsers",
14
+ "unit_savers",
15
+ "chunk_savers",
16
+ "retrievers",
17
+ "answer_generators",
18
+ "evaluators",
19
+ "reporters",
20
+ ]
21
+
22
+
23
+ def main(argv: list[str] | None = None) -> None:
24
+ sys.path.insert(0, str(Path.cwd()))
25
+ args = list(sys.argv[1:] if argv is None else argv)
26
+
27
+ if not args or args[0] in {"-h", "--help"}:
28
+ print_help()
29
+ return
30
+
31
+ if args[0] == "init":
32
+ init_cli(args[1:])
33
+ return
34
+
35
+ pipeline_cli(args)
36
+
37
+
38
+ def print_help() -> None:
39
+ print(
40
+ "usage: right-rag {init,index,query,evaluate} ...\n\n"
41
+ "commands:\n"
42
+ " init [path] create a right-rag project\n"
43
+ " index build unit and chunk artifacts\n"
44
+ " query TEXT query saved artifacts\n"
45
+ " evaluate [cases] evaluate RAG quality\n"
46
+ )
47
+
48
+
49
+ def init_cli(argv: list[str]) -> None:
50
+ parser = argparse.ArgumentParser(prog="right-rag init")
51
+ parser.add_argument("path", nargs="?", default=".")
52
+ parser.add_argument("--force", action="store_true")
53
+ args = parser.parse_args(argv)
54
+
55
+ init_project(Path(args.path), force=args.force)
56
+ print(f"Created right-rag project at {Path(args.path).resolve()}")
57
+
58
+
59
+ def init_project(root: Path, force: bool = False) -> None:
60
+ root = root.resolve()
61
+ if root.exists() and any(root.iterdir()) and not force:
62
+ raise SystemExit(f"{root} is not empty. Use --force to write into it.")
63
+
64
+ for path in [
65
+ root / "data" / "documents",
66
+ root / "output",
67
+ root / "eval",
68
+ root / "spec",
69
+ ]:
70
+ path.mkdir(parents=True, exist_ok=True)
71
+
72
+ write_once(root / "right_rag.toml", RIGHT_RAG_TOML, force)
73
+ write_once(root / ".gitignore", GITIGNORE, force)
74
+ write_once(root / "data" / "documents" / ".gitkeep", "", force)
75
+ write_once(root / "eval" / "cases.example.json", EVAL_CASES, force)
76
+ write_once(root / "spec" / "__init__.py", "", force)
77
+
78
+ for package in SPEC_PACKAGES:
79
+ directory = root / "spec" / package
80
+ directory.mkdir(parents=True, exist_ok=True)
81
+ write_once(directory / "__init__.py", "", force)
82
+ write_once(directory / "default.py", default_reexport(package), force)
83
+
84
+
85
+ def write_once(path: Path, text: str, force: bool) -> None:
86
+ if path.exists() and not force:
87
+ return
88
+
89
+ path.write_text(text, encoding="utf-8")
90
+
91
+
92
+ def default_reexport(package: str) -> str:
93
+ return f"from right_rag.spec.{package}.default import * # noqa: F401,F403\n"
94
+
95
+
96
+ RIGHT_RAG_TOML = """\
97
+ [right-rag]
98
+ spec_root_package = "spec"
99
+ document_root_folder = "data/documents"
100
+ state_path = ".right-rag-state.json"
101
+ """
102
+
103
+
104
+ GITIGNORE = """\
105
+ __pycache__/
106
+ *.py[oc]
107
+ .right-rag-state.json
108
+ output/
109
+ data/documents/*
110
+ !data/documents/.gitkeep
111
+ """
112
+
113
+
114
+ EVAL_CASES = """\
115
+ {
116
+ "cases": [
117
+ {
118
+ "id": "edit-me",
119
+ "query": "replace this with a real domain question",
120
+ "expected": {
121
+ "answer_contains": [],
122
+ "hit_contains": [],
123
+ "hit_ids": [],
124
+ "must_not_contain": []
125
+ },
126
+ "tags": ["smoke"]
127
+ }
128
+ ]
129
+ }
130
+ """
@@ -0,0 +1,155 @@
1
+ from __future__ import annotations
2
+
3
+ from abc import ABC, abstractmethod
4
+ from collections.abc import Iterable, Iterator, Mapping
5
+ from pathlib import Path
6
+
7
+ from .model import Answer, Chunk, Document, EvalCase, EvalResult, Hit, ParsedUnit, Query, Unit
8
+
9
+
10
+ class DocumentLoader(ABC):
11
+ @property
12
+ @abstractmethod
13
+ def loader_name(self) -> str:
14
+ pass
15
+
16
+ @abstractmethod
17
+ def __iter__(self) -> Iterator[Document]:
18
+ pass
19
+
20
+ def load_documents(self, root_folder: str) -> Iterator[Document]:
21
+ return iter(self)
22
+
23
+
24
+ class UnitParser(ABC):
25
+ @property
26
+ @abstractmethod
27
+ def parser_name(self) -> str:
28
+ pass
29
+
30
+ @abstractmethod
31
+ def parser_can_handle_document(self, document: Document) -> bool:
32
+ pass
33
+
34
+ @abstractmethod
35
+ def parse_document(self, document: Document) -> Iterable[ParsedUnit]:
36
+ pass
37
+
38
+
39
+ class ChunkParser(ABC):
40
+ @property
41
+ @abstractmethod
42
+ def parser_name(self) -> str:
43
+ pass
44
+
45
+ @abstractmethod
46
+ def parse_unit(self, unit: Unit) -> Iterable[Chunk]:
47
+ pass
48
+
49
+
50
+ class UnitArtifact(ABC):
51
+ @property
52
+ @abstractmethod
53
+ def name(self) -> str:
54
+ pass
55
+
56
+ @abstractmethod
57
+ def retrieve(self, query: Query) -> Iterable[Hit]:
58
+ pass
59
+
60
+
61
+ class ChunkArtifact(ABC):
62
+ @property
63
+ @abstractmethod
64
+ def name(self) -> str:
65
+ pass
66
+
67
+ @abstractmethod
68
+ def retrieve(self, query: Query) -> Iterable[Hit]:
69
+ pass
70
+
71
+
72
+ class ChunkSaver(ABC):
73
+ @property
74
+ @abstractmethod
75
+ def saver_name(self) -> str:
76
+ pass
77
+
78
+ @abstractmethod
79
+ def save_chunks(self, chunks: Iterable[Chunk]) -> ChunkArtifact:
80
+ pass
81
+
82
+ @abstractmethod
83
+ def open_chunk_artifact(self) -> ChunkArtifact:
84
+ pass
85
+
86
+
87
+ class UnitSaver(ABC):
88
+ @property
89
+ @abstractmethod
90
+ def saver_name(self) -> str:
91
+ pass
92
+
93
+ @abstractmethod
94
+ def save_units(self, units: Iterable[Unit]) -> UnitArtifact:
95
+ pass
96
+
97
+ @abstractmethod
98
+ def open_unit_artifact(self) -> UnitArtifact:
99
+ pass
100
+
101
+
102
+ class Retriever(ABC):
103
+ @property
104
+ @abstractmethod
105
+ def retriever_name(self) -> str:
106
+ pass
107
+
108
+ def query_options(self) -> Mapping[str, str]:
109
+ return {}
110
+
111
+ @abstractmethod
112
+ def retrieve(
113
+ self,
114
+ query: Query,
115
+ unit_artifacts: Mapping[str, UnitArtifact],
116
+ chunk_artifacts: Mapping[str, ChunkArtifact],
117
+ ) -> list[Hit]:
118
+ pass
119
+
120
+
121
+ class AnswerGenerator(ABC):
122
+ @property
123
+ @abstractmethod
124
+ def generator_name(self) -> str:
125
+ pass
126
+
127
+ @abstractmethod
128
+ def generate(self, query: Query, hits: Iterable[Hit]) -> Answer:
129
+ pass
130
+
131
+
132
+ class Evaluator(ABC):
133
+ @property
134
+ @abstractmethod
135
+ def evaluator_name(self) -> str:
136
+ pass
137
+
138
+ @abstractmethod
139
+ def evaluate(self, case: EvalCase, answer: Answer) -> EvalResult:
140
+ pass
141
+
142
+
143
+ class Reporter(ABC):
144
+ @property
145
+ @abstractmethod
146
+ def reporter_name(self) -> str:
147
+ pass
148
+
149
+ @abstractmethod
150
+ def write_report(
151
+ self,
152
+ results: Iterable[EvalResult],
153
+ output_path: str | Path,
154
+ ) -> None:
155
+ pass