# How Episode and Speaker Profiles Enable Multi-Speaker Podcast Generation

> Discover how episode and speaker profiles in lfnovo open-notebook power multi-speaker podcast generation by defining structure, voice models, and personalities for unique audio experiences.

- Repository: [Luis Novo/open-notebook](https://github.com/lfnovo/open-notebook)
- Tags: deep-dive
- Published: 2026-06-27

---

**Episode profiles define the structural blueprint and LLM configuration for a podcast, while speaker profiles encapsulate voice models, personalities, and backstories for up to four distinct speakers, enabling the system to composite multiple voice tracks into a cohesive multi-speaker podcast.**

The `open-notebook` repository treats podcast generation as a declarative composition of two distinct configuration objects. By separating the episode structure from speaker identity, the architecture supports complex conversational dynamics where each participant maintains a consistent voice and personality throughout the generated audio.

## The Architecture of Multi-Speaker Podcast Generation

The system implements a strict separation of concerns between **what** gets produced and **who** produces it. This design allows content creators to remix episode formats with different speaker rosters without changing underlying generation logic.

### EpisodeProfile: Configuring the Podcast Structure

The `EpisodeProfile` class in [`open_notebook/podcasts/models.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/podcasts/models.py) serves as the macro-configuration container. It defines the episode's shape through several critical fields:

- **`speaker_config`** – Stores the name of the `SpeakerProfile` to use (lines 34-35), establishing the link between structure and voice roster.
- **`outline_llm`** and **`transcript_llm`** – Reference specific LLM models for generating the episode outline and dialogue transcript (lines 68-74).
- **`language`**, **`default_briefing`**, and **`num_segments`** – Control localization, content direction, and episode length.

### SpeakerProfile: Defining Voice and Personality

The `SpeakerProfile` class manages the micro-configuration of individual speakers. At its core lies the **`speakers`** list (lines 54-57), where each entry is a dictionary containing:

- **`name`** – The speaker identifier used in transcript generation.
- **`voice_id`** – The TTS engine reference (e.g., ElevenLabs or OpenAI voice IDs).
- **`backstory`** and **`personality`** – Prompt data that guides the LLM's response style and content knowledge.
- **`voice_model`** – Optional per-speaker overrides for the TTS engine (lines 78-80).

The model enforces architectural constraints through **`validate_speakers`**, which restricts configurations to between one and four speakers (lines 61-63). The **`voice_model`** field at the profile level (lines 50-52) points to a default TTS model, while the `_prepare_save_data` method normalizes this into a `RecordID` (lines 71-79).

## How the Generation Pipeline Orchestrates Profiles

When a user initiates generation, the `PodcastService.submit_generation_job` method in [`api/podcast_service.py`](https://github.com/lfnovo/open-notebook/blob/main/api/podcast_service.py) executes a validation and orchestration sequence.

First, the service retrieves the episode profile by name using `EpisodeProfile.get_by_name` and validates its existence (lines 48-50). It then performs the same lookup for the speaker profile (lines 52-55). Both profile names are passed to the background `generate_podcast` command implemented in [`commands/podcast_commands.py`](https://github.com/lfnovo/open-notebook/blob/main/commands/podcast_commands.py).

Inside the background command, the episode's `speaker_config` field is used to fetch the full `SpeakerProfile` object. The system then calls `SpeakerProfile.resolve_tts_config` (lines 82-89) to resolve each speaker's `voice_model` or apply per-speaker overrides. The command iterates over the `speakers` array, generating audio segments with the appropriate voice for each participant. Because the speaker list supports multiple entries, the final podcast assembles distinct voice tracks into a single conversational experience.

## Creating Profiles for Multi-Speaker Content

To create a multi-speaker podcast, you must first define the speaker roster, then reference it in an episode configuration.

### Define the Speaker Profile

Create a speaker profile containing up to four distinct voices. Each speaker requires a unique voice ID and descriptive attributes that guide the LLM's generation:

```json
{
  "name": "TechPanel",
  "description": "Three tech experts debating AI",
  "voice_model": "model:openai:gpt-4o-mini",
  "speakers": [
    {
      "name": "Alice",
      "voice_id": "elevenlabs:alice",
      "backstory": "AI researcher at XYZ",
      "personality": "curious, analytical"
    },
    {
      "name": "Bob",
      "voice_id": "elevenlabs:bob",
      "backstory": "Startup founder",
      "personality": "enthusiastic, pragmatic"
    },
    {
      "name": "Carol",
      "voice_id": "elevenlabs:carol",
      "backstory": "Policy analyst",
      "personality": "thoughtful, cautious"
    }
  ]
}

```

### Configure the Episode Profile

Create an episode profile that references the speaker configuration via the `speaker_config` field, while specifying the LLM models and structural parameters:

```json
{
  "name": "AI Debate Episode",
  "description": "A 10-minute debate on generative AI risks",
  "speaker_config": "TechPanel",
  "outline_llm": "model:openai:gpt-4o-mini",
  "transcript_llm": "model:openai:gpt-4o-mini",
  "language": "en-US",
  "default_briefing": "Discuss the advantages and concerns of large language models.",
  "num_segments": 5
}

```

### Trigger Generation

Submit the generation job using the service layer, which validates both profiles before queuing the background task:

```python
from open_notebook.api.podcast_service import PodcastService

job_id = await PodcastService.submit_generation_job(
    episode_profile_name="AI Debate Episode",
    speaker_profile_name="TechPanel",
    episode_name="AI_Debate_01",
    content="Intro text or notebook ID"
)

print(f"Submitted job {job_id}")

```

Monitor the job status to track progress:

```python
status = await PodcastService.get_job_status(job_id)
print(status)

```

Upon completion, the resulting `PodcastEpisode` record contains a `speaker_profile` object (lines 12-13 in [`models.py`](https://github.com/lfnovo/open-notebook/blob/main/models.py)) that preserves the exact speaker configuration used for that specific episode.

## Summary

- **Episode profiles** control the structural blueprint, LLM selection, and content briefing for podcast generation via the `EpisodeProfile` class in [`open_notebook/podcasts/models.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/podcasts/models.py).
- **Speaker profiles** encapsulate the voice models, personalities, and backstories for 1-4 speakers, enforcing constraints through `validate_speakers` and supporting per-speaker TTS overrides.
- The `PodcastService.submit_generation_job` method orchestrates profile validation and hands off to `generate_podcast` in [`commands/podcast_commands.py`](https://github.com/lfnovo/open-notebook/blob/main/commands/podcast_commands.py).
- Background generation resolves TTS configurations through `SpeakerProfile.resolve_tts_config` and composites multiple voice tracks into the final audio file.

## Frequently Asked Questions

### How many speakers can a single podcast episode support?

The `SpeakerProfile` model enforces a limit of four speakers through the `validate_speakers` method in [`open_notebook/podcasts/models.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/podcasts/models.py) (lines 61-63). This constraint ensures manageable audio compositing while supporting diverse conversational formats like interviews, panels, and debates.

### Can individual speakers use different text-to-speech models?

Yes. While the `SpeakerProfile` defines a default `voice_model` at the profile level, each speaker entry in the `speakers` list can optionally specify a `voice_model` override. The `resolve_tts_config` method processes these overrides during generation, allowing one speaker to use ElevenLabs while another uses OpenAI's TTS engine within the same episode.

### How does the system link an episode to its speaker configuration?

The `EpisodeProfile` stores the speaker configuration name in its `speaker_config` field (lines 34-35 of [`models.py`](https://github.com/lfnovo/open-notebook/blob/main/models.py)). When `PodcastService.submit_generation_job` executes, it retrieves both the episode profile and the named speaker profile, passing both to the background generation command where they are resolved into full objects.

### Where is the final speaker configuration preserved?

The `PodcastEpisode` model includes a `speaker_profile` field (lines 12-13 of [`models.py`](https://github.com/lfnovo/open-notebook/blob/main/models.py)) that records the complete speaker configuration used during generation. This ensures that the specific voices, personalities, and TTS settings remain associated with the episode record for future reference or regeneration.