ps1811 commited on
Commit
cc4d7d1
·
1 Parent(s): 5d2d2d7

app.py & generate.py simplified

Browse files
Files changed (2) hide show
  1. app/models/llm.py +7 -99
  2. app/recs/generate.py +11 -98
app/models/llm.py CHANGED
@@ -13,118 +13,26 @@ _model: Any = None
13
  _init_lock = threading.Lock()
14
 
15
 
16
- def _validate_gguf_file(model_path: str) -> None:
17
- if not os.path.isfile(model_path):
18
- raise FileNotFoundError(f"Downloaded model file does not exist: {model_path}")
19
-
20
- size_bytes = os.path.getsize(model_path)
21
- with open(model_path, "rb") as fh:
22
- magic = fh.read(4)
23
- fh.seek(0)
24
- first_128 = fh.read(128)
25
-
26
- print(
27
- f"[load_model] GGUF file check: size={size_bytes / (1024 ** 2):.1f} MB, "
28
- f"magic={magic!r}",
29
- flush=True,
30
- )
31
-
32
- if magic != b"GGUF":
33
- preview = first_128.decode("utf-8", errors="replace")
34
- raise RuntimeError(
35
- "Downloaded file is not a valid GGUF file. "
36
- f"Expected magic b'GGUF', got {magic!r}. First bytes: {preview!r}"
37
- )
38
-
39
- if size_bytes < 100 * 1024 * 1024:
40
- raise RuntimeError(
41
- "Downloaded GGUF file is unexpectedly small. "
42
- f"Size was {size_bytes} bytes; this often means a bad upload or LFS pointer."
43
- )
44
-
45
-
46
- def _preload_cuda_libs() -> None:
47
- """Expose pip-installed CUDA runtime to llama.cpp on ZeroGPU (no system libcudart)."""
48
- try:
49
- import ctypes
50
-
51
- import nvidia.cublas
52
- import nvidia.cuda_runtime
53
- except ImportError:
54
- return
55
-
56
- lib_dirs: list[str] = []
57
- for module, lib_name in (
58
- (nvidia.cublas, "libcublas.so.12"),
59
- (nvidia.cuda_runtime, "libcudart.so.12"),
60
- ):
61
- lib_dir = os.path.join(module.__path__[0], "lib")
62
- lib_path = os.path.join(lib_dir, lib_name)
63
- if os.path.isfile(lib_path):
64
- ctypes.CDLL(lib_path, mode=ctypes.RTLD_GLOBAL)
65
- lib_dirs.append(lib_dir)
66
-
67
- if lib_dirs:
68
- existing = os.environ.get("LD_LIBRARY_PATH", "")
69
- merged = lib_dirs + ([existing] if existing else [])
70
- os.environ["LD_LIBRARY_PATH"] = ":".join(merged)
71
-
72
-
73
  def load_model() -> Any:
74
  global _model
75
- print("[load_model] called", flush=True)
76
 
77
  if _model is not None:
78
- print("[load_model] returning cached model", flush=True)
79
  return _model
80
 
81
  with _init_lock:
82
  if _model is not None:
83
  return _model
84
 
85
- force_download = os.getenv("LLAMA_FORCE_DOWNLOAD", "0") == "1"
86
- print(
87
- f"[load_model] downloading/resolving model {HF_REPO}/{HF_FILENAME} "
88
- f"(force_download={force_download})...",
89
- flush=True,
90
- )
91
- model_path = hf_hub_download(
92
- repo_id=HF_REPO,
93
- filename=HF_FILENAME,
94
- force_download=force_download,
95
- )
96
- print(f"[load_model] model resolved at {model_path}", flush=True)
97
- _validate_gguf_file(model_path)
98
 
99
- _preload_cuda_libs()
100
  from llama_cpp import Llama
101
 
