Advisor / fine_tuning /scripts /build_seed_dataset.py
ps1811's picture
Fine-tuning files added
796e424
Raw
History Blame Contribute Delete
5.84 kB
from __future__ import annotations
import argparse
import json
import random
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT))
from app.ads1.ads_analyst import build_ads_analyst_context, build_ads_analyst_prompt, rule_based_insights
from app.ads1.budget_optimizer import build_budget_optimizer_context, rule_based_budget_actions
from app.ads1.growth_finder import build_growth_finder_context, rule_based_growth_actions
from app.ads1.keyword_inspector import build_keyword_features, build_keyword_prompt
from app.ads1.sample_data import generate_sample_dfs
from app.ads1.search_term_optimizer import (
build_search_optimizer_context,
build_search_optimizer_prompt,
rule_based_search_actions,
)
SYSTEM = "You are a Google Ads analyst. Reply with concise actionable markdown bullets only."
def chat_record(user: str, assistant: str, card: str, campaign_name: str | None = None) -> dict:
return {
"messages": [
{"role": "system", "content": SYSTEM},
{"role": "user", "content": user},
{"role": "assistant", "content": assistant},
],
"metadata": {
"card": card,
"campaign_name": campaign_name,
"source": "seed_sample_data",
},
}
def keyword_answer(context: dict) -> str:
rows = context.get("keywords", [])
bullets: list[str] = []
for row in rows:
keyword = row.get("keyword", "this keyword")
cost = float(row.get("cost", 0) or 0)
clicks = int(row.get("clicks", 0) or 0)
conversions = float(row.get("conversions", 0) or 0)
ctr = float(row.get("ctr", 0) or 0)
cpa = cost / conversions if conversions else cost
if conversions >= 10:
bullets.append(
f"- Treat '{keyword}' as a winning keyword because it produced {conversions:g} conversions at CPA {cpa:.2f} and CTR {ctr:.2f}%."
)
elif conversions == 0 and cost > 0:
bullets.append(
f"- Reduce or pause '{keyword}' because it spent {cost:.2f} across {clicks} clicks with 0 conversions."
)
elif conversions > 0:
bullets.append(
f"- Investigate '{keyword}' because it has {conversions:g} conversions but CPA {cpa:.2f}; scale only if CPA improves."
)
if len(bullets) >= 5:
break
return "\n\n".join(bullets) or "- No keyword action found because no usable keyword rows were available."
def keyword_context(dfs: dict, campaign_name: str) -> dict:
df = dfs["keywords"].copy()
if "campaign_name" in df.columns:
df = df[df["campaign_name"] == campaign_name]
df = build_keyword_features(df)
df = df.sort_values(["conversions", "cost"], ascending=[False, False]).head(12)
return {
"campaign_name": campaign_name,
"keywords": df.round(2).to_dict("records"),
}
def build_records() -> list[dict]:
dfs = generate_sample_dfs()
campaigns = dfs["campaigns"]["name"].dropna().astype(str).tolist()
records: list[dict] = []
for campaign_name in campaigns:
ads_context = build_ads_analyst_context(dfs, campaign_name)
records.append(
chat_record(
build_ads_analyst_prompt(ads_context),
rule_based_insights(ads_context),
"ads_analyst",
campaign_name,
)
)
kw_context = keyword_context(dfs, campaign_name)
records.append(
chat_record(
build_keyword_prompt(kw_context),
keyword_answer(kw_context),
"keyword_inspector",
campaign_name,
)
)
search_context = build_search_optimizer_context(dfs, campaign_name)
records.append(
chat_record(
build_search_optimizer_prompt(search_context),
rule_based_search_actions(search_context),
"search_term_cleaner",
campaign_name,
)
)
budget_context = build_budget_optimizer_context(dfs, campaign_name)
user = (
f"Rewrite these computed budget decisions for {campaign_name} into 3 to 5 concise bullets.\n"
"Each bullet must mention the campaign or keyword, the action, and the evidence.\n\n"
f"Data (JSON):\n{json.dumps(budget_context, indent=2, default=str)}"
)
records.append(chat_record(user, rule_based_budget_actions(budget_context), "budget_optimizer", campaign_name))
growth_context = build_growth_finder_context(dfs, campaign_name)
user = (
f"Rewrite these computed growth decisions for {campaign_name} into concise growth opportunity bullets.\n"
"Each bullet must mention the keyword, the scale action, and the evidence.\n\n"
f"Data (JSON):\n{json.dumps(growth_context, indent=2, default=str)}"
)
records.append(chat_record(user, rule_based_growth_actions(growth_context), "growth_finder", campaign_name))
return records
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--out", type=Path, default=ROOT / "fine_tuning" / "data" / "seed.jsonl")
parser.add_argument("--shuffle", action="store_true")
args = parser.parse_args()
records = build_records()
if args.shuffle:
random.Random(42).shuffle(records)
args.out.parent.mkdir(parents=True, exist_ok=True)
with args.out.open("w", encoding="utf-8") as handle:
for record in records:
handle.write(json.dumps(record, ensure_ascii=False) + "\n")
print(f"Wrote {len(records)} records to {args.out}")
return 0
if __name__ == "__main__":
raise SystemExit(main())