biblicus 0.1.1__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.
- biblicus/__init__.py +28 -0
- biblicus/__main__.py +8 -0
- biblicus/backends/__init__.py +44 -0
- biblicus/backends/base.py +65 -0
- biblicus/backends/scan.py +292 -0
- biblicus/backends/sqlite_full_text_search.py +427 -0
- biblicus/cli.py +468 -0
- biblicus/constants.py +10 -0
- biblicus/corpus.py +952 -0
- biblicus/evaluation.py +261 -0
- biblicus/frontmatter.py +92 -0
- biblicus/models.py +307 -0
- biblicus/retrieval.py +137 -0
- biblicus/sources.py +132 -0
- biblicus/time.py +18 -0
- biblicus/uris.py +64 -0
- biblicus-0.1.1.dist-info/METADATA +174 -0
- biblicus-0.1.1.dist-info/RECORD +22 -0
- biblicus-0.1.1.dist-info/WHEEL +5 -0
- biblicus-0.1.1.dist-info/entry_points.txt +2 -0
- biblicus-0.1.1.dist-info/licenses/LICENSE +21 -0
- biblicus-0.1.1.dist-info/top_level.txt +1 -0
biblicus/retrieval.py
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Shared retrieval helpers for Biblicus backends.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import hashlib
|
|
8
|
+
import json
|
|
9
|
+
from typing import Any, Dict, Iterable, List, Optional
|
|
10
|
+
|
|
11
|
+
from .corpus import Corpus
|
|
12
|
+
from .models import Evidence, QueryBudget, RecipeManifest, RetrievalRun
|
|
13
|
+
from .time import utc_now_iso
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def create_recipe_manifest(
|
|
17
|
+
*,
|
|
18
|
+
backend_id: str,
|
|
19
|
+
name: str,
|
|
20
|
+
config: Dict[str, Any],
|
|
21
|
+
description: Optional[str] = None,
|
|
22
|
+
) -> RecipeManifest:
|
|
23
|
+
"""
|
|
24
|
+
Create a deterministic recipe manifest from a backend configuration.
|
|
25
|
+
|
|
26
|
+
:param backend_id: Backend identifier for the recipe.
|
|
27
|
+
:type backend_id: str
|
|
28
|
+
:param name: Human-readable recipe name.
|
|
29
|
+
:type name: str
|
|
30
|
+
:param config: Backend-specific configuration values.
|
|
31
|
+
:type config: dict[str, Any]
|
|
32
|
+
:param description: Optional recipe description.
|
|
33
|
+
:type description: str or None
|
|
34
|
+
:return: Deterministic recipe manifest.
|
|
35
|
+
:rtype: RecipeManifest
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
config_json = json.dumps(config, sort_keys=True, separators=(",", ":"))
|
|
39
|
+
recipe_seed = f"{backend_id}:{config_json}"
|
|
40
|
+
recipe_id = hashlib.sha256(recipe_seed.encode("utf-8")).hexdigest()
|
|
41
|
+
return RecipeManifest(
|
|
42
|
+
recipe_id=recipe_id,
|
|
43
|
+
backend_id=backend_id,
|
|
44
|
+
name=name,
|
|
45
|
+
created_at=utc_now_iso(),
|
|
46
|
+
config=config,
|
|
47
|
+
description=description,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def create_run_manifest(
|
|
52
|
+
corpus: Corpus,
|
|
53
|
+
*,
|
|
54
|
+
recipe: RecipeManifest,
|
|
55
|
+
stats: Dict[str, Any],
|
|
56
|
+
artifact_paths: Optional[List[str]] = None,
|
|
57
|
+
) -> RetrievalRun:
|
|
58
|
+
"""
|
|
59
|
+
Create a retrieval run manifest tied to the current catalog snapshot.
|
|
60
|
+
|
|
61
|
+
:param corpus: Corpus used to generate the run.
|
|
62
|
+
:type corpus: Corpus
|
|
63
|
+
:param recipe: Recipe manifest for the run.
|
|
64
|
+
:type recipe: RecipeManifest
|
|
65
|
+
:param stats: Backend-specific run statistics.
|
|
66
|
+
:type stats: dict[str, Any]
|
|
67
|
+
:param artifact_paths: Optional relative paths to materialized artifacts.
|
|
68
|
+
:type artifact_paths: list[str] or None
|
|
69
|
+
:return: Run manifest.
|
|
70
|
+
:rtype: RetrievalRun
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
catalog = corpus.load_catalog()
|
|
74
|
+
created_at = utc_now_iso()
|
|
75
|
+
run_id = hashlib.sha256(f"{recipe.recipe_id}:{created_at}".encode("utf-8")).hexdigest()
|
|
76
|
+
return RetrievalRun(
|
|
77
|
+
run_id=run_id,
|
|
78
|
+
recipe=recipe,
|
|
79
|
+
corpus_uri=catalog.corpus_uri,
|
|
80
|
+
catalog_generated_at=catalog.generated_at,
|
|
81
|
+
created_at=created_at,
|
|
82
|
+
artifact_paths=list(artifact_paths or []),
|
|
83
|
+
stats=stats,
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def hash_text(text: str) -> str:
|
|
88
|
+
"""
|
|
89
|
+
Hash a text payload for provenance.
|
|
90
|
+
|
|
91
|
+
:param text: Text to hash.
|
|
92
|
+
:type text: str
|
|
93
|
+
:return: Secure Hash Algorithm 256 hex digest.
|
|
94
|
+
:rtype: str
|
|
95
|
+
"""
|
|
96
|
+
|
|
97
|
+
return hashlib.sha256(text.encode("utf-8")).hexdigest()
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def apply_budget(evidence: Iterable[Evidence], budget: QueryBudget) -> List[Evidence]:
|
|
101
|
+
"""
|
|
102
|
+
Apply a query budget to a ranked evidence list.
|
|
103
|
+
|
|
104
|
+
:param evidence: Ranked evidence iterable (highest score first).
|
|
105
|
+
:type evidence: Iterable[Evidence]
|
|
106
|
+
:param budget: Budget constraints to enforce.
|
|
107
|
+
:type budget: QueryBudget
|
|
108
|
+
:return: Evidence list respecting the budget.
|
|
109
|
+
:rtype: list[Evidence]
|
|
110
|
+
"""
|
|
111
|
+
|
|
112
|
+
selected_evidence: List[Evidence] = []
|
|
113
|
+
source_counts: Dict[str, int] = {}
|
|
114
|
+
total_characters = 0
|
|
115
|
+
|
|
116
|
+
for candidate_evidence in evidence:
|
|
117
|
+
if len(selected_evidence) >= budget.max_total_items:
|
|
118
|
+
break
|
|
119
|
+
|
|
120
|
+
source_key = candidate_evidence.source_uri or candidate_evidence.item_id
|
|
121
|
+
if budget.max_items_per_source is not None:
|
|
122
|
+
if source_counts.get(source_key, 0) >= budget.max_items_per_source:
|
|
123
|
+
continue
|
|
124
|
+
|
|
125
|
+
text_character_count = len(candidate_evidence.text or "")
|
|
126
|
+
if budget.max_total_characters is not None:
|
|
127
|
+
if total_characters + text_character_count > budget.max_total_characters:
|
|
128
|
+
continue
|
|
129
|
+
|
|
130
|
+
selected_evidence.append(candidate_evidence)
|
|
131
|
+
source_counts[source_key] = source_counts.get(source_key, 0) + 1
|
|
132
|
+
total_characters += text_character_count
|
|
133
|
+
|
|
134
|
+
return [
|
|
135
|
+
evidence_item.model_copy(update={"rank": index})
|
|
136
|
+
for index, evidence_item in enumerate(selected_evidence, start=1)
|
|
137
|
+
]
|
biblicus/sources.py
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Source loading helpers for Biblicus ingestion.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import mimetypes
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Optional
|
|
11
|
+
from urllib.parse import unquote, urlparse
|
|
12
|
+
from urllib.request import Request, urlopen
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _looks_like_uri(value: str) -> bool:
|
|
16
|
+
"""
|
|
17
|
+
Check whether a string resembles a uniform resource identifier.
|
|
18
|
+
|
|
19
|
+
:param value: Candidate string.
|
|
20
|
+
:type value: str
|
|
21
|
+
:return: True if the string has a valid uniform resource identifier scheme prefix.
|
|
22
|
+
:rtype: bool
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
return "://" in value and value.split("://", 1)[0].isidentifier()
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _filename_from_url_path(path: str) -> str:
|
|
29
|
+
"""
|
|
30
|
+
Derive a filename from a uniform resource locator path.
|
|
31
|
+
|
|
32
|
+
:param path: Uniform resource locator path component.
|
|
33
|
+
:type path: str
|
|
34
|
+
:return: Filename or a fallback name.
|
|
35
|
+
:rtype: str
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
filename = Path(unquote(path)).name
|
|
39
|
+
return filename or "download"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _media_type_from_filename(name: str) -> str:
|
|
43
|
+
"""
|
|
44
|
+
Guess media type from a filename.
|
|
45
|
+
|
|
46
|
+
:param name: Filename to inspect.
|
|
47
|
+
:type name: str
|
|
48
|
+
:return: Guessed media type or application/octet-stream.
|
|
49
|
+
:rtype: str
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
media_type, _ = mimetypes.guess_type(name)
|
|
53
|
+
return media_type or "application/octet-stream"
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@dataclass(frozen=True)
|
|
57
|
+
class SourcePayload:
|
|
58
|
+
"""
|
|
59
|
+
Loaded source payload for ingestion.
|
|
60
|
+
|
|
61
|
+
:ivar data: Raw bytes from the source.
|
|
62
|
+
:vartype data: bytes
|
|
63
|
+
:ivar filename: Suggested filename for the payload.
|
|
64
|
+
:vartype filename: str
|
|
65
|
+
:ivar media_type: Internet Assigned Numbers Authority media type for the payload.
|
|
66
|
+
:vartype media_type: str
|
|
67
|
+
:ivar source_uri: Source uniform resource identifier used to load the payload.
|
|
68
|
+
:vartype source_uri: str
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
data: bytes
|
|
72
|
+
filename: str
|
|
73
|
+
media_type: str
|
|
74
|
+
source_uri: str
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def load_source(source: str | Path, *, source_uri: Optional[str] = None) -> SourcePayload:
|
|
78
|
+
"""
|
|
79
|
+
Load bytes from a source reference.
|
|
80
|
+
|
|
81
|
+
:param source: File path or uniform resource locator to load.
|
|
82
|
+
:type source: str or Path
|
|
83
|
+
:param source_uri: Optional override for the source uniform resource identifier.
|
|
84
|
+
:type source_uri: str or None
|
|
85
|
+
:return: Source payload with bytes and metadata.
|
|
86
|
+
:rtype: SourcePayload
|
|
87
|
+
:raises ValueError: If a file:// uniform resource identifier has a non-local host.
|
|
88
|
+
:raises NotImplementedError: If the uniform resource identifier scheme is unsupported.
|
|
89
|
+
"""
|
|
90
|
+
|
|
91
|
+
if isinstance(source, Path):
|
|
92
|
+
path = source.resolve()
|
|
93
|
+
media_type = _media_type_from_filename(path.name)
|
|
94
|
+
if path.suffix.lower() in {".md", ".markdown"}:
|
|
95
|
+
media_type = "text/markdown"
|
|
96
|
+
return SourcePayload(
|
|
97
|
+
data=path.read_bytes(),
|
|
98
|
+
filename=path.name,
|
|
99
|
+
media_type=media_type,
|
|
100
|
+
source_uri=source_uri or path.as_uri(),
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
if _looks_like_uri(source):
|
|
104
|
+
parsed = urlparse(source)
|
|
105
|
+
if parsed.scheme == "file":
|
|
106
|
+
if parsed.netloc not in ("", "localhost"):
|
|
107
|
+
raise ValueError(f"Unsupported file uniform resource identifier host: {parsed.netloc!r}")
|
|
108
|
+
path = Path(unquote(parsed.path)).resolve()
|
|
109
|
+
return load_source(path, source_uri=source_uri or source)
|
|
110
|
+
|
|
111
|
+
if parsed.scheme in {"http", "https"}:
|
|
112
|
+
request = Request(source, headers={"User-Agent": "biblicus/0"})
|
|
113
|
+
with urlopen(request, timeout=30) as response:
|
|
114
|
+
response_bytes = response.read()
|
|
115
|
+
content_type = response.headers.get("Content-Type", "").split(";", 1)[0].strip()
|
|
116
|
+
filename = _filename_from_url_path(parsed.path)
|
|
117
|
+
media_type = content_type or _media_type_from_filename(filename)
|
|
118
|
+
if Path(filename).suffix.lower() in {".md", ".markdown"}:
|
|
119
|
+
media_type = "text/markdown"
|
|
120
|
+
return SourcePayload(
|
|
121
|
+
data=response_bytes,
|
|
122
|
+
filename=filename,
|
|
123
|
+
media_type=media_type,
|
|
124
|
+
source_uri=source_uri or source,
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
raise NotImplementedError(
|
|
128
|
+
f"Unsupported source uniform resource identifier scheme: {parsed.scheme}://"
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
path = Path(source).resolve()
|
|
132
|
+
return load_source(path, source_uri=source_uri)
|
biblicus/time.py
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Time utilities for Biblicus.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from datetime import datetime, timezone
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def utc_now_iso() -> str:
|
|
11
|
+
"""
|
|
12
|
+
Return the current Coordinated Universal Time as an International Organization for Standardization 8601 string.
|
|
13
|
+
|
|
14
|
+
:return: Current Coordinated Universal Time timestamp in International Organization for Standardization 8601 format.
|
|
15
|
+
:rtype: str
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
return datetime.now(timezone.utc).replace(microsecond=0).isoformat()
|
biblicus/uris.py
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Uniform resource identifier and path helpers for Biblicus corpora.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Union
|
|
9
|
+
from urllib.parse import unquote, urlparse
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _looks_like_uri(value: str) -> bool:
|
|
13
|
+
"""
|
|
14
|
+
Check whether a string resembles a uniform resource identifier.
|
|
15
|
+
|
|
16
|
+
:param value: Candidate string.
|
|
17
|
+
:type value: str
|
|
18
|
+
:return: True if the string has a valid uniform resource identifier scheme prefix.
|
|
19
|
+
:rtype: bool
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
return "://" in value and value.split("://", 1)[0].isidentifier()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def corpus_ref_to_path(ref: Union[str, Path]) -> Path:
|
|
26
|
+
"""
|
|
27
|
+
Convert a corpus reference to a filesystem path.
|
|
28
|
+
|
|
29
|
+
:param ref: Filesystem path or file:// uniform resource identifier.
|
|
30
|
+
:type ref: str or Path
|
|
31
|
+
:return: Resolved filesystem path.
|
|
32
|
+
:rtype: Path
|
|
33
|
+
:raises NotImplementedError: If a non-file uniform resource identifier scheme is used.
|
|
34
|
+
:raises ValueError: If a file:// uniform resource identifier has a non-local host.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
if isinstance(ref, Path):
|
|
38
|
+
return ref.resolve()
|
|
39
|
+
|
|
40
|
+
if _looks_like_uri(ref):
|
|
41
|
+
parsed = urlparse(ref)
|
|
42
|
+
if parsed.scheme != "file":
|
|
43
|
+
raise NotImplementedError(
|
|
44
|
+
"Only file:// corpus uniform resource identifiers are supported in version zero "
|
|
45
|
+
f"(got {parsed.scheme}://)"
|
|
46
|
+
)
|
|
47
|
+
if parsed.netloc not in ("", "localhost"):
|
|
48
|
+
raise ValueError(f"Unsupported file uniform resource identifier host: {parsed.netloc!r}")
|
|
49
|
+
return Path(unquote(parsed.path)).resolve()
|
|
50
|
+
|
|
51
|
+
return Path(ref).resolve()
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def normalize_corpus_uri(ref: Union[str, Path]) -> str:
|
|
55
|
+
"""
|
|
56
|
+
Normalize a corpus reference into a file:// uniform resource identifier.
|
|
57
|
+
|
|
58
|
+
:param ref: Filesystem path or file:// uniform resource identifier.
|
|
59
|
+
:type ref: str or Path
|
|
60
|
+
:return: Canonical file:// uniform resource identifier.
|
|
61
|
+
:rtype: str
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
return corpus_ref_to_path(ref).as_uri()
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: biblicus
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: Command line interface and Python library for corpus ingestion, retrieval, and evaluation.
|
|
5
|
+
License: MIT
|
|
6
|
+
Requires-Python: >=3.9
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Requires-Dist: pydantic>=2.0
|
|
10
|
+
Requires-Dist: PyYAML>=6.0
|
|
11
|
+
Provides-Extra: dev
|
|
12
|
+
Requires-Dist: behave>=1.2.6; extra == "dev"
|
|
13
|
+
Requires-Dist: coverage[toml]>=7.0; extra == "dev"
|
|
14
|
+
Requires-Dist: sphinx>=7.0; extra == "dev"
|
|
15
|
+
Requires-Dist: myst-parser>=2.0; extra == "dev"
|
|
16
|
+
Requires-Dist: ruff>=0.4.0; extra == "dev"
|
|
17
|
+
Requires-Dist: black>=24.0; extra == "dev"
|
|
18
|
+
Requires-Dist: python-semantic-release>=9.0.0; extra == "dev"
|
|
19
|
+
Dynamic: license-file
|
|
20
|
+
|
|
21
|
+
# Biblicus
|
|
22
|
+
|
|
23
|
+
Make your documents usable by your assistant, then decide later how you will search and retrieve them.
|
|
24
|
+
|
|
25
|
+
If you are building an assistant in Python, you probably have material you want it to use: notes, documents, web pages, and reference files. A common approach is retrieval augmented generation, where a system retrieves relevant material and uses it as evidence when generating a response.
|
|
26
|
+
|
|
27
|
+
The first practical problem is not retrieval. It is collection and care. You need a stable place to put raw items, you need a small amount of metadata so you can find them again, and you need a way to evolve your retrieval approach over time without rewriting ingestion.
|
|
28
|
+
|
|
29
|
+
This library gives you a corpus, which is a normal folder on disk. It stores each ingested item as a file, with optional metadata stored next to it. You can open and inspect the raw files directly. Any derived catalog or index can be rebuilt from the raw corpus.
|
|
30
|
+
|
|
31
|
+
It integrates with LangChain, Tactus, Pydantic AI, and the agent development kit. Use it from Python or from the command line interface.
|
|
32
|
+
|
|
33
|
+
See [retrieval augmented generation overview] for a short introduction to the idea.
|
|
34
|
+
|
|
35
|
+
## The framework
|
|
36
|
+
|
|
37
|
+
The framework is a small, explicit vocabulary that appears in code, specifications, and documentation. If you learn these words, the rest of the system becomes predictable.
|
|
38
|
+
|
|
39
|
+
- Corpus is the folder that holds raw items and their metadata.
|
|
40
|
+
- Item is the raw bytes of a document or other artifact, plus its source.
|
|
41
|
+
- Catalog is the rebuildable index of the corpus.
|
|
42
|
+
- Evidence is what retrieval returns, ready to be turned into context for a large language model.
|
|
43
|
+
- Run is a recorded retrieval build for a corpus.
|
|
44
|
+
- Backend is a pluggable retrieval implementation.
|
|
45
|
+
- Recipe is a named configuration for a backend.
|
|
46
|
+
- Pipeline stage is a distinct retrieval step such as retrieve, rerank, and filter.
|
|
47
|
+
|
|
48
|
+
## Practical value
|
|
49
|
+
|
|
50
|
+
- You can ingest raw material once, then try many retrieval approaches over time.
|
|
51
|
+
- You can keep raw files readable and portable, without locking your data inside a database.
|
|
52
|
+
- You can evaluate retrieval runs against shared datasets and compare backends using the same corpus.
|
|
53
|
+
|
|
54
|
+
## Typical flow
|
|
55
|
+
|
|
56
|
+
- Initialize a corpus folder.
|
|
57
|
+
- Ingest items from file paths, web addresses, or text input.
|
|
58
|
+
- Reindex to refresh the catalog after edits.
|
|
59
|
+
- Build a retrieval run with a backend.
|
|
60
|
+
- Query the run to collect evidence and evaluate it with datasets.
|
|
61
|
+
|
|
62
|
+
## Install
|
|
63
|
+
|
|
64
|
+
This repository is a working Python package. Install it into a virtual environment from the repository root.
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
python3 -m pip install -e .
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
After the first release, you can install it from Python Package Index.
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
python3 -m pip install biblicus
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Quick start
|
|
77
|
+
|
|
78
|
+
```
|
|
79
|
+
biblicus init corpora/example
|
|
80
|
+
biblicus ingest --corpus corpora/example notes/example.txt
|
|
81
|
+
echo "A short note" | biblicus ingest --corpus corpora/example --stdin --title "First note"
|
|
82
|
+
biblicus list --corpus corpora/example
|
|
83
|
+
biblicus build --corpus corpora/example --backend scan
|
|
84
|
+
biblicus query --corpus corpora/example --query "note"
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Python usage
|
|
88
|
+
|
|
89
|
+
From Python, the same flow is available through the Corpus class and backend interfaces. The public surface area is small on purpose.
|
|
90
|
+
|
|
91
|
+
- Create a corpus with `Corpus.init` or open one with `Corpus.open`.
|
|
92
|
+
- Ingest notes with `Corpus.ingest_note`.
|
|
93
|
+
- Ingest files or web addresses with `Corpus.ingest_source`.
|
|
94
|
+
- List items with `Corpus.list_items`.
|
|
95
|
+
- Build a retrieval run with `get_backend` and `backend.build_run`.
|
|
96
|
+
- Query a run with `backend.query`.
|
|
97
|
+
- Evaluate with `evaluate_run`.
|
|
98
|
+
|
|
99
|
+
## How it fits into an assistant
|
|
100
|
+
|
|
101
|
+
In an assistant system, retrieval usually produces context for a model call. This library treats evidence as the primary output so you can decide how to use it.
|
|
102
|
+
|
|
103
|
+
- Use a corpus as the source of truth for raw items.
|
|
104
|
+
- Use a backend run to build any derived artifacts needed for retrieval.
|
|
105
|
+
- Use queries to obtain evidence objects.
|
|
106
|
+
- Convert evidence into the format your framework expects, such as message content, tool output, or citations.
|
|
107
|
+
|
|
108
|
+
## Learn more
|
|
109
|
+
|
|
110
|
+
The documents below are written to be read in order.
|
|
111
|
+
|
|
112
|
+
- [Architecture][architecture]
|
|
113
|
+
- [Backends][backends]
|
|
114
|
+
|
|
115
|
+
## Metadata and catalog
|
|
116
|
+
|
|
117
|
+
Raw items are stored as files in the corpus raw directory. Metadata can live in a Markdown front matter block or a sidecar file with the suffix `.biblicus.yml`. The catalog lives in `.biblicus/catalog.json` and can be rebuilt at any time with `biblicus reindex`.
|
|
118
|
+
|
|
119
|
+
## Corpus layout
|
|
120
|
+
|
|
121
|
+
```
|
|
122
|
+
corpus/
|
|
123
|
+
raw/
|
|
124
|
+
item.bin
|
|
125
|
+
item.bin.biblicus.yml
|
|
126
|
+
.biblicus/
|
|
127
|
+
config.json
|
|
128
|
+
catalog.json
|
|
129
|
+
runs/
|
|
130
|
+
run-id.json
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Retrieval backends
|
|
134
|
+
|
|
135
|
+
Two backends are included.
|
|
136
|
+
|
|
137
|
+
- `scan` is a minimal baseline that scans raw items directly.
|
|
138
|
+
- `sqlite-full-text-search` is a practical baseline that builds a full text search index in Sqlite.
|
|
139
|
+
|
|
140
|
+
## Integration corpus and evaluation dataset
|
|
141
|
+
|
|
142
|
+
Use `scripts/download_wikipedia.py` to download a small integration corpus from Wikipedia when running tests or demos. The repository does not include that content.
|
|
143
|
+
|
|
144
|
+
The dataset file `datasets/wikipedia_mini.json` provides a small evaluation set that matches the integration corpus.
|
|
145
|
+
|
|
146
|
+
## Tests and coverage
|
|
147
|
+
|
|
148
|
+
```
|
|
149
|
+
python3 scripts/test.py
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## Releases
|
|
153
|
+
|
|
154
|
+
Releases are automated from the main branch using semantic versioning and conventional commit messages.
|
|
155
|
+
|
|
156
|
+
The release pipeline publishes a GitHub release and uploads the package to Python Package Index when continuous integration succeeds.
|
|
157
|
+
|
|
158
|
+
Publishing uses a Python Package Index token stored in the GitHub secret named PYPI_TOKEN.
|
|
159
|
+
|
|
160
|
+
## Documentation
|
|
161
|
+
|
|
162
|
+
Reference documentation is generated from Sphinx style docstrings. Build the documentation with the command below.
|
|
163
|
+
|
|
164
|
+
```
|
|
165
|
+
sphinx-build -b html docs docs/_build
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## License
|
|
169
|
+
|
|
170
|
+
License terms are in `LICENSE`.
|
|
171
|
+
|
|
172
|
+
[retrieval augmented generation overview]: https://en.wikipedia.org/wiki/Retrieval-augmented_generation
|
|
173
|
+
[architecture]: docs/ARCHITECTURE.md
|
|
174
|
+
[backends]: docs/BACKENDS.md
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
biblicus/__init__.py,sha256=o_1kQ7q9DCcjH7zm5MAvPx49hArnSvbr88kHKzBFMvM,432
|
|
2
|
+
biblicus/__main__.py,sha256=ipfkUoTlocVnrQDM69C7TeBqQxmHVeiWMRaT3G9rtnk,117
|
|
3
|
+
biblicus/cli.py,sha256=DwnvcDmjelzUq_9VMo_U_-FoBs3Si3QONVJdWGonXs4,15116
|
|
4
|
+
biblicus/constants.py,sha256=t8p0yStpJAYPxsFlM0u5zJcQr_ARKEqEnIgNckjyF5Y,196
|
|
5
|
+
biblicus/corpus.py,sha256=953gzT77HvYeTs2pcBXyixYRTxh65nm1JtlHVfKvCzg,30921
|
|
6
|
+
biblicus/evaluation.py,sha256=H_W35vF5_L4B2JCfLu19VRu402tZ2pFkN2BbBP69lVY,8119
|
|
7
|
+
biblicus/frontmatter.py,sha256=8Tqlpd3bVzZrGRB9Rdj2IwHMSJLvd2ABxMNOi3L5br4,2466
|
|
8
|
+
biblicus/models.py,sha256=ZDb7-t9pycPpgZWVs5CcrpyeA_8OZLoQk-aflKjU7M4,10512
|
|
9
|
+
biblicus/retrieval.py,sha256=T7HELWCNAxZ26yj7dPH8IBUaxV_gx8Ql9iwwGz0teyI,4184
|
|
10
|
+
biblicus/sources.py,sha256=XFF75kqMyYdeYy6k8NtDnOmCxAmroW7DH6mdzWMPMuY,4358
|
|
11
|
+
biblicus/time.py,sha256=rvp2fJXSLVmyA76GCfNKtZoifASodemJTOWN8smPt0s,486
|
|
12
|
+
biblicus/uris.py,sha256=sRDyGmoHr_H4XR4qv_lSbQJXylYD0fNEr02H5wjomnQ,1986
|
|
13
|
+
biblicus/backends/__init__.py,sha256=5OXKSzsn7THhwh9T5StOvEqojx_85XXuYSGdTpMK11U,1214
|
|
14
|
+
biblicus/backends/base.py,sha256=699TKygGgL72Ifkhz1V890nOK6BslwO0-OY7xeqZl-I,1764
|
|
15
|
+
biblicus/backends/scan.py,sha256=qvktqHIB0459sjzEO4EnS1PCXwwM19LjOx8oaDoU7DQ,9245
|
|
16
|
+
biblicus/backends/sqlite_full_text_search.py,sha256=s_3gsEcdlxSFuluWcug4XEklwEoY42_Dgd7luY-BqqI,14152
|
|
17
|
+
biblicus-0.1.1.dist-info/licenses/LICENSE,sha256=lw44GXFG_Q0fS8m5VoEvv_xtdBXK26pBcbSPUCXee_Q,1078
|
|
18
|
+
biblicus-0.1.1.dist-info/METADATA,sha256=lgvWJUgESiwWTCZ6_uUzgZeM3SkvnwjIzcsb8OE53BA,6635
|
|
19
|
+
biblicus-0.1.1.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
20
|
+
biblicus-0.1.1.dist-info/entry_points.txt,sha256=BZmO4H8Uz00fyi1RAFryOCGfZgX7eHWkY2NE-G54U5A,47
|
|
21
|
+
biblicus-0.1.1.dist-info/top_level.txt,sha256=sUD_XVZwDxZ29-FBv1MknTGh4mgDXznGuP28KJY_WKc,9
|
|
22
|
+
biblicus-0.1.1.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Biblicus Contributors
|
|
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.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
biblicus
|