From afc70b5a9593beabd0307639accf1031fe2201b8 Mon Sep 17 00:00:00 2001 From: Lars Date: Thu, 26 Mar 2026 08:14:41 +0100 Subject: [PATCH] fix: integrate placeholder resolver + JSON unwrapping (Issue #28) - Backend: integrate get_placeholder_example_values in execute_prompt_with_data - Backend: now provides BOTH raw data AND processed placeholders - Backend: unwrap Markdown-wrapped JSON (```json ... ```) - Fixes old-style prompts that expect name, weight_trend, caliper_summary Resolves unresolved placeholders issue. Co-Authored-By: Claude Opus 4.6 --- backend/prompt_executor.py | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/backend/prompt_executor.py b/backend/prompt_executor.py index 1d1b586..d1284d5 100644 --- a/backend/prompt_executor.py +++ b/backend/prompt_executor.py @@ -64,6 +64,8 @@ def validate_json_output(output: str, schema: Optional[Dict] = None, debug_info: """ Validate that output is valid JSON. + Unwraps Markdown-wrapped JSON (```json ... ```) if present. + Args: output: String to validate schema: Optional JSON schema to validate against (TODO: jsonschema library) @@ -75,14 +77,28 @@ def validate_json_output(output: str, schema: Optional[Dict] = None, debug_info: Raises: HTTPException: If output is not valid JSON (with debug info attached) """ + # Try to unwrap Markdown code blocks (common AI pattern) + unwrapped = output.strip() + if unwrapped.startswith('```json'): + # Extract content between ```json and ``` + lines = unwrapped.split('\n') + if len(lines) > 2 and lines[-1].strip() == '```': + unwrapped = '\n'.join(lines[1:-1]) + elif unwrapped.startswith('```'): + # Generic code block + lines = unwrapped.split('\n') + if len(lines) > 2 and lines[-1].strip() == '```': + unwrapped = '\n'.join(lines[1:-1]) + try: - parsed = json.loads(output) + parsed = json.loads(unwrapped) # TODO: Add jsonschema validation if schema provided return parsed except json.JSONDecodeError as e: error_detail = { "error": f"AI returned invalid JSON: {str(e)}", "raw_output": output[:500] + ('...' if len(output) > 500 else ''), + "unwrapped": unwrapped[:500] if unwrapped != output else None, "output_length": len(output) } if debug_info: @@ -352,6 +368,7 @@ async def execute_prompt_with_data( Execution result dict """ from datetime import datetime, timedelta + from placeholder_resolver import get_placeholder_example_values # Build variables from data modules variables = { @@ -359,6 +376,16 @@ async def execute_prompt_with_data( 'today': datetime.now().strftime('%Y-%m-%d') } + # Add PROCESSED placeholders (name, weight_trend, caliper_summary, etc.) + # This makes old-style prompts work with the new executor + try: + processed_placeholders = get_placeholder_example_values(profile_id) + variables.update(processed_placeholders) + except Exception as e: + # Continue even if placeholder resolution fails + if enable_debug: + variables['_placeholder_error'] = str(e) + # Load data for enabled modules if modules: with get_db() as conn: