# Episode Profile System in Open Notebook: A Complete Guide to Podcast Customization

> Discover the episode profile system in Open Notebook for podcast customization. Easily define LLM models, language, briefing, and speaker settings for tailored podcast generation.

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

---

**The episode profile system in Open Notebook encapsulates podcast configuration into reusable `EpisodeProfile` objects that define LLM models, language settings, briefing templates, and speaker configurations, enabling users to customize podcast generation without managing low-level model IDs.**

Open Notebook treats podcast episodes as compositions of configuration rather than monolithic jobs. The system replaces a legacy 15-field schema with concise, user-friendly profile objects that separate content strategy from technical implementation. By centralizing these settings in SurrealDB-backed Pydantic models, the repository allows users to create, version, and reuse podcast configurations across multiple generation runs.

## Core Components of the Episode Profile System

The architecture revolves around two primary models defined in [`open_notebook/podcasts/models.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/podcasts/models.py): `EpisodeProfile` for generation parameters and `SpeakerProfile` for voice configuration.

### EpisodeProfile Data Model

The `EpisodeProfile` class inherits from `ObjectModel` and persists to the `episode_profile` table. It serves as the single source of truth for episode generation parameters:

```python
class EpisodeProfile(ObjectModel):
    table_name = "episode_profile"
    name: str
    description: Optional[str]
    speaker_config: str
    outline_llm: Optional[str]
    transcript_llm: Optional[str]
    language: Optional[str]
    default_briefing: str
    num_segments: int = 5

```

**Key fields include:**
- **`speaker_config`** – References a `SpeakerProfile` by name, linking voice and personality data.
- **`outline_llm`** and **`transcript_llm`** – SurrealDB record IDs (e.g., `"model:uuid-1234"`) pointing to Model objects for outline generation and transcript writing.
- **`default_briefing`** – The system prompt template that primes the LLM for content generation.
- **`num_segments`** – Controls podcast structure, defaulting to 5 sections (validated between 3-20 in [`tests/test_domain.py`](https://github.com/lfnovo/open-notebook/blob/main/tests/test_domain.py)).

Legacy fields like `outline_provider` and `outline_model` exist only for database migration and are ignored by current application logic.

### SpeakerProfile Integration

Each `EpisodeProfile` references a `SpeakerProfile` via the `speaker_config` field. The `SpeakerProfile` class (defined later in the same models file) describes one to four speakers, including their voice-model IDs and personality parameters. When a podcast job executes, the system resolves both profiles to obtain complete provider-model-configuration triples for the audio synthesis pipeline.

## API Endpoints for Managing Episode Profiles

All profile management operations expose through FastAPI routes in [`api/routers/episode_profiles.py`](https://github.com/lfnovo/open-notebook/blob/main/api/routers/episode_profiles.py), registered under the `"episode-profiles"` tag in [`api/main.py`](https://github.com/lfnovo/open-notebook/blob/main/api/main.py):

- **`GET /api/episode-profiles`** – Returns a list of `EpisodeProfileResponse` objects.
- **`GET /api/episode-profiles/{profile_name}`** – Retrieves a specific profile by its unique name.
- **`POST /api/episode-profiles`** – Creates a profile using `EpisodeProfileCreate` schema.
- **`PUT /api/episode-profiles/{profile_id}`** – Performs partial updates using `model_dump(exclude_unset=True)`.
- **`DELETE /api/episode-profiles/{profile_id}`** – Removes the profile from SurrealDB.
- **`POST /api/episode-profiles/{profile_id}/duplicate`** – Creates a copy with " - Copy" appended to the name.

The router leverages the generic CRUD helpers from `ObjectModel`, including `_prepare_save_data`, which automatically normalizes model IDs to SurrealDB `RecordID` objects before persistence.

## How Episode Profiles Drive Podcast Generation

When `run_podcast()` executes in [`commands/podcast_commands.py`](https://github.com/lfnovo/open-notebook/blob/main/commands/podcast_commands.py), the episode profile system resolves abstract configuration into concrete model instances:

1. **Profile Loading** – The system loads the selected profile via `await EpisodeProfile.get_by_name(input_data.episode_profile)`.
2. **LLM Resolution** – It resolves outline and transcript configurations using `await profile.resolve_outline_config()` and `await profile.resolve_transcript_config()`.
3. **Speaker Resolution** – It fetches the referenced `SpeakerProfile` using `await SpeakerProfile.get_by_name(profile.speaker_config)` and resolves TTS model settings.
4. **Pipeline Execution** – The resolved triples pass to the LangGraph workflow that builds outlines, generates transcripts, and synthesizes audio.

This resolution pattern isolates users from provider-specific model IDs while allowing runtime swapping of LLM backends.

## Creating and Using Episode Profiles

### Creating a Profile via Python Client

Use the `APIClient` from `open_notebook.api.client` to programmatically create profiles:

```python
from open_notebook.api.client import APIClient

client = APIClient(base_url="http://localhost:5055")
profile_data = {
    "name": "Tech Talk – AI",
    "description": "Standard AI-focused tech podcast",
    "speaker_config": "default_speakers",
    "outline_llm": "model:uuid-1234",
    "transcript_llm": "model:uuid-5678",
    "language": "en-US",
    "default_briefing": "Create a 10-minute episode about recent AI news.",
    "num_segments": 6,
}
new_profile = client.create_episode_profile(**profile_data)
print(new_profile)

```

### Creating a Profile via cURL

For direct API access:

```bash
curl -X POST http://localhost:5055/api/episode-profiles \
  -H "Content-Type: application/json" \
  -d '{
        "name": "Science Daily",
        "description": "Daily science digest",
        "speaker_config": "science_speakers",
        "outline_llm": "model:uuid-1111",
        "transcript_llm": "model:uuid-2222",
        "language": "en-US",
        "default_briefing": "Summarise the top three science headlines.",
        "num_segments": 4
      }'

