ps1811 commited on
Commit
86eaf57
·
1 Parent(s): be0cc02

llm corrected

Browse files
app/ads1/ads_analyst.py CHANGED
@@ -145,54 +145,59 @@ def rule_based_insights(context: dict) -> str:
145
  bullets: list[str] = []
146
 
147
  if c["leads"] == 0:
148
- bullets.append("- No conversions recorded review targeting, landing page, and conversion tracking.")
149
  elif c["cpl"] > TARGET_CPL:
150
  bullets.append(
151
- f"- CPL is ${c['cpl']:.2f}, above the ${TARGET_CPL:.0f} target tighten bids on expensive terms."
152
  )
153
  else:
154
- bullets.append(f"- CPL is ${c['cpl']:.2f} with {c['leads']} leads performance is within a workable range.")
155
 
156
  if trend["spend_change_pct"] > 10 and trend["clicks_change_pct"] < trend["spend_change_pct"]:
157
- bullets.append("- Spend is rising faster than clicks efficiency is slipping; audit keyword bids.")
158
 
159
  if signals["wasted_spend_ratio"] > 0.2:
160
  bullets.append(
161
- f"- About {signals['wasted_spend_ratio'] * 100:.0f}% of keywords have zero conversions pause or cut budget there."
162
  )
163
 
164
  for row in drivers.get("best_keywords", [])[:1]:
165
  bullets.append(
166
- f"- '{row['keyword']}' is a top performer ({row['conversions']} conversions) consider scaling budget."
167
  )
168
 
169
  for row in drivers.get("worst_keywords", [])[:1]:
170
  if row.get("conversions", 0) == 0:
171
- bullets.append(f"- '{row['keyword']}' spent without converting reduce bids or pause.")
172
 
173
  if len(bullets) < 3:
174
- bullets.append(f"- CTR is {c['ctr']:.2f}% across {c['impressions']:,} impressions test stronger ad copy if CTR is low.")
175
 
176
  return "\n\n".join(bullets[:5])
177
 
178
 
179
  def run_ads_analyst_card(dfs: dict, campaign_name: str | None = None) -> str:
180
- print("\n🚀 [analyst_card] STARTED", flush=True)
181
 
182
  if not dfs:
183
- return "⚠️ No campaign data select a campaign on the Dashboard tab first."
184
 
185
  scoped = _dfs_for_campaign(dfs, campaign_name)
186
  context = build_ads_analyst_context(scoped, campaign_name)
187
- print("🧠 [analyst_card] context built", flush=True)
188
 
189
  prompt = build_ads_analyst_prompt(context)
190
- print("✍️ [analyst_card] prompt built", flush=True)
191
 
192
  result = generate_explanation(prompt)
 
 
 
 
 
193
  if is_bad_llm_output(result):
194
- print("⚠️ [analyst_card] LLM fallback using rule-based insights", flush=True)
195
  result = rule_based_insights(context)
196
 
197
- print("\n📤 [analyst_card] LLM result received", flush=True)
198
  return result
 
145
  bullets: list[str] = []
146
 
147
  if c["leads"] == 0:
148
+ bullets.append("- No conversions recorded - review targeting, landing page, and conversion tracking.")
149
  elif c["cpl"] > TARGET_CPL:
150
  bullets.append(
151
+ f"- CPL is ${c['cpl']:.2f}, above the ${TARGET_CPL:.0f} target - tighten bids on expensive terms."
152
  )
153
  else:
154
+ bullets.append(f"- CPL is ${c['cpl']:.2f} with {c['leads']} leads - performance is within a workable range.")
155
 
156
  if trend["spend_change_pct"] > 10 and trend["clicks_change_pct"] < trend["spend_change_pct"]:
157
+ bullets.append("- Spend is rising faster than clicks - efficiency is slipping; audit keyword bids.")
158
 
159
  if signals["wasted_spend_ratio"] > 0.2:
160
  bullets.append(
161
+ f"- About {signals['wasted_spend_ratio'] * 100:.0f}% of keywords have zero conversions - pause or cut budget there."
162
  )
163
 
164
  for row in drivers.get("best_keywords", [])[:1]:
165
  bullets.append(
166
+ f"- '{row['keyword']}' is a top performer ({row['conversions']} conversions) - consider scaling budget."
167
  )
168
 
169
  for row in drivers.get("worst_keywords", [])[:1]:
170
  if row.get("conversions", 0) == 0:
171
+ bullets.append(f"- '{row['keyword']}' spent without converting - reduce bids or pause.")
172
 
173
  if len(bullets) < 3:
174
+ bullets.append(f"- CTR is {c['ctr']:.2f}% across {c['impressions']:,} impressions - test stronger ad copy if CTR is low.")
175
 
176
  return "\n\n".join(bullets[:5])
177
 
178
 
179
  def run_ads_analyst_card(dfs: dict, campaign_name: str | None = None) -> str:
180
+ print("\nSTART [analyst_card] STARTED", flush=True)
181
 
182
  if not dfs:
183
+ return "WARNING: No campaign data - select a campaign on the Dashboard tab first."
184
 
185
  scoped = _dfs_for_campaign(dfs, campaign_name)
186
  context = build_ads_analyst_context(scoped, campaign_name)
187
+ print("CONTEXT [analyst_card] context built", flush=True)
188
 
189
  prompt = build_ads_analyst_prompt(context)
190
+ print("PROMPT [analyst_card] prompt built", flush=True)
191
 
192
  result = generate_explanation(prompt)
193
+ print(f"[analyst_card] raw/clean LLM result preview: {str(result)[:400]}", flush=True)
194
+ if "Analysis failed:" in str(result):
195
+ print("[analyst_card] LLM call failed; returning the backend error instead of hiding it.", flush=True)
196
+ return result
197
+
198
  if is_bad_llm_output(result):
199
+ print("WARNING [analyst_card] LLM fallback - using rule-based insights", flush=True)
200
  result = rule_based_insights(context)
201
 
202
+ print("\nRESULT [analyst_card] LLM result received", flush=True)
203
  return result
app/ads1/keyword_inspector.py CHANGED
@@ -22,6 +22,15 @@ def build_keyword_features(df: pd.DataFrame) -> pd.DataFrame:
22
  return df
23
 
24
 
 
 
 
 
 
 
 
 
 
25
  # -------------------------
26
  # Prompt (simplified + stronger reasoning)
27
  # -------------------------
@@ -29,14 +38,40 @@ def build_keyword_prompt(context: dict) -> str:
29
  payload = json.dumps(context, indent=2, default=str)
30
 
31
  return keyword_inspector_prompt(payload)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32
  # -------------------------
33
  # Main runner
34
  # -------------------------
35
  def run_keyword_inspector(dfs: dict, campaign_name: str | None = None) -> str:
36
- print("\n🚀 [keyword_inspector] STARTED", flush=True)
37
 
38
  if not dfs or "keywords" not in dfs:
39
- return "⚠️ No keyword data available."
40
 
41
  df = dfs["keywords"].copy()
42
  df = build_keyword_features(df)
@@ -45,24 +80,33 @@ def run_keyword_inspector(dfs: dict, campaign_name: str | None = None) -> str:
45
  if campaign_name and "campaign_name" in df.columns:
46
  df = df[df["campaign_name"] == campaign_name]
47
 
 
 
 
 
 
 
 
48
  context = {
49
  "campaign_name": campaign_name,
50
- "keywords": df.to_dict("records") # FULL DATA given to LLM
51
  }
52
 
53
- print("🧠 [keyword_inspector] context built", flush=True)
54
 
55
  prompt = build_keyword_prompt(context)
56
- print("✍️ [keyword_inspector] prompt built", flush=True)
57
 
58
  result = generate_explanation(prompt)
 
 
 
 
 
59
 
60
- if is_bad_llm_output(result):
61
- print("⚠️ [keyword_inspector] LLM fallback triggered", flush=True)
62
- return (
63
- "- Unable to generate LLM insights right now.\n"
64
- "- Check keyword data quality or retry."
65
- )
66
 
67
- print("📤 [keyword_inspector] result received", flush=True)
68
  return result
 
22
  return df
