From c1583820ea675e882dc3d167b77ee7c3f74f380c Mon Sep 17 00:00:00 2001 From: jlightner Date: Tue, 31 Mar 2026 17:27:40 +0000 Subject: [PATCH] feat: Add context labels to multi-call pipeline stages Stage 3 (extraction) LLM calls now show the topic group label (e.g., 'Sound Design Basics') and Stage 5 (synthesis) calls show the category name. Displayed as a cyan italic label in the event row between the event type badge and model name. Helps admins understand why there are multiple LLM calls per stage. --- backend/pipeline/stages.py | 6 ++++-- frontend/src/App.css | 10 ++++++++++ frontend/src/pages/AdminPipeline.tsx | 5 +++++ 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/backend/pipeline/stages.py b/backend/pipeline/stages.py index ec99333..c8ff245 100644 --- a/backend/pipeline/stages.py +++ b/backend/pipeline/stages.py @@ -135,6 +135,7 @@ def _make_llm_callback( system_prompt: str | None = None, user_prompt: str | None = None, run_id: str | None = None, + context_label: str | None = None, ): """Create an on_complete callback for LLMClient that emits llm_call events. @@ -162,6 +163,7 @@ def _make_llm_callback( "content_length": len(content) if content else 0, "finish_reason": finish_reason, "is_fallback": is_fallback, + **({"context": context_label} if context_label else {}), }, system_prompt_text=system_prompt if debug else None, user_prompt_text=user_prompt if debug else None, @@ -427,7 +429,7 @@ def stage3_extraction(self, video_id: str, run_id: str | None = None) -> str: ) max_tokens = estimate_max_tokens(system_prompt, user_prompt, stage="stage3_extraction", hard_limit=hard_limit) - raw = llm.complete(system_prompt, user_prompt, response_model=ExtractionResult, on_complete=_make_llm_callback(video_id, "stage3_extraction", system_prompt=system_prompt, user_prompt=user_prompt, run_id=run_id), + raw = llm.complete(system_prompt, user_prompt, response_model=ExtractionResult, on_complete=_make_llm_callback(video_id, "stage3_extraction", system_prompt=system_prompt, user_prompt=user_prompt, run_id=run_id, context_label=topic_label), modality=modality, model_override=model_override, max_tokens=max_tokens) result = _safe_parse_llm_response(raw, ExtractionResult, llm, system_prompt, user_prompt, modality=modality, model_override=model_override) @@ -781,7 +783,7 @@ def stage5_synthesis(self, video_id: str, run_id: str | None = None) -> str: user_prompt = f"{creator_name}\n\n{moments_text}\n" max_tokens = estimate_max_tokens(system_prompt, user_prompt, stage="stage5_synthesis", hard_limit=hard_limit) - raw = llm.complete(system_prompt, user_prompt, response_model=SynthesisResult, on_complete=_make_llm_callback(video_id, "stage5_synthesis", system_prompt=system_prompt, user_prompt=user_prompt, run_id=run_id), + raw = llm.complete(system_prompt, user_prompt, response_model=SynthesisResult, on_complete=_make_llm_callback(video_id, "stage5_synthesis", system_prompt=system_prompt, user_prompt=user_prompt, run_id=run_id, context_label=category), modality=modality, model_override=model_override, max_tokens=max_tokens) result = _safe_parse_llm_response(raw, SynthesisResult, llm, system_prompt, user_prompt, modality=modality, model_override=model_override) diff --git a/frontend/src/App.css b/frontend/src/App.css index bab480d..067fa42 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -4133,6 +4133,16 @@ a.app-footer__repo:hover { font-weight: 500; } +.pipeline-event__context { + color: var(--color-accent); + font-size: 0.75rem; + font-style: italic; + max-width: 150px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + .pipeline-event__model { color: var(--color-text-muted); font-size: 0.75rem; diff --git a/frontend/src/pages/AdminPipeline.tsx b/frontend/src/pages/AdminPipeline.tsx index 0c6b657..66cfa44 100644 --- a/frontend/src/pages/AdminPipeline.tsx +++ b/frontend/src/pages/AdminPipeline.tsx @@ -294,6 +294,11 @@ function EventLog({ videoId, status, runId }: { videoId: string; status: string; {evt.event_type} + {typeof evt.payload?.context === "string" && ( + + {evt.payload.context} + + )} {evt.model && {evt.model}} {evt.total_tokens != null && evt.total_tokens > 0 && (