JirasakJo commited on
Commit
4e79282
·
verified ·
1 Parent(s): 84e3816

Update calendar_rag.py

Browse files
Files changed (1) hide show
  1. calendar_rag.py +434 -288
calendar_rag.py CHANGED
@@ -3,7 +3,7 @@ from haystack.components.generators.openai import OpenAIGenerator
3
  from haystack.components.builders import PromptBuilder
4
  from haystack.components.embedders import SentenceTransformersDocumentEmbedder
5
  from haystack.components.retrievers.in_memory import *
6
- from haystack.document_stores.in_memory import *
7
  from haystack.utils import Secret
8
  from pathlib import Path
9
  import hashlib
@@ -14,6 +14,7 @@ import json
14
  import logging
15
  import re
16
  import pickle
 
17
 
18
  # Setup logging
19
  logging.basicConfig(level=logging.INFO)
@@ -647,7 +648,6 @@ class CalendarDataProcessor:
647
  transportation=transportation
648
  ))
649
  except Exception as e:
650
- print(f"Error processing contact data: {e}")
651
  continue
652
 
653
  return contact_details
@@ -710,25 +710,25 @@ class CalendarDataProcessor:
710
  # Create course categories
711
  structure = {
712
  'หมวดวิชาปรับพื้นฐาน': CourseCategory( # Previously foundation_courses
713
- description=foundation_data.get('metadata', {}).get('description'),
714
  credits=foundation_data.get('metadata', {}).get('credits', 'non-credit'),
715
  minimum_credits=None,
716
  courses=foundation_courses
717
  ),
718
  'หมวดวิชาบังคับ': CourseCategory( # Previously core_courses
719
- description=None,
720
  credits=0,
721
  minimum_credits=core_data.get('minimum_requirement_credits'),
722
  courses=core_courses
723
  ),
724
  'หมวดวิชาเลือก': CourseCategory( # Previously elective_courses
725
- description=None,
726
  credits=0,
727
  minimum_credits=elective_data.get('minimum_requirement_credits'),
728
  courses=elective_courses
729
  ),
730
  'หมวดวิชาการค้นคว้าอิสระ': CourseCategory( # Previously research_courses
731
- description=None,
732
  credits=0,
733
  minimum_credits=research_data.get('minimum_requirement_credits'),
734
  courses=research_courses
@@ -877,33 +877,52 @@ class HybridDocumentStore:
877
  self.cache_manager.set_embedding_cache(text, embedding)
878
  return embedding
879
 
880
- def add_document(self, text: str, event_type: str):
881
- """Add a single document to the store"""
882
- try:
883
- # Compute embedding
884
- embedding = self._compute_embedding(text)
885
-
886
- # Create document with unique ID
887
- doc = Document(
888
- id=self._generate_unique_id(),
889
- content=text,
890
- embedding=embedding,
891
- meta={'event_type': event_type}
892
- )
893
-
894
- # Write document
895
- self.store.write_documents([doc])
896
-
897
- # Cache document
898
- self.cache_manager.set_document_cache(doc.id, doc)
899
-
900
- except Exception as e:
901
- logger.error(f"Error adding document: {str(e)}")
902
- raise
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
903
 
904
- def add_events(self, events: List[CalendarEvent], contact_details: Optional[List[ContactDetail]] = None,
905
- course_structure: Optional[List[CourseStructure]] = None,
906
- study_plans: Optional[List[StudyPlan]] = None):
 
 
 
 
907
  """Add events and additional data with caching"""
908
  documents = []
909
  added_events = set() # Track added events to prevent duplicates
@@ -976,7 +995,6 @@ class HybridDocumentStore:
976
  # Process course structure
977
  if course_structure:
978
  for course in course_structure:
979
- self.course_data.append(course)
980
  text = f"""
981
  โครงสร้างหลักสูตร:
982
  ชื่อหลักสูตร: {course.program_name}
@@ -985,23 +1003,33 @@ class HybridDocumentStore:
985
  ระดับการศึกษา: {course.degree_level}
986
 
987
  รายละเอียดโครงสร้าง:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
988
  """
989
- for category_name, category in course.structure.items():
990
- text += f"\n{category_name}:\n"
991
- if category.description:
992
- text += f"คำอธิบาย: {category.description}\n"
993
- text += f"หน่วยกิต: {category.credits}\n"
994
- if category.minimum_credits:
995
- text += f"หน่วยกิตขั้นต่ำ: {category.minimum_credits}\n"
996
- text += "รายวิชา:\n"
997
- for course_item in category.courses:
998
- text += f"- {course_item.code}: {course_item.title_th} ({course_item.title_en}) - {course_item.credits} หน่วยกิต\n"
999
 
1000
- embedding = self._compute_embedding(text)
1001
  doc = Document(
1002
  id=self._generate_unique_id(),
1003
- content=text,
1004
- embedding=embedding,
1005
  meta={'event_type': 'curriculum'}
1006
  )
1007
  documents.append(doc)
@@ -1010,23 +1038,107 @@ class HybridDocumentStore:
1010
  if study_plans:
1011
  for plan in study_plans:
1012
  self.study_plan_data.append(plan)
1013
- text = "แผนการศึกษา:\n"
1014
  for year, semesters in plan.years.items():
1015
- text += f"\nปีที่ {year}:\n"
1016
  for semester, data in semesters.items():
1017
- text += f"\n{semester}:\n"
1018
- if 'metadata' in data and data['metadata']:
1019
- text += f"ข้อมูลเพิ่มเติม: {json.dumps(data['metadata'], ensure_ascii=False)}\n"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1020
  if 'courses' in data:
1021
  for course in data['courses']:
1022
- text += f"- {course['code']}: {course['title'].get('th', '')} ({course['title'].get('en', '')}) - {course['credits']} หน่วยกิต\n"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1023
 
1024
- embedding = self._compute_embedding(text)
1025
  doc = Document(
1026
  id=self._generate_unique_id(),
1027
- content=text,
1028
- embedding=embedding,
1029
- meta={'event_type': 'study_plan'}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1030
  )
1031
  documents.append(doc)
1032
 
@@ -1045,10 +1157,12 @@ class HybridDocumentStore:
1045
 
1046
  def hybrid_search(self,
1047
  query: str,
1048
- event_type: Optional[str] = None,
1049
- semester: Optional[str] = None,
1050
- top_k: int = 10,
 
1051
  weight_semantic: float = 0.5) -> List[Document]:
 
1052
  """Hybrid search combining semantic and lexical search results"""
1053
 
1054
  cache_key = json.dumps({
@@ -1065,15 +1179,16 @@ class HybridDocumentStore:
1065
 
1066
  # Get semantic search results
1067
  query_embedding = self._compute_embedding(query)
1068
- semantic_results = self.embedding_retriever.run(
1069
- query_embedding=query_embedding
1070
- )["documents"]
1071
 
1072
  # Get BM25 results
1073
  bm25_results = self.bm25_retriever.run(
1074
  query=query
1075
  )["documents"]
1076
 
 
 
 
1077
  # Combine results using score fusion
1078
  combined_results = self._merge_results(
1079
  semantic_results=semantic_results,
@@ -1085,10 +1200,8 @@ class HybridDocumentStore:
1085
  # Filter results based on metadata
1086
  filtered_results = []
1087
  for doc in combined_results:
1088
- if event_type and doc.meta.get('event_type') != event_type:
1089
- continue
1090
- if semester and doc.meta.get('semester') != semester:
1091
- continue
1092
  filtered_results.append(doc)
1093
 
1094
  final_results = filtered_results[:top_k]
@@ -1139,80 +1252,6 @@ class HybridDocumentStore:
1139
  )
1140
 
1141
  return sorted_docs[:top_k]
1142
-
1143
- class AdvancedQueryProcessor:
1144
- """Process queries with better understanding"""
1145
-
1146
- def __init__(self, config: PipelineConfig):
1147
- self.generator = OpenAIGenerator(
1148
- api_key=Secret.from_token(config.model.openai_api_key),
1149
- model=config.model.openai_model
1150
- )
1151
- self.prompt_builder = PromptBuilder(
1152
- template="""
1153
- วิเคราะห์คำถามที่เกี่ยวข้องกับปฏิทินการศึกษา (ภาษาไทย):
1154
- คำถาม: {{query}}
1155
-
1156
- ระบุ:
1157
- 1. ประเภทของข้อมูลที่ต้องการค้นหา
1158
- 2. ภาคการศึกษาที่ระบุไว้ (ถ้ามี)
1159
- 3. คำสำคัญที่เกี่ยวข้อง
1160
-
1161
- ให้ผลลัพธ์ในรูปแบบ JSON:
1162
- {
1163
- "event_type": "ลงทะเบียน|กำหน���เวลา|การสอบ|วิชาการ|วันหยุด",
1164
- "semester": "ภาคการศึกษาที่ระบุ หรือ null",
1165
- "key_terms": ["คำสำคัญ 3 คำที่สำคัญที่สุด"],
1166
- "response_format": "รายการ|คำตอบเดียว|คำตอบละเอียด"
1167
- }
1168
- """
1169
- )
1170
-
1171
- def _get_default_analysis(self, query: str) -> Dict[str, Any]:
1172
- """Return default analysis when processing fails"""
1173
- logger.info("Returning default analysis")
1174
- return {
1175
- "original_query": query,
1176
- "event_type": None,
1177
- "semester": None,
1178
- "key_terms": [],
1179
- "response_format": "detailed"
1180
- }
1181
-
1182
- def process_query(self, query: str) -> Dict[str, Any]:
1183
- """Enhanced query processing with better error handling."""
1184
-
1185
- try:
1186
- result = self.prompt_builder.run(query=query)
1187
- response = self.generator.run(prompt=result["prompt"])
1188
-
1189
- if not response or not response.get("replies") or not response["replies"][0]:
1190
- logger.warning("Received empty response from OpenAI")
1191
- return self._get_default_analysis(query)
1192
-
1193
- try:
1194
- analysis = json.loads(response["replies"][0])
1195
- except json.JSONDecodeError as je:
1196
- return self._get_default_analysis(query)
1197
-
1198
- # **Ensure course-related queries retrieve study plans & curricula**
1199
- course_keywords = ['หน่วยกิต', 'วิชา', 'หลักสูตร', 'แผนการเรียน', 'วิชาเลือก', 'วิชาบังคับ', 'วิชาการค้นคว้า', 'วิชาหลัก']
1200
- if any(keyword in query for keyword in course_keywords):
1201
- analysis['event_type'] = 'curriculum'
1202
-
1203
- # **Ensure fee-related queries retrieve tuition fee documents**
1204
- fee_keywords = ['ค่าเทอม', 'ค่าธรรมเนียม', 'ค่าเรียน', 'ค่าปรับ']
1205
- if any(keyword in query for keyword in fee_keywords):
1206
- analysis['event_type'] = 'fees'
1207
-
1208
- return {
1209
- "original_query": query,
1210
- **analysis
1211
- }
1212
-
1213
- except Exception as e:
1214
- logger.error(f"Query processing failed: {str(e)}")
1215
- return self._get_default_analysis(query)
1216
 
1217
  class ResponseGenerator:
1218
  """Generate responses with better context utilization"""
@@ -1224,21 +1263,27 @@ class ResponseGenerator:
1224
  )
1225
  self.prompt_builder = PromptBuilder(
1226
  template="""
1227
- คุณเป็นที่ปรึกษาทางวิชาการ กรุณาตอบคำถามต่อไปนี้โดยใช้ข้อมูลจากปฏิทินการศึกษาที่ให้มา
1228
 
1229
  คำถาม: {{query}}
1230
 
1231
- ข้อมูลที่เกี่ยวข้องจากปฏิทินการศึกษา:
1232
  {% for doc in context %}
1233
- ---
 
 
1234
  {{doc.content}}
1235
  {% endfor %}
1236
 
1237
- **ห้ามเดาข้อมูลเอง ถ้าไม่มีข้อมูลให้ตอบว่า "ไม่มีข้อมูลที่ตรงกับคำถาม"**
 
 
 
 
 
 
1238
 
1239
  กรุณาตอบเป็นภาษาไทย:
1240
-
1241
- ต้องบอกเสมอว่า **หากมีข้อสงสัยเพิ่มเติมสามารถสอบถามได้**
1242
  """
1243
  )
1244
 
@@ -1248,12 +1293,12 @@ class ResponseGenerator:
1248
  query_info: Dict[str, Any]) -> str:
1249
  """Generate response using retrieved documents"""
1250
  try:
 
1251
  result = self.prompt_builder.run(
1252
  query=query,
1253
  context=documents,
1254
  format=query_info["response_format"]
1255
  )
1256
-
1257
  response = self.generator.run(prompt=result["prompt"])
1258
  return response["replies"][0]
1259
 
@@ -1261,6 +1306,184 @@ class ResponseGenerator:
1261
  logger.error(f"Response generation failed: {str(e)}")
1262
  return "ขออภัย ไม่สามารถประมวลผลคำตอบได้ในขณะนี้"
1263
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1264
  class AcademicCalendarRAG:
1265
  """Enhanced RAG system for academic calendar and program information"""
1266
 
@@ -1295,152 +1518,75 @@ class AcademicCalendarRAG:
1295
  self.study_plans = self.data_processor.extract_program_study_plan(json_data)
1296
  self.tuition_fees = self.data_processor.extract_fees(json_data)
1297
 
1298
- self._add_calendar_events()
1299
- self._add_program_info()
1300
-
1301
- except Exception as e:
1302
- logger.error(f"Error loading data: {str(e)}")
1303
- raise
1304
-
1305
- def _add_calendar_events(self):
1306
- """Add calendar events and other data to document store"""
1307
- if self.calendar_events:
1308
  self.document_store.add_events(
1309
  events=self.calendar_events,
 
1310
  contact_details=self.contact_details,
1311
  course_structure=self.course_structure,
1312
- study_plans=self.study_plans
 
1313
  )
1314
-
1315
- def _add_program_info(self):
1316
- """Enhanced method to add program-related information to document store"""
1317
- if self.program_details:
1318
- for detail in self.program_details:
1319
- text = f"""
1320
- ข้อมูลการสมัคร:
1321
- เว็บไซต์รับสมัคร: {detail.application_info.application_portal}
1322
- อีเมล: {detail.application_info.program_email}
1323
-
1324
- เอกสารที่ต้องใช้:
1325
- {self._format_required_docs(detail.required_documents)}
1326
-
1327
- ขั้นตอนการส่งเอกสาร:
1328
- {detail.submission_process}
1329
-
1330
- ขั้นตอนการคัดเลือก:
1331
- {self._format_selection_steps(detail.selection_process)}
1332
- """
1333
- self.document_store.add_document(text, "program_details")
1334
-
1335
- if self.tuition_fees:
1336
- for fee in self.tuition_fees:
1337
- text = f"""
1338
- ค่าธรรมเนียมการศึกษา:
1339
- ค่าเล่าเรียนปกติ: {fee.regular_fee.amount:,.2f} {fee.regular_fee.currency} {fee.regular_fee.period}
1340
- ค่าปรับชำระล่าช้า: {fee.late_payment_fee.amount:,.2f} {fee.late_payment_fee.currency}
1341
- """
1342
- self.document_store.add_document(text, "fees")
1343
-
1344
- def _format_required_docs(self, docs: Dict) -> str:
1345
- """Format required documents information with detailed English proficiency requirements"""
1346
- result = []
1347
-
1348
- if 'mandatory' in docs:
1349
- result.append("เอกสารที่ต้องใช้:")
1350
- for doc in docs['mandatory'].values():
1351
- result.append(f"- {doc.name}: {doc.description}")
1352
-
1353
- if 'optional' in docs:
1354
- result.append("\nเอกสารเพิ่มเติม:")
1355
- for doc_key, doc in docs['optional'].items():
1356
- if doc_key == 'english_proficiency':
1357
- result.append(f"- {doc.name}")
1358
- # Parse and format the accepted tests
1359
- try:
1360
- accepted_tests = eval(doc.description)
1361
- result.append(" เกณฑ์คะแนนที่ยอมรับ:")
1362
- for test, requirement in accepted_tests.items():
1363
- result.append(f" * {test}: {requirement}")
1364
- except:
1365
- result.append(f" {doc.description}")
1366
-
1367
- if doc.conditions:
1368
- conditions = doc.conditions.split(', ')
1369
- for condition in conditions:
1370
- result.append(f" {condition}")
1371
- else:
1372
- desc = f"- {doc.name}"
1373
- if doc.conditions:
1374
- desc += f" ({doc.conditions})"
1375
- result.append(desc)
1376
-
1377
- return "\n".join(result)
1378
-
1379
- def _format_selection_steps(self, steps: List[SelectionStep]) -> str:
1380
- """Format selection process steps"""
1381
- return "\n".join(f"{step.step_number}. {step.description}" for step in steps)
1382
-
1383
- def _get_fee_documents(self) -> List[Document]:
1384
- """Get fee-related documents"""
1385
- if not self.tuition_fees:
1386
- return []
1387
 
1388
- documents = []
1389
- for fee in self.tuition_fees:
1390
- text = f"""
1391
- ค่าธรรมเนียมการศึกษา:
1392
- - ค่าเล่าเรียน: {fee.regular_fee.amount:,.2f} {fee.regular_fee.currency} {fee.regular_fee.period}
1393
- - ค่าปรับชำระล่าช้า: {fee.late_payment_fee.amount:,.2f} {fee.late_payment_fee.currency}
1394
- """
1395
- doc = Document(
1396
- content=text,
1397
- meta={"event_type": "fees"}
1398
- )
1399
- documents.append(doc)
1400
-
1401
- return documents
1402
 
1403
- def process_query(self, query: str, weight_semantic: float = 0.5) -> Dict[str, Any]:
1404
- """Process user query using hybrid retrieval"""
1405
- try:
1406
- # Analyze query
1407
- query_info = self.query_processor.process_query(query)
1408
-
1409
- # Get relevant documents using hybrid search
1410
- documents = self.document_store.hybrid_search(
1411
- query=query,
1412
- event_type=query_info.get("event_type"),
1413
- semester=query_info.get("semester"),
1414
- top_k=self.config.retriever.top_k,
1415
- weight_semantic= 0.3
1416
- )
1417
-
1418
- # Add fee information for fee-related queries
1419
- if query_info.get("event_type") == "fees" and self.tuition_fees:
1420
- fee_docs = self._get_fee_documents()
1421
- documents.extend(fee_docs)
1422
-
1423
- # Generate response
1424
- response = self.response_generator.generate_response(
1425
- query=query,
1426
- documents=documents,
1427
- query_info=query_info
1428
- )
1429
-
1430
- return {
1431
- "query": query,
1432
- "answer": response,
1433
- "relevant_docs": documents,
1434
- "query_info": query_info
1435
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1436
 
1437
- except Exception as e:
1438
- logger.error(f"Error processing query: {str(e)}")
1439
- return {
1440
- "query": query,
1441
- "answer": "ขออภัย ไม่สามารถประมวลผลคำตอบได้ในขณะนี้",
1442
- "error": str(e)
1443
- }
1444
  # def main():
1445
  # """Main function demonstrating hybrid retrieval"""
1446
  # try:
@@ -1459,8 +1605,8 @@ class AcademicCalendarRAG:
1459
  # pipeline.load_data(raw_data)
1460
 
1461
  # # Test queries with different semantic weights
1462
- # queries = ["มีวิชาหลักหรือวิชาเลือกอะไรบ้าง"]
1463
-
1464
  # print("=" * 80)
1465
 
1466
  # for query in queries:
@@ -1472,6 +1618,6 @@ class AcademicCalendarRAG:
1472
  # except Exception as e:
1473
  # logger.error(f"Pipeline execution failed: {str(e)}")
1474
  # raise
1475
-
1476
  # if __name__ == "__main__":
1477
  # main()
 
3
  from haystack.components.builders import PromptBuilder
4
  from haystack.components.embedders import SentenceTransformersDocumentEmbedder
5
  from haystack.components.retrievers.in_memory import *
6
+ from haystack.document_stores.in_memory import InMemoryDocumentStore
7
  from haystack.utils import Secret
8
  from pathlib import Path
9
  import hashlib
 
14
  import logging
15
  import re
16
  import pickle
17
+ import statistics
18
 
19
  # Setup logging
20
  logging.basicConfig(level=logging.INFO)
 
648
  transportation=transportation
649
  ))
650
  except Exception as e:
 
651
  continue
652
 
653
  return contact_details
 
710
  # Create course categories
711
  structure = {
712
  'หมวดวิชาปรับพื้นฐาน': CourseCategory( # Previously foundation_courses
713
+ description="วิชาพื้นฐานที่จำเป็นต้องเรียน foundation courses รายวิชาปรับพื้นฐาน",
714
  credits=foundation_data.get('metadata', {}).get('credits', 'non-credit'),
715
  minimum_credits=None,
716
  courses=foundation_courses
717
  ),
718
  'หมวดวิชาบังคับ': CourseCategory( # Previously core_courses
719
+ description="วิชาบังคับ วิชาหลัก core courses รายวิชาที่ต้องเรียน",
720
  credits=0,
721
  minimum_credits=core_data.get('minimum_requirement_credits'),
722
  courses=core_courses
723
  ),
724
  'หมวดวิชาเลือก': CourseCategory( # Previously elective_courses
725
+ description="วิชาเลือก elective courses รายวิชาเลือก วิชาที่สามารถเลือกเรียนได้",
726
  credits=0,
727
  minimum_credits=elective_data.get('minimum_requirement_credits'),
728
  courses=elective_courses
729
  ),
730
  'หมวดวิชาการค้นคว้าอิสระ': CourseCategory( # Previously research_courses
731
+ description="วิชาค้นคว้าอิสระ research courses วิทยานิพนธ์",
732
  credits=0,
733
  minimum_credits=research_data.get('minimum_requirement_credits'),
734
  courses=research_courses
 
877
  self.cache_manager.set_embedding_cache(text, embedding)
878
  return embedding
879
 
880
+ def _format_required_docs(self, docs: Dict) -> str:
881
+ """Format required documents information with detailed English proficiency requirements"""
882
+ result = []
883
+
884
+ if 'mandatory' in docs:
885
+ result.append("เอกสารที่ต้องใช้:")
886
+ for doc in docs['mandatory'].values():
887
+ result.append(f"- {doc.name}: {doc.description}")
888
+
889
+ if 'optional' in docs:
890
+ result.append("\nเอกสารเพิ่มเติม:")
891
+ for doc_key, doc in docs['optional'].items():
892
+ if doc_key == 'english_proficiency':
893
+ result.append(f"- {doc.name}")
894
+ # Parse and format the accepted tests
895
+ try:
896
+ accepted_tests = eval(doc.description)
897
+ result.append(" เกณฑ์คะแนนที่ยอมรับ:")
898
+ for test, requirement in accepted_tests.items():
899
+ result.append(f" * {test}: {requirement}")
900
+ except:
901
+ result.append(f" {doc.description}")
902
+
903
+ if doc.conditions:
904
+ conditions = doc.conditions.split(', ')
905
+ for condition in conditions:
906
+ result.append(f" {condition}")
907
+ else:
908
+ desc = f"- {doc.name}"
909
+ if doc.conditions:
910
+ desc += f" ({doc.conditions})"
911
+ result.append(desc)
912
+
913
+ return "\n".join(result)
914
+
915
+ def _format_selection_steps(self, steps: List[SelectionStep]) -> str:
916
+ """Format selection process steps"""
917
+ return "\n".join(f"{step.step_number}. {step.description}" for step in steps)
918
 
919
+ def add_events(self,
920
+ events: List[CalendarEvent],
921
+ contact_details: Optional[List[ContactDetail]] = None,
922
+ course_structure: Optional[List[CourseStructure]] = None,
923
+ study_plans: Optional[List[StudyPlan]] = None,
924
+ program_details: Optional[List[ProgramDetailInfo]] = None,
925
+ tuition_fees: Optional[List[TuitionFee]] = None):
926
  """Add events and additional data with caching"""
927
  documents = []
928
  added_events = set() # Track added events to prevent duplicates
 
995
  # Process course structure
996
  if course_structure:
997
  for course in course_structure:
 
998
  text = f"""
999
  โครงสร้างหลักสูตร:
1000
  ชื่อหลักสูตร: {course.program_name}
 
1003
  ระดับการศึกษา: {course.degree_level}
1004
 
1005
  รายละเอียดโครงสร้าง:
1006
+
1007
+ หมวดวิชาปรับพื้นฐาน/วิชาพื้นฐาน:
1008
+ คำอธิบาย: {course.structure['หมวดวิชาปรับพื้นฐาน'].description or 'ไม่ระบุ'}
1009
+ หน่วยกิต: {course.structure['หมวดวิชาปรับพื้นฐาน'].credits}
1010
+ รายวิชา:
1011
+ {' '.join([f'- {c.code}: {c.title_th} ({c.title_en}) - {c.credits} หน่วยกิต\n' for c in course.structure['หมวดวิชาปรับพื้นฐาน'].courses])}
1012
+
1013
+ หมวดวิชาบังคับ/วิชาหลัก:
1014
+ หน่วยกิตขั้นต่ำ: {course.structure['หมวดวิชาบังคับ'].minimum_credits}
1015
+ รายวิชา:
1016
+ {' '.join([f'- {c.code}: {c.title_th} ({c.title_en}) - {c.credits} หน่วยกิต\n' for c in course.structure['หมวดวิชาบังคับ'].courses])}
1017
+
1018
+ หมวดวิชาเลือก:
1019
+ หน่วยกิตขั้นต่ำ: {course.structure['หมวดวิชาเลือก'].minimum_credits}
1020
+ รายวิชา:
1021
+ {' '.join([f'- {c.code}: {c.title_th} ({c.title_en}) - {c.credits} หน่วยกิต\n' for c in course.structure['หมวดวิชาเลือก'].courses])}
1022
+
1023
+ หมวดวิชาการค้นคว้าอิสระ:
1024
+ หน่วยกิตขั้นต่ำ: {course.structure['หมวดวิชาการค้นคว้าอิสระ'].minimum_credits}
1025
+ รายวิชา:
1026
+ {' '.join([f'- {c.code}: {c.title_th} ({c.title_en}) - {c.credits} หน่วยกิต\n' for c in course.structure['หมวดวิชาการค้นคว้าอิสระ'].courses])}
1027
  """
 
 
 
 
 
 
 
 
 
 
1028
 
 
1029
  doc = Document(
1030
  id=self._generate_unique_id(),
1031
+ content=text.strip(),
1032
+ embedding=self._compute_embedding(text),
1033
  meta={'event_type': 'curriculum'}
1034
  )
1035
  documents.append(doc)
 
1038
  if study_plans:
1039
  for plan in study_plans:
1040
  self.study_plan_data.append(plan)
 
1041
  for year, semesters in plan.years.items():
 
1042
  for semester, data in semesters.items():
1043
+ # Convert year and semester format
1044
+ year_num = year.replace('year', '')
1045
+ semester_num = semester.replace('semester', '')
1046
+
1047
+ # Determine course type and translate to Thai
1048
+ course_type = data.get('metadata', {}).get('course_type', 'core')
1049
+ course_type_th = 'วิชาหลัก' if course_type == 'core' else 'วิชาเลือก'
1050
+
1051
+ # Calculate total credits
1052
+ total_credits = sum(course.get('credits', 0) for course in data.get('courses', []))
1053
+
1054
+ text = f"""แผนการศึกษา:
1055
+ ปี: {year_num}
1056
+ ภาคการศึกษา: {semester_num}
1057
+ ประเภทรายวิชา: {course_type_th} ({course_type})
1058
+ จำนวนหน่วยกิตรวม: {total_credits}
1059
+
1060
+ รายวิชาที่ต้องเรียน:"""
1061
+
1062
+ # Add courses
1063
  if 'courses' in data:
1064
  for course in data['courses']:
1065
+ text += f"\n- {course['code']}: {course['title'].get('th', '')} ({course['title'].get('en', '')}) - {course['credits']} หน่วยกิต"
1066
+
1067
+ embedding = self._compute_embedding(text)
1068
+ doc = Document(
1069
+ id=self._generate_unique_id(),
1070
+ content=text,
1071
+ embedding=embedding,
1072
+ meta={
1073
+ 'event_type': 'study_plan',
1074
+ 'year': year_num,
1075
+ 'semester': semester_num,
1076
+ 'course_type': course_type
1077
+ }
1078
+ )
1079
+ documents.append(doc)
1080
+
1081
+ if program_details:
1082
+ for detail in program_details:
1083
+ # Main application document
1084
+ app_text = f"""
1085
+ ข้อมูลการสมัคร:
1086
+ เว็บไซต์รับสมัคร: {detail.application_info.application_portal}
1087
+ อีเมล: {detail.application_info.program_email}
1088
+
1089
+ เอกสารที่ต้องใช้:
1090
+ {self._format_required_docs(detail.required_documents)}
1091
+
1092
+ ขั้นตอนการส่งเอกสาร:
1093
+ {detail.submission_process}
1094
+
1095
+ ขั้นตอนการคัดเลือก:
1096
+ {self._format_selection_steps(detail.selection_process)}
1097
+ """
1098
 
 
1099
  doc = Document(
1100
  id=self._generate_unique_id(),
1101
+ content=app_text.strip(),
1102
+ embedding=self._compute_embedding(app_text),
1103
+ meta={'event_type': 'program_details'}
1104
+ )
1105
+ documents.append(doc)
1106
+
1107
+ # Create separate document for English proficiency requirements
1108
+ if 'optional' in detail.required_documents:
1109
+ eng_prof = next((doc for doc_key, doc in detail.required_documents['optional'].items()
1110
+ if doc_key == 'english_proficiency'), None)
1111
+ if eng_prof:
1112
+ eng_text = f"""
1113
+ ข้อกำหนดภาษาอังกฤษ:
1114
+ {eng_prof.name}
1115
+ รายละเอียด: {eng_prof.description}
1116
+ เงื่อนไข: {eng_prof.conditions}
1117
+ """
1118
+
1119
+ eng_doc = Document(
1120
+ id=self._generate_unique_id(),
1121
+ content=eng_text.strip(),
1122
+ embedding=self._compute_embedding(eng_text),
1123
+ meta={
1124
+ 'event_type': 'program_details' }
1125
+ )
1126
+ documents.append(eng_doc)
1127
+
1128
+ # Process tuition fees
1129
+ if tuition_fees:
1130
+ for fee in tuition_fees:
1131
+ fee_text = f"""
1132
+ ค่าธรรมเนียมการศึกษา:
1133
+ - ค่าเล่าเรียน: {fee.regular_fee.amount:,.2f} {fee.regular_fee.currency} {fee.regular_fee.period}
1134
+ - ค่าปรับชำระล่าช้า: {fee.late_payment_fee.amount:,.2f} {fee.late_payment_fee.currency}
1135
+ """
1136
+
1137
+ doc = Document(
1138
+ id=self._generate_unique_id(),
1139
+ content=fee_text.strip(),
1140
+ embedding=self._compute_embedding(fee_text),
1141
+ meta={'event_type': 'fees'}
1142
  )
1143
  documents.append(doc)
1144
 
 
1157
 
1158
  def hybrid_search(self,
1159
  query: str,
1160
+ event_type: Optional[str] = None,
1161
+ detail_type: Optional[str] = None,
1162
+ semester: Optional[str] = None,
1163
+ top_k: int = 10,
1164
  weight_semantic: float = 0.5) -> List[Document]:
1165
+
1166
  """Hybrid search combining semantic and lexical search results"""
1167
 
1168
  cache_key = json.dumps({
 
1179
 
1180
  # Get semantic search results
1181
  query_embedding = self._compute_embedding(query)
1182
+ semantic_results = self.embedding_retriever.run(query_embedding=query_embedding)["documents"]
 
 
1183
 
1184
  # Get BM25 results
1185
  bm25_results = self.bm25_retriever.run(
1186
  query=query
1187
  )["documents"]
1188
 
1189
+ if event_type == "program_details":
1190
+ weight_semantic = 0.3 # Give more weight to keyword matching
1191
+
1192
  # Combine results using score fusion
1193
  combined_results = self._merge_results(
1194
  semantic_results=semantic_results,
 
1200
  # Filter results based on metadata
1201
  filtered_results = []
1202
  for doc in combined_results:
1203
+ if event_type and event_type != "program_details" and doc.meta.get('event_type') != event_type:
1204
+ continue # Keep only relevant event type unless it's program_details
 
 
1205
  filtered_results.append(doc)
1206
 
1207
  final_results = filtered_results[:top_k]
 
1252
  )
1253
 
1254
  return sorted_docs[:top_k]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1255
 
1256
  class ResponseGenerator:
1257
  """Generate responses with better context utilization"""
 
1263
  )
1264
  self.prompt_builder = PromptBuilder(
1265
  template="""
1266
+ คุณเป็นที่ปรึกษาทางวิชาการ กรุณาตอบคำถามต่อไปนี้โดยใช้ข้อมูลจากเอกสารที่ให้มาเท่านั้น
1267
 
1268
  คำถาม: {{query}}
1269
 
1270
+ ข้อมูลที่เกี่ยวข้อง:
1271
  {% for doc in context %}
1272
+ ---
1273
+ ประเภท: {{doc.meta.event_type}}{% if doc.meta.detail_type %}, รายละเอียด: {{doc.meta.detail_type}}{% endif %}
1274
+ เนื้อหา:
1275
  {{doc.content}}
1276
  {% endfor %}
1277
 
1278
+ คำแนะนำในการตอบ:
1279
+ 1. ตอบเฉพาะข้อมูลที่มีในเอกสารเท่านั้น
1280
+ 2. หากไม่มีข้อมูลให้ตอบว่า "ขออภัย ไม่พบข้อมูลที่เกี่ยวข้องกับคำถามนี้"
1281
+ 3. หากข้อมูลไม่ชัดเจนให้ระบุว่าข้อมูลอาจไม่ครบถ้วน
1282
+ 4. จัดรูปแบบคำตอบให้อ่านง่าย ใช้หัวข้อและย่อหน้าตามความเหมาะสม
1283
+ 5. สำหรับคำถามเกี่ยวกับข้อกำหนดภาษาอังกฤษหรือขั้นตอนการสมัคร ให้อธิบายข้อมูลอย่างละเอียด
1284
+ 6. ใส่ข้อความ "หากมีข้อสงสัยเพิ่มเติม สามารถสอบถามได้" ท้ายคำตอบเสมอ
1285
 
1286
  กรุณาตอบเป็นภาษาไทย:
 
 
1287
  """
1288
  )
1289
 
 
1293
  query_info: Dict[str, Any]) -> str:
1294
  """Generate response using retrieved documents"""
1295
  try:
1296
+ print(query_info)
1297
  result = self.prompt_builder.run(
1298
  query=query,
1299
  context=documents,
1300
  format=query_info["response_format"]
1301
  )
 
1302
  response = self.generator.run(prompt=result["prompt"])
1303
  return response["replies"][0]
1304
 
 
1306
  logger.error(f"Response generation failed: {str(e)}")
1307
  return "ขออภัย ไม่สามารถประมวลผลคำตอบได้ในขณะนี้"
1308
 
1309
+ class AdvancedQueryProcessor:
1310
+ """Process queries with better understanding"""
1311
+
1312
+ def __init__(self, config: PipelineConfig):
1313
+ self.generator = OpenAIGenerator(
1314
+ api_key=Secret.from_token(config.model.openai_api_key),
1315
+ model=config.model.openai_model
1316
+ )
1317
+ self.prompt_builder = PromptBuilder(
1318
+ template="""
1319
+ คุณเป็นผู้ช่วย AI ที่เชี่ยวชาญด้านการศึกษาในประเทศไทย หน้าที่ของคุณคือการวิเคราะห์และจำแนกคำถามของผู้ใช้ให้ตรงกับหมวดหมู่ข้อมูลที่เหมาะสม ได้แก่:
1320
+
1321
+ 1. **รายละเอียดโปรแกรมการศึกษา (program_details)**: ข้อมูลเกี่ยวกับหลักสูตร โปรแกรมการเรียนการสอน และโครงสร้างหลักสูตร
1322
+ 2. **ข้อมูลการติดต่อ (contact)**: ข้อมูลการติดต่อของหน่วยงานหรือบุคคลที่เกี่ยวข้องในสถาบันการศึกษา
1323
+ 3. **โครงสร้างหลักสูตร (curriculum)**: รายละเอียดเกี่ยวกับวิชาเรียน หน่วยกิต และแผนการศึกษา
1324
+ 4. **ค่าเล่าเรียน (fees)**: ข้อมูลเกี่ยวกับค่าใช้จ่ายในการศึกษา ค่าธรรมเนียม และทุนการศึกษา
1325
+ 5. **แผนการศึกษารายปี (study_plan)**: ข้อมูลแผนการเรียนแบ่งตามชั้นปีและภาคการศึกษา รายละเอียดรายวิชาที่ต้องลงทะเบียนในแต่ละเทอม และจำนวนหน่วยกิตรวม
1326
+
1327
+ **คำถาม**: {{query}}
1328
+
1329
+ **คำแนะนำในการวิเคราะห์**:
1330
+ - ตรวจสอบคำสำคัญในคำถามเพื่อระบุหมวดหมู่ที่สอดคล้อง
1331
+ - หากคำถามเกี่ยวข้องกับหลายหมวดหมู่ ให้จัดลำดับความสำคัญตามความต้องการของผู้ใช้
1332
+ - หากไม่สามารถระบุหมวดหมู่ได้อย่างชัดเจน ให้จัดหมวดหมู่เป็น "อื่นๆ" และระบุความไม่แน่นอน
1333
+
1334
+ **รูปแบบการตอบกลับ**:
1335
+
1336
+ หมายเหตุ:
1337
+ - รูปแบบปีการศึกษาที่ยอมรับ: "ปีที่ 1", "ปี 1", "ชั้นปีที่ 1"
1338
+ - รูปแบบภาคการศึกษาที่ยอมรับ: "เทอมที่ 1", "เทอม 1", "ภาคการศึกษาที่ 1"
1339
+ - หากข้อมูลไม่ครบ ให้ระบุค่าสำหรับฟิลด์ที่ขาดหายเป็น null พร้อมข้อความแจ้งความไม���แน่นอน
1340
+
1341
+ ให้ผลลัพธ์ในรูปแบบ JSON ตามโครงสร้าง:
1342
+ {
1343
+ "event_type": "program_details" | "contact" | "curriculum" | "fees" | "study_plan",
1344
+ "year": "ปีที่ X", // แปลงเป็นรูปแบบมาตรฐาน หรือ null หากไม่ระบุ
1345
+ "semester": "เทอมที่ X", // แปลงเป็นรูปแบบมาตรฐาน หรือ null หากไม่ระบุ
1346
+ "key_terms": ["คำสำคัญที่เกี่ยวข้อง"],
1347
+ "response_format": "detailed",
1348
+ "uncertainty": "low" // ระบุระดับความไม่แน่นอน (เช่น 'low', 'high')
1349
+ }
1350
+
1351
+ ตัวอย่าง:
1352
+ Input: "โปรแกรมการศึกษามีรายละเอียดอะไรบ้าง"
1353
+ Output: {
1354
+ "event_type": "program_details",
1355
+ "year": null,
1356
+ "semester": null,
1357
+ "key_terms": ["โปรแกรมการศึกษา", "รายละเอียด"],
1358
+ "response_format": "detailed",
1359
+ "uncertainty": "low"
1360
+ }
1361
+
1362
+ Input: "ฉันจะติดต่อภาควิชาได้อย่างไร"
1363
+ Output: {
1364
+ "event_type": "contact",
1365
+ "year": null,
1366
+ "semester": null,
1367
+ "key_terms": ["ติดต่อ", "ภาควิชา"],
1368
+ "response_format": "detailed",
1369
+ "uncertainty": "low"
1370
+ }
1371
+
1372
+ Input: "โครงสร้างหลักสูตรของปี 2 เป็นอย่างไร"
1373
+ Output: {
1374
+ "event_type": "curriculum",
1375
+ "year": "ปีที่ 2",
1376
+ "semester": null,
1377
+ "key_terms": ["โครงสร้างหลักสูตร"],
1378
+ "response_format": "detailed",
1379
+ "uncertainty": "low"
1380
+ }
1381
+
1382
+ Input: "ค่าเล่าเรียนสำหรับเทอม 1 เท่าไหร่"
1383
+ Output: {
1384
+ "event_type": "fees",
1385
+ "year": null,
1386
+ "semester": "เทอมที่ 1",
1387
+ "key_terms": ["ค่าเล่าเรียน", "เทอม 1"],
1388
+ "response_format": "detailed",
1389
+ "uncertainty": "low"
1390
+ }
1391
+
1392
+ Input: "ปี 1 เทอม 1 ต้องเรียนอะไรบ้าง"
1393
+ Output: {
1394
+ "event_type": "study_plan",
1395
+ "year": null,
1396
+ "semester": null,
1397
+ "key_terms": ["เรียนอะไร", "เทอม"],
1398
+ "response_format": "detailed",
1399
+ "uncertainty": "low"
1400
+ }
1401
+
1402
+ กรุณาตอบเป็นภาษาไทยและตรวจสอบให้แน่ใจว่า JSON มีโครงสร้างที่ถูกต้อง
1403
+ """
1404
+ )
1405
+
1406
+ def normalize_year_semester(self, query: str) -> str:
1407
+ """Normalize year and semester formats in queries"""
1408
+ # Year patterns
1409
+ year_patterns = {
1410
+ r'ปี\s*(\d+)': r'ปีที่ \1',
1411
+ r'ชั้นปีที่\s*(\d+)': r'ปีที่ \1',
1412
+ r'ปีการศึกษาที่\s*(\d+)': r'ปีที่ \1'
1413
+ }
1414
+ # Semester patterns
1415
+ semester_patterns = {
1416
+ r'เทอม\s*(\d+)': r'เทอมที่ \1',
1417
+ r'ภาคเรียนที่\s*(\d+)': r'เทอมที่ \1',
1418
+ r'ภาคการศึกษาที่\s*(\d+)': r'เทอมที่ \1'
1419
+ }
1420
+ normalized_query = query
1421
+ for pattern, replacement in year_patterns.items():
1422
+ normalized_query = re.sub(pattern, replacement, normalized_query)
1423
+ for pattern, replacement in semester_patterns.items():
1424
+ normalized_query = re.sub(pattern, replacement, normalized_query)
1425
+ return normalized_query
1426
+
1427
+ def normalize_query(self, query: str) -> str:
1428
+ """เพิ่มการเปลี่ยนแปลงคำ (synonym mapping) เพื่อลดปัญหา Vocabulary Mismatch"""
1429
+ normalized_query = self.normalize_year_semester(query)
1430
+ # เพิ่ม mapping สำหรับคำที่มีความหมายเดียวกัน
1431
+ synonyms = {
1432
+ "วิชาเลือก": "หมวดวิชาเลือก"
1433
+ # สามารถเพิ่มคำอื่น ๆ ได้ตามต้องการ
1434
+ }
1435
+ for original, replacement in synonyms.items():
1436
+ normalized_query = normalized_query.replace(original, replacement)
1437
+ return normalized_query
1438
+
1439
+ def _get_default_analysis(self, query: str) -> Dict[str, Any]:
1440
+ logger.info("Returning default analysis")
1441
+ return {
1442
+ "original_query": query,
1443
+ "event_type": None,
1444
+ "semester": None,
1445
+ "key_terms": [],
1446
+ "response_format": "detailed"
1447
+ }
1448
+
1449
+ def process_query(self, query: str) -> Dict[str, Any]:
1450
+ """Enhanced query processing with support for detail types and better categorization."""
1451
+ try:
1452
+ # ใช้ normalize_query ที่แก้ไขแล้วเพื่อให้คำค้นมีรูปแบบที่ตรงกับดัชนีข้อมูล
1453
+ normalized_query = self.normalize_query(query)
1454
+ result = self.prompt_builder.run(query=normalized_query)
1455
+ response = self.generator.run(prompt=result["prompt"])
1456
+
1457
+ if not response or not response.get("replies") or not response["replies"][0]:
1458
+ logger.warning("Received empty response from OpenAI")
1459
+ return self._get_default_analysis(query)
1460
+
1461
+ # ทำความสะอาด JSON string
1462
+ json_str = response["replies"][0]
1463
+ json_str = json_str.replace("```json", "").replace("```", "").strip()
1464
+ analysis = json.loads(json_str)
1465
+
1466
+ analysis['detail_type'] = None
1467
+
1468
+ # Enhanced categorization with detail types
1469
+ if any(keyword in query.lower() for keyword in ['ภาษาอังกฤษ', 'toefl', 'ielts', 'swu-set', 'โทอิค', 'คะแนนภาษา']):
1470
+ analysis['event_type'] = 'program_details'
1471
+ elif any(keyword in query.lower() for keyword in ['สมัคร', 'ขั้นตอน', 'วิธีการ', 'เอกสาร', 'หลักฐาน', 'admission']):
1472
+ analysis['event_type'] = 'program_details'
1473
+ analysis['detail_type'] = None
1474
+ elif any(keyword in query.lower() for keyword in ['ค่าเทอม', 'ค่าธรรมเนียม', 'ค่าเรียน', 'ค่าปรับ', 'ค่าใช้จ่าย']):
1475
+ analysis['event_type'] = 'fees'
1476
+ elif any(keyword in query.lower() for keyword in ['หน่วยกิต', 'วิชา', 'หลักสูตร', 'แผนการเรียน', 'วิชาเลือก', 'วิชาบังคับ', 'วิชาหลัก', 'หมวดวิชา']):
1477
+ analysis['event_type'] = 'curriculum'
1478
+ return {
1479
+ "original_query": query,
1480
+ **analysis
1481
+ }
1482
+ except Exception as e:
1483
+ logger.error(f"Query processing failed: {str(e)}")
1484
+ return self._get_default_analysis(query)
1485
+
1486
+
1487
  class AcademicCalendarRAG:
1488
  """Enhanced RAG system for academic calendar and program information"""
1489
 
 
1518
  self.study_plans = self.data_processor.extract_program_study_plan(json_data)
1519
  self.tuition_fees = self.data_processor.extract_fees(json_data)
1520
 
 
 
 
 
 
 
 
 
 
 
1521
  self.document_store.add_events(
1522
  events=self.calendar_events,
1523
+ program_details=self.program_details,
1524
  contact_details=self.contact_details,
1525
  course_structure=self.course_structure,
1526
+ study_plans=self.study_plans,
1527
+ tuition_fees=self.tuition_fees
1528
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1529
 
1530
+ except Exception as e:
1531
+ logger.error(f"Error loading data: {str(e)}")
1532
+ raise
 
 
 
 
 
 
 
 
 
 
 
1533
 
1534
+ def process_query(self, query: str) -> Dict[str, Any]:
1535
+ """Process user query using hybrid retrieval with dynamic retries."""
1536
+ max_attempts = 4 # Allow up to 4 attempts
1537
+ attempt = 0
1538
+ weight_values = [0.3, 0.7, 0.3, 0.7] # Switching semantic retrieval weight
1539
+
1540
+ while attempt < max_attempts:
1541
+ attempt += 1
1542
+ try:
1543
+ # Analyze query
1544
+ if attempt <= 2:
1545
+ query_info = self.query_processor.process_query(query) # Use normal query analysis for first 2 attempts
1546
+ else:
1547
+ query_info = self.query_processor._get_default_analysis(query) # Use default analysis for last 2 attempts
1548
+ logger.info(f"Retrying query processing (attempt {attempt}) with default analysis")
1549
+
1550
+ weight_semantic = weight_values[attempt - 1] # Adjust weight for semantic search dynamically
1551
+
1552
+ # Get relevant documents using hybrid search
1553
+ logger.info(f"Attempt {attempt}: Searching with weight_semantic={weight_semantic}")
1554
+ documents = self.document_store.hybrid_search(
1555
+ query=query,
1556
+ event_type=query_info.get("event_type"),
1557
+ detail_type=query_info.get("detail_type"),
1558
+ semester=query_info.get("semester"),
1559
+ top_k=self.config.retriever.top_k,
1560
+ weight_semantic=weight_semantic
1561
+ )
1562
+
1563
+ # Generate response
1564
+ response = self.response_generator.generate_response(
1565
+ query=query,
1566
+ documents=documents,
1567
+ query_info=query_info
1568
+ ).strip()
1569
+
1570
+ # If response indicates no relevant information, retry with adjusted approach
1571
+ if "ขออภัย ไม่พบข้อมูลที่เกี่ยวข้อง" in response and attempt < max_attempts:
1572
+ continue # Try again with new weight or default analysis
1573
+
1574
+ return {
1575
+ "query": query,
1576
+ "answer": response,
1577
+ "relevant_docs": documents,
1578
+ "query_info": query_info
1579
+ }
1580
+
1581
+ except Exception as e:
1582
+ logger.error(f"Error processing query: {str(e)}")
1583
+
1584
+ return {
1585
+ "query": query,
1586
+ "answer": "ขออภัย ไม่สามารถประมวลผลคำตอบได้ในขณะนี้",
1587
+ "error": "Maximum retry attempts reached"
1588
+ }
1589
 
 
 
 
 
 
 
 
1590
  # def main():
1591
  # """Main function demonstrating hybrid retrieval"""
1592
  # try:
 
1605
  # pipeline.load_data(raw_data)
1606
 
1607
  # # Test queries with different semantic weights
1608
+ # queries = ["ค่าเทอมเท่าไหร่","เปิดเรียนวันไหน","ขั้นตอนการสมัครที่สาขานี้มีอะไรบ้าง","ต้องใช้ระดับภาษาอังกฤษเท่าไหร่ในการสมัครเรียนที่นี้","ถ้าจะไปติดต่อมาหลายต้องลง mrt อะไร","มีวิชาหลักเเละวิชาเลือกออะไรบ้าง", "ปีที่ 1 เทอม 1 ต้องเรียนอะไรบ้าง", "ปีที่ 2 เทอม 1 ต้องเรียนอะไรบ้าง"]
1609
+ # # queries = ["ขั้นตอนการสมัคเรียนที่สาขานี้มีอะไรบ้าง" ,"ต้องใช้ระดับภาษาอังกฤษเท่าไหร่ในการสมัครเรียนที่นี้"]
1610
  # print("=" * 80)
1611
 
1612
  # for query in queries:
 
1618
  # except Exception as e:
1619
  # logger.error(f"Pipeline execution failed: {str(e)}")
1620
  # raise
1621
+
1622
  # if __name__ == "__main__":
1623
  # main()