```

### Using Profiles in Generation Commands

Reference profiles by name when triggering podcast generation:

```python
from commands.podcast_commands import run_podcast

await run_podcast(
    podcast_name="my_first_episode",
    episode_profile="Science Daily",
    source_url="https://example.com/news",
)

```

### Duplicating Existing Profiles

Clone configurations for rapid iteration:

```bash
curl -X POST http://localhost:5055/api/episode-profiles/12345-abcde/duplicate

```

## Summary

- The **episode profile system** centralizes podcast configuration in `EpisodeProfile` objects within [`open_notebook/podcasts/models.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/podcasts/models.py).
- Profiles encapsulate LLM references (`outline_llm`, `transcript_llm`), language settings, briefing templates, and segment counts.
- **SpeakerProfile** integration via `speaker_config` enables voice and personality customization separate from episode structure.
- Full CRUD operations expose through `/api/episode-profiles` endpoints, including a duplicate function for rapid templating.
- Runtime resolution in [`commands/podcast_commands.py`](https://github.com/lfnovo/open-notebook/blob/main/commands/podcast_commands.py) translates profile names into concrete model configurations for the LangGraph pipeline.

## Frequently Asked Questions

### What fields are required when creating an EpisodeProfile?

The `name`, `speaker_config`, and `default_briefing` fields are required, while `num_segments` defaults to 5. The `outline_llm` and `transcript_llm` fields are optional but necessary for generation; if omitted, the system cannot resolve model configurations during the podcast pipeline execution.

### How does the episode profile system handle speaker configuration?

The `speaker_config` field stores the name of a `SpeakerProfile` record. When `run_podcast()` executes, it calls `SpeakerProfile.get_by_name(profile.speaker_config)` to load voice model IDs and personality data for one to four speakers, then passes these to the TTS synthesis stage of the pipeline.

### Can I update an episode profile after creating it?

Yes. The `PUT /api/episode-profiles/{profile_id}` endpoint supports partial updates using Pydantic's `model_dump(exclude_unset=True)`, allowing you to modify specific fields like `default_briefing` or `num_segments` without resubmitting the entire configuration object.

### What is the maximum number of segments allowed in an episode profile?

The `num_segments` field accepts integers between 3 and 20, as validated by unit tests in [`tests/test_domain.py`](https://github.com/lfnovo/open-notebook/blob/main/tests/test_domain.py). Attempting to set values outside this range will fail validation before persistence to SurrealDB.