Metadata-Version: 2.4
Name: feather-structured-output
Version: 0.1.1
Summary: Add your description here
License-File: LICENSE
Requires-Python: >=3.13
Description-Content-Type: text/markdown

# feather-structured-output

`feather-structured-output` is a lightweight Python library for turning LLM text
responses into validated Python dictionaries.

It does not call an LLM, retry requests, or manage conversation history. It only
handles schema definition, short JSON prompt generation, JSON extraction, field
validation, normalization, and retry-friendly error messages.

## Installation

This repository uses `uv`.

```powershell
uv run pytest
```

## Basic Usage

```python
from feather_structured_output import IntField, Schema, StringField

schema = Schema(
    instruction="Extract the following JSON.",
    fields=[
        StringField(
            source_label="Company name",
            output_name="name",
            description="Use the formal company name.",
            example="Acme Corp",
        ),
        IntField(
            source_label="Quantity",
            output_name="quantity",
            gt=0,
        ),
    ],
)

prompt = schema.dump_prompt()
```

`prompt` becomes a compact JSON example with comments:

```text
Extract the following JSON.

{
  "name": "Acme Corp", // Company name。String. Use the formal company name. Required.
  "quantity": 1 // Quantity。Integer. Greater than 0. Required.
}
```

Pass that prompt to any LLM client yourself, then validate the response:

```python
result = schema.extract('{"name": "Acme Corp", "quantity": 3}')

if result.ok:
    data = result.data
else:
    retry_prompt = result.error_message
```

## Fields

Available field types:

- `StringField`
- `IntField`
- `FloatField`
- `BooleanField`
- `ObjectField`
- `UnionField`

Common field options:

- `source_label`: label from the source document or UI; optional
- `output_name`: JSON key; if omitted, `source_label` is used
- `description`: extra instruction for the LLM
- `example`: sample value in the generated prompt
- `allowed_values`: fixed allowed values for the field
- `presence`: missing value policy
- `repeatable`, `min_items`, `max_items`: array support

At least one of `output_name` or `source_label` is required when a field is used
in a `Schema`.

## Presence Policy

```python
from feather_structured_output import PresencePolicy, StringField

StringField(
    output_name="note",
    presence=PresencePolicy.NULLABLE,
)
```

Policies:

- `REQUIRED`: value is required
- `OMITTABLE`: missing value is omitted from normalized data
- `NULLABLE`: missing value is normalized to `None`
- `BLANKABLE`: missing value is normalized to `""`

`""` is treated as a missing value during validation.

## Arrays and Objects

```python
from feather_structured_output import IntField, ObjectField, StringField

items = ObjectField(
    output_name="items",
    repeatable=True,
    min_items=1,
    fields=[
        StringField(output_name="name"),
        IntField(output_name="quantity", gt=0),
    ],
)
```

## Fixed Values and Unions

Use `allowed_values` for fixed values. Use `UnionField` when a value may match
more than one field shape.

```python
from feather_structured_output import IntField, StringField, UnionField

dog_age = UnionField(
    output_name="dog_age",
    variants=[
        StringField(allowed_values=["N/A"]),
        StringField(allowed_values=["-"]),
        IntField(gt=0),
    ],
)
```

## JSON Extraction

`Schema.extract()` accepts raw JSON or a single fenced `json` code block. Text
outside one JSON code block is ignored.

````python
result = schema.extract(
    """Here is the result:
```json
{"name": "Acme Corp", "quantity": 3}
```"""
)
````

Multiple JSON code blocks, non-JSON code blocks, invalid JSON, and non-object
JSON values return `ExtractResult` with validation errors.

## Custom Validators

Use validators for cross-field rules.

```python
from feather_structured_output import IntField, Schema, StringField, ValidationError


def validate_octopus_legs(data: dict[str, object]) -> list[ValidationError]:
    if data.get("pet") == "octopus" and data.get("legs") != 8:
        return [
            ValidationError(
                path="legs",
                message={
                    "en": "legs must be 8 when pet is octopus.",
                    "ja": "pet が octopus の場合、legs は 8 である必要があります。",
                },
                actual=data.get("legs"),
            )
        ]
    return []


schema = Schema(
    fields=[
        StringField(output_name="pet"),
        IntField(output_name="legs", ge=0),
    ],
    validators=[validate_octopus_legs],
)
```

Validators run only after field validation succeeds. Exceptions raised inside a
validator are not converted to LLM-facing validation errors.

## Error Messages and Locale

English is the default. Japanese retry messages are available with
`locale="ja"`.

```python
schema = Schema(
    locale="ja",
    fields=[IntField(output_name="quantity", gt=0)],
)

result = schema.extract('{"quantity": 0}')
print(result.error_message)
```

Internal message templates are split by locale under
`src/feather_structured_output/locales/`.
