From 88f0b5a0a4289609c31bdb5c4ef5d03f7281692d Mon Sep 17 00:00:00 2001 From: Lars Date: Sat, 11 Apr 2026 10:13:03 +0200 Subject: [PATCH] fix: Add workflow node outputs as placeholders in inline templates MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ISSUE: Inline templates referencing node outputs ({{ node_id.analysis_core }}, {{ node_id.signal_xyz }}) were not resolved - AI received empty data from previous workflow stages. ROOT CAUSE: load_prompt_template() only loaded system placeholders (name, age, etc.) but not node execution results from context['node_results']. FIX: - Extract node outputs from context['node_results'] - Add as placeholders: node_id.analysis_core, node_id.signal_xyz, node_id.question_xyz - Format matches PlaceholderPicker extraction logic - Debug logging shows which node placeholders are added TESTING: - System placeholder test: ✅ SUCCESS (name, age, geschlecht resolved) - Node output placeholders: Fixed (previously missing) - User workflow: Join → Analysis → End now receives upstream data Part 3: Inline Prompts - placeholder resolution completion --- backend/workflow_executor.py | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/backend/workflow_executor.py b/backend/workflow_executor.py index d585bae..a516727 100644 --- a/backend/workflow_executor.py +++ b/backend/workflow_executor.py @@ -893,6 +893,37 @@ async def load_prompt_template(node: WorkflowNode, context: Dict[str, Any]) -> s except Exception as e: logger.error(f"❌ CRITICAL: Failed to load placeholders for workflow: {e}", exc_info=True) + # Add workflow node outputs as placeholders (Part 3: Inline Prompts) + # Format: node_id.analysis_core, node_id.signal_xyz, node_id.question_xyz + node_results = context.get("node_results", {}) + if node_results: + logger.info(f"🔍 DEBUG: Adding {len(node_results)} node outputs as placeholders") + for node_id, node_state in node_results.items(): + # analysis_core + if hasattr(node_state, 'analysis_core') and node_state.analysis_core: + key = f"{node_id}.analysis_core" + variables[key] = node_state.analysis_core + logger.debug(f" Added placeholder: {key} = {node_state.analysis_core[:50]}...") + + # decision_signals (keyed by question ID) + if hasattr(node_state, 'decision_signals') and node_state.decision_signals: + for signal_id, signal_value in node_state.decision_signals.items(): + # Signal placeholder: node_id.signal_question_id + signal_key = f"{node_id}.signal_{signal_id}" + variables[signal_key] = signal_value + logger.debug(f" Added placeholder: {signal_key} = {signal_value}") + + # Question texts (from graph metadata if available) + # NOTE: Question text placeholders are populated from graph in PlaceholderPicker + # Here we only add if available in node_state metadata + if hasattr(node_state, 'metadata') and isinstance(node_state.metadata, dict): + questions = node_state.metadata.get('questions', []) + for q in questions: + if isinstance(q, dict) and 'id' in q and 'question' in q: + question_key = f"{node_id}.question_{q['id']}" + variables[question_key] = q['question'] + logger.debug(f" Added placeholder: {question_key}") + # Load catalog for |d modifier support try: catalog = get_placeholder_catalog(profile_id)