verbatim-core 0.1.9__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.
- verbatim_core-0.1.9/.gitignore +218 -0
- verbatim_core-0.1.9/PKG-INFO +84 -0
- verbatim_core-0.1.9/README.md +64 -0
- verbatim_core-0.1.9/pyproject.toml +40 -0
- verbatim_core-0.1.9/verbatim_core/__init__.py +24 -0
- verbatim_core-0.1.9/verbatim_core/cli.py +71 -0
- verbatim_core-0.1.9/verbatim_core/enhance.py +76 -0
- verbatim_core-0.1.9/verbatim_core/extractor_models/dataset.py +258 -0
- verbatim_core-0.1.9/verbatim_core/extractor_models/model.py +151 -0
- verbatim_core-0.1.9/verbatim_core/extractor_models/preprocess_ragbench.py +116 -0
- verbatim_core-0.1.9/verbatim_core/extractor_models/train.py +287 -0
- verbatim_core-0.1.9/verbatim_core/extractor_models/trainer.py +494 -0
- verbatim_core-0.1.9/verbatim_core/extractors.py +577 -0
- verbatim_core-0.1.9/verbatim_core/llm_client.py +439 -0
- verbatim_core-0.1.9/verbatim_core/models.py +65 -0
- verbatim_core-0.1.9/verbatim_core/providers.py +26 -0
- verbatim_core-0.1.9/verbatim_core/response_builder.py +182 -0
- verbatim_core-0.1.9/verbatim_core/templates/__init__.py +21 -0
- verbatim_core-0.1.9/verbatim_core/templates/base.py +91 -0
- verbatim_core-0.1.9/verbatim_core/templates/contextual.py +247 -0
- verbatim_core-0.1.9/verbatim_core/templates/filler.py +144 -0
- verbatim_core-0.1.9/verbatim_core/templates/manager.py +444 -0
- verbatim_core-0.1.9/verbatim_core/templates/question_specific.py +270 -0
- verbatim_core-0.1.9/verbatim_core/templates/random.py +244 -0
- verbatim_core-0.1.9/verbatim_core/templates/static.py +161 -0
- verbatim_core-0.1.9/verbatim_core/templates/structured.py +265 -0
- verbatim_core-0.1.9/verbatim_core/transform.py +161 -0
- verbatim_core-0.1.9/verbatim_core/types.py +7 -0
- verbatim_core-0.1.9/verbatim_core/universal_document.py +44 -0
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# C extensions
|
|
7
|
+
*.so
|
|
8
|
+
|
|
9
|
+
# Distribution / packaging
|
|
10
|
+
.Python
|
|
11
|
+
build/
|
|
12
|
+
develop-eggs/
|
|
13
|
+
dist/
|
|
14
|
+
downloads/
|
|
15
|
+
eggs/
|
|
16
|
+
.eggs/
|
|
17
|
+
lib/
|
|
18
|
+
lib64/
|
|
19
|
+
parts/
|
|
20
|
+
sdist/
|
|
21
|
+
var/
|
|
22
|
+
wheels/
|
|
23
|
+
share/python-wheels/
|
|
24
|
+
*.egg-info/
|
|
25
|
+
.installed.cfg
|
|
26
|
+
*.egg
|
|
27
|
+
MANIFEST
|
|
28
|
+
|
|
29
|
+
# PyInstaller
|
|
30
|
+
# Usually these files are written by a python script from a template
|
|
31
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
32
|
+
*.manifest
|
|
33
|
+
*.spec
|
|
34
|
+
|
|
35
|
+
# Installer logs
|
|
36
|
+
pip-log.txt
|
|
37
|
+
pip-delete-this-directory.txt
|
|
38
|
+
|
|
39
|
+
# Unit test / coverage reports
|
|
40
|
+
htmlcov/
|
|
41
|
+
.tox/
|
|
42
|
+
.nox/
|
|
43
|
+
.coverage
|
|
44
|
+
.coverage.*
|
|
45
|
+
.cache
|
|
46
|
+
nosetests.xml
|
|
47
|
+
coverage.xml
|
|
48
|
+
*.cover
|
|
49
|
+
*.py,cover
|
|
50
|
+
.hypothesis/
|
|
51
|
+
.pytest_cache/
|
|
52
|
+
cover/
|
|
53
|
+
|
|
54
|
+
# Translations
|
|
55
|
+
*.mo
|
|
56
|
+
*.pot
|
|
57
|
+
|
|
58
|
+
# Django stuff:
|
|
59
|
+
*.log
|
|
60
|
+
local_settings.py
|
|
61
|
+
db.sqlite3
|
|
62
|
+
db.sqlite3-journal
|
|
63
|
+
|
|
64
|
+
# Flask stuff:
|
|
65
|
+
instance/
|
|
66
|
+
.webassets-cache
|
|
67
|
+
|
|
68
|
+
# Scrapy stuff:
|
|
69
|
+
.scrapy
|
|
70
|
+
|
|
71
|
+
# Sphinx documentation
|
|
72
|
+
docs/_build/
|
|
73
|
+
|
|
74
|
+
# PyBuilder
|
|
75
|
+
.pybuilder/
|
|
76
|
+
target/
|
|
77
|
+
|
|
78
|
+
# Jupyter Notebook
|
|
79
|
+
.ipynb_checkpoints
|
|
80
|
+
|
|
81
|
+
# IPython
|
|
82
|
+
profile_default/
|
|
83
|
+
ipython_config.py
|
|
84
|
+
|
|
85
|
+
# pyenv
|
|
86
|
+
# For a library or package, you might want to ignore these files since the code is
|
|
87
|
+
# intended to run in multiple environments; otherwise, check them in:
|
|
88
|
+
# .python-version
|
|
89
|
+
|
|
90
|
+
# pipenv
|
|
91
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
92
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
93
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
94
|
+
# install all needed dependencies.
|
|
95
|
+
#Pipfile.lock
|
|
96
|
+
|
|
97
|
+
# UV
|
|
98
|
+
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
|
99
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
100
|
+
# commonly ignored for libraries.
|
|
101
|
+
#uv.lock
|
|
102
|
+
|
|
103
|
+
# poetry
|
|
104
|
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
105
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
106
|
+
# commonly ignored for libraries.
|
|
107
|
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
|
108
|
+
#poetry.lock
|
|
109
|
+
|
|
110
|
+
# pdm
|
|
111
|
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
112
|
+
#pdm.lock
|
|
113
|
+
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
|
114
|
+
# in version control.
|
|
115
|
+
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
|
|
116
|
+
.pdm.toml
|
|
117
|
+
.pdm-python
|
|
118
|
+
.pdm-build/
|
|
119
|
+
|
|
120
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
121
|
+
__pypackages__/
|
|
122
|
+
|
|
123
|
+
# Celery stuff
|
|
124
|
+
celerybeat-schedule
|
|
125
|
+
celerybeat.pid
|
|
126
|
+
|
|
127
|
+
# SageMath parsed files
|
|
128
|
+
*.sage.py
|
|
129
|
+
|
|
130
|
+
# Environments
|
|
131
|
+
.env
|
|
132
|
+
.venv
|
|
133
|
+
env/
|
|
134
|
+
venv/
|
|
135
|
+
ENV/
|
|
136
|
+
env.bak/
|
|
137
|
+
venv.bak/
|
|
138
|
+
|
|
139
|
+
# Spyder project settings
|
|
140
|
+
.spyderproject
|
|
141
|
+
.spyproject
|
|
142
|
+
|
|
143
|
+
# Rope project settings
|
|
144
|
+
.ropeproject
|
|
145
|
+
|
|
146
|
+
# mkdocs documentation
|
|
147
|
+
/site
|
|
148
|
+
|
|
149
|
+
# mypy
|
|
150
|
+
.mypy_cache/
|
|
151
|
+
.dmypy.json
|
|
152
|
+
dmypy.json
|
|
153
|
+
|
|
154
|
+
# Pyre type checker
|
|
155
|
+
.pyre/
|
|
156
|
+
|
|
157
|
+
# pytype static type analyzer
|
|
158
|
+
.pytype/
|
|
159
|
+
|
|
160
|
+
# Cython debug symbols
|
|
161
|
+
cython_debug/
|
|
162
|
+
|
|
163
|
+
# PyCharm
|
|
164
|
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
|
165
|
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
|
166
|
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
|
167
|
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
|
168
|
+
#.idea/
|
|
169
|
+
|
|
170
|
+
# PyPI configuration file
|
|
171
|
+
.pypirc
|
|
172
|
+
|
|
173
|
+
documents/
|
|
174
|
+
index/
|
|
175
|
+
templates.json
|
|
176
|
+
ui/
|
|
177
|
+
.ruff_cache/
|
|
178
|
+
|
|
179
|
+
data/
|
|
180
|
+
.qodo
|
|
181
|
+
|
|
182
|
+
# Frontend / Node.js
|
|
183
|
+
frontend/node_modules/
|
|
184
|
+
frontend/.env
|
|
185
|
+
frontend/.env.local
|
|
186
|
+
frontend/.env.development.local
|
|
187
|
+
frontend/.env.test.local
|
|
188
|
+
frontend/.env.production.local
|
|
189
|
+
frontend/build/
|
|
190
|
+
frontend/dist/
|
|
191
|
+
frontend/.next/
|
|
192
|
+
frontend/.nuxt/
|
|
193
|
+
frontend/.vite/
|
|
194
|
+
frontend/npm-debug.log*
|
|
195
|
+
frontend/yarn-debug.log*
|
|
196
|
+
frontend/yarn-error.log*
|
|
197
|
+
frontend/pnpm-debug.log*
|
|
198
|
+
frontend/lerna-debug.log*
|
|
199
|
+
|
|
200
|
+
# IDEs and editors
|
|
201
|
+
.vscode/
|
|
202
|
+
.idea/
|
|
203
|
+
*.swp
|
|
204
|
+
*.swo
|
|
205
|
+
*~
|
|
206
|
+
|
|
207
|
+
# OS generated files
|
|
208
|
+
.DS_Store
|
|
209
|
+
.DS_Store?
|
|
210
|
+
._*
|
|
211
|
+
.Spotlight-V100
|
|
212
|
+
.Trashes
|
|
213
|
+
ehthumbs.db
|
|
214
|
+
Thumbs.db
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
mockup/
|
|
218
|
+
research-copilot/
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: verbatim-core
|
|
3
|
+
Version: 0.1.9
|
|
4
|
+
Summary: Lightweight verbatim span extraction -- the RAG-agnostic core of verbatim-rag
|
|
5
|
+
Project-URL: Homepage, https://github.com/krlabsorg/verbatim-rag
|
|
6
|
+
Project-URL: Bug Tracker, https://github.com/krlabsorg/verbatim-rag/issues
|
|
7
|
+
Author-email: Adam Kovacs <kovacs@krlabs.eu>
|
|
8
|
+
License: MIT
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Requires-Python: >=3.10
|
|
13
|
+
Requires-Dist: openai>=1.3.0
|
|
14
|
+
Requires-Dist: pydantic>=2.0.0
|
|
15
|
+
Provides-Extra: model
|
|
16
|
+
Requires-Dist: scikit-learn==1.6.1; extra == 'model'
|
|
17
|
+
Requires-Dist: torch>=2.6.0; extra == 'model'
|
|
18
|
+
Requires-Dist: transformers==4.53.3; extra == 'model'
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
|
|
21
|
+
# verbatim-core
|
|
22
|
+
|
|
23
|
+
Lightweight verbatim span extraction -- the RAG-agnostic core of [verbatim-rag](https://github.com/KRLabsOrg/verbatim-rag).
|
|
24
|
+
|
|
25
|
+
Extract exact, verbatim text spans from documents that answer a question. No vector databases, no embeddings, no heavy ML dependencies -- just `openai` and `pydantic`.
|
|
26
|
+
|
|
27
|
+
## Installation
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
pip install verbatim-core
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Quick Start
|
|
34
|
+
|
|
35
|
+
```python
|
|
36
|
+
from verbatim_core import VerbatimTransform
|
|
37
|
+
|
|
38
|
+
vt = VerbatimTransform()
|
|
39
|
+
response = vt.transform(
|
|
40
|
+
question="What is the main finding?",
|
|
41
|
+
context=[
|
|
42
|
+
{"content": "The study found that X leads to Y.", "title": "Paper A"},
|
|
43
|
+
{"content": "Results show Z is statistically significant.", "title": "Paper B"},
|
|
44
|
+
],
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
print(response.answer)
|
|
48
|
+
|
|
49
|
+
# Access individual highlights and citations
|
|
50
|
+
for doc in response.documents:
|
|
51
|
+
for highlight in doc.highlights:
|
|
52
|
+
print(f" [{highlight.start}:{highlight.end}] {highlight.text}")
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## What This Package Includes
|
|
56
|
+
|
|
57
|
+
- **VerbatimTransform** -- question + context -> cited, grounded answer
|
|
58
|
+
- **LLMSpanExtractor** -- extract verbatim spans using an LLM
|
|
59
|
+
- **LLMClient** -- unified OpenAI API wrapper (sync + async)
|
|
60
|
+
- **TemplateManager** -- response formatting with multiple template strategies
|
|
61
|
+
- **@verbatim_enhance** -- decorator to enhance existing RAG functions
|
|
62
|
+
- **CLI** (`verbatim-enhance`) -- batch processing from the command line
|
|
63
|
+
|
|
64
|
+
## Model-Based Extraction
|
|
65
|
+
|
|
66
|
+
For ModernBERT or Zilliz semantic highlight extractors (adds torch, transformers):
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
pip install verbatim-core[model]
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Environment
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
export OPENAI_API_KEY=your_api_key_here
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Full RAG System
|
|
79
|
+
|
|
80
|
+
For the complete RAG pipeline with vector indexing, embeddings, and document processing, install the full package:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
pip install verbatim-rag
|
|
84
|
+
```
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# verbatim-core
|
|
2
|
+
|
|
3
|
+
Lightweight verbatim span extraction -- the RAG-agnostic core of [verbatim-rag](https://github.com/KRLabsOrg/verbatim-rag).
|
|
4
|
+
|
|
5
|
+
Extract exact, verbatim text spans from documents that answer a question. No vector databases, no embeddings, no heavy ML dependencies -- just `openai` and `pydantic`.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pip install verbatim-core
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Quick Start
|
|
14
|
+
|
|
15
|
+
```python
|
|
16
|
+
from verbatim_core import VerbatimTransform
|
|
17
|
+
|
|
18
|
+
vt = VerbatimTransform()
|
|
19
|
+
response = vt.transform(
|
|
20
|
+
question="What is the main finding?",
|
|
21
|
+
context=[
|
|
22
|
+
{"content": "The study found that X leads to Y.", "title": "Paper A"},
|
|
23
|
+
{"content": "Results show Z is statistically significant.", "title": "Paper B"},
|
|
24
|
+
],
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
print(response.answer)
|
|
28
|
+
|
|
29
|
+
# Access individual highlights and citations
|
|
30
|
+
for doc in response.documents:
|
|
31
|
+
for highlight in doc.highlights:
|
|
32
|
+
print(f" [{highlight.start}:{highlight.end}] {highlight.text}")
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## What This Package Includes
|
|
36
|
+
|
|
37
|
+
- **VerbatimTransform** -- question + context -> cited, grounded answer
|
|
38
|
+
- **LLMSpanExtractor** -- extract verbatim spans using an LLM
|
|
39
|
+
- **LLMClient** -- unified OpenAI API wrapper (sync + async)
|
|
40
|
+
- **TemplateManager** -- response formatting with multiple template strategies
|
|
41
|
+
- **@verbatim_enhance** -- decorator to enhance existing RAG functions
|
|
42
|
+
- **CLI** (`verbatim-enhance`) -- batch processing from the command line
|
|
43
|
+
|
|
44
|
+
## Model-Based Extraction
|
|
45
|
+
|
|
46
|
+
For ModernBERT or Zilliz semantic highlight extractors (adds torch, transformers):
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
pip install verbatim-core[model]
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Environment
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
export OPENAI_API_KEY=your_api_key_here
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Full RAG System
|
|
59
|
+
|
|
60
|
+
For the complete RAG pipeline with vector indexing, embeddings, and document processing, install the full package:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
pip install verbatim-rag
|
|
64
|
+
```
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "verbatim-core"
|
|
7
|
+
version = "0.1.9"
|
|
8
|
+
description = "Lightweight verbatim span extraction -- the RAG-agnostic core of verbatim-rag"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = {text = "MIT"}
|
|
12
|
+
authors = [
|
|
13
|
+
{name = "Adam Kovacs", email = "kovacs@krlabs.eu"},
|
|
14
|
+
]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Programming Language :: Python :: 3",
|
|
17
|
+
"License :: OSI Approved :: MIT License",
|
|
18
|
+
"Operating System :: OS Independent",
|
|
19
|
+
]
|
|
20
|
+
dependencies = [
|
|
21
|
+
"openai>=1.3.0",
|
|
22
|
+
"pydantic>=2.0.0",
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
[project.optional-dependencies]
|
|
26
|
+
model = [
|
|
27
|
+
"torch>=2.6.0",
|
|
28
|
+
"transformers==4.53.3",
|
|
29
|
+
"scikit-learn==1.6.1",
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
[project.urls]
|
|
33
|
+
"Homepage" = "https://github.com/krlabsorg/verbatim-rag"
|
|
34
|
+
"Bug Tracker" = "https://github.com/krlabsorg/verbatim-rag/issues"
|
|
35
|
+
|
|
36
|
+
[project.scripts]
|
|
37
|
+
verbatim-enhance = "verbatim_core.cli:main"
|
|
38
|
+
|
|
39
|
+
[tool.hatch.build.targets.wheel]
|
|
40
|
+
packages = ["verbatim_core"]
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Lightweight, RAG-agnostic core for verbatim transformation.
|
|
3
|
+
|
|
4
|
+
This subpackage provides:
|
|
5
|
+
- VerbatimTransform: question + context -> cited, grounded answer (sync/async)
|
|
6
|
+
- RAGProvider (interface): minimal retrieval contract (no index/Milvus deps)
|
|
7
|
+
- UniversalDocument: simple context container
|
|
8
|
+
- @verbatim_enhance decorator: drop-in enhancement for existing RAG fns
|
|
9
|
+
- CLI: `verbatim-enhance` for batch processing JSON(L)
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from .enhance import verbatim_enhance
|
|
13
|
+
from .providers import RAGProvider
|
|
14
|
+
from .transform import VerbatimTransform, verbatim_query, verbatim_query_async
|
|
15
|
+
from .universal_document import UniversalDocument
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
"VerbatimTransform",
|
|
19
|
+
"verbatim_query",
|
|
20
|
+
"verbatim_query_async",
|
|
21
|
+
"RAGProvider",
|
|
22
|
+
"UniversalDocument",
|
|
23
|
+
"verbatim_enhance",
|
|
24
|
+
]
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import json
|
|
5
|
+
import sys
|
|
6
|
+
from typing import Any, Dict, Iterable, List
|
|
7
|
+
|
|
8
|
+
from .transform import VerbatimTransform
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _iter_records(fp) -> Iterable[Dict[str, Any]]:
|
|
12
|
+
"""Yield JSON objects from a file path or stdin. Supports JSONL or JSON array."""
|
|
13
|
+
try:
|
|
14
|
+
data = fp.read()
|
|
15
|
+
except Exception as e:
|
|
16
|
+
print(f"Error reading input: {e}", file=sys.stderr)
|
|
17
|
+
return
|
|
18
|
+
data = (data or "").strip()
|
|
19
|
+
if not data:
|
|
20
|
+
return
|
|
21
|
+
# Try JSONL first
|
|
22
|
+
if "\n" in data and not data.startswith("["):
|
|
23
|
+
for line in data.splitlines():
|
|
24
|
+
line = line.strip()
|
|
25
|
+
if not line:
|
|
26
|
+
continue
|
|
27
|
+
try:
|
|
28
|
+
yield json.loads(line)
|
|
29
|
+
except Exception as e:
|
|
30
|
+
print(f"Skipping malformed JSONL line: {e}", file=sys.stderr)
|
|
31
|
+
else:
|
|
32
|
+
try:
|
|
33
|
+
obj = json.loads(data)
|
|
34
|
+
except Exception as e:
|
|
35
|
+
print(f"Malformed JSON input: {e}", file=sys.stderr)
|
|
36
|
+
return
|
|
37
|
+
if isinstance(obj, list):
|
|
38
|
+
for item in obj:
|
|
39
|
+
yield item
|
|
40
|
+
else:
|
|
41
|
+
yield obj
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def main(argv: List[str] | None = None) -> int:
|
|
45
|
+
p = argparse.ArgumentParser(description="Verbatim enhance JSON(L) records")
|
|
46
|
+
p.add_argument("--input", "-i", help="Input file (JSON or JSONL). Defaults to stdin.")
|
|
47
|
+
p.add_argument("--output", "-o", help="Output file (JSONL). Defaults to stdout.")
|
|
48
|
+
p.add_argument("--max-spans", type=int, default=5, help="Max display spans")
|
|
49
|
+
args = p.parse_args(argv)
|
|
50
|
+
|
|
51
|
+
fin = open(args.input, "r", encoding="utf-8") if args.input else sys.stdin
|
|
52
|
+
fout = open(args.output, "w", encoding="utf-8") if args.output else sys.stdout
|
|
53
|
+
|
|
54
|
+
vt = VerbatimTransform(max_display_spans=args.max_spans)
|
|
55
|
+
|
|
56
|
+
for rec in _iter_records(fin) or []:
|
|
57
|
+
question = rec.get("question") or ""
|
|
58
|
+
context = rec.get("context") or rec.get("sources") or []
|
|
59
|
+
answer = rec.get("answer")
|
|
60
|
+
resp = vt.transform(question=question, context=context, answer=answer)
|
|
61
|
+
fout.write(json.dumps(resp.model_dump()) + "\n")
|
|
62
|
+
|
|
63
|
+
if fin is not sys.stdin:
|
|
64
|
+
fin.close()
|
|
65
|
+
if fout is not sys.stdout:
|
|
66
|
+
fout.close()
|
|
67
|
+
return 0
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
if __name__ == "__main__":
|
|
71
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from functools import wraps
|
|
4
|
+
from typing import Any, Callable, Dict, Iterable, Mapping
|
|
5
|
+
|
|
6
|
+
from .transform import VerbatimTransform
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _to_context_dicts(obj: Any) -> list[dict]:
|
|
10
|
+
"""Best-effort conversion of sources/context to context dicts."""
|
|
11
|
+
ctx: list[dict] = []
|
|
12
|
+
if obj is None:
|
|
13
|
+
return ctx
|
|
14
|
+
if isinstance(obj, Mapping):
|
|
15
|
+
# Single dict
|
|
16
|
+
data = dict(obj)
|
|
17
|
+
if "content" in data or "text" in data:
|
|
18
|
+
ctx.append(
|
|
19
|
+
{
|
|
20
|
+
"content": data.get("content") or data.get("text"),
|
|
21
|
+
"title": data.get("title", ""),
|
|
22
|
+
"source": data.get("source", ""),
|
|
23
|
+
"metadata": data.get("metadata") or {},
|
|
24
|
+
}
|
|
25
|
+
)
|
|
26
|
+
return ctx
|
|
27
|
+
if isinstance(obj, (list, tuple)):
|
|
28
|
+
for item in obj:
|
|
29
|
+
ctx.extend(_to_context_dicts(item))
|
|
30
|
+
return ctx
|
|
31
|
+
# Fallback: treat as raw text
|
|
32
|
+
if isinstance(obj, str) and obj.strip():
|
|
33
|
+
ctx.append({"content": obj})
|
|
34
|
+
return ctx
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def verbatim_enhance(
|
|
38
|
+
max_display_spans: int = 5,
|
|
39
|
+
) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
|
|
40
|
+
"""Decorator to enhance existing RAG functions with verbatim transformation.
|
|
41
|
+
|
|
42
|
+
The wrapped function can return:
|
|
43
|
+
- dict with keys: 'question'?, 'answer'?, 'context' or 'sources'
|
|
44
|
+
- tuple: (answer, sources)
|
|
45
|
+
- or just 'sources' (context list/dict) if no answer
|
|
46
|
+
Currently, provided 'answer' is ignored; verbatim answer is derived from context.
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
def decorator(fn: Callable[..., Any]) -> Callable[..., Any]:
|
|
50
|
+
@wraps(fn)
|
|
51
|
+
def wrapper(*args, **kwargs):
|
|
52
|
+
result = fn(*args, **kwargs)
|
|
53
|
+
question = kwargs.get("question") or (args[0] if args else "")
|
|
54
|
+
answer = None
|
|
55
|
+
context: Iterable[Dict[str, Any]] = []
|
|
56
|
+
|
|
57
|
+
if isinstance(result, dict):
|
|
58
|
+
answer = result.get("answer")
|
|
59
|
+
context = result.get("context") or result.get("sources") or []
|
|
60
|
+
elif isinstance(result, (list, tuple)):
|
|
61
|
+
if len(result) == 2:
|
|
62
|
+
answer, context = result
|
|
63
|
+
else:
|
|
64
|
+
context = result
|
|
65
|
+
else:
|
|
66
|
+
context = result
|
|
67
|
+
|
|
68
|
+
context_dicts = _to_context_dicts(context)
|
|
69
|
+
|
|
70
|
+
vt = VerbatimTransform(max_display_spans=max_display_spans)
|
|
71
|
+
resp = vt.transform(question=question or "", context=context_dicts, answer=answer)
|
|
72
|
+
return resp
|
|
73
|
+
|
|
74
|
+
return wrapper
|
|
75
|
+
|
|
76
|
+
return decorator
|