Danialebrat commited on
Commit
10458ab
·
1 Parent(s): 9023f08

- Adding multiple models from API and upgrading OpenAI

Browse files

- Adding PromptEngine layer before each request
- Modifying system prompts and all prompts
- Modifying the protection layer
- Adding model selection to UI
- Adding open-sourced models

Config_files/message_system_config.json CHANGED
@@ -21,7 +21,7 @@
21
  "AI_phrases_singeo": ["your voice deserves more"],
22
  "header_limit": 30,
23
  "message_limit": 110,
24
- "LLM_models": ["4o-mini", "gpt-4o", "deepseek-r1:1.5b", "gemma3:4b"]
25
  }
26
 
27
 
 
21
  "AI_phrases_singeo": ["your voice deserves more"],
22
  "header_limit": 30,
23
  "message_limit": 110,
24
+ "LLM_models": ["4o-mini", "gpt-4o", "gpt-4.1-nano", "gpt-4.1-mini", "gpt-3.5-turbo", "o4-mini", "o1"]
25
  }
26
 
27
 
Messaging_system/LLM.py CHANGED
@@ -21,6 +21,7 @@ class LLM:
21
  self.model_type = "openai" # valid values -> ["openai", "ollama"]
22
  self.client = None
23
  self.connect_to_llm()
 
24
 
25
 
26
  def get_response(self, prompt, instructions):
@@ -38,11 +39,14 @@ class LLM:
38
  connect to selected llm -> ollama or openai connection
39
  :return:
40
  """
41
- openai_models = ["4o-mini", "gpt-4o", "gpt-4.1-nano"]
 
42
  ollama_models = ["deepseek-r1:1.5b", "gemma3:4b", "deepseek-r1:7b", "gemma3:4b"]
43
 
44
  if self.Core.model in openai_models:
45
  self.model_type = "openai"
 
 
46
 
47
  if self.Core.model in ollama_models:
48
  self.model_type = "ollama"
@@ -51,27 +55,28 @@ class LLM:
51
  self.model = self.Core.model
52
 
53
 
54
-
55
  def get_message_openai(self, prompt, instructions, max_retries=4):
56
- """
57
- sending the prompt to openai LLM and get back the response
58
- """
59
 
60
  openai.api_key = self.Core.api_key
61
  client = OpenAI(api_key=self.Core.api_key)
62
 
63
  for attempt in range(max_retries):
64
  try:
65
- response = client.chat.completions.create(
66
  model=self.Core.model,
67
- response_format={"type": "json_object"},
68
- messages=[
69
- {"role": "system", "content": instructions},
70
- {"role": "user", "content": prompt}
71
- ],
72
- max_tokens=500,
73
- n=1,
 
 
74
  temperature=self.Core.temperature,
 
 
75
  )
76
 
77
  tokens = {
@@ -120,6 +125,76 @@ class LLM:
120
  print("Max retries exceeded. Returning empty response.")
121
  return None
122
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
123
  # ======================================================================
124
 
125
  def get_message_ollama(self, prompt, instructions, max_retries=10):
 
21
  self.model_type = "openai" # valid values -> ["openai", "ollama"]
22
  self.client = None
23
  self.connect_to_llm()
24
+ self.reasoning = {}
25
 
26
 
27
  def get_response(self, prompt, instructions):
 
39
  connect to selected llm -> ollama or openai connection
40
  :return:
41
  """
42
+ openai_models = ["4o-mini", "gpt-4o", "gpt-4.1-nano", "gpt-3.5-turbo", "gpt-4.1-mini"]
43
+ reasoning = ["o1", "o4-mini"]
44
  ollama_models = ["deepseek-r1:1.5b", "gemma3:4b", "deepseek-r1:7b", "gemma3:4b"]
45
 
46
  if self.Core.model in openai_models:
47
  self.model_type = "openai"
48
+ if self.Core.model in reasoning:
49
+ self.reasoning= {"effort": "medium"}
50
 
51
  if self.Core.model in ollama_models:
