batchor 0.0.1__tar.gz → 0.0.4__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.
Files changed (124) hide show
  1. {batchor-0.0.1 → batchor-0.0.4}/.agents/plugins/marketplace.json +12 -0
  2. {batchor-0.0.1 → batchor-0.0.4}/.github/workflows/ci.yml +1 -1
  3. {batchor-0.0.1 → batchor-0.0.4}/PKG-INFO +116 -61
  4. {batchor-0.0.1 → batchor-0.0.4}/README.md +109 -58
  5. {batchor-0.0.1 → batchor-0.0.4}/docs/design_docs/ARCHITECTURE.md +55 -15
  6. batchor-0.0.4/docs/design_docs/GEMINI_BATCHING.md +96 -0
  7. {batchor-0.0.1 → batchor-0.0.4}/docs/design_docs/OPENAI_BATCHING.md +16 -2
  8. {batchor-0.0.1 → batchor-0.0.4}/docs/design_docs/ROADMAP.md +2 -1
  9. {batchor-0.0.1 → batchor-0.0.4}/docs/design_docs/STORAGE_AND_RUNS.md +30 -4
  10. {batchor-0.0.1 → batchor-0.0.4}/docs/design_docs/STORAGE_MIGRATIONS.md +2 -1
  11. {batchor-0.0.1 → batchor-0.0.4}/docs/doc-map.md +2 -0
  12. {batchor-0.0.1 → batchor-0.0.4}/docs/getting-started/cli.md +43 -0
  13. {batchor-0.0.1 → batchor-0.0.4}/docs/getting-started/installation.md +37 -1
  14. {batchor-0.0.1 → batchor-0.0.4}/docs/getting-started/python-api.md +43 -0
  15. {batchor-0.0.1 → batchor-0.0.4}/docs/index.md +2 -2
  16. {batchor-0.0.1 → batchor-0.0.4}/docs/reference/api.md +10 -0
  17. {batchor-0.0.1 → batchor-0.0.4}/docs/smoke-test.md +43 -2
  18. {batchor-0.0.1 → batchor-0.0.4}/mkdocs.yml +2 -1
  19. batchor-0.0.4/plugins/batchor/.codex-plugin/plugin.json +44 -0
  20. batchor-0.0.4/plugins/batchor/.mcp.json +10 -0
  21. batchor-0.0.4/plugins/batchor/scripts/batchor_user_mcp.py +205 -0
  22. batchor-0.0.4/plugins/batchor/skills/use-batchor/SKILL.md +57 -0
  23. batchor-0.0.4/plugins/batchor/skills/use-batchor/agents/openai.yaml +4 -0
  24. batchor-0.0.4/plugins/batchor/skills/use-batchor/references/cli-workflows.md +45 -0
  25. batchor-0.0.4/plugins/batchor/skills/use-batchor/references/python-pipelines.md +64 -0
  26. {batchor-0.0.1 → batchor-0.0.4}/plugins/batchor-agent-tools/scripts/batchor_repo_mcp.py +10 -3
  27. {batchor-0.0.1 → batchor-0.0.4}/pyproject.toml +12 -4
  28. {batchor-0.0.1 → batchor-0.0.4}/src/batchor/__init__.py +8 -2
  29. {batchor-0.0.1 → batchor-0.0.4}/src/batchor/cli.py +93 -3
  30. {batchor-0.0.1 → batchor-0.0.4}/src/batchor/core/enums.py +16 -0
  31. {batchor-0.0.1 → batchor-0.0.4}/src/batchor/core/exceptions.py +8 -2
  32. {batchor-0.0.1 → batchor-0.0.4}/src/batchor/core/models.py +155 -1
  33. batchor-0.0.4/src/batchor/core/responses.py +60 -0
  34. {batchor-0.0.1 → batchor-0.0.4}/src/batchor/core/types.py +10 -7
  35. {batchor-0.0.1 → batchor-0.0.4}/src/batchor/providers/base.py +33 -3
  36. batchor-0.0.4/src/batchor/providers/gemini.py +554 -0
  37. {batchor-0.0.1 → batchor-0.0.4}/src/batchor/providers/registry.py +20 -7
  38. batchor-0.0.4/src/batchor/runtime/__init__.py +1 -0
  39. batchor-0.0.4/src/batchor/runtime/artifacts.py +177 -0
  40. batchor-0.0.4/src/batchor/runtime/context.py +164 -0
  41. batchor-0.0.4/src/batchor/runtime/ingestion.py +380 -0
  42. batchor-0.0.4/src/batchor/runtime/polling.py +552 -0
  43. batchor-0.0.4/src/batchor/runtime/results.py +119 -0
  44. {batchor-0.0.1 → batchor-0.0.4}/src/batchor/runtime/retry.py +63 -0
  45. {batchor-0.0.1 → batchor-0.0.4}/src/batchor/runtime/run_handle.py +113 -46
  46. batchor-0.0.4/src/batchor/runtime/runner.py +597 -0
  47. batchor-0.0.4/src/batchor/runtime/submission.py +561 -0
  48. {batchor-0.0.1 → batchor-0.0.4}/src/batchor/runtime/validation.py +15 -68
  49. {batchor-0.0.1 → batchor-0.0.4}/src/batchor/sources/base.py +10 -0
  50. {batchor-0.0.1 → batchor-0.0.4}/src/batchor/sources/composite.py +4 -0
  51. {batchor-0.0.1 → batchor-0.0.4}/src/batchor/sources/files.py +16 -0
  52. {batchor-0.0.1 → batchor-0.0.4}/src/batchor/storage/memory.py +58 -0
  53. {batchor-0.0.1 → batchor-0.0.4}/src/batchor/storage/postgres_store.py +18 -4
  54. {batchor-0.0.1 → batchor-0.0.4}/src/batchor/storage/sqlite_lifecycle.py +98 -1
  55. {batchor-0.0.1 → batchor-0.0.4}/src/batchor/storage/sqlite_protocol.py +1 -0
  56. {batchor-0.0.1 → batchor-0.0.4}/src/batchor/storage/sqlite_queries.py +3 -0
  57. {batchor-0.0.1 → batchor-0.0.4}/src/batchor/storage/sqlite_results.py +1 -0
  58. {batchor-0.0.1 → batchor-0.0.4}/src/batchor/storage/sqlite_schema.py +2 -1
  59. {batchor-0.0.1 → batchor-0.0.4}/src/batchor/storage/state_models.py +26 -0
  60. batchor-0.0.4/tests/integration/test_batchor_gemini_runner.py +219 -0
  61. batchor-0.0.4/tests/integration/test_batchor_live_gemini.py +157 -0
  62. {batchor-0.0.1 → batchor-0.0.4}/tests/integration/test_batchor_runner.py +324 -105
  63. batchor-0.0.4/tests/unit/test_agent_tooling.py +163 -0
  64. {batchor-0.0.1 → batchor-0.0.4}/tests/unit/test_batchor_architecture.py +31 -0
  65. {batchor-0.0.1 → batchor-0.0.4}/tests/unit/test_batchor_cli.py +123 -1
  66. batchor-0.0.4/tests/unit/test_batchor_gemini_provider.py +355 -0
  67. batchor-0.0.4/tests/unit/test_batchor_results.py +130 -0
  68. batchor-0.0.4/tests/unit/test_batchor_retry.py +143 -0
  69. batchor-0.0.4/tests/unit/test_batchor_runtime_ingestion.py +935 -0
  70. batchor-0.0.4/tests/unit/test_batchor_runtime_polling.py +419 -0
  71. batchor-0.0.4/tests/unit/test_batchor_runtime_submission.py +425 -0
  72. {batchor-0.0.1 → batchor-0.0.4}/tests/unit/test_batchor_sources.py +282 -0
  73. batchor-0.0.4/tests/unit/test_batchor_storage.py +55 -0
  74. batchor-0.0.4/tests/unit/test_batchor_storage_contracts.py +438 -0
  75. {batchor-0.0.1 → batchor-0.0.4}/tests/unit/test_batchor_tokens.py +86 -0
  76. batchor-0.0.4/tests/unit/test_batchor_validation.py +379 -0
  77. {batchor-0.0.1 → batchor-0.0.4}/uv.lock +367 -1
  78. batchor-0.0.1/src/batchor/runtime/__init__.py +0 -1
  79. batchor-0.0.1/src/batchor/runtime/runner.py +0 -1015
  80. batchor-0.0.1/src/batchor/runtime/runner_execution.py +0 -753
  81. batchor-0.0.1/tests/unit/test_agent_tooling.py +0 -49
  82. batchor-0.0.1/tests/unit/test_batchor_retry.py +0 -29
  83. batchor-0.0.1/tests/unit/test_batchor_storage.py +0 -22
  84. batchor-0.0.1/tests/unit/test_batchor_storage_contracts.py +0 -201
  85. batchor-0.0.1/tests/unit/test_batchor_validation.py +0 -174
  86. {batchor-0.0.1 → batchor-0.0.4}/.agents/skills/batchor-dev/SKILL.md +0 -0
  87. {batchor-0.0.1 → batchor-0.0.4}/.agents/skills/batchor-dev/agents/openai.yaml +0 -0
  88. {batchor-0.0.1 → batchor-0.0.4}/.github/workflows/docs.yml +0 -0
  89. {batchor-0.0.1 → batchor-0.0.4}/.github/workflows/publish.yml +0 -0
  90. {batchor-0.0.1 → batchor-0.0.4}/.gitignore +0 -0
  91. {batchor-0.0.1 → batchor-0.0.4}/.pre-commit-config.yaml +0 -0
  92. {batchor-0.0.1 → batchor-0.0.4}/AGENTS.md +0 -0
  93. {batchor-0.0.1 → batchor-0.0.4}/CONTRIBUTING.md +0 -0
  94. {batchor-0.0.1 → batchor-0.0.4}/LICENSE +0 -0
  95. {batchor-0.0.1 → batchor-0.0.4}/SUPPORT.md +0 -0
  96. {batchor-0.0.1 → batchor-0.0.4}/VERSIONING.md +0 -0
  97. {batchor-0.0.1 → batchor-0.0.4}/docs/assets/stylesheets/extra.css +0 -0
  98. {batchor-0.0.1 → batchor-0.0.4}/docs/design_docs/BOUNDARY_AND_PHILOSOPHY.md +0 -0
  99. {batchor-0.0.1 → batchor-0.0.4}/docs/getting-started/use-cases.md +0 -0
  100. {batchor-0.0.1 → batchor-0.0.4}/docs/policies/contributing.md +0 -0
  101. {batchor-0.0.1 → batchor-0.0.4}/docs/policies/support.md +0 -0
  102. {batchor-0.0.1 → batchor-0.0.4}/docs/policies/versioning.md +0 -0
  103. {batchor-0.0.1 → batchor-0.0.4}/plugins/batchor-agent-tools/.codex-plugin/plugin.json +0 -0
  104. {batchor-0.0.1 → batchor-0.0.4}/plugins/batchor-agent-tools/.mcp.json +0 -0
  105. {batchor-0.0.1 → batchor-0.0.4}/src/batchor/artifacts/__init__.py +0 -0
  106. {batchor-0.0.1 → batchor-0.0.4}/src/batchor/artifacts/base.py +0 -0
  107. {batchor-0.0.1 → batchor-0.0.4}/src/batchor/artifacts/local.py +0 -0
  108. {batchor-0.0.1 → batchor-0.0.4}/src/batchor/core/__init__.py +0 -0
  109. {batchor-0.0.1 → batchor-0.0.4}/src/batchor/providers/__init__.py +0 -0
  110. {batchor-0.0.1 → batchor-0.0.4}/src/batchor/providers/openai.py +0 -0
  111. {batchor-0.0.1 → batchor-0.0.4}/src/batchor/py.typed +0 -0
  112. {batchor-0.0.1 → batchor-0.0.4}/src/batchor/runtime/tokens.py +0 -0
  113. {batchor-0.0.1 → batchor-0.0.4}/src/batchor/sources/__init__.py +0 -0
  114. {batchor-0.0.1 → batchor-0.0.4}/src/batchor/storage/__init__.py +0 -0
  115. {batchor-0.0.1 → batchor-0.0.4}/src/batchor/storage/postgres.py +0 -0
  116. {batchor-0.0.1 → batchor-0.0.4}/src/batchor/storage/registry.py +0 -0
  117. {batchor-0.0.1 → batchor-0.0.4}/src/batchor/storage/sqlite.py +0 -0
  118. {batchor-0.0.1 → batchor-0.0.4}/src/batchor/storage/sqlite_codec.py +0 -0
  119. {batchor-0.0.1 → batchor-0.0.4}/src/batchor/storage/sqlite_store.py +0 -0
  120. {batchor-0.0.1 → batchor-0.0.4}/src/batchor/storage/state.py +0 -0
  121. {batchor-0.0.1 → batchor-0.0.4}/tests/integration/test_batchor_live_openai.py +0 -0
  122. {batchor-0.0.1 → batchor-0.0.4}/tests/unit/test_batchor_artifacts.py +0 -0
  123. {batchor-0.0.1 → batchor-0.0.4}/tests/unit/test_batchor_openai_provider.py +0 -0
  124. {batchor-0.0.1 → batchor-0.0.4}/tests/unit/test_batchor_sqlite_storage_flow.py +0 -0
