Pygmales commited on
Commit
1e5fe91
·
1 Parent(s): 13ee704

updated project status

Browse files
Files changed (3) hide show
  1. src/rag/agent_chain.py +195 -20
  2. src/rag/models.py +1 -1
  3. src/rag/prompts.py +1 -1
src/rag/agent_chain.py CHANGED
@@ -28,7 +28,7 @@ from src.rag.models import ModelConfigurator as modelconf
28
  from src.rag.input_handler import InputHandler
29
  from src.rag.response_formatter import ResponseFormatter
30
  from src.rag.scope_guardian import ScopeGuardian
31
- from src.rag.quality_score_handler import QualityEvaluationResult, QualityScoreHandler
32
  from src.rag.language_detection import LanguageDetector
33
 
34
  from src.utils.logging import get_logger
@@ -49,9 +49,10 @@ class ExecutiveAgentChain:
49
  self._conversation_history = []
50
  self._cache = Cache.get_cache()
51
 
52
- # AI-middlewares
53
- if config.chain.EVALUATE_RESPONSE_QUALITY:
54
- self._quality_handler = QualityScoreHandler()
 
55
  self._language_detector = LanguageDetector()
56
 
57
  # Generate unique user ID for this session
@@ -340,6 +341,160 @@ class ExecutiveAgentChain:
340
 
341
  return False
342
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
343
  def _is_explicit_booking_intent(self, query: str) -> bool:
344
  """Detect whether the user is actively asking to book or accepting a booking offer."""
345
  query_lower = query.lower()
@@ -348,6 +503,8 @@ class ExecutiveAgentChain:
348
  "schedule",
349
  "appointment",
350
  "consultation",
 
 
351
  "speak with",
352
  "talk to an advisor",
353
  "talk to admissions",
@@ -360,6 +517,12 @@ class ExecutiveAgentChain:
360
  "termin vereinbaren",
361
  "beratungstermin",
362
  "beratungsgespräch",
 
 
 
 
 
 
363
  "mit jemandem sprechen",
364
  "mit admissions sprechen",
365
  "mit der zulassung sprechen",
@@ -724,6 +887,11 @@ class ExecutiveAgentChain:
724
 
725
  response_language = self._stored_language
726
  explicit_booking_intent = self._is_explicit_booking_intent(preprocessed_query)
 
 
 
 
 
727
 
728
  # 1. History Update
729
  self._conversation_history.append(HumanMessage(preprocessed_query))
@@ -752,18 +920,17 @@ class ExecutiveAgentChain:
752
 
753
  formatted_response = ResponseFormatter.clean_response(formatted_response)
754
 
755
- # Step 7: Language fallback mechanisms and response quality evaluation
756
  confidence_fallback = False
757
- if config.chain.EVALUATE_RESPONSE_QUALITY:
758
- quality_evaluation: QualityEvaluationResult = self._quality_handler. \
759
- evaluate_response_quality(preprocessed_query, formatted_response)
760
-
761
- chain_logger.info(f"Quality Score: {quality_evaluation.overall_score:1.2f}")
762
-
763
- if quality_evaluation.overall_score < config.chain.CONFIDENCE_THRESHOLD:
764
- confidence_fallback = True
765
- formatted_response = CONFIDENCE_FALLBACK_MESSAGE[response_language]
766
- chain_logger.info(f"Fallback Mechanism activated!")
767
 
768
  # Add to history
769
  self._conversation_history.append(AIMessage(formatted_response))
@@ -777,17 +944,25 @@ class ExecutiveAgentChain:
777
  self._log_user_profile()
778
 
779
  formatted_response = ResponseFormatter.format_name_of_university(formatted_response, language=response_language)
780
- appointment_requested = bool(explicit_booking_intent)
781
- show_booking_widget = bool(explicit_booking_intent)
 
 
 
 
 
 
782
 
783
- if structured_response.appointment_requested and not explicit_booking_intent:
784
- chain_logger.info("Suppressed booking widget because no explicit booking intent was detected.")
 
 
785
 
786
  return LeadAgentQueryResponse(
787
  response = formatted_response,
788
  language = response_language,
789
  confidence_fallback = confidence_fallback,
790
- should_cache = not any([confidence_fallback, appointment_requested, structured_response.is_context_dependent]),
791
  processed_query = preprocessed_query,
792
  appointment_requested = appointment_requested,
793
  show_booking_widget = show_booking_widget,
 
28
  from src.rag.input_handler import InputHandler
29
  from src.rag.response_formatter import ResponseFormatter
30
  from src.rag.scope_guardian import ScopeGuardian
31
+ # from src.rag.quality_score_handler import QualityEvaluationResult, QualityScoreHandler
32
  from src.rag.language_detection import LanguageDetector
33
 
34
  from src.utils.logging import get_logger
 
49
  self._conversation_history = []
50
  self._cache = Cache.get_cache()
51
 
52
+ # Confidence scoring is intentionally disabled here because the extra
53
+ # model call adds latency and has not been reliable enough to justify it.
54
+ # if config.chain.EVALUATE_RESPONSE_QUALITY:
55
+ # self._quality_handler = QualityScoreHandler()
56
  self._language_detector = LanguageDetector()
57
 
58
  # Generate unique user ID for this session
 
341
 
342
  return False
343
 
344
+ def _get_latest_ai_message_content(self, skip_latest: bool = False) -> str:
345
+ """Return the latest assistant message content from conversation history."""
346
+ ai_messages_seen = 0
347
+
348
+ for message in reversed(self._conversation_history):
349
+ if not isinstance(message, AIMessage):
350
+ continue
351
+
352
+ ai_messages_seen += 1
353
+ if skip_latest and ai_messages_seen == 1:
354
+ continue
355
+
356
+ content = getattr(message, "content", "") or getattr(message, "text", "")
357
+ if isinstance(content, list):
358
+ return " ".join(str(part) for part in content)
359
+ return str(content)
360
+
361
+ return ""
362
+
363
+ def _is_booking_preference_follow_up(self, query: str) -> bool:
364
+ """Detect short follow-up answers that continue an active booking flow."""
365
+ query_lower = query.lower().strip()
366
+ if not query_lower:
367
+ return False
368
+
369
+ preference_terms = [
370
+ "online",
371
+ "on-site",
372
+ "onsite",
373
+ "in person",
374
+ "in-person",
375
+ "st.gallen",
376
+ "st. gallen",
377
+ "morning",
378
+ "mornings",
379
+ "afternoon",
380
+ "afternoons",
381
+ "evening",
382
+ "beginning of the week",
383
+ "start of the week",
384
+ "end of the week",
385
+ "monday",
386
+ "tuesday",
387
+ "wednesday",
388
+ "thursday",
389
+ "friday",
390
+ "morgens",
391
+ "vormittag",
392
+ "vormittags",
393
+ "nachmittag",
394
+ "nachmittags",
395
+ "abends",
396
+ "wochenanfang",
397
+ "anfang der woche",
398
+ "ende der woche",
399
+ "montag",
400
+ "dienstag",
401
+ "mittwoch",
402
+ "donnerstag",
403
+ "freitag",
404
+ "vor ort",
405
+ "vor-ort",
406
+ "persönlich",
407
+ "persoenlich",
408
+ "hybrid",
409
+ ]
410
+
411
+ if any(term in query_lower for term in preference_terms):
412
+ return True
413
+
414
+ return False
415
+
416
+ def _previous_response_requested_booking_preferences(self) -> bool:
417
+ """Return True when the previous assistant turn asked clarifying booking questions."""
418
+ content_lower = self._get_latest_ai_message_content().lower()
419
+ if not content_lower:
420
+ return False
421
+
422
+ booking_context_terms = [
423
+ "appointment options",
424
+ "available appointments",
425
+ "available slots",
426
+ "appointment slots",
427
+ "online-terminoptionen",
428
+ "terminoptionen",
429
+ "verfügbare slots",
430
+ "verfügbare termine",
431
+ "beratungsgespräch",
432
+ "beratung",
433
+ ]
434
+ clarification_terms = [
435
+ "do you prefer",
436
+ "would you prefer",
437
+ "which programme",
438
+ "which program",
439
+ "one short question",
440
+ "final question",
441
+ "when i know this",
442
+ "bitte noch kurz",
443
+ "eine kurze rückfrage",
444
+ "eine kurze letzte frage",
445
+ "bevorzugen sie",
446
+ "haben sie eine tagespräferenz",
447
+ "sobald ich das weiss",
448
+ "damit die slots besser passen",
449
+ ]
450
+
451
+ return (
452
+ any(term in content_lower for term in booking_context_terms)
453
+ and any(term in content_lower for term in clarification_terms)
454
+ )
455
+
456
+ def _response_commits_to_showing_booking_widget(self, response: str) -> bool:
457
+ """Detect when the assistant says booking options are being shown now."""
458
+ response_lower = response.lower()
459
+
460
+ positive_terms = [
461
+ "i can show you",
462
+ "contact details and available appointment slots are shown below",
463
+ "appointment options are shown below",
464
+ "available slots are shown below",
465
+ "i can now show you",
466
+ "ich kann ihnen nun",
467
+ "ich kann ihnen jetzt",
468
+ "unten werden ihnen",
469
+ "unten finden sie",
470
+ "unten sehen sie",
471
+ "terminoptionen anzeigen",
472
+ "verfügbaren slots",
473
+ "verfügbaren termine",
474
+ ]
475
+ defer_terms = [
476
+ "if you would like",
477
+ "if you later wish",
478
+ "you can ask me",
479
+ "if that would be helpful",
480
+ "sobald ich das weiss",
481
+ "wenn ich das weiss",
482
+ "damit die slots besser passen",
483
+ "bitte noch kurz",
484
+ "eine kurze rückfrage",
485
+ "eine kurze letzte frage",
486
+ "bevorzugen sie",
487
+ "have you got a preference",
488
+ "do you prefer",
489
+ "would you prefer",
490
+ "which programme",
491
+ "which program",
492
+ ]
493
+
494
+ return (
495
+ any(term in response_lower for term in positive_terms)
496
+ and not any(term in response_lower for term in defer_terms)
497
+ )
498
  def _is_explicit_booking_intent(self, query: str) -> bool:
499
  """Detect whether the user is actively asking to book or accepting a booking offer."""
500
  query_lower = query.lower()
 
503
  "schedule",
504
  "appointment",
505
  "consultation",
506
+ "need a consultation",
507
+ "personal consultation",
508
  "speak with",
509
  "talk to an advisor",
510
  "talk to admissions",
 
517
  "termin vereinbaren",
518
  "beratungstermin",
519
  "beratungsgespräch",
520
+ "ich brauche eine beratung",
521
+ "ich möchte eine beratung",
522
+ "ich will eine beratung",
523
+ "beratung für",
524
+ "persönliche beratung",
525
+ "persoenliche beratung",
526
  "mit jemandem sprechen",
527
  "mit admissions sprechen",
528
  "mit der zulassung sprechen",
 
887
 
888
  response_language = self._stored_language
889
  explicit_booking_intent = self._is_explicit_booking_intent(preprocessed_query)
890
+ booking_preference_follow_up = (
891
+ self._conversation_state.get('handover_requested') is True
892
+ and self._previous_response_requested_booking_preferences()
893
+ and self._is_booking_preference_follow_up(preprocessed_query)
894
+ )
895
 
896
  # 1. History Update
897
  self._conversation_history.append(HumanMessage(preprocessed_query))
 
920
 
921
  formatted_response = ResponseFormatter.clean_response(formatted_response)
922
 
 
923
  confidence_fallback = False
924
+ # if config.chain.EVALUATE_RESPONSE_QUALITY:
925
+ # quality_evaluation: QualityEvaluationResult = self._quality_handler. \
926
+ # evaluate_response_quality(preprocessed_query, formatted_response)
927
+ #
928
+ # chain_logger.info(f"Quality Score: {quality_evaluation.overall_score:1.2f}")
929
+ #
930
+ # if quality_evaluation.overall_score < config.chain.CONFIDENCE_THRESHOLD:
931
+ # confidence_fallback = True
932
+ # formatted_response = CONFIDENCE_FALLBACK_MESSAGE[response_language]
933
+ # chain_logger.info("Fallback Mechanism activated!")
934
 
935
  # Add to history
936
  self._conversation_history.append(AIMessage(formatted_response))
 
944
  self._log_user_profile()
945
 
946
  formatted_response = ResponseFormatter.format_name_of_university(formatted_response, language=response_language)
947
+ booking_flow_requested = explicit_booking_intent or booking_preference_follow_up
948
+ appointment_requested = bool(booking_flow_requested)
949
+ show_booking_widget = bool(
950
+ booking_flow_requested and (
951
+ structured_response.show_booking_widget
952
+ or self._response_commits_to_showing_booking_widget(formatted_response)
953
+ )
954
+ )
955
 
956
+ if structured_response.appointment_requested and not booking_flow_requested:
957
+ chain_logger.info("Suppressed booking state because no user-led booking intent was detected.")
958
+ elif booking_preference_follow_up and show_booking_widget:
959
+ chain_logger.info("Continuing active booking flow and showing booking widget for a preference follow-up.")
960
 
961
  return LeadAgentQueryResponse(
962
  response = formatted_response,
963
  language = response_language,
964
  confidence_fallback = confidence_fallback,
965
+ should_cache = False if (confidence_fallback or appointment_requested or structured_response.is_context_dependent) else True,
966
  processed_query = preprocessed_query,
967
  appointment_requested = appointment_requested,
968
  show_booking_widget = show_booking_widget,
src/rag/models.py CHANGED
@@ -76,7 +76,7 @@ class ModelConfigurator:
76
 
77
  from langchain_openai import ChatOpenAI
78
  cls._subagent_model_instance = ChatOpenAI(
79
- model='gpt-5.1',
80
  openai_api_key=config.llm.get_api_key(),
81
  max_tokens=3072,
82
  temperature=0.01,
 
76
 
77
  from langchain_openai import ChatOpenAI
78
  cls._subagent_model_instance = ChatOpenAI(
79
+ model='gpt-5.1-instant',
80
  openai_api_key=config.llm.get_api_key(),
81
  max_tokens=3072,
82
  temperature=0.01,
src/rag/prompts.py CHANGED
@@ -300,7 +300,7 @@ RULES:
300
  - factual, static information (e.g. prices, durations, deadlines, program structure)
301
  - general definitions or explanations
302
  - publicly available information that does not vary by user
303
-
304
  RULES:
305
  - Answer in the user's language. NEVER leave English terms untranslated in a German response. Key German translations:
306
  "tuition fee reduction" → "Studiengebührenreduktion", "tuition" → "Studiengebühr(en)", "included in tuition" → "in den Studiengebühren enthalten", "not included" → "nicht enthalten", "application deadline" → "Bewerbungsfrist".
 
300
  - factual, static information (e.g. prices, durations, deadlines, program structure)
301
  - general definitions or explanations
302
  - publicly available information that does not vary by user
303
+
304
  RULES:
305
  - Answer in the user's language. NEVER leave English terms untranslated in a German response. Key German translations:
306
  "tuition fee reduction" → "Studiengebührenreduktion", "tuition" → "Studiengebühr(en)", "included in tuition" → "in den Studiengebühren enthalten", "not included" → "nicht enthalten", "application deadline" → "Bewerbungsfrist".