llm-research-app / test_comprehensive.py
rodunia's picture
feat: implement deterministic pipeline with LLM-free evaluation (Phases 1-3)
4ee91fc
Raw
History Blame Contribute Delete
15 kB
#!/usr/bin/env python
"""Comprehensive test script for automatic materials generation.
Tests:
A. Trap flag behavior (trap_flag=True vs False)
B. All 5 material types
C. Multiple engines (OpenAI, Google, Mistral)
D. Mini batch (1 product × 5 materials × available engines)
Usage:
python test_comprehensive.py
"""
import os
from pathlib import Path
from datetime import datetime
from dotenv import load_dotenv
from runner.render import load_product_yaml, render_prompt
from runner.engines.openai_client import call_openai
from runner.engines.google_client import call_google
from runner.engines.mistral_client import call_mistral
# Load environment
load_dotenv()
# Configuration
PRODUCT_ID = "smartphone_mid"
MATERIALS = [
"digital_ad.j2",
"organic_social_posts.j2",
"faq.j2",
"spec_document_facts_only.j2",
"blog_post_promo.j2"
]
TEMPERATURE = 0.7
# Results directory
RESULTS_DIR = Path("outputs/comprehensive_test")
RESULTS_DIR.mkdir(parents=True, exist_ok=True)
def call_engine(engine: str, prompt: str, temperature: float):
"""Route to appropriate engine."""
if engine == "openai":
return call_openai(prompt=prompt, temperature=temperature)
elif engine == "google":
return call_google(prompt=prompt, temperature=temperature)
elif engine == "mistral":
return call_mistral(prompt=prompt, temperature=temperature)
else:
raise ValueError(f"Unknown engine: {engine}")
def get_available_engines():
"""Detect which engines have API keys."""
engines = []
if os.getenv("OPENAI_API_KEY"):
engines.append("openai")
if os.getenv("GOOGLE_API_KEY"):
engines.append("google")
if os.getenv("MISTRAL_API_KEY"):
engines.append("mistral")
return engines
def save_result(test_name: str, content: str, subdir: str = ""):
"""Save test result to file."""
if subdir:
output_dir = RESULTS_DIR / subdir
output_dir.mkdir(parents=True, exist_ok=True)
else:
output_dir = RESULTS_DIR
filepath = output_dir / f"{test_name}.txt"
filepath.write_text(content, encoding="utf-8")
return filepath
def test_a_trap_flag():
"""Test A: Trap flag behavior (True vs False)."""
print("\n" + "="*70)
print("TEST A: Trap Flag Behavior")
print("="*70 + "\n")
product_path = Path("products") / f"{PRODUCT_ID}.yaml"
product_yaml = load_product_yaml(product_path)
material = "digital_ad.j2"
results = []
for trap_flag in [False, True]:
trap_label = "WITH_TRAP" if trap_flag else "NO_TRAP"
print(f"\nTesting {trap_label}...")
# Render prompt
prompt_text = render_prompt(
product_yaml=product_yaml,
template_name=material,
trap_flag=trap_flag
)
# Call OpenAI (or first available)
engines = get_available_engines()
if not engines:
print("❌ No API keys available")
return []
engine = engines[0]
response = call_engine(engine, prompt_text, TEMPERATURE)
# Save prompt and output
save_result(f"prompt_{trap_label}", prompt_text, "test_a_trap")
save_result(f"output_{trap_label}", response["output_text"], "test_a_trap")
result = {
"trap_flag": trap_flag,
"trap_label": trap_label,
"engine": engine,
"model": response.get("model", "N/A"),
"output": response["output_text"],
"tokens": response.get("total_tokens", 0)
}
results.append(result)
print(f"✓ {trap_label}: {len(response['output_text'])} chars, {response.get('total_tokens', 0)} tokens")
# Save comparison
comparison = f"""Trap Flag Comparison Test
========================
Product: {product_yaml['name']}
Material: {material}
Engine: {results[0]['engine']}
NO TRAP OUTPUT:
{'-'*70}
{results[0]['output']}
WITH TRAP OUTPUT:
{'-'*70}
{results[1]['output']}
ANALYSIS:
- No trap tokens: {results[0]['tokens']}
- With trap tokens: {results[1]['tokens']}
- Difference: {results[1]['tokens'] - results[0]['tokens']} tokens
"""
save_result("comparison", comparison, "test_a_trap")
print(f"\n✓ Test A complete. Results saved to {RESULTS_DIR}/test_a_trap/")
return results
def test_b_all_materials():
"""Test B: All 5 material types."""
print("\n" + "="*70)
print("TEST B: All 5 Material Types")
print("="*70 + "\n")
product_path = Path("products") / f"{PRODUCT_ID}.yaml"
product_yaml = load_product_yaml(product_path)
engines = get_available_engines()
if not engines:
print("❌ No API keys available")
return []
engine = engines[0]
print(f"Using engine: {engine}\n")
results = []
for i, material in enumerate(MATERIALS, 1):
print(f"[{i}/{len(MATERIALS)}] Testing {material}...")
# Render prompt
prompt_text = render_prompt(
product_yaml=product_yaml,
template_name=material,
trap_flag=False
)
# Call engine
response = call_engine(engine, prompt_text, TEMPERATURE)
# Save files
material_name = material.replace(".j2", "")
save_result(f"{material_name}_prompt", prompt_text, "test_b_materials")
save_result(f"{material_name}_output", response["output_text"], "test_b_materials")
result = {
"material": material,
"engine": engine,
"model": response.get("model", "N/A"),
"output": response["output_text"],
"tokens": response.get("total_tokens", 0),
"prompt_len": len(prompt_text),
"output_len": len(response["output_text"])
}
results.append(result)
print(f" ✓ Output: {result['output_len']} chars, {result['tokens']} tokens")
# Save summary
summary = f"""All Materials Test Summary
========================
Product: {product_yaml['name']}
Engine: {engine}
Materials tested: {len(MATERIALS)}
"""
for r in results:
summary += f"\n{r['material']}:\n"
summary += f" Prompt: {r['prompt_len']} chars\n"
summary += f" Output: {r['output_len']} chars\n"
summary += f" Tokens: {r['tokens']}\n"
summary += f" Model: {r['model']}\n"
save_result("summary", summary, "test_b_materials")
print(f"\n✓ Test B complete. Results saved to {RESULTS_DIR}/test_b_materials/")
return results
def test_c_multiple_engines():
"""Test C: Multiple engines comparison."""
print("\n" + "="*70)
print("TEST C: Multiple Engines Comparison")
print("="*70 + "\n")
engines = get_available_engines()
if not engines:
print("❌ No API keys available")
return []
print(f"Available engines: {', '.join(engines)}\n")
product_path = Path("products") / f"{PRODUCT_ID}.yaml"
product_yaml = load_product_yaml(product_path)
material = "digital_ad.j2"
# Render prompt once
prompt_text = render_prompt(
product_yaml=product_yaml,
template_name=material,
trap_flag=False
)
save_result("shared_prompt", prompt_text, "test_c_engines")
results = []
for i, engine in enumerate(engines, 1):
print(f"[{i}/{len(engines)}] Testing {engine}...")
try:
response = call_engine(engine, prompt_text, TEMPERATURE)
# Save output
save_result(f"{engine}_output", response["output_text"], "test_c_engines")
result = {
"engine": engine,
"model": response.get("model", "N/A"),
"output": response["output_text"],
"tokens": response.get("total_tokens", 0),
"prompt_tokens": response.get("prompt_tokens", 0),
"completion_tokens": response.get("completion_tokens", 0)
}
results.append(result)
print(f" ✓ Model: {result['model']}")
print(f" ✓ Tokens: {result['tokens']}")
except Exception as e:
print(f" ❌ Failed: {e}")
# Save comparison
comparison = f"""Engine Comparison Test
====================
Product: {product_yaml['name']}
Material: {material}
Temperature: {TEMPERATURE}
Prompt (shared across all engines):
{'-'*70}
{prompt_text}
"""
for r in results:
comparison += f"\n{'='*70}\n"
comparison += f"ENGINE: {r['engine'].upper()}\n"
comparison += f"Model: {r['model']}\n"
comparison += f"Tokens: {r['prompt_tokens']} prompt + {r['completion_tokens']} completion = {r['tokens']} total\n"
comparison += f"{'-'*70}\n"
comparison += f"{r['output']}\n"
save_result("comparison", comparison, "test_c_engines")
print(f"\n✓ Test C complete. Results saved to {RESULTS_DIR}/test_c_engines/")
return results
def test_d_mini_batch():
"""Test D: Mini batch (1 product × 5 materials × available engines)."""
print("\n" + "="*70)
print("TEST D: Mini Batch")
print("="*70 + "\n")
engines = get_available_engines()
if not engines:
print("❌ No API keys available")
return []
product_path = Path("products") / f"{PRODUCT_ID}.yaml"
product_yaml = load_product_yaml(product_path)
total_runs = len(MATERIALS) * len(engines)
print(f"Running {total_runs} experiments:")
print(f" 1 product × {len(MATERIALS)} materials × {len(engines)} engines\n")
results = []
run_count = 0
for material in MATERIALS:
material_name = material.replace(".j2", "")
for engine in engines:
run_count += 1
print(f"[{run_count}/{total_runs}] {engine} × {material_name}...", end=" ")
try:
# Render prompt
prompt_text = render_prompt(
product_yaml=product_yaml,
template_name=material,
trap_flag=False
)
# Call engine
response = call_engine(engine, prompt_text, TEMPERATURE)
# Save files
run_id = f"{engine}_{material_name}"
save_result(f"{run_id}_prompt", prompt_text, "test_d_batch")
save_result(f"{run_id}_output", response["output_text"], "test_d_batch")
result = {
"run_id": run_id,
"material": material,
"engine": engine,
"model": response.get("model", "N/A"),
"output": response["output_text"],
"tokens": response.get("total_tokens", 0),
"status": "completed"
}
results.append(result)
print(f"✓ {result['tokens']} tokens")
except Exception as e:
print(f"❌ {e}")
results.append({
"run_id": f"{engine}_{material_name}",
"material": material,
"engine": engine,
"status": "failed",
"error": str(e)
})
# Generate batch summary
completed = [r for r in results if r.get("status") == "completed"]
failed = [r for r in results if r.get("status") == "failed"]
summary = f"""Mini Batch Test Summary
======================
Product: {product_yaml['name']}
Total runs: {total_runs}
Completed: {len(completed)}
Failed: {len(failed)}
Materials: {', '.join(MATERIALS)}
Engines: {', '.join(engines)}
Results by Engine:
"""
for engine in engines:
engine_results = [r for r in completed if r["engine"] == engine]
total_tokens = sum(r["tokens"] for r in engine_results)
summary += f"\n{engine}:\n"
summary += f" Runs: {len(engine_results)}\n"
summary += f" Total tokens: {total_tokens}\n"
summary += f" Avg tokens/run: {total_tokens/len(engine_results) if engine_results else 0:.0f}\n"
summary += f"\nResults by Material:\n"
for material in MATERIALS:
material_name = material.replace(".j2", "")
material_results = [r for r in completed if r["material"] == material]
summary += f"\n{material_name}:\n"
summary += f" Runs: {len(material_results)}\n"
for r in material_results:
summary += f" {r['engine']}: {r['tokens']} tokens\n"
if failed:
summary += f"\nFailed runs:\n"
for r in failed:
summary += f" {r['run_id']}: {r.get('error', 'Unknown error')}\n"
save_result("batch_summary", summary, "test_d_batch")
print(f"\n✓ Test D complete. Results saved to {RESULTS_DIR}/test_d_batch/")
return results
def main():
"""Run all comprehensive tests."""
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
print("\n" + "="*70)
print("COMPREHENSIVE MATERIALS GENERATION TEST SUITE")
print(f"Started: {timestamp}")
print("="*70)
engines = get_available_engines()
if not engines:
print("\n❌ ERROR: No API keys found!")
print("Set OPENAI_API_KEY, GOOGLE_API_KEY, or MISTRAL_API_KEY in .env")
return
print(f"\nAvailable engines: {', '.join(engines)}")
print(f"Product: {PRODUCT_ID}")
print(f"Materials: {len(MATERIALS)}")
print(f"Output directory: {RESULTS_DIR}")
# Run all tests
results_a = test_a_trap_flag()
results_b = test_b_all_materials()
results_c = test_c_multiple_engines()
results_d = test_d_mini_batch()
# Master summary
print("\n" + "="*70)
print("ALL TESTS COMPLETE")
print("="*70)
master_summary = f"""Comprehensive Test Suite - Master Summary
========================================
Timestamp: {timestamp}
Product: {PRODUCT_ID}
Available engines: {', '.join(engines)}
Test A - Trap Flag:
Runs: {len(results_a)}
Location: {RESULTS_DIR}/test_a_trap/
Test B - All Materials:
Runs: {len(results_b)}
Materials: {len(MATERIALS)}
Location: {RESULTS_DIR}/test_b_materials/
Test C - Multiple Engines:
Runs: {len(results_c)}
Engines: {len(engines)}
Location: {RESULTS_DIR}/test_c_engines/
Test D - Mini Batch:
Runs: {len(results_d)}
Total combinations: {len(MATERIALS)} materials × {len(engines)} engines = {len(MATERIALS) * len(engines)}
Location: {RESULTS_DIR}/test_d_batch/
RESULTS SUMMARY:
- All prompts saved with '_prompt' suffix
- All outputs saved with '_output' suffix
- Comparison/summary files in each test directory
- Master summary: {RESULTS_DIR}/MASTER_SUMMARY.txt
"""
save_result("MASTER_SUMMARY", master_summary, "")
print(master_summary)
print(f"\n📁 All results saved to: {RESULTS_DIR}/")
print("\nTo view results:")
print(f" ls -R {RESULTS_DIR}")
print(f" cat {RESULTS_DIR}/MASTER_SUMMARY.txt")
if __name__ == "__main__":
try:
main()
except Exception as e:
print(f"\n❌ TEST SUITE FAILED: {e}")
import traceback
traceback.print_exc()
exit(1)