CoolFace
Modelpublic

MuratcanKoylan/Marketing-Memory-Routing-8B

sourceHugging Faceapache-2.0updated 10mo agoView on Hugging Face
1likes
run_balanced_async.py260 linesDownload Raw Back to synthetic_data
1"""2Balanced Dataset Generation with Concurrent API Calls3 4Generates 10 items simultaneously per batch for faster generation.5"""6 7import json8import random9import time10import sys11import asyncio12import os13from typing import List, Dict, Any, Optional14from datetime import datetime15from concurrent.futures import ThreadPoolExecutor16import cohere17from dotenv import load_dotenv18 19load_dotenv()20 21# Target counts per category (balanced)22CATEGORY_TARGETS = {23    "company.brand_core": 77,24    "company.strategic_signatures": 77,25    "company.knowledge_artifacts": 77,26    "company.business_priorities": 77,27    "company.tools_config": 77,28    "company.performance_context": 77,29    "user.communication_style": 77,30    "user.strategic_approach": 77,31    "user.role_context": 77,32    "user.workflow_patterns": 77,33    "user.session_history": 77,34    "user.interaction_preferences": 77,35    "none": 77,36}37 38CATEGORY_EXAMPLES = {39    "company.brand_core": {40        "signals": ["brand voice is warm", "primary color is #2563EB", "never use jargon", "tagline is..."],41    },42    "company.strategic_signatures": {43        "signals": ["always prioritize retention", "80/20 rule", "never launch without testing"],44    },45    "company.knowledge_artifacts": {46        "signals": ["style guide says", "playbook recommends", "SOP for launches", "template includes"],47    },48    "company.business_priorities": {49        "signals": ["Q4 focus is", "this quarter's target", "holiday campaign", "prioritizing APAC"],50    },51    "company.tools_config": {52        "signals": ["Slack webhook URL", "HubSpot sync", "API key is", "Zapier integration"],53    },54    "company.performance_context": {55        "signals": ["24% open rate", "CTR improved by", "retrospective showed", "conversion dropped"],56    },57    "user.communication_style": {58        "signals": ["prefer bullet points", "keep it under 200 words", "casual tone", "data-driven"],59    },60    "user.strategic_approach": {61        "signals": ["prioritize speed over perfection", "test fast fail fast", "customer feedback"],62    },63    "user.role_context": {64        "signals": ["As VP of Marketing", "report to CMO", "budget authority up to", "manage team of"],65    },66    "user.workflow_patterns": {67        "signals": ["review drafts Monday", "don't send Friday", "async via Slack", "weekly sync Tuesday"],68    },69    "user.session_history": {70        "signals": ["as we discussed yesterday", "continuing from last", "proposal we started"],71    },72    "user.interaction_preferences": {73        "signals": ["push back on my ideas", "give me options", "be direct", "ask clarifying questions"],74    },75    "none": {76        "signals": ["what time is meeting", "checking status", "confirming receipt", "quick question"],77    },78}79 80 81class BalancedAsyncGenerator:82    def __init__(self):83        self.api_key = os.getenv("COHERE_API_KEY")84        if not self.api_key:85            raise ValueError("COHERE_API_KEY not found")86        self.client = cohere.ClientV2(api_key=self.api_key)87        self.model = "command-r-plus-08-2024"88        self.executor = ThreadPoolExecutor(max_workers=10)89    90    def _extract_text(self, response) -> Optional[str]:91        if not response or not getattr(response, "message", None):92            return None93        blocks = getattr(response.message, "content", []) or []94        for block in blocks:95            text = getattr(block, "text", None)96            if isinstance(text, str) and text.strip():97                return text98        return None99    100    def _generate_sync(self, category: str) -> Optional[Dict]:101        """Synchronous generation for a single category."""102        signals = CATEGORY_EXAMPLES.get(category, {}).get("signals", [])103        signals_text = "\n".join(f"- {s}" for s in signals[:4])104        105        if category == "none":106            prompt = f"""Generate a marketing conversation with NO long-term memory value.107Transactional, vague, or temporary only. Examples: status check, scheduling, confirming.1084-6 turns, no greetings, start mid-conversation.109 110OUTPUT (JSON only):111{{"scenario_id": "none_{random.randint(100,999)}", "conversation": [{{"role": "user", "content": "..."}}, {{"role": "assistant", "content": "..."}}], "labels": {{"categories": ["none"], "persistence_horizon": "short", "memory_scope": "none", "rationale": "..."}}, "metadata": {{"primary_category": "none", "turn_count": 4}}}}"""112        else:113            prompt = f"""Generate a marketing conversation demonstrating: {category}114 115SIGNALS FOR THIS CATEGORY:116{signals_text}117 118REQUIREMENTS:1191. MUST contain clear signals for {category}1202. 4-6 turns, no greetings, start mid-conversation1213. Include specific details (names, numbers, dates)122 123CRITICAL: categories array MUST include "{category}"124 125OUTPUT (JSON only):126{{"scenario_id": "{category.replace('.', '_')}_{random.randint(100,999)}", "conversation": [{{"role": "user", "content": "..."}}, {{"role": "assistant", "content": "..."}}], "labels": {{"categories": ["{category}"], "persistence_horizon": "long", "memory_scope": "company", "rationale": "..."}}, "metadata": {{"primary_category": "{category}", "turn_count": 4}}}}"""127 128        try:129            response = self.client.chat(130                messages=[{"role": "user", "content": prompt}],131                temperature=0.7,132                model=self.model,133                response_format={"type": "json_object"}134            )135            136            content = self._extract_text(response)137            if not content:138                return None139            140            if content.startswith("```json"):141                content = content[7:]142            if content.endswith("```"):143                content = content[:-3]144            145            data = json.loads(content.strip())146            147            # Validate target category is present148            categories = data.get("labels", {}).get("categories", [])149            if category.lower() not in [c.lower() for c in categories]:150                return None151            152            # Clean: Remove "none" if other categories exist153            if len(categories) > 1 and "none" in [c.lower() for c in categories]:154                data["labels"]["categories"] = [c for c in categories if c.lower() != "none"]155            156            return data157            158        except Exception as e:159            return None160    161    async def generate_batch(self, categories: List[str]) -> List[Dict]:162        """Generate a batch of items concurrently."""163        loop = asyncio.get_event_loop()164        tasks = [165            loop.run_in_executor(self.executor, self._generate_sync, cat)166            for cat in categories167        ]168        results = await asyncio.gather(*tasks, return_exceptions=True)169        return [r for r in results if isinstance(r, dict)]170 171 172async def run_balanced_generation_async():173    """Run balanced generation with concurrent batches."""174    175    generator = BalancedAsyncGenerator()176    timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")177    output_file = f"synthetic_data/balanced_dataset_{timestamp}.jsonl"178    179    # Track progress per category180    category_counts = {cat: 0 for cat in CATEGORY_TARGETS}181    all_data = []182    183    print("=" * 70, flush=True)184    print("BALANCED CONCURRENT DATASET GENERATION", flush=True)185    print("=" * 70, flush=True)186    print(f"Target per category: 77", flush=True)187    print(f"Total categories: {len(CATEGORY_TARGETS)}", flush=True)188    print(f"Expected total: {77 * len(CATEGORY_TARGETS)}", flush=True)189    print(f"Batch size: 10 concurrent requests", flush=True)190    print(flush=True)191    192    batch_num = 0193    194    while True:195        # Find categories that still need examples196        needed = []197        for cat, target in CATEGORY_TARGETS.items():198            remaining = target - category_counts[cat]199            needed.extend([cat] * min(remaining, 2))  # Up to 2 per category per batch200        201        if not needed:202            break203        204        # Take up to 10 for this batch205        batch_categories = needed[:10]206        batch_num += 1207        208        print(f"\n[Batch {batch_num}] Generating {len(batch_categories)} items...", flush=True)209        210        results = await generator.generate_batch(batch_categories)211        212        # Process results213        for result in results:214            if result:215                primary = result.get("metadata", {}).get("primary_category") or \216                         result.get("labels", {}).get("categories", ["unknown"])[0]217                218                if primary in category_counts:219                    category_counts[primary] += 1220                    all_data.append(result)221                    222                    # Save incrementally223                    with open(output_file, "a") as f:224                        f.write(json.dumps(result) + "\n")225        226        # Progress report227        total_done = sum(category_counts.values())228        total_target = sum(CATEGORY_TARGETS.values())229        print(f"  Success: {len(results)}/{len(batch_categories)} | Total: {total_done}/{total_target}", flush=True)230        231        # Show category progress every 10 batches232        if batch_num % 10 == 0:233            print("\n  Category Progress:", flush=True)234            for cat, count in sorted(category_counts.items()):235                target = CATEGORY_TARGETS[cat]236                bar = "█" * (count * 20 // target) + "░" * (20 - count * 20 // target)237                print(f"    {cat:<35} [{bar}] {count}/{target}", flush=True)238        239        # Rate limit: wait 3 seconds between batches240        await asyncio.sleep(3)241    242    # Final summary243    print("\n" + "=" * 70, flush=True)244    print("GENERATION COMPLETE", flush=True)245    print("=" * 70, flush=True)246    print(f"\nFinal Distribution:", flush=True)247    for cat, count in sorted(category_counts.items(), key=lambda x: -x[1]):248        pct = count / len(all_data) * 100 if all_data else 0249        print(f"  {cat:<40} {count:>4} ({pct:.1f}%)", flush=True)250    251    print(f"\nTotal examples: {len(all_data)}", flush=True)252    print(f"Output file: {output_file}", flush=True)253    254    return output_file255 256 257if __name__ == "__main__":258    asyncio.run(run_balanced_generation_async())259 260