# Understanding the Relationship Between Chandra's BatchInputItem and BatchOutputItem Schemas

> Explore the one-to-one relationship between Chandra's BatchInputItem and BatchOutputItem schemas. Understand how input images transform into detailed model outputs in the inference pipeline.

- Repository: [Datalab/chandra](https://github.com/datalab-to/chandra)
- Tags: api-reference
- Published: 2026-03-27

---

**BatchInputItem and BatchOutputItem form a strict one-to-one request-response pair in Chandra's inference pipeline, where each input item containing an image and optional prompt is transformed by the model into a corresponding output item containing parsed markdown, HTML, bounding boxes, and generation metadata.**

In the `datalab-to/chandra` repository, these two dataclasses define the complete lifecycle of a single document processing operation. Understanding how `BatchInputItem` and `BatchOutputItem` relate to each other is essential for implementing custom batch inference workflows or integrating the model into production pipelines.

## BatchInputItem and BatchOutputItem Schema Overview

### The Input Schema: BatchInputItem

`BatchInputItem` defines everything the model requires to process a single document. Located in [`chandra/model/schema.py`](https://github.com/datalab-to/chandra/blob/main/chandra/model/schema.py), this dataclass encapsulates the source image and optional guidance parameters.

- **`image`** (`Image.Image`): The source document or page provided as a PIL Image.
- **`prompt`** (`str | None`): Optional user instructions to guide generation.
- **`prompt_type`** (`str | None`): Strategy identifier such as `"ocr_layout"` or `"ocr"` that controls how the model interprets the image.

### The Output Schema: BatchOutputItem

`BatchOutputItem` captures the complete result set generated for each input. Also defined in [`chandra/model/schema.py`](https://github.com/datalab-to/chandra/blob/main/chandra/model/schema.py), it contains both rendered formats and raw metadata extracted during inference.

- **`markdown`** (`str`): Rendered markdown representation of the document content.
- **`html`** (`str`): Rendered HTML version of the output.
- **`chunks`** (`dict`): Bounding-box text fragments parsed from the raw output.
- **`raw`** (`str`): Unprocessed model output string.
- **`page_box`** (`List[int]`): Image dimensions formatted as `[0, 0, width, height]`.
- **`token_count`** (`int`): Number of tokens consumed during generation.
- **`images`** (`dict`): Extracted images discovered within the output.
- **`error`** (`bool`): Flag indicating whether generation failed for this item.

## How InferenceManager Maps Input to Output

The relationship between these schemas is implemented as a positional transformation inside `InferenceManager.generate` in [`chandra/model/__init__.py`](https://github.com/datalab-to/chandra/blob/main/chandra/model/__init__.py). The method receives a list of `BatchInputItem` objects and returns a corresponding list of `BatchOutputItem` objects, maintaining index alignment throughout the pipeline.

### Input Collection

Users or client code construct a Python list of `BatchInputItem` instances, as demonstrated in [`tests/integration/test_image_inference.py`](https://github.com/datalab-to/chandra/blob/main/tests/integration/test_image_inference.py) and the CLI entry point at [`chandra/scripts/cli.py`](https://github.com/datalab-to/chandra/blob/main/chandra/scripts/cli.py). Each item bundles the image data with optional prompt configuration.

### Processing and Generation

Inside `InferenceManager.generate`, the batch is submitted to either the VLLM or HuggingFace backend. The system iterates over the input list, generating a `GenerationResult` for each `BatchInputItem` through the underlying model inference.

### Output Construction

The critical linkage occurs where the code zips results with their originating inputs. For each pair, the method constructs a `BatchOutputItem` that combines the raw generation result with metadata derived from the original input image:

```python

# From chandra/model/__init__.py - InferenceManager.generate

for result, input_item in zip(results, batch):
    chunks = parse_chunks(result.raw, input_item.image, bbox_scale=bbox_scale)
    output.append(
        BatchOutputItem(
            markdown=parse_markdown(result.raw, **output_kwargs),
            html=parse_html(result.raw, **output_kwargs),
            chunks=chunks,
            raw=result.raw,
            page_box=[0, 0, input_item.image.width, input_item.image.height],
            token_count=result.token_count,
            images=extract_images(result.raw, chunks, input_item.image),
            error=result.error,
        )
    )

```

Notice how `page_box` explicitly references `input_item.image.width` and `input_item.image.height`, demonstrating that output metadata is enriched with properties from the corresponding input item. The `chunks` and `images` fields also depend on both the generation result and the original image dimensions.

## Practical Batch Processing Example

The following implementation illustrates the complete flow from input definition to output consumption:

```python
from chandra.model.schema import BatchInputItem, BatchOutputItem
from chandra.model import InferenceManager
from PIL import Image

# 1️⃣ Build a batch of inputs

batch = [
    BatchInputItem(
        image=Image.open("sample_page.png"),
        prompt="Summarize the table on page 1",
        prompt_type="ocr_layout",
    ),
    BatchInputItem(
        image=Image.open("invoice.jpg"),
        prompt=None,
        prompt_type="ocr",
    ),
]

# 2️⃣ Run inference (VLLM is the default backend)

manager = InferenceManager(method="vllm")
outputs: list[BatchOutputItem] = manager.generate(batch)

# 3️⃣ Access the paired output

for inp, out in zip(batch, outputs):
    print("Input image size:", inp.image.size)
    print("Generated Markdown:", out.markdown[:200])
    print("Token count:", out.token_count)
    print("---")

```

This example demonstrates the one-to-one correspondence: the first output corresponds to the first input (the table summarization), and the second output corresponds to the second input (the OCR-only invoice processing).

## Key Implementation Files

Understanding the relationship between these schemas requires examining four critical files in the `datalab-to/chandra` repository:

- **[`chandra/model/schema.py`](https://github.com/datalab-to/chandra/blob/main/chandra/model/schema.py)**: Defines the dataclass structures for both `BatchInputItem` and `BatchOutputItem`, including type hints and default values.

- **[`chandra/model/__init__.py`](https://github.com/datalab-to/chandra/blob/main/chandra/model/__init__.py)**: Contains `InferenceManager.generate`, the core method that implements the input-to-output mapping logic and constructs `BatchOutputItem` instances from generation results.

- **[`tests/integration/test_image_inference.py`](https://github.com/datalab-to/chandra/blob/main/tests/integration/test_image_inference.py)**: Provides integration test cases showing how `BatchInputItem` objects are instantiated and passed through the inference pipeline.

- **[`chandra/scripts/cli.py`](https://github.com/datalab-to/chandra/blob/main/chandra/scripts/cli.py)**: Command-line interface implementation demonstrating batch construction from file paths and argument parsing.

## Summary

- **BatchInputItem** and **BatchOutputItem** maintain a strict one-to-one positional relationship throughout the inference pipeline.
- **Input items** carry the source image and optional prompts, while **output items** contain parsed markdown, HTML, bounding box chunks, and generation metadata.
- **The mapping** is implemented in `InferenceManager.generate` inside [`chandra/model/__init__.py`](https://github.com/datalab-to/chandra/blob/main/chandra/model/__init__.py), where each output explicitly references properties of its corresponding input (such as image dimensions).
- **Index alignment** is preserved through Python's `zip()` function, ensuring that `outputs[i]` always corresponds to `batch[i]`.

## Frequently Asked Questions

### How does Chandra ensure each BatchOutputItem corresponds to the correct BatchInputItem?

Chandra maintains index alignment through positional iteration. In `InferenceManager.generate`, the code uses `zip(results, batch)` to pair each generation result with its originating input item at the same list index. This positional mapping guarantees that the first output corresponds to the first input, the second to the second, and so on, without requiring unique identifiers.

### Can I access the original input image from a BatchOutputItem?

No, `BatchOutputItem` does not store a reference to the input image object directly. However, it stores the original image dimensions in the `page_box` field (formatted as `[0, 0, width, height]`), which is extracted from `input_item.image` during output construction. If you need the full image, you must maintain a separate reference to your original `BatchInputItem` list and access it by index.

### What happens if one item in the batch fails during generation?

The `BatchOutputItem` includes an `error` boolean field that indicates whether generation failed for that specific item. When an error occurs, the `raw` field typically contains error details while other fields like `markdown` and `html` may be empty strings. The remaining items in the batch continue processing normally, preserving the one-to-one relationship between inputs and outputs even for failed generations.

### Where are the BatchInputItem and BatchOutputItem schemas defined?

Both dataclasses are defined in [`chandra/model/schema.py`](https://github.com/datalab-to/chandra/blob/main/chandra/model/schema.py). The input schema contains fields for `image`, `prompt`, and `prompt_type`, while the output schema contains `markdown`, `html`, `chunks`, `raw`, `page_box`, `token_count`, `images`, and `error`. These definitions use Python dataclasses with type hints for validation and IDE support.