Skip to content

feat: Add batch API support for entity extraction (Gemini + OpenAI) - #2901

Closed
acorretti wants to merge 14 commits into
HKUDS:mainfrom
acorretti:feature/batch-api-ingestion
Closed

feat: Add batch API support for entity extraction (Gemini + OpenAI)#2901
acorretti wants to merge 14 commits into
HKUDS:mainfrom
acorretti:feature/batch-api-ingestion

Conversation

@acorretti

Copy link
Copy Markdown

Description

Adds batch API support for LightRAG's entity extraction pipeline. Depending on the provider, this may lead to reduced ingestion costs by submitting all extraction prompts as a single batch job instead of individual concurrent calls. The tradeoff is higher latency — batch jobs may queue for minutes to hours before processing starts.

Two new LLM bindings -- gemini_batch and openai_batch -- use their respective provider batch APIs for entity extraction during ingestion while keeping queries on the live API for real-time responses.

Architecture

  • BatchProvider abstraction (batch_provider.py): Provider-agnostic ABC with submit_completion_batch(), get_job_status(), get_results(), cancel_job(), and a default polling implementation
  • GeminiBatchProvider (gemini_batch.py): Uses client.aio.batches.create() with inlined requests
  • OpenAIBatchProvider (openai_batch.py): Uses JSONL file upload -> Files API -> Batch API -> results download
  • Batch extraction path (operate.py): Bypasses the per-chunk semaphore, pre-generates all prompts, checks the LLM cache, submits cache misses as a batch, and handles gleaning as a second batch round

Key Features

  • Automatic sub-batch splitting: When a provider rejects a batch for exceeding token limits, automatically splits in half and retries (recursive binary search)
  • Restart recovery: Batch job state (job IDs + submitted chunk keys) is persisted in llm_response_cache. If the server restarts mid-batch, re-ingesting the same document resumes polling instead of resubmitting
  • Graceful fallback: Per-row failures and entire batch failures fall back to the live API individually
  • Pipeline cancellation: Cancelling the pipeline also cancels the provider-side batch job
  • Cache compatibility: Batch results populate the same LLM cache as live API calls -- switching between batch and live modes is seamless

Related Issues

Closes #2210

Changes Made

New Files

  • lightrag/llm/batch_provider.py -- BatchProvider ABC + BatchRequest/BatchResponse/BatchJobStatus data types
  • lightrag/llm/gemini_batch.py -- GeminiBatchProvider implementation
  • lightrag/llm/openai_batch.py -- OpenAIBatchProvider implementation
  • tests/test_batch_extraction.py -- 19 offline unit tests (merge logic, cache compatibility, persistence, auto-split, cancellation)
  • docs/BatchAPIImplementationPlan.md -- Design document with architecture, retry strategies, and edge cases

Modified Files

  • lightrag/operate.py -- Batch extraction path, auto-split logic, job persistence, polling helpers
  • lightrag/lightrag.py -- batch_provider, llm_batch_timeout, llm_batch_poll_interval config fields; _build_global_config() helper for safe asdict() handling
  • lightrag/api/lightrag_server.py -- gemini_batch and openai_batch binding wiring, validation, provider instantiation
  • lightrag/api/config.py -- Batch binding arg registration and default hosts
  • scripts/setup/setup.sh -- Batch bindings in setup wizard
  • env.example -- Batch configuration examples

Configuration

# OpenAI Batch (50% cost savings)
LLM_BINDING=openai_batch
LLM_MODEL=gpt-4o-mini
LLM_BATCH_TIMEOUT=86400
LLM_BATCH_POLL_INTERVAL=30

# Gemini Batch (50% cost savings)
LLM_BINDING=gemini_batch
LLM_MODEL=gemini-2.0-flash
LLM_BATCH_TIMEOUT=86400
LLM_BATCH_POLL_INTERVAL=30

Checklist

  • Changes tested locally
  • Code reviewed
  • Documentation updated
  • Unit tests added (19 offline tests)

Additional Notes

  • Batch APIs are designed for non-time-critical workloads -- jobs may queue for minutes to hours before processing starts
  • EMBEDDING_BINDING is independent of LLM_BINDING -- set it to match your existing vector store
  • The BatchProvider abstraction is designed for extensibility -- adding new providers requires implementing 4 methods

acorretti added 14 commits April 5, 2026 20:44
Introduce a provider-agnostic BatchProvider ABC for submitting LLM
completion requests as batch jobs, with data types for request/response
handling and job lifecycle management.

