LLM Batch Service
The LLM Batch Service lets you process large volumes of LLM requests asynchronously. Instead of making individual synchronous inference calls, you submit a collection of requests as a single JSONL input file. The service processes them in the background and writes the results to your object store.
Note: Batch consumption only supports native LLM calls. Orchestration requests are not supported.
Batch consumption is available in EU and US regions, except
prod-euonlyand sovereign cloud deployments.
Key capabilities:
Process hundreds or thousands of LLM requests in a single submission
Reduced cost compared to synchronous inference calls
Automatic retry handling for transient provider errors
No rate-limit management required on the client side
Prerequisites
Before using the SDK, complete the setup steps described in the SAP Help Portal — Batch Consumption:
Ensure you have a valid SAP AI Core service instance with access to the generative AI hub.
Register an object store secret for one of the supported providers (Amazon S3, Azure Blob Storage, Google Cloud Storage, Alibaba Cloud OSS, or SAP HANA Cloud, Data Lake).
Prepare your input file as a JSON Lines (
.jsonl) file and upload it to your object store, following the input file specification and upload instructions on the Help Portal.
Once the input file is uploaded, note down its ai:// URI and the ai:// URI of the output directory — you will need both below.
The BatchService initialises a GenAIHubProxyClient automatically, which reads credentials from configuration files or environment variables. You can also pass a custom proxy_client instance.
from gen_ai_hub.batch_service import BatchService
# Initialise using credentials from the environment / config file
batch_service = BatchService()
# Or provide a proxy client explicitly:
# from gen_ai_hub.proxy import get_proxy_client
# proxy_client = get_proxy_client('gen-ai-hub')
# batch_service = BatchService(proxy_client=proxy_client)
# Configure these values before running the workflow cells
INPUT_URI = "ai://<object_store_secret_name>/<path>/input.jsonl"
OUTPUT_URI = "ai://<object_store_secret_name>/<output_folder>/"
MODEL = "gpt-4.1" # Must match the model used in the input file
PROVIDER = "azure-openai"
Create a Batch Job
Submit the batch job by calling batch_service.create(). Provide:
Parameter |
Description |
|---|---|
|
Batch processing type. Only |
|
|
|
|
|
LLM provider (e.g. |
|
Model name — must match the value used in the input file. |
The service schedules the job and returns a unique batch ID.
create_response = batch_service.create(
type="llm-native",
input_uri=INPUT_URI,
output_uri=OUTPUT_URI,
provider=PROVIDER,
model=MODEL,
)
BATCH_ID = create_response.id
print(f"Batch ID: {BATCH_ID}")
print(f"Status: {create_response.status}")
print(f"Message: {create_response.message}")
Check Batch Status
Batches are processed asynchronously. Poll the status endpoint until the job reaches a terminal state (COMPLETED, FAILED, or CANCELLED).
import time
TERMINAL_STATUSES = {"COMPLETED", "FAILED", "CANCELLED"}
POLLING_INTERVAL = 30 # seconds
while True:
status_response = batch_service.get_status(BATCH_ID)
current = status_response.current_status
print(f"[{time.strftime('%H:%M:%S')}] Status: {current} → target: {status_response.target_status}")
if current in TERMINAL_STATUSES:
break
time.sleep(POLLING_INTERVAL)
print(f"\nFinal status: {current}")
if status_response.message:
print(f"Message: {status_response.message}")
List Batch Jobs and Get Batch Details
List all batch jobs
Retrieve a summary of all batch jobs for the current resource group.
list_response = batch_service.list()
print(f"Total batch jobs: {list_response.count}")
for job in list_response.resources or []:
print(f" {job.id} | {job.provider:<15} | {job.created_at} | {job.status}")
Get full details of a batch job
Retrieve the complete details of a specific batch job: input/output URIs, provider, model, and current status.
detail = batch_service.get(BATCH_ID)
print(f"ID: {detail.id}")
print(f"Type: {detail.type}")
print(f"Provider: {detail.provider}")
print(f"Created at: {detail.created_at}")
print(f"Input URI: {detail.input.uri}")
print(f"Output URI: {detail.output.uri}")
print(f"Model: {detail.spec.get('model') if detail.spec else None}")
print(f"Status: {detail.status.current_status}")
print(f"Updated at: {detail.status.updated_at}")
if detail.status.message:
print(f"Message: {detail.status.message}")
Cancel a Batch Job
You can cancel a batch job that is in a non-terminal state (e.g. PENDING or IN_PROGRESS). Cancellation is asynchronous — the job transitions to CANCELLING and then CANCELLED after any in-flight provider requests have been terminated.
Use get_status() to track the cancellation progress.
# Cancel the batch job — only meaningful if the job is still in a non-terminal state.
# Skip this cell if the job has already completed.
cancel_response = batch_service.cancel(BATCH_ID)
print(f"ID: {cancel_response.id}")
print(f"Message: {cancel_response.message}")
# Poll until the cancellation is confirmed
while True:
status_response = batch_service.get_status(BATCH_ID)
print(f"Status: {status_response.current_status}")
if status_response.current_status in TERMINAL_STATUSES:
break
time.sleep(10)
Delete a Batch Job
Deleting a job removes its metadata from the service. Output files in the object store are not affected.
Restriction: Only batch jobs in
COMPLETED,FAILED, orCANCELLEDstate can be deleted.
# Confirm the job is in a deletable state before deleting
status_response = batch_service.get_status(BATCH_ID)
deletable = {"COMPLETED", "FAILED", "CANCELLED"}
if status_response.current_status not in deletable:
print(f"Cannot delete: job is in state '{status_response.current_status}'. Cancel it first.")
else:
delete_response = batch_service.delete(BATCH_ID)
print(f"Deleted batch job: {delete_response.id}")
print(f"Message: {delete_response.message}")
Retrieving Results
Once the job status is COMPLETED, the results are available in your object store under the output URI specified at creation, in a subdirectory named after the batch ID. Successful responses are written to output.jsonl and any failed individual requests to error.jsonl, each line matched to its input by custom_id.
Async Support
All BatchService methods have async counterparts: acreate, alist, aget, aget_status, acancel, adelete. The example below runs the full workflow asynchronously.
import asyncio
async def run_batch_async():
service = BatchService()
# Create
resp = await service.acreate(
type="llm-native",
input_uri=INPUT_URI,
output_uri=OUTPUT_URI,
provider=PROVIDER,
model=MODEL,
)
batch_id = resp.id
print(f"Created batch: {batch_id} ({resp.status})")
# Poll until terminal
while True:
status = await service.aget_status(batch_id)
print(f"Status: {status.current_status}")
if status.current_status in TERMINAL_STATUSES:
break
await asyncio.sleep(30)
# Get full details
detail = await service.aget(batch_id)
print(f"Completed. Output at: {detail.output.uri}{batch_id}/output.jsonl")
await service.aclose_http_connection()
await run_batch_async()