fix(gtm): Remove unsupported 'system_message' arg from call_openai_chat calls and fix indentation
This commit is contained in:
@@ -16,23 +16,91 @@ market_db_manager.init_db()
|
|||||||
|
|
||||||
def get_system_instruction(lang):
|
def get_system_instruction(lang):
|
||||||
if lang == 'de':
|
if lang == 'de':
|
||||||
# ... (rest of get_system_instruction remains same)
|
return """
|
||||||
|
# IDENTITY & PURPOSE
|
||||||
|
Du bist die "GTM Architect Engine" für Roboplanet. Deine Aufgabe ist es, für neue technische Produkte (Roboter) eine präzise Go-to-Market-Strategie zu entwickeln.
|
||||||
|
Du handelst nicht als kreativer Werbetexter, sondern als strategischer Analyst. Dein oberstes Ziel ist Product-Market-Fit und operative Umsetzbarkeit.
|
||||||
|
Antworte IMMER auf DEUTSCH.
|
||||||
|
|
||||||
|
# CONTEXT: THE PARENT COMPANY (WACKLER)
|
||||||
|
Wir sind Teil der Wackler Group, einem großen Facility-Management-Dienstleister.
|
||||||
|
Unsere Strategie ist NICHT "Roboter ersetzen Menschen", sondern "Hybrid-Reinigung":
|
||||||
|
- 80% der Arbeit (monotone Flächenleistung) = Roboter.
|
||||||
|
- 20% der Arbeit (Edge Cases, Winterdienst, Treppen, Grobschmutz) = Manuelle Reinigung durch Wackler.
|
||||||
|
|
||||||
|
# STRICT ANALYSIS RULES (MUST FOLLOW):
|
||||||
|
1. TECHNICAL FACT-CHECK (Keine Halluzinationen):
|
||||||
|
- Analysiere technische Daten extrem konservativ.
|
||||||
|
- Vakuumsystem = Kein "Winterdienst" (Schnee) und keine "Schwerindustrie" (Metallspäne), außer explizit genannt.
|
||||||
|
- Erfinde keine Features, nur um eine Zielgruppe passend zu machen.
|
||||||
|
|
||||||
|
2. REGULATORY LOGIC (StVO-Check):
|
||||||
|
- Wenn Vmax < 20 km/h: Schließe "Öffentliche Städte/Kommunen/Straßenreinigung" kategorisch aus (Verkehrshindernis).
|
||||||
|
- Fokusänderung: Konzentriere dich stattdessen ausschließlich auf "Große, zusammenhängende Privatflächen" (Gated Areas).
|
||||||
|
|
||||||
|
3. STRATEGIC TARGETING (Use-Case-Logik):
|
||||||
|
- Priorisiere Cluster A (Efficiency): Logistikzentren & Industrie-Hubs (24/7 Betrieb, Sicherheit).
|
||||||
|
- Priorisiere Cluster B (Experience): Shopping Center, Outlets & Freizeitparks (Sauberkeit als Visitenkarte).
|
||||||
|
- Entferne reine E-Commerce-Händler ohne physische Kundenfläche.
|
||||||
|
|
||||||
|
4. THE "HYBRID SERVICE" LOGIC (RULE 5):
|
||||||
|
Wann immer du ein "Hartes Constraint" oder eine technische Limitierung identifizierst (z.B. "Kein Winterdienst" oder "Kommt nicht in Ecken"), darfst du dies niemals als reines "Nein" stehen lassen.
|
||||||
|
Wende stattdessen die **"Yes, and..." Logik** an:
|
||||||
|
1. **Identifiziere die Lücke:** (z.B. "Roboter kann bei Schnee nicht fahren").
|
||||||
|
2. **Fülle die Lücke mit Service:** Schlage explizit vor, diesen Teil durch "Wackler Human Manpower" abzudecken.
|
||||||
|
3. **Formuliere den USP:** Positioniere das Gesamtpaket als "100% Coverage" (Roboter + Mensch aus einer Hand).
|
||||||
|
|
||||||
|
# THE PRODUCT MATRIX (CONTEXT)
|
||||||
|
Behalte immer im Hinterkopf, dass wir bereits folgende Produkte im Portfolio haben:
|
||||||
|
1. "Indoor Scrubber 50": Innenreinigung, Hartboden, Fokus: Supermärkte. Message: "Sauberkeit im laufenden Betrieb."
|
||||||
|
2. "Service Bot Bella": Service/Gastro, Indoor. Fokus: Restaurants. Message: "Entlastung für Servicekräfte."
|
||||||
|
""
|
||||||
else:
|
else:
|
||||||
return """
|
return """
|
||||||
# IDENTITY & PURPOSE
|
# IDENTITY & PURPOSE
|
||||||
You are the "GTM Architect Engine" for Roboplanet. Your task is to develop a precise Go-to-Market strategy for new technical products (robots).
|
You are the "GTM Architect Engine" for Roboplanet. Your task is to develop a precise Go-to-Market strategy for new technical products (robots).
|
||||||
# ... (rest of english instructions)
|
You do not act as a creative copywriter, but as a strategic analyst. Your top goal is product-market fit and operational feasibility.
|
||||||
|
ALWAYS respond in ENGLISH.
|
||||||
|
|
||||||
|
# CONTEXT: THE PARENT COMPANY (WACKLER)
|
||||||
|
We are part of the Wackler Group, a major facility management service provider.
|
||||||
|
Our strategy is NOT "Robots replace humans", but "Hybrid Cleaning":
|
||||||
|
- 80% of work (monotonous area coverage) = Robots.
|
||||||
|
- 20% of work (Edge cases, winter service, stairs, heavy debris) = Manual cleaning by Wackler.
|
||||||
|
|
||||||
|
# STRICT ANALYSIS RULES (MUST FOLLOW):
|
||||||
|
1. TECHNICAL FACT-CHECK (No Hallucinations):
|
||||||
|
- Analyze technical data extremely conservatively.
|
||||||
|
- Vacuum System = No "Winter Service" (snow) and no "Heavy Industry" (metal shavings), unless explicitly stated.
|
||||||
|
- Do not invent features just to fit a target audience.
|
||||||
|
|
||||||
|
2. REGULATORY LOGIC (Traffic Regs):
|
||||||
|
- If Vmax < 20 km/h: Categorically exclude "Public Cities/Streets" (traffic obstruction).
|
||||||
|
- Change Focus: Concentrate exclusively on "Large, contiguous private areas" (Gated Areas).
|
||||||
|
|
||||||
|
3. STRATEGIC TARGETING (Use Case Logic):
|
||||||
|
- Prioritize Cluster A (Efficiency): Logistics Centers & Industrial Hubs (24/7 ops, safety).
|
||||||
|
- Prioritize Cluster B (Experience): Shopping Centers, Outlets & Theme Parks (Cleanliness as a calling card).
|
||||||
|
- Remove pure E-commerce retailers without physical customer areas.
|
||||||
|
|
||||||
|
4. THE "HYBRID SERVICE" LOGIC (RULE 5):
|
||||||
|
Whenever you identify a "Hard Constraint" or a technical limitation (e.g., "No winter service" or "Cannot reach corners"), never let this stand as a simple "No".
|
||||||
|
Instead, apply the **"Yes, and..." logic**:
|
||||||
|
1. **Identify the gap:** (e.g., "Robot cannot operate in snow").
|
||||||
|
2. **Fill the gap with service:** Explicitly suggest covering this part with "Wackler Human Manpower".
|
||||||
|
3. **Formulate the USP:** Position the total package as "100% Coverage" (Robot + Human from a single source).
|
||||||
|
|
||||||
|
# THE PRODUCT MATRIX (CONTEXT)
|
||||||
|
Always keep in mind that we already have the following products in our portfolio:
|
||||||
|
1. "Indoor Scrubber 50": Indoor cleaning, hard floor, supermarkets.
|
||||||
|
2. "Service Bot Bella": Service/Hospitality, indoor. Focus: restaurants. Message: "Relief for service staff."
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# --- Database Handlers ---
|
# --- Database Handlers ---
|
||||||
|
|
||||||
def save_project_handler(data):
|
def save_project_handler(data):
|
||||||
# data contains the full application state
|
|
||||||
# Ensure we have a name for the list view
|
|
||||||
if 'name' not in data:
|
if 'name' not in data:
|
||||||
# Try to derive a name from product input or phases
|
|
||||||
input_text = data.get('productInput', '')
|
input_text = data.get('productInput', '')
|
||||||
# Take first 30 chars or first line
|
|
||||||
derived_name = input_text.split('\n')[0][:30] if input_text else "Untitled Strategy"
|
derived_name = input_text.split('\n')[0][:30] if input_text else "Untitled Strategy"
|
||||||
data['name'] = derived_name
|
data['name'] = derived_name
|
||||||
|
|
||||||
@@ -40,7 +108,6 @@ def save_project_handler(data):
|
|||||||
print(json.dumps(result))
|
print(json.dumps(result))
|
||||||
|
|
||||||
def list_projects_handler(data):
|
def list_projects_handler(data):
|
||||||
# data is ignored here
|
|
||||||
projects = market_db_manager.get_all_projects()
|
projects = market_db_manager.get_all_projects()
|
||||||
print(json.dumps(projects))
|
print(json.dumps(projects))
|
||||||
|
|
||||||
@@ -60,48 +127,173 @@ def delete_project_handler(data):
|
|||||||
# --- AI Handlers ---
|
# --- AI Handlers ---
|
||||||
|
|
||||||
def analyze_product(data):
|
def analyze_product(data):
|
||||||
# ... (rest of analyze_product and other AI handlers remain same)
|
product_input = data.get('productInput')
|
||||||
|
lang = data.get('language', 'de')
|
||||||
|
sys_instr = get_system_instruction(lang)
|
||||||
|
|
||||||
|
extraction_prompt = f"""
|
||||||
|
PHASE 1-A: TECHNICAL EXTRACTION
|
||||||
|
Input Product Description: "{product_input[:25000]}..."
|
||||||
|
|
||||||
|
Task:
|
||||||
|
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.
|
||||||
|
|
||||||
|
Output JSON format ONLY.
|
||||||
|
""" if lang == 'en' else f"""
|
||||||
|
PHASE 1-A: TECHNICAL EXTRACTION
|
||||||
|
Input Product Description: "{product_input[:25000]}..."
|
||||||
|
|
||||||
|
Aufgabe:
|
||||||
|
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.
|
||||||
|
|
||||||
|
Output JSON format ONLY.
|
||||||
|
"""
|
||||||
|
# Fix: Prepend system instruction manually
|
||||||
|
full_extraction_prompt = sys_instr + "\n\n" + extraction_prompt
|
||||||
|
extraction_response = call_openai_chat(full_extraction_prompt, json_mode=True)
|
||||||
|
extraction_data = json.loads(extraction_response)
|
||||||
|
|
||||||
|
conflict_prompt = f"""
|
||||||
|
PHASE 1-B: PORTFOLIO CONFLICT CHECK
|
||||||
|
|
||||||
|
New Product Features: {json.dumps(extraction_data.get('features'))}
|
||||||
|
New Product Constraints: {json.dumps(extraction_data.get('constraints'))}
|
||||||
|
|
||||||
|
Existing Portfolio:
|
||||||
|
1. "Indoor Scrubber 50": Indoor cleaning, hard floor, supermarkets.
|
||||||
|
2. "Service Bot Bella": Service/Gastro, indoor, restaurants.
|
||||||
|
|
||||||
|
Task:
|
||||||
|
Check if the new product overlaps significantly with existing ones (is it just a clone?).
|
||||||
|
|
||||||
|
Output JSON format ONLY.
|
||||||
|
""" if lang == 'en' else f"""
|
||||||
|
PHASE 1-B: PORTFOLIO CONFLICT CHECK
|
||||||
|
|
||||||
|
Neue Produkt-Features: {json.dumps(extraction_data.get('features'))}
|
||||||
|
Neue Produkt-Constraints: {json.dumps(extraction_data.get('constraints'))}
|
||||||
|
|
||||||
|
Existierendes Portfolio:
|
||||||
|
1. "Indoor Scrubber 50": Innenreinigung, Hartboden, Supermärkte.
|
||||||
|
2. "Service Bot Bella": Service/Gastro, Indoor, Restaurants.
|
||||||
|
|
||||||
|
Aufgabe:
|
||||||
|
Prüfe, ob das neue Produkt signifikant mit bestehenden Produkten überlappt (Ist es nur ein Klon?).
|
||||||
|
|
||||||
|
Output JSON format ONLY.
|
||||||
|
"""
|
||||||
|
# Fix: Prepend system instruction manually
|
||||||
|
full_conflict_prompt = sys_instr + "\n\n" + conflict_prompt
|
||||||
|
conflict_response = call_openai_chat(full_conflict_prompt, json_mode=True)
|
||||||
|
conflict_data = json.loads(conflict_response)
|
||||||
|
|
||||||
|
final_result = {{**extraction_data, **conflict_data}}
|
||||||
|
print(json.dumps(final_result))
|
||||||
|
|
||||||
|
def discover_icps(data):
|
||||||
|
phase1_result = data.get('phase1Result')
|
||||||
|
lang = data.get('language', 'de')
|
||||||
|
sys_instr = get_system_instruction(lang)
|
||||||
|
|
||||||
|
prompt = f"""
|
||||||
|
PHASE 2: ICP DISCOVERY & DATA PROXIES
|
||||||
|
Based on the product features: {json.dumps(phase1_result.get('features'))}
|
||||||
|
And constraints: {json.dumps(phase1_result.get('constraints'))}
|
||||||
|
|
||||||
|
Output JSON format ONLY:
|
||||||
|
{{
|
||||||
|
"icps": [ {{ "name": "Industry Name", "rationale": "Rationale" }} ],
|
||||||
|
"dataProxies": [ {{ "target": "Criteria", "method": "How" }} ]
|
||||||
|
}}
|
||||||
|
""" if lang == 'en' else f"""
|
||||||
|
PHASE 2: ICP DISCOVERY & DATA PROXIES
|
||||||
|
Basierend auf Features: {json.dumps(phase1_result.get('features'))}
|
||||||
|
Und Constraints: {json.dumps(phase1_result.get('constraints'))}
|
||||||
|
|
||||||
|
Output JSON format ONLY:
|
||||||
|
{{
|
||||||
|
"icps": [ {{ "name": "Branchen Name", "rationale": "Begründung" }} ],
|
||||||
|
"dataProxies": [ {{ "target": "Kriterium", "method": "Methode" }} ]
|
||||||
|
}}
|
||||||
|
"""
|
||||||
|
# Fix: Prepend system instruction manually
|
||||||
|
full_prompt = sys_instr + "\n\n" + prompt
|
||||||
|
response = call_openai_chat(full_prompt, json_mode=True)
|
||||||
|
print(response)
|
||||||
|
|
||||||
|
def hunt_whales(data):
|
||||||
|
phase2_result = data.get('phase2Result')
|
||||||
|
lang = data.get('language', 'de')
|
||||||
|
sys_instr = get_system_instruction(lang)
|
||||||
|
prompt = f"PHASE 3: WHALE HUNTING for {json.dumps(phase2_result.get('icps'))}. 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, json_mode=True)
|
||||||
|
print(response)
|
||||||
|
|
||||||
|
def develop_strategy(data):
|
||||||
|
phase3_result = data.get('phase3Result')
|
||||||
|
phase1_result = data.get('phase1Result')
|
||||||
|
lang = data.get('language', 'de')
|
||||||
|
sys_instr = get_system_instruction(lang)
|
||||||
|
prompt = f"PHASE 4: STRATEGY Matrix for {json.dumps(phase3_result)}. Apply Hybrid logic. Output JSON ONLY."
|
||||||
|
# Fix: Prepend system instruction manually
|
||||||
|
full_prompt = sys_instr + "\n\n" + prompt
|
||||||
|
response = call_openai_chat(full_prompt, json_mode=True)
|
||||||
|
print(response)
|
||||||
|
|
||||||
|
def generate_assets(data):
|
||||||
|
lang = data.get('language', 'de')
|
||||||
|
sys_instr = get_system_instruction(lang)
|
||||||
|
prompt = f"PHASE 5: GTM STRATEGY REPORT Markdown. Use facts, TCO, ROI. Data: {json.dumps(data)}"
|
||||||
|
# Fix: Prepend system instruction manually
|
||||||
|
full_prompt = sys_instr + "\n\n" + prompt
|
||||||
|
response = call_openai_chat(full_prompt)
|
||||||
|
print(json.dumps(response))
|
||||||
|
|
||||||
|
def generate_sales_enablement(data):
|
||||||
|
lang = data.get('language', 'de')
|
||||||
|
sys_instr = get_system_instruction(lang)
|
||||||
|
prompt = f"PHASE 6: Battlecards and MJ Prompts. Data: {json.dumps(data)}. Output JSON ONLY."
|
||||||
|
# Fix: Prepend system instruction manually
|
||||||
|
full_prompt = sys_instr + "\n\n" + prompt
|
||||||
|
response = call_openai_chat(full_prompt, json_mode=True)
|
||||||
|
print(response)
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
parser = argparse.ArgumentParser(description="GTM Architect Orchestrator")
|
parser = argparse.ArgumentParser(description="GTM Architect Orchestrator")
|
||||||
parser.add_argument("--mode", type=str, required=True, help="Execution mode")
|
parser.add_argument("--mode", type=str, required=True, help="Execution mode")
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
# Read stdin only if there is input, otherwise data is empty dict
|
|
||||||
if not sys.stdin.isatty():
|
if not sys.stdin.isatty():
|
||||||
try:
|
try:
|
||||||
data = json.loads(sys.stdin.read())
|
data = json.loads(sys.stdin.read())
|
||||||
except json.JSONDecodeError:
|
except:
|
||||||
data = {}
|
data = {}
|
||||||
else:
|
else:
|
||||||
data = {}
|
data = {}
|
||||||
|
|
||||||
if args.mode == "analyze_product":
|
modes = {
|
||||||
analyze_product(data)
|
"analyze_product": analyze_product,
|
||||||
elif args.mode == "discover_icps":
|
"discover_icps": discover_icps,
|
||||||
discover_icps(data)
|
"hunt_whales": hunt_whales,
|
||||||
elif args.mode == "hunt_whales":
|
"develop_strategy": develop_strategy,
|
||||||
hunt_whales(data)
|
"generate_assets": generate_assets,
|
||||||
elif args.mode == "develop_strategy":
|
"generate_sales_enablement": generate_sales_enablement,
|
||||||
develop_strategy(data)
|
"save_project": save_project_handler,
|
||||||
elif args.mode == "generate_assets":
|
"list_projects": list_projects_handler,
|
||||||
generate_assets(data)
|
"load_project": load_project_handler,
|
||||||
elif args.mode == "generate_sales_enablement":
|
"delete_project": delete_project_handler
|
||||||
generate_sales_enablement(data)
|
}
|
||||||
# DB Modes
|
|
||||||
elif args.mode == "save_project":
|
if args.mode in modes:
|
||||||
save_project_handler(data)
|
modes[args.mode](data)
|
||||||
elif args.mode == "list_projects":
|
|
||||||
list_projects_handler(data)
|
|
||||||
elif args.mode == "load_project":
|
|
||||||
load_project_handler(data)
|
|
||||||
elif args.mode == "delete_project":
|
|
||||||
delete_project_handler(data)
|
|
||||||
else:
|
else:
|
||||||
print(json.dumps({"status": "error", "message": f"Unknown mode: {args.mode}"}))
|
print(json.dumps({"error": f"Unknown mode: {args.mode}"}))
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|||||||
Reference in New Issue
Block a user