| """ |
| Logging module for XENO Bot |
| Handles CSV logging for responses and timing data |
| """ |
|
|
| import csv |
| import os |
| from datetime import datetime |
| from typing import Dict, List, Optional, Tuple |
|
|
|
|
| CHAT_LOG_DIR = os.environ.get("CHAT_LOG_DIR", "chats") |
| RESPONSE_LOG_PATH = os.path.join(CHAT_LOG_DIR, "responses.csv") |
| TIMING_LOG_PATH = os.path.join(CHAT_LOG_DIR, "timing.csv") |
|
|
| RESPONSE_HEADERS = [ |
| "Timestamp", |
| "Session_ID", |
| "Question", |
| "Answer", |
| "Source_IDs", |
| "Knowledge_Q1", |
| "Knowledge_A1", |
| "Knowledge_Q2", |
| "Knowledge_A2", |
| ] |
|
|
| TIMING_HEADERS = [ |
| "Timestamp", |
| "Session_ID", |
| "Question", |
| "Total_Time_MS", |
| "Intent_Classification_MS", |
| "Memory_Retrieval_MS", |
| "RAG_Retrieval_MS", |
| "Embedding_Generation_MS", |
| "Similarity_Calculation_MS", |
| "Context_Processing_MS", |
| "LLM_Generation_MS", |
| "Memory_Update_MS", |
| "Logging_MS", |
| "Error_Step", |
| "Notes", |
| ] |
|
|
| def _append_csv_row(path: str, headers: List[str], row: List): |
| """Create CSV with headers if needed, then append a row.""" |
| try: |
| os.makedirs(CHAT_LOG_DIR, exist_ok=True) |
|
|
| if not os.path.exists(path): |
| with open(path, "w", newline="", encoding="utf-8") as f: |
| writer = csv.writer(f) |
| writer.writerow(headers) |
|
|
| with open(path, "a", newline="", encoding="utf-8") as f: |
| writer = csv.writer(f) |
| writer.writerow(row) |
| except Exception as e: |
| print(f"Failed to append CSV row to {path}: {e}") |
|
|
|
|
| def log_response( |
| question: str, |
| answer: str, |
| source_ids: str, |
| knowledge_pairs: List[Tuple[str, str]], |
| session_id: str, |
| timer=None, |
| ): |
| """ |
| Log response to CSV file |
| |
| Args: |
| question: User's question |
| answer: Generated answer |
| source_ids: Source IDs used |
| knowledge_pairs: Knowledge base Q&A pairs used |
| session_id: Session identifier |
| timer: Optional timer object for tracking |
| """ |
| if timer: |
| with timer.time_step("response_logging"): |
| _log_response_impl( |
| question, answer, source_ids, knowledge_pairs, session_id |
| ) |
| else: |
| _log_response_impl(question, answer, source_ids, knowledge_pairs, session_id) |
|
|
|
|
| def _log_response_impl( |
| question: str, |
| answer: str, |
| source_ids: str, |
| knowledge_pairs: List[Tuple[str, str]], |
| session_id: str, |
| ): |
| """Internal implementation of response logging""" |
| timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") |
|
|
| |
| knowledge_question_1 = knowledge_pairs[0][0] if len(knowledge_pairs) > 0 else "N/A" |
| knowledge_answer_1 = knowledge_pairs[0][1] if len(knowledge_pairs) > 0 else "N/A" |
| knowledge_question_2 = knowledge_pairs[1][0] if len(knowledge_pairs) > 1 else "N/A" |
| knowledge_answer_2 = knowledge_pairs[1][1] if len(knowledge_pairs) > 1 else "N/A" |
|
|
| row = [ |
| timestamp, |
| session_id, |
| question, |
| answer, |
| source_ids, |
| knowledge_question_1, |
| knowledge_answer_1, |
| knowledge_question_2, |
| knowledge_answer_2, |
| ] |
|
|
| try: |
| _append_csv_row(RESPONSE_LOG_PATH, RESPONSE_HEADERS, row) |
| print(f"Logged response: {question} | Source IDs: {source_ids}") |
| except Exception as e: |
| print(f"Failed to log response: {e}") |
|
|
|
|
| def log_timing_data( |
| question: str, |
| session_id: str, |
| timing_summary: Dict, |
| error_step: Optional[str] = None, |
| notes: Optional[str] = None, |
| ): |
| """ |
| Log timing data to CSV file |
| |
| Args: |
| question: User's question |
| session_id: Session identifier |
| timing_summary: Timing summary dictionary |
| error_step: Step where error occurred (if any) |
| notes: Additional notes |
| """ |
| timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") |
| step_times = timing_summary["step_times"] |
|
|
| |
| truncated_question = question[:100] + "..." if len(question) > 100 else question |
|
|
| row = [ |
| timestamp, |
| session_id, |
| truncated_question, |
| timing_summary["total_time_ms"], |
| step_times.get("intent_classification", 0), |
| step_times.get("memory_retrieval", 0), |
| step_times.get("rag_retrieval", 0), |
| step_times.get("embedding_generation", 0), |
| step_times.get("similarity_calculation", 0), |
| step_times.get("context_processing", 0), |
| step_times.get("llm_generation", 0), |
| step_times.get("memory_update", 0), |
| step_times.get("response_logging", 0), |
| error_step or "", |
| notes or "", |
| ] |
|
|
| try: |
| _append_csv_row(TIMING_LOG_PATH, TIMING_HEADERS, row) |
| print(f"Logged timing data: Total {timing_summary['total_time_ms']}ms") |
| except Exception as e: |
| print(f"Failed to log timing data: {e}") |
|
|
|
|
|
|