diff --git a/src/database/adapters/__init__.py b/src/database/adapters/__init__.py index 4bcb9c8e..78902f77 100644 --- a/src/database/adapters/__init__.py +++ b/src/database/adapters/__init__.py @@ -240,10 +240,18 @@ class ConversationAdapters: def get_openai_conversation_config(): return OpenAIProcessorConversationConfig.objects.filter().first() + @staticmethod + async def aget_openai_conversation_config(): + return await OpenAIProcessorConversationConfig.objects.filter().afirst() + @staticmethod def get_offline_chat_conversation_config(): return OfflineChatProcessorConversationConfig.objects.filter().first() + @staticmethod + async def aget_offline_chat_conversation_config(): + return await OfflineChatProcessorConversationConfig.objects.filter().afirst() + @staticmethod def has_valid_offline_conversation_config(): return OfflineChatProcessorConversationConfig.objects.filter(enabled=True).exists() @@ -267,10 +275,21 @@ class ConversationAdapters: return None return config.setting + @staticmethod + async def aget_conversation_config(user: KhojUser): + config = await UserConversationConfig.objects.filter(user=user).prefetch_related("setting").afirst() + if not config: + return None + return config.setting + @staticmethod def get_default_conversation_config(): return ChatModelOptions.objects.filter().first() + @staticmethod + async def aget_default_conversation_config(): + return await ChatModelOptions.objects.filter().afirst() + @staticmethod def save_conversation(user: KhojUser, conversation_log: dict): conversation = Conversation.objects.filter(user=user) diff --git a/src/khoj/processor/conversation/openai/gpt.py b/src/khoj/processor/conversation/openai/gpt.py index b86ebc6b..31cfda1e 100644 --- a/src/khoj/processor/conversation/openai/gpt.py +++ b/src/khoj/processor/conversation/openai/gpt.py @@ -1,5 +1,6 @@ # Standard Packages import logging +import json from datetime import datetime, timedelta from typing import Optional @@ -31,6 +32,10 @@ def extract_questions( """ Infer search queries to retrieve relevant notes to answer user query """ + + def _valid_question(question: str): + return not is_none_or_empty(question) and question != "[]" + # Extract Past User Message and Inferred Questions from Conversation Log chat_history = "".join( [ @@ -70,7 +75,7 @@ def extract_questions( # Extract, Clean Message from GPT's Response try: - questions = ( + split_questions = ( response.content.strip(empty_escape_sequences) .replace("['", '["') .replace("']", '"]') @@ -79,9 +84,18 @@ def extract_questions( .replace('"]', "") .split('", "') ) + questions = [] + + for question in split_questions: + if question not in questions and _valid_question(question): + questions.append(question) + + if is_none_or_empty(questions): + raise ValueError("GPT returned empty JSON") except: logger.warning(f"GPT returned invalid JSON. Falling back to using user message as search query.\n{response}") questions = [text] + logger.debug(f"Extracted Questions by GPT: {questions}") return questions diff --git a/src/khoj/routers/api.py b/src/khoj/routers/api.py index 556e18a2..790c5041 100644 --- a/src/khoj/routers/api.py +++ b/src/khoj/routers/api.py @@ -55,6 +55,7 @@ from database.models import ( Entry as DbEntry, GithubConfig, NotionConfig, + ChatModelOptions, ) @@ -669,7 +670,16 @@ async def extract_references_and_questions( # Infer search queries from user message with timer("Extracting search queries took", logger): # If we've reached here, either the user has enabled offline chat or the openai model is enabled. - if await ConversationAdapters.ahas_offline_chat(): + offline_chat_config = await ConversationAdapters.aget_offline_chat_conversation_config() + conversation_config = await ConversationAdapters.aget_conversation_config(user) + if conversation_config is None: + conversation_config = await ConversationAdapters.aget_default_conversation_config() + openai_chat_config = await ConversationAdapters.aget_openai_conversation_config() + if ( + offline_chat_config + and offline_chat_config.enabled + and conversation_config.model_type == ChatModelOptions.ModelType.OFFLINE.value + ): using_offline_chat = True offline_chat = await ConversationAdapters.get_offline_chat() chat_model = offline_chat.chat_model @@ -681,7 +691,7 @@ async def extract_references_and_questions( inferred_queries = extract_questions_offline( defiltered_query, loaded_model=loaded_model, conversation_log=meta_log, should_extract_questions=False ) - elif await ConversationAdapters.has_openai_chat(): + elif openai_chat_config and conversation_config.model_type == ChatModelOptions.ModelType.OPENAI.value: openai_chat_config = await ConversationAdapters.get_openai_chat_config() openai_chat = await ConversationAdapters.get_openai_chat() api_key = openai_chat_config.api_key @@ -690,11 +700,6 @@ async def extract_references_and_questions( defiltered_query, model=chat_model, api_key=api_key, conversation_log=meta_log ) - logger.info(f"🔍 Inferred queries: {inferred_queries}") - logger.info(f"🔍 Defiltered query: {defiltered_query}") - logger.info(f"using max distance: {d}") - logger.info(f"using filters: {filters_in_query}") - logger.info(f"Max results: {n}") # Collate search results as context for GPT with timer("Searching knowledge base took", logger): result_list = [] @@ -711,20 +716,7 @@ async def extract_references_and_questions( common=common, ) ) - logger.info(f"🔍 Found {len(result_list)} results") - logger.info(f"Confidence scores: {[item.score for item in result_list]}") - # Dedupe the results again, as duplicates may be returned across queries. - with open("compiled_references_pre_deduped.txt", "w") as f: - for item in compiled_references: - f.write(f"{item}\n") - result_list = text_search.deduplicated_search_responses(result_list) compiled_references = [item.additional["compiled"] for item in result_list] - with open("compiled_references_deduped.txt", "w") as f: - for item in compiled_references: - f.write(f"{item}\n") - - logger.info(f"🔍 Deduped results: {len(result_list)}") - return compiled_references, inferred_queries, defiltered_query