raglab 0.2.4__tar.gz → 0.2.6__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.
- raglab-0.2.6/.editorconfig +17 -0
- raglab-0.2.6/CHANGELOG.md +32 -0
- raglab-0.2.6/PKG-INFO +322 -0
- raglab-0.2.6/README.md +288 -0
- {raglab-0.2.4 → raglab-0.2.6}/pyproject.toml +23 -6
- raglab-0.2.4/PKG-INFO +0 -34
- raglab-0.2.4/README.md +0 -11
- {raglab-0.2.4 → raglab-0.2.6}/.claude/CLAUDE.md +0 -0
- {raglab-0.2.4 → raglab-0.2.6}/.gitattributes +0 -0
- {raglab-0.2.4 → raglab-0.2.6}/.github/workflows/ci.yml +0 -0
- {raglab-0.2.4 → raglab-0.2.6}/.gitignore +0 -0
- {raglab-0.2.4 → raglab-0.2.6}/LICENSE +0 -0
- {raglab-0.2.4 → raglab-0.2.6}/raglab/__init__.py +0 -0
- {raglab-0.2.4 → raglab-0.2.6}/raglab/agent.py +0 -0
- {raglab-0.2.4 → raglab-0.2.6}/raglab/llm.py +0 -0
- {raglab-0.2.4 → raglab-0.2.6}/tests/test_agent.py +0 -0
- {raglab-0.2.4 → raglab-0.2.6}/tests/test_fanin.py +0 -0
- {raglab-0.2.4 → raglab-0.2.6}/tests/test_llm_roles.py +0 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
root = true
|
|
2
|
+
|
|
3
|
+
[*]
|
|
4
|
+
charset = utf-8
|
|
5
|
+
end_of_line = lf
|
|
6
|
+
insert_final_newline = true
|
|
7
|
+
trim_trailing_whitespace = true
|
|
8
|
+
|
|
9
|
+
[*.{py,toml,yml,yaml}]
|
|
10
|
+
indent_style = space
|
|
11
|
+
indent_size = 4
|
|
12
|
+
|
|
13
|
+
[*.md]
|
|
14
|
+
trim_trailing_whitespace = false
|
|
15
|
+
|
|
16
|
+
[Makefile]
|
|
17
|
+
indent_style = tab
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is inspired by [Keep a Changelog](https://keepachangelog.com/);
|
|
6
|
+
each section corresponds to a git version tag (which is also the release
|
|
7
|
+
published to PyPI). Entries are commit subjects and PR titles, verbatim.
|
|
8
|
+
|
|
9
|
+
## [0.2.5] - 2026-06-16
|
|
10
|
+
|
|
11
|
+
### Docs
|
|
12
|
+
|
|
13
|
+
- docs: rewrite README as an agent-serving guide to the Composable Search Agent ([#7](https://github.com/thorwhalen/raglab/pull/7))
|
|
14
|
+
|
|
15
|
+
## [0.2.4] - 2026-06-12
|
|
16
|
+
|
|
17
|
+
- Reranker at fan-in — rank-based cross-source merge (RRF) ([#4](https://github.com/thorwhalen/raglab/pull/4))
|
|
18
|
+
|
|
19
|
+
## [0.2.3] - 2026-06-12
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
|
|
23
|
+
- feat: LLM Formulator + Evaluator roles — turn on the back-edge ([#2](https://github.com/thorwhalen/raglab/pull/2)) ([#3](https://github.com/thorwhalen/raglab/pull/3))
|
|
24
|
+
|
|
25
|
+
## [0.2.2] - 2026-06-11
|
|
26
|
+
|
|
27
|
+
- 0.2.1:
|
|
28
|
+
- Initial wads scaffold for fresh raglab (v0.2.0)
|
|
29
|
+
|
|
30
|
+
### Added
|
|
31
|
+
|
|
32
|
+
- feat: Composable Search Agent foundation (ir_09) — roles, value types, SingleContextAgent ([#1](https://github.com/thorwhalen/raglab/pull/1))
|
raglab-0.2.6/PKG-INFO
ADDED
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: raglab
|
|
3
|
+
Version: 0.2.6
|
|
4
|
+
Summary: A medley of tools to make RAG-based applications.
|
|
5
|
+
Project-URL: Homepage, https://thorwhalen.github.io/raglab/
|
|
6
|
+
Project-URL: Repository, https://github.com/thorwhalen/raglab
|
|
7
|
+
Project-URL: Documentation, https://thorwhalen.github.io/raglab
|
|
8
|
+
Project-URL: Issues, https://github.com/thorwhalen/raglab/issues
|
|
9
|
+
Project-URL: Changelog, https://github.com/thorwhalen/raglab/blob/main/CHANGELOG.md
|
|
10
|
+
Author: thorwhalen
|
|
11
|
+
License-Expression: MIT
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Keywords: agents,information-retrieval,llm,rag,retrieval,search-agent
|
|
14
|
+
Classifier: Development Status :: 4 - Beta
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
21
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
22
|
+
Requires-Python: >=3.10
|
|
23
|
+
Requires-Dist: ir>=0.1.16
|
|
24
|
+
Provides-Extra: dev
|
|
25
|
+
Requires-Dist: pytest-cov>=4.0; extra == 'dev'
|
|
26
|
+
Requires-Dist: pytest>=7.0; extra == 'dev'
|
|
27
|
+
Requires-Dist: ruff>=0.1.0; extra == 'dev'
|
|
28
|
+
Provides-Extra: docs
|
|
29
|
+
Requires-Dist: sphinx-rtd-theme>=1.0; extra == 'docs'
|
|
30
|
+
Requires-Dist: sphinx>=6.0; extra == 'docs'
|
|
31
|
+
Provides-Extra: llm
|
|
32
|
+
Requires-Dist: oa; extra == 'llm'
|
|
33
|
+
Description-Content-Type: text/markdown
|
|
34
|
+
|
|
35
|
+
# raglab
|
|
36
|
+
|
|
37
|
+
The **agentic-search / RAG orchestration layer on top of [`ir`](https://github.com/i2mint/ir)** (the retrieval substrate).
|
|
38
|
+
|
|
39
|
+
`raglab` turns a retrieval substrate into a **Composable Search Agent**: a small
|
|
40
|
+
set of injected **roles** — Planner, Formulator, Retriever, Evaluator, Reranker,
|
|
41
|
+
Citer — wired by a control loop. The loop's defining feature is the **back-edge**
|
|
42
|
+
(*evaluator → reformulate*): when results aren't good enough, the agent rewrites
|
|
43
|
+
the query and searches again. That back-edge is what makes it an *agent* rather
|
|
44
|
+
than a fixed pipeline (DAG).
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
pip install raglab
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
> **Fresh start (v0.2.0+).** This repo took over the `raglab` PyPI name. The
|
|
51
|
+
> earlier backend (PyPI 0.0.x–0.1.x) was renamed and now lives at
|
|
52
|
+
> [addaix/raglab_bak](https://github.com/addaix/raglab_bak) (PyPI:
|
|
53
|
+
> [`raglab_bak`](https://pypi.org/project/raglab_bak/)). The API below is the new
|
|
54
|
+
> agent layer.
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## Quick start
|
|
59
|
+
|
|
60
|
+
The default agent runs **fully offline** — no LLM, no network. Point it at one or
|
|
61
|
+
more `ir` corpora and call it like a function; you get back ranked
|
|
62
|
+
[`ir.SearchHit`](https://github.com/i2mint/ir)s.
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
import ir
|
|
66
|
+
import raglab
|
|
67
|
+
|
|
68
|
+
# Register named ir corpora as the agent's sources, then search across them.
|
|
69
|
+
sources = raglab.ir_sources("skills", "reports", mode="hybrid")
|
|
70
|
+
agent = raglab.make_search_agent(sources)
|
|
71
|
+
results = agent("how do I deploy the app") # -> list[ir.SearchHit], ranked
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
A self-contained, runnable example (build a tiny corpus, search it):
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
import ir
|
|
78
|
+
from ir.store import CorpusStore
|
|
79
|
+
import raglab
|
|
80
|
+
|
|
81
|
+
docs = {
|
|
82
|
+
"deploy": "deploy the app to the server with systemd units",
|
|
83
|
+
"embed": "embed text with a model and cache the vectors",
|
|
84
|
+
"search": "vector similarity search with metadata filters",
|
|
85
|
+
}
|
|
86
|
+
corpus = ir.build(
|
|
87
|
+
ir.CorpusSource.from_mapping(docs, name="t", strategy=ir.WholeText()),
|
|
88
|
+
store=CorpusStore.memory(),
|
|
89
|
+
embedder="light", # numpy-only, no model download
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
agent = raglab.make_search_agent({"t": ir.as_retriever(corpus, k=3)})
|
|
93
|
+
results = agent("deploy the app to the server")
|
|
94
|
+
results[0].artifact_id # -> "deploy"
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
`sources` is any `Mapping[name, Retriever]`. Build it with
|
|
98
|
+
[`raglab.ir_sources(*names, **defaults)`](#sources), with `ir.retrievers()` (a
|
|
99
|
+
lazy live view over everything ir has registered), or by hand:
|
|
100
|
+
`{"skills": ir.as_retriever("skills")}`.
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## The mental model
|
|
105
|
+
|
|
106
|
+
```
|
|
107
|
+
┌─────────── back-edge (re-query when insufficient) ──────────┐
|
|
108
|
+
▼ │
|
|
109
|
+
Query → Planner → [SubTask] → Formulator → [LowLevelQuery] → Retriever(s)
|
|
110
|
+
│
|
|
111
|
+
accumulated hits ◄───────┘
|
|
112
|
+
│
|
|
113
|
+
Evaluator ──► Judgement(relevant, sufficient, refinement)
|
|
114
|
+
│ └─ refinement ⟲ back-edge
|
|
115
|
+
(sufficient / budget hit)
|
|
116
|
+
▼
|
|
117
|
+
Reranker (fan-in) → Citer → ranked results
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
The **loop is fixed**; every *decision* in it is an injected **role** (a plain
|
|
121
|
+
callable satisfying a `Protocol`). v1 ships every role with a sensible default,
|
|
122
|
+
so the simple path — `make_search_agent(sources)("query")` — just works. You turn
|
|
123
|
+
capabilities on by swapping in richer roles.
|
|
124
|
+
|
|
125
|
+
| Role | Decides | Default (no-LLM) | Inject an LLM version to… |
|
|
126
|
+
|------|---------|------------------|---------------------------|
|
|
127
|
+
| **Planner** | decompose query → sub-tasks, pick sources | `single_subtask_planner` (one task, all sources) | split a query into parallel sub-goals |
|
|
128
|
+
| **Formulator** | sub-task → concrete search queries | `identity_formulator` (verbatim) | rewrite / expand / HyDE (`make_llm_formulator`) |
|
|
129
|
+
| **Retriever** | run one query against one source | *injected at the leaf* (`ir.as_retriever`) | add web / SQL / graph backends |
|
|
130
|
+
| **Evaluator** | relevance + sufficiency + **back-edge** | `passthrough_evaluator` (keep all, stop) | judge sufficiency & re-query (`make_llm_evaluator`) |
|
|
131
|
+
| **Reranker** | final cross-source ordering (fan-in) | `rrf_reranker` (rank fusion) | weight sources, change fusion |
|
|
132
|
+
| **Citer** | confirm each result supports its use | `identity_citer` (no-op) | *(generation/verification lives in `srag`)* |
|
|
133
|
+
|
|
134
|
+
**`Budget`** bounds the loop (`max_rounds`, `max_sources_per_task`,
|
|
135
|
+
`max_results_per_task`) — the safety net under the (harder) sufficiency decision.
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## Turning on query understanding and the back-edge
|
|
140
|
+
|
|
141
|
+
Inject the two LLM roles to upgrade the offline slice into a real agent. Both
|
|
142
|
+
build their model lazily on [`oa`](https://github.com/thorwhalen/oa) **only when
|
|
143
|
+
invoked**, so `import raglab` stays offline.
|
|
144
|
+
|
|
145
|
+
```python
|
|
146
|
+
import raglab
|
|
147
|
+
|
|
148
|
+
agent = raglab.make_search_agent(
|
|
149
|
+
sources,
|
|
150
|
+
formulator=raglab.make_llm_formulator(), # rewrite/expand the query (HyDE)
|
|
151
|
+
evaluator=raglab.make_llm_evaluator(), # sufficiency + the back-edge
|
|
152
|
+
)
|
|
153
|
+
results = agent("how do I deploy the app")
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
How the Evaluator splits the decision (a load-bearing boundary):
|
|
157
|
+
|
|
158
|
+
- **Relevance is `ir`'s.** Each round the accumulated hits pass through
|
|
159
|
+
`ir.select` (default strategy `"conservative"` — distractor-robust); the
|
|
160
|
+
committed subset becomes `Judgement.relevant`. LLM relevance scoring is
|
|
161
|
+
known-fragile, so the LLM stays out of it.
|
|
162
|
+
- **Sufficiency is the LLM's.** Informed by ir's model-free
|
|
163
|
+
`Selection.sufficient` hint, the judge decides whether the committed subset
|
|
164
|
+
actually satisfies the goal. If not, it emits an improved query — a
|
|
165
|
+
`refinement` `SubTask` over the same sources. **That refinement is the
|
|
166
|
+
back-edge.**
|
|
167
|
+
|
|
168
|
+
Both builders accept an injectable double (`formulate=` / `judge=`) for
|
|
169
|
+
deterministic tests, and both **fail safe**: a formulator error falls back to the
|
|
170
|
+
raw query (never makes retrieval worse); a judge error returns no refinement
|
|
171
|
+
(the loop's break condition — a failing judge can never spin forever).
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## Multi-source search and the fan-in reranker
|
|
176
|
+
|
|
177
|
+
The agent is **multi-source by default**. The loop stamps each hit's provenance
|
|
178
|
+
(`hit.source`), and the default fan-in reranker, `rrf_reranker`, merges
|
|
179
|
+
heterogeneous sources **by rank, never by raw score**.
|
|
180
|
+
|
|
181
|
+
This matters because scores from different corpora / embedders / retrieval modes
|
|
182
|
+
are *incommensurable* — a cosine score (~0–1) and a BM25 score (~0–50) cannot be
|
|
183
|
+
compared directly. Reciprocal Rank Fusion (RRF) sidesteps this: within one source
|
|
184
|
+
raw scores order and dedup that source's hits (one scale, sound); across sources
|
|
185
|
+
only **ranks** interact.
|
|
186
|
+
|
|
187
|
+
```python
|
|
188
|
+
# Two sources on different score scales:
|
|
189
|
+
# rrf_reranker (default): interleaves both sources' rank-1 hits first.
|
|
190
|
+
# score_reranker (opt-in): a raw-score sort — only sound when every source
|
|
191
|
+
# shares one scale (same embedder + mode).
|
|
192
|
+
agent = raglab.make_search_agent(sources, reranker=raglab.score_reranker)
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
Colliding `artifact_id`s from different sources stay distinct (identity is
|
|
196
|
+
`(source, artifact_id)`). Each fused hit keeps its pre-fusion magnitude in
|
|
197
|
+
`metadata["source_score"]`. For per-source trust weights or a custom `rrf_k`:
|
|
198
|
+
|
|
199
|
+
```python
|
|
200
|
+
reranker = raglab.make_rrf_reranker(weights={"skills": 2.0, "web": 0.5}, rrf_k=60)
|
|
201
|
+
agent = raglab.make_search_agent(sources, reranker=reranker)
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
---
|
|
205
|
+
|
|
206
|
+
## Customizing roles
|
|
207
|
+
|
|
208
|
+
Every role is just a callable matching a `Protocol`, so you can inject your own:
|
|
209
|
+
|
|
210
|
+
```python
|
|
211
|
+
from raglab import SubTask, LowLevelQuery, Judgement, make_search_agent
|
|
212
|
+
|
|
213
|
+
def my_planner(query, sources):
|
|
214
|
+
# split into two sub-goals over all sources
|
|
215
|
+
return [SubTask(goal=g, sources=tuple(sources))
|
|
216
|
+
for g in (query.text, query.text + " examples")]
|
|
217
|
+
|
|
218
|
+
def my_formulator(task, source):
|
|
219
|
+
return [LowLevelQuery(source=source, query=task.goal, params={"k": 10})]
|
|
220
|
+
|
|
221
|
+
agent = make_search_agent(sources, planner=my_planner, formulator=my_formulator)
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
A **custom Retriever** is any `callable(query, **params) -> Sequence[ir.SearchHit]`.
|
|
225
|
+
It must return `ir.SearchHit` instances (the `Result` alias) — the loop stamps
|
|
226
|
+
provenance on its output, so duck-typed hit objects raise at the tagging step.
|
|
227
|
+
ir-backed retrievers self-attribute their corpus name.
|
|
228
|
+
|
|
229
|
+
---
|
|
230
|
+
|
|
231
|
+
## API at a glance
|
|
232
|
+
|
|
233
|
+
Everything is exported from the top-level `raglab` namespace.
|
|
234
|
+
|
|
235
|
+
**Value types** (frozen dataclasses, plain data):
|
|
236
|
+
|
|
237
|
+
| Type | Fields |
|
|
238
|
+
|------|--------|
|
|
239
|
+
| `Query` | `text`, `constraints` |
|
|
240
|
+
| `SubTask` | `goal`, `sources: tuple[str, ...]` |
|
|
241
|
+
| `LowLevelQuery` | `source`, `query`, `params` (per-call retriever overrides) |
|
|
242
|
+
| `Judgement` | `relevant`, `sufficient`, `refinement: SubTask \| None` |
|
|
243
|
+
| `Budget` | `max_rounds=3`, `max_sources_per_task=4`, `max_results_per_task=50` |
|
|
244
|
+
| `Result` | alias for `ir.SearchHit` |
|
|
245
|
+
|
|
246
|
+
**Role Protocols** (the open-closed strategy seams): `Planner`, `Formulator`,
|
|
247
|
+
`Retriever` (re-exported from `ir`), `Evaluator`, `Reranker`, `Citer`.
|
|
248
|
+
|
|
249
|
+
**Orchestrator & builders:**
|
|
250
|
+
|
|
251
|
+
| Name | What it does |
|
|
252
|
+
|------|--------------|
|
|
253
|
+
| `make_search_agent(sources, *, planner, formulator, evaluator, reranker, citer, budget)` | Build a `SingleContextAgent` with smart defaults for every role. |
|
|
254
|
+
| `SingleContextAgent` | The orchestrator dataclass: one ReAct-style loop, sequential sub-tasks. |
|
|
255
|
+
| <a name="sources"></a>`ir_sources(*names, **search_defaults)` | A `{name: Retriever}` registry backed by named ir corpora. |
|
|
256
|
+
|
|
257
|
+
**Default roles (the no-LLM thin slice):** `single_subtask_planner`,
|
|
258
|
+
`identity_formulator`, `passthrough_evaluator`, `score_reranker`, `rrf_reranker`,
|
|
259
|
+
`make_rrf_reranker(*, rrf_k, weights, identity)`, `identity_citer`.
|
|
260
|
+
|
|
261
|
+
**LLM roles:** `make_llm_formulator(*, formulate=None, params=None, **make_kwargs)`,
|
|
262
|
+
`make_llm_evaluator(*, judge=None, select_strategy="conservative", select_kwargs=None, prerank=None, prompt=EVALUATION_PROMPT, ...)`,
|
|
263
|
+
and the default `EVALUATION_PROMPT` string.
|
|
264
|
+
|
|
265
|
+
---
|
|
266
|
+
|
|
267
|
+
## Architecture & boundaries
|
|
268
|
+
|
|
269
|
+
`raglab` **owns the agent**: the value types, the role Protocols, the control
|
|
270
|
+
loop with the back-edge, the budget governor, the source registry, and the
|
|
271
|
+
cross-source fan-in.
|
|
272
|
+
|
|
273
|
+
It **consumes from `ir`** (and does not reimplement): `ir.as_retriever` (corpus →
|
|
274
|
+
Retriever), `ir.retrievers()` (the live registry view), `ir.make_llm_formulator`
|
|
275
|
+
(the query rewriter), `ir.select` + its `Selection.sufficient` signal (relevance
|
|
276
|
+
+ the sufficiency hint), and `ir.fuse_hits` (RRF). The `Result` type and the
|
|
277
|
+
`Retriever` contract are **ir's** (single source of truth).
|
|
278
|
+
|
|
279
|
+
**Dependency direction is one-way:** `raglab` imports `ir` (and `oa` for the LLM
|
|
280
|
+
roles, lazily); **`ir` never imports `raglab`.** Keeping `oa` opt-in is what lets
|
|
281
|
+
`import raglab` stay offline.
|
|
282
|
+
|
|
283
|
+
What lives **elsewhere:** answer synthesis / generation and the
|
|
284
|
+
Citer/Verifier (which needs a *generated claim* to verify) belong in the
|
|
285
|
+
RAG/generation layer (`srag`), not the search agent. raglab's deliverable is
|
|
286
|
+
**pointers + extractions**, not an essay.
|
|
287
|
+
|
|
288
|
+
Where it sits in the ecosystem: **`raglab` → `ir` → {`ef`, `vd`}** — `ir` is the
|
|
289
|
+
retrieval substrate, `ef` owns embedders/segmenters, `vd` is the vector-store
|
|
290
|
+
facade.
|
|
291
|
+
|
|
292
|
+
---
|
|
293
|
+
|
|
294
|
+
## Status & roadmap
|
|
295
|
+
|
|
296
|
+
Shipped at **0.2.4**: the `SingleContextAgent` control loop with the back-edge,
|
|
297
|
+
all six role Protocols, the no-LLM defaults, the LLM Formulator + Evaluator, and
|
|
298
|
+
the RRF cross-source fan-in reranker. The roles, value types, and loop are
|
|
299
|
+
stable; richer strategies are added behind the same contracts.
|
|
300
|
+
|
|
301
|
+
The living roadmap is **[issue #2](https://github.com/thorwhalen/raglab/issues/2)**
|
|
302
|
+
(Composable Search Agent on ir, the *ir_09* reference design). Not yet built:
|
|
303
|
+
|
|
304
|
+
- **Budget governor refinements** — the per-task results cap currently truncates
|
|
305
|
+
in arrival order rather than by rank
|
|
306
|
+
([#5](https://github.com/thorwhalen/raglab/issues/5)); termination as a
|
|
307
|
+
separately injected policy is planned.
|
|
308
|
+
- **PurposeStore** — persistent, purpose-indexed memory overlay
|
|
309
|
+
([#6](https://github.com/thorwhalen/raglab/issues/6)). There is no cross-run
|
|
310
|
+
memory today.
|
|
311
|
+
- **`MultiAgentAgent`** — one subagent per sub-task/source (breadth-first, ~15×
|
|
312
|
+
cost); promotion swaps only the orchestrator, keeping role contracts identical.
|
|
313
|
+
|
|
314
|
+
---
|
|
315
|
+
|
|
316
|
+
## House style
|
|
317
|
+
|
|
318
|
+
Functional over OOP; SOLID when OOP; facades, SSOT, dependency injection;
|
|
319
|
+
progressive disclosure; keyword-only args beyond the third positional;
|
|
320
|
+
`collections.abc` + frozen `dataclass`es; `Protocol`s for the role seams. Tests
|
|
321
|
+
are hermetic (fake retrievers, injected LLM doubles) with a few end-to-end checks
|
|
322
|
+
over a real `ir` corpus using the light, numpy-only embedder.
|
raglab-0.2.6/README.md
ADDED
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
# raglab
|
|
2
|
+
|
|
3
|
+
The **agentic-search / RAG orchestration layer on top of [`ir`](https://github.com/i2mint/ir)** (the retrieval substrate).
|
|
4
|
+
|
|
5
|
+
`raglab` turns a retrieval substrate into a **Composable Search Agent**: a small
|
|
6
|
+
set of injected **roles** — Planner, Formulator, Retriever, Evaluator, Reranker,
|
|
7
|
+
Citer — wired by a control loop. The loop's defining feature is the **back-edge**
|
|
8
|
+
(*evaluator → reformulate*): when results aren't good enough, the agent rewrites
|
|
9
|
+
the query and searches again. That back-edge is what makes it an *agent* rather
|
|
10
|
+
than a fixed pipeline (DAG).
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
pip install raglab
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
> **Fresh start (v0.2.0+).** This repo took over the `raglab` PyPI name. The
|
|
17
|
+
> earlier backend (PyPI 0.0.x–0.1.x) was renamed and now lives at
|
|
18
|
+
> [addaix/raglab_bak](https://github.com/addaix/raglab_bak) (PyPI:
|
|
19
|
+
> [`raglab_bak`](https://pypi.org/project/raglab_bak/)). The API below is the new
|
|
20
|
+
> agent layer.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Quick start
|
|
25
|
+
|
|
26
|
+
The default agent runs **fully offline** — no LLM, no network. Point it at one or
|
|
27
|
+
more `ir` corpora and call it like a function; you get back ranked
|
|
28
|
+
[`ir.SearchHit`](https://github.com/i2mint/ir)s.
|
|
29
|
+
|
|
30
|
+
```python
|
|
31
|
+
import ir
|
|
32
|
+
import raglab
|
|
33
|
+
|
|
34
|
+
# Register named ir corpora as the agent's sources, then search across them.
|
|
35
|
+
sources = raglab.ir_sources("skills", "reports", mode="hybrid")
|
|
36
|
+
agent = raglab.make_search_agent(sources)
|
|
37
|
+
results = agent("how do I deploy the app") # -> list[ir.SearchHit], ranked
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
A self-contained, runnable example (build a tiny corpus, search it):
|
|
41
|
+
|
|
42
|
+
```python
|
|
43
|
+
import ir
|
|
44
|
+
from ir.store import CorpusStore
|
|
45
|
+
import raglab
|
|
46
|
+
|
|
47
|
+
docs = {
|
|
48
|
+
"deploy": "deploy the app to the server with systemd units",
|
|
49
|
+
"embed": "embed text with a model and cache the vectors",
|
|
50
|
+
"search": "vector similarity search with metadata filters",
|
|
51
|
+
}
|
|
52
|
+
corpus = ir.build(
|
|
53
|
+
ir.CorpusSource.from_mapping(docs, name="t", strategy=ir.WholeText()),
|
|
54
|
+
store=CorpusStore.memory(),
|
|
55
|
+
embedder="light", # numpy-only, no model download
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
agent = raglab.make_search_agent({"t": ir.as_retriever(corpus, k=3)})
|
|
59
|
+
results = agent("deploy the app to the server")
|
|
60
|
+
results[0].artifact_id # -> "deploy"
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
`sources` is any `Mapping[name, Retriever]`. Build it with
|
|
64
|
+
[`raglab.ir_sources(*names, **defaults)`](#sources), with `ir.retrievers()` (a
|
|
65
|
+
lazy live view over everything ir has registered), or by hand:
|
|
66
|
+
`{"skills": ir.as_retriever("skills")}`.
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## The mental model
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
┌─────────── back-edge (re-query when insufficient) ──────────┐
|
|
74
|
+
▼ │
|
|
75
|
+
Query → Planner → [SubTask] → Formulator → [LowLevelQuery] → Retriever(s)
|
|
76
|
+
│
|
|
77
|
+
accumulated hits ◄───────┘
|
|
78
|
+
│
|
|
79
|
+
Evaluator ──► Judgement(relevant, sufficient, refinement)
|
|
80
|
+
│ └─ refinement ⟲ back-edge
|
|
81
|
+
(sufficient / budget hit)
|
|
82
|
+
▼
|
|
83
|
+
Reranker (fan-in) → Citer → ranked results
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
The **loop is fixed**; every *decision* in it is an injected **role** (a plain
|
|
87
|
+
callable satisfying a `Protocol`). v1 ships every role with a sensible default,
|
|
88
|
+
so the simple path — `make_search_agent(sources)("query")` — just works. You turn
|
|
89
|
+
capabilities on by swapping in richer roles.
|
|
90
|
+
|
|
91
|
+
| Role | Decides | Default (no-LLM) | Inject an LLM version to… |
|
|
92
|
+
|------|---------|------------------|---------------------------|
|
|
93
|
+
| **Planner** | decompose query → sub-tasks, pick sources | `single_subtask_planner` (one task, all sources) | split a query into parallel sub-goals |
|
|
94
|
+
| **Formulator** | sub-task → concrete search queries | `identity_formulator` (verbatim) | rewrite / expand / HyDE (`make_llm_formulator`) |
|
|
95
|
+
| **Retriever** | run one query against one source | *injected at the leaf* (`ir.as_retriever`) | add web / SQL / graph backends |
|
|
96
|
+
| **Evaluator** | relevance + sufficiency + **back-edge** | `passthrough_evaluator` (keep all, stop) | judge sufficiency & re-query (`make_llm_evaluator`) |
|
|
97
|
+
| **Reranker** | final cross-source ordering (fan-in) | `rrf_reranker` (rank fusion) | weight sources, change fusion |
|
|
98
|
+
| **Citer** | confirm each result supports its use | `identity_citer` (no-op) | *(generation/verification lives in `srag`)* |
|
|
99
|
+
|
|
100
|
+
**`Budget`** bounds the loop (`max_rounds`, `max_sources_per_task`,
|
|
101
|
+
`max_results_per_task`) — the safety net under the (harder) sufficiency decision.
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## Turning on query understanding and the back-edge
|
|
106
|
+
|
|
107
|
+
Inject the two LLM roles to upgrade the offline slice into a real agent. Both
|
|
108
|
+
build their model lazily on [`oa`](https://github.com/thorwhalen/oa) **only when
|
|
109
|
+
invoked**, so `import raglab` stays offline.
|
|
110
|
+
|
|
111
|
+
```python
|
|
112
|
+
import raglab
|
|
113
|
+
|
|
114
|
+
agent = raglab.make_search_agent(
|
|
115
|
+
sources,
|
|
116
|
+
formulator=raglab.make_llm_formulator(), # rewrite/expand the query (HyDE)
|
|
117
|
+
evaluator=raglab.make_llm_evaluator(), # sufficiency + the back-edge
|
|
118
|
+
)
|
|
119
|
+
results = agent("how do I deploy the app")
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
How the Evaluator splits the decision (a load-bearing boundary):
|
|
123
|
+
|
|
124
|
+
- **Relevance is `ir`'s.** Each round the accumulated hits pass through
|
|
125
|
+
`ir.select` (default strategy `"conservative"` — distractor-robust); the
|
|
126
|
+
committed subset becomes `Judgement.relevant`. LLM relevance scoring is
|
|
127
|
+
known-fragile, so the LLM stays out of it.
|
|
128
|
+
- **Sufficiency is the LLM's.** Informed by ir's model-free
|
|
129
|
+
`Selection.sufficient` hint, the judge decides whether the committed subset
|
|
130
|
+
actually satisfies the goal. If not, it emits an improved query — a
|
|
131
|
+
`refinement` `SubTask` over the same sources. **That refinement is the
|
|
132
|
+
back-edge.**
|
|
133
|
+
|
|
134
|
+
Both builders accept an injectable double (`formulate=` / `judge=`) for
|
|
135
|
+
deterministic tests, and both **fail safe**: a formulator error falls back to the
|
|
136
|
+
raw query (never makes retrieval worse); a judge error returns no refinement
|
|
137
|
+
(the loop's break condition — a failing judge can never spin forever).
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## Multi-source search and the fan-in reranker
|
|
142
|
+
|
|
143
|
+
The agent is **multi-source by default**. The loop stamps each hit's provenance
|
|
144
|
+
(`hit.source`), and the default fan-in reranker, `rrf_reranker`, merges
|
|
145
|
+
heterogeneous sources **by rank, never by raw score**.
|
|
146
|
+
|
|
147
|
+
This matters because scores from different corpora / embedders / retrieval modes
|
|
148
|
+
are *incommensurable* — a cosine score (~0–1) and a BM25 score (~0–50) cannot be
|
|
149
|
+
compared directly. Reciprocal Rank Fusion (RRF) sidesteps this: within one source
|
|
150
|
+
raw scores order and dedup that source's hits (one scale, sound); across sources
|
|
151
|
+
only **ranks** interact.
|
|
152
|
+
|
|
153
|
+
```python
|
|
154
|
+
# Two sources on different score scales:
|
|
155
|
+
# rrf_reranker (default): interleaves both sources' rank-1 hits first.
|
|
156
|
+
# score_reranker (opt-in): a raw-score sort — only sound when every source
|
|
157
|
+
# shares one scale (same embedder + mode).
|
|
158
|
+
agent = raglab.make_search_agent(sources, reranker=raglab.score_reranker)
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Colliding `artifact_id`s from different sources stay distinct (identity is
|
|
162
|
+
`(source, artifact_id)`). Each fused hit keeps its pre-fusion magnitude in
|
|
163
|
+
`metadata["source_score"]`. For per-source trust weights or a custom `rrf_k`:
|
|
164
|
+
|
|
165
|
+
```python
|
|
166
|
+
reranker = raglab.make_rrf_reranker(weights={"skills": 2.0, "web": 0.5}, rrf_k=60)
|
|
167
|
+
agent = raglab.make_search_agent(sources, reranker=reranker)
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
## Customizing roles
|
|
173
|
+
|
|
174
|
+
Every role is just a callable matching a `Protocol`, so you can inject your own:
|
|
175
|
+
|
|
176
|
+
```python
|
|
177
|
+
from raglab import SubTask, LowLevelQuery, Judgement, make_search_agent
|
|
178
|
+
|
|
179
|
+
def my_planner(query, sources):
|
|
180
|
+
# split into two sub-goals over all sources
|
|
181
|
+
return [SubTask(goal=g, sources=tuple(sources))
|
|
182
|
+
for g in (query.text, query.text + " examples")]
|
|
183
|
+
|
|
184
|
+
def my_formulator(task, source):
|
|
185
|
+
return [LowLevelQuery(source=source, query=task.goal, params={"k": 10})]
|
|
186
|
+
|
|
187
|
+
agent = make_search_agent(sources, planner=my_planner, formulator=my_formulator)
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
A **custom Retriever** is any `callable(query, **params) -> Sequence[ir.SearchHit]`.
|
|
191
|
+
It must return `ir.SearchHit` instances (the `Result` alias) — the loop stamps
|
|
192
|
+
provenance on its output, so duck-typed hit objects raise at the tagging step.
|
|
193
|
+
ir-backed retrievers self-attribute their corpus name.
|
|
194
|
+
|
|
195
|
+
---
|
|
196
|
+
|
|
197
|
+
## API at a glance
|
|
198
|
+
|
|
199
|
+
Everything is exported from the top-level `raglab` namespace.
|
|
200
|
+
|
|
201
|
+
**Value types** (frozen dataclasses, plain data):
|
|
202
|
+
|
|
203
|
+
| Type | Fields |
|
|
204
|
+
|------|--------|
|
|
205
|
+
| `Query` | `text`, `constraints` |
|
|
206
|
+
| `SubTask` | `goal`, `sources: tuple[str, ...]` |
|
|
207
|
+
| `LowLevelQuery` | `source`, `query`, `params` (per-call retriever overrides) |
|
|
208
|
+
| `Judgement` | `relevant`, `sufficient`, `refinement: SubTask \| None` |
|
|
209
|
+
| `Budget` | `max_rounds=3`, `max_sources_per_task=4`, `max_results_per_task=50` |
|
|
210
|
+
| `Result` | alias for `ir.SearchHit` |
|
|
211
|
+
|
|
212
|
+
**Role Protocols** (the open-closed strategy seams): `Planner`, `Formulator`,
|
|
213
|
+
`Retriever` (re-exported from `ir`), `Evaluator`, `Reranker`, `Citer`.
|
|
214
|
+
|
|
215
|
+
**Orchestrator & builders:**
|
|
216
|
+
|
|
217
|
+
| Name | What it does |
|
|
218
|
+
|------|--------------|
|
|
219
|
+
| `make_search_agent(sources, *, planner, formulator, evaluator, reranker, citer, budget)` | Build a `SingleContextAgent` with smart defaults for every role. |
|
|
220
|
+
| `SingleContextAgent` | The orchestrator dataclass: one ReAct-style loop, sequential sub-tasks. |
|
|
221
|
+
| <a name="sources"></a>`ir_sources(*names, **search_defaults)` | A `{name: Retriever}` registry backed by named ir corpora. |
|
|
222
|
+
|
|
223
|
+
**Default roles (the no-LLM thin slice):** `single_subtask_planner`,
|
|
224
|
+
`identity_formulator`, `passthrough_evaluator`, `score_reranker`, `rrf_reranker`,
|
|
225
|
+
`make_rrf_reranker(*, rrf_k, weights, identity)`, `identity_citer`.
|
|
226
|
+
|
|
227
|
+
**LLM roles:** `make_llm_formulator(*, formulate=None, params=None, **make_kwargs)`,
|
|
228
|
+
`make_llm_evaluator(*, judge=None, select_strategy="conservative", select_kwargs=None, prerank=None, prompt=EVALUATION_PROMPT, ...)`,
|
|
229
|
+
and the default `EVALUATION_PROMPT` string.
|
|
230
|
+
|
|
231
|
+
---
|
|
232
|
+
|
|
233
|
+
## Architecture & boundaries
|
|
234
|
+
|
|
235
|
+
`raglab` **owns the agent**: the value types, the role Protocols, the control
|
|
236
|
+
loop with the back-edge, the budget governor, the source registry, and the
|
|
237
|
+
cross-source fan-in.
|
|
238
|
+
|
|
239
|
+
It **consumes from `ir`** (and does not reimplement): `ir.as_retriever` (corpus →
|
|
240
|
+
Retriever), `ir.retrievers()` (the live registry view), `ir.make_llm_formulator`
|
|
241
|
+
(the query rewriter), `ir.select` + its `Selection.sufficient` signal (relevance
|
|
242
|
+
+ the sufficiency hint), and `ir.fuse_hits` (RRF). The `Result` type and the
|
|
243
|
+
`Retriever` contract are **ir's** (single source of truth).
|
|
244
|
+
|
|
245
|
+
**Dependency direction is one-way:** `raglab` imports `ir` (and `oa` for the LLM
|
|
246
|
+
roles, lazily); **`ir` never imports `raglab`.** Keeping `oa` opt-in is what lets
|
|
247
|
+
`import raglab` stay offline.
|
|
248
|
+
|
|
249
|
+
What lives **elsewhere:** answer synthesis / generation and the
|
|
250
|
+
Citer/Verifier (which needs a *generated claim* to verify) belong in the
|
|
251
|
+
RAG/generation layer (`srag`), not the search agent. raglab's deliverable is
|
|
252
|
+
**pointers + extractions**, not an essay.
|
|
253
|
+
|
|
254
|
+
Where it sits in the ecosystem: **`raglab` → `ir` → {`ef`, `vd`}** — `ir` is the
|
|
255
|
+
retrieval substrate, `ef` owns embedders/segmenters, `vd` is the vector-store
|
|
256
|
+
facade.
|
|
257
|
+
|
|
258
|
+
---
|
|
259
|
+
|
|
260
|
+
## Status & roadmap
|
|
261
|
+
|
|
262
|
+
Shipped at **0.2.4**: the `SingleContextAgent` control loop with the back-edge,
|
|
263
|
+
all six role Protocols, the no-LLM defaults, the LLM Formulator + Evaluator, and
|
|
264
|
+
the RRF cross-source fan-in reranker. The roles, value types, and loop are
|
|
265
|
+
stable; richer strategies are added behind the same contracts.
|
|
266
|
+
|
|
267
|
+
The living roadmap is **[issue #2](https://github.com/thorwhalen/raglab/issues/2)**
|
|
268
|
+
(Composable Search Agent on ir, the *ir_09* reference design). Not yet built:
|
|
269
|
+
|
|
270
|
+
- **Budget governor refinements** — the per-task results cap currently truncates
|
|
271
|
+
in arrival order rather than by rank
|
|
272
|
+
([#5](https://github.com/thorwhalen/raglab/issues/5)); termination as a
|
|
273
|
+
separately injected policy is planned.
|
|
274
|
+
- **PurposeStore** — persistent, purpose-indexed memory overlay
|
|
275
|
+
([#6](https://github.com/thorwhalen/raglab/issues/6)). There is no cross-run
|
|
276
|
+
memory today.
|
|
277
|
+
- **`MultiAgentAgent`** — one subagent per sub-task/source (breadth-first, ~15×
|
|
278
|
+
cost); promotion swaps only the orchestrator, keeping role contracts identical.
|
|
279
|
+
|
|
280
|
+
---
|
|
281
|
+
|
|
282
|
+
## House style
|
|
283
|
+
|
|
284
|
+
Functional over OOP; SOLID when OOP; facades, SSOT, dependency injection;
|
|
285
|
+
progressive disclosure; keyword-only args beyond the third positional;
|
|
286
|
+
`collections.abc` + frozen `dataclass`es; `Protocol`s for the role seams. Tests
|
|
287
|
+
are hermetic (fake retrievers, injected LLM doubles) with a few end-to-end checks
|
|
288
|
+
over a real `ir` corpus using the light, numpy-only embedder.
|
|
@@ -6,11 +6,29 @@ build-backend = "hatchling.build"
|
|
|
6
6
|
|
|
7
7
|
[project]
|
|
8
8
|
name = "raglab"
|
|
9
|
-
version = "0.2.
|
|
9
|
+
version = "0.2.6"
|
|
10
10
|
description = "A medley of tools to make RAG-based applications."
|
|
11
11
|
readme = "README.md"
|
|
12
12
|
requires-python = ">=3.10"
|
|
13
|
-
|
|
13
|
+
license = "MIT"
|
|
14
|
+
keywords = [
|
|
15
|
+
"rag",
|
|
16
|
+
"retrieval",
|
|
17
|
+
"search-agent",
|
|
18
|
+
"llm",
|
|
19
|
+
"agents",
|
|
20
|
+
"information-retrieval",
|
|
21
|
+
]
|
|
22
|
+
classifiers = [
|
|
23
|
+
"Development Status :: 4 - Beta",
|
|
24
|
+
"Intended Audience :: Developers",
|
|
25
|
+
"License :: OSI Approved :: MIT License",
|
|
26
|
+
"Programming Language :: Python :: 3",
|
|
27
|
+
"Programming Language :: Python :: 3.10",
|
|
28
|
+
"Programming Language :: Python :: 3.12",
|
|
29
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
30
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
31
|
+
]
|
|
14
32
|
authors = [
|
|
15
33
|
{ name = "thorwhalen" },
|
|
16
34
|
]
|
|
@@ -21,13 +39,12 @@ dependencies = [
|
|
|
21
39
|
"ir>=0.1.16",
|
|
22
40
|
]
|
|
23
41
|
|
|
24
|
-
[project.license]
|
|
25
|
-
text = "mit"
|
|
26
|
-
|
|
27
42
|
[project.urls]
|
|
28
|
-
Homepage = "https://github.
|
|
43
|
+
Homepage = "https://thorwhalen.github.io/raglab/"
|
|
29
44
|
Repository = "https://github.com/thorwhalen/raglab"
|
|
30
45
|
Documentation = "https://thorwhalen.github.io/raglab"
|
|
46
|
+
Issues = "https://github.com/thorwhalen/raglab/issues"
|
|
47
|
+
Changelog = "https://github.com/thorwhalen/raglab/blob/main/CHANGELOG.md"
|
|
31
48
|
|
|
32
49
|
[project.optional-dependencies]
|
|
33
50
|
llm = [
|
raglab-0.2.4/PKG-INFO
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: raglab
|
|
3
|
-
Version: 0.2.4
|
|
4
|
-
Summary: A medley of tools to make RAG-based applications.
|
|
5
|
-
Project-URL: Homepage, https://github.com/thorwhalen/raglab
|
|
6
|
-
Project-URL: Repository, https://github.com/thorwhalen/raglab
|
|
7
|
-
Project-URL: Documentation, https://thorwhalen.github.io/raglab
|
|
8
|
-
Author: thorwhalen
|
|
9
|
-
License: mit
|
|
10
|
-
License-File: LICENSE
|
|
11
|
-
Requires-Python: >=3.10
|
|
12
|
-
Requires-Dist: ir>=0.1.16
|
|
13
|
-
Provides-Extra: dev
|
|
14
|
-
Requires-Dist: pytest-cov>=4.0; extra == 'dev'
|
|
15
|
-
Requires-Dist: pytest>=7.0; extra == 'dev'
|
|
16
|
-
Requires-Dist: ruff>=0.1.0; extra == 'dev'
|
|
17
|
-
Provides-Extra: docs
|
|
18
|
-
Requires-Dist: sphinx-rtd-theme>=1.0; extra == 'docs'
|
|
19
|
-
Requires-Dist: sphinx>=6.0; extra == 'docs'
|
|
20
|
-
Provides-Extra: llm
|
|
21
|
-
Requires-Dist: oa; extra == 'llm'
|
|
22
|
-
Description-Content-Type: text/markdown
|
|
23
|
-
|
|
24
|
-
# raglab
|
|
25
|
-
|
|
26
|
-
A medley of tools to make RAG-based applications.
|
|
27
|
-
|
|
28
|
-
To install: ```pip install raglab```
|
|
29
|
-
|
|
30
|
-
> **Note** — fresh start (v0.2.0+). This is a new project that has taken over the
|
|
31
|
-
> `raglab` name. The earlier `raglab` backend (PyPI 0.0.x–0.1.x) was renamed and
|
|
32
|
-
> now lives at [addaix/raglab_bak](https://github.com/addaix/raglab_bak)
|
|
33
|
-
> (published on PyPI as [`raglab_bak`](https://pypi.org/project/raglab_bak/)).
|
|
34
|
-
> Development of this new `raglab` is just beginning.
|
raglab-0.2.4/README.md
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
# raglab
|
|
2
|
-
|
|
3
|
-
A medley of tools to make RAG-based applications.
|
|
4
|
-
|
|
5
|
-
To install: ```pip install raglab```
|
|
6
|
-
|
|
7
|
-
> **Note** — fresh start (v0.2.0+). This is a new project that has taken over the
|
|
8
|
-
> `raglab` name. The earlier `raglab` backend (PyPI 0.0.x–0.1.x) was renamed and
|
|
9
|
-
> now lives at [addaix/raglab_bak](https://github.com/addaix/raglab_bak)
|
|
10
|
-
> (published on PyPI as [`raglab_bak`](https://pypi.org/project/raglab_bak/)).
|
|
11
|
-
> Development of this new `raglab` is just beginning.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|