loom-batch 0.1.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.
- loom_batch-0.1.0/LICENSE +21 -0
- loom_batch-0.1.0/PKG-INFO +377 -0
- loom_batch-0.1.0/README.md +339 -0
- loom_batch-0.1.0/loom/__init__.py +3 -0
- loom_batch-0.1.0/loom/core/__init__.py +0 -0
- loom_batch-0.1.0/loom/core/models.py +39 -0
- loom_batch-0.1.0/loom/core/orchestrator.py +357 -0
- loom_batch-0.1.0/loom/main.py +425 -0
- loom_batch-0.1.0/loom/providers/__init__.py +38 -0
- loom_batch-0.1.0/loom/providers/anthropic.py +63 -0
- loom_batch-0.1.0/loom/providers/anthropic_sync.py +36 -0
- loom_batch-0.1.0/loom/providers/base.py +26 -0
- loom_batch-0.1.0/loom/providers/google.py +96 -0
- loom_batch-0.1.0/loom/providers/google_sync.py +36 -0
- loom_batch-0.1.0/loom/providers/openai.py +92 -0
- loom_batch-0.1.0/loom/providers/openai_sync.py +24 -0
- loom_batch-0.1.0/loom/providers/openrouter_sync.py +32 -0
- loom_batch-0.1.0/loom/providers/sync_base.py +26 -0
- loom_batch-0.1.0/loom/utils/__init__.py +0 -0
- loom_batch-0.1.0/loom/utils/cache.py +77 -0
- loom_batch-0.1.0/loom/utils/converters.py +199 -0
- loom_batch-0.1.0/loom/utils/keys.py +37 -0
- loom_batch-0.1.0/loom/utils/storage.py +59 -0
- loom_batch-0.1.0/loom_batch.egg-info/PKG-INFO +377 -0
- loom_batch-0.1.0/loom_batch.egg-info/SOURCES.txt +33 -0
- loom_batch-0.1.0/loom_batch.egg-info/dependency_links.txt +1 -0
- loom_batch-0.1.0/loom_batch.egg-info/entry_points.txt +2 -0
- loom_batch-0.1.0/loom_batch.egg-info/requires.txt +13 -0
- loom_batch-0.1.0/loom_batch.egg-info/top_level.txt +1 -0
- loom_batch-0.1.0/pyproject.toml +58 -0
- loom_batch-0.1.0/setup.cfg +4 -0
- loom_batch-0.1.0/tests/test_cli_help.py +30 -0
- loom_batch-0.1.0/tests/test_converters.py +201 -0
- loom_batch-0.1.0/tests/test_keys.py +23 -0
- loom_batch-0.1.0/tests/test_storage.py +50 -0
loom_batch-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Jan Nehring
|
|
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,377 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: loom-batch
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Weave batch LLM jobs across OpenAI, Anthropic, and Google.
|
|
5
|
+
Author: Jan Nehring
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/jannehring/loom
|
|
8
|
+
Project-URL: Repository, https://github.com/jannehring/loom
|
|
9
|
+
Project-URL: Issues, https://github.com/jannehring/loom/issues
|
|
10
|
+
Keywords: llm,batch,openai,anthropic,gemini,cli
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Environment :: Console
|
|
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: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
21
|
+
Classifier: Topic :: Utilities
|
|
22
|
+
Requires-Python: >=3.10
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
License-File: LICENSE
|
|
25
|
+
Requires-Dist: typer>=0.12
|
|
26
|
+
Requires-Dist: pydantic>=2
|
|
27
|
+
Requires-Dist: pandas>=2
|
|
28
|
+
Requires-Dist: python-dotenv>=1
|
|
29
|
+
Requires-Dist: openai>=1.40
|
|
30
|
+
Requires-Dist: anthropic>=0.34
|
|
31
|
+
Requires-Dist: google-genai>=0.3
|
|
32
|
+
Requires-Dist: rich>=13
|
|
33
|
+
Provides-Extra: dev
|
|
34
|
+
Requires-Dist: pytest>=7; extra == "dev"
|
|
35
|
+
Requires-Dist: pytest-cov>=4; extra == "dev"
|
|
36
|
+
Requires-Dist: build>=1; extra == "dev"
|
|
37
|
+
Dynamic: license-file
|
|
38
|
+
|
|
39
|
+
# Loom: LLM Batch Processing Made Easy
|
|
40
|
+
|
|
41
|
+
> Weave LLM jobs across OpenAI, Anthropic, Google, and OpenRouter — in batch or live.
|
|
42
|
+
|
|
43
|
+
## 1. Introduction
|
|
44
|
+
|
|
45
|
+
Loom is a small Python CLI for running a dataset of prompts (JSON or CSV) through an LLM and merging the responses back into the original file. It speaks two modes:
|
|
46
|
+
|
|
47
|
+
- **Batch** (`loom run`, default): submits the dataset to the provider's batch API, persists the batch id locally, and later you call `loom fetch` to download and merge results. Cheap (50% off on OpenAI / Anthropic) but asynchronous — can take up to 24 hours.
|
|
48
|
+
- **Sequential** (`loom run --sync`): calls the chat-completion endpoint per prompt with a concurrent worker pool, writes the output file immediately, and uses an on-disk response cache.
|
|
49
|
+
|
|
50
|
+
It also ships a `loom tokens` command that uses each provider's token-counting API where available.
|
|
51
|
+
|
|
52
|
+
### Supported providers
|
|
53
|
+
|
|
54
|
+
| Provider | Batch (`loom run`) | Sequential (`loom run --sync`) | Token counter (`loom tokens`) |
|
|
55
|
+
| --- | --- | --- | --- |
|
|
56
|
+
| OpenAI | ✓ | ✓ | ✗ — no remote API |
|
|
57
|
+
| Anthropic | ✓ | ✓ | ✓ |
|
|
58
|
+
| Google (Gemini) | ✓ | ✓ | ✓ |
|
|
59
|
+
| OpenRouter | ✗ | ✓ | ✗ — no remote API |
|
|
60
|
+
|
|
61
|
+
### Table of contents
|
|
62
|
+
|
|
63
|
+
- [1. Introduction](#1-introduction)
|
|
64
|
+
- [2. Getting started](#2-getting-started)
|
|
65
|
+
- [Installation](#installation)
|
|
66
|
+
- [Preparing the data](#preparing-the-data)
|
|
67
|
+
- [Submit a batch request](#submit-a-batch-request)
|
|
68
|
+
- [3. Usage](#3-usage)
|
|
69
|
+
- [Command-line reference](#command-line-reference)
|
|
70
|
+
- [Batch vs sequential](#batch-vs-sequential)
|
|
71
|
+
- [Storing API keys](#storing-api-keys)
|
|
72
|
+
- [Caching](#caching)
|
|
73
|
+
- [Token counter](#token-counter)
|
|
74
|
+
- [Where Loom stores state](#where-loom-stores-state)
|
|
75
|
+
- [Troubleshooting](#troubleshooting)
|
|
76
|
+
- [4. Developer instructions](#4-developer-instructions)
|
|
77
|
+
- [5. License](#5-license)
|
|
78
|
+
|
|
79
|
+
## 2. Getting started
|
|
80
|
+
|
|
81
|
+
### Installation
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
pip install loom-batch
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
The PyPI package is `loom-batch` (the name `loom` was taken); the CLI command is `loom`.
|
|
88
|
+
|
|
89
|
+
From source, for hacking or running tests:
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
git clone https://github.com/jannehring/loom
|
|
93
|
+
cd loom
|
|
94
|
+
python -m venv .venv && source .venv/bin/activate
|
|
95
|
+
pip install -e ".[dev]"
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
> **Tip:** if you create the venv with `uv venv`, `pip` is not installed inside it. Use `uv pip install -e ".[dev]"` instead, or recreate the venv with stdlib `python -m venv` (see [Troubleshooting](#troubleshooting)).
|
|
99
|
+
|
|
100
|
+
### Preparing the data
|
|
101
|
+
|
|
102
|
+
Loom accepts two input formats — plain or **gzip-compressed** (`.json.gz`, `.csv.gz`). Compressed inputs are decompressed transparently; outputs are always written uncompressed (`.json` / `.csv`).
|
|
103
|
+
|
|
104
|
+
**JSON** — a list of `{id, prompt}` objects. The `id` is reused as the row key in the merged output.
|
|
105
|
+
|
|
106
|
+
```json
|
|
107
|
+
[
|
|
108
|
+
{"id": "task-001", "prompt": "Summarize the plot of Hamlet in one sentence."},
|
|
109
|
+
{"id": "task-002", "prompt": "Translate 'Good morning' to French."}
|
|
110
|
+
]
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
**CSV** — any schema; you tell Loom which column holds the prompt with `--col`. All original columns are preserved; a new `llm_response` column is appended.
|
|
114
|
+
|
|
115
|
+
```csv
|
|
116
|
+
id,text,priority
|
|
117
|
+
1,"Explain quantum physics in one paragraph",low
|
|
118
|
+
2,"Write a haiku about rust",high
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Submit a batch request
|
|
122
|
+
|
|
123
|
+
Minimal end-to-end run, passing the API key inline (see [Storing API keys](#storing-api-keys) for cleaner options):
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
loom run --file prompts.json \
|
|
127
|
+
--provider openai \
|
|
128
|
+
--model gpt-4o-mini \
|
|
129
|
+
--api-key sk-...
|
|
130
|
+
# -> Batch submitted. id=batch_abc123 provider=openai
|
|
131
|
+
|
|
132
|
+
# ...minutes or hours later...
|
|
133
|
+
loom fetch # --all is the default; fetches every pending batch
|
|
134
|
+
# -> Fabric complete. id=batch_abc123 -> prompts_results_openai_gpt-4o-mini.json
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
The output is written next to the input as `<name>_results_<provider>_<model>.<ext>`. Forward slashes and other unsafe characters in the model id are replaced with underscores (e.g. `openai/gpt-4o-mini` → `openai_gpt-4o-mini`). For gzipped inputs the `.gz` is dropped — `data.csv.gz` → `data_results_<provider>_<model>.csv`. Override the path entirely with `--output`.
|
|
138
|
+
|
|
139
|
+
## 3. Usage
|
|
140
|
+
|
|
141
|
+
### Command-line reference
|
|
142
|
+
|
|
143
|
+
#### `loom run`
|
|
144
|
+
|
|
145
|
+
Submit a dataset as a batch job (default) or run it synchronously with `--sync`.
|
|
146
|
+
|
|
147
|
+
| Flag | Default | Description |
|
|
148
|
+
| --- | --- | --- |
|
|
149
|
+
| `--file`, `-f` | _required_ | Input `.json`, `.csv`, `.json.gz`, or `.csv.gz`. |
|
|
150
|
+
| `--provider`, `-p` | _required_ | `openai`, `anthropic`, `google`, or `openrouter`. |
|
|
151
|
+
| `--model`, `-m` | _required_ | Provider-specific model id (e.g. `gpt-4o-mini`, `claude-3-5-sonnet-latest`, `gemini-2.0-flash`, `openai/gpt-4o-mini`). |
|
|
152
|
+
| `--col`, `-c` | — | Prompt column name (required for CSV). |
|
|
153
|
+
| `--api-key` | env / `.env` | Override the resolved API key for this run. |
|
|
154
|
+
| `--output`, `-o` | `<input>_results_<provider>_<model>.<ext>` | Custom output file path. |
|
|
155
|
+
| `--sync` / `--batch` | `--batch` | `--sync` calls the provider per prompt and writes the output immediately. `--batch` uses the provider's batch API. |
|
|
156
|
+
| `--workers`, `-w` | `8` | Concurrent workers in `--sync` mode. |
|
|
157
|
+
| `--no-cache` | off | Disable the on-disk response cache (`--sync` only). |
|
|
158
|
+
| `--force` | off | Overwrite an existing output file without prompting (`--sync` only). |
|
|
159
|
+
| `--with-meta` | off | Add `llm_provider` and `llm_model` columns (CSV) or fields (JSON) to the output, alongside `llm_response`. |
|
|
160
|
+
|
|
161
|
+
OpenRouter has no batch API; using `--provider openrouter` without `--sync` exits with a helpful error.
|
|
162
|
+
|
|
163
|
+
#### `loom fetch`
|
|
164
|
+
|
|
165
|
+
Poll the provider, download results, merge into the output file.
|
|
166
|
+
|
|
167
|
+
| Flag | Default | Description |
|
|
168
|
+
| --- | --- | --- |
|
|
169
|
+
| `--id`, `-i` | — | Fetch a single batch by id. If set, implies `--no-all`. |
|
|
170
|
+
| `--all` / `--no-all`, `-a` | `--all` | Process every pending batch under `~/.loom/batches/`. This is the default — `loom fetch` with no args walks all batches. |
|
|
171
|
+
| `--api-key` | env / `.env` | Override the resolved API key. |
|
|
172
|
+
| `--keep`, `-k` | off | Keep the metadata file in `~/.loom/batches/` after a successful fetch (default: delete it). |
|
|
173
|
+
| `--force` | off | Overwrite existing output files without prompting. |
|
|
174
|
+
|
|
175
|
+
For pending batches, `loom fetch` prints the current status and a one-sentence explanation. The full set of possible statuses:
|
|
176
|
+
|
|
177
|
+
| Status | Meaning |
|
|
178
|
+
| --- | --- |
|
|
179
|
+
| `validating` | Provider has accepted the batch and is queueing/preparing it; no work has started yet. |
|
|
180
|
+
| `in_progress` | Provider is actively running the prompts; check back later. |
|
|
181
|
+
| `completed` | All prompts finished and results were downloaded — the merged output file has been written. |
|
|
182
|
+
| `failed` | Provider reported the batch as failed; results are not available. |
|
|
183
|
+
| `expired` | Batch exceeded the provider's time limit (typically 24h) before completing. |
|
|
184
|
+
| `cancelled` | Batch was cancelled — either by you on the provider's dashboard, or by the provider itself. |
|
|
185
|
+
| `unknown` | The last fetch attempt raised an error (invalid id, auth failure, network glitch, or an API response Loom doesn't recognise). Re-run `loom fetch` to retry; if it persists, inspect the metadata file under `~/.loom/batches/`. |
|
|
186
|
+
|
|
187
|
+
`validating` and `in_progress` are the only non-terminal states — `loom fetch` will pick the batch up again on the next run. The other states are terminal: `completed` means the output file is on disk, and `failed` / `expired` / `cancelled` mean no merge happened.
|
|
188
|
+
|
|
189
|
+
#### `loom list`
|
|
190
|
+
|
|
191
|
+
List every batch known to Loom, with last-seen status, model, and source file. No flags.
|
|
192
|
+
|
|
193
|
+
#### `loom tokens`
|
|
194
|
+
|
|
195
|
+
Count input tokens for every prompt using the provider's token-counting API. See [Token counter](#token-counter).
|
|
196
|
+
|
|
197
|
+
| Flag | Default | Description |
|
|
198
|
+
| --- | --- | --- |
|
|
199
|
+
| `--file`, `-f` | _required_ | Input `.json`, `.csv`, `.json.gz`, or `.csv.gz`. |
|
|
200
|
+
| `--provider`, `-p` | _required_ | `openai`, `anthropic`, `google`, or `openrouter`. |
|
|
201
|
+
| `--model`, `-m` | _required_ | Provider-specific model id. |
|
|
202
|
+
| `--col`, `-c` | — | Prompt column name (required for CSV). |
|
|
203
|
+
| `--api-key` | env / `.env` | Override the resolved API key. |
|
|
204
|
+
| `--workers`, `-w` | `8` | Concurrent workers. |
|
|
205
|
+
|
|
206
|
+
#### `loom cache clear`
|
|
207
|
+
|
|
208
|
+
Delete every cached response under `~/.loom/cache/`. See [Caching](#caching).
|
|
209
|
+
|
|
210
|
+
| Flag | Default | Description |
|
|
211
|
+
| --- | --- | --- |
|
|
212
|
+
| `--yes`, `-y` | off | Skip the confirmation prompt. |
|
|
213
|
+
|
|
214
|
+
### Batch vs sequential
|
|
215
|
+
|
|
216
|
+
| | `loom run` (batch, default) | `loom run --sync` (sequential) |
|
|
217
|
+
| --- | --- | --- |
|
|
218
|
+
| Latency | Up to 24h | Real-time |
|
|
219
|
+
| Pricing (OpenAI, Anthropic) | 50% off | Standard |
|
|
220
|
+
| Steps | `run` → wait → `fetch` | Single command |
|
|
221
|
+
| Cache | n/a | On-disk, on by default |
|
|
222
|
+
| OpenRouter | ✗ | ✓ (only mode) |
|
|
223
|
+
| State on disk | `~/.loom/batches/` | None (cache only) |
|
|
224
|
+
|
|
225
|
+
Pick **batch** when you have a large dataset and don't care about wall-clock time. Pick **sync** when you want results now, or when the provider has no batch API (OpenRouter).
|
|
226
|
+
|
|
227
|
+
### Storing API keys
|
|
228
|
+
|
|
229
|
+
Loom resolves keys in this order: **`--api-key` flag → environment variable → `.env` file** in the current working directory (loaded via `python-dotenv`, does not overwrite existing env vars).
|
|
230
|
+
|
|
231
|
+
Recognised environment variables:
|
|
232
|
+
|
|
233
|
+
```ini
|
|
234
|
+
OPENAI_API_KEY=sk-...
|
|
235
|
+
ANTHROPIC_API_KEY=sk-ant-...
|
|
236
|
+
GOOGLE_API_KEY=...
|
|
237
|
+
OPENROUTER_API_KEY=sk-or-...
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
A `.env` in the working directory is the friction-free option for daily use; `--api-key` is handy for one-offs or shared workstations.
|
|
241
|
+
|
|
242
|
+
### Caching
|
|
243
|
+
|
|
244
|
+
In `--sync` mode, Loom caches every response under `~/.loom/cache/`. The cache key is `sha256("<provider>|<model>|<prompt>")`, so changing any of those misses the cache. There is no TTL or eviction — the cache grows monotonically until you clear it.
|
|
245
|
+
|
|
246
|
+
```bash
|
|
247
|
+
loom run --sync -p openai -m gpt-4o-mini -f data.csv -c text # first run: API calls
|
|
248
|
+
loom run --sync -p openai -m gpt-4o-mini -f data.csv -c text # second run: 100% cache hits
|
|
249
|
+
loom run --sync -p openai -m gpt-4o-mini -f data.csv -c text --no-cache # bypass
|
|
250
|
+
loom cache clear # wipe ~/.loom/cache/
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
`loom run --sync` reports cache hits live in its progress bar.
|
|
254
|
+
|
|
255
|
+
### Token counter
|
|
256
|
+
|
|
257
|
+
```bash
|
|
258
|
+
loom tokens --file prompts.json --provider anthropic --model claude-3-5-sonnet-latest
|
|
259
|
+
# Counting tokens ████████░░░░ 340/1000 est_total≈36,210 errors=0 0:01:12 eta 0:02:35
|
|
260
|
+
# -> Total input tokens: 12,345 across 100 prompts (provider=anthropic, model=claude-3-5-sonnet-latest, errors=0)
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
`loom tokens` calls each provider's official count-tokens endpoint, one prompt at a time, with a concurrent worker pool. The live progress bar shows:
|
|
264
|
+
|
|
265
|
+
- `done/total` prompts processed,
|
|
266
|
+
- `est_total` — running estimate of the final input-token count, computed as the mean tokens-per-prompt-so-far multiplied by `total` (refines as more prompts complete),
|
|
267
|
+
- `errors`,
|
|
268
|
+
- elapsed time and `eta` (estimated time remaining, based on the current rate).
|
|
269
|
+
|
|
270
|
+
| Provider | Endpoint | Available |
|
|
271
|
+
| --- | --- | --- |
|
|
272
|
+
| Anthropic | `client.messages.count_tokens(...)` → `input_tokens` | ✓ |
|
|
273
|
+
| Google | `client.models.count_tokens(...)` → `total_tokens` | ✓ |
|
|
274
|
+
| OpenAI | — | ✗ (no remote API; use `tiktoken` locally) |
|
|
275
|
+
| OpenRouter | — | ✗ |
|
|
276
|
+
|
|
277
|
+
For unsupported providers, `loom tokens` prints _"Token counting not available: ..."_ and exits with code 2.
|
|
278
|
+
|
|
279
|
+
### Where Loom stores state
|
|
280
|
+
|
|
281
|
+
```
|
|
282
|
+
~/.loom/
|
|
283
|
+
├── batches/ # one <provider>_<batch_id>.json per pending or kept batch
|
|
284
|
+
└── cache/ # one <sha256>.json per cached --sync response
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
- `~/.loom/batches/<provider>_<safe_id>.json` is created by `loom run` (batch mode) and contains `batch_id`, `provider`, `model`, `original_file_path`, `file_type`, the prompt column, an `id_map` mapping internal `custom_id` → original row id, `created_at`, and the last-seen `status`. `loom fetch` updates `status`, downloads results, and (unless `--keep` is passed) deletes the file on success.
|
|
288
|
+
- `~/.loom/cache/<sha256>.json` is the response cache used by `--sync`. Each file holds `{provider, model, response, created_at}`.
|
|
289
|
+
|
|
290
|
+
Both directories are safe to delete by hand: cache will rebuild itself; deleting `batches/` orphans any in-flight batch jobs (they still complete on the provider's side, you just lose Loom's view of them).
|
|
291
|
+
|
|
292
|
+
### Troubleshooting
|
|
293
|
+
|
|
294
|
+
**`ModuleNotFoundError: No module named 'pandas'` right after `pip install -e ".[dev]"`**
|
|
295
|
+
Your `.venv` was probably created with `uv venv`, which doesn't install `pip` inside. Your `pip install` ran against the system / conda `pip` and dropped the packages elsewhere. Fix with `uv pip install -e ".[dev]"`, or recreate the venv with `python -m venv .venv && source .venv/bin/activate && pip install -e ".[dev]"`.
|
|
296
|
+
|
|
297
|
+
**`which loom` shows `/opt/miniconda3/bin/loom` even after `source .venv/bin/activate`**
|
|
298
|
+
Conda's path is being prepended after the venv. Either reorder your shell init, or just call the venv binary directly: `./.venv/bin/loom <cmd>`.
|
|
299
|
+
|
|
300
|
+
**Google batch `Invalid batch job name: jqpem7...`**
|
|
301
|
+
The stored `batch_id` is missing the required `batches/` prefix (an older Loom version stripped it). Edit `~/.loom/batches/google_<id>.json`, change `"batch_id": "<id>"` to `"batch_id": "batches/<id>"`, and rename the file to `google_batches_<id>.json` so the on-disk filename and the in-file id stay consistent.
|
|
302
|
+
|
|
303
|
+
**`status=unknown` in `loom fetch`**
|
|
304
|
+
The previous fetch attempt raised an exception (bad id, network blip, expired key, or an API response Loom doesn't recognise). Re-running `loom fetch` retries; if it persists, run with the provider's SDK directly to surface the underlying error.
|
|
305
|
+
|
|
306
|
+
**`Error: OpenRouter has no batch API`**
|
|
307
|
+
OpenRouter doesn't offer batch processing. Re-run with `--sync`.
|
|
308
|
+
|
|
309
|
+
## 4. Developer instructions
|
|
310
|
+
|
|
311
|
+
### Repository layout
|
|
312
|
+
|
|
313
|
+
```
|
|
314
|
+
loom/
|
|
315
|
+
main.py # CLI entry point (Typer commands)
|
|
316
|
+
core/
|
|
317
|
+
orchestrator.py # run_batch, fetch_batch, generate_sync, count_tokens
|
|
318
|
+
models.py # Pydantic models, ProviderName, BatchStatus
|
|
319
|
+
providers/
|
|
320
|
+
base.py # Batch provider ABC (submit/check_status/download)
|
|
321
|
+
sync_base.py # Sync provider ABC (generate/count_tokens)
|
|
322
|
+
openai.py, anthropic.py,
|
|
323
|
+
google.py # Batch implementations
|
|
324
|
+
openai_sync.py, anthropic_sync.py,
|
|
325
|
+
google_sync.py, openrouter_sync.py # Sync implementations
|
|
326
|
+
utils/
|
|
327
|
+
converters.py # Load / merge JSON & CSV
|
|
328
|
+
storage.py # ~/.loom/batches/ persistence
|
|
329
|
+
cache.py # ~/.loom/cache/ response cache
|
|
330
|
+
keys.py # API-key resolution
|
|
331
|
+
tests/ # pytest suite
|
|
332
|
+
.github/workflows/ # CI: test.yml, publish.yml
|
|
333
|
+
pyproject.toml # Dependencies and package metadata
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
### Running unit tests
|
|
337
|
+
|
|
338
|
+
```bash
|
|
339
|
+
pip install -e ".[dev]"
|
|
340
|
+
pytest # quiet
|
|
341
|
+
pytest -v # verbose
|
|
342
|
+
pytest tests/test_converters.py # one file
|
|
343
|
+
pytest tests/test_storage.py::test_save_and_load_roundtrip # one test
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
### GitHub Actions
|
|
347
|
+
|
|
348
|
+
- [`.github/workflows/test.yml`](.github/workflows/test.yml) — runs on every push and PR, with a matrix over Python 3.10 / 3.11 / 3.12. Installs the project with `pip install -e ".[dev]"` and runs `pytest -v`.
|
|
349
|
+
- [`.github/workflows/publish.yml`](.github/workflows/publish.yml) — fires when you push a `v*` tag or publish a GitHub Release. It runs tests, builds an sdist + wheel, and uploads to PyPI via **OIDC Trusted Publishing** — no PyPI token is stored in repo secrets.
|
|
350
|
+
|
|
351
|
+
### Releasing
|
|
352
|
+
|
|
353
|
+
**One-time PyPI setup:**
|
|
354
|
+
|
|
355
|
+
1. Create the project on PyPI: https://pypi.org/manage/account/publishing/
|
|
356
|
+
2. Add a **trusted publisher** with:
|
|
357
|
+
- Owner: `jannehring`
|
|
358
|
+
- Repository: `loom`
|
|
359
|
+
- Workflow: `publish.yml`
|
|
360
|
+
- Environment: `pypi`
|
|
361
|
+
3. In GitHub: **Settings → Environments → New environment → `pypi`**. Enable manual approval here if you want a human in the loop for every release.
|
|
362
|
+
|
|
363
|
+
**Cutting a release:**
|
|
364
|
+
|
|
365
|
+
```bash
|
|
366
|
+
# 1. Bump version in pyproject.toml
|
|
367
|
+
# 2. Commit and tag
|
|
368
|
+
git commit -am "Release v0.1.1"
|
|
369
|
+
git tag v0.1.1
|
|
370
|
+
git push origin main --tags
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
The `publish` workflow runs on the tag, executes the test suite, builds, and uploads. You can also trigger it manually from the Actions tab (`workflow_dispatch`).
|
|
374
|
+
|
|
375
|
+
## 5. License
|
|
376
|
+
|
|
377
|
+
MIT — see [LICENSE](LICENSE).
|