52
  self.model_type = "ollama"
 
55
  self.model = self.Core.model
56
 
57
 
 
58
  def get_message_openai(self, prompt, instructions, max_retries=4):
59
+
 
 
60
 
61
  openai.api_key = self.Core.api_key
62
  client = OpenAI(api_key=self.Core.api_key)
63
 
64
  for attempt in range(max_retries):
65
  try:
66
+ response = client.responses.create(
67
  model=self.Core.model,
68
+ input=[{"role": "system", "content": instructions},
69
+ {"role": "user", "content": prompt}],
70
+ text={
71
+ "format": {
72
+ "type": "json_object"
73
+ }
74
+ },
75
+ reasoning=self.reasoning,
76
+ max_output_tokens=500,
77
  temperature=self.Core.temperature,
78
+ top_p=1,
79
+ tools=[],
80
  )
81
 
82
  tokens = {
 
125
  print("Max retries exceeded. Returning empty response.")
126
  return None
127
 
128
+
129
+
130
+ # def get_message_openai(self, prompt, instructions, max_retries=4):
131
+ # """
132
+ # sending the prompt to openai LLM and get back the response
133
+ # """
134
+ #
135
+ # openai.api_key = self.Core.api_key
136
+ # client = OpenAI(api_key=self.Core.api_key)
137
+ #
138
+ # for attempt in range(max_retries):
139
+ # try:
140
+ # response = client.chat.completions.create(
141
+ # model=self.Core.model,
142
+ # response_format={"type": "json_object"},
143
+ # messages=[
144
+ # {"role": "system", "content": instructions},
145
+ # {"role": "user", "content": prompt}
146
+ # ],
147
+ # max_tokens=500,
148
+ # n=1,
149
+ # temperature=self.Core.temperature,
150
+ # )
151
+ #
152
+ # tokens = {
153
+ # 'prompt_tokens': response.usage.prompt_tokens,
154
+ # 'completion_tokens': response.usage.completion_tokens,
155
+ # 'total_tokens': response.usage.total_tokens
156
+ # }
157
+ #
158
+ # try:
159
+ # content = response.choices[0].message.content
160
+ #
161
+ # # Extract JSON code block
162
+ #
163
+ # output = json.loads(content)
164
+ #
165
+ # if 'message' not in output or 'header' not in output:
166
+ # print(f"'message' or 'header' is missing in response on attempt {attempt + 1}. Retrying...")
167
+ # continue # Continue to next attempt
168
+ #
169
+ # else:
170
+ # if len(output["header"].strip()) > self.Core.config_file["header_limit"] or len(
171
+ # output["message"].strip()) > self.Core.config_file["message_limit"]:
172
+ # print(
173
+ # f"'header' or 'message' is more than specified characters in response on attempt {attempt + 1}. Retrying...")
174
+ # continue
175
+ #
176
+ # # validating the JSON
177
+ # self.Core.total_tokens['prompt_tokens'] += tokens['prompt_tokens']
178
+ # self.Core.total_tokens['completion_tokens'] += tokens['completion_tokens']
179
+ # self.Core.temp_token_counter += tokens['total_tokens']
180
+ # return output
181
+ #
182
+ # except json.JSONDecodeError:
183
+ # print(f"Invalid JSON from LLM on attempt {attempt + 1}. Retrying...")
184
+ #
185
+ # except openai.APIConnectionError as e:
186
+ # print("The server could not be reached")
187
+ # print(e.__cause__) # an underlying Exception, likely raised within httpx.
188
+ # except openai.RateLimitError as e:
189
+ # print("A 429 status code was received; we should back off a bit.")
190
+ # except openai.APIStatusError as e:
191
+ # print("Another non-200-range status code was received")
192
+ # print(e.status_code)
193
+ # print(e.response)
194
+ #
195
+ # print("Max retries exceeded. Returning empty response.")
196
+ # return None
197
+
198
  # ======================================================================
199
 
200
  def get_message_ollama(self, prompt, instructions, max_retries=10):
Messaging_system/MultiMessage.py CHANGED
@@ -1,6 +1,8 @@
1
  import json
2
  import time
3
  from openai import OpenAI
 
 
4
  from Messaging_system.protection_layer import ProtectionLayer
5
  import openai
6
  from Messaging_system.LLM import LLM
@@ -13,6 +15,7 @@ class MultiMessage:
13
  """
14
  self.Core = CoreConfig
15
  self.llm = LLM(CoreConfig)
 
16
 
17
  # --------------------------------------------------------------
18
  def generate_multi_messages(self, user):
@@ -107,8 +110,10 @@ class MultiMessage:
107
  # 1) Build a prompt that includes only those last two messages
108
  prompt = self.generate_prompt(context, step)
109
 
 
 
110
  # 2) Call our existing LLM routine
111
- response_dict = self.llm.get_response(prompt=prompt, instructions=self.llm_instructions())
112
 
113
  return response_dict
114
 
 
1
  import json
2
  import time
3
  from openai import OpenAI
4
+
5
+ from Messaging_system.PromptEng import PromptEngine
6
  from Messaging_system.protection_layer import ProtectionLayer
7
  import openai
8
  from Messaging_system.LLM import LLM
 
15
  """
16
  self.Core = CoreConfig
17
  self.llm = LLM(CoreConfig)
18
+ self.engine = PromptEngine(self.Core)
19
 
20
  # --------------------------------------------------------------
21
  def generate_multi_messages(self, user):
 
110
  # 1) Build a prompt that includes only those last two messages
111
  prompt = self.generate_prompt(context, step)
112
 
113
+ new_prompt = self.engine.prompt_engineering(prompt)
114
+
115
  # 2) Call our existing LLM routine
116
+ response_dict = self.llm.get_response(prompt=new_prompt, instructions=self.llm_instructions())
117
 
118
  return response_dict
119
 
Messaging_system/PromptEng.py ADDED
@@ -0,0 +1,86 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ This is the prompt engineering layer to modifty the prompt for better perfromance
3
+ """
4
+ import openai
5
+ from openai import OpenAI
6
+
7
+
8
+ class PromptEngine:
9
+
10
+ def __init__(self, coreconfig):
11
+ self.Core=coreconfig
12
+
13
+ # =============================================================
14
+ def prompt_engineering(self, prompt):
15
+ """
16
+ prompt engineering layer to modify the prompt as needed
17
+ :param prompt:
18
+ :return:
19
+ """
20
+
21
+ new_prompt = self.get_llm_response(prompt)
22
+ return new_prompt
23
+
24
+ # ============================================================
25
+ def llm_instructions(self):
26
+
27
+ system_prompt = """
28
+ You are a prompt engineer. Rewrite the following prompt to be clearer, more specific, and likely to produce a better response from an LLM following best prompt engineering techniques and styles.
29
+ """
30
+
31
+ return system_prompt
32
+
33
+ # =============================================================
34
+
35
+ def get_llm_response(self, prompt, max_retries=4):
36
+ """
37
+ sending the prompt to openai LLM and get back the response
38
+ """
39
+
40
+ openai.api_key = self.Core.api_key
41
+ client = OpenAI(api_key=self.Core.api_key)
42
+
43
+ for attempt in range(max_retries):
44
+ try:
45
+ response = client.chat.completions.create(
46
+ model=self.Core.model,
47
+ response_format={"type": "text"},
48
+ messages=[
49
+ {"role": "system", "content": self.llm_instructions()},
50
+ {"role": "user", "content": prompt}
51
+ ],
52
+ max_tokens=1000,
53
+ n=1,
54
+ temperature=self.Core.temperature,
55
+ )
56
+
57
+ tokens = {
58
+ 'prompt_tokens': response.usage.prompt_tokens,
59
+ 'completion_tokens': response.usage.completion_tokens,
60
+ 'total_tokens': response.usage.total_tokens
61
+ }
62
+
63
+
64
+ content = response.choices[0].message.content
65
+ output = str(content)
66
+
67
+ # validating the JSON
68
+ self.Core.total_tokens['prompt_tokens'] += tokens['prompt_tokens']
69
+ self.Core.total_tokens['completion_tokens'] += tokens['completion_tokens']
70
+ self.Core.temp_token_counter += tokens['total_tokens']
71
+ return output
72
+
73
+ except openai.APIConnectionError as e:
74
+ print("The server could not be reached")
75
+ print(e.__cause__) # an underlying Exception, likely raised within httpx.
76
+ except openai.RateLimitError as e:
77
+ print("A 429 status code was received; we should back off a bit.")
78
+ except openai.APIStatusError as e:
79
+ print("Another non-200-range status code was received")
80
+ print(e.status_code)
81
+ print(e.response)
82
+
83
+ print("Max retries exceeded. Returning empty response.")
84
+ return prompt # returns original prompt if needed
85
+
86
+ # ==========================================================================
Messaging_system/PromptGenerator.py CHANGED
@@ -3,6 +3,7 @@ THis class generate proper prompts for the messaging system
3
  """
4
  import pandas as pd
5
  from tqdm import tqdm
 
6
 
7
 
8
  class PromptGenerator:
@@ -18,16 +19,19 @@ class PromptGenerator:
18
  :return:
19
  """
20
 
 
 
21
  # if we have personalized information about them, we generate a personalized prompt
22
  for idx, row in tqdm(self.Core.users_df.iterrows(), desc="generating prompts"):
23
  # check if we have enough information to generate a personalized message
24
  prompt = self.generate_personalized_prompt(user=row)
25
- # message = self.call_llm(prompt)
26
- self.Core.users_df.at[idx, "prompt"] = prompt
27
  self.Core.users_df.at[idx, "source"] = "AI-generated"
28
 
29
  return self.Core
30
 
 
31
  # --------------------------------------------------------------
32
  def safe_get(self, value):
33
  return str(value) if pd.notna(value) else "Not available"
 
3
  """
4
  import pandas as pd
5
  from tqdm import tqdm
6
+ from Messaging_system.PromptEng import PromptEngine
7
 
8
 
9
  class PromptGenerator:
 
19
  :return:
20
  """
21
 
22
+ engine = PromptEngine(self.Core)
23
+
24
  # if we have personalized information about them, we generate a personalized prompt
25
  for idx, row in tqdm(self.Core.users_df.iterrows(), desc="generating prompts"):
26
  # check if we have enough information to generate a personalized message
27
  prompt = self.generate_personalized_prompt(user=row)
28
+ new_prompt = engine.prompt_engineering(prompt)
29
+ self.Core.users_df.at[idx, "prompt"] = new_prompt
30
  self.Core.users_df.at[idx, "source"] = "AI-generated"
31
 
32
  return self.Core
33
 
34
+
35
  # --------------------------------------------------------------
36
  def safe_get(self, value):
37
  return str(value) if pd.notna(value) else "Not available"
Messaging_system/protection_layer.py CHANGED
@@ -2,11 +2,6 @@
2
  protection layer on top of the messaging system to make sure the messages are as expected.
3
  """
4
 
5
- import json
6
- import os
7
- import openai
8
- from openai import OpenAI
9
- from dotenv import load_dotenv
10
  from Messaging_system.LLM import LLM
11
 
12
 
@@ -29,125 +24,106 @@ class ProtectionLayer:
29
  }