23
 
24
 
25
+ def prepare_keyword_context_df(df: pd.DataFrame, max_rows: int = 20) -> pd.DataFrame:
26
+ winners = df[df["conversions"] > 0].sort_values(["conversions", "cpa"], ascending=[False, True]).head(10)
27
+ waste = df[df["conversions"] == 0].sort_values("cost", ascending=False).head(10)
28
+ out = pd.concat([waste, winners], ignore_index=True)
29
+ if "keyword" in out.columns:
30
+ out = out.drop_duplicates(subset=["keyword"])
31
+ return out.head(max_rows)
32
+
33
+
34
  # -------------------------
35
  # Prompt (simplified + stronger reasoning)
36
  # -------------------------
 
38
  payload = json.dumps(context, indent=2, default=str)
39
 
40
  return keyword_inspector_prompt(payload)
41
+
42
+
43
+ def rule_based_keyword_actions(context: dict) -> str:
44
+ rows = context.get("keywords", [])
45
+ if not rows:
46
+ return "- No keyword action found because no usable keyword rows were available."
47
+
48
+ bullets = []
49
+ for row in rows[:5]:
50
+ keyword = row.get("keyword", "this keyword")
51
+ cost = row.get("cost", 0)
52
+ clicks = row.get("clicks", 0)
53
+ conversions = row.get("conversions", 0)
54
+ ctr = row.get("ctr", 0)
55
+ cpa = row.get("cpa", 0)
56
+ if conversions > 0:
57
+ bullets.append(
58
+ f"- Treat '{keyword}' as a winning or scaling keyword because it produced {conversions} conversions at CPA {cpa:.2f} and CTR {ctr:.2f}%."
59
+ )
60
+ else:
61
+ bullets.append(
62
+ f"- Reduce or pause '{keyword}' because it spent {cost:.2f} across {clicks} clicks with 0 conversions."
63
+ )
64
+ return "\n\n".join(bullets)
65
+
66
+
67
  # -------------------------
68
  # Main runner
69
  # -------------------------
70
  def run_keyword_inspector(dfs: dict, campaign_name: str | None = None) -> str:
71
+ print("\nSTART [keyword_inspector] STARTED", flush=True)
72
 
73
  if not dfs or "keywords" not in dfs:
74
+ return "WARNING: No keyword data available."
75
 
76
  df = dfs["keywords"].copy()
77
  df = build_keyword_features(df)
 
80
  if campaign_name and "campaign_name" in df.columns:
81
  df = df[df["campaign_name"] == campaign_name]
82
 
83
+ df = prepare_keyword_context_df(df)
84
+ keep_cols = [
85
+ col
86
+ for col in ["keyword", "cost", "clicks", "impressions", "conversions", "ctr", "cpa"]
87
+ if col in df.columns
88
+ ]
89
+
90
  context = {
91
  "campaign_name": campaign_name,
92
+ "keywords": df[keep_cols].round(2).to_dict("records")
93
  }
94
 
95
+ print("CONTEXT [keyword_inspector] context built", flush=True)
96
 
97
  prompt = build_keyword_prompt(context)
98
+ print("PROMPT [keyword_inspector] prompt built", flush=True)
99
 
100
  result = generate_explanation(prompt)
101
+ print(f"[keyword_inspector] raw/clean LLM result preview: {str(result)[:400]}", flush=True)
102
+ if "Analysis failed:" in str(result):
103
+ print("[keyword_inspector] LLM call failed; returning the backend error instead of hiding it.", flush=True)
104
+ return result
105
+
106
 
107
+ if is_bad_llm_output(result) or not result.strip().startswith("-"):
108
+ print("WARNING [keyword_inspector] LLM fallback triggered", flush=True)
109
+ return rule_based_keyword_actions(context)
 
 
 
110
 
111
+ print("RESULT [keyword_inspector] result received", flush=True)
112
  return result
app/recs/generate.py CHANGED
@@ -57,9 +57,11 @@ def _looks_like_garbage(text: str) -> bool:
57
 
58
 
59
  def is_fallback_output(text: str) -> bool:
 
