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

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. 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) 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 (lines 82-99). The request payload must include a speakers array with 1-4 entries.

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 lines 100-118).

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 lines 41-51).

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 (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): 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 convert model references into executable provider configurations for both LLM and TTS pipelines.
  • The orchestration logic in 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). 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 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), 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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →