longloom 1.0.0__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.
- longloom-1.0.0/LICENSE +21 -0
- longloom-1.0.0/PKG-INFO +358 -0
- longloom-1.0.0/README.md +289 -0
- longloom-1.0.0/longloom/__init__.py +87 -0
- longloom-1.0.0/longloom/chunks.py +109 -0
- longloom-1.0.0/longloom/factory.py +245 -0
- longloom-1.0.0/longloom/graph.py +220 -0
- longloom-1.0.0/longloom/llm.py +185 -0
- longloom-1.0.0/longloom/nvidia.py +166 -0
- longloom-1.0.0/longloom/processor.py +114 -0
- longloom-1.0.0/longloom/prompts.py +103 -0
- longloom-1.0.0/longloom/py.typed +0 -0
- longloom-1.0.0/longloom/stores/__init__.py +29 -0
- longloom-1.0.0/longloom/stores/base.py +139 -0
- longloom-1.0.0/longloom/stores/jsonfile.py +115 -0
- longloom-1.0.0/longloom/stores/memory.py +63 -0
- longloom-1.0.0/longloom/stores/mongo.py +170 -0
- longloom-1.0.0/longloom/stores/naming.py +69 -0
- longloom-1.0.0/longloom/stores/sql.py +372 -0
- longloom-1.0.0/longloom/stores/sqlite.py +47 -0
- longloom-1.0.0/longloom/stores/supabase.py +170 -0
- longloom-1.0.0/longloom/tokens.py +67 -0
- longloom-1.0.0/longloom.egg-info/PKG-INFO +358 -0
- longloom-1.0.0/longloom.egg-info/SOURCES.txt +37 -0
- longloom-1.0.0/longloom.egg-info/dependency_links.txt +1 -0
- longloom-1.0.0/longloom.egg-info/requires.txt +52 -0
- longloom-1.0.0/longloom.egg-info/top_level.txt +1 -0
- longloom-1.0.0/pyproject.toml +86 -0
- longloom-1.0.0/setup.cfg +4 -0
- longloom-1.0.0/tests/test_db_integration.py +204 -0
- longloom-1.0.0/tests/test_e2e.py +117 -0
- longloom-1.0.0/tests/test_embed_existing.py +272 -0
- longloom-1.0.0/tests/test_factory.py +126 -0
- longloom-1.0.0/tests/test_llm_adapter.py +52 -0
- longloom-1.0.0/tests/test_production_hardening.py +145 -0
- longloom-1.0.0/tests/test_real_nvidia.py +86 -0
- longloom-1.0.0/tests/test_real_openai.py +71 -0
- longloom-1.0.0/tests/test_stores.py +78 -0
- longloom-1.0.0/tests/test_unstructured_no_loss.py +118 -0
longloom-1.0.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 LongLoom Maintainers
|
|
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.
|
longloom-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: longloom
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Weave documents of any length through LLM map/merge/repair — any LLM, any storage, measured no-loss
|
|
5
|
+
Author: LongLoom Maintainers
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/sumitnemade/longloom
|
|
8
|
+
Project-URL: Documentation, https://github.com/sumitnemade/longloom#readme
|
|
9
|
+
Project-URL: Changelog, https://github.com/sumitnemade/longloom/blob/main/CHANGELOG.md
|
|
10
|
+
Project-URL: Issues, https://github.com/sumitnemade/longloom/issues
|
|
11
|
+
Keywords: llm,long-context,map-reduce,langgraph,chunking,rag,summarization,nvidia,openai,postgresql,mongodb
|
|
12
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
22
|
+
Classifier: Topic :: Text Processing :: General
|
|
23
|
+
Classifier: Typing :: Typed
|
|
24
|
+
Requires-Python: >=3.10
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
License-File: LICENSE
|
|
27
|
+
Requires-Dist: langgraph>=0.2.0
|
|
28
|
+
Requires-Dist: langchain-core>=0.3.0
|
|
29
|
+
Provides-Extra: tiktoken
|
|
30
|
+
Requires-Dist: tiktoken>=0.7.0; extra == "tiktoken"
|
|
31
|
+
Provides-Extra: nvidia
|
|
32
|
+
Requires-Dist: requests>=2.31; extra == "nvidia"
|
|
33
|
+
Provides-Extra: openai
|
|
34
|
+
Requires-Dist: langchain-openai>=0.2.0; extra == "openai"
|
|
35
|
+
Provides-Extra: sql
|
|
36
|
+
Requires-Dist: sqlalchemy>=2.0; extra == "sql"
|
|
37
|
+
Provides-Extra: postgres
|
|
38
|
+
Requires-Dist: sqlalchemy>=2.0; extra == "postgres"
|
|
39
|
+
Requires-Dist: psycopg2-binary>=2.9; extra == "postgres"
|
|
40
|
+
Provides-Extra: mysql
|
|
41
|
+
Requires-Dist: sqlalchemy>=2.0; extra == "mysql"
|
|
42
|
+
Requires-Dist: pymysql>=1.1; extra == "mysql"
|
|
43
|
+
Provides-Extra: mongodb
|
|
44
|
+
Requires-Dist: pymongo>=4.6; extra == "mongodb"
|
|
45
|
+
Provides-Extra: supabase
|
|
46
|
+
Requires-Dist: supabase>=2.4; extra == "supabase"
|
|
47
|
+
Provides-Extra: databases
|
|
48
|
+
Requires-Dist: sqlalchemy>=2.0; extra == "databases"
|
|
49
|
+
Requires-Dist: psycopg2-binary>=2.9; extra == "databases"
|
|
50
|
+
Requires-Dist: pymysql>=1.1; extra == "databases"
|
|
51
|
+
Requires-Dist: pymongo>=4.6; extra == "databases"
|
|
52
|
+
Requires-Dist: supabase>=2.4; extra == "databases"
|
|
53
|
+
Provides-Extra: all
|
|
54
|
+
Requires-Dist: tiktoken>=0.7.0; extra == "all"
|
|
55
|
+
Requires-Dist: requests>=2.31; extra == "all"
|
|
56
|
+
Requires-Dist: langchain-openai>=0.2.0; extra == "all"
|
|
57
|
+
Requires-Dist: sqlalchemy>=2.0; extra == "all"
|
|
58
|
+
Requires-Dist: psycopg2-binary>=2.9; extra == "all"
|
|
59
|
+
Requires-Dist: pymysql>=1.1; extra == "all"
|
|
60
|
+
Requires-Dist: pymongo>=4.6; extra == "all"
|
|
61
|
+
Provides-Extra: dev
|
|
62
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
63
|
+
Requires-Dist: tiktoken>=0.7.0; extra == "dev"
|
|
64
|
+
Requires-Dist: pymongo>=4.6; extra == "dev"
|
|
65
|
+
Requires-Dist: sqlalchemy>=2.0; extra == "dev"
|
|
66
|
+
Requires-Dist: psycopg2-binary>=2.9; extra == "dev"
|
|
67
|
+
Requires-Dist: pymysql>=1.1; extra == "dev"
|
|
68
|
+
Dynamic: license-file
|
|
69
|
+
|
|
70
|
+
<div align="center">
|
|
71
|
+
|
|
72
|
+
# 🧶 LongLoom
|
|
73
|
+
|
|
74
|
+
**Weave documents of any length through LLM map/merge/repair — without losing a thread.**
|
|
75
|
+
|
|
76
|
+
[](https://pypi.org/project/longloom/)
|
|
77
|
+
[](https://pypi.org/project/longloom/)
|
|
78
|
+
[](https://github.com/sumitnemade/longloom/actions/workflows/ci.yml)
|
|
79
|
+
[](LICENSE)
|
|
80
|
+
[](longloom/py.typed)
|
|
81
|
+
[](#test-it-yourself)
|
|
82
|
+
|
|
83
|
+
*Any LLM · Any storage · Measured no-loss · 2 core dependencies*
|
|
84
|
+
|
|
85
|
+
</div>
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
Your model accepts 8K, 128K, or 200K tokens — your documents don't care.
|
|
90
|
+
Annual reports, BRDs, legal contracts, log archives, codebases: they all blow
|
|
91
|
+
past the window, and naive truncation silently drops the facts you need.
|
|
92
|
+
|
|
93
|
+
**LongLoom** splits the problem the way humans do — read in chunks, extract,
|
|
94
|
+
combine, condense — as a LangGraph pipeline with **measured** information
|
|
95
|
+
preservation, over **any LLM** you already use and **any storage** you already run.
|
|
96
|
+
|
|
97
|
+
```text
|
|
98
|
+
┌───────────────┐
|
|
99
|
+
big document ─► CHUNK + STORE ─► every byte persisted (SQLite / Postgres / Mongo / …)
|
|
100
|
+
└───────┬───────┘
|
|
101
|
+
▼ MAP one LLM call per chunk — verbatim or summarizing
|
|
102
|
+
┌───────────────┐
|
|
103
|
+
│ map results │
|
|
104
|
+
└───────┬───────┘
|
|
105
|
+
▼ MERGE window-sized hierarchical combine calls
|
|
106
|
+
┌───────────────┐ (scales past any single context window)
|
|
107
|
+
│ merged draft │
|
|
108
|
+
└───────┬───────┘
|
|
109
|
+
▼ REPAIR bounded condensation loop, only if still too big
|
|
110
|
+
┌───────────────┐
|
|
111
|
+
│ final result │ + verification: every fact accounted for
|
|
112
|
+
└───────────────┘
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Install
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
pip install "longloom[all]" # batteries included
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
or just the core (two dependencies: `langgraph` + `langchain-core`):
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
pip install longloom
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Python ≥ 3.10 · Optional extras: `[tiktoken]` `[nvidia]` `[openai]` `[postgres]` `[mysql]` `[mongodb]` `[supabase]` `[databases]`
|
|
128
|
+
|
|
129
|
+
## 60-second quickstart
|
|
130
|
+
|
|
131
|
+
```python
|
|
132
|
+
import os
|
|
133
|
+
from longloom import MapReduceProcessor, SQLiteContextStore, init_llm
|
|
134
|
+
|
|
135
|
+
processor = MapReduceProcessor(
|
|
136
|
+
llm=init_llm("nvidia", "nvidia/nemotron-3-ultra-550b-a55b",
|
|
137
|
+
api_key=os.environ["NVIDIA_API_KEY"], # your app owns the key
|
|
138
|
+
thinking=False),
|
|
139
|
+
store=SQLiteContextStore("ctx.db"),
|
|
140
|
+
max_chunk_tokens=3000,
|
|
141
|
+
max_context_tokens=8192, # your model's real window
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
result = processor.process(
|
|
145
|
+
annual_report, # 50K tokens? 500K? doesn't matter
|
|
146
|
+
task="Summarize the key findings",
|
|
147
|
+
)
|
|
148
|
+
print(result["final_result"])
|
|
149
|
+
print(f"{result['num_chunks']} chunks · {result['total_input_tokens']:,} tokens · "
|
|
150
|
+
f"{result['repair_attempts']} repairs")
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Every run is **persisted and queryable** afterwards:
|
|
154
|
+
|
|
155
|
+
```python
|
|
156
|
+
processor.search_session(result["session_id"], "revenue") # which chunks mention it
|
|
157
|
+
processor.retrieve_chunks(result["session_id"]) # original text, byte-faithful
|
|
158
|
+
processor.get_session_info(result["session_id"]) # chunk/token stats
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## When facts must not be lost
|
|
162
|
+
|
|
163
|
+
Summarizing prompts measurably drop facts (we measured 2–10% on messy
|
|
164
|
+
documents — different facts every run). When you need *everything*:
|
|
165
|
+
|
|
166
|
+
```python
|
|
167
|
+
from longloom import PromptTemplates
|
|
168
|
+
|
|
169
|
+
processor = MapReduceProcessor(
|
|
170
|
+
llm=llm,
|
|
171
|
+
store=store,
|
|
172
|
+
prompts=PromptTemplates.for_extraction(), # verbatim line-copy mode
|
|
173
|
+
...
|
|
174
|
+
)
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
**Measured result: 100% fact preservation (41/41 planted facts in a log/JSON/
|
|
178
|
+
email/table/document mix — stable across repeated real-LLM runs on NVIDIA and
|
|
179
|
+
OpenAI).** The trade-off is output length: extraction copies, it doesn't
|
|
180
|
+
compress. Summaries → `DEFAULT_PROMPTS`; completeness → `for_extraction()`.
|
|
181
|
+
|
|
182
|
+
## Any LLM
|
|
183
|
+
|
|
184
|
+
```python
|
|
185
|
+
init_llm("nvidia", "nvidia/nemotron-3-ultra-550b-a55b",
|
|
186
|
+
api_key=os.environ["NVIDIA_API_KEY"], thinking=False)
|
|
187
|
+
init_llm("openai", "gpt-4o-mini", api_key=os.environ["OPENAI_API_KEY"])
|
|
188
|
+
init_llm("anthropic", "claude-sonnet-4-20250514", api_key=os.environ["ANTHROPIC_API_KEY"])
|
|
189
|
+
init_llm("groq" | "mistral" | "deepseek" | "together", ..., api_key=...)
|
|
190
|
+
init_llm("ollama", "llama3.1") # no key needed
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
> 🔐 **Keys are constructor arguments, never environment reads.** LongLoom
|
|
194
|
+
> itself never touches `os.environ` for credentials — your application reads
|
|
195
|
+
> the secret (vault, env, wherever) and passes it explicitly. Explicit beats
|
|
196
|
+
> implicit when credentials are involved.
|
|
197
|
+
|
|
198
|
+
…or bring your own: any LangChain `BaseChatModel`, or a bare function —
|
|
199
|
+
the minimum contract is `str → str`:
|
|
200
|
+
|
|
201
|
+
```python
|
|
202
|
+
processor = MapReduceProcessor(llm=lambda prompt: my_gateway(prompt), store=store)
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
Provider quirks are handled for you: NVIDIA reasoning models get real model
|
|
206
|
+
IDs, a `thinking` toggle, and a `content=None` starvation guard; OpenAI
|
|
207
|
+
`extra_body` and temperature restrictions are abstracted away.
|
|
208
|
+
|
|
209
|
+
## Any storage
|
|
210
|
+
|
|
211
|
+
```python
|
|
212
|
+
from longloom import create_store
|
|
213
|
+
|
|
214
|
+
store = create_store("sqlite:///ctx.db") # default, single file
|
|
215
|
+
store = create_store("postgresql://user:pw@host/longloom")
|
|
216
|
+
store = create_store("mysql://user:pw@host/longloom")
|
|
217
|
+
store = create_store("mongodb://host:27017/longloom")
|
|
218
|
+
store = create_store("https://myteam.supabase.co") # + SUPABASE_KEY
|
|
219
|
+
store = create_store("json://data") # human-readable JSONL
|
|
220
|
+
store = create_store("memory://") # ephemeral
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
| Backend | Extra | Notes |
|
|
224
|
+
|---|---|---|
|
|
225
|
+
| In-memory | — | zero-dependency default |
|
|
226
|
+
| SQLite | — (stdlib) | inline content or file-backed |
|
|
227
|
+
| JSONL files | — | one readable file per session |
|
|
228
|
+
| **PostgreSQL** | `[postgres]` | SQLAlchemy or psycopg2/psycopg |
|
|
229
|
+
| **MySQL / MariaDB** | `[mysql]` | SQLAlchemy or PyMySQL |
|
|
230
|
+
| **MongoDB** | `[mongodb]` | server-side regex search, `$group` stats |
|
|
231
|
+
| **Supabase** | `[supabase]` | REST via supabase-py — no DB connection needed |
|
|
232
|
+
| **Your own** | — | subclass `BaseContextStore` — implement four methods |
|
|
233
|
+
|
|
234
|
+
All backends pass the identical contract suite (round-trip, filters, budget,
|
|
235
|
+
upsert idempotency, search, stats, delete).
|
|
236
|
+
|
|
237
|
+
## Embedding in existing databases
|
|
238
|
+
|
|
239
|
+
No dedicated database needed — LongLoom lives inside **your** tables with
|
|
240
|
+
**your** column names, next to your application's own data:
|
|
241
|
+
|
|
242
|
+
```python
|
|
243
|
+
store = create_store(
|
|
244
|
+
"postgresql://app@host/mydb", # the database you already run
|
|
245
|
+
table="app_document_chunks", # your table
|
|
246
|
+
columns={ # your column names
|
|
247
|
+
"id": "chunk_pk", "content": "text_body",
|
|
248
|
+
"metadata": "meta_json", "session_id": "sess",
|
|
249
|
+
},
|
|
250
|
+
namespace="support-agent", # several apps share one table safely
|
|
251
|
+
init_schema=False, # your migrations own the DDL
|
|
252
|
+
)
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
| Option | Meaning | Supported by |
|
|
256
|
+
|---|---|---|
|
|
257
|
+
| `table=` | your table/collection name | SQL, MongoDB, Supabase |
|
|
258
|
+
| `columns=` | logical → physical field mapping | SQL, MongoDB, Supabase |
|
|
259
|
+
| `namespace=` + `namespace_field=` | discriminator so multiple apps share one table | SQL, MongoDB, Supabase |
|
|
260
|
+
| `init_schema=False` | your migrations own the DDL | SQL backends |
|
|
261
|
+
| `SqlContextStore.ddl()` / `table_sql(...)` | generate DDL matching your names, for your migration tooling | SQL, Supabase |
|
|
262
|
+
|
|
263
|
+
Same table, two apps, zero cross-talk — namespaces isolate sessions even with
|
|
264
|
+
identical session IDs. See `examples/17_embed_existing_database.py`.
|
|
265
|
+
|
|
266
|
+
## Built for production
|
|
267
|
+
|
|
268
|
+
| | |
|
|
269
|
+
|---|---|
|
|
270
|
+
| **Hierarchical merge** | Verbatim runs beyond one window still complete — merge calls are grouped and combined recursively (verified: a 45K-token extraction survives an 8K window). |
|
|
271
|
+
| **Retry + pacing** | Exponential backoff for 429/5xx/timeouts, optional min-interval between calls for long matrix runs. |
|
|
272
|
+
| **Bounded repair** | Oversized results condense with a capped attempt count — no infinite loops, no silent truncation. |
|
|
273
|
+
| **Token-count guardrails** | tiktoken exact or zero-dep estimator; chunk sizes clamped so map results are always combinable. |
|
|
274
|
+
| **Typed & versioned** | `py.typed`, semantic versioning, CHANGELOG, per-provider extras. |
|
|
275
|
+
|
|
276
|
+
### Honest limits
|
|
277
|
+
|
|
278
|
+
- Map/merge **aggregates**; it doesn't reason globally — cross-chunk conflicts
|
|
279
|
+
(two versions of the same figure) are preserved side by side, not reconciled.
|
|
280
|
+
- Final results don't cite source chunks (the store does — provenance output is
|
|
281
|
+
on the roadmap).
|
|
282
|
+
- The map phase is sequential; parallelize externally for very large corpora.
|
|
283
|
+
- tiktoken approximates non-OpenAI tokenizers — keep 2× headroom on chunk sizes.
|
|
284
|
+
|
|
285
|
+
## Examples — every claim is a runnable script
|
|
286
|
+
|
|
287
|
+
```bash
|
|
288
|
+
git clone https://github.com/sumitnemade/longloom && cd longloom
|
|
289
|
+
pip install -e ".[dev]"
|
|
290
|
+
export NVIDIA_API_KEY=... # or OPENAI_API_KEY=...
|
|
291
|
+
python examples/run_all.py # 17 examples; skips gracefully what's missing
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
| # | Example | One-liner |
|
|
295
|
+
|---|---|---|
|
|
296
|
+
| 01 | quickstart | minimal pipeline, zero setup |
|
|
297
|
+
| 02 | all storage backends | every store via one URL |
|
|
298
|
+
| 03–06 | NVIDIA · OpenAI · Anthropic · local Ollama | same workflow, four vendors |
|
|
299
|
+
| 07 | custom callable | a bare function as the LLM (routing pattern) |
|
|
300
|
+
| 08–11 | PostgreSQL · MySQL · MongoDB · Supabase | one example per database |
|
|
301
|
+
| 12 | custom backend | a working Redis store in ~40 lines |
|
|
302
|
+
| 13 | advanced | custom prompts, token counters, brace-safety |
|
|
303
|
+
| 14 | **NVIDIA × every database** | real Nemotron over 8 backends |
|
|
304
|
+
| 15 | **OpenAI × every database** | real GPT over 8 backends |
|
|
305
|
+
| 16 | **no-loss extraction** | 41/41 facts on a hostile document mix |
|
|
306
|
+
| 17 | **embed in existing database** | your table, your columns, namespaces |
|
|
307
|
+
| 18 | **enterprise gateway** | corporate LLM gateway + existing DB + tenant namespace |
|
|
308
|
+
|
|
309
|
+
## Test it yourself
|
|
310
|
+
|
|
311
|
+
```bash
|
|
312
|
+
pip install -e ".[dev]"
|
|
313
|
+
pytest tests/ -v # 50 tests; network-dependent ones self-skip
|
|
314
|
+
NVIDIA_API_KEY=... pytest tests/ -v # + real NVIDIA, Postgres, MySQL, MongoDB
|
|
315
|
+
OPENAI_API_KEY=... pytest tests/ -v # + real OpenAI end-to-end
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
## FAQ
|
|
319
|
+
|
|
320
|
+
**Do I need LangChain?** Only `langchain-core` (a lightweight dependency).
|
|
321
|
+
Your LLM can even be a plain function.
|
|
322
|
+
|
|
323
|
+
**What if my output is bigger than the window?** The repair loop condenses it,
|
|
324
|
+
and the merge stage itself groups results hierarchically — oversized inputs are
|
|
325
|
+
the library's job, not yours.
|
|
326
|
+
|
|
327
|
+
**Is my data sent anywhere?** Only to the LLM provider you configure. Storage
|
|
328
|
+
stays in whatever database *you* chose.
|
|
329
|
+
|
|
330
|
+
**Why not just use a 200K-token model?** Cost, latency, and attention decay.
|
|
331
|
+
Chunked extraction is cheaper per token and verifiably complete — and LongLoom
|
|
332
|
+
works with *any* window, so you're never locked to a model generation.
|
|
333
|
+
|
|
334
|
+
## Releasing (maintainers)
|
|
335
|
+
|
|
336
|
+
```bash
|
|
337
|
+
scripts/release.sh 1.2.0 --push # tests → bump → changelog → tag → CI publishes to PyPI
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
One-time setup:
|
|
341
|
+
1. Create the project on pypi.org, then **Trusted Publishing**: owner =
|
|
342
|
+
your GitHub username/org, repo = `longloom`, workflow = `release.yml`,
|
|
343
|
+
environment = `pypi` — no API tokens live in the repository.
|
|
344
|
+
2. Push the tag (`--push`) — the release workflow builds, checks metadata,
|
|
345
|
+
verifies tag↔version, publishes, and attaches artifacts to a GitHub release.
|
|
346
|
+
3. Optional local path: `scripts/release.sh 1.2.0 --publish` with
|
|
347
|
+
`PYPI_TOKEN` set (TestPyPI: `--testpypi` with `TESTPYPI_TOKEN`).
|
|
348
|
+
|
|
349
|
+
## Contributing
|
|
350
|
+
|
|
351
|
+
Issues and PRs welcome — see [CONTRIBUTING.md](CONTRIBUTING.md) for setup and
|
|
352
|
+
conventions (every fix ships with a test; honest verification is the house
|
|
353
|
+
style). By participating you agree to the
|
|
354
|
+
[Code of Conduct](CODE_OF_CONDUCT.md).
|
|
355
|
+
|
|
356
|
+
## License
|
|
357
|
+
|
|
358
|
+
[MIT](LICENSE) — © The LongLoom Maintainers
|
longloom-1.0.0/README.md
ADDED
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# 🧶 LongLoom
|
|
4
|
+
|
|
5
|
+
**Weave documents of any length through LLM map/merge/repair — without losing a thread.**
|
|
6
|
+
|
|
7
|
+
[](https://pypi.org/project/longloom/)
|
|
8
|
+
[](https://pypi.org/project/longloom/)
|
|
9
|
+
[](https://github.com/sumitnemade/longloom/actions/workflows/ci.yml)
|
|
10
|
+
[](LICENSE)
|
|
11
|
+
[](longloom/py.typed)
|
|
12
|
+
[](#test-it-yourself)
|
|
13
|
+
|
|
14
|
+
*Any LLM · Any storage · Measured no-loss · 2 core dependencies*
|
|
15
|
+
|
|
16
|
+
</div>
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
Your model accepts 8K, 128K, or 200K tokens — your documents don't care.
|
|
21
|
+
Annual reports, BRDs, legal contracts, log archives, codebases: they all blow
|
|
22
|
+
past the window, and naive truncation silently drops the facts you need.
|
|
23
|
+
|
|
24
|
+
**LongLoom** splits the problem the way humans do — read in chunks, extract,
|
|
25
|
+
combine, condense — as a LangGraph pipeline with **measured** information
|
|
26
|
+
preservation, over **any LLM** you already use and **any storage** you already run.
|
|
27
|
+
|
|
28
|
+
```text
|
|
29
|
+
┌───────────────┐
|
|
30
|
+
big document ─► CHUNK + STORE ─► every byte persisted (SQLite / Postgres / Mongo / …)
|
|
31
|
+
└───────┬───────┘
|
|
32
|
+
▼ MAP one LLM call per chunk — verbatim or summarizing
|
|
33
|
+
┌───────────────┐
|
|
34
|
+
│ map results │
|
|
35
|
+
└───────┬───────┘
|
|
36
|
+
▼ MERGE window-sized hierarchical combine calls
|
|
37
|
+
┌───────────────┐ (scales past any single context window)
|
|
38
|
+
│ merged draft │
|
|
39
|
+
└───────┬───────┘
|
|
40
|
+
▼ REPAIR bounded condensation loop, only if still too big
|
|
41
|
+
┌───────────────┐
|
|
42
|
+
│ final result │ + verification: every fact accounted for
|
|
43
|
+
└───────────────┘
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Install
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
pip install "longloom[all]" # batteries included
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
or just the core (two dependencies: `langgraph` + `langchain-core`):
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
pip install longloom
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Python ≥ 3.10 · Optional extras: `[tiktoken]` `[nvidia]` `[openai]` `[postgres]` `[mysql]` `[mongodb]` `[supabase]` `[databases]`
|
|
59
|
+
|
|
60
|
+
## 60-second quickstart
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
import os
|
|
64
|
+
from longloom import MapReduceProcessor, SQLiteContextStore, init_llm
|
|
65
|
+
|
|
66
|
+
processor = MapReduceProcessor(
|
|
67
|
+
llm=init_llm("nvidia", "nvidia/nemotron-3-ultra-550b-a55b",
|
|
68
|
+
api_key=os.environ["NVIDIA_API_KEY"], # your app owns the key
|
|
69
|
+
thinking=False),
|
|
70
|
+
store=SQLiteContextStore("ctx.db"),
|
|
71
|
+
max_chunk_tokens=3000,
|
|
72
|
+
max_context_tokens=8192, # your model's real window
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
result = processor.process(
|
|
76
|
+
annual_report, # 50K tokens? 500K? doesn't matter
|
|
77
|
+
task="Summarize the key findings",
|
|
78
|
+
)
|
|
79
|
+
print(result["final_result"])
|
|
80
|
+
print(f"{result['num_chunks']} chunks · {result['total_input_tokens']:,} tokens · "
|
|
81
|
+
f"{result['repair_attempts']} repairs")
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Every run is **persisted and queryable** afterwards:
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
processor.search_session(result["session_id"], "revenue") # which chunks mention it
|
|
88
|
+
processor.retrieve_chunks(result["session_id"]) # original text, byte-faithful
|
|
89
|
+
processor.get_session_info(result["session_id"]) # chunk/token stats
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## When facts must not be lost
|
|
93
|
+
|
|
94
|
+
Summarizing prompts measurably drop facts (we measured 2–10% on messy
|
|
95
|
+
documents — different facts every run). When you need *everything*:
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
from longloom import PromptTemplates
|
|
99
|
+
|
|
100
|
+
processor = MapReduceProcessor(
|
|
101
|
+
llm=llm,
|
|
102
|
+
store=store,
|
|
103
|
+
prompts=PromptTemplates.for_extraction(), # verbatim line-copy mode
|
|
104
|
+
...
|
|
105
|
+
)
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
**Measured result: 100% fact preservation (41/41 planted facts in a log/JSON/
|
|
109
|
+
email/table/document mix — stable across repeated real-LLM runs on NVIDIA and
|
|
110
|
+
OpenAI).** The trade-off is output length: extraction copies, it doesn't
|
|
111
|
+
compress. Summaries → `DEFAULT_PROMPTS`; completeness → `for_extraction()`.
|
|
112
|
+
|
|
113
|
+
## Any LLM
|
|
114
|
+
|
|
115
|
+
```python
|
|
116
|
+
init_llm("nvidia", "nvidia/nemotron-3-ultra-550b-a55b",
|
|
117
|
+
api_key=os.environ["NVIDIA_API_KEY"], thinking=False)
|
|
118
|
+
init_llm("openai", "gpt-4o-mini", api_key=os.environ["OPENAI_API_KEY"])
|
|
119
|
+
init_llm("anthropic", "claude-sonnet-4-20250514", api_key=os.environ["ANTHROPIC_API_KEY"])
|
|
120
|
+
init_llm("groq" | "mistral" | "deepseek" | "together", ..., api_key=...)
|
|
121
|
+
init_llm("ollama", "llama3.1") # no key needed
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
> 🔐 **Keys are constructor arguments, never environment reads.** LongLoom
|
|
125
|
+
> itself never touches `os.environ` for credentials — your application reads
|
|
126
|
+
> the secret (vault, env, wherever) and passes it explicitly. Explicit beats
|
|
127
|
+
> implicit when credentials are involved.
|
|
128
|
+
|
|
129
|
+
…or bring your own: any LangChain `BaseChatModel`, or a bare function —
|
|
130
|
+
the minimum contract is `str → str`:
|
|
131
|
+
|
|
132
|
+
```python
|
|
133
|
+
processor = MapReduceProcessor(llm=lambda prompt: my_gateway(prompt), store=store)
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Provider quirks are handled for you: NVIDIA reasoning models get real model
|
|
137
|
+
IDs, a `thinking` toggle, and a `content=None` starvation guard; OpenAI
|
|
138
|
+
`extra_body` and temperature restrictions are abstracted away.
|
|
139
|
+
|
|
140
|
+
## Any storage
|
|
141
|
+
|
|
142
|
+
```python
|
|
143
|
+
from longloom import create_store
|
|
144
|
+
|
|
145
|
+
store = create_store("sqlite:///ctx.db") # default, single file
|
|
146
|
+
store = create_store("postgresql://user:pw@host/longloom")
|
|
147
|
+
store = create_store("mysql://user:pw@host/longloom")
|
|
148
|
+
store = create_store("mongodb://host:27017/longloom")
|
|
149
|
+
store = create_store("https://myteam.supabase.co") # + SUPABASE_KEY
|
|
150
|
+
store = create_store("json://data") # human-readable JSONL
|
|
151
|
+
store = create_store("memory://") # ephemeral
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
| Backend | Extra | Notes |
|
|
155
|
+
|---|---|---|
|
|
156
|
+
| In-memory | — | zero-dependency default |
|
|
157
|
+
| SQLite | — (stdlib) | inline content or file-backed |
|
|
158
|
+
| JSONL files | — | one readable file per session |
|
|
159
|
+
| **PostgreSQL** | `[postgres]` | SQLAlchemy or psycopg2/psycopg |
|
|
160
|
+
| **MySQL / MariaDB** | `[mysql]` | SQLAlchemy or PyMySQL |
|
|
161
|
+
| **MongoDB** | `[mongodb]` | server-side regex search, `$group` stats |
|
|
162
|
+
| **Supabase** | `[supabase]` | REST via supabase-py — no DB connection needed |
|
|
163
|
+
| **Your own** | — | subclass `BaseContextStore` — implement four methods |
|
|
164
|
+
|
|
165
|
+
All backends pass the identical contract suite (round-trip, filters, budget,
|
|
166
|
+
upsert idempotency, search, stats, delete).
|
|
167
|
+
|
|
168
|
+
## Embedding in existing databases
|
|
169
|
+
|
|
170
|
+
No dedicated database needed — LongLoom lives inside **your** tables with
|
|
171
|
+
**your** column names, next to your application's own data:
|
|
172
|
+
|
|
173
|
+
```python
|
|
174
|
+
store = create_store(
|
|
175
|
+
"postgresql://app@host/mydb", # the database you already run
|
|
176
|
+
table="app_document_chunks", # your table
|
|
177
|
+
columns={ # your column names
|
|
178
|
+
"id": "chunk_pk", "content": "text_body",
|
|
179
|
+
"metadata": "meta_json", "session_id": "sess",
|
|
180
|
+
},
|
|
181
|
+
namespace="support-agent", # several apps share one table safely
|
|
182
|
+
init_schema=False, # your migrations own the DDL
|
|
183
|
+
)
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
| Option | Meaning | Supported by |
|
|
187
|
+
|---|---|---|
|
|
188
|
+
| `table=` | your table/collection name | SQL, MongoDB, Supabase |
|
|
189
|
+
| `columns=` | logical → physical field mapping | SQL, MongoDB, Supabase |
|
|
190
|
+
| `namespace=` + `namespace_field=` | discriminator so multiple apps share one table | SQL, MongoDB, Supabase |
|
|
191
|
+
| `init_schema=False` | your migrations own the DDL | SQL backends |
|
|
192
|
+
| `SqlContextStore.ddl()` / `table_sql(...)` | generate DDL matching your names, for your migration tooling | SQL, Supabase |
|
|
193
|
+
|
|
194
|
+
Same table, two apps, zero cross-talk — namespaces isolate sessions even with
|
|
195
|
+
identical session IDs. See `examples/17_embed_existing_database.py`.
|
|
196
|
+
|
|
197
|
+
## Built for production
|
|
198
|
+
|
|
199
|
+
| | |
|
|
200
|
+
|---|---|
|
|
201
|
+
| **Hierarchical merge** | Verbatim runs beyond one window still complete — merge calls are grouped and combined recursively (verified: a 45K-token extraction survives an 8K window). |
|
|
202
|
+
| **Retry + pacing** | Exponential backoff for 429/5xx/timeouts, optional min-interval between calls for long matrix runs. |
|
|
203
|
+
| **Bounded repair** | Oversized results condense with a capped attempt count — no infinite loops, no silent truncation. |
|
|
204
|
+
| **Token-count guardrails** | tiktoken exact or zero-dep estimator; chunk sizes clamped so map results are always combinable. |
|
|
205
|
+
| **Typed & versioned** | `py.typed`, semantic versioning, CHANGELOG, per-provider extras. |
|
|
206
|
+
|
|
207
|
+
### Honest limits
|
|
208
|
+
|
|
209
|
+
- Map/merge **aggregates**; it doesn't reason globally — cross-chunk conflicts
|
|
210
|
+
(two versions of the same figure) are preserved side by side, not reconciled.
|
|
211
|
+
- Final results don't cite source chunks (the store does — provenance output is
|
|
212
|
+
on the roadmap).
|
|
213
|
+
- The map phase is sequential; parallelize externally for very large corpora.
|
|
214
|
+
- tiktoken approximates non-OpenAI tokenizers — keep 2× headroom on chunk sizes.
|
|
215
|
+
|
|
216
|
+
## Examples — every claim is a runnable script
|
|
217
|
+
|
|
218
|
+
```bash
|
|
219
|
+
git clone https://github.com/sumitnemade/longloom && cd longloom
|
|
220
|
+
pip install -e ".[dev]"
|
|
221
|
+
export NVIDIA_API_KEY=... # or OPENAI_API_KEY=...
|
|
222
|
+
python examples/run_all.py # 17 examples; skips gracefully what's missing
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
| # | Example | One-liner |
|
|
226
|
+
|---|---|---|
|
|
227
|
+
| 01 | quickstart | minimal pipeline, zero setup |
|
|
228
|
+
| 02 | all storage backends | every store via one URL |
|
|
229
|
+
| 03–06 | NVIDIA · OpenAI · Anthropic · local Ollama | same workflow, four vendors |
|
|
230
|
+
| 07 | custom callable | a bare function as the LLM (routing pattern) |
|
|
231
|
+
| 08–11 | PostgreSQL · MySQL · MongoDB · Supabase | one example per database |
|
|
232
|
+
| 12 | custom backend | a working Redis store in ~40 lines |
|
|
233
|
+
| 13 | advanced | custom prompts, token counters, brace-safety |
|
|
234
|
+
| 14 | **NVIDIA × every database** | real Nemotron over 8 backends |
|
|
235
|
+
| 15 | **OpenAI × every database** | real GPT over 8 backends |
|
|
236
|
+
| 16 | **no-loss extraction** | 41/41 facts on a hostile document mix |
|
|
237
|
+
| 17 | **embed in existing database** | your table, your columns, namespaces |
|
|
238
|
+
| 18 | **enterprise gateway** | corporate LLM gateway + existing DB + tenant namespace |
|
|
239
|
+
|
|
240
|
+
## Test it yourself
|
|
241
|
+
|
|
242
|
+
```bash
|
|
243
|
+
pip install -e ".[dev]"
|
|
244
|
+
pytest tests/ -v # 50 tests; network-dependent ones self-skip
|
|
245
|
+
NVIDIA_API_KEY=... pytest tests/ -v # + real NVIDIA, Postgres, MySQL, MongoDB
|
|
246
|
+
OPENAI_API_KEY=... pytest tests/ -v # + real OpenAI end-to-end
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
## FAQ
|
|
250
|
+
|
|
251
|
+
**Do I need LangChain?** Only `langchain-core` (a lightweight dependency).
|
|
252
|
+
Your LLM can even be a plain function.
|
|
253
|
+
|
|
254
|
+
**What if my output is bigger than the window?** The repair loop condenses it,
|
|
255
|
+
and the merge stage itself groups results hierarchically — oversized inputs are
|
|
256
|
+
the library's job, not yours.
|
|
257
|
+
|
|
258
|
+
**Is my data sent anywhere?** Only to the LLM provider you configure. Storage
|
|
259
|
+
stays in whatever database *you* chose.
|
|
260
|
+
|
|
261
|
+
**Why not just use a 200K-token model?** Cost, latency, and attention decay.
|
|
262
|
+
Chunked extraction is cheaper per token and verifiably complete — and LongLoom
|
|
263
|
+
works with *any* window, so you're never locked to a model generation.
|
|
264
|
+
|
|
265
|
+
## Releasing (maintainers)
|
|
266
|
+
|
|
267
|
+
```bash
|
|
268
|
+
scripts/release.sh 1.2.0 --push # tests → bump → changelog → tag → CI publishes to PyPI
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
One-time setup:
|
|
272
|
+
1. Create the project on pypi.org, then **Trusted Publishing**: owner =
|
|
273
|
+
your GitHub username/org, repo = `longloom`, workflow = `release.yml`,
|
|
274
|
+
environment = `pypi` — no API tokens live in the repository.
|
|
275
|
+
2. Push the tag (`--push`) — the release workflow builds, checks metadata,
|
|
276
|
+
verifies tag↔version, publishes, and attaches artifacts to a GitHub release.
|
|
277
|
+
3. Optional local path: `scripts/release.sh 1.2.0 --publish` with
|
|
278
|
+
`PYPI_TOKEN` set (TestPyPI: `--testpypi` with `TESTPYPI_TOKEN`).
|
|
279
|
+
|
|
280
|
+
## Contributing
|
|
281
|
+
|
|
282
|
+
Issues and PRs welcome — see [CONTRIBUTING.md](CONTRIBUTING.md) for setup and
|
|
283
|
+
conventions (every fix ships with a test; honest verification is the house
|
|
284
|
+
style). By participating you agree to the
|
|
285
|
+
[Code of Conduct](CODE_OF_CONDUCT.md).
|
|
286
|
+
|
|
287
|
+
## License
|
|
288
|
+
|
|
289
|
+
[MIT](LICENSE) — © The LongLoom Maintainers
|