Skip to content

Memory Title Generator

Metadata

  • System type: service

System Intent

  • What this is: A pure-function service (worldmm/llm/title_generator.py) that generates concise, descriptive titles for memory segments by prompting Claude via GPULLMClient. It is called as a best-effort step inside the ingest-window pipeline after transcription. Failures (timeout, rate limit, validation) return None without blocking enrichment.

Mermaid Diagram

flowchart TD
  IW["ingest_window lambda_handler"]
  TG["generate_title(transcript, llm_client)"]
  BP["build_title_prompt(transcript)"]
  LLM["GPULLMClient.call(request)\n→ GPU worker /generate"]
  CL["clean_title(raw)"]
  VL["validate_title(cleaned)\n50–150 chars"]
  DB["update_segment(title=...)"]

  IW -->|"transcript present"| TG
  TG --> BP
  BP -->|"2-message prompt"| LLM
  LLM -->|"raw string"| CL
  CL --> VL
  VL -->|"valid"| DB
  VL -->|"invalid → None"| DB
  TG -->|"TimeoutError / Exception → None"| DB

Flows

Flow: titleGenerator.generate

  • Core files: main/server/worldmm/llm/title_generator.py
  • Test files: main/server/tests/unit/test_title_generator.py

Types

generate_title(
  transcript: str,          // raw transcript text; must be >= 20 chars to proceed
  llm_client: GPULLMClient  // wraps GPU worker /generate endpoint
) -> str | None             // cleaned+validated title, or None on any failure

build_title_prompt(
  transcript: str,
  max_length: int = 150
) -> list[dict]             // [{"role": "system", ...}, {"role": "user", ...}]

validate_title(title: str | None) -> bool
// True if 50 <= len(title.strip()) <= 150

clean_title(title: str) -> str
// strips whitespace, removes leading/trailing quotes (", ', `), normalises interior whitespace

Paths

path input output path-type notes
titleGenerator.happyPath transcript >= 20 chars, LLM returns valid string cleaned title (50–150 chars) happy path Returned string is written to WorldMMSegment.title
titleGenerator.shortTranscript transcript < 20 chars or empty None skip Logged as title_skipped with reason=transcript_too_short; LLM never called
titleGenerator.emptyResponse LLM returns empty string None error Logged as title_generation_failed with reason=empty_response
titleGenerator.validationFail LLM returns string outside 50–150 chars None error Logged as title_validation_failed with title, length, min, max
titleGenerator.timeout TimeoutError raised during LLM call None error Logged as title_generation_failed with error=timeout
titleGenerator.apiError Any other exception (rate limit, etc.) None error Logged as title_generation_failed with error class name and message

Pseudocode

generate_title(transcript, llm_client):
  if not transcript or len(transcript.strip()) < 20:
    log title_skipped
    return None

  try:
    messages = build_title_prompt(transcript)   // truncates transcript to 4000 chars
    request = build_request(
      model="claude-opus",
      messages=messages,
      max_completion_tokens=100,
    )
    raw = llm_client.call(request)

    if not raw:
      log title_generation_failed (empty_response)
      return None

    title = clean_title(raw)   // strip, remove quotes, normalise whitespace

    if not validate_title(title):   // 50 <= len <= 150
      log title_validation_failed
      return None

    log title_generated
    return title

  except TimeoutError:
    log title_generation_failed (timeout)
    return None

  except Exception as e:
    log title_generation_failed (error class + message)
    return None

Logs

Structured log steps emitted by create_logger({"component": "title_generator"}):

Step key Where Key fields
title_skipped transcript too short reason=transcript_too_short
title_generation_failed empty response / timeout / exception reason or error, message
title_validation_failed length check failed title, length, min_length, max_length
title_generated success title_length

Data Model

WorldMMSegment.title — nullable Text column added to worldmm_segments.

Column Type Nullable Default Notes
title Text yes null Written by update_segment(title=...) after enrichment; null means generation was skipped or failed

update_segment signature (relevant fields):

def update_segment(
    segment_id: str,
    processing_status: str,
    caption: str | None = None,
    title: str | None = None,
) -> None

Only non-None keyword arguments are written; passing title=None leaves the column unchanged (the column is skipped in the SQL UPDATE).

Integration Point: ingest_window

Title generation is inserted as a best-effort step in lambda_handler (step 5, before the GPU enrichment block). It runs only when a transcript is present:

if transcript:
  try:
    gpu_instance_id = _read_gpu_instance_id()
    if gpu_instance_id and frames_b64:
      llm_client = GPULLMClient(gpu_client=VLM2VecClient(gpu_url, gpu_instance_id))
      title = generate_title(transcript, llm_client)
  except Exception:
    title = None  // enrichment continues regardless

The title value (string or None) is forwarded to update_segment at the end of both the audio-only path and the full enrichment path.

Integration Point: Frontend

MemoryFeedItem (TypeScript, main/app/lib/api/memory/listMemories.ts) includes title?: string | null.

MemoryCard (main/app/components/memory/MemoryCard.tsx) renders:

const titleDisplay = memory.title || "Untitled Memory";
// overlaid on the thumbnail in a semi-transparent black bar at card bottom

The feed API (main/server/api/memories/feed/app.py) does not currently propagate title into the response payload — the title field in MemoryFeedItem is declared optional and will be absent until the feed lambda is updated to include it.

Deployment

No new Lambda. The title generator is imported inline inside ingest_window.py. No additional IAM permissions or environment variables are required beyond those already granted to IngestWindowFunction.

The title column must exist in worldmm_segments before deploying the updated ingest_window Lambda. Add the column via the migrate Lambda:

ALTER TABLE worldmm_segments ADD COLUMN IF NOT EXISTS title TEXT;