Source code for maeser.generate_response
# SPDX-License-Identifier: LGPL-3.0-or-later
import os
# Import Maeser components
from maeser.chat.chat_logs import ChatLogsManager
from maeser.chat.chat_session_manager import ChatSessionManager
from maeser.graphs.universal_rag import get_universal_rag
from langgraph.graph.graph import CompiledGraph
# Import configuration
from maeser.config import (
LOG_SOURCE_PATH, OPENAI_API_KEY, VEC_STORE_PATH, CHAT_HISTORY_PATH, LLM_MODEL_NAME
)
# Set API key
os.environ["OPENAI_API_KEY"] = OPENAI_API_KEY
# Path to the bot data directory
BOT_DATA_PATH = VEC_STORE_PATH
# Managers
chat_logs_manager = ChatLogsManager(CHAT_HISTORY_PATH)
sessions_manager = ChatSessionManager(chat_logs_manager=chat_logs_manager)
# Unified session tracking for Maeser: {session_key (user_id:course_id): maeser_session_id}
# This dictionary will hold the Maeser chat session IDs for ALL interfaces (Discord, Teams, etc.)
global_maeser_sessions = {}
# --- Utility Functions (shared by all bot handlers) ---
[docs]
def parse_data_from_bot_txt(path):
"""Parses a bot config file to extract rules and datasets."""
sections = {}
current_header = None
buffer = []
with open(path, "r", encoding="utf-8") as f:
for line in f:
line = line.strip()
if line.startswith("#"):
if current_header:
sections[current_header] = buffer if len(buffer) > 1 else buffer[0] if buffer else ""
current_header = line[1:].lower()
buffer = []
elif current_header:
buffer.append(line)
# Save the last section
if current_header:
sections[current_header] = buffer if len(buffer) > 1 else buffer[0] if buffer else ""
return sections
[docs]
def get_valid_course_ids():
"""Retrieves a list of valid course IDs from the bot_data directory."""
if not os.path.exists(BOT_DATA_PATH):
print("Error: bot_data directory not found. Please ensure it exists with course subdirectories.")
return []
return [
name for name in os.listdir(BOT_DATA_PATH)
if os.path.isdir(os.path.join(BOT_DATA_PATH, name))
]
[docs]
def register_branch(branch_name:str, course_id:str, bot_config_path:str):
"""Registers a branch to the session handler.
Args:
branch_name (str): The name to give the branch.
course_id (str): The ID for the course to be registered. This should match the name of the course's vector store directory
bot_config_path (str): The path to `bot.txt` for the course's chatbot.
"""
parsed_data = parse_data_from_bot_txt(bot_config_path)
# Ensure required keys exist in parsed data
if "rules" not in parsed_data or "datasets" not in parsed_data:
return f"Error: 'rules' or 'datasets' section missing in bot.txt for course '{course_id}'."
rules = parsed_data["rules"]
datasets = parsed_data["datasets"]
if isinstance(datasets, str):
datasets = [datasets]
vectorstore_config = {
dataset: os.path.join(VEC_STORE_PATH, course_id, dataset) for dataset in datasets
}
ruleset = "\n".join(rules) + "\n{context}\n"
universal_rag: CompiledGraph = get_universal_rag(
vectorstore_config=vectorstore_config,
memory_filepath=f"{LOG_SOURCE_PATH}/universal_memory_{course_id}.db",
api_key=OPENAI_API_KEY,
system_prompt_text=ruleset,
model=LLM_MODEL_NAME
)
sessions_manager.register_branch(
branch_name=branch_name,
branch_label=f"Universal-{course_id}",
graph=universal_rag
)
print(f"Registered Maeser bot branch for course: {course_id}")
# --- Main Chat Handling Function (Unified Logic) ---
[docs]
def handle_message(user_id: str, course_id: str, message_text: str) -> str:
"""Handles a message from any interface, routing it to the correct Maeser session.
Manages bot registration and session creation for Maeser.
Args:
user_id (str): The unique string identifier for the user.
course_id (str): The string identifier corresponding to a configured course in `vec_store_path` (defined in `config.yaml`).
message_text (str): The user's question or input message.
Returns:
str: A string representing the chatbot's final response message.
"""
# Verify bot config exists for the given course ID
bot_config_path = f"{BOT_DATA_PATH}/{course_id}/bot.txt"
if not os.path.exists(bot_config_path):
return f"Bot config for course '{course_id}' not found. Please ensure the course ID is valid and configured."
branch_name = f"universal_{course_id}"
# Register the Maeser bot branch if it hasn't been registered yet
if branch_name not in sessions_manager.branches:
register_branch(branch_name, course_id, bot_config_path)
# Get or create a Maeser session for the unique user+course combination
session_key = f"{user_id}:{course_id}"
if session_key not in global_maeser_sessions:
maeser_session_id = sessions_manager.get_new_session_id(branch_name)
global_maeser_sessions[session_key] = maeser_session_id
print(f"Started new Maeser session '{maeser_session_id}' for user '{user_id}' in course '{course_id}'.")
else:
maeser_session_id = global_maeser_sessions[session_key]
# Ask the question to the Maeser session and return the reply
response = sessions_manager.ask_question(message_text, branch_name, maeser_session_id)
return response['messages'][-1]