30
 
31
  # --------------------------------------------------------------
32
- def llm_instructions(self):
 
33
  """
34
- Setting instructions for the LLM for the second pass.
 
35
  """
36
 
37
  jargon_list = "\n".join(f"- {word}" for word in self.Core.config_file["AI_Jargon"])
38
 
39
- instructions = f"""
40
-
41
- You are friendly copywriter. Your job is to *either* approve the candidate message or return a corrected version that obeys the style guide.
42
- the 'header' and a 'message' as a push notification should sounds like everyday speech—friendly, short, no jargon, following the instructions.
43
-
44
- ABSOLUTE RULE OVERRIDES EVERYTHING ELSE:
45
- Always Capitalize the first word of 'header' and 'message'
46
- The message should sounds like a normal person, something that people normally say in daily conversations. This is the most important part of the message, the message should sounds like a normal and casual conversation.
47
- If the header or message contains any banned word or phrases, you must rewrite the offending sentence so that **none** of those words or phrases(case-insensitive; singular, plural, verb forms, or their derivatives)
48
- remain. If no banned words or phrases are present, leave the text unchanged. Return only valid JSON.
49
-
50
- Banned word:
51
- {jargon_list}
52
-
53
- Banned phrases:
54
- Voice is NOT an instrument, so avoid below phrases and similar ones like below:
55
- - Your voice is waiting
56
- - Your voice awaits
57
- - Your voice needs you
58
- - Your voice is calling
59
- - Your voice deserves more
60
- - ...
61
-
62
- """
63
- return instructions
64
 
