ps1811 commited on
Commit
f689170
Β·
1 Parent(s): bbeab8f

Cleaned files

Browse files
Files changed (4) hide show
  1. app.py +33 -32
  2. app/ads1/ads_analyst.py +7 -23
  3. app/models/llm.py +57 -20
  4. app/recs/generate.py +67 -51
app.py CHANGED
@@ -1,5 +1,6 @@
1
  import gradio as gr
2
  import os
 
3
  print("APP STARTED", flush=True)
4
 
5
  from app.db.repo import init_db
@@ -9,9 +10,11 @@ from app.controller.session_loader import load_google_ads_data
9
  from app.ads1.ads_analyst import run_ads_analyst_card
10
  from app.ads1.budget_optimizer import run_budget_optimizer_card
11
  import spaces
 
12
  print("πŸ”₯ STEP 1: imports done", flush=True)
13
 
14
- # @spaces.GPU(duration=0)
 
15
  def run_ads_card(state):
16
  print("\nπŸ”₯ [run_ads_card] ENTERED", flush=True)
17
  try:
@@ -23,6 +26,9 @@ def run_ads_card(state):
23
  dfs = state.get("full_dfs")
24
  print("πŸ“Š [run_ads_card] extracted dfs:", type(dfs), flush=True)
25
 
 
 
 
26
  result = run_ads_analyst_card(dfs)
27
  print("βœ… [run_ads_card] returning result", flush=True)
28
  return result
@@ -30,19 +36,26 @@ def run_ads_card(state):
30
  print("❌ [run_ads_card] ERROR:", repr(e), flush=True)
31
  return f"⚠️ Analysis failed: {e}"
32
 
33
- # def run_ads_card(state):
34
- # print("πŸ”₯ FUNCTION CALLED", flush=True)
35
- # return "test"
36
-
37
- # def run_ads_card(state):
38
- # if not state:
39
- # return "⚠️ Select a campaign from the Dashboard tab first."
40
- # return run_ads_analyst_card(state["full_dfs"])
41
 
 
42
  def run_budget_card(state):
43
- if not state:
44
- return "⚠️ Select a campaign from the Dashboard tab first."
45
- return run_budget_optimizer_card(state["full_dfs"])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
46
 
47
  def startup():
48
  try:
@@ -53,6 +66,7 @@ def startup():
53
  print("⚠️ DB failed:", e, flush=True)
54
  return "error"
55
 
 
56
  print("πŸ”₯ STEP 2: DB init done", flush=True)
57
 
58
  # UI Optimization: Fetch data AFTER UI elements are drawn
@@ -63,13 +77,6 @@ def initial_data_load():
63
  spend, leads, cpl, count, formatted_df = get_dashboard_data()
64
  return dfs, formatted_df, spend, leads, cpl, count
65
 
66
- # def campaign_row_selected(evt: gr.SelectData, df, full_state):
67
- # if df.empty or full_state is None:
68
- # return gr.State(), "⚠️ Data state is missing. Please click Refresh."
69
- # row_index = evt.index[0]
70
- # campaign_name = df.iloc[row_index]["Campaign"]
71
- # campaign_state = on_campaign_select(full_state, campaign_name)
72
- # return campaign_state, f"## πŸ“Š Selected Campaign: {campaign_name}"
73
 
74
  def campaign_row_selected(df, full_state, evt: gr.SelectData):
75
  row_index = evt.index[0]
@@ -77,6 +84,7 @@ def campaign_row_selected(df, full_state, evt: gr.SelectData):
77
  campaign_state = on_campaign_select(full_state, campaign_name)
78
  return campaign_state, f"## πŸ“Š Selected Campaign: {campaign_name}"
79
 
 
80
  print("πŸ”₯ STEP 3: building UI", flush=True)
81
 
82
  with gr.Blocks() as demo:
@@ -102,38 +110,31 @@ with gr.Blocks() as demo:
102
  with gr.Tab("Analysis"):
103
  selected = gr.Markdown("πŸ‘ˆ Select a campaign from the Dashboard tab")
104
  output = gr.Markdown()
105
-
106
  with gr.Row():
107
  gr.Button("πŸš€ Run Ads Analysis").click(run_ads_card, campaign_state, output)
108
  gr.Button("πŸ’° Run Budget Optimization").click(run_budget_card, campaign_state, output)
109
 
110
- # Core Event Bindings
111
- # campaign_table.change(fn=lambda x: x, inputs=[campaign_table], outputs=df_state)
112
- # campaign_table.select(
113
- # fn=campaign_row_selected,
114
- # inputs=[df_state, full_state],
115
- # outputs=[campaign_state, selected]
116
- # )
117
-
118
  campaign_table.select(
119
  fn=campaign_row_selected,
120
  inputs=[campaign_table, full_state],
121
- outputs=[campaign_state, selected]
122
  )
123
 
124
  # Button manual refresh
125
  refresh_btn.click(
126
  fn=initial_data_load,
127
- outputs=[full_state, campaign_table, total_spend, total_leads, average_cpl, active_campaigns]
128
  )
129
 
130
  # ⚑ MAGIC FIX: App automatically loads data into UI components instantly on launch
131
  demo.load(
132
  fn=initial_data_load,
133
- outputs=[full_state, campaign_table, total_spend, total_leads, average_cpl, active_campaigns]
134
  )
135
  demo.load(fn=startup, outputs=[])
136
 
137
  demo.queue()
 
138
  if __name__ == "__main__":
139
- demo.launch()
 
1
  import gradio as gr
2
  import os
3
+
4
  print("APP STARTED", flush=True)
5
 
6
  from app.db.repo import init_db
 
10
  from app.ads1.ads_analyst import run_ads_analyst_card
11
  from app.ads1.budget_optimizer import run_budget_optimizer_card
12
  import spaces
13
+
14
  print("πŸ”₯ STEP 1: imports done", flush=True)
15
 
16
+
17
+ @spaces.GPU(duration=120)
18
  def run_ads_card(state):
19
  print("\nπŸ”₯ [run_ads_card] ENTERED", flush=True)
20
  try:
 
26
  dfs = state.get("full_dfs")
27
  print("πŸ“Š [run_ads_card] extracted dfs:", type(dfs), flush=True)
28
 
29
+ if not dfs:
30
+ return "⚠️ No campaign data β€” select a campaign on the Dashboard tab first."
31
+
32
  result = run_ads_analyst_card(dfs)
33
  print("βœ… [run_ads_card] returning result", flush=True)
34
  return result
 
36
  print("❌ [run_ads_card] ERROR:", repr(e), flush=True)
37
  return f"⚠️ Analysis failed: {e}"
38
 
 
 
 
 
 
 
 
 
39
 
40
+ @spaces.GPU(duration=120)
41
  def run_budget_card(state):
42
+ print("\nπŸ”₯ [run_budget_card] ENTERED", flush=True)
43
+ try:
44
+ if not state:
45
+ print("❌ [run_budget_card] state is EMPTY", flush=True)
46
+ return "⚠️ Select a campaign first"
47
+
48
+ dfs = state.get("full_dfs")
49
+ if not dfs:
50
+ return "⚠️ No campaign data β€” select a campaign on the Dashboard tab first."
51
+
52
+ result = run_budget_optimizer_card(dfs)
53
+ print("βœ… [run_budget_card] returning result", flush=True)
54
+ return result
55
+ except Exception as e:
56
+ print("❌ [run_budget_card] ERROR:", repr(e), flush=True)
57
+ return f"⚠️ Budget optimization failed: {e}"
58
+
59
 
60
  def startup():
61
  try:
 
66
  print("⚠️ DB failed:", e, flush=True)
67
  return "error"
68
 
69
+
70
  print("πŸ”₯ STEP 2: DB init done", flush=True)
71
 
72
  # UI Optimization: Fetch data AFTER UI elements are drawn
 
77
  spend, leads, cpl, count, formatted_df = get_dashboard_data()
78
  return dfs, formatted_df, spend, leads, cpl, count
79
 
 
 
 
 
 
 
 
80
 
81
  def campaign_row_selected(df, full_state, evt: gr.SelectData):
82
  row_index = evt.index[0]
 
84
  campaign_state = on_campaign_select(full_state, campaign_name)
85
  return campaign_state, f"## πŸ“Š Selected Campaign: {campaign_name}"
86
 
