Skip to content

Commit 455c7c5

Browse files
committed
feat(r14): unify error context formatting with PromptComposer, ring buffer logs, and summary-first prompts
1 parent 7967133 commit 455c7c5

File tree

11 files changed

+1416
-87
lines changed

11 files changed

+1416
-87
lines changed

ROADMAP.md

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -292,14 +292,24 @@ graph TD
292292

293293
*Sorted by priority (High → Low):*
294294

295-
- [ ] **R14**: Error context extraction & prompt packaging optimization - 🔴 High ⚠️ *Use dev branch*
295+
- [x] **R14**: Error context extraction & prompt packaging optimization - 🔴 High *Completed (2026-01-14)*
296296
- **Problem**: LLM context is often dominated by raw tracebacks; log context capture is unreliable; env/pip list can waste tokens.
297297
- **Approach**:
298298
- Add `error_summary` before full traceback (exception line + optional pattern/category)
299299
- Reliable execution log context via a dedicated ring buffer (not dependent on ComfyUI logger handlers)
300300
- Expand non-traceback error triggers (single-line fatals, validation failures) with strict dedup/noise filtering
301301
- Build structured LLM context via pipeline (`llm_builder.py`) + token budgets (R12) instead of ad-hoc string concatenation
302302
- **Plan**: `.planning/260113-R14_ERROR_CONTEXT_EXTRACTION_OPTIMIZATION_PLAN.md`
303+
- **Implementation Record**: `.planning/260113-R14_ERROR_CONTEXT_EXTRACTION_IMPLEMENTATION_RECORD.md`
304+
- [ ] **R15**: Canonicalize `system_info` + populate pipeline `execution_logs` - 🟡 Medium 🧩 *Follow-up*
305+
- **Context**: R14 core is complete; remaining improvements are quality/consistency enhancements (not gating R14 acceptance).
306+
- **Scope**:
307+
- Normalize `get_system_environment()` output into a PromptComposer-friendly canonical shape (single schema across endpoints)
308+
- Implement smart package selection for `system_info.packages` (keyword-based filtering; strict cap by default)
309+
- Populate pipeline context `execution_logs` from `LogRingBuffer` (so pipeline and endpoints share the same log source)
310+
- **Acceptance**:
311+
- Prompts show consistent `system_info` formatting regardless of endpoint
312+
- Pipeline-produced LLM context includes recent execution logs without relying on ComfyUI handler `.buffer`
303313
- [x] **R12**: Smart Token Budget Management - 🟡 Medium ✅ *Completed (2026-01-10)*
304314
- **Core Strategy**: Progressive trimming system with token estimation for LLM context management
305315
- **Implemented Features**:
@@ -315,8 +325,8 @@ graph TD
315325
- **Enhanced Metadata**: R12Metadata v1.0 schema for observability
316326
- **A/B Validation Harness**: Quality metrics tracking (`scripts/r12_ab_harness.py`)
317327
- **Deferred Features** (not implemented):
318-
- ⏸️ Smart pip list filtering (keyword extraction from errors) → planned in **R14**
319-
- ⏸️ Stack frame collapsing (first 5 + last 5 frames) → planned in **R14**
328+
- ⏸️ Smart pip list filtering (keyword extraction from errors) → follow-up in **R15**
329+
- Stack frame collapsing (first N + last M frames) → delivered in **R14**
320330
- **Cost Impact**: 40-60% token reduction estimated, saving $24-36 per 1000 analyses (GPT-4)
321331
- **Implementation**: `.planning/260110-R12_SMART_TOKEN_BUDGET_IMPLEMENTATION_RECORD.md`
322332
- **Integration**: `services/token_estimator.py`, `services/workflow_pruner.py`, `services/token_budget.py`

__init__.py

Lines changed: 166 additions & 70 deletions
Large diffs are not rendered by default.

config.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,10 @@ class DiagnosticsConfig:
6767
r12_prune_default_nodes: int = 40
6868
r12_overhead_fixed: int = 1000 # Fixed overhead (reserved tokens for structure)
6969

