From 9ad52893f9f9f086053471ecef08b02afbd430de Mon Sep 17 00:00:00 2001 From: Floke Date: Wed, 31 Dec 2025 15:04:45 +0000 Subject: [PATCH] fix(gtm): Final robust prompt construction to eliminate SyntaxError and update migration guide --- BUILDER_APPS_MIGRATION.md | 43 +++++++++++++++-------------------- gtm_architect_orchestrator.py | 40 ++++++++++++++++---------------- 2 files changed, 37 insertions(+), 46 deletions(-) diff --git a/BUILDER_APPS_MIGRATION.md b/BUILDER_APPS_MIGRATION.md index 72500b03..03305992 100644 --- a/BUILDER_APPS_MIGRATION.md +++ b/BUILDER_APPS_MIGRATION.md @@ -51,36 +51,29 @@ Das Projekt nutzt ein geteiltes `helpers.py`, das auf der alten OpenAI Python Li openai==0.28.1 # weitere deps... ### 1.5 Python Syntax & F-Strings -Multi-Line f-Strings sind in Docker-Umgebungen fehleranfällig, besonders wenn sie JSON-Strukturen oder komplexe Texte enthalten. +Multi-Line Prompts können in Docker-Umgebungen zu **sehr hartnäckigen Syntaxfehlern** führen, selbst wenn sie lokal korrekt aussehen. -* **Wiederkehrender Fehler: `SyntaxError: invalid decimal literal` (oft bei `2. Derive` oder ähnlichen Zeilen in Prompts)** - * **Symptom:** Python meldet einen Syntaxfehler innerhalb eines scheinbar korrekten Multi-Line-Strings (`f"""..."""`). - * **Ursachen:** - 1. **Falsches Escaping von `{}`:** Wenn geschweifte Klammern (`{}`) als Teil eines JSON-Beispiels in einem f-String verwendet werden, müssen sie oft **doppelt** escaped werden (`{{` und `}}`). Wenn es aber kein f-String ist oder die Klammern nicht zur Variableneinfügung dienen, dürfen sie nicht escaped werden (ein `dict()`-Literal ist `{}`). Das ist eine häufige Quelle für Verwirrung. - 2. **Fehlende/Falsche String-Delimiter:** Der Multi-Line-String wurde nicht korrekt mit `"""` geöffnet oder geschlossen, oder eine Zeile davor/danach bricht die String-Definition. - 3. **Unsichtbare Zeichen:** Manchmal können unsichtbare Unicode-Zeichen (z.B. Zero-Width Space) den Parser stören. - 4. **Ternary Operators in f-Strings:** Das ist ein bekannter Anti-Pattern (siehe unten). +* **Das Problem:** Der Python-Parser (insbesondere bei `f-strings` in Kombination mit Zahlen/Punkten am Zeilenanfang oder verschachtelten Klammern) kann Multi-Line-Strings (`f"""..."""`) falsch interpretieren, was zu Fehlern wie `SyntaxError: invalid decimal literal` oder `unmatched ')'` führt, auch wenn der Code scheinbar korrekt ist. -* **Best Practice zur Fehlervermeidung bei Prompts:** - 1. **Nutze explizite `if/else` Blöcke** zur Definition des Prompts, anstatt Ternary Operators (`... if x else ...`) innerhalb von Multi-Line f-Strings. Dies ist die **sicherste Methode**. - 2. **Definiere komplexe JSON-Strukturen** oder sehr lange Textblöcke **vorher als separate Variablen** und füge sie dann in den f-String ein (siehe Beispiel unten). - 3. **Prüfe akribisch die `"""` Delimiter** und die Einrückung des Multi-Line-Strings. +* **ULTIMATIVE LÖSUNG (Maximale Robustheit):** + 1. **Vermeide `f"""` komplett für komplexe Multi-Line-Prompts.** Definiere stattdessen den Prompt als normalen Multi-Line-String (ohne `f` Präfix). + 2. **Nutze die `.format()` Methode** zur Variablen-Injektion. Dies trennt die String-Definition komplett von der Variablen-Interpolation und ist die robusteste Methode. ```python - # Beispiel: So sollte es NICHT gemacht werden (fehleranfällig) - # prompt = f"""Daten: {json.dumps(data)}""" if lang=='de' else f"""Data: ...""" + # Schlecht (fehleranfällig, auch wenn es manchmal funktioniert): + # prompt = f""" + # 1. Mache dies: {var1} + # 2. Mache das: {var2} + # """ - # Beispiel: So ist es robust (bevorzugte Methode) - json_data_for_prompt = json.dumps(data) # JSON vorformatieren - if lang == 'de': - prompt_template = f""" - Dies ist ein deutscher Prompt mit Daten: {json_data_for_prompt} - """ - else: - prompt_template = f""" - This is an English prompt with data: {json_data_for_prompt} - """ - prompt = sys_instr + "\n\n" + prompt_template # System-Instruktion voranstellen + # Gut (maximal robust): + prompt_template = """ + 1) Mache dies: {variable_1} + 2) Mache das: {variable_2} + """ + prompt = prompt_template.format(variable_1=wert1, variable_2=wert2) + # System-Instruktion muss immer noch vorangestellt werden: + full_prompt = sys_instr + "\n\n" + prompt ``` * **Signaturen prüfen:** Shared Libraries (`helpers.py`) haben oft ältere Signaturen. Immer die tatsächliche Definition prüfen! diff --git a/gtm_architect_orchestrator.py b/gtm_architect_orchestrator.py index ebda1e07..e81e456e 100644 --- a/gtm_architect_orchestrator.py +++ b/gtm_architect_orchestrator.py @@ -132,33 +132,33 @@ def analyze_product(data): sys_instr = get_system_instruction(lang) if lang == 'en': - extraction_prompt = f""" + extraction_prompt_template = """ PHASE 1-A: TECHNICAL EXTRACTION -Input Product Description: "{product_input[:25000]}..." +Input Product Description: "{product_description}" Task: -1. Extract key technical features (specs, capabilities). +1) Extract key technical features (specs, capabilities). 2) Derive "Hard Constraints". IMPORTANT: Check Vmax (<20km/h = Private Grounds) and Cleaning Type (Vacuum != Heavy Debris/Snow). -3. Create a short raw analysis summary. +3) Create a short raw analysis summary. Output JSON format ONLY. """ + extraction_prompt = extraction_prompt_template.format(product_description=product_input[:25000]) else: - extraction_prompt = f""" + extraction_prompt_template = """ PHASE 1-A: TECHNICAL EXTRACTION -Input Product Description: "{product_input[:25000]}..." +Input Product Description: "{product_description}" Aufgabe: -1. Extrahiere technische Hauptmerkmale (Specs, Fähigkeiten). +1) Extrahiere technische Hauptmerkmale (Specs, Fähigkeiten). 2) Leite "Harte Constraints" ab. WICHTIG: Prüfe Vmax (<20km/h = Privatgelände) und Reinigungstyp (Vakuum != Grobschmutz/Schnee). -3. Erstelle eine kurze Rohanalyse-Zusammenfassung. +3) Erstelle eine kurze Rohanalyse-Zusammenfassung. Output JSON format ONLY. """ + extraction_prompt = extraction_prompt_template.format(product_description=product_input[:25000]) - # Fix: Prepend system instruction manually full_extraction_prompt = sys_instr + "\n\n" + extraction_prompt - # Using response_format_json=True since helpers.py supports it (for logging/prompt hint) extraction_response = call_openai_chat(full_extraction_prompt, response_format_json=True) extraction_data = json.loads(extraction_response) @@ -166,7 +166,7 @@ Output JSON format ONLY. constraints_json = json.dumps(extraction_data.get('constraints')) if lang == 'en': - conflict_prompt = f""" + conflict_prompt_template = """ PHASE 1-B: PORTFOLIO CONFLICT CHECK New Product Features: {features_json} @@ -181,8 +181,9 @@ Check if the new product overlaps significantly with existing ones (is it just a Output JSON format ONLY. """ + conflict_prompt = conflict_prompt_template.format(features_json=features_json, constraints_json=constraints_json) else: - conflict_prompt = f""" + conflict_prompt_template = """ PHASE 1-B: PORTFOLIO CONFLICT CHECK Neue Produkt-Features: {features_json} @@ -197,8 +198,8 @@ Prüfe, ob das neue Produkt signifikant mit bestehenden Produkten überlappt (Is Output JSON format ONLY. """ + conflict_prompt = conflict_prompt_template.format(features_json=features_json, constraints_json=constraints_json) - # Fix: Prepend system instruction manually full_conflict_prompt = sys_instr + "\n\n" + conflict_prompt conflict_response = call_openai_chat(full_conflict_prompt, response_format_json=True) conflict_data = json.loads(conflict_response) @@ -215,7 +216,7 @@ def discover_icps(data): constraints_json = json.dumps(phase1_result.get('constraints')) if lang == 'en': - prompt = f""" + prompt_template = """ PHASE 2: ICP DISCOVERY & DATA PROXIES Based on the product features: {features_json} And constraints: {constraints_json} @@ -226,8 +227,9 @@ Output JSON format ONLY: "dataProxies": [ {{ "target": "Criteria", "method": "How" }} ] }} """ + prompt = prompt_template.format(features_json=features_json, constraints_json=constraints_json) else: - prompt = f""" + prompt_template = """ PHASE 2: ICP DISCOVERY & DATA PROXIES Basierend auf Features: {features_json} Und Constraints: {constraints_json} @@ -238,8 +240,8 @@ Output JSON format ONLY: "dataProxies": [ {{ "target": "Kriterium", "method": "Methode" }} ] }} """ + prompt = prompt_template.format(features_json=features_json, constraints_json=constraints_json) - # Fix: Prepend system instruction manually full_prompt = sys_instr + "\n\n" + prompt response = call_openai_chat(full_prompt, response_format_json=True) print(response) @@ -250,7 +252,6 @@ def hunt_whales(data): sys_instr = get_system_instruction(lang) icps_json = json.dumps(phase2_result.get('icps')) prompt = f"PHASE 3: WHALE HUNTING for {icps_json}. Identify 3-5 concrete DACH companies per industry and buying center roles. Output JSON ONLY." - # Fix: Prepend system instruction manually full_prompt = sys_instr + "\n\n" + prompt response = call_openai_chat(full_prompt, response_format_json=True) print(response) @@ -262,7 +263,6 @@ def develop_strategy(data): sys_instr = get_system_instruction(lang) phase3_json = json.dumps(phase3_result) prompt = f"PHASE 4: STRATEGY Matrix for {phase3_json}. Apply Hybrid logic. Output JSON ONLY." - # Fix: Prepend system instruction manually full_prompt = sys_instr + "\n\n" + prompt response = call_openai_chat(full_prompt, response_format_json=True) print(response) @@ -272,7 +272,6 @@ def generate_assets(data): sys_instr = get_system_instruction(lang) data_json = json.dumps(data) prompt = f"PHASE 5: GTM STRATEGY REPORT Markdown. Use facts, TCO, ROI. Data: {data_json}" - # Fix: Prepend system instruction manually full_prompt = sys_instr + "\n\n" + prompt response = call_openai_chat(full_prompt) print(json.dumps(response)) @@ -282,7 +281,6 @@ def generate_sales_enablement(data): sys_instr = get_system_instruction(lang) data_json = json.dumps(data) prompt = f"PHASE 6: Battlecards and MJ Prompts. Data: {data_json}. Output JSON ONLY." - # Fix: Prepend system instruction manually full_prompt = sys_instr + "\n\n" + prompt response = call_openai_chat(full_prompt, response_format_json=True) print(response) @@ -321,4 +319,4 @@ def main(): print(json.dumps({"error": f"Unknown mode: {args.mode}"})) if __name__ == "__main__": - main() + main() \ No newline at end of file