Spaces:
Sleeping
Sleeping
Initial deploy: full app with 4-tier keyword research, per-task models; RAG index served from a separate private dataset repo
f23046e verified | """Ads plan module: looks up static industry ad benchmarks (and real per-keyword | |
| CPC data when the Google Ads API tier produced the keyword research) and asks | |
| the LLM to allocate the user's budget across channels, sized to their manpower.""" | |
| from __future__ import annotations | |
| import json | |
| from pathlib import Path | |
| from modules import llm, rag | |
| from modules.keywords import KeywordData | |
| # Ads planning is fundamentally quantitative β allocating a budget across channels | |
| # and projecting clicks/conversions/CAC from CPC/CTR/CVR benchmarks means the | |
| # arithmetic actually has to be right. DeepSeek's line is consistently the | |
| # strongest open-weight performer on math/quantitative-reasoning benchmarks, | |
| # which matters more here than general planning ability (seo.py) or creative | |
| # voice (social.py). | |
| RECOMMENDED_MODEL = "deepseek-ai/DeepSeek-V4-Pro" | |
| _BENCHMARKS_PATH = Path(__file__).resolve().parent.parent / "data" / "ad_benchmarks.json" | |
| _CHANNELS = ["search", "display", "meta", "linkedin", "tiktok", "youtube"] | |
| _METRICS = ["cpc_usd", "ctr_pct", "cvr_pct"] | |
| _benchmarks_cache: dict | None = None | |
| def _load_benchmarks() -> dict: | |
| global _benchmarks_cache | |
| if _benchmarks_cache is None: | |
| _benchmarks_cache = json.loads(_BENCHMARKS_PATH.read_text(encoding="utf-8")) | |
| return _benchmarks_cache | |
| def available_industries() -> list[str]: | |
| return sorted(_load_benchmarks()["industries"].keys()) | |
| def _average_benchmarks(all_industries: dict) -> dict: | |
| avg = {} | |
| for ch in _CHANNELS: | |
| avg[ch] = {} | |
| for m in _METRICS: | |
| values = [ind[ch][m] for ind in all_industries.values() if ch in ind] | |
| avg[ch][m] = round(sum(values) / len(values), 2) if values else None | |
| return avg | |
| def get_industry_benchmarks(industry_key: str) -> dict: | |
| industries = _load_benchmarks()["industries"] | |
| if industry_key in industries: | |
| return industries[industry_key] | |
| return _average_benchmarks(industries) # unknown industry: fall back to an overall average | |
| def _real_keyword_cpc_block(keyword_data: list[KeywordData] | None) -> str: | |
| """Real per-keyword Search CPC bid ranges from the Google Ads API tier, if | |
| that's what produced the keyword research β a more accurate signal for | |
| this specific product than the generic industry benchmark, which is | |
| always included regardless as a fallback for the other channels.""" | |
| if not keyword_data: | |
| return "" | |
| real = [kd for kd in keyword_data if kd.source == "google_ads_api" and kd.cpc] | |
| if not real: | |
| return "" | |
| rows = [{"keyword": kd.keyword, "volume": kd.volume, "cpc_bid_range": kd.cpc} for kd in real[:20]] | |
| return f""" | |
| ## Real per-keyword Search CPC data (Google Ads API, this product's actual keywords) | |
| Prefer this over the generic industry Search CPC benchmark above when projecting Search | |
| channel costs β it reflects this product's actual keywords, not an industry average. | |
| {json.dumps(rows, indent=2)} | |
| """ | |
| def build_ads_plan( | |
| hf_token: str, | |
| product_description: str, | |
| budget_usd_per_month: float, | |
| manpower_summary: str, | |
| industry_key: str, | |
| geo: str, | |
| keyword_data: list[KeywordData] | None = None, | |
| model: str | None = None, | |
| ) -> str: | |
| model = model or RECOMMENDED_MODEL | |
| benchmarks = get_industry_benchmarks(industry_key) | |
| rag_chunks = rag.retrieve(product_description, top_k=6, category=["online_ads", "general"]) | |
| rag_context = rag.grounding_block(rag_chunks) | |
| real_cpc_block = _real_keyword_cpc_block(keyword_data) | |
| prompt = f"""You are a senior digital advertising strategist. Using the industry benchmark | |
| data below (CPC/CTR/CVR per channel β directional industry averages, not guarantees), propose a | |
| paid advertising plan. | |
| Product/service: {product_description} | |
| Monthly budget: ${budget_usd_per_month:,.0f} USD | |
| Available manpower: {manpower_summary} | |
| Geography: {geo or "not specified"} | |
| Industry benchmark data (JSON, per channel: cpc_usd, ctr_pct, cvr_pct): | |
| {json.dumps(benchmarks, indent=2)} | |
| {real_cpc_block} | |
| ## Grounding context from paid advertising books & industry publications | |
| {rag_context} | |
| Produce: | |
| 1. A recommended channel mix (which of Search, Display, Meta, LinkedIn, TikTok, YouTube to use | |
| and why), sized to the available manpower β fewer channels if manpower is limited. | |
| 2. A budget split across the chosen channels (USD/month and % of budget). | |
| 3. Projected monthly clicks, leads/conversions, and estimated CAC per channel, computed from the | |
| benchmark CPC/CTR/CVR figures and the allocated budget (for Search, use the real per-keyword | |
| CPC data above instead of the benchmark if it's present). Show the math briefly. | |
| 4. Note explicitly which figures are real (Google Ads API keyword data, if present) versus | |
| industry-average estimates. | |
| 5. **Step-by-step implementation guide** β a numbered, actionable sequence for launching this in | |
| the first 2 weeks (account/pixel setup per platform, campaign structure to create first, | |
| targeting/budget settings for the first campaign, what to check at the day-3 and day-14 | |
| check-ins), written so someone with no prior paid-ads experience could follow it. | |
| Format as concise markdown with a summary table. | |
| """ | |
| return llm.chat( | |
| hf_token=hf_token, | |
| model=model, | |
| messages=[{"role": "user", "content": prompt}], | |
| max_tokens=1800, | |
| temperature=0.4, | |
| ) | |