Commit ·
09bc76a
1
Parent(s): 8a8d6cc
- Making Gemini more robust with error hnadling and parsing various kind of outputs that are possible.
Browse files- integrating gemini-2.5-flash
- integrating gpt5-mini and nano
- Estimating the prices and times per 1000 messages for each model.
- Config_files/message_system_config.json +5 -4
- Messaging_system/CoreConfig.py +8 -7
- Messaging_system/LLM.py +64 -26
- Messaging_system/Message_generator.py +17 -13
- Messaging_system/MultiMessage.py +29 -21
- Messaging_system/Permes.py +50 -3
- Messaging_system/PromptGenerator.py +3 -1
- Messaging_system/PromptGenerator_2.py +5 -1
- Singeo_camp.csv +0 -0
- messaging_main_test.py +1 -1
- requirements.txt +0 -0
- singeo_segment.csv +0 -0
Config_files/message_system_config.json
CHANGED
|
@@ -18,16 +18,17 @@
|
|
| 18 |
"genres"
|
| 19 |
],
|
| 20 |
"AI_Jargon": ["elevate", "enhance", "ignite", "reignite", "rekindle", "rediscover","passion", "boost", "fuel", "thrill", "revive", "spark", "performing", "fresh", "tone", "enthusiasm", "illuminate"],
|
|
|
|
| 21 |
"AI_phrases_singeo": ["your voice deserves more"],
|
| 22 |
"header_limit": 30,
|
| 23 |
"message_limit": 135,
|
| 24 |
-
"LLM_models": ["gpt-4o-mini", "gpt-
|
| 25 |
-
"openai_models": ["gpt-4o-mini", "gpt-4o", "gpt-4.1-nano", "gpt-3.5-turbo", "gpt-4.1-mini"],
|
| 26 |
-
"reasoning": ["o1", "o4-mini", "o1-mini", "o3-mini"],
|
| 27 |
"ollama_models": ["deepseek-r1:1.5b", "gemma3:4b", "deepseek-r1:7b", "gemma3:4b"],
|
| 28 |
"claude_models": ["claude-3-5-haiku-latest"],
|
| 29 |
"inference_models": ["google/gemma-3-27b-instruct/bf-16", "meta-llama/llama-3.2-11b-instruct/fp-16"],
|
| 30 |
-
"google_models": ["gemini-2.5-flash-lite-
|
| 31 |
}
|
| 32 |
|
| 33 |
|
|
|
|
| 18 |
"genres"
|
| 19 |
],
|
| 20 |
"AI_Jargon": ["elevate", "enhance", "ignite", "reignite", "rekindle", "rediscover","passion", "boost", "fuel", "thrill", "revive", "spark", "performing", "fresh", "tone", "enthusiasm", "illuminate"],
|
| 21 |
+
"singeo_banned_phrases": " Banned phrases:\n Voice is NOT an instrument, so avoid phrases like below:\n - Your voice is waiting\n - Your voice awaits\n - Your voice needs you\n - Your voice is calling\n - Your voice deserves more\n - Hit the high notes / Hit those notes\n - ...\n",
|
| 22 |
"AI_phrases_singeo": ["your voice deserves more"],
|
| 23 |
"header_limit": 30,
|
| 24 |
"message_limit": 135,
|
| 25 |
+
"LLM_models": ["gpt-4o-mini", "gpt-5-mini", "gpt-5-nano", "gemini-2.5-flash","claude-3-5-haiku-latest", "google/gemma-3-27b-instruct/bf-16"],
|
| 26 |
+
"openai_models": ["gpt-4o-mini", "gpt-4o", "gpt-4.1-nano", "gpt-3.5-turbo", "gpt-4.1-mini", "gpt-5-mini", "gpt-5-nano"],
|
| 27 |
+
"reasoning": ["o1", "o4-mini", "o1-mini", "o3-mini", "gpt-5-mini", "gpt-5-nano"],
|
| 28 |
"ollama_models": ["deepseek-r1:1.5b", "gemma3:4b", "deepseek-r1:7b", "gemma3:4b"],
|
| 29 |
"claude_models": ["claude-3-5-haiku-latest"],
|
| 30 |
"inference_models": ["google/gemma-3-27b-instruct/bf-16", "meta-llama/llama-3.2-11b-instruct/fp-16"],
|
| 31 |
+
"google_models": ["gemini-2.5-flash-lite", "gemini-2.5-flash", "gemini-2.0-flash"]
|
| 32 |
}
|
| 33 |
|
| 34 |
|
Messaging_system/CoreConfig.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
| 1 |
"""
|
| 2 |
the flow of the Program starts from create_personalized_message function
|
| 3 |
"""
|
| 4 |
-
|
| 5 |
import time
|
| 6 |
from Messaging_system.SnowFlakeConnection import SnowFlakeConn
|
| 7 |
|
|
@@ -75,16 +75,17 @@ class CoreConfig:
|
|
| 75 |
# ===============================================================
|
| 76 |
def get_brand_voice(self):
|
| 77 |
"""
|
| 78 |
-
|
| 79 |
-
:
|
|
|
|
| 80 |
"""
|
| 81 |
-
# assuming your file is named 'example.txt' and is in the same directory
|
| 82 |
file_path = f'Config_files/{self.brand.lower()}_phrases.txt'
|
| 83 |
|
| 84 |
-
|
| 85 |
-
|
| 86 |
|
| 87 |
-
|
|
|
|
| 88 |
|
| 89 |
# --------------------------------------------------------------
|
| 90 |
# --------------------------------------------------------------
|
|
|
|
| 1 |
"""
|
| 2 |
the flow of the Program starts from create_personalized_message function
|
| 3 |
"""
|
| 4 |
+
import os
|
| 5 |
import time
|
| 6 |
from Messaging_system.SnowFlakeConnection import SnowFlakeConn
|
| 7 |
|
|
|
|
| 75 |
# ===============================================================
|
| 76 |
def get_brand_voice(self):
|
| 77 |
"""
|
| 78 |
+
Get positive phrases for the brand to instruct the LLM to talk like this.
|
| 79 |
+
Returns:
|
| 80 |
+
str: The brand voice phrases if file exists, otherwise None.
|
| 81 |
"""
|
|
|
|
| 82 |
file_path = f'Config_files/{self.brand.lower()}_phrases.txt'
|
| 83 |
|
| 84 |
+
if not os.path.exists(file_path):
|
| 85 |
+
return None
|
| 86 |
|
| 87 |
+
with open(file_path, 'r', encoding='utf-8') as f:
|
| 88 |
+
return f.read()
|
| 89 |
|
| 90 |
# --------------------------------------------------------------
|
| 91 |
# --------------------------------------------------------------
|
Messaging_system/LLM.py
CHANGED
|
@@ -171,6 +171,17 @@ class LLM:
|
|
| 171 |
response_mime_type="application/json"
|
| 172 |
))
|
| 173 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 174 |
output = self.preprocess_and_parse_json(response.text)
|
| 175 |
# output = json.loads(str(response.text))
|
| 176 |
|
|
@@ -218,7 +229,7 @@ class LLM:
|
|
| 218 |
{"role": "system", "content": instructions},
|
| 219 |
{"role": "user", "content": prompt}
|
| 220 |
],
|
| 221 |
-
reasoning_effort="
|
| 222 |
n=1,
|
| 223 |
)
|
| 224 |
|
|
@@ -240,6 +251,11 @@ class LLM:
|
|
| 240 |
'total_tokens': response.usage.total_tokens
|
| 241 |
}
|
| 242 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 243 |
try:
|
| 244 |
content = response.choices[0].message.content
|
| 245 |
|
|
@@ -258,10 +274,6 @@ class LLM:
|
|
| 258 |
f"'header' or 'message' is more than specified characters in response on attempt {attempt + 1}. Retrying...")
|
| 259 |
continue
|
| 260 |
|
| 261 |
-
# validating the JSON
|
| 262 |
-
self.Core.total_tokens['prompt_tokens'] += tokens['prompt_tokens']
|
| 263 |
-
self.Core.total_tokens['completion_tokens'] += tokens['completion_tokens']
|
| 264 |
-
self.Core.temp_token_counter += tokens['total_tokens']
|
| 265 |
return output
|
| 266 |
|
| 267 |
except json.JSONDecodeError:
|
|
@@ -398,30 +410,56 @@ class LLM:
|
|
| 398 |
|
| 399 |
def preprocess_and_parse_json(self, response: str):
|
| 400 |
"""
|
| 401 |
-
|
| 402 |
-
|
| 403 |
-
then returns the parsed object or None on failure.
|
| 404 |
"""
|
| 405 |
-
# 1) Remove all <think>...</think> blocks
|
| 406 |
-
cleaned = re.sub(r'<think>.*?</think>', '', response, flags=re.DOTALL)
|
| 407 |
-
|
| 408 |
-
# 2) Look for a ```json ... ``` fenced block
|
| 409 |
-
fence_pattern = re.compile(r'```json(.*?)```', flags=re.DOTALL)
|
| 410 |
-
fence_match = fence_pattern.search(cleaned)
|
| 411 |
-
if fence_match:
|
| 412 |
-
json_text = fence_match.group(1).strip()
|
| 413 |
-
else:
|
| 414 |
-
# No fence; assume whole cleaned text is JSON
|
| 415 |
-
json_text = cleaned.strip()
|
| 416 |
|
| 417 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 418 |
try:
|
| 419 |
-
return json.loads(
|
| 420 |
-
except json.JSONDecodeError
|
| 421 |
-
|
| 422 |
-
|
| 423 |
-
|
| 424 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 425 |
|
| 426 |
# ===============================================================
|
| 427 |
def preprocess_and_parse_json_claude(self, response: str):
|
|
|
|
| 171 |
response_mime_type="application/json"
|
| 172 |
))
|
| 173 |
|
| 174 |
+
tokens = {
|
| 175 |
+
'prompt_tokens': response.usage_metadata.prompt_token_count,
|
| 176 |
+
'completion_tokens': response.usage_metadata.candidates_token_count,
|
| 177 |
+
'total_tokens': response.usage_metadata.total_token_count
|
| 178 |
+
}
|
| 179 |
+
|
| 180 |
+
# validating the JSON
|
| 181 |
+
self.Core.total_tokens['prompt_tokens'] += tokens['prompt_tokens']
|
| 182 |
+
self.Core.total_tokens['completion_tokens'] += tokens['completion_tokens']
|
| 183 |
+
self.Core.temp_token_counter += tokens['total_tokens']
|
| 184 |
+
|
| 185 |
output = self.preprocess_and_parse_json(response.text)
|
| 186 |
# output = json.loads(str(response.text))
|
| 187 |
|
|
|
|
| 229 |
{"role": "system", "content": instructions},
|
| 230 |
{"role": "user", "content": prompt}
|
| 231 |
],
|
| 232 |
+
reasoning_effort="minimal",
|
| 233 |
n=1,
|
| 234 |
)
|
| 235 |
|
|
|
|
| 251 |
'total_tokens': response.usage.total_tokens
|
| 252 |
}
|
| 253 |
|
| 254 |
+
# validating the JSON
|
| 255 |
+
self.Core.total_tokens['prompt_tokens'] += tokens['prompt_tokens']
|
| 256 |
+
self.Core.total_tokens['completion_tokens'] += tokens['completion_tokens']
|
| 257 |
+
self.Core.temp_token_counter += tokens['total_tokens']
|
| 258 |
+
|
| 259 |
try:
|
| 260 |
content = response.choices[0].message.content
|
| 261 |
|
|
|
|
| 274 |
f"'header' or 'message' is more than specified characters in response on attempt {attempt + 1}. Retrying...")
|
| 275 |
continue
|
| 276 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 277 |
return output
|
| 278 |
|
| 279 |
except json.JSONDecodeError:
|
|
|
|
| 410 |
|
| 411 |
def preprocess_and_parse_json(self, response: str):
|
| 412 |
"""
|
| 413 |
+
Remove <think> blocks, extract JSON (from ```json fences or first {...} block),
|
| 414 |
+
and parse. Includes a repair pass to handle common LLM issues like trailing commas.
|
|
|
|
| 415 |
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 416 |
|
| 417 |
+
def extract_json(text: str) -> str:
|
| 418 |
+
# Remove <think>...</think>
|
| 419 |
+
text = re.sub(r'<think>.*?</think>', '', text, flags=re.DOTALL).strip()
|
| 420 |
+
|
| 421 |
+
# Prefer fenced code if present
|
| 422 |
+
fence = re.search(r'```(?:json)?(.*?)```', text, flags=re.DOTALL | re.IGNORECASE)
|
| 423 |
+
if fence:
|
| 424 |
+
return fence.group(1).strip()
|
| 425 |
+
|
| 426 |
+
# Otherwise, grab the first {...} block
|
| 427 |
+
brace = re.search(r'\{.*\}', text, flags=re.DOTALL)
|
| 428 |
+
return brace.group(0).strip() if brace else text.strip()
|
| 429 |
+
|
| 430 |
+
def normalize_quotes(text: str) -> str:
|
| 431 |
+
return (text
|
| 432 |
+
.replace('\ufeff', '') # strip BOM if present
|
| 433 |
+
.replace('“', '"').replace('”', '"')
|
| 434 |
+
.replace('‘', "'").replace('’', "'"))
|
| 435 |
+
|
| 436 |
+
def strip_comments(text: str) -> str:
|
| 437 |
+
# Remove // line comments and /* block comments */
|
| 438 |
+
text = re.sub(r'//.*?$', '', text, flags=re.MULTILINE)
|
| 439 |
+
text = re.sub(r'/\*.*?\*/', '', text, flags=re.DOTALL)
|
| 440 |
+
return text
|
| 441 |
+
|
| 442 |
+
def remove_trailing_commas(text: str) -> str:
|
| 443 |
+
# Remove commas before } or ]
|
| 444 |
+
return re.sub(r',(\s*[}\]])', r'\1', text)
|
| 445 |
+
|
| 446 |
+
raw = extract_json(response)
|
| 447 |
+
raw = normalize_quotes(raw)
|
| 448 |
+
|
| 449 |
try:
|
| 450 |
+
return json.loads(raw)
|
| 451 |
+
except json.JSONDecodeError:
|
| 452 |
+
# Repair pass
|
| 453 |
+
repaired = strip_comments(raw)
|
| 454 |
+
repaired = remove_trailing_commas(repaired)
|
| 455 |
+
repaired = repaired.strip()
|
| 456 |
+
|
| 457 |
+
try:
|
| 458 |
+
return json.loads(repaired)
|
| 459 |
+
except json.JSONDecodeError as e:
|
| 460 |
+
print(f"Failed to parse JSON: {e}")
|
| 461 |
+
# print('Offending text:', repaired)
|
| 462 |
+
return None
|
| 463 |
|
| 464 |
# ===============================================================
|
| 465 |
def preprocess_and_parse_json_claude(self, response: str):
|
Messaging_system/Message_generator.py
CHANGED
|
@@ -175,33 +175,37 @@ class MessageGenerator:
|
|
| 175 |
"""
|
| 176 |
|
| 177 |
jargon_list = "\n".join(f"- {word}" for word in self.Core.config_file["AI_Jargon"])
|
|
|
|
| 178 |
|
| 179 |
instructions = f"""
|
| 180 |
You are a copywriter. Your task is to write a 'header' and a 'message' as a push notification for a {self.Core.get_instrument()} student. It is critical that the message sounds like natural, everyday speech: friendly, concise, no jargon, and it must follow the instructions.
|
| 181 |
-
Write a SUPER CASUAL and NATURAL push notification, as if you are chatting over coffee. Avoid odd phrasings. The message should sound like something that a {self.Core.get_instrument()} instructor would realistically say to a student in a daily conversation.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 182 |
|
| 183 |
Common instructor phrases:
|
| 184 |
{self.Core.brand_voice}
|
|
|
|
| 185 |
|
| 186 |
|
|
|
|
| 187 |
ABSOLUTE RULE – OVERRIDES EVERYTHING ELSE:
|
| 188 |
the header and the message **MUST NOT** contain any banned word or phrases(case-insensitive; singular, plural, verb forms, or their derivatives)
|
| 189 |
- **important Note:** header **must be** less than {self.Core.config_file["header_limit"]} characters and message **must be less** than {self.Core.config_file["message_limit"]} characters.
|
| 190 |
|
| 191 |
-
|
| 192 |
Banned word:
|
| 193 |
{jargon_list}
|
| 194 |
|
| 195 |
-
Banned phrases:
|
| 196 |
-
Voice is NOT an instrument, so avoid phrases like below:
|
| 197 |
-
- Your voice is waiting
|
| 198 |
-
- Your voice awaits
|
| 199 |
-
- Your voice needs you
|
| 200 |
-
- Your voice is calling
|
| 201 |
-
- Your voice deserves more
|
| 202 |
-
- Hit the high notes / Hit those notes
|
| 203 |
-
- ...
|
| 204 |
-
|
| 205 |
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 206 |
|
| 207 |
-
return
|
|
|
|
| 175 |
"""
|
| 176 |
|
| 177 |
jargon_list = "\n".join(f"- {word}" for word in self.Core.config_file["AI_Jargon"])
|
| 178 |
+
banned_phrases = self.Core.config_file.get(f"{self.Core.brand}_banned_phrases", None)
|
| 179 |
|
| 180 |
instructions = f"""
|
| 181 |
You are a copywriter. Your task is to write a 'header' and a 'message' as a push notification for a {self.Core.get_instrument()} student. It is critical that the message sounds like natural, everyday speech: friendly, concise, no jargon, and it must follow the instructions.
|
| 182 |
+
Write a SUPER CASUAL and NATURAL push notification, as if you are chatting over coffee. Avoid odd phrasings. The message should sound like something that a {self.Core.get_instrument()} instructor would realistically say to a student in a daily conversation.
|
| 183 |
+
|
| 184 |
+
"""
|
| 185 |
+
if self.Core.brand_voice is not None:
|
| 186 |
+
instructions += f"""
|
| 187 |
+
Here are some examples of things that an instructor would realistically say to a student, to give you a general sense of tone and phrasing:
|
| 188 |
|
| 189 |
Common instructor phrases:
|
| 190 |
{self.Core.brand_voice}
|
| 191 |
+
"""
|
| 192 |
|
| 193 |
|
| 194 |
+
rules = f"""
|
| 195 |
ABSOLUTE RULE – OVERRIDES EVERYTHING ELSE:
|
| 196 |
the header and the message **MUST NOT** contain any banned word or phrases(case-insensitive; singular, plural, verb forms, or their derivatives)
|
| 197 |
- **important Note:** header **must be** less than {self.Core.config_file["header_limit"]} characters and message **must be less** than {self.Core.config_file["message_limit"]} characters.
|
| 198 |
|
|
|
|
| 199 |
Banned word:
|
| 200 |
{jargon_list}
|
| 201 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 202 |
"""
|
| 203 |
+
if banned_phrases is not None:
|
| 204 |
+
rules += banned_phrases
|
| 205 |
+
|
| 206 |
+
final_instructions = f"""
|
| 207 |
+
{instructions}
|
| 208 |
+
{rules}
|
| 209 |
+
"""
|
| 210 |
|
| 211 |
+
return final_instructions
|
Messaging_system/MultiMessage.py
CHANGED
|
@@ -172,7 +172,7 @@ Mimic the example style as much as possible and make it personalized using provi
|
|
| 172 |
# Combine into a single string
|
| 173 |
previous_text_str = "\n\n".join(previous_text)
|
| 174 |
|
| 175 |
-
|
| 176 |
input_context = self.promptGen.input_context()
|
| 177 |
instructions = self.message_instructions(step)
|
| 178 |
recommendation_instructions = self.promptGen.recommendations_instructions(user)
|
|
@@ -195,6 +195,8 @@ Write the *next* follow up personalized push notification following the instruct
|
|
| 195 |
- The new header and message should be different from previous messages and we should not have similar words and phrases from previous messages.
|
| 196 |
- You are **ONLY ALLOWED** to use {self.Core.get_emoji()} if we didn't use in previous messages. (ONLY ONCE, and ONLY at the end of header or message).
|
| 197 |
|
|
|
|
|
|
|
| 198 |
{recommendation_instructions}
|
| 199 |
|
| 200 |
{examples}
|
|
@@ -362,33 +364,39 @@ Write the *next* follow up personalized push notification following the instruct
|
|
| 362 |
"""
|
| 363 |
|
| 364 |
jargon_list = "\n".join(f"- {word}" for word in self.Core.config_file["AI_Jargon"])
|
|
|
|
| 365 |
|
| 366 |
instructions = f"""
|
| 367 |
-
You are a copywriter. Your task is to write a 'header' and a 'message' as a push notification for a {self.Core.get_instrument()} student. It is critical that the message sounds like natural, everyday speech: friendly, concise, no jargon, and it must follow the instructions.
|
| 368 |
-
Write a SUPER CASUAL and NATURAL push notification, as if you are chatting over coffee. Avoid odd phrasings. The message should sound like something that a {self.Core.get_instrument()} instructor would realistically say to a student
|
| 369 |
|
| 370 |
-
|
| 371 |
-
|
|
|
|
|
|
|
| 372 |
|
| 373 |
-
|
| 374 |
-
|
| 375 |
-
|
| 376 |
|
| 377 |
-
|
| 378 |
-
|
|
|
|
|
|
|
| 379 |
|
| 380 |
-
Banned phrases:
|
| 381 |
-
Voice is NOT an instrument, so avoid phrases like below:
|
| 382 |
-
- Your voice is waiting
|
| 383 |
-
- Your voice awaits
|
| 384 |
-
- Your voice needs you
|
| 385 |
-
- Your voice is calling
|
| 386 |
-
- Your voice deserves more
|
| 387 |
-
- Hit the high notes / Hit those notes
|
| 388 |
-
- ...
|
| 389 |
|
| 390 |
-
|
|
|
|
| 391 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 392 |
|
| 393 |
-
return instructions
|
| 394 |
|
|
|
|
| 172 |
# Combine into a single string
|
| 173 |
previous_text_str = "\n\n".join(previous_text)
|
| 174 |
|
| 175 |
+
user_info = self.promptGen.get_user_profile(user=user)
|
| 176 |
input_context = self.promptGen.input_context()
|
| 177 |
instructions = self.message_instructions(step)
|
| 178 |
recommendation_instructions = self.promptGen.recommendations_instructions(user)
|
|
|
|
| 195 |
- The new header and message should be different from previous messages and we should not have similar words and phrases from previous messages.
|
| 196 |
- You are **ONLY ALLOWED** to use {self.Core.get_emoji()} if we didn't use in previous messages. (ONLY ONCE, and ONLY at the end of header or message).
|
| 197 |
|
| 198 |
+
{user_info}
|
| 199 |
+
|
| 200 |
{recommendation_instructions}
|
| 201 |
|
| 202 |
{examples}
|
|
|
|
| 364 |
"""
|
| 365 |
|
| 366 |
jargon_list = "\n".join(f"- {word}" for word in self.Core.config_file["AI_Jargon"])
|
| 367 |
+
banned_phrases = self.Core.config_file.get(f"{self.Core.brand}_banned_phrases", None)
|
| 368 |
|
| 369 |
instructions = f"""
|
| 370 |
+
You are a copywriter. Your task is to write a 'header' and a 'message' as a push notification for a {self.Core.get_instrument()} student. It is critical that the message sounds like natural, everyday speech: friendly, concise, no jargon, and it must follow the instructions.
|
| 371 |
+
Write a SUPER CASUAL and NATURAL push notification, as if you are chatting over coffee. Avoid odd phrasings. The message should sound like something that a {self.Core.get_instrument()} instructor would realistically say to a student in a daily conversation.
|
| 372 |
|
| 373 |
+
"""
|
| 374 |
+
if self.Core.brand_voice is not None:
|
| 375 |
+
instructions += f"""
|
| 376 |
+
Here are some examples of things that an instructor would realistically say to a student, to give you a general sense of tone and phrasing:
|
| 377 |
|
| 378 |
+
Common instructor phrases:
|
| 379 |
+
{self.Core.brand_voice}
|
| 380 |
+
"""
|
| 381 |
|
| 382 |
+
rules = f"""
|
| 383 |
+
ABSOLUTE RULE – OVERRIDES EVERYTHING ELSE:
|
| 384 |
+
the header and the message **MUST NOT** contain any banned word or phrases(case-insensitive; singular, plural, verb forms, or their derivatives)
|
| 385 |
+
- **important Note:** header **must be** less than {self.Core.config_file["header_limit"]} characters and message **must be less** than {self.Core.config_file["message_limit"]} characters.
|
| 386 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 387 |
|
| 388 |
+
Banned word:
|
| 389 |
+
{jargon_list}
|
| 390 |
|
| 391 |
+
"""
|
| 392 |
+
if banned_phrases is not None:
|
| 393 |
+
rules += banned_phrases
|
| 394 |
+
|
| 395 |
+
final_instructions = f"""
|
| 396 |
+
{instructions}
|
| 397 |
+
{rules}
|
| 398 |
+
"""
|
| 399 |
+
|
| 400 |
+
return final_instructions
|
| 401 |
|
|
|
|
| 402 |
|
Messaging_system/Permes.py
CHANGED
|
@@ -90,9 +90,14 @@ class Permes:
|
|
| 90 |
total_prompt_tokens = personalize_message.total_tokens["prompt_tokens"]
|
| 91 |
total_completion_tokens = personalize_message.total_tokens["completion_tokens"]
|
| 92 |
|
| 93 |
-
total_cost = (
|
| 94 |
-
|
| 95 |
-
print(f"Estimated Cost (USD): {total_cost:.5f}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 96 |
|
| 97 |
# Storing process can also happen after some evaluation steps
|
| 98 |
# snowflake_conn = SnowFlakeConn(session=session, brand=brand)
|
|
@@ -103,6 +108,48 @@ class Permes:
|
|
| 103 |
return users_df
|
| 104 |
|
| 105 |
# -----------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 106 |
def identify_users(self, users_df, identifier_column):
|
| 107 |
"""
|
| 108 |
specifying the users for identification
|
|
|
|
| 90 |
total_prompt_tokens = personalize_message.total_tokens["prompt_tokens"]
|
| 91 |
total_completion_tokens = personalize_message.total_tokens["completion_tokens"]
|
| 92 |
|
| 93 |
+
total_cost = self.calculate_cost(total_prompt_tokens, total_completion_tokens, model)
|
| 94 |
+
|
| 95 |
+
print(f"Estimated Cost (USD): {total_cost:.5f} ---> Number of messages: {(len(users_df) * number_of_messages)}")
|
| 96 |
+
st.write(f"Estimated Cost (USD): {total_cost:.5f} ---> Number of messages: {(len(users_df) * number_of_messages)}")
|
| 97 |
+
|
| 98 |
+
scale_price = (total_cost * 1000) / (len(users_df) * number_of_messages)
|
| 99 |
+
print(f"Estimated Cost (USD) for 1000 messages: {scale_price}")
|
| 100 |
+
st.write(f"Estimated Cost (USD) for 1000 messages: {scale_price}")
|
| 101 |
|
| 102 |
# Storing process can also happen after some evaluation steps
|
| 103 |
# snowflake_conn = SnowFlakeConn(session=session, brand=brand)
|
|
|
|
| 108 |
return users_df
|
| 109 |
|
| 110 |
# -----------------------------------------------------
|
| 111 |
+
def calculate_cost(self, total_prompt_tokens, total_completion_tokens, model):
|
| 112 |
+
input_price, output_price = self.get_model_price(model)
|
| 113 |
+
|
| 114 |
+
total_cost = ((total_prompt_tokens / 1000000) * input_price) + (
|
| 115 |
+
(total_completion_tokens / 1000000) * output_price) # Cost calculation estimation
|
| 116 |
+
|
| 117 |
+
return total_cost
|
| 118 |
+
|
| 119 |
+
# ====================================================
|
| 120 |
+
def get_model_price(self, model):
|
| 121 |
+
"""
|
| 122 |
+
getting the input price and output price per 1m token for the requested model
|
| 123 |
+
:param model:
|
| 124 |
+
:return:
|
| 125 |
+
"""
|
| 126 |
+
|
| 127 |
+
input_prices = {
|
| 128 |
+
"gpt-4o-mini":0.15,
|
| 129 |
+
"gpt-4.1-mini":0.4,
|
| 130 |
+
"gpt-5-mini": 0.25,
|
| 131 |
+
"gpt-5-nano": 0.05,
|
| 132 |
+
"gemini-2.5-flash":0.3,
|
| 133 |
+
"gemini-2.0-flash":0.1,
|
| 134 |
+
"gemini-2.5-flash-lite":0.1
|
| 135 |
+
}
|
| 136 |
+
|
| 137 |
+
out_prices = {
|
| 138 |
+
"gpt-4o-mini":0.6,
|
| 139 |
+
"gpt-4.1-mini":1.6,
|
| 140 |
+
"gpt-5-mini": 2,
|
| 141 |
+
"gpt-5-nano": 0.4,
|
| 142 |
+
"gemini-2.5-flash":2.5,
|
| 143 |
+
"gemini-2.0-flash":0.7,
|
| 144 |
+
"gemini-2.5-flash-lite":0.4
|
| 145 |
+
}
|
| 146 |
+
|
| 147 |
+
i_price = input_prices.get(model, 0)
|
| 148 |
+
o_price= out_prices.get(model, 0)
|
| 149 |
+
|
| 150 |
+
return i_price, o_price
|
| 151 |
+
|
| 152 |
+
# ====================================================
|
| 153 |
def identify_users(self, users_df, identifier_column):
|
| 154 |
"""
|
| 155 |
specifying the users for identification
|
Messaging_system/PromptGenerator.py
CHANGED
|
@@ -88,7 +88,7 @@ Here is the information about the user:
|
|
| 88 |
"""
|
| 89 |
input_context = self.input_context()
|
| 90 |
instructions = self.message_type_instructions()
|
| 91 |
-
|
| 92 |
|
| 93 |
recommendation_instructions = self.recommendations_instructions(user)
|
| 94 |
|
|
@@ -101,6 +101,8 @@ Here is the information about the user:
|
|
| 101 |
|
| 102 |
{instructions}
|
| 103 |
|
|
|
|
|
|
|
| 104 |
{recommendation_instructions}
|
| 105 |
|
| 106 |
{example_output}
|
|
|
|
| 88 |
"""
|
| 89 |
input_context = self.input_context()
|
| 90 |
instructions = self.message_type_instructions()
|
| 91 |
+
user_info = self.get_user_profile(user=user)
|
| 92 |
|
| 93 |
recommendation_instructions = self.recommendations_instructions(user)
|
| 94 |
|
|
|
|
| 101 |
|
| 102 |
{instructions}
|
| 103 |
|
| 104 |
+
{user_info}
|
| 105 |
+
|
| 106 |
{recommendation_instructions}
|
| 107 |
|
| 108 |
{example_output}
|
Messaging_system/PromptGenerator_2.py
CHANGED
|
@@ -117,10 +117,14 @@ Here is the information about the user:
|
|
| 117 |
Your task is to write a 'header' and a 'message' as a push notification for a {self.Core.get_instrument()} student that sounds like everyday natural speech: friendly, short, no jargon by following the instructions.
|
| 118 |
The output should sound like something that a {self.Core.get_instrument()} instructor would realistically say to a student in a daily conversation.
|
| 119 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 120 |
** Examples of actual phrases an instructor might say:**
|
| 121 |
|
| 122 |
{self.Core.brand_voice}
|
| 123 |
-
|
| 124 |
|
| 125 |
return context
|
| 126 |
|
|
|
|
| 117 |
Your task is to write a 'header' and a 'message' as a push notification for a {self.Core.get_instrument()} student that sounds like everyday natural speech: friendly, short, no jargon by following the instructions.
|
| 118 |
The output should sound like something that a {self.Core.get_instrument()} instructor would realistically say to a student in a daily conversation.
|
| 119 |
|
| 120 |
+
"""
|
| 121 |
+
|
| 122 |
+
if self.Core.brand_voice is not None:
|
| 123 |
+
context += f"""
|
| 124 |
** Examples of actual phrases an instructor might say:**
|
| 125 |
|
| 126 |
{self.Core.brand_voice}
|
| 127 |
+
"""
|
| 128 |
|
| 129 |
return context
|
| 130 |
|
Singeo_camp.csv
CHANGED
|
The diff for this file is too large to render.
See raw diff
|
|
|
messaging_main_test.py
CHANGED
|
@@ -161,7 +161,7 @@ if __name__ == "__main__":
|
|
| 161 |
# o3-mini o1-mini o4-mini o1
|
| 162 |
|
| 163 |
users_message = permes.create_personalize_messages(session=session,
|
| 164 |
-
model="gemini-2.5-flash
|
| 165 |
users=users,
|
| 166 |
brand=brand,
|
| 167 |
message_style = instructions,
|
|
|
|
| 161 |
# o3-mini o1-mini o4-mini o1
|
| 162 |
|
| 163 |
users_message = permes.create_personalize_messages(session=session,
|
| 164 |
+
model="gemini-2.5-flash",
|
| 165 |
users=users,
|
| 166 |
brand=brand,
|
| 167 |
message_style = instructions,
|
requirements.txt
CHANGED
|
Binary files a/requirements.txt and b/requirements.txt differ
|
|
|
singeo_segment.csv
CHANGED
|
The diff for this file is too large to render.
See raw diff
|
|
|