65
- # --------------------------------------------------------------
66
- def get_general_rules(self):
67
  """
68
- Core rules to apply when checking or modifying the message.
69
  """
70
 
71
-
72
- return f"""
73
- - No two consecutive sentences should end with exclamation points, change one of them to dot.
74
- - Header and message should Start directly with the message content without greetings or closing phrases (every word counts).
75
- - First word of the 'header' and 'message' as well as names or any proper nouns **MUST** be Capitalized.
76
- - If there is any grammar error in the message, you must fix it.
77
- - Do not include any words that explicitly or implicitly reference a time-related concept (e.g., “new,” “recent,” “latest,” “upcoming,” etc.).
78
- - Preserve the original JSON structure: {{"header": "...", "message": "..."}}; The output must be strictly a valid JSON with no extra commentary or text.
79
- - Would a normal freind text this to a friend? If not, modify it as needed.
80
- - If no rule is violated, return the exact same JSON unchanged.
81
  """
82
 
83
- # --------------------------------------------------------------
84
- def output_instruction(self):
85
  """
86
- :return: output instructions as a string
87
  """
88
- instructions = f"""
89
- **You must output only valid JSON in the form:**
90
-
91
- {{
92
- "header": "Original header or modified version",
93
- "message": "Original header or modified version"
94
- }}
95
-
96
-
97
- **Constraints:**
98
- - The "header" must be less than 30 character.
99
- - The "message" must be less than 100 character.
100
- - No text is allowed outside this JSON structure.\n"
101
- """
102
-
103
- return instructions
104
 