60
  return (
61
  not text
62
- or text.startswith("⚠️")
 
63
  or text.startswith("This recommendation was generated")
64
  )
65
 
@@ -74,7 +76,7 @@ def sanitize_explanation(text: str, rec: Dict | None = None) -> str:
74
 
75
  bullets: list[str] = []
76
  for ln in lines:
77
- if re.match(r"^[-*]\s+\S", ln):
78
  bullets.append(ln)
79
  elif re.match(r"^\d+\.\s+\S", ln):
80
  bullets.append(re.sub(r"^\d+\.\s+", "- ", ln))
@@ -89,14 +91,13 @@ def sanitize_explanation(text: str, rec: Dict | None = None) -> str:
89
 
90
 
91
  def _messages_to_prompt(messages: list[dict[str, str]]) -> str:
92
- chunks: list[str] = []
93
- for msg in messages:
94
- role = msg["role"]
95
- content = msg["content"]
96
- chunks.append(f"<|im_start|>{role}\n{content}{_IM_END}\n")
97
- chunks.append("<|im_start|>assistant\n")
98
- return "".join(chunks)
99
-
100
 
101
  def _message_text(message: dict) -> str:
102
  content = (message.get("content") or "").strip()
@@ -109,21 +110,24 @@ def _message_text(message: dict) -> str:
109
  def _infer(llm, messages: list[dict[str, str]]) -> str:
110
  max_tokens = int(os.getenv("LLAMA_MAX_TOKENS", "384"))
111
  temperature = float(os.getenv("LLAMA_TEMPERATURE", "0.35"))
112
-
113
- try:
114
- out = llm.create_chat_completion(
115
- messages=messages,
116
- max_tokens=max_tokens,
117
- temperature=temperature,
118
- )
119
- raw = _message_text(out["choices"][0]["message"])
120
- if raw and not _looks_like_garbage(raw):
121
- print("[generate_explanation] via create_chat_completion", flush=True)
122
- return raw
123
- print("⚠️ [generate_explanation] chat_completion empty/garbage — raw fallback", flush=True)
124
- except TypeError as exc:
125
- print(f"⚠️ [generate_explanation] chat_completion failed: {exc}", flush=True)
126
-
 
 
 
