Batch content capture
Prompt samples from batch and flex jobs with track_batch_request and track_batch_result.
sp.wrap() samples prompts and completions from live calls. Batch and flex jobs have no live call, and submit() and the track_*_batch helpers record usage only, so batch work produces no prompt samples. If you loop over a job's results yourself, use these two functions instead.
They need Python SDK 0.19.0 or later (track_batch_result with request= is available from 0.17.0). Capture still requires the org opt-in in Settings; see Content capture and privacy.
When to use it
Use it for batch and flex jobs whose output files hold only responses. The prompt is in the input file you submitted, and the result arrives hours later without it. Instead of keeping every prompt until its result returns, send each half when you already have it. The server joins the two halves on the line's idempotency key.
Recommended: send each half when you have it
import superpenguin as sp
sp.init(api_key="sp-...")
# At submit time, once the batch id is known: the prompt half.
for line in input_lines:
sp.track_batch_request(
f"{batch.id}:{line['custom_id']}", # the key the usage event will carry
line, # the input line (its body is read)
{"feature": "ai_extractor"},
provider="sail_research",
)
# On completion: usage, plus the output half. No request needed.
for line in output_lines:
event = build_usage_event(line) # idempotency_key = f"{batch.id}:{custom_id}"
sp.track_batch_result(usage_event=event, response=line["response"]["body"])
sp.flush()The key passed to track_batch_request must equal the usage event's idempotency_key, for example f"{batch_id}:{custom_id}". This is the key track_openai_batch uses. If the batch id is not known until submit returns, call track_batch_request after submit.
The server pairs a result with a prompt half sent up to 3 days earlier. Batch jobs finish within 24 hours, so a normal job always pairs.
track_batch_request
sp.track_batch_request(
key: str,
request: Any,
metadata: dict | None = None,
*,
provider: str | None = None,
base_url: str | None = None,
) -> bool| Parameter | Description |
|---|---|
key | The line's idempotency key, the same one its usage event carries. |
request | The input line's request body, or the whole input line (its body is used). |
metadata | Attribution such as feature, customer_id and prompt_key. It outranks an active sp.metadata() or @sp.trace scope. Where the result's usage event also sets a field, the usage event wins. |
provider, base_url | The provider, as on the usage event. |
Returns True when the prompt half was queued for upload, and False when the line is not sampled or capture is off.
One call, if you still hold the prompts
If you keep the request body until the result returns, pass it as request= and one call sends the usage event and one complete sample:
for line in output_lines: # the job's output JSONL, parsed
custom_id = line["custom_id"]
event = build_usage_event(line) # the dict you pass to submit() today
sp.track_batch_result(
request=request_lines[custom_id]["body"], # the matching input line's body
response=line["response"]["body"],
usage_event=event,
)
sp.flush()This path samples at the org's adaptive rate, the same rate sp.wrap() uses. Reporting the same result twice stores one sample.
track_batch_result
sp.track_batch_result(
*,
response: Any,
request: Any = None,
usage_event: dict | None = None,
key: str | None = None,
provider: str | None = None,
base_url: str | None = None,
batch_id: str | None = None,
custom_id: str | None = None,
completed_at: Any = None,
metadata: dict | None = None,
region: str | None = None,
deployment_type: str | None = None,
service_tier: str | None = None,
) -> bool| Parameter | Description |
|---|---|
response | The result's response body, or the whole output line (its response.body is used). |
request | The input line's request body, or the whole input line. Omit it after track_batch_request to send only the output half. |
usage_event | The usage dict you already pass to submit(). It is submitted unchanged. |
key | Join key of the output half when request is omitted. Defaults to the usage event's idempotency_key. |
provider, batch_id, custom_id | Without usage_event, the SDK builds the event the way track_openai_batch does and needs all three. custom_id is taken from a whole input or output line when not given. |
completed_at | The job's completion time (datetime, unix seconds or ISO string). Backdates a built event; also stamps the sample when the usage event has no created_at. |
metadata | Attribution for a built event. With usage_event, it only fills sample attribution the event does not carry. |
base_url, region, deployment_type, service_tier | Pricing fields for a built event. |
Returns True when a usage event was submitted. A built event is skipped for a non-200 result line, matching track_openai_batch. Without usage_event, and without provider, batch_id and custom_id, it raises ValueError.
Chat (messages, choices) and Responses (instructions, input, output) bodies are both read, including the system prompt.
Guarantees
- Spend never depends on capture.
track_batch_resultsubmits the usage event first and unchanged, exactly assubmit()does. Capture runs afterwards, and a capture error never affects the usage event. - Only sampled lines send anything. In the split pattern, both calls hash the key against your organization's stable batch rate, so they make the same decision without shared state. An unsampled line reads no text and makes no network call. The SDK fetches your organization's capture settings once and caches them.
- The same privacy rules apply. The org opt-in, redaction and encryption at rest apply as for every sample.
sp.configure_content_capture()can still narrow capture:allow=Falsevetoes both halves,record_prompt=Falsedrops the prompt half,record_outcome=Falsedrops the output half, andsample_rate_multiplierscales the batch rate. - Unpaired halves expire. A half without its partner, such as a prompt half whose result never arrives, is discarded after 14 days.
- Capture is optional. Skipping
track_batch_requestjust means no samples for those lines. Usage and cost are recorded the same way.
Related
- Python advanced usage for the
track_*_batchusage helpers - Batch and service-tier pricing
- TypeScript batch content capture