105
- # --------------------------------------------------------------
106
- # --------------------------------------------------------------
107
- def get_context(self):
108
  """
109
- context for the LLM
110
- :return: the context string
111
  """
112
- context = (
113
- "We created a personalized message for a user "
114
- "considering the provided information. Your task is to double-check "
115
- "the message and correct or improve the output, according to instructions."
116
  )
117
- return context
118
 
119
- # --------------------------------------------------------------
120
- def generate_prompt(self, message, user):
121
  """
122
- generating the prompt for criticizing
123
- :param query: input query
124
- :param message: llm response
125
- :return: new prompt
126
  """
127
- # recommended_content = ""
128
- # if self.Core.messaging_mode == "recsys_result":
129
- # recommended_content = f"""
130
- # ### ** Recommended Content **
131
- # {user['recommendation_info']}
132
- # """
133
 
134
  prompt = f"""
135
-
136
- ### Context:
137
- We created a personalized push notification message based on available information.
138
- Your job is to check the message and correct only if it violates rules. Otherwise, leave it unchanged.
139
-
140
 
141
- ### Original JSON Message:
142
- {message}
143
 
144
- ### Rules:
145
- {self.get_general_rules()}
146
-
147
- ### Output Requirements:
148
- {self.output_instruction()}
149
- """
150
 
 
 
 
151
  return prompt
152
 
153
  # --------------------------------------------------------------
 
2
  protection layer on top of the messaging system to make sure the messages are as expected.
3
  """
4
 
 
 
 
 
 
5
  from Messaging_system.LLM import LLM
6
 
7
 
 
24
  }
25
 
26
  # --------------------------------------------------------------
27
+ # ----------------------------------------------------------------------
28
+ def llm_instructions(self) -> str:
29
  """
30
+ System-level directions for the *second-pass* LLM that either approves
31
+ or fixes a push-notification draft produced earlier.
32
  """
33
 
34
  jargon_list = "\n".join(f"- {word}" for word in self.Core.config_file["AI_Jargon"])
35
 
36
+ return f"""
37
+ You are a friendly copy-writer. **Approve the candidate JSON as-is, or
38
+ return a corrected version that obeys every rule below.**
39
+
40
+ ABSOLUTE RULES (override everything else)
41
+ Output **only** valid JSON with exactly two keys: "header" and "message".
42
+ Capitalize the **first** word in each value.
43
+ Keep the original if it already passes every rule.
44
+
45
+ STYLE
46
+ • Sound like everyday speech: casual, friendly, concise.
47
+ No greetings or sign-offs.
48
+
49
+ JARGON / BANNED CONTENT
50
+ Never use any of these words (case-insensitive, all forms):
51
+ {jargon_list}
52
+
53
+ Never use or paraphrase the following phrases (Voice ≠ instrument):
54
+ - Your voice is waiting
55
+ - Your voice awaits
56
+ - Your voice needs you
57
+ - Your voice is calling
58
+ - Your voice deserves more
59
+ - Hit the high notes
60
+ """
61
 
62
+ # ----------------------------------------------------------------------
63
+ def get_general_rules(self) -> str:
64
  """
65
+ Validation rules applied to both 'header' and 'message'.
66
  """
67
 
68
+ return """
69
+ - No two consecutive sentences may both end with '!'. Change one to '.'.
70
+ - Begin directly with content—no greetings or closings.
71
+ - Capitalize the first word and any proper noun.
72
+ - Fix any grammar or spelling errors.
73
+ - Remove words that imply recency (e.g. “new”, “latest”, “upcoming”).
74
+ - Would a friendly musician casually say such message? If not, rewrite.
75
+ - Preserve the exact JSON structure: {"header":"...", "message":"..."}.
76
+ - If no rule is violated, return the JSON unchanged.
 
77
  """
78
 
79
+ # ----------------------------------------------------------------------
80
+ def output_instruction(self) -> str:
81
  """
82
+ Explicit output contract (shown last so it’s freshest in token memory).
83
  """
84
+ return """
85
+ **Return ONLY JSON, nothing else**
86
+
87
+ {
88
+ "header": "Header text here",
89
+ "message": "Message text here"
90
+ }
91
+
92
+ Constraints
93
+ - "header" ≤ 30 characters (including spaces & punctuation)
94
+ - "message" 100 characters
95
+ - Do NOT add, remove, or rename keys.
96
+ """
 
 
 
97
 
98
+ # ----------------------------------------------------------------------
99
+ def get_context(self) -> str:
 
100
  """
101
+ High-level context for the LLM.
 
102
  """
103
+ return (
104
+ "We generated a personalized push-notification. "
105
+ "Please check it against the rules and fix only what is necessary."
 
106
  )
 
107
 
108
+ # ----------------------------------------------------------------------
109
+ def generate_prompt(self, message: str, user: dict) -> str:
110
  """
111
+ Combine all pieces into the final prompt sent to the validator LLM.
 
 
 
112
  """
 
 
 
 
 
 
113
 
114
  prompt = f"""
115
+ ### Context
116
+ {self.get_context()}
 
 
 
117
 
118
+ ### Original JSON
119
+ {message}
120
 
121
+ ### Rules
122
+ {self.get_general_rules()}
 
 
 
 
123
 
124
+ ### Output Contract
125
+ {self.output_instruction()}
126
+ """
127
  return prompt
128
 
129
  # --------------------------------------------------------------
requirements.txt CHANGED
Binary files a/requirements.txt and b/requirements.txt differ