70+
# R14: Error Context Extraction & Prompt Optimization
71+
r14_use_prompt_composer: bool = True # Use PromptComposer for unified context formatting
72+
r14_use_legacy_format: bool = False # Fallback to legacy format (traceback-first)
73+
7074
def to_dict(self) -> dict:
7175
"""Convert config to dictionary."""
7276
return asdict(self)

logger.py

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,21 @@
2828
from .analyzer import ErrorAnalyzer
2929
from .config import CONFIG
3030
from .history_store import HistoryStore, HistoryEntry
31+
from services.log_ring_buffer import get_ring_buffer
32+
from services.context_extractor import detect_fatal_pattern
3133
except ImportError:
3234
# Fallback for direct execution (tests)
3335
from analyzer import ErrorAnalyzer
3436
from config import CONFIG
3537
from history_store import HistoryStore, HistoryEntry
38+
try:
39+
from services.log_ring_buffer import get_ring_buffer
40+
except ImportError:
41+
get_ring_buffer = None
42+
try:
43+
from services.context_extractor import detect_fatal_pattern
44+
except ImportError:
45+
detect_fatal_pattern = None
3646

3747

3848
# ==============================================================================
@@ -281,6 +291,14 @@ def write(self, data):
281291
self._queue.put_nowait(data, priority=priority)
282292
except Exception:
283293
pass
294+
295+
# R14: Add to ring buffer for reliable log context capture
296+
try:
297+
if get_ring_buffer:
298+
ring_buffer = get_ring_buffer()
299+
ring_buffer.add_line(data)
300+
except Exception:
301+
pass # Never fail on ring buffer operations
284302

285303
def flush(self):
286304
"""Flush original stream."""
@@ -370,7 +388,17 @@ def _process_message(self, message):
370388
return # Skip Doctor's own output to prevent recursion
371389

372390
# P3: Urgent single-line warnings (immediate analysis)
373-
if "❌ CRITICAL" in message or "⚠️ Meta Tensor" in message:
391+
# R14: Enhanced with detect_fatal_pattern for non-traceback errors
392+
is_urgent = "❌ CRITICAL" in message or "⚠️ Meta Tensor" in message
393+
394+
# R14: Check for fatal patterns (CUDA OOM, CRITICAL, etc.)
395+
if not is_urgent and detect_fatal_pattern:
396+
fatal_marker = detect_fatal_pattern(message)
397+
if fatal_marker:
398+
is_urgent = True
399+
logging.debug(f"[Doctor] R14 fatal pattern detected: {fatal_marker}")
400+
401+
if is_urgent:
374402
result = ErrorAnalyzer.analyze(message)
375403
suggestion, metadata = result if result else (None, None)
376404
if suggestion:

pipeline/context.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,10 @@ class AnalysisContext:
5757

5858
# R12 / LLM Data
5959
llm_context: Optional[Dict[str, Any]] = None
60+
61+
# R14: Error Context Extraction
62+
error_summary: Optional[str] = None # Short summary (exception type + message)
63+
execution_logs: List[str] = field(default_factory=list) # Recent log lines from ring buffer
6064

6165
def __post_init__(self) -> None:
6266
# Ensure metadata contract version is always present

pipeline/stages/llm_builder.py

Lines changed: 59 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,33 @@
44
from ..context import AnalysisContext
55
try:
66
from services.workflow_pruner import WorkflowPruner
7+
from services.context_extractor import (
8+
extract_error_summary,
9+
collapse_stack_frames,
10+
build_context_manifest,
11+
)
712
except ImportError:
813
# Use relative import for flexibility
914
import sys
1015
import os
1116
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../..")))
1217
from services.workflow_pruner import WorkflowPruner
18+
from services.context_extractor import (
19+
extract_error_summary,
20+
collapse_stack_frames,
21+
build_context_manifest,
22+
)
1323

1424
logger = logging.getLogger(__name__)
1525

