# How to Configure Speaker Profiles and Episode Profiles for Multi-Speaker Podcast Generation in Open Notebook

> Learn to configure speaker profiles and episode profiles for multi-speaker podcast generation in Open Notebook. Define podcast structure and map TTS voices for unique personas.

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

---

**Open Notebook orchestrates multi-speaker podcast generation by combining an Episode Profile that defines the podcast structure with a Speaker Profile containing 1-4 personas, each mapped to specific TTS voices and language models.**

Open Notebook enables dynamic podcast creation through a modular configuration system that separates content structure from voice characteristics. The platform uses two distinct profile types—**Speaker Profiles** and **Episode Profiles**—to generate multi-speaker audio content with sophisticated text-to-speech (TTS) voice mapping and language model orchestration. This architecture, implemented in the `lfnovo/open-notebook` repository, allows developers to create complex dialogue scenarios with up to four distinct speakers per episode.

## Understanding the Profile Architecture

Open Notebook’s multi-speaker podcast generation relies on two tightly integrated data models defined in [`open_notebook/podcasts/models.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/podcasts/models.py). These models work together to resolve voice configurations at runtime while storing persistent configuration in SurrealDB.

### SpeakerProfile Model

The **SpeakerProfile** class (lines 31-64 in [`open_notebook/podcasts/models.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/podcasts/models.py)) defines the personas that participate in the dialogue. Each profile stores an array of 1-4 speakers, where each entry is a dictionary containing:

- **`name`**: The speaker’s identifier used in transcripts
- **`voice_id`**: The specific TTS voice identifier (e.g., `"en-GB-Standard-A"`)
- **`backstory`**: Contextual background used by the LLM to generate appropriate dialogue
- **`personality`**: Behavioral traits that shape the speaker’s tone and style
- **`voice_model`** (optional): A per-speaker override specifying the TTS provider and model

The model validates that the `speakers` array contains between 1 and 4 entries with all required keys (lines 60-68). These records persist in the `speaker_profile` table in SurrealDB. An optional top-level `voice_model` field can specify a default TTS model for the entire profile, while individual speakers may override this via their own `voice_model` property.

### EpisodeProfile Model

The **EpisodeProfile** class (lines 31-53 in the same file) defines the structural parameters of the podcast episode. This model includes:

- **`speaker_config`**: A string referencing the Speaker Profile by name
- **`outline_llm`**: The language model used to generate the episode outline
- **`transcript_llm`**: The language model used to generate the full dialogue transcript
- **`num_segments`**: The number of dialogue segments to generate
- **`language`**: The target language code (e.g., `"en-US"`)
- **`default_briefing`**: The system prompt template guiding the episode’s tone and content

When a generation job starts, the system looks up the Episode Profile by name, then resolves the referenced Speaker Profile to configure the TTS pipeline.

## Configuration Workflow for Multi-Speaker Podcasts

Configuring multi-speaker podcast generation follows a three-step process involving REST API endpoints that validate and persist profile data.

### Step 1: Create Speaker Profiles

