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 viaGPULLMClient. It is called as a best-effort step inside the ingest-window pipeline after transcription. Failures (timeout, rate limit, validation) returnNonewithout 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: