ladybug11 commited on
Commit
cdc4967
·
1 Parent(s): 5b510b1
Files changed (1) hide show
  1. quote_generator_gemini.py +160 -135
quote_generator_gemini.py CHANGED
@@ -8,16 +8,18 @@ import json
8
  import time
9
  from typing import List, Optional
10
 
 
11
  class QuoteGenerator:
12
  """
13
  Gemini-powered quote generator with built-in variety tracking.
14
- Prevents repetitive quotes by maintaining history in persistent storage.
 
15
  """
16
-
17
  def __init__(self, api_key: Optional[str] = None, history_file: str = "/data/quote_history.json"):
18
  """
19
  Initialize with Gemini API.
20
-
21
  Args:
22
  api_key: Gemini API key (defaults to GEMINI_API_KEY env var)
23
  history_file: Path to persistent quote history storage
@@ -25,33 +27,34 @@ class QuoteGenerator:
25
  api_key = api_key or os.getenv("GEMINI_API_KEY")
26
  if not api_key:
27
  raise ValueError("GEMINI_API_KEY not found in environment variables")
28
-
29
  genai.configure(api_key=api_key)
30
- self.model = genai.GenerativeModel('gemini-1.5-flash') # Updated model name
31
-
 
32
  # Persistent storage for quote history - PER NICHE
33
  self.history_file = history_file
34
- self.quotes_by_niche = self._load_history() # Returns dict
35
  self.max_history = 100 # Keep last 100 quotes PER NICHE
36
-
37
  def _load_history(self) -> dict:
38
  """Load quote history from persistent storage - organized by niche"""
39
  try:
40
  if os.path.exists(self.history_file):
41
- with open(self.history_file, 'r') as f:
42
  data = json.load(f)
43
  # Support both old format (list) and new format (dict)
44
- if 'quotes_by_niche' in data:
45
  print(f"📖 Loaded history for {len(data['quotes_by_niche'])} niches")
46
- return data['quotes_by_niche']
47
- elif 'quotes' in data:
48
- # Old format - migrate
49
- print("📖 Migrating to per-niche tracking")
50
- return {"General": data['quotes']}
51
  except Exception as e:
52
  print(f"Could not load history: {e}")
53
  return {}
54
-
55
  def _save_history(self):
56
  """Save quote history to persistent storage"""
57
  try:
@@ -60,125 +63,142 @@ class QuoteGenerator:
60
  trimmed_data = {}
61
  for niche, quotes in self.quotes_by_niche.items():
62
  trimmed_data[niche] = quotes[-self.max_history:]
63
-
64
- with open(self.history_file, 'w') as f:
65
- json.dump({'quotes_by_niche': trimmed_data}, f, indent=2)
66
  except Exception as e:
67
  print(f"Could not save history: {e}")
68
-
69
  def generate_quote(self, niche: str, style: str) -> str:
70
  """
71
- Generate a unique quote using Gemini with variety enforcement.
72
-
73
  Args:
74
  niche: Quote category (Motivation, Business, etc.)
75
  style: Visual style (Cinematic, Nature, etc.)
76
-
77
  Returns:
78
- A unique quote string
79
  """
80
-
81
- # Get this niche's history
82
  if niche not in self.quotes_by_niche:
83
  self.quotes_by_niche[niche] = []
84
-
85
  recent_quotes = self.quotes_by_niche[niche]
86
-
87
  # Get recent quotes to avoid (last 30 for better variety)
88
  recent_quotes_text = ""
89
  if recent_quotes:
90
- recent_quotes_text = f"\n\n❌ PREVIOUSLY GENERATED {niche.upper()} QUOTES (YOU MUST AVOID THESE - DO NOT REPEAT OR PARAPHRASE):\n"
91
- for i, quote in enumerate(recent_quotes[-30:], 1): # Last 30
 
 
 
92
  recent_quotes_text += f"{i}. {quote}\n"
93
-
94
- print(f"📊 {niche} history: {len(recent_quotes)} total quotes, showing last 30 to avoid")
 
 
 
95
  else:
96
  print(f"📊 No {niche} history yet - generating first quote for this niche")
97
-
98
- # Build prompt with VERY strong variety instructions
99
- prompt = f"""Generate a COMPLETELY UNIQUE and powerful {niche} quote suitable for an Instagram/TikTok video.
100
 
101
- Visual Style: {style}
 
 
102
 
103
- CRITICAL REQUIREMENTS - READ CAREFULLY:
104
- - 2-4 sentences maximum (can be longer if deeply meaningful)
105
- - Must be RADICALLY DIFFERENT from all previously generated quotes below
106
- - Inspirational and impactful
107
- - Deep and meaningful insights
108
- - Fresh perspective that viewers haven't heard before
109
- - Unexpected wisdom or unique angle
110
- - DO NOT use clichés or overused phrases
111
- - AVOID common motivational phrases like "success is", "believe in yourself", "the only limit"
112
 
113
- {recent_quotes_text}
 
 
 
114
 
115
- IMPORTANT: Your quote must be COMPLETELY ORIGINAL in:
116
- - Core message and theme (different life area or concept)
117
- - Wording and phrasing (no similar sentences)
118
- - Perspective and angle (unique viewpoint)
119
- - Examples or metaphors used (creative analogies)
120
 
121
- Generate something viewers have NEVER seen before.
 
 
 
122
 
123
- Return ONLY the quote text, nothing else. No quotation marks, no attribution."""
 
 
 
 
 
 
 
124
 
125
  try:
126
- # Add timestamp-based variation
127
- seed_value = int(time.time() * 1000) % 10000
128
-
129
- # Generate with high temperature for maximum variety
130
  response = self.model.generate_content(
131
  prompt,
132
  generation_config={
133
- "temperature": 1.0, # Maximum creativity
134
- "top_p": 0.95,
135
- "top_k": 64,
136
- "max_output_tokens": 200,
137
- }
138
  )
139
-
140
  quote = response.text.strip()
141
  quote = quote.strip('"').strip("'").strip()
142
-
143
- # Check if this quote already exists in THIS niche
 
 
 
 
 
144
  if quote in recent_quotes:
145
- print(f"⚠️ WARNING: Generated duplicate {niche} quote! Trying again with higher temperature...")
146
- # Try one more time with even higher temperature
147
- response = self.model.generate_content(
148
  prompt,
149
  generation_config={
150
- "temperature": 1.2, # Even higher
151
- "top_p": 0.98,
152
- "top_k": 80,
153
- "max_output_tokens": 200,
154
- }
155
  )
156
- quote = response.text.strip().strip('"').strip("'").strip()
157
-
158
- # Add to this niche's history and save
 
 
 
 
159
  self.quotes_by_niche[niche].append(quote)
160
  self._save_history()
161
-
162
- print(f"✅ Generated unique {niche} quote #{len(self.quotes_by_niche[niche])}")
163
- print(f"💾 Saved to: {self.history_file}")
164
-
165
  return quote
166
-
167
  except Exception as e:
168
  raise Exception(f"Gemini quote generation failed: {str(e)}")
169
-
170
  def get_stats(self) -> dict:
171
  """Get statistics about quote generation - per niche"""
172
  total_quotes = sum(len(quotes) for quotes in self.quotes_by_niche.values())
173
  return {
174
  "total_quotes_generated": total_quotes,
175
- "quotes_by_niche": {niche: len(quotes) for niche, quotes in self.quotes_by_niche.items()},
176
- "niches_tracked": len(self.quotes_by_niche)
 
 
177
  }
178
-
179
  def clear_history(self, niche: Optional[str] = None):
180
- """Clear quote history (use with caution)
181
-
 
182
  Args:
183
  niche: If provided, clear only this niche. Otherwise clear all.
184
  """
@@ -194,19 +214,18 @@ Return ONLY the quote text, nothing else. No quotation marks, no attribution."""
194
  class HybridQuoteGenerator:
195
  """
196
  Hybrid system using Gemini as primary, OpenAI as fallback.
197
- Maximizes prize eligibility while ensuring reliability.
198
  """
199
-
200
- def __init__(self, gemini_key: Optional[str] = None, openai_client = None):
201
  """
202
  Initialize hybrid generator.
203
-
204
  Args:
205
  gemini_key: Gemini API key
206
  openai_client: OpenAI client instance (for fallback)
207
  """
208
  self.openai_client = openai_client
209
-
210
  try:
211
  self.gemini_generator = QuoteGenerator(api_key=gemini_key)
212
  self.gemini_available = True
@@ -214,20 +233,19 @@ class HybridQuoteGenerator:
214
  except Exception as e:
215
  self.gemini_available = False
216
  print(f"⚠️ Gemini not available: {e}")
217
-
218
  def generate_quote(self, niche: str, style: str, prefer_gemini: bool = True) -> dict:
219
  """
220
  Generate quote with automatic fallback.
221
-
222
  Args:
223
  niche: Quote category
224
  style: Visual style
225
  prefer_gemini: Try Gemini first if True
226
-
227
  Returns:
228
- Dict with quote, source, and metadata
229
  """
230
-
231
  # Try Gemini first
232
  if prefer_gemini and self.gemini_available:
233
  try:
@@ -237,11 +255,11 @@ class HybridQuoteGenerator:
237
  "quote": quote,
238
  "source": "gemini",
239
  "stats": stats,
240
- "success": True
241
  }
242
  except Exception as e:
243
  print(f"⚠️ Gemini failed, falling back to OpenAI: {e}")
244
-
245
  # Fallback to OpenAI
246
  if self.openai_client:
247
  try:
@@ -250,53 +268,64 @@ class HybridQuoteGenerator:
250
  "quote": quote,
251
  "source": "openai",
252
  "stats": None,
253
- "success": True
254
  }
255
  except Exception as e:
256
  return {
257
  "quote": None,
258
  "source": None,
259
  "error": f"Both generators failed: {str(e)}",
260
- "success": False
261
  }
262
-
263
  return {
264
  "quote": None,
265
  "source": None,
266
  "error": "No generator available",
267
- "success": False
268
  }
269
-
270
  def _generate_openai(self, niche: str, style: str) -> str:
271
- """OpenAI fallback generator"""
272
- prompt = f"""Generate a UNIQUE and powerful {niche} quote suitable for an Instagram/TikTok video.
 
273
 
274
- Style: {style}
275
 
276
  Requirements:
277
- - 2-4 sentences (can be longer)
278
- - Inspirational and impactful
279
- - Deep and meaningful
280
- - Should resonate deeply with viewers
281
- - Must be DIFFERENT from typical motivational quotes
282
- - Add unexpected wisdom or fresh perspective
283
-
284
- Return ONLY the quote text, nothing else."""
285
-
286
  seed = int(time.time() * 1000) % 1000
287
-
288
  response = self.openai_client.chat.completions.create(
289
  model="gpt-4o-mini",
290
  messages=[
291
- {"role": "system", "content": f"You are a quote generator. Seed: {seed}"},
292
- {"role": "user", "content": prompt}
 
 
 
293
  ],
294
- max_tokens=150,
295
- temperature=0.9
296
  )
297
-
298
  quote = response.choices[0].message.content.strip()
299
- return quote.strip('"').strip("'")
 
 
 
 
 
 
 
300
 
301
 
302
  # Integration function for smolagents tool
@@ -307,32 +336,28 @@ def create_hybrid_generator(openai_client):
307
  """
308
  return HybridQuoteGenerator(
309
  gemini_key=os.getenv("GEMINI_API_KEY"),
310
- openai_client=openai_client
311
  )
312
 
313
 
314
  # Example usage and testing
315
  if __name__ == "__main__":
316
- # Test Gemini generator
317
- print("Testing Gemini Quote Generator with Variety Tracking\n")
318
- print("="*60)
319
-
320
  try:
321
  generator = QuoteGenerator()
322
-
323
- # Generate 5 quotes to show variety
324
- print("\nGenerating 5 quotes about Motivation/Cinematic:\n")
325
  for i in range(5):
326
  quote = generator.generate_quote("Motivation", "Cinematic")
327
  print(f"{i+1}. {quote}\n")
328
-
329
- # Show stats
330
  stats = generator.get_stats()
331
  print("\nStats:")
332
  print(f" Total generated: {stats['total_quotes_generated']}")
333
  print(f" Niches tracked: {stats['niches_tracked']}")
334
  print(f" Per niche: {stats['quotes_by_niche']}")
335
-
336
  except Exception as e:
337
  print(f"Error: {e}")
338
- print("\nMake sure GEMINI_API_KEY is set in environment variables")
 
8
  import time
9
  from typing import List, Optional
10
 
11
+
12
  class QuoteGenerator:
13
  """
14
  Gemini-powered quote generator with built-in variety tracking.
15
+ Prevents repetitive quotes by maintaining history in persistent storage,
16
+ tracked per niche.
17
  """
18
+
19
  def __init__(self, api_key: Optional[str] = None, history_file: str = "/data/quote_history.json"):
20
  """
21
  Initialize with Gemini API.
22
+
23
  Args:
24
  api_key: Gemini API key (defaults to GEMINI_API_KEY env var)
25
  history_file: Path to persistent quote history storage
 
27
  api_key = api_key or os.getenv("GEMINI_API_KEY")
28
  if not api_key:
29
  raise ValueError("GEMINI_API_KEY not found in environment variables")
30
+
31
  genai.configure(api_key=api_key)
32
+ # You can switch to "gemini-1.5-pro" if you want higher quality
33
+ self.model = genai.GenerativeModel("gemini-1.5-flash")
34
+
35
  # Persistent storage for quote history - PER NICHE
36
  self.history_file = history_file
37
+ self.quotes_by_niche = self._load_history() # dict: niche -> list[str]
38
  self.max_history = 100 # Keep last 100 quotes PER NICHE
39
+
40
  def _load_history(self) -> dict:
41
  """Load quote history from persistent storage - organized by niche"""
42
  try:
43
  if os.path.exists(self.history_file):
44
+ with open(self.history_file, "r") as f:
45
  data = json.load(f)
46
  # Support both old format (list) and new format (dict)
47
+ if "quotes_by_niche" in data:
48
  print(f"📖 Loaded history for {len(data['quotes_by_niche'])} niches")
49
+ return data["quotes_by_niche"]
50
+ elif "quotes" in data:
51
+ # Old format - migrate to a default niche
52
+ print("📖 Migrating to per-niche tracking (General)")
53
+ return {"General": data["quotes"]}
54
  except Exception as e:
55
  print(f"Could not load history: {e}")
56
  return {}
57
+
58
  def _save_history(self):
59
  """Save quote history to persistent storage"""
60
  try:
 
63
  trimmed_data = {}
64
  for niche, quotes in self.quotes_by_niche.items():
65
  trimmed_data[niche] = quotes[-self.max_history:]
66
+
67
+ with open(self.history_file, "w") as f:
68
+ json.dump({"quotes_by_niche": trimmed_data}, f, indent=2)
69
  except Exception as e:
70
  print(f"Could not save history: {e}")
71
+
72
  def generate_quote(self, niche: str, style: str) -> str:
73
  """
74
+ Generate a unique, SHORT quote using Gemini with variety enforcement.
75
+
76
  Args:
77
  niche: Quote category (Motivation, Business, etc.)
78
  style: Visual style (Cinematic, Nature, etc.)
79
+
80
  Returns:
81
+ A unique quote string.
82
  """
83
+ # Ensure niche bucket exists
 
84
  if niche not in self.quotes_by_niche:
85
  self.quotes_by_niche[niche] = []
86
+
87
  recent_quotes = self.quotes_by_niche[niche]
88
+
89
  # Get recent quotes to avoid (last 30 for better variety)
90
  recent_quotes_text = ""
91
  if recent_quotes:
92
+ recent_quotes_text = (
93
+ f"\n\nPREVIOUSLY GENERATED {niche.upper()} QUOTES "
94
+ f"(DO NOT REPEAT OR PARAPHRASE ANY OF THESE):\n"
95
+ )
96
+ for i, quote in enumerate(recent_quotes[-30:], 1):
97
  recent_quotes_text += f"{i}. {quote}\n"
98
+
99
+ print(
100
+ f"📊 {niche} history: {len(recent_quotes)} total quotes, "
101
+ f"showing last {min(30, len(recent_quotes))} to avoid"
102
+ )
103
  else:
104
  print(f"📊 No {niche} history yet - generating first quote for this niche")
 
 
 
105
 
106
+ # Build prompt with strong variety + length control
107
+ prompt = f"""
108
+ Generate a COMPLETELY UNIQUE and SHORT {niche} quote suitable for an Instagram/TikTok video overlay.
109
 
110
+ Visual style / mood context: {style}
 
 
 
 
 
 
 
 
111
 
112
+ STRICT FORMAT:
113
+ - 1–2 short sentences ONLY (NO paragraphs)
114
+ - Maximum 22 words total
115
+ - The quote must be self-contained and readable in one quick glance on screen
116
 
117
+ STYLE REQUIREMENTS:
118
+ - Sharp, specific, and emotionally or philosophically strong
119
+ - NO clichés (avoid things like "believe in yourself", "follow your dreams", "the only limit", "trust the process")
120
+ - NO generic motivational filler, no long poetic rambling
121
+ - Avoid starting with bland openers like "Sometimes", "In life", "When you", "If you", "Success is"
122
 
123
+ VARIETY REQUIREMENTS:
124
+ - Must be RADICALLY DIFFERENT from all previously generated quotes below
125
+ - Do NOT reuse the same metaphors, structure, or idea as earlier quotes
126
+ - New angle, new imagery, new insight
127
 
128
+ {recent_quotes_text}
129
+
130
+ Return ONLY the quote text, nothing else:
131
+ - No quotation marks
132
+ - No author name
133
+ - No emojis
134
+ - No extra commentary
135
+ """
136
 
137
  try:
138
+ # Generate with moderate temperature and low token limit to keep it tight
 
 
 
139
  response = self.model.generate_content(
140
  prompt,
141
  generation_config={
142
+ "temperature": 0.7,
143
+ "top_p": 0.9,
144
+ "top_k": 40,
145
+ "max_output_tokens": 60, # keeps it short
146
+ },
147
  )
148
+
149
  quote = response.text.strip()
150
  quote = quote.strip('"').strip("'").strip()
151
+
152
+ # If for some reason it's still long, hard-trim by words
153
+ words = quote.split()
154
+ if len(words) > 22:
155
+ quote = " ".join(words[:22])
156
+
157
+ # Check if this exact quote already exists in THIS niche
158
  if quote in recent_quotes:
159
+ print(f"⚠️ WARNING: Generated duplicate {niche} quote! Retrying once with higher temperature...")
160
+ retry_response = self.model.generate_content(
 
161
  prompt,
162
  generation_config={
163
+ "temperature": 0.9,
164
+ "top_p": 0.95,
165
+ "top_k": 60,
166
+ "max_output_tokens": 60,
167
+ },
168
  )
169
+ quote_retry = retry_response.text.strip().strip('"').strip("'").strip()
170
+ words_retry = quote_retry.split()
171
+ if len(words_retry) > 22:
172
+ quote_retry = " ".join(words_retry[:22])
173
+ quote = quote_retry
174
+
175
+ # Store in this niche's history and persist
176
  self.quotes_by_niche[niche].append(quote)
177
  self._save_history()
178
+
179
+ print(f"✅ Generated {niche} quote #{len(self.quotes_by_niche[niche])}")
180
+ print(f"💾 History file: {self.history_file}")
181
+
182
  return quote
183
+
184
  except Exception as e:
185
  raise Exception(f"Gemini quote generation failed: {str(e)}")
186
+
187
  def get_stats(self) -> dict:
188
  """Get statistics about quote generation - per niche"""
189
  total_quotes = sum(len(quotes) for quotes in self.quotes_by_niche.values())
190
  return {
191
  "total_quotes_generated": total_quotes,
192
+ "quotes_by_niche": {
193
+ niche: len(quotes) for niche, quotes in self.quotes_by_niche.items()
194
+ },
195
+ "niches_tracked": len(self.quotes_by_niche),
196
  }
197
+
198
  def clear_history(self, niche: Optional[str] = None):
199
+ """
200
+ Clear quote history (use with caution).
201
+
202
  Args:
203
  niche: If provided, clear only this niche. Otherwise clear all.
204
  """
 
214
  class HybridQuoteGenerator:
215
  """
216
  Hybrid system using Gemini as primary, OpenAI as fallback.
 
217
  """
218
+
219
+ def __init__(self, gemini_key: Optional[str] = None, openai_client=None):
220
  """
221
  Initialize hybrid generator.
222
+
223
  Args:
224
  gemini_key: Gemini API key
225
  openai_client: OpenAI client instance (for fallback)
226
  """
227
  self.openai_client = openai_client
228
+
229
  try:
230
  self.gemini_generator = QuoteGenerator(api_key=gemini_key)
231
  self.gemini_available = True
 
233
  except Exception as e:
234
  self.gemini_available = False
235
  print(f"⚠️ Gemini not available: {e}")
236
+
237
  def generate_quote(self, niche: str, style: str, prefer_gemini: bool = True) -> dict:
238
  """
239
  Generate quote with automatic fallback.
240
+
241
  Args:
242
  niche: Quote category
243
  style: Visual style
244
  prefer_gemini: Try Gemini first if True
245
+
246
  Returns:
247
+ Dict with quote, source, and metadata.
248
  """
 
249
  # Try Gemini first
250
  if prefer_gemini and self.gemini_available:
251
  try:
 
255
  "quote": quote,
256
  "source": "gemini",
257
  "stats": stats,
258
+ "success": True,
259
  }
260
  except Exception as e:
261
  print(f"⚠️ Gemini failed, falling back to OpenAI: {e}")
262
+
263
  # Fallback to OpenAI
264
  if self.openai_client:
265
  try:
 
268
  "quote": quote,
269
  "source": "openai",
270
  "stats": None,
271
+ "success": True,
272
  }
273
  except Exception as e:
274
  return {
275
  "quote": None,
276
  "source": None,
277
  "error": f"Both generators failed: {str(e)}",
278
+ "success": False,
279
  }
280
+
281
  return {
282
  "quote": None,
283
  "source": None,
284
  "error": "No generator available",
285
+ "success": False,
286
  }
287
+
288
  def _generate_openai(self, niche: str, style: str) -> str:
289
+ """OpenAI fallback generator (short quote version)"""
290
+ prompt = f"""
291
+ Generate a UNIQUE, SHORT {niche} quote for an Instagram/TikTok video overlay.
292
 
293
+ Style / mood: {style}
294
 
295
  Requirements:
296
+ - 1–2 short sentences ONLY
297
+ - Maximum 22 words
298
+ - Inspirational or insightful, but NOT generic
299
+ - Avoid clichés like "believe in yourself", "follow your dreams", "the only limit", "trust the process"
300
+ - No paragraphs, no long explanations
301
+
302
+ Return ONLY the quote text, nothing else (no quotes, no author, no emojis).
303
+ """
304
+
305
  seed = int(time.time() * 1000) % 1000
306
+
307
  response = self.openai_client.chat.completions.create(
308
  model="gpt-4o-mini",
309
  messages=[
310
+ {
311
+ "role": "system",
312
+ "content": f"You are a concise, original quote generator. Seed: {seed}",
313
+ },
314
+ {"role": "user", "content": prompt},
315
  ],
316
+ max_tokens=60,
317
+ temperature=0.7,
318
  )
319
+
320
  quote = response.choices[0].message.content.strip()
321
+ quote = quote.strip('"').strip("'").strip()
322
+
323
+ # Hard trim in case model gets wordy
324
+ words = quote.split()
325
+ if len(words) > 22:
326
+ quote = " ".join(words[:22])
327
+
328
+ return quote
329
 
330
 
331
  # Integration function for smolagents tool
 
336
  """
337
  return HybridQuoteGenerator(
338
  gemini_key=os.getenv("GEMINI_API_KEY"),
339
+ openai_client=openai_client,
340
  )
341
 
342
 
343
  # Example usage and testing
344
  if __name__ == "__main__":
345
+ print("Testing Gemini Quote Generator with Variety Tracking (short quotes)\n")
346
+ print("=" * 60)
347
+
 
348
  try:
349
  generator = QuoteGenerator()
350
+
351
+ print("\nGenerating 5 short quotes about Motivation/Cinematic:\n")
 
352
  for i in range(5):
353
  quote = generator.generate_quote("Motivation", "Cinematic")
354
  print(f"{i+1}. {quote}\n")
355
+
 
356
  stats = generator.get_stats()
357
  print("\nStats:")
358
  print(f" Total generated: {stats['total_quotes_generated']}")
359
  print(f" Niches tracked: {stats['niches_tracked']}")
360
  print(f" Per niche: {stats['quotes_by_niche']}")
 
361
  except Exception as e:
362
  print(f"Error: {e}")
363
+ print("\nMake sure GEMINI_API_KEY is set in environment variables")