First, define your speakers using the `POST /speaker-profiles` endpoint implemented in [`api/routers/speaker_profiles.py`](https://github.com/lfnovo/open-notebook/blob/main/api/routers/speaker_profiles.py) (lines 82-99). The request payload must include a `speakers` array with 1-4 entries.

```python
import requests

url = "http://localhost:5055/api/speaker-profiles"
payload = {
    "name": "Debate Duo",
    "description": "Two experts for a friendly debate",
    "voice_model": "model:openai:tts-1",          # optional default TTS model

    "speakers": [
        {
            "name": "Expert Alex",
            "voice_id": "en-GB-Standard-A",
            "backstory": "AI alignment researcher with 10 years experience",
            "personality": "rigorous, patient, loves examples",
            "voice_model": "model:elevenlabs:alex-voice"   # per-speaker override

        },
        {
            "name": "Researcher Sam",
            "voice_id": "en-US-Standard-B",
            "backstory": "Field observer who asks clarifying questions",
            "personality": "curious, friendly"
            # Uses the profile-level voice_model since no override is specified

        }
    ]
}

resp = requests.post(url, json=payload)
print(resp.json())

```

The system validates the payload using `SpeakerProfile.validate_speakers` and stores the record in the SurrealDB `speaker_profile` table.

### Step 2: Define Episode Profiles

Next, create an Episode Profile that references your Speaker Profile by name via the `speaker_config` field. Use the `POST /episode-profiles` endpoint (see [`api/routers/episode_profiles.py`](https://github.com/lfnovo/open-notebook/blob/main/api/routers/episode_profiles.py) lines 100-118).

```python
url = "http://localhost:5055/api/episode-profiles"
payload = {
    "name": "AI Safety Debate",
    "description": "A short debate on AI safety approaches",
    "speaker_config": "Debate Duo",          # References the Speaker Profile by name

    "outline_llm": "model:openai:gpt-4o-mini",   # Model for outline generation

    "transcript_llm": "model:openai:gpt-4o",     # Model for transcript generation

    "language": "en-US",
    "default_briefing": "Explain the topic briefly then dive into a debate.",
    "num_segments": 5
}

resp = requests.post(url, json=payload)
print(resp.json())

```

### Step 3: Trigger Podcast Generation

Finally, initiate generation by submitting both profile names to the `POST /podcasts/generate` endpoint (see [`api/routers/podcasts.py`](https://github.com/lfnovo/open-notebook/blob/main/api/routers/podcasts.py) lines 41-51).

```python
url = "http://localhost:5055/api/podcasts/generate"
payload = {
    "episode_profile": "AI Safety Debate",
    "speaker_profile": "Debate Duo",
    "episode_name": "AI Safety Debate – Episode 1",
    "notebook_id": "notebook:example",   # ID of the notebook containing source material

    "content": "Provide outline of AI safety research",   # Optional raw content

    "briefing_suffix": ""                # Optional extra briefing text

}

resp = requests.post(url, json=payload)
job = resp.json()
print(f"Job started: {job['job_id']}")

```

The orchestration logic in [`commands/podcast_commands.py`](https://github.com/lfnovo/open-notebook/blob/main/commands/podcast_commands.py) (lines 85-98) loads both profiles by name, resolves the model configurations, and invokes the podcast-creator pipeline.

## Runtime Resolution and TTS Configuration

At runtime, Open Notebook resolves abstract model references (e.g., `"model:openai:gpt-4o"`) into concrete provider configurations using three key methods:

1. **`EpisodeProfile.resolve_outline_config()`** (lines 98-113 in [`models.py`](https://github.com/lfnovo/open-notebook/blob/main/models.py)): Converts the `outline_llm` field into a `(provider, model_name, config)` tuple for outline generation.

2. **`EpisodeProfile.resolve_transcript_config()`** (lines 98-113): Performs the same resolution for the `transcript_llm` field used for dialogue generation.

3. **`SpeakerProfile.resolve_tts_config()`** (lines 82-89): Resolves the default TTS model for the profile, returning the provider and voice configuration.

Per-speaker TTS overrides are normalized during save operations via `_prepare_save_data()` (lines 71-80), which ensures that individual `voice_model` entries within the `speakers` array are properly formatted before persistence. The podcast-creator library receives these resolved configurations to render each speaker’s text through the appropriate TTS engine and mix the resulting audio streams into a single MP3 file.

## Summary

- **Speaker Profiles** define 1-4 personas with voice IDs, backstories, and optional per-speaker TTS model overrides, stored in the `speaker_profile` SurrealDB table.
- **Episode Profiles** reference Speaker Profiles by name via `speaker_config` and specify the LLMs for outline and transcript generation.
- The configuration workflow involves creating profiles via `POST /speaker-profiles` and `POST /episode-profiles`, then triggering generation via `POST /podcasts/generate`.
- Runtime resolution methods in [`open_notebook/podcasts/models.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/podcasts/models.py) convert model references into executable provider configurations for both LLM and TTS pipelines.
- The orchestration logic in [`commands/podcast_commands.py`](https://github.com/lfnovo/open-notebook/blob/main/commands/podcast_commands.py) coordinates the lookup of both profiles and the resolution of TTS configurations before invoking the audio generation pipeline.

## Frequently Asked Questions

### How many speakers can I configure in a single podcast episode?

Open Notebook supports **1 to 4 speakers** per episode. The `SpeakerProfile` model validates that the `speakers` array contains at least one entry and at most four entries (see lines 60-68 in [`open_notebook/podcasts/models.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/podcasts/models.py)). Attempting to create a profile with zero speakers or more than four will fail validation.

### Can I use different TTS providers for different speakers in the same episode?

Yes. While the Speaker Profile supports a default `voice_model` field, individual speakers can override this via their own `voice_model` property within the `speakers` array. During the save process, `_prepare_save_data()` (lines 71-80) normalizes these overrides, and `resolve_tts_config()` resolves them at runtime, allowing you to mix providers (e.g., OpenAI for one speaker, ElevenLabs for another) within the same episode.

### What happens if the Episode Profile references a non-existent Speaker Profile?

When you submit a generation request to `POST /podcasts/generate`, the system calls `EpisodeProfile.get_by_name` followed by `SpeakerProfile.get_by_name` (see [`commands/podcast_commands.py`](https://github.com/lfnovo/open-notebook/blob/main/commands/podcast_commands.py) lines 85-98). If the Speaker Profile specified in the `speaker_config` field cannot be found, the lookup will fail and the API will return an error before starting the generation job, preventing invalid configurations from reaching the audio pipeline.

### How do I specify which language model generates the podcast outline versus the dialogue transcript?

The **EpisodeProfile** model includes two distinct fields: `outline_llm` for the model that structures the episode segments, and `transcript_llm` for the model that generates the actual dialogue. These are resolved separately via `resolve_outline_config()` and `resolve_transcript_config()` (lines 98-113 in [`models.py`](https://github.com/lfnovo/open-notebook/blob/main/models.py)), allowing you to use a lightweight model (like GPT-4o-mini) for outlining and a more capable model (like GPT-4o) for the detailed conversation.