102
- gpu_layers = int(os.getenv("LLAMA_GPU_LAYERS", "-1"))
103
- n_ctx = int(os.getenv("LLAMA_N_CTX", "2048"))
104
- n_threads = int(os.getenv("LLAMA_N_THREADS", "4"))
105
- verbose = os.getenv("LLAMA_VERBOSE", "0") == "1"
106
- print(
107
- f"[load_model] initializing Llama "
108
- f"(n_gpu_layers={gpu_layers}, n_ctx={n_ctx}, n_threads={n_threads}, verbose={verbose})",
109
- flush=True,
110
  )
111
 
112
- try:
113
- _model = Llama(
114
- model_path=model_path,
115
- n_ctx=n_ctx,
116
- n_gpu_layers=gpu_layers,
117
- n_threads=n_threads,
118
- verbose=verbose,
119
- )
120
- except Exception as exc:
121
- raise RuntimeError(
122
- f"llama.cpp failed to load a valid GGUF file from {model_path}. "
123
- "If the file check above says magic=b'GGUF' and the size is large, "
124
- "this is usually a llama-cpp-python/GGUF compatibility issue or a bad quantized export. "
125
- "Try setting LLAMA_VERBOSE=1 for the next run."
126
- ) from exc
127
-
128
- print("[load_model] model initialized", flush=True)
129
-
130
  return _model
 
13
  _init_lock = threading.Lock()
14
 
15
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
  def load_model() -> Any:
17
  global _model
 
18
 
19
  if _model is not None:
 
20
  return _model
21
 
22
  with _init_lock:
23
  if _model is not None:
24
  return _model
25
 
26
+ model_path = hf_hub_download(repo_id=HF_REPO, filename=HF_FILENAME)
 
 
 
 
 
 
 
 
 
 
 
 
27
 
 
28
  from llama_cpp import Llama
29
 
30
+ _model = Llama(
31
+ model_path=model_path,
32
+ n_ctx=int(os.getenv("LLAMA_N_CTX", "2048")),
33
+ n_gpu_layers=int(os.getenv("LLAMA_GPU_LAYERS", "0")),
34
+ n_threads=int(os.getenv("LLAMA_N_THREADS", "4")),
35
+ verbose=os.getenv("LLAMA_VERBOSE", "0") == "1",
 
 
36
  )
37
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38
  return _model
app/recs/generate.py CHANGED
@@ -11,18 +11,8 @@ from app.models.llm import load_model
11
 
12
  TARGET_CPL = 20.0
13
 
14
- _IM_END = "<|im_end|>"
15
- _STOP_SEQUENCES = [_IM_END, "<|im_start|>", "</s>"]
16
-
17
  _infer_lock = threading.Lock()
18
 
19
- _SYSTEM = (
20
- "You are a Google Ads analyst. "
21
- "Reply with 3 to 5 markdown bullet points only. "
22
- "Each bullet must be one short, actionable insight about the campaign data. "
23
- "No introduction, no numbered lists, no step-by-step reasoning."
24
- )
25
-
26
 
27
  def fallback_explanation(rec: Dict | None = None) -> str:
28
  return "This recommendation was generated from campaign performance metrics."
@@ -30,12 +20,7 @@ def fallback_explanation(rec: Dict | None = None) -> str:
30
 
31
  def _strip_thinking(text: str) -> str:
32
  text = re.sub(r"<\s*think\s*>.*?<\s*/\s*think\s*>", "", text, flags=re.DOTALL | re.IGNORECASE)
33
- text = re.sub(
34
- r"<think>.*?</think>",
35
- "",
36
- text,
37
- flags=re.DOTALL | re.IGNORECASE,
38
- )
39
  return text.strip()
40
 
41
 
@@ -47,8 +32,6 @@ def _looks_like_garbage(text: str) -> bool:
47
  return True
48
  if "google ads analyst" in lower and text.count("-") < 2:
49
  return True
50
- if "ads performance analyst" in lower and text.count("-") < 2:
51
- return True
52
  if re.search(r"(?:\d[\s\n]+){6,}", text):
53
  return True
54
  digit_ratio = sum(ch.isdigit() for ch in text) / max(len(text), 1)
@@ -57,13 +40,7 @@ def _looks_like_garbage(text: str) -> bool:
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
 
