#!/usr/bin/env python3 """ Novelty-Driven Task Generation Demo Interactive CLI for exploring the novelty-driven task generation agent. Examples: # Basic usage with default settings python demo.py "Improve urban transportation" # Custom threshold and iterations python demo.py "Design a better bicycle" --threshold 0.35 --max-iter 15 # Use Chinese language python demo.py "改进城市交通" --language zh # Use exhaustion strategy (explore until stuck) python demo.py "Sustainable energy solutions" --strategy exhaust # Use coverage strategy (find N distinct clusters) python demo.py "Future of education" --strategy coverage --clusters 5 # Save results to file python demo.py "Smart home innovations" --output results.json # Verbose mode with detailed logging python demo.py "Healthcare improvements" --verbose """ import argparse import asyncio import json import logging import sys from datetime import datetime from pathlib import Path # Add parent directory to path for imports sys.path.insert(0, str(Path(__file__).parent.parent.parent)) from experiments.novelty_loop.agent import ( NoveltyDrivenTaskAgent, ExhaustFrontierAgent, CoverageTargetAgent, GeneratedTask, TaskGenerationResult ) # ANSI color codes for terminal output class Colors: HEADER = '\033[95m' BLUE = '\033[94m' CYAN = '\033[96m' GREEN = '\033[92m' YELLOW = '\033[93m' RED = '\033[91m' BOLD = '\033[1m' UNDERLINE = '\033[4m' END = '\033[0m' def print_header(text: str): """Print a styled header.""" print(f"\n{Colors.BOLD}{Colors.HEADER}{'='*60}{Colors.END}") print(f"{Colors.BOLD}{Colors.HEADER}{text.center(60)}{Colors.END}") print(f"{Colors.BOLD}{Colors.HEADER}{'='*60}{Colors.END}\n") def print_iteration(task: GeneratedTask): """Print iteration result with colors.""" status_color = Colors.GREEN if task.is_breakthrough else Colors.CYAN print(f"\n{Colors.BOLD}Iteration {task.iteration}{Colors.END}") print(f" {Colors.YELLOW}Expert:{Colors.END} {task.expert} ({task.expert_domain})") print(f" {Colors.YELLOW}Task:{Colors.END} {task.task}") novelty_bar = "█" * int(task.novelty_score * 20) + "░" * (20 - int(task.novelty_score * 20)) print(f" {Colors.YELLOW}Novelty:{Colors.END} [{novelty_bar}] {task.novelty_score:.4f}") if task.is_breakthrough: print(f" {Colors.GREEN}{Colors.BOLD}★ BREAKTHROUGH! ★{Colors.END}") def print_result(result: TaskGenerationResult): """Print final result summary.""" print_header("RESULTS") print(f"{Colors.BOLD}Seed Problem:{Colors.END} {result.seed_problem}") print(f"{Colors.BOLD}Total Iterations:{Colors.END} {result.total_iterations}") print(f"{Colors.BOLD}Terminated By:{Colors.END} {result.terminated_by}") if result.novelty_trajectory: print(f"\n{Colors.BOLD}Novelty Statistics:{Colors.END}") print(f" Mean Novelty: {result.novelty_trajectory.mean_novelty:.4f}") print(f" Max Novelty: {result.novelty_trajectory.max_novelty:.4f}") print(f" Jump Ratio: {result.novelty_trajectory.jump_ratio:.2%}") if result.breakthrough_task: print(f"\n{Colors.GREEN}{Colors.BOLD}{'='*60}{Colors.END}") print(f"{Colors.GREEN}{Colors.BOLD}BREAKTHROUGH TASK{Colors.END}") print(f"{Colors.GREEN}{Colors.BOLD}{'='*60}{Colors.END}") print(f"\n{Colors.BOLD}Expert:{Colors.END} {result.breakthrough_task.expert}") print(f"{Colors.BOLD}Domain:{Colors.END} {result.breakthrough_task.expert_domain}") print(f"{Colors.BOLD}Task:{Colors.END}") print(f" {Colors.CYAN}{result.breakthrough_task.task}{Colors.END}") print(f"\n{Colors.BOLD}Novelty Score:{Colors.END} {result.breakthrough_task.novelty_score:.4f}") print(f"{Colors.BOLD}Found at Iteration:{Colors.END} {result.breakthrough_task.iteration}") # Show trajectory summary print(f"\n{Colors.BOLD}Exploration Trajectory:{Colors.END}") for task in result.trajectory: marker = "★" if task.is_breakthrough else "○" novelty_indicator = "█" * int(task.novelty_score * 10) print(f" {marker} [{task.iteration:2d}] {task.expert:20s} | {novelty_indicator:10s} {task.novelty_score:.3f}") def save_result(result: TaskGenerationResult, output_path: str): """Save result to JSON file.""" with open(output_path, "w", encoding="utf-8") as f: json.dump(result.to_dict(), f, ensure_ascii=False, indent=2) print(f"\n{Colors.GREEN}Results saved to: {output_path}{Colors.END}") async def run_demo(args): """Run the novelty-driven task generation demo.""" print_header("NOVELTY-DRIVEN TASK GENERATION") print(f"{Colors.BOLD}Configuration:{Colors.END}") print(f" Seed Problem: {args.seed_problem}") print(f" Strategy: {args.strategy}") print(f" Novelty Threshold: {args.threshold}") print(f" Max Iterations: {args.max_iter}") print(f" Language: {args.language}") print(f" LLM Model: {args.model}") # Create appropriate agent based on strategy common_kwargs = { "max_iterations": args.max_iter, "llm_model": args.model, "embedding_model": args.embedding_model, "language": args.language, "temperature": args.temperature, "on_iteration": print_iteration if not args.quiet else None } if args.strategy == "breakthrough": agent = NoveltyDrivenTaskAgent( novelty_threshold=args.threshold, **common_kwargs ) elif args.strategy == "exhaust": agent = ExhaustFrontierAgent( exhaustion_threshold=args.exhaust_threshold, window_size=args.window_size, min_iterations=args.min_iter, **common_kwargs ) elif args.strategy == "coverage": agent = CoverageTargetAgent( target_clusters=args.clusters, cluster_threshold=args.cluster_threshold, **common_kwargs ) else: print(f"{Colors.RED}Unknown strategy: {args.strategy}{Colors.END}") return print(f"\n{Colors.BOLD}Starting generation loop...{Colors.END}") print("-" * 60) try: result = await agent.run(args.seed_problem) print_result(result) if args.output: save_result(result, args.output) except Exception as e: print(f"\n{Colors.RED}Error: {e}{Colors.END}") if args.verbose: import traceback traceback.print_exc() finally: await agent.close() def main(): parser = argparse.ArgumentParser( description="Novelty-Driven Task Generation Demo", formatter_class=argparse.RawDescriptionHelpFormatter, epilog=__doc__ ) # Required argument parser.add_argument( "seed_problem", help="The seed problem or challenge to explore" ) # Strategy selection parser.add_argument( "--strategy", "-s", choices=["breakthrough", "exhaust", "coverage"], default="breakthrough", help="Termination strategy (default: breakthrough)" ) # Common options parser.add_argument( "--threshold", "-t", type=float, default=0.4, help="Novelty threshold for breakthrough (default: 0.4)" ) parser.add_argument( "--max-iter", "-m", type=int, default=20, help="Maximum iterations (default: 20)" ) parser.add_argument( "--language", "-l", choices=["en", "zh"], default="en", help="Language for prompts and experts (default: en)" ) # Model options parser.add_argument( "--model", default="qwen3:8b", help="LLM model for task generation (default: qwen3:8b)" ) parser.add_argument( "--embedding-model", default="qwen3-embedding:4b", help="Embedding model (default: qwen3-embedding:4b)" ) parser.add_argument( "--temperature", type=float, default=0.7, help="LLM temperature (default: 0.7)" ) # Exhaust strategy options parser.add_argument( "--exhaust-threshold", type=float, default=0.15, help="Exhaustion threshold for 'exhaust' strategy (default: 0.15)" ) parser.add_argument( "--window-size", type=int, default=3, help="Window size for exhaustion check (default: 3)" ) parser.add_argument( "--min-iter", type=int, default=5, help="Minimum iterations before exhaustion check (default: 5)" ) # Coverage strategy options parser.add_argument( "--clusters", type=int, default=5, help="Target clusters for 'coverage' strategy (default: 5)" ) parser.add_argument( "--cluster-threshold", type=float, default=0.7, help="Cluster similarity threshold (default: 0.7)" ) # Output options parser.add_argument( "--output", "-o", help="Save results to JSON file" ) parser.add_argument( "--quiet", "-q", action="store_true", help="Suppress iteration output" ) parser.add_argument( "--verbose", "-v", action="store_true", help="Enable verbose logging" ) args = parser.parse_args() # Configure logging if args.verbose: logging.basicConfig( level=logging.DEBUG, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s" ) else: logging.basicConfig(level=logging.WARNING) # Run the demo asyncio.run(run_demo(args)) if __name__ == "__main__": main()