@@ -15,6 +15,18 @@
15
15
  "authentication": "ON_INSTALL"
16
16
  },
17
17
  "category": "Coding"
18
+ },
19
+ {
20
+ "name": "batchor",
21
+ "source": {
22
+ "source": "local",
23
+ "path": "./plugins/batchor"
24
+ },
25
+ "policy": {
26
+ "installation": "AVAILABLE",
27
+ "authentication": "ON_INSTALL"
28
+ },
29
+ "category": "Coding"
18
30
  }
19
31
  ]
20
32
  }
@@ -46,7 +46,7 @@ jobs:
46
46
  strategy:
47
47
  fail-fast: false
48
48
  matrix:
49
- python-version: ["3.12", "3.13"]
49
+ python-version: ["3.12", "3.13", "3.14"]
50
50
 
51
51
  steps:
52
52
  - uses: actions/checkout@v4
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: batchor
3
- Version: 0.0.1
4
- Summary: Structured-first OpenAI Batch runner with typed Pydantic results.
3
+ Version: 0.0.4
4
+ Summary: Structured-first provider Batch runner with typed Pydantic results.
5
5
  Project-URL: Documentation, https://github.com/AnsonDev42/batchor#readme
6
6
  Project-URL: Repository, https://github.com/AnsonDev42/batchor
7
7
  Project-URL: Issues, https://github.com/AnsonDev42/batchor/issues
@@ -79,7 +79,7 @@ License: # PolyForm Noncommercial License 1.0.0
79
79
 
80
80
  **Use** means anything you do with the software requiring one of your licenses.
81
81
  License-File: LICENSE
82
- Keywords: batch,cli,openai,pydantic,sqlite
82
+ Keywords: batch,cli,gemini,openai,pydantic,sqlite
83
83
  Classifier: Development Status :: 3 - Alpha
84
84
  Classifier: Environment :: Console
85
85
  Classifier: Intended Audience :: Developers
@@ -88,6 +88,7 @@ Classifier: Operating System :: OS Independent
88
88
  Classifier: Programming Language :: Python :: 3
89
89
  Classifier: Programming Language :: Python :: 3.12
90
90
  Classifier: Programming Language :: Python :: 3.13
91
+ Classifier: Programming Language :: Python :: 3.14
91
92
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
92
93
  Requires-Python: <4,>=3.12
93
94
  Requires-Dist: openai>=2.7.1
@@ -98,20 +99,26 @@ Requires-Dist: python-dotenv<2,>=1.0.1
98
99
  Requires-Dist: sqlalchemy<3,>=2.0.43
99
100
  Requires-Dist: tiktoken>=0.12.0
100
101
  Requires-Dist: typer<1,>=0.16.0
102
+ Provides-Extra: gemini
103
+ Requires-Dist: google-cloud-storage<4,>=3.4.0; extra == 'gemini'
104
+ Requires-Dist: google-genai<2,>=1.55.0; extra == 'gemini'
101
105
  Description-Content-Type: text/markdown
102
106
 
103
107
  # batchor
104
108
 
105
- `batchor` is a durable OpenAI Batch runner for Python teams that want:
109
+ `batchor` grew out of a recurring problem in academic research: running large datasets through LLMs in batch — reliably, reproducibly, and without reinventing the same glue code across every project. The patterns that kept emerging (durable state, typed results, safe resume after failure) were extracted into this library so they do not have to be rebuilt each time.
110
+
111
+ `batchor` is a durable provider Batch runner for Python teams that want:
106
112
 
107
113
  - typed Pydantic results
108
114
  - resumable durable runs
109
115
  - replayable request artifacts
110
116
  - deterministic source checkpoints
117
+ - provider-side enqueue limit controls to stay within token budgets
111
118
  - library-first run controls
112
119
  - a small operator CLI for CSV and JSONL jobs
113
120
 
114
- It is intentionally narrow today: OpenAI-first, SQLite-first, and library-first.
121
+ It is intentionally narrow today: OpenAI is the CLI default, Gemini is opt-in through `batchor[gemini]`, SQLite is the CLI durability backend, and the Python API exposes the broadest configuration surface.
115
122
 
116
123
  ## What problem it solves
117
124
 
@@ -136,6 +143,7 @@ Most OpenAI Batch examples stop at "upload a JSONL file and poll until it finish
136
143
  Built-in implementations:
137
144
 
138
145
  - `OpenAIProviderConfig` + `OpenAIBatchProvider`
146
+ - `GeminiProviderConfig` + `GeminiBatchProvider` for text-only Gemini Batch jobs
139
147
  - `SQLiteStorage`
140
148
  - `PostgresStorage` as an opt-in durable control-plane backend
141
149
  - `MemoryStateStore`
@@ -151,9 +159,12 @@ Important constraints:
151
159
  - the CLI supports file-backed inputs only