127
  out = llm(
128
  _messages_to_prompt(messages),
129
  max_tokens=max_tokens,
@@ -145,12 +149,12 @@ def _coerce_prompt(prompt: str | Dict, rec: Dict | None) -> tuple[str, Dict | No
145
 
146
 
147
  def generate_explanation(prompt: str | Dict, rec: Dict | None = None, stream: bool = False):
148
- print("\n🔥 [generate_explanation] CALLED", flush=True)
149
 
150
  try:
151
  user_content, rec = _coerce_prompt(prompt, rec)
152
  print(
153
- f"🧾 [generate_explanation] prompt type={type(prompt).__name__} "
154
  f"len={len(user_content)}",
155
  flush=True,
156
  )
@@ -164,27 +168,28 @@ def generate_explanation(prompt: str | Dict, rec: Dict | None = None, stream: bo
164
 
165
  with _infer_lock:
166
  llm = load_model()
167
- print("🧠 [generate_explanation] model loaded", flush=True)
168
- print("🚀 [generate_explanation] calling LLM...", flush=True)
169
  raw = _infer(llm, messages)
170
 
171
- print("📡 [generate_explanation] response received", flush=True)
172
- print("📄 [generate_explanation] raw output length:", len(raw), flush=True)
173
  if raw:
174
- print("📄 [generate_explanation] raw preview:", raw[:400], flush=True)
175
 
176
  clean = sanitize_explanation(raw, rec)
177
  if is_bad_llm_output(clean):
178
  clean = fallback_explanation(rec)
179
- print(" [generate_explanation] cleaned output ready", flush=True)
180
  if stream:
181
  return iter([clean])
182
  return clean
183
 
184
  except Exception as e:
185
- print(" [generate_explanation] ERROR:", repr(e), flush=True)
186
  traceback.print_exc()
187
- err = f"⚠️ Analysis failed: {e}"
188
  if stream:
189
  return iter([err])
190
  return err
 
 
57
 
58
 
59
  def is_fallback_output(text: str) -> bool:
60
+ lower = (text or "").lower()
61
  return (
62
  not text
63
+ or lower.startswith("warning:")
64
+ or "analysis failed:" in lower
65
  or text.startswith("This recommendation was generated")
66
  )
67
 
 
76
 
77
  bullets: list[str] = []
78
  for ln in lines:
79
+ if re.match(r"^[-*]\s+\S", ln):
80
  bullets.append(ln)
81
  elif re.match(r"^\d+\.\s+\S", ln):
82
  bullets.append(re.sub(r"^\d+\.\s+", "- ", ln))
 
91
 
92
 
93
  def _messages_to_prompt(messages: list[dict[str, str]]) -> str:
94
+ system = next((msg["content"] for msg in messages if msg["role"] == "system"), "")
95
+ user = next((msg["content"] for msg in messages if msg["role"] == "user"), "")
96
+ return (
97
+ f"{system}\n\n"
98
+ f"Request:\n{user}\n\n"
99
+ "Answer with only the final bullet points:\n"
100
+ )
 
101
 
102
  def _message_text(message: dict) -> str:
103
  content = (message.get("content") or "").strip()
 
110
  def _infer(llm, messages: list[dict[str, str]]) -> str:
111
  max_tokens = int(os.getenv("LLAMA_MAX_TOKENS", "384"))
112
  temperature = float(os.getenv("LLAMA_TEMPERATURE", "0.35"))
113
+ use_chat_completion = os.getenv("LLAMA_USE_CHAT_COMPLETION", "0") == "1"
114
+
115
+ if use_chat_completion:
116
+ try:
117
+ out = llm.create_chat_completion(
118
+ messages=messages,
119
+ max_tokens=max_tokens,
120
+ temperature=temperature,
121
+ )
122
+ raw = _message_text(out["choices"][0]["message"])
123
+ if raw and not _looks_like_garbage(raw):
124
+ print("OK [generate_explanation] via create_chat_completion", flush=True)
125
+ return raw
126
+ print("WARNING [generate_explanation] chat_completion empty/garbage - raw fallback", flush=True)
127
+ except Exception as exc:
128
+ print(f"WARNING [generate_explanation] chat_completion failed - raw fallback: {repr(exc)}", flush=True)
129
+
130
+ print("LLM [generate_explanation] using raw llama prompt", flush=True)
131
  out = llm(
132
  _messages_to_prompt(messages),
133
  max_tokens=max_tokens,
 
149
 
150
 
151
  def generate_explanation(prompt: str | Dict, rec: Dict | None = None, stream: bool = False):
152
+ print("\nLLM [generate_explanation] CALLED", flush=True)
153
 
154
  try:
155
  user_content, rec = _coerce_prompt(prompt, rec)
156
  print(
157
+ f"PROMPT [generate_explanation] prompt type={type(prompt).__name__} "
158
  f"len={len(user_content)}",
159
  flush=True,
160
  )
 
168
 
169
  with _infer_lock:
170
  llm = load_model()
171
+ print("MODEL [generate_explanation] model loaded", flush=True)
172
+ print("LLM [generate_explanation] calling LLM...", flush=True)
173
  raw = _infer(llm, messages)
174
 
175
+ print("LLM [generate_explanation] response received", flush=True)
176
+ print("RAW [generate_explanation] raw output length:", len(raw), flush=True)
177
  if raw:
178
+ print("RAW [generate_explanation] raw preview:", raw[:400], flush=True)
179
 
180
  clean = sanitize_explanation(raw, rec)
181
  if is_bad_llm_output(clean):
182
  clean = fallback_explanation(rec)
183
+ print("OK [generate_explanation] cleaned output ready", flush=True)
184
  if stream:
185
  return iter([clean])
186
  return clean
187
 
188
  except Exception as e:
189
+ print("ERROR [generate_explanation] ERROR:", repr(e), flush=True)
190
  traceback.print_exc()
191
+ err = f"WARNING: Analysis failed: {e}"
192
  if stream:
193
  return iter([err])
194
  return err
195
+