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

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 (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 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.


# 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 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 (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:

configure("speakers_config", {"profiles": speaker_profiles_dict})
configure("episode_config", {"profiles": episode_profiles_dict})
  1. Episode Recording: Before invoking create_podcast, the system creates a PodcastEpisode record storing full dumps of both profiles for status tracking and reproducibility.

  2. 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

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

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

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

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.
  • 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, 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, 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 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, enables status tracking through PodcastService.get_job_status in api/podcast_service.py and ensures reproducibility by preserving the exact configuration state at execution time.

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 →