152
160
  - users still own selecting and ordering input files or partitions
153
161
  - the built-in CLI uses SQLite durability only
162
+ - the CLI supports OpenAI plus Gemini Developer API and Vertex AI text jobs
163
+ - Gemini support is text-only for now and does not build multimodal requests
154
164
  - structured-output rehydration requires an importable module-level Pydantic model
155
165
  - raw output artifacts are retained by default and must be exported before raw pruning
156
166
  - pause/resume/cancel and incremental terminal-result APIs are library-first today
167
+ - OpenAI control-plane or batch-level 429 quota/billing exhaustion auto-pauses the run instead of burning attempts across the remaining backlog
157
168
 
158
169
  ## Mental model
159
170
 
@@ -173,6 +184,7 @@ Durability is split on purpose:
173
184
  - the artifact store keeps replayable request JSONL and downloaded raw batch payloads
174
185
 
175
186
  That split is what allows retries and fresh-process resume without keeping every request inline in the control-plane store.
187
+ On resume, existing active provider batches are polled before `batchor` materializes or submits new local work, so completed batches and retry backoff are reconciled first. Deterministic sources also use stored checkpoint completion metadata to avoid opening a fully materialized Parquet or composite source just to discover there are no rows left.
176
188
 
177
189
  ## Architecture
178
190
 
@@ -187,6 +199,7 @@ graph LR
187
199
 
188
200
  subgraph providers["providers/"]
189
201
  OpenAI["OpenAIBatchProvider"]
202
+ Gemini["GeminiBatchProvider"]
190
203
  end
191
204
 
192
205
  subgraph sources["sources/"]
@@ -206,6 +219,7 @@ graph LR
206
219
  User -->|"start() / run_and_wait()"| BatchRunner
207
220
  BatchRunner --> Run
208
221
  BatchRunner --> OpenAI
222
+ BatchRunner --> Gemini
209
223
  BatchRunner --> SQLite
210
224
  BatchRunner --> LocalFS
211
225
  Files -->|"BatchItem stream"| BatchRunner
@@ -241,28 +255,45 @@ Operational semantics for resume, run control, and artifact retention live in
241
255
  pip install batchor
242
256
  ```
243
257
 
244
- ## Repo Agent Setup
258
+ For Gemini Batch support, install the optional extra:
259
+
260
+ ```bash
261
+ pip install "batchor[gemini]"
262
+ ```
263
+
264
+ ## Agent setup
245
265
 
246
- This repo now includes local AI-agent scaffolding so a contributor agent can pick up repo conventions without extra global setup:
266
+ Batchor keeps contributor tooling separate from the tools intended for researchers and downstream projects:
247
267
 
248
- - repo-local skill: `.agents/skills/batchor-dev/`
249
- - repo-local plugin marketplace: `.agents/plugins/marketplace.json`
250
- - repo-local MCP plugin: `plugins/batchor-agent-tools/`
251
- - VS Code workspace MCP config: `.vscode/mcp.json`
268
+ - `plugins/batchor/` is the user-facing Codex plugin. Its `$use-batchor` skill and MCP helpers turn datasets and prompts into safe CLI or Python workflows without assuming a Batchor source checkout.
269
+ - `.agents/skills/batchor-dev/` and `plugins/batchor-agent-tools/` are contributor-only. They teach agents how to change this repository, find design docs, and run its validation suite.
270
+ - `.agents/plugins/marketplace.json` registers both plugins for local development.
271
+ - `.vscode/mcp.json` configures the contributor MCP for this workspace.
252
272
 
253
- The skill captures repo-specific workflow and validation guidance. The MCP plugin exposes a small repo-aware guide for project overview, docs entry points, and validation commands.
273
+ The PyPI package remains the runtime dependency: `pip install batchor` installs the library and CLI. Agent skills and MCP configuration are distributed as the separate `batchor` plugin so Python environments do not receive Codex-specific files implicitly.
274
+
275
+ To try the user plugin from a local checkout:
276
+
277
+ ```bash
278
+ codex plugin marketplace add /path/to/batchor
279
+ codex plugin add batchor@batchor-local
280
+ ```
281
+
282
+ Start a new Codex task after installation, then ask: `Use $use-batchor to turn my CSV and research prompt into a resumable batch job.`
254
283
 
255
284
  Supported Python versions:
256
285
 
257
286
  - `3.12`
258
287
  - `3.13`
288
+ - `3.14`
259
289
 
260
290
  ## Authentication
261
291
 
262
292
  For Python API usage, auth resolution is:
263
293
 
264
- 1. explicit `OpenAIProviderConfig(api_key=...)`
265
- 2. ambient `OPENAI_API_KEY`
294
+ 1. explicit provider config credentials such as `OpenAIProviderConfig(api_key=...)` or `GeminiProviderConfig(api_key=...)`
295
+ 2. ambient provider environment variables, currently `OPENAI_API_KEY` or `GEMINI_API_KEY`
296
+ 3. Vertex AI Application Default Credentials when `GeminiProviderConfig(vertexai=True, ...)` or `GOOGLE_GENAI_USE_VERTEXAI=true` is used
266
297
 
267
298
  The Python library does not auto-load `.env`.
268
299
 
@@ -291,6 +322,43 @@ run = runner.run_and_wait(
291
322
  print(run.results()[0].output_text)
292
323
  ```
293
324
 
325
+ ### Gemini text job
326
+
327
+ ```python
328
+ from batchor import BatchItem, BatchJob, BatchRunner, GeminiProviderConfig, PromptParts
329
+
330
+
331
+ runner = BatchRunner(storage="memory")
332
+ run = runner.run_and_wait(
333
+ BatchJob(
334
+ items=[BatchItem(item_id="row1", payload="Summarize this text")],
335
+ build_prompt=lambda item: PromptParts(prompt=item.payload),
336
+ provider_config=GeminiProviderConfig(
337
+ model="gemini-2.5-flash",
338
+ api_key="YOUR_GEMINI_API_KEY",
339
+ ),
340
+ )
341
+ )
342
+
343
+ print(run.results()[0].output_text)
344
+ ```
345
+
346
+ Gemini support currently builds text-only `GenerateContent` batch requests. It uses Gemini JSONL `key` values internally while keeping `batchor`'s durable item and attempt tracking unchanged.
347
+
348
+ For Vertex AI, provide a Cloud Storage staging prefix and use Application Default Credentials:
349
+
350
+ ```python
351
+ provider_config = GeminiProviderConfig(
352
+ model="gemini-2.5-flash",
353
+ vertexai=True,
354
+ project="my-project",
355
+ location="europe-west8",
356
+ gcs_uri="gs://my-bucket/batchor",
357
+ )
358
+ ```
359
+
360
+ Vertex AI stages JSONL input and output in that prefix. The Gemini Developer API uses inline requests for batches below 20 MB and the Files API for larger batches; `input_mode=` can override that automatic choice. Vertex request/output correlation uses a generated request label because Vertex output does not include the Developer API JSONL `key`.
361
+
294
362
  ### Structured output
295
363
 
296
364
  ```python
@@ -334,6 +402,8 @@ run.wait()
334
402
  print(run.results()[0].output)
335
403
  ```
336
404
 
405
+ While waiting, `batchor` polls active batches before submitting more work. If a refresh consumes a completed batch, records terminal item state, or submits another provider batch, `Run.wait()` immediately continues draining instead of sleeping for the next poll interval.
406
+
337
407
  Structured-output models are validated up front against the OpenAI strict-schema subset used by `batchor`.
338
408
 
339
409
  - root schema must be an object
@@ -342,6 +412,8 @@ Structured-output models are validated up front against the OpenAI strict-schema
342
412
 
343
413
  If you need a field to be optional in Python, model it as nullable in the schema shape OpenAI accepts rather than relying on omitted required fields.
344
414
 
415
+ The same `structured_output=` API is available with `GeminiProviderConfig`; batchor sends the schema through Gemini `generation_config.response_json_schema` and validates the returned JSON text with the same Pydantic model.
416
+
345
417
  ### Rehydrate a durable run
346
418
 
347
419
  ```python
@@ -479,6 +551,8 @@ run.cancel()
479
551
 
480
552
  Run control and incremental terminal-result APIs are Python-first in this release. The CLI does not yet expose `pause`, `resume`, `cancel`, or incremental terminal-result export commands.
481
553
 
554
+ OpenAI insufficient-quota failures during upload/create/polling, or as a batch-level terminal error, are treated as an automatic pause with `summary().control_reason == "openai_insufficient_quota"`. Row-level insufficient-quota records inside a completed batch output stay item-scoped: those rows become retryable without consuming attempts, emit `openai_insufficient_quota` failure telemetry, and use retry backoff before resubmission. Auto-pause does not replace an in-progress cancellation.
555
+
482
556
  ## CLI quickstart
483
557
 
484
558
  The CLI is intentionally narrower than the Python API:
@@ -487,6 +561,31 @@ The CLI is intentionally narrower than the Python API:
487
561
  - CSV and JSONL only
488
562
  - SQLite-backed durable runs only
489
563
 
564
+ OpenAI remains the default provider. For Gemini, install `batchor[gemini]` and select the backend explicitly or let `auto` follow `GOOGLE_GENAI_USE_VERTEXAI`:
565
+
566
+ ```bash
567
+ batchor start \
568
+ --input input/items.jsonl \
569
+ --id-field id \
570
+ --prompt-field text \
571
+ --provider gemini \
572
+ --model gemini-2.5-flash \
573
+ --gemini-backend developer
574
+ ```
575
+
576
+ Vertex AI additionally needs a writable staging prefix:
577
+
578
+ ```bash
579
+ batchor start \
580
+ --input input/items.jsonl \
581
+ --id-field id \
582
+ --prompt-field text \
583
+ --provider gemini \
584
+ --model gemini-2.5-flash \
585
+ --gemini-backend vertex \
586
+ --gcs-uri gs://my-bucket/batchor
587
+ ```
588
+
490
589
  Start a run from JSONL:
491
590
 
492
591
  ```bash
@@ -539,12 +638,13 @@ def observer(event: RunEvent) -> None:
539
638
  runner = BatchRunner(observer=observer)
540
639
  ```
541
640
 
542
- Current events include run creation/resume, item ingestion, batch submission/polling/completion, item completion/failure, and artifact export/prune.
641
+ Current events include run creation/resume, automatic quota pause, item ingestion, batch submission/polling/completion, item completion/failure, and artifact export/prune.
543
642
 
544
643
  ## Storage notes
545
644
 
546
645
  - SQLite remains the default durable backend.
547
- - `PostgresStorage` is available for shared control-plane state, but the CLI remains SQLite-only today.
646
+ - `PostgresStorage` is available for shared control-plane state, but the CLI remains SQLite-only today. Plain `postgresql://` and `postgres://` DSNs are normalized to the package's psycopg v3 driver URL.
647
+ - Item `attempt_count` means consumed provider attempts: successful submitted completions increment it, counted item-level failures increment it, and local or batch-level retry/reset paths that did not consume an item attempt leave it unchanged.
548
648
  - Durable artifacts now go through an `ArtifactStore` seam. The built-in implementation is `LocalArtifactStore`, intended for local disk or a shared mounted volume.
549
649
  - Fresh-process resume requeues any locally claimed but not yet submitted items before continuing, so a process crash after request-artifact persistence does not strand work in `queued_local`.
550
650
 
@@ -591,51 +691,6 @@ The default pytest configuration enforces an `85%` coverage floor.
591
691
 
592
692
  GitHub Actions pull requests run:
593
693
 
