# How Episode Profiles and Speaker Profiles Configure Podcast Generation in Open Notebook

> Learn how episode and speaker profiles configure podcast generation in Open Notebook. Define structure, LLMs, and voice for seamless audio creation.

- Repository: [Luis Novo/open-notebook](https://github.com/lfnovo/open-notebook)
- Tags: how-to-guide
- Published: 2026-06-24

---

**Episode profiles and speaker profiles in Open Notebook are reusable configuration objects that define podcast structure, LLM selections, and voice characteristics, which the system resolves into concrete provider configurations before audio generation begins.**

Open Notebook separates podcast generation into two distinct configuration layers that enable modular, reusable audio content creation. By storing **episode profiles** and **speaker profiles** as database objects in SurrealDB, the system allows you to mix and match narrative structures with different voice personalities. This architecture ensures that model credentials and voice settings are validated and resolved before any heavy processing begins.

## Profile Architecture and Data Models

### EpisodeProfile (High-Level Configuration)

The `EpisodeProfile` class, defined in [`open_notebook/podcasts/models.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/podcasts/models.py) (lines 31-82), captures the structural and linguistic settings for a podcast series. It stores references to language models for outline generation and transcript creation, along with default briefing templates and segment counts.

Key fields include:

- `name`: Unique identifier for the profile
- `speaker_config`: Reference to a SpeakerProfile by name
- `outline_llm` and `transcript_llm`: Model record IDs for content generation
- `default_briefing`: System prompt providing episode context
- `num_segments`: Structural division of the episode

### SpeakerProfile (Voice and Personality)

The `SpeakerProfile` class (lines 26-70 in the same file) defines one to four speakers, each with distinct voice models and personality attributes. This enables multi-voice podcasts where each participant can use different text-to-speech providers or voice configurations.

Each speaker entry requires:

- `name`: Display name for the speaker
- `voice_id`: Provider-specific voice identifier
- `backstory` and `personality`: Context for the LLM to generate appropriate dialogue
- `voice_model`: Optional per-speaker override of the global TTS model

Validation ensures the `speakers` list contains between one and four entries with all required fields populated.

## Model Resolution and Validation

Both profiles inherit from `ObjectModel`, which handles SurrealDB persistence. Before generation, the system must convert stored model IDs into executable configurations.

The `_resolve_model_config` helper in [`models.py`](https://github.com/lfnovo/open-notebook/blob/main/models.py) loads the referenced `Model` record, retrieves credentials via Esperanto, and returns a tuple of `(provider, model_name, config_dict)`. This resolution happens for both episode-level LLMs and speaker-level TTS models.

```python

# Example resolution calls from EpisodeProfile

outline_provider, outline_model, outline_cfg = await episode_profile.resolve_outline_config()
tts_provider, tts_model, tts_cfg = await speaker_profile.resolve_tts_config()

```

The `_prepare_save_data` hook ensures user-supplied IDs are converted to proper `RecordID` objects before persistence.

## The Podcast Generation Workflow

When you submit a generation request via the API, the system executes a coordinated workflow across multiple layers:

1. **API Validation**: `PodcastService.submit_generation_job` in [`api/podcast_service.py`](https://github.com/lfnovo/open-notebook/blob/main/api/podcast_service.py) validates that profile names exist and are properly configured.

2. **Command Queueing**: The service queues a `generate_podcast` command via surreal-commands, passing the profile names and content.

3. **Profile Loading**: The `generate_podcast_command` in [`commands/podcast_commands.py`](https://github.com/lfnovo/open-notebook/blob/main/commands/podcast_commands.py) (lines 84-98) fetches both profiles by name using `EpisodeProfile.get_by_name` and `SpeakerProfile.get_by_name`.

4. **Model Resolution**: The command resolves all model IDs to concrete provider configurations. This step is critical because the downstream `podcast-creator` library validates every profile before audio generation.

5. **Library Configuration**: The command injects resolved data into the third-party library:

```python
configure("speakers_config", {"profiles": speaker_profiles_dict})
configure("episode_config", {"profiles": episode_profiles_dict})

```

6. **Episode Recording**: Before invoking `create_podcast`, the system creates a `PodcastEpisode` record storing full dumps of both profiles for status tracking and reproducibility.

7. **Audio Generation**: The `podcast-creator` library uses the configured profiles to select TTS voices for each speaker and LLMs for content generation.

## Practical Implementation Examples

### Creating an Episode Profile

```python
from open_notebook.podcasts.models import EpisodeProfile

await EpisodeProfile(
    name="TechNewsWeekly",
    description="Weekly tech roundup",
    speaker_config="TechSpeakers",          # points to a SpeakerProfile

    outline_llm="model:openai:gpt-4o",      # Model record ID

    transcript_llm="model:openai:gpt-4o-mini",
    language="en-US",
    default_briefing="Summarize the latest tech headlines.",
    num_segments=5,
).save()

```

### Defining Speakers with Voice Overrides

```python
from open_notebook.podcasts.models import SpeakerProfile

await SpeakerProfile(
    name="TechSpeakers",
    speakers=[
        {
            "name": "Host",
            "voice_id": "en_us_female_1",
            "backstory": "Tech-savvy journalist",
            "personality": "cheerful, informative",
            "voice_model": "model:elevenlabs:eleven_multilingual_v2"
        },
        {
            "name": "Analyst",
            "voice_id": "en_us_male_1",
            "backstory": "Data-driven analyst",
            "personality": "analytical, calm"
        },
    ],
).save()

```

### Submitting a Generation Request

```python
import httpx

payload = {
    "episode_profile": "TechNewsWeekly",
    "speaker_profile": "TechSpeakers",
    "episode_name": "2024-06-22 Tech Update",
    "content": "Latest announcements from Apple, Google, and Microsoft..."
}
resp = httpx.post("http://localhost:5055/podcasts/generate", json=payload)
print(resp.json())   # => {"job_id": "...", "status": "queued", ...}

```

### Monitoring Generation Status

```python
job_id = "command:open_notebook:12345"
status = httpx.get(f"http://localhost:5055/podcasts/status/{job_id}").json()
print(status)   # {"job_id": "...", "status": "completed", "result": {...}}

```

## Summary

- **Episode profiles** define structural settings, LLM references, and default briefings for podcast episodes, stored in [`open_notebook/podcasts/models.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/podcasts/models.py).
- **Speaker profiles** configure one to four distinct voices with personality data and optional per-speaker TTS model overrides.
- **Model resolution** converts stored IDs into executable provider configurations before generation begins, ensuring credentials are validated via the `_resolve_model_config` helper.
- **The generation workflow** loads both profiles in [`commands/podcast_commands.py`](https://github.com/lfnovo/open-notebook/blob/main/commands/podcast_commands.py), resolves their models, configures the `podcast-creator` library, and persists a `PodcastEpisode` record for tracking.
- **Reusability** allows mixing episode structures with different speaker combinations without reconfiguring model credentials or system prompts.

## Frequently Asked Questions

### Can I use different text-to-speech providers for each speaker in the same podcast?

Yes. The `SpeakerProfile` supports per-speaker `voice_model` overrides that take precedence over the global TTS configuration. When the system resolves models in [`commands/podcast_commands.py`](https://github.com/lfnovo/open-notebook/blob/main/commands/podcast_commands.py), each speaker's specific voice model is validated and passed to the `podcast-creator` library, enabling multi-provider episodes where one speaker uses ElevenLabs and another uses a different provider.

### How does Open Notebook validate that my model IDs are correct before generating audio?

During the `generate_podcast_command` execution, the system calls `_resolve_model_config` for every referenced model ID (outline LLM, transcript LLM, and TTS models). This function loads the corresponding `Model` record from SurrealDB and verifies that credentials exist via Esperanto. If any model is missing or misconfigured, the command aborts before queuing audio generation jobs.

### What is the maximum number of speakers allowed in a single podcast episode?

The `SpeakerProfile` validation enforces a limit of four speakers per profile. The `validate_speakers` method in [`models.py`](https://github.com/lfnovo/open-notebook/blob/main/models.py) ensures the `speakers` list contains between one and four entries, each with required fields like `name`, `voice_id`, `backstory`, and `personality`.

### Where does the system store the configuration used for a specific podcast generation?

Before calling `create_podcast`, the `generate_podcast_command` creates a `PodcastEpisode` record that stores full dumps of both the `EpisodeProfile` and `SpeakerProfile` used for that generation. This record, defined in [`models.py`](https://github.com/lfnovo/open-notebook/blob/main/models.py), enables status tracking through `PodcastService.get_job_status` in [`api/podcast_service.py`](https://github.com/lfnovo/open-notebook/blob/main/api/podcast_service.py) and ensures reproducibility by preserving the exact configuration state at execution time.