68
 
69
  def is_bad_llm_output(text: str) -> bool:
@@ -90,54 +67,6 @@ def sanitize_explanation(text: str, rec: Dict | None = None) -> str:
90
  return flat
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()
104
- reasoning = (message.get("reasoning_content") or "").strip()
105
- if content and reasoning and _looks_like_garbage(content):
106
- return reasoning
107
- return content or reasoning
108
-
109
-
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,
134
- temperature=temperature,
135
- stop=_STOP_SEQUENCES,
136
- echo=False,
137
- )
138
- return (out["choices"][0].get("text") or "").strip()
139
-
140
-
141
  def _coerce_prompt(prompt: str | Dict, rec: Dict | None) -> tuple[str, Dict | None]:
142
  if isinstance(prompt, dict):
143
  rec = rec or prompt
@@ -149,47 +78,31 @@ def _coerce_prompt(prompt: str | Dict, rec: Dict | None) -> tuple[str, Dict | No
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
- )
161
  if "/no_think" not in user_content:
162
  user_content = f"{user_content}\n/no_think"
163
 
164
- messages = [
165
- {"role": "system", "content": _SYSTEM},
166
- {"role": "user", "content": user_content},
167
- ]
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
-
 
11
 
12
  TARGET_CPL = 20.0
13
 
 
 
 
14
  _infer_lock = threading.Lock()
15
 
 
 
 
 
 
 
 
16
 
17
  def fallback_explanation(rec: Dict | None = None) -> str:
18
  return "This recommendation was generated from campaign performance metrics."
 
20
 
21
  def _strip_thinking(text: str) -> str:
22
  text = re.sub(r"<\s*think\s*>.*?<\s*/\s*think\s*>", "", text, flags=re.DOTALL | re.IGNORECASE)
23
+ text = re.sub(r"<think>.*?</think>", "", text, flags=re.DOTALL | re.IGNORECASE)
 
 
 
 
 
24
  return text.strip()
25
 
26
 
 
32
  return True
33
  if "google ads analyst" in lower and text.count("-") < 2:
34
  return True
 
 
35
  if re.search(r"(?:\d[\s\n]+){6,}", text):
36
  return True
37
  digit_ratio = sum(ch.isdigit() for ch in text) / max(len(text), 1)
 
40
 
41
 
42
  def is_fallback_output(text: str) -> bool:
43
+ return not text or text.startswith("WARNING:") or text.startswith("This recommendation was generated")
 
 
 
 
 
 
44
 
45
 
46
  def is_bad_llm_output(text: str) -> bool:
 
67
  return flat
68
 
69
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
70
  def _coerce_prompt(prompt: str | Dict, rec: Dict | None) -> tuple[str, Dict | None]:
71
  if isinstance(prompt, dict):
72
  rec = rec or prompt
 
78
 
79
 
80
  def generate_explanation(prompt: str | Dict, rec: Dict | None = None, stream: bool = False):
 
 
81
  try:
82
  user_content, rec = _coerce_prompt(prompt, rec)
 
 
 
 
 
83
  if "/no_think" not in user_content:
84
  user_content = f"{user_content}\n/no_think"
85
 
 
 
 
 
 
86
  with _infer_lock:
87
  llm = load_model()
88
+ out = llm(
89
+ user_content,
90
+ max_tokens=int(os.getenv("LLAMA_MAX_TOKENS", "384")),
91
+ temperature=float(os.getenv("LLAMA_TEMPERATURE", "0.35")),
92
+ stop=["</s>"],
93
+ echo=False,
94
+ )
 
95
 
96
+ raw = (out["choices"][0].get("text") or "").strip()
97
  clean = sanitize_explanation(raw, rec)
98
+
 
 
99
  if stream:
100
  return iter([clean])
101
  return clean
102
 
103
  except Exception as e:
 
104
  traceback.print_exc()
105
  err = f"WARNING: Analysis failed: {e}"
106
  if stream:
107
  return iter([err])
108
  return err