> ## Documentation Index
> Fetch the complete documentation index at: https://docs.bespokelabs.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Structured output

> Use Pydantic models to get typed responses and chain LLM calls into a pipeline.

Set `response_format` to a Pydantic model, and Curator asks the model for output that matches it. Your `parse` method then gets an instance of that model instead of a string.

## Generate poems with structured output

```python theme={null}
from typing import Dict, List

from datasets import Dataset
from pydantic import BaseModel, Field

from bespokelabs import curator


class Poem(BaseModel):
    poem: str = Field(description="A poem.")


class Poems(BaseModel):
    poems: List[Poem] = Field(description="A list of poems.")


class Poet(curator.LLM):
    response_format = Poems

    def prompt(self, input: Dict) -> str:
        return f"Write two poems about {input['topic']}."

    def parse(self, input: Dict, response: Poems) -> List[Dict]:
        return [{"topic": input["topic"], "poem": p.poem} for p in response.poems]


poet = Poet(model_name="gpt-4o-mini")

topics = Dataset.from_dict({
    "topic": [
        "Urban loneliness in a bustling city",
        "The first snow of winter",
    ]
})

poems = poet(topics)
print(poems.dataset.to_pandas())
# Output:
#                                  topic                                               poem
# 0  Urban loneliness in a bustling city  In the city's heart, where the lights never di...
# 1  Urban loneliness in a bustling city  Steps echo loudly, pavement slick with rain,\n...
# 2              The first snow of winter  Soft and silent, the first flakes fall,\nA hu...
# 3              The first snow of winter  White drifts cover the sleeping ground,\nAnd ...
```

This is what the code does.

1. The Pydantic models `Poem` and `Poems` describe the output you want from the LLM.
2. `Poet` sets `response_format = Poems`. You can also pass `response_format=Poems` when you create the object.
3. For each topic, `prompt` builds the prompt and Curator sends it to the LLM.
4. `parse` gets a `Poems` object and returns one row for each poem, with the topic next to it.

The result has one poem per row, and each row keeps the topic it came from.

## Chain LLM calls

You can pass the output of one `LLM` object to the next. In this example, a `Muse` generates the topics and the `Poet` from above writes poems about them.

```python theme={null}
from typing import Dict, List

from pydantic import BaseModel, Field

from bespokelabs import curator


class Topic(BaseModel):
    topic: str = Field(description="A topic.")


class Topics(BaseModel):
    topics: List[Topic] = Field(description="A list of topics.")


class Muse(curator.LLM):
    response_format = Topics

    def prompt(self, input: Dict) -> str:
        return "Generate ten evocative poetry topics."

    def parse(self, input: Dict, response: Topics) -> List[Dict]:
        return [{"topic": t.topic} for t in response.topics]


class Poem(BaseModel):
    poem: str = Field(description="A poem.")


class Poems(BaseModel):
    poems: List[Poem] = Field(description="A list of poems.")


class Poet(curator.LLM):
    response_format = Poems

    def prompt(self, input: Dict) -> str:
        return f"Write two poems about {input['topic']}."

    def parse(self, input: Dict, response: Poems) -> List[Dict]:
        return [{"topic": input["topic"], "poem": p.poem} for p in response.poems]


muse = Muse(model_name="gpt-4o-mini")
topics = muse()
print(topics.dataset.to_pandas())

poet = Poet(model_name="gpt-4o-mini")
poems = poet(topics)
print(poems.dataset.to_pandas())
# Output:
#                                   topic                                               poem
# 0  The fleeting beauty of autumn leaves  In a whisper of wind, they dance and they sway...
# 1  The fleeting beauty of autumn leaves  Once vibrant with life, now a radiant fade,\nC...
# 2    The whispers of an abandoned house  In shadows deep where light won't tread,  \nAn...
# 3    The whispers of an abandoned house  Abandoned now, my heart does fade,  \nOnce a h...
# ...
```

`muse()` has no input, so Curator sends one request. You can pass the `CuratorResponse` it returns straight to `poet`. Each step fans out the rows of the step before it, so a short chain can produce a large dataset.

<Note>
  Not every model supports structured output. If a model does not support it, Curator raises an error before it sends any requests.
</Note>