87
+
88
  print("πŸ”₯ STEP 3: building UI", flush=True)
89
 
90
  with gr.Blocks() as demo:
 
110
  with gr.Tab("Analysis"):
111
  selected = gr.Markdown("πŸ‘ˆ Select a campaign from the Dashboard tab")
112
  output = gr.Markdown()
113
+
114
  with gr.Row():
115
  gr.Button("πŸš€ Run Ads Analysis").click(run_ads_card, campaign_state, output)
116
  gr.Button("πŸ’° Run Budget Optimization").click(run_budget_card, campaign_state, output)
117
 
 
 
 
 
 
 
 
 
118
  campaign_table.select(
119
  fn=campaign_row_selected,
120
  inputs=[campaign_table, full_state],
121
+ outputs=[campaign_state, selected],
122
  )
123
 
124
  # Button manual refresh
125
  refresh_btn.click(
126
  fn=initial_data_load,
127
+ outputs=[full_state, campaign_table, total_spend, total_leads, average_cpl, active_campaigns],
128
  )
129
 
130
  # ⚑ MAGIC FIX: App automatically loads data into UI components instantly on launch
131
  demo.load(
132
  fn=initial_data_load,
133
+ outputs=[full_state, campaign_table, total_spend, total_leads, average_cpl, active_campaigns],
134
  )
135
  demo.load(fn=startup, outputs=[])
136
 
137
  demo.queue()
138
+
139
  if __name__ == "__main__":
140
+ demo.launch()
app/ads1/ads_analyst.py CHANGED
@@ -9,6 +9,7 @@ TARGET_CPL = 20.0
9
  # 1. DATA BUILDERS
10
  # -------------------------
11
 
 
12
  def build_campaign_snapshot(dfs: dict) -> dict:
13
  df = dfs["campaigns"]
14
 
@@ -86,6 +87,7 @@ def build_signals(dfs: dict) -> dict:
86
  # 2. CONTEXT BUILDER
87
  # -------------------------
88
 
 
89
  def build_ads_analyst_context(dfs: dict) -> dict:
90
  return {
91
  "campaign": build_campaign_snapshot(dfs),
@@ -99,6 +101,7 @@ def build_ads_analyst_context(dfs: dict) -> dict:
99
  # 3. PROMPT BUILDER
100
  # -------------------------
101
 
 
102
  def build_ads_analyst_prompt(context: dict) -> str:
103
  return f"""
104
  You are an Ads performance analyst.
@@ -125,38 +128,19 @@ Format:
125
  """
126
 
127
 
128
- # -------------------------
129
- # 4. MAIN ORCHESTRATOR (THIS IS WHAT MAIN.PY CALLS)
130
- # -------------------------
131
-
132
- # def run_ads_analyst_card(dfs: dict) -> str:
133
- # context = build_ads_analyst_context(dfs)
134
- # prompt = build_ads_analyst_prompt(context)
135
-
136
- # print("\n========== PROMPT ==========")
137
- # print(prompt)
138
-
139
- # result = generate_explanation(prompt)
140
-
141
- # print("\n========== LLM OUTPUT ==========")
142
- # print(result)
143
-
144
- # return result
145
-
146
  def run_ads_analyst_card(dfs: dict) -> str:
147
  print("\nπŸš€ [analyst_card] STARTED", flush=True)
148
 
 
 
 
149
  context = build_ads_analyst_context(dfs)
150
  print("🧠 [analyst_card] context built", flush=True)
151
 
152
  prompt = build_ads_analyst_prompt(context)
153
  print("✍️ [analyst_card] prompt built", flush=True)
154
 
155
- print("\n========== PROMPT ==========")
156
- print(prompt)
157
-
158
  result = generate_explanation(prompt)
159
-
160
  print("\nπŸ“€ [analyst_card] LLM result received", flush=True)
161
 
162
- return result
 
9
  # 1. DATA BUILDERS
10
  # -------------------------
11
 
12
+
13
  def build_campaign_snapshot(dfs: dict) -> dict:
14
  df = dfs["campaigns"]
15
 
 
87
  # 2. CONTEXT BUILDER
88
  # -------------------------
89
 
90
+
91
  def build_ads_analyst_context(dfs: dict) -> dict:
92
  return {
93
  "campaign": build_campaign_snapshot(dfs),
 
101
  # 3. PROMPT BUILDER
102
  # -------------------------
103
 
104
+
105
  def build_ads_analyst_prompt(context: dict) -> str:
106
  return f"""
107
  You are an Ads performance analyst.
 
128
  """
129
 
130
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
131
  def run_ads_analyst_card(dfs: dict) -> str:
132
  print("\nπŸš€ [analyst_card] STARTED", flush=True)
133
 
134
+ if not dfs:
135
+ return "⚠️ No campaign data β€” select a campaign on the Dashboard tab first."
136
+
137
  context = build_ads_analyst_context(dfs)
138
  print("🧠 [analyst_card] context built", flush=True)
139
 
140
  prompt = build_ads_analyst_prompt(context)
141
  print("✍️ [analyst_card] prompt built", flush=True)
142
 
 
 
 
143
  result = generate_explanation(prompt)
 
144
  print("\nπŸ“€ [analyst_card] LLM result received", flush=True)
145
 
146
+ return result
app/models/llm.py CHANGED
@@ -1,35 +1,72 @@
 
 
1
  import os
 
 
2
  from huggingface_hub import hf_hub_download
3
  from llama_cpp import Llama
4
 
5
- _model = None
 
6
 
7
- def load_model():
8
- print("🧠 [load_model] called", flush=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
  global _model
 
10
 
11
  if _model is not None:
12
  print("🧠 [load_model] returning cached model", flush=True)
13
  return _model
14
 
15
- print("⬇️ [load_model] downloading model...", flush=True)
16
- model_path = hf_hub_download(
17
- repo_id="Abiray/MiniCPM5-1B-GGUF",
18
- filename="minicpm5-1b-Q4_K_M.gguf",
19
- )
20
-
21
- print(f"βœ… [load_model] model downloaded at {model_path}", flush=True)
22
 
23
- gpu_layers = int(os.getenv("LLAMA_GPU_LAYERS", "0"))
24
- print(f"πŸš€ [load_model] initializing Llama with n_gpu_layers={gpu_layers}", flush=True)
 
 
 
 
25
 
26
- _model = Llama(
27
- model_path=model_path,
28
- n_ctx=4096,
29
- n_gpu_layers=gpu_layers,
30
- verbose=True,
31
- )
 
 
 
32
 
33
- print("βœ… [load_model] model initialized", flush=True)
 
 
 
 
 
 
 
34
 
35
- return _model
 
1
+ from __future__ import annotations
2
+
3
  import os
4
+ import threading
5
+
6
  from huggingface_hub import hf_hub_download
7
  from llama_cpp import Llama
8
 
9
+ HF_REPO = "Abiray/MiniCPM5-1B-GGUF"
10
+ HF_FILENAME = "minicpm5-1b-Q4_K_M.gguf"
11
 
12
+ _model: Llama | None = None
13
+ _init_lock = threading.Lock()
14
+
15
+
16
+ def _preload_cuda_libs() -> None:
17
+ try:
18
+ import ctypes
19
+
20
+ import nvidia.cublas
21
+ import nvidia.cuda_runtime
22
+ except ImportError:
23
+ return
24
+
25
+ for module, lib_name in (
26
+ (nvidia.cublas, "libcublas.so.12"),
27
+ (nvidia.cuda_runtime, "libcudart.so.12"),
28
+ ):
29
+ lib_path = os.path.join(module.__path__[0], "lib", lib_name)
30
+ if os.path.isfile(lib_path):
31
+ ctypes.CDLL(lib_path, mode=ctypes.RTLD_GLOBAL)
32
+
33
+
34
+ def load_model() -> Llama:
35
  global _model
36
+ print("🧠 [load_model] called", flush=True)
37
 
38
  if _model is not None:
39
  print("🧠 [load_model] returning cached model", flush=True)
40
  return _model
41
 
42
+ with _init_lock:
43
+ if _model is not None:
44
+ return _model
 
 
 
 
45
 
46
+ print("⬇️ [load_model] downloading model...", flush=True)
47
+ model_path = hf_hub_download(
48
+ repo_id=HF_REPO,
49
+ filename=HF_FILENAME,
50
+ )
51
+ print(f"βœ… [load_model] model downloaded at {model_path}", flush=True)
52
 
53
+ _preload_cuda_libs()
54
+ gpu_layers = int(os.getenv("LLAMA_GPU_LAYERS", "-1"))
55
+ n_ctx = int(os.getenv("LLAMA_N_CTX", "2048"))
56
+ n_threads = int(os.getenv("LLAMA_N_THREADS", "4"))
57
+ print(
58
+ f"πŸš€ [load_model] initializing Llama "
59
+ f"(n_gpu_layers={gpu_layers}, n_ctx={n_ctx}, n_threads={n_threads})",
60
+ flush=True,
61
+ )
62
 
63
+ _model = Llama(
64
+ model_path=model_path,
65
+ n_ctx=n_ctx,
66
+ n_gpu_layers=gpu_layers,
67
+ n_threads=n_threads,
68
+ verbose=False,
69
+ )
70
+ print("βœ… [load_model] model initialized", flush=True)
71
 
72
+ return _model
app/recs/generate.py CHANGED
@@ -1,84 +1,100 @@
1
- from typing import Dict, Iterator
 
 
2
  import re
3
- import spaces
 
 
4
  from app.models.llm import load_model
5
 
6
  TARGET_CPL = 20.0
7
 
 
 
 
8
 
9
- def fallback_explanation(rec: Dict = None) -> str:
10
  return "This recommendation was generated from campaign performance metrics."
11
 
12
 
13
- def sanitize_explanation(text: str, rec: Dict = None) -> str:
14
  cleaned = re.sub(r"\s+", " ", text).strip()
15
-
16
  if not cleaned or len(cleaned) < 10:
17
  return fallback_explanation(rec)
18
-
19
  return cleaned
20
 
21
- @spaces.GPU(duration=120)
22
- def generate_explanation(prompt: str, rec: Dict = None, stream: bool = False):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
  print("\nπŸ”₯ [generate_explanation] CALLED", flush=True)
24
 
25
  try:
26
- print(f"🧾 [generate_explanation] prompt type={type(prompt).__name__} len={len(str(prompt))}", flush=True)
 
 
 
 
27
 
28
  llm = load_model()
29
  print("🧠 [generate_explanation] model loaded", flush=True)
30
 
31
- print("πŸš€ [generate_explanation] calling LLM...", flush=True)
 
 
 
 
 
 
 
 
 
 
32
 
33
- response = llm.create_chat_completion(
34
- messages=[
35
- {
36
- "role": "system",
37
- "content": "You are an expert marketing analyst. Output only final answer."
38
- },
39
- {"role": "user", "content": prompt}
40
- ],
41
  temperature=0.7,
 
 
42
  )
43
-
44
  print("πŸ“‘ [generate_explanation] response received", flush=True)
45
-
46
- raw = response["choices"][0]["message"]["content"]
47
-
48
  print("πŸ“„ [generate_explanation] raw output length:", len(raw), flush=True)
49
 
50
- clean = re.sub(r"<think>.*?</think>", "", raw, flags=re.DOTALL)
 
 
 
 
 
 
 
 
 
 
 
 
 
51
 
52
  print("✨ [generate_explanation] cleaned output ready", flush=True)
53
-
54
  return clean
55
 
56
  except Exception as e:
57
- print("❌ [generate_explanation] ERROR:", e, flush=True)
 
58
  return fallback_explanation(rec)
59
-
60
- # def generate_explanation(prompt: str, rec: Dict = None, stream: bool = False):
61
- # print("πŸ”₯ LLM CALLED")
62
-
63
- # llm = load_model()
64
-
65
- # try:
66
-
67
- # response = llm.create_chat_completion(
68
- # messages=[
69
- # {"role": "system", "content": "You are an expert marketing analyst for Google Ads.You MUST NOT output reasoning, thinking, or tags like <think>.You MUST ONLY output final answer."},
70
- # {"role": "user", "content": prompt}
71
- # ],
72
- # temperature=0.7,)
73
- # raw = response["choices"][0]["message"]["content"]
74
-
75
- # clean = re.sub(r"<think>.*?</think>", "", raw, flags=re.DOTALL)
76
- # clean = re.sub(r"(?s).*?Reasoning:.*?\n", "", clean)
77
- # clean = re.sub(r"(?s).*?Step \d+.*?\n", "", clean)
78
-
79
- # print(clean)
80
- # return clean
81
-
82
- # except Exception as e:
83
- # print("❌ LLM ERROR:", e)
84
- # return fallback_explanation(rec)
 
1
+ from __future__ import annotations
2
+
3
+ import os
4
  import re
5
+ import traceback
6
+ from typing import Dict
7
+
8
  from app.models.llm import load_model
9
 
10
  TARGET_CPL = 20.0
11
 
12
+ _IM_END = "<|im_end|>"
13
+ _STOP_SEQUENCES = [_IM_END, "<|im_start|>", "</s>"]
14
+
15
 
16
+ def fallback_explanation(rec: Dict | None = None) -> str:
17
  return "This recommendation was generated from campaign performance metrics."
18
 
19
 
20
+ def sanitize_explanation(text: str, rec: Dict | None = None) -> str:
21
  cleaned = re.sub(r"\s+", " ", text).strip()
 
22
  if not cleaned or len(cleaned) < 10:
23
  return fallback_explanation(rec)
 
24
  return cleaned
25
 
26
+
27
+ def _messages_to_prompt(messages: list[dict[str, str]]) -> str:
28
+ chunks: list[str] = []
29
+ for msg in messages:
30
+ role = msg["role"]
31
+ content = msg["content"]
32
+ if role == "system":
33
+ chunks.append(f"<|im_start|>system\n{content}\n")
34
+ elif role == "user":
35
+ chunks.append(f"<|im_start|>user\n{content}\n")
36
+ elif role == "assistant":
37
+ chunks.append(f"<|im_start|>assistant\n{content}\n")
38
+ chunks.append("<|im_start|>assistant\n")
39
+ return "".join(chunks)
40
+
41
+
42
+ def generate_explanation(prompt: str, rec: Dict | None = None, stream: bool = False) -> str:
43
  print("\nπŸ”₯ [generate_explanation] CALLED", flush=True)
44
 
45
  try:
46
+ print(
47
+ f"🧾 [generate_explanation] prompt type={type(prompt).__name__} "
48
+ f"len={len(str(prompt))}",
49
+ flush=True,
50
+ )
51
 
52
  llm = load_model()
53
  print("🧠 [generate_explanation] model loaded", flush=True)
54
 
55
+ user_content = str(prompt).rstrip()
56
+ if "/no_think" not in user_content:
57
+ user_content = f"{user_content} /no_think"
58
+
59
+ messages = [
60
+ {
61
+ "role": "system",
62
+ "content": "You are an expert marketing analyst. Output only the final answer.",
63
+ },
64
+ {"role": "user", "content": user_content},
65
+ ]
66
 
67
+ print("πŸš€ [generate_explanation] calling LLM...", flush=True)
68
+ out = llm(
69
+ _messages_to_prompt(messages),
70
+ max_tokens=int(os.getenv("LLAMA_MAX_TOKENS", "512")),
 
 
 
 
71
  temperature=0.7,
72
+ stop=_STOP_SEQUENCES,
73
+ echo=False,
74
  )
75
+ raw = (out["choices"][0].get("text") or "").strip()
76
  print("πŸ“‘ [generate_explanation] response received", flush=True)
 
 
 
77
  print("πŸ“„ [generate_explanation] raw output length:", len(raw), flush=True)
78
 
79
+ clean = re.sub(
80
+ r"<\s*think\s*>.*?<\s*/\s*think\s*>",
81
+ "",
82
+ raw,
83
+ flags=re.DOTALL | re.IGNORECASE,
84
+ )
85
+ clean = re.sub(
86
+ r"<think>.*?</think>",
87
+ "",
88
+ clean,
89
+ flags=re.DOTALL | re.IGNORECASE,
90
+ )
91
+ clean = re.sub(r"\s+", " ", clean).strip()
92
+ clean = sanitize_explanation(clean, rec)
93
 
94
  print("✨ [generate_explanation] cleaned output ready", flush=True)
 
95
  return clean
96
 
97
  except Exception as e:
98
+ print("❌ [generate_explanation] ERROR:", repr(e), flush=True)
99
+ traceback.print_exc()
100
  return fallback_explanation(rec)