1626
class LLMContextBuilderStage(PipelineStage):
1727
"""
1828
Stage 4: LLM Context Builder.
19-
Prepares the context for LLM analysis (R12).
20-
- Prunes workflow JSON to relevant subgraph.
21-
- Estimates token usage.
22-
- Structures data for the LLM prompt.
29+
Prepares the context for LLM analysis (R12 + R14).
30+
- Extracts error summary (R14: summary-first prompt)
31+
- Collapses stack frames (R14: semantic truncation)
32+
- Prunes workflow JSON to relevant subgraph (R12)
33+
- Builds context manifest for observability (R14)
2334
"""
2435

2536
def __init__(self, workflow_pruner: WorkflowPruner):
@@ -32,8 +43,8 @@ def __init__(self, workflow_pruner: WorkflowPruner):
3243
self._name = "LLMContextBuilderStage"
3344
self.stage_id = "llm_context_builder"
3445
self.requires = ["sanitized_traceback"]
35-
self.provides = ["llm_context", "metadata.estimated_tokens"]
36-
self.version = "1.0"
46+
self.provides = ["llm_context", "metadata.estimated_tokens", "metadata.context_manifest"]
47+
self.version = "2.0" # R14 upgrade
3748
self.pruner = workflow_pruner
3849

3950
@property
@@ -42,12 +53,36 @@ def name(self) -> str:
4253

4354
def process(self, context: AnalysisContext) -> None:
4455
"""
45-
Builds the LLM context.
56+
Builds the LLM context with R14 optimizations.
57+
58+
Order of sections in llm_context (summary-first):
59+
1. error_summary - Short exception type + message
60+
2. node_info - Failed node details
61+
3. traceback - Collapsed if long
62+
4. execution_logs - Recent log lines
63+
5. workflow_subset - Pruned workflow
64+
6. system_info - Environment info
4665
"""
4766
if not context.sanitized_traceback:
4867
return
4968

50-
# 1. Prune Workflow if available
69+
# R14 Step 1: Extract error summary
70+
pattern_category = context.metadata.get("pattern_match", {}).get("category")
71+
error_summary = extract_error_summary(
72+
context.sanitized_traceback,
73+
pattern_category=pattern_category
74+
)
75+
if error_summary:
76+
context.error_summary = error_summary.to_string()
77+
78+
# R14 Step 2: Collapse stack frames for token efficiency
79+
collapsed_traceback = collapse_stack_frames(
80+
context.sanitized_traceback,
81+
head_frames=3,
82+
tail_frames=5
83+
)
84+
85+
# Step 3: Prune Workflow if available
5186
pruned_workflow = None
5287
if context.workflow_json and context.node_context and context.node_context.node_id:
5388
try:
@@ -57,23 +92,34 @@ def process(self, context: AnalysisContext) -> None:
5792
)
5893
except Exception as e:
5994
logger.warning(f"Workflow pruning failed: {e}")
60-
# Fallback to original or summary?
61-
# For now, maybe just keep original or nothing
6295
pruned_workflow = context.workflow_json
6396

64-
# 2. Build LLM Context Dict
97+
# Step 4: Build LLM Context Dict (summary-first order)
6598
llm_data = {
66-
"traceback": context.sanitized_traceback,
99+
"error_summary": context.error_summary, # R14: First
67100
"node_info": context.node_context.to_dict() if context.node_context else {},
101+
"traceback": collapsed_traceback, # R14: Collapsed
102+
"execution_logs": context.execution_logs, # R14: From ring buffer
68103
"workflow_subset": pruned_workflow,
69104
"system_info": context.system_info
70105
}
71106

72107
context.llm_context = llm_data
73108

74-
# 3. Estimate Tokens (Optional Metadata)
109+
# R14 Step 5: Build context manifest for observability
110+
manifest = build_context_manifest(
111+
traceback_text=context.sanitized_traceback,
112+
execution_logs=context.execution_logs,
113+
workflow_json=context.workflow_json,
114+
system_info=context.system_info,
115+
error_summary=error_summary
116+
)
117+
context.add_metadata("context_manifest", manifest.to_dict())
118+
119+
# Step 6: Estimate Tokens (Optional Metadata)
75120
try:
76121
tokens = self.pruner.estimate_tokens(llm_data)
77122
context.add_metadata("estimated_tokens", tokens)
78123
except Exception:
79124
pass
125+

0 commit comments

Comments
 (0)