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 profilespeaker_config: Reference to a SpeakerProfile by nameoutline_llmandtranscript_llm: Model record IDs for content generationdefault_briefing: System prompt providing episode contextnum_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 speakervoice_id: Provider-specific voice identifierbackstoryandpersonality: Context for the LLM to generate appropriate dialoguevoice_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:
-
API Validation:
PodcastService.submit_generation_jobinapi/podcast_service.pyvalidates that profile names exist and are properly configured. -
Command Queueing: The service queues a
generate_podcastcommand via surreal-commands, passing the profile names and content. -
Profile Loading: The
generate_podcast_commandincommands/podcast_commands.py(lines 84-98) fetches both profiles by name usingEpisodeProfile.get_by_nameandSpeakerProfile.get_by_name. -
Model Resolution: The command resolves all model IDs to concrete provider configurations. This step is critical because the downstream
podcast-creatorlibrary validates every profile before audio generation. -
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})
-
Episode Recording: Before invoking
create_podcast, the system creates aPodcastEpisoderecord storing full dumps of both profiles for status tracking and reproducibility. -
Audio Generation: The
podcast-creatorlibrary 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_confighelper. - The generation workflow loads both profiles in
commands/podcast_commands.py, resolves their models, configures thepodcast-creatorlibrary, and persists aPodcastEpisoderecord 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →