ganesh-vilje commited on
Commit
dd547c8
·
1 Parent(s): b1206db

Implement Gemini-based automatic chat name generation

Browse files

- Replace Bedrock chat naming with Gemini model
- Add simple message detection (greetings use timestamp)
- Creative naming for meaningful messages using Gemini
- Auto-trigger after first user message
- Fallback to timestamp on Gemini API errors

Files changed (1) hide show
  1. api_routes_v2.py +100 -70
api_routes_v2.py CHANGED
@@ -353,80 +353,94 @@ def _record_model_attribution(
353
 
354
  ## helpers for presigned url chat name and some more updates
355
 
356
- def _resolve_bedrock_model_for_titles(session: Dict[str, Any]) -> str:
357
  """
358
- CHANGE: NEW helper.
359
- Use the same model as other tasks if available (session.proposed_pipeline._model).
360
- Fallback to env, then a sane default.
361
  """
362
- try:
363
- model = (session.get("proposed_pipeline") or {}).get("_model")
364
- except Exception:
365
- model = None
366
- if not model:
367
- model = os.getenv("BEDROCK_MODEL_ID") or os.getenv("BEDROCK_DEFAULT_MODEL") or "mistral.mistral-large-2402-v1:0"
368
- return model
 
 
 
369
 
370
- def _bedrock_invoke_title(model_id: str, prompt_text: str) -> str:
371
  """
372
- CHANGE: NEW helper.
373
- Minimal Bedrock invocation for Anthropic/Titan models to produce a short title.
374
- If invocation fails, returns 'New Chat'.
375
  """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
376
  try:
377
- bedrock_runtime = boto3.client("bedrock-runtime", region_name=AWS_REGION)
378
- if model_id.startswith("anthropic."):
379
- # Anthropic Messages on Bedrock
380
- body = {
381
- "anthropic_version": "bedrock-2023-05-31",
382
- "max_tokens": 48,
383
- "temperature": 0.2,
384
- "messages": [{"role": "user", "content": [{"type": "text", "text": prompt_text}]}],
385
- }
386
- resp = bedrock_runtime.invoke_model(
387
- modelId=model_id,
388
- accept="application/json",
389
- contentType="application/json",
390
- body=json.dumps(body),
391
- )
392
- payload = json.loads(resp["body"].read())
393
- text = ""
394
- if isinstance(payload.get("content"), list) and payload["content"]:
395
- part = payload["content"][0]
396
- if isinstance(part, dict):
397
- text = part.get("text") or ""
398
- return (text or "").strip().strip('"').strip() or "New Chat"
399
- else:
400
- # Titan text models (or similar)
401
- body = {
402
- "inputText": prompt_text,
403
- "textGenerationConfig": {"maxTokenCount": 64, "temperature": 0.2, "stopSequences": []},
404
- }
405
- resp = bedrock_runtime.invoke_model(
406
- modelId=model_id,
407
- accept="application/json",
408
- contentType="application/json",
409
- body=json.dumps(body),
410
- )
411
- payload = json.loads(resp["body"].read())
412
- results = payload.get("results") or []
413
- if results:
414
- return (results[0].get("outputText") or "").strip().strip('"').strip() or "New Chat"
415
- return "New Chat"
416
- except Exception:
417
- return "New Chat"
418
 
419
  def _maybe_generate_chat_name(chat_id: str):
420
  """
421
- CHANGE: NEW helper.
422
- Auto-generate a succinct chat title after the first real user message (not the 'Uploaded file:' stub).
423
- Uses the same Bedrock model as used elsewhere where possible.
424
  """
425
  try:
426
  s = session_manager.get_session(chat_id) or {}
 
 
427
  if s.get("chat_name"):
428
  return
429
- msgs = list(s.get("messages", []))
 
 
 
 
430
  first_user = None
431
  for m in msgs:
432
  if (m.get("role") or "") == "user":
@@ -434,26 +448,38 @@ def _maybe_generate_chat_name(chat_id: str):
434
  if not content.lower().startswith("uploaded file:"):
435
  first_user = content
436
  break
 
437
  if not first_user:
438
  return
 
 
439
  file_name = (s.get("file_metadata") or {}).get("file_name")
440
- prompt = (
441
- "Create a succinct, descriptive 3–6 word title for this chat session based on the first user message.\n"
442
- "Return only the title, without quotes.\n\n"
443
- f"First message: {first_user}\n"
444
- f"File name (optional): {file_name or 'N/A'}"
445
- )
446
- model_id = _resolve_bedrock_model_for_titles(s)
447
- title = _bedrock_invoke_title(model_id, prompt) or "New Chat"
 
 
 
 
448
  session_manager.update_session(
449
  chat_id,
450
  {
451
  "chat_name": title[:100],
452
  "chat_name_generated_at": datetime.utcnow().isoformat() + "Z",
453
- "chat_name_model": model_id,
454
  },
455
  )
456
- except Exception:
 
 
 
 
 
457
  pass
458
 
459
  def _generate_presigned_get_url(bucket: str, key: str, expires_in: int = 604800) -> Dict[str, str]:
@@ -585,6 +611,10 @@ def _add_and_mirror_message(chat_id: str, role: str, content: str, file_metadata
585
 
586
  # 3. Save to S3 (this updates MongoDB metadata too)
587
  _save_conversation_to_s3(chat_id, current_messages)
 
 
 
 
588
 
589
  def _assistant_response_payload(
590
  chat_id: str,
 
353
 
354
  ## helpers for presigned url chat name and some more updates
355
 
356
+ def _is_simple_message(message: str) -> bool:
357
  """
358
+ Check if message is a simple greeting or test message that should use timestamp naming.
 
 
359
  """
360
+ if not message or len(message.strip()) > 30:
361
+ return False
362
+
363
+ simple_patterns = [
364
+ "hello", "hi", "hey", "test", "testing", "hola", "bonjour",
365
+ "namaste", "greetings", "good morning", "good afternoon", "good evening"
366
+ ]
367
+
368
+ msg_lower = message.lower().strip()
369
+ return any(pattern in msg_lower for pattern in simple_patterns)
370
 
371
+ def _generate_chat_name_with_gemini(user_message: str, file_name: Optional[str] = None) -> str:
372
  """
373
+ Generate a creative chat name using Gemini model.
374
+ Returns generated name or falls back to timestamp on error.
 
375
  """
376
+ GEMINI_API_KEY = os.getenv("GEMINI_API_KEY") or os.getenv("GOOGLE_API_KEY")
377
+ GEMINI_MODEL = os.getenv("GEMINI_MODEL", "gemini-2.0-flash")
378
+ GEMINI_ENDPOINT = f"https://generativelanguage.googleapis.com/v1beta/models/{GEMINI_MODEL}:generateContent"
379
+
380
+ if not GEMINI_API_KEY:
381
+ # Fallback to timestamp if no API key
382
+ return f"Chat - {datetime.utcnow().strftime('%Y-%m-%d %H:%M')}"
383
+
384
+ # Build prompt
385
+ prompt = (
386
+ "Create a succinct, creative, and descriptive 3-6 word title for this chat session.\n"
387
+ "The title should capture the essence of what the user wants to do.\n"
388
+ "Return ONLY the title, without quotes or extra text.\n\n"
389
+ f"User's first message: {user_message}\n"
390
+ )
391
+
392
+ if file_name:
393
+ prompt += f"File uploaded: {file_name}\n"
394
+
395
  try:
396
+ import requests
397
+ response = requests.post(
398
+ f"{GEMINI_ENDPOINT}?key={GEMINI_API_KEY}",
399
+ headers={"Content-Type": "application/json"},
400
+ json={
401
+ "contents": [{"parts": [{"text": prompt}]}],
402
+ "generationConfig": {
403
+ "temperature": 0.7,
404
+ "maxOutputTokens": 50,
405
+ }
406
+ },
407
+ timeout=5, # Short timeout to avoid blocking
408
+ )
409
+
410
+ response.raise_for_status()
411
+ result = response.json()
412
+
413
+ # Extract text from Gemini response
414
+ title = result["candidates"][0]["content"]["parts"][0]["text"]
415
+ title = title.strip().strip('"').strip("'").strip()
416
+
417
+ # Validate title length (should be reasonable)
418
+ if len(title) > 100:
419
+ title = title[:100]
420
+
421
+ return title or f"Chat - {datetime.utcnow().strftime('%Y-%m-%d %H:%M')}"
422
+
423
+ except Exception as e:
424
+ print(f"Gemini chat name generation failed: {e}")
425
+ # Fallback to timestamp
426
+ return f"Chat - {datetime.utcnow().strftime('%Y-%m-%d %H:%M')}"
 
 
 
 
 
 
 
 
 
 
427
 
428
  def _maybe_generate_chat_name(chat_id: str):
429
  """
430
+ Auto-generate a chat title after the first real user message.
431
+ Uses Gemini for creative naming, or timestamp for simple messages.
 
432
  """
433
  try:
434
  s = session_manager.get_session(chat_id) or {}
435
+
436
+ # Skip if chat name already exists
437
  if s.get("chat_name"):
438
  return
439
+
440
+ # Load messages from S3 (V3 architecture)
441
+ msgs = _load_conversation_from_s3(chat_id)
442
+
443
+ # Find first real user message (not file upload)
444
  first_user = None
445
  for m in msgs:
446
  if (m.get("role") or "") == "user":
 
448
  if not content.lower().startswith("uploaded file:"):
449
  first_user = content
450
  break
451
+
452
  if not first_user:
453
  return
454
+
455
+ # Get file name if available
456
  file_name = (s.get("file_metadata") or {}).get("file_name")
457
+
458
+ # Check if it's a simple message
459
+ if _is_simple_message(first_user):
460
+ # Use timestamp for simple greetings
461
+ title = f"Chat - {datetime.utcnow().strftime('%Y-%m-%d %H:%M')}"
462
+ model_used = "timestamp"
463
+ else:
464
+ # Use Gemini for creative naming
465
+ title = _generate_chat_name_with_gemini(first_user, file_name)
466
+ model_used = os.getenv("GEMINI_MODEL", "gemini-2.0-flash")
467
+
468
+ # Update session with chat name
469
  session_manager.update_session(
470
  chat_id,
471
  {
472
  "chat_name": title[:100],
473
  "chat_name_generated_at": datetime.utcnow().isoformat() + "Z",
474
+ "chat_name_model": model_used,
475
  },
476
  )
477
+
478
+ print(f"✅ Generated chat name for {chat_id}: '{title}' (using {model_used})")
479
+
480
+ except Exception as e:
481
+ print(f"Error generating chat name: {e}")
482
+ # Don't fail the request if chat naming fails
483
  pass
484
 
485
  def _generate_presigned_get_url(bucket: str, key: str, expires_in: int = 604800) -> Dict[str, str]:
 
611
 
612
  # 3. Save to S3 (this updates MongoDB metadata too)
613
  _save_conversation_to_s3(chat_id, current_messages)
614
+
615
+ # 4. Auto-generate chat name after first user message
616
+ if role == "user":
617
+ _maybe_generate_chat_name(chat_id)
618
 
619
  def _assistant_response_payload(
620
  chat_id: str,