594
- - `test (3.12)` and `test (3.13)`: `uv run ty check src` plus `uv run pytest -q`
595
- - `docs`: `uv run mkdocs build --strict`
596
- - `build (3.13)`: `uv build`
597
- - `postgres-contract`: `uv run pytest tests/unit/test_batchor_storage_contracts.py --no-cov -q` with an ephemeral PostgreSQL service
598
-
599
- The live OpenAI smoke remains manual-only and is not part of required CI.
600
-
601
- Manual live OpenAI smoke:
602
-
603
- ```bash
604
- export OPENAI_API_KEY=sk-...
605
- export BATCHOR_RUN_LIVE_TESTS=1
606
- uv run pytest tests/integration/test_batchor_live_openai.py --no-cov -q
607
- ```
608
-
609
- If `BATCHOR_LIVE_OPENAI_MODEL` is unset, the harness defaults to `gpt-5-nano`.
610
- If `BATCHOR_LIVE_OPENAI_REASONING_EFFORT` is unset, no reasoning field is sent.
611
- The live smoke also requires an OpenAI account with Batch API access and available billing quota.
612
-
613
- ## Documentation guide
614
-
615
- - `docs/design_docs/ARCHITECTURE.md`: canonical runtime diagrams and module boundaries
616
- - `docs/design_docs/STORAGE_AND_RUNS.md`: durable run, resume, and artifact lifecycle semantics
617
- - `docs/getting-started/use-cases.md`: practical single-file, multi-file, and pipeline examples
618
- - `docs/getting-started/python-api.md`: Python-first workflows
619
- - `docs/getting-started/cli.md`: operator CLI workflows
620
- - `docs/reference/api.md`: generated API surface
621
- - `docs/design_docs/ARCHITECTURE.md`: package boundaries
622
- - `docs/design_docs/OPENAI_BATCHING.md`: OpenAI-specific behavior
623
- - `docs/design_docs/STORAGE_AND_RUNS.md`: durability, rehydration, and artifacts
624
- - `docs/smoke-test.md`: minimum validation bar
625
-
626
- ## Scope
627
-
628
- Today `batchor` is intentionally narrow:
629
-
630
- - one built-in provider: OpenAI Batch
631
- - one default durable backend: SQLite
632
- - one opt-in durable backend: Postgres
633
- - one ephemeral backend: in-memory storage
634
- - structured output is Python API-first
635
- - CLI job creation is file-backed and supports both text and structured-output jobs
636
-
637
- Anything outside that scope should be treated as out of scope or `TBD`, not implied support.
638
-
639
694
  ## License
640
695
 
641
696
  This project is licensed under PolyForm Noncommercial 1.0.0. See [LICENSE](LICENSE).
@@ -1,15 +1,18 @@
1
1
  # batchor
2
2
 
3
- `batchor` is a durable OpenAI Batch runner for Python teams that want:
3
+ `batchor` grew out of a recurring problem in academic research: running large datasets through LLMs in batch — reliably, reproducibly, and without reinventing the same glue code across every project. The patterns that kept emerging (durable state, typed results, safe resume after failure) were extracted into this library so they do not have to be rebuilt each time.
4
+
5
+ `batchor` is a durable provider Batch runner for Python teams that want:
4
6
 
5
7
  - typed Pydantic results
6
8
  - resumable durable runs
7
9
  - replayable request artifacts
8
10
  - deterministic source checkpoints
11
+ - provider-side enqueue limit controls to stay within token budgets
9
12
  - library-first run controls
10
13
  - a small operator CLI for CSV and JSONL jobs
11
14
 
12
- It is intentionally narrow today: OpenAI-first, SQLite-first, and library-first.
15
+ It is intentionally narrow today: OpenAI is the CLI default, Gemini is opt-in through `batchor[gemini]`, SQLite is the CLI durability backend, and the Python API exposes the broadest configuration surface.
13
16
 
14
17
  ## What problem it solves
15
18
 
@@ -34,6 +37,7 @@ Most OpenAI Batch examples stop at "upload a JSONL file and poll until it finish
34
37
  Built-in implementations:
35
38
 
36
39
  - `OpenAIProviderConfig` + `OpenAIBatchProvider`
40
+ - `GeminiProviderConfig` + `GeminiBatchProvider` for text-only Gemini Batch jobs
37
41
  - `SQLiteStorage`
38
42
  - `PostgresStorage` as an opt-in durable control-plane backend
39
43
  - `MemoryStateStore`
@@ -49,9 +53,12 @@ Important constraints:
49
53
  - the CLI supports file-backed inputs only
50
54
  - users still own selecting and ordering input files or partitions
51
55
  - the built-in CLI uses SQLite durability only
56
+ - the CLI supports OpenAI plus Gemini Developer API and Vertex AI text jobs
57
+ - Gemini support is text-only for now and does not build multimodal requests
52
58
  - structured-output rehydration requires an importable module-level Pydantic model
53
59
  - raw output artifacts are retained by default and must be exported before raw pruning
54
60
  - pause/resume/cancel and incremental terminal-result APIs are library-first today
61
+ - OpenAI control-plane or batch-level 429 quota/billing exhaustion auto-pauses the run instead of burning attempts across the remaining backlog
55
62
 
56
63
  ## Mental model
57
64
 
@@ -71,6 +78,7 @@ Durability is split on purpose:
71
78
  - the artifact store keeps replayable request JSONL and downloaded raw batch payloads
72
79
 
73
80
  That split is what allows retries and fresh-process resume without keeping every request inline in the control-plane store.
81
+ On resume, existing active provider batches are polled before `batchor` materializes or submits new local work, so completed batches and retry backoff are reconciled first. Deterministic sources also use stored checkpoint completion metadata to avoid opening a fully materialized Parquet or composite source just to discover there are no rows left.
74
82
 
75
83
  ## Architecture
76
84
 
@@ -85,6 +93,7 @@ graph LR
85
93
 
86
94
  subgraph providers["providers/"]
87
95
  OpenAI["OpenAIBatchProvider"]
96
+ Gemini["GeminiBatchProvider"]
88
97
  end
89
98
 
90
99
  subgraph sources["sources/"]
@@ -104,6 +113,7 @@ graph LR
104
113
  User -->|"start() / run_and_wait()"| BatchRunner
105
114
  BatchRunner --> Run
106
115
  BatchRunner --> OpenAI
116
+ BatchRunner --> Gemini
107
117
  BatchRunner --> SQLite
108
118
  BatchRunner --> LocalFS
109
119
  Files -->|"BatchItem stream"| BatchRunner
@@ -139,28 +149,45 @@ Operational semantics for resume, run control, and artifact retention live in
139
149
  pip install batchor
140
150
  ```
141
151
 
142
- ## Repo Agent Setup
152
+ For Gemini Batch support, install the optional extra:
153
+
154
+ ```bash
155
+ pip install "batchor[gemini]"
156
+ ```
157
+
158
+ ## Agent setup
143
159
 
144
- This repo now includes local AI-agent scaffolding so a contributor agent can pick up repo conventions without extra global setup:
160
+ Batchor keeps contributor tooling separate from the tools intended for researchers and downstream projects:
145
161
 
146
- - repo-local skill: `.agents/skills/batchor-dev/`
147
- - repo-local plugin marketplace: `.agents/plugins/marketplace.json`
148
- - repo-local MCP plugin: `plugins/batchor-agent-tools/`
149
- - VS Code workspace MCP config: `.vscode/mcp.json`
162
+ - `plugins/batchor/` is the user-facing Codex plugin. Its `$use-batchor` skill and MCP helpers turn datasets and prompts into safe CLI or Python workflows without assuming a Batchor source checkout.
163
+ - `.agents/skills/batchor-dev/` and `plugins/batchor-agent-tools/` are contributor-only. They teach agents how to change this repository, find design docs, and run its validation suite.
164
+ - `.agents/plugins/marketplace.json` registers both plugins for local development.
165
+ - `.vscode/mcp.json` configures the contributor MCP for this workspace.
150
166
 
151
- The skill captures repo-specific workflow and validation guidance. The MCP plugin exposes a small repo-aware guide for project overview, docs entry points, and validation commands.
167
+ The PyPI package remains the runtime dependency: `pip install batchor` installs the library and CLI. Agent skills and MCP configuration are distributed as the separate `batchor` plugin so Python environments do not receive Codex-specific files implicitly.
168
+
169
+ To try the user plugin from a local checkout:
170
+
171
+ ```bash
172
+ codex plugin marketplace add /path/to/batchor
173
+ codex plugin add batchor@batchor-local
174
+ ```
175
+
176
+ Start a new Codex task after installation, then ask: `Use $use-batchor to turn my CSV and research prompt into a resumable batch job.`
152
177
 
153
178
  Supported Python versions:
154
179
 
155
180
  - `3.12`
156
181
  - `3.13`
182
+ - `3.14`
157
183
 
158
184
  ## Authentication
159
185
 
160
186
  For Python API usage, auth resolution is:
161
187
 
162
- 1. explicit `OpenAIProviderConfig(api_key=...)`
163
- 2. ambient `OPENAI_API_KEY`
188
+ 1. explicit provider config credentials such as `OpenAIProviderConfig(api_key=...)` or `GeminiProviderConfig(api_key=...)`
189
+ 2. ambient provider environment variables, currently `OPENAI_API_KEY` or `GEMINI_API_KEY`
190
+ 3. Vertex AI Application Default Credentials when `GeminiProviderConfig(vertexai=True, ...)` or `GOOGLE_GENAI_USE_VERTEXAI=true` is used
164
191
 
165
192
  The Python library does not auto-load `.env`.
166
193
 
@@ -189,6 +216,43 @@ run = runner.run_and_wait(
189
216
  print(run.results()[0].output_text)
190
217
  ```
191
218
 
219
+ ### Gemini text job
220
+
221
+ ```python
222
+ from batchor import BatchItem, BatchJob, BatchRunner, GeminiProviderConfig, PromptParts
223
+
224
+
225
+ runner = BatchRunner(storage="memory")
226
+ run = runner.run_and_wait(
227
+ BatchJob(
228
+ items=[BatchItem(item_id="row1", payload="Summarize this text")],
229
+ build_prompt=lambda item: PromptParts(prompt=item.payload),
230
+ provider_config=GeminiProviderConfig(
231
+ model="gemini-2.5-flash",
232
+ api_key="YOUR_GEMINI_API_KEY",
233
+ ),
234
+ )
235
+ )
236
+
237
+ print(run.results()[0].output_text)
238
+ ```
239
+
240
+ Gemini support currently builds text-only `GenerateContent` batch requests. It uses Gemini JSONL `key` values internally while keeping `batchor`'s durable item and attempt tracking unchanged.
241
+
242
+ For Vertex AI, provide a Cloud Storage staging prefix and use Application Default Credentials:
243
+
244
+ ```python
245
+ provider_config = GeminiProviderConfig(
246
+ model="gemini-2.5-flash",
247
+ vertexai=True,
248
+ project="my-project",
249
+ location="europe-west8",
250
+ gcs_uri="gs://my-bucket/batchor",
251
+ )
252
+ ```
253
+
254
+ Vertex AI stages JSONL input and output in that prefix. The Gemini Developer API uses inline requests for batches below 20 MB and the Files API for larger batches; `input_mode=` can override that automatic choice. Vertex request/output correlation uses a generated request label because Vertex output does not include the Developer API JSONL `key`.
255
+
192
256
  ### Structured output
193
257
 
194
258
  ```python
@@ -232,6 +296,8 @@ run.wait()
232
296
  print(run.results()[0].output)
233
297
  ```
234
298
 
299
+ While waiting, `batchor` polls active batches before submitting more work. If a refresh consumes a completed batch, records terminal item state, or submits another provider batch, `Run.wait()` immediately continues draining instead of sleeping for the next poll interval.
300
+
235
301
  Structured-output models are validated up front against the OpenAI strict-schema subset used by `batchor`.
236
302
 
237
303
  - root schema must be an object
@@ -240,6 +306,8 @@ Structured-output models are validated up front against the OpenAI strict-schema
240
306
 
241
307
  If you need a field to be optional in Python, model it as nullable in the schema shape OpenAI accepts rather than relying on omitted required fields.
242
308
 
309
+ The same `structured_output=` API is available with `GeminiProviderConfig`; batchor sends the schema through Gemini `generation_config.response_json_schema` and validates the returned JSON text with the same Pydantic model.
310
+
243
311
  ### Rehydrate a durable run
244
312
 
245
313
  ```python
@@ -377,6 +445,8 @@ run.cancel()
377
445
 
378
446
  Run control and incremental terminal-result APIs are Python-first in this release. The CLI does not yet expose `pause`, `resume`, `cancel`, or incremental terminal-result export commands.
379
447
 
448
+ OpenAI insufficient-quota failures during upload/create/polling, or as a batch-level terminal error, are treated as an automatic pause with `summary().control_reason == "openai_insufficient_quota"`. Row-level insufficient-quota records inside a completed batch output stay item-scoped: those rows become retryable without consuming attempts, emit `openai_insufficient_quota` failure telemetry, and use retry backoff before resubmission. Auto-pause does not replace an in-progress cancellation.
449
+
380
450
  ## CLI quickstart
381
451
 
382
452
  The CLI is intentionally narrower than the Python API:
@@ -385,6 +455,31 @@ The CLI is intentionally narrower than the Python API:
385
455
  - CSV and JSONL only
386
456
  - SQLite-backed durable runs only
387
457
 
458
+ OpenAI remains the default provider. For Gemini, install `batchor[gemini]` and select the backend explicitly or let `auto` follow `GOOGLE_GENAI_USE_VERTEXAI`:
459
+
460
+ ```bash
461
+ batchor start \
462
+ --input input/items.jsonl \
463
+ --id-field id \
464
+ --prompt-field text \
465
+ --provider gemini \
466
+ --model gemini-2.5-flash \
467
+ --gemini-backend developer
468
+ ```
469
+
470
+ Vertex AI additionally needs a writable staging prefix:
471
+
472
+ ```bash
473
+ batchor start \
474
+ --input input/items.jsonl \
475
+ --id-field id \
476
+ --prompt-field text \
477
+ --provider gemini \
478
+ --model gemini-2.5-flash \
479
+ --gemini-backend vertex \
480
+ --gcs-uri gs://my-bucket/batchor
481
+ ```
482
+
388
483
  Start a run from JSONL:
389
484
 
390
485
  ```bash
@@ -437,12 +532,13 @@ def observer(event: RunEvent) -> None:
437
532
  runner = BatchRunner(observer=observer)
438
533
  ```
439
534
 
440
- Current events include run creation/resume, item ingestion, batch submission/polling/completion, item completion/failure, and artifact export/prune.
535
+ Current events include run creation/resume, automatic quota pause, item ingestion, batch submission/polling/completion, item completion/failure, and artifact export/prune.
441
536
 
442
537
  ## Storage notes
443
538
 
444
539
  - SQLite remains the default durable backend.
445
- - `PostgresStorage` is available for shared control-plane state, but the CLI remains SQLite-only today.
540
+ - `PostgresStorage` is available for shared control-plane state, but the CLI remains SQLite-only today. Plain `postgresql://` and `postgres://` DSNs are normalized to the package's psycopg v3 driver URL.
541
+ - Item `attempt_count` means consumed provider attempts: successful submitted completions increment it, counted item-level failures increment it, and local or batch-level retry/reset paths that did not consume an item attempt leave it unchanged.
446
542
  - Durable artifacts now go through an `ArtifactStore` seam. The built-in implementation is `LocalArtifactStore`, intended for local disk or a shared mounted volume.
447
543
  - Fresh-process resume requeues any locally claimed but not yet submitted items before continuing, so a process crash after request-artifact persistence does not strand work in `queued_local`.
448
544
 
@@ -489,51 +585,6 @@ The default pytest configuration enforces an `85%` coverage floor.
489
585
 
490
586
  GitHub Actions pull requests run:
491
587
 
492
- - `test (3.12)` and `test (3.13)`: `uv run ty check src` plus `uv run pytest -q`
493
- - `docs`: `uv run mkdocs build --strict`
494
- - `build (3.13)`: `uv build`
495
- - `postgres-contract`: `uv run pytest tests/unit/test_batchor_storage_contracts.py --no-cov -q` with an ephemeral PostgreSQL service
496
-
497
- The live OpenAI smoke remains manual-only and is not part of required CI.
498
-
499
- Manual live OpenAI smoke:
500
-
501
- ```bash
502
- export OPENAI_API_KEY=sk-...
503
- export BATCHOR_RUN_LIVE_TESTS=1
504
- uv run pytest tests/integration/test_batchor_live_openai.py --no-cov -q
505
- ```
506
-
507
- If `BATCHOR_LIVE_OPENAI_MODEL` is unset, the harness defaults to `gpt-5-nano`.
508
- If `BATCHOR_LIVE_OPENAI_REASONING_EFFORT` is unset, no reasoning field is sent.
509
- The live smoke also requires an OpenAI account with Batch API access and available billing quota.
510
-
511
- ## Documentation guide
512
-
513
- - `docs/design_docs/ARCHITECTURE.md`: canonical runtime diagrams and module boundaries
514
- - `docs/design_docs/STORAGE_AND_RUNS.md`: durable run, resume, and artifact lifecycle semantics
515
- - `docs/getting-started/use-cases.md`: practical single-file, multi-file, and pipeline examples
516
- - `docs/getting-started/python-api.md`: Python-first workflows
517
- - `docs/getting-started/cli.md`: operator CLI workflows
518
- - `docs/reference/api.md`: generated API surface
519
- - `docs/design_docs/ARCHITECTURE.md`: package boundaries
520
- - `docs/design_docs/OPENAI_BATCHING.md`: OpenAI-specific behavior
521
- - `docs/design_docs/STORAGE_AND_RUNS.md`: durability, rehydration, and artifacts
522
- - `docs/smoke-test.md`: minimum validation bar
523
-
524
- ## Scope
525
-
526
- Today `batchor` is intentionally narrow:
527
-
528
- - one built-in provider: OpenAI Batch
529
- - one default durable backend: SQLite
530
- - one opt-in durable backend: Postgres
531
- - one ephemeral backend: in-memory storage
532
- - structured output is Python API-first
533
- - CLI job creation is file-backed and supports both text and structured-output jobs
534
-
535
- Anything outside that scope should be treated as out of scope or `TBD`, not implied support.
536
-
537
588
  ## License
538
589
 
539
590
  This project is licensed under PolyForm Noncommercial 1.0.0. See [LICENSE](LICENSE).