Add GeminiBatchProvider that implements the abstraction using
Google's Gemini Batch Prediction API (client.aio.batches), with
inlined request submission, polling, and result retrieval.
Add batch_provider, llm_batch_timeout, and llm_batch_poll_interval
fields for opt-in batch API ingestion support. These flow through
global_config to the extraction pipeline.
When batch_provider is set in global_config, extract_entities() takes
an alternate path that pre-generates all prompts, checks the LLM cache,
submits cache misses as a single batch via BatchProvider, and optionally
runs a second batch for gleaning. Failed rows fall back to the live API.

Includes _merge_gleaning_results() and _await_batch_with_cancellation()
helpers shared between batch and live paths.
Add gemini_batch as a recognized LLM/embedding binding that reuses
Gemini's live API functions for queries while additionally creating a
GeminiBatchProvider for batch entity extraction during ingestion.
Users can now select gemini_batch as an LLM or embedding provider in
the interactive setup flow. It shares Gemini's configuration prompts
(API key, endpoint, model) and default values.
Document the LLM_BINDING=gemini_batch option with LLM_BATCH_TIMEOUT
and LLM_BATCH_POLL_INTERVAL settings.
Store {job_id, submitted_keys} in llm_response_cache after submitting
a batch. On re-entry, if a persisted job exists and the chunk keys
match the current cache misses, resume polling instead of resubmitting.
Clear persisted state once the job reaches a terminal state.
OpenAIBatchProvider implements BatchProvider using OpenAI's Batch API
(JSONL file upload -> batch creation -> polling -> results download).
Wire openai_batch as a selectable binding through config, server,
setup wizard, and env.example.
When a batch fails with a token limit error (detected via error_code
from the provider), automatically split the unresolved requests in
half and resubmit as smaller sub-batches. This handles both submission-
time rejections and post-submission validation failures (like OpenAI's
enqueued token limit). Persistence updated to store multiple job IDs.
19 offline tests covering:
- _merge_gleaning_results: new/existing entities, description length
  comparison, edges, None handling
- Batch extraction: all-cached, all-miss, mixed cache, per-row failure
  fallback, entire batch failure fallback, gleaning as second batch
- Auto-split on token limit errors (recursive halving)
- Cache key compatibility between batch and live paths
- Job persistence: save after submission, resume on matching keys,
  discard on mismatched keys
- Pipeline cancellation cancels provider-side job
The split condition checked status.total > 1, but OpenAI doesn't
populate request_counts when rejecting a batch for token limits
(total=0). Removed the total check and instead guard against infinite
splitting by checking len(unresolved) > 1. Updated test to match
real OpenAI behavior (total=0 on rejection).
When a sub-batch failed with token_limit_exceeded, the split logic
scanned all cache_misses instead of just the failed job's requests,
causing duplicate sub-batches. Now each job ID maps to its specific
requests via job_requests dict, so splitting only affects the failed
job's requests.
…ting

The "Enqueued token limit reached" error means the org-wide batch
queue is full, not that the individual batch is too large. Splitting
only created more queued batches, worsening the problem. Now waits
for the queue to drain (2x poll interval) then resubmits the same
batch unchanged.
@danielaskdd danielaskdd added enhancement New feature or request feature labels Apr 6, 2026
@acorretti

Copy link
Copy Markdown
Author

Hi @danielaskdd — thanks for triaging this last week. No rush, I can see you're busy.
One quick thing I just realized: should I retarget this to dev instead of main? Happy to rebase.

No pressure on timing. Thanks for maintaining LightRAG.

@danielaskdd

Copy link
Copy Markdown
Collaborator

Thank you for your interest and contributions to LightRAG. Given the significant changes introduced in version 1.5, we will review this PR following the official release of v1.5.

@sacr1ficerq

Copy link
Copy Markdown

@acorretti, dude you are a hero! I really needed this feature, and randomly stumbling on this PR was a great surprise. Great work!

@github-actions

Copy link
Copy Markdown

This pull request has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs.

@github-actions github-actions Bot added the Stale label Jul 28, 2026
@danielaskdd danielaskdd added the tracked Issue is tracked by project label Jul 29, 2026
@github-actions github-actions Bot removed the Stale label Jul 29, 2026
@danielaskdd
danielaskdd marked this pull request as draft August 16, 2026 04:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request tracked Issue is tracked by project

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Question]: Support for Batch LLM API to reduce costs?

3 participants