From cac26dafe3a51223b47000b7c558f5081d5f316e Mon Sep 17 00:00:00 2001 From: Debanjum Singh Solanky Date: Fri, 15 Mar 2024 11:58:39 +0530 Subject: [PATCH 01/13] Only create new chat on get if a specific chat id, slug isn't requested --- src/khoj/database/adapters/__init__.py | 13 +++++-------- src/khoj/routers/api_chat.py | 7 ++++--- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/src/khoj/database/adapters/__init__.py b/src/khoj/database/adapters/__init__.py index 9d74df05..6de359ae 100644 --- a/src/khoj/database/adapters/__init__.py +++ b/src/khoj/database/adapters/__init__.py @@ -439,16 +439,13 @@ class ConversationAdapters: user: KhojUser, client_application: ClientApplication = None, conversation_id: int = None, slug: str = None ): if conversation_id: - conversation = Conversation.objects.filter(user=user, client=client_application, id=conversation_id) + return await Conversation.objects.filter(user=user, client=client_application, id=conversation_id).afirst() elif slug: - conversation = Conversation.objects.filter(user=user, client=client_application, slug=slug) + return await Conversation.objects.filter(user=user, client=client_application, slug=slug).afirst() else: - conversation = Conversation.objects.filter(user=user, client=client_application).order_by("-updated_at") - - if await conversation.aexists(): - return await conversation.afirst() - - return await Conversation.objects.acreate(user=user, client=client_application, slug=slug) + return await ( + Conversation.objects.filter(user=user, client=client_application).order_by("-updated_at").afirst() + ) or Conversation.objects.acreate(user=user, client=client_application, slug=slug) @staticmethod async def adelete_conversation_by_user( diff --git a/src/khoj/routers/api_chat.py b/src/khoj/routers/api_chat.py index 7a99869c..79f13351 100644 --- a/src/khoj/routers/api_chat.py +++ b/src/khoj/routers/api_chat.py @@ -250,9 +250,10 @@ async def chat( formatted_help = help_message.format(model=model_type, version=state.khoj_version, device=get_device()) return StreamingResponse(iter([formatted_help]), media_type="text/event-stream", status_code=200) - meta_log = ( - await ConversationAdapters.aget_conversation_by_user(user, request.user.client_app, conversation_id, slug) - ).conversation_log + conversation = await ConversationAdapters.aget_conversation_by_user( + user, request.user.client_app, conversation_id, slug + ) + meta_log = conversation.conversation_log if conversation else {} if conversation_commands == [ConversationCommand.Default]: conversation_commands = await aget_relevant_information_sources(q, meta_log) From c9e05dc18422ee13d0851b93b430dae6a484978a Mon Sep 17 00:00:00 2001 From: Debanjum Singh Solanky Date: Fri, 15 Mar 2024 12:03:47 +0530 Subject: [PATCH 02/13] Get conversation by title when requested via chat API --- src/khoj/database/adapters/__init__.py | 8 ++++---- src/khoj/routers/api_chat.py | 11 ++++++++--- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/khoj/database/adapters/__init__.py b/src/khoj/database/adapters/__init__.py index 6de359ae..e6927d27 100644 --- a/src/khoj/database/adapters/__init__.py +++ b/src/khoj/database/adapters/__init__.py @@ -436,16 +436,16 @@ class ConversationAdapters: @staticmethod async def aget_conversation_by_user( - user: KhojUser, client_application: ClientApplication = None, conversation_id: int = None, slug: str = None + user: KhojUser, client_application: ClientApplication = None, conversation_id: int = None, title: str = None ): if conversation_id: return await Conversation.objects.filter(user=user, client=client_application, id=conversation_id).afirst() - elif slug: - return await Conversation.objects.filter(user=user, client=client_application, slug=slug).afirst() + elif title: + return await Conversation.objects.filter(user=user, client=client_application, title=title).afirst() else: return await ( Conversation.objects.filter(user=user, client=client_application).order_by("-updated_at").afirst() - ) or Conversation.objects.acreate(user=user, client=client_application, slug=slug) + ) or Conversation.objects.acreate(user=user, client=client_application) @staticmethod async def adelete_conversation_by_user( diff --git a/src/khoj/routers/api_chat.py b/src/khoj/routers/api_chat.py index 79f13351..7cd04c98 100644 --- a/src/khoj/routers/api_chat.py +++ b/src/khoj/routers/api_chat.py @@ -224,7 +224,7 @@ async def chat( n: Optional[int] = 5, d: Optional[float] = 0.18, stream: Optional[bool] = False, - slug: Optional[str] = None, + title: Optional[str] = None, conversation_id: Optional[int] = None, city: Optional[str] = None, region: Optional[str] = None, @@ -251,9 +251,14 @@ async def chat( return StreamingResponse(iter([formatted_help]), media_type="text/event-stream", status_code=200) conversation = await ConversationAdapters.aget_conversation_by_user( - user, request.user.client_app, conversation_id, slug + user, request.user.client_app, conversation_id, title ) - meta_log = conversation.conversation_log if conversation else {} + if not conversation: + return Response( + content=f"No conversation found with requested id, title", media_type="text/plain", status_code=400 + ) + else: + meta_log = conversation.conversation_log if conversation_commands == [ConversationCommand.Default]: conversation_commands = await aget_relevant_information_sources(q, meta_log) From c792fa819f8c828f51f3976ae017efdc07fa073f Mon Sep 17 00:00:00 2001 From: Debanjum Singh Solanky Date: Fri, 15 Mar 2024 13:06:08 +0530 Subject: [PATCH 03/13] Fix setting chat session title from Desktop app Pass auth headers to not have the chat session title update request fail --- src/interface/desktop/chat.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/interface/desktop/chat.html b/src/interface/desktop/chat.html index cc081da7..c1341290 100644 --- a/src/interface/desktop/chat.html +++ b/src/interface/desktop/chat.html @@ -918,7 +918,7 @@ let newTitle = conversationTitleInput.value; if (newTitle != null) { let editURL = `/api/chat/title?client=web&conversation_id=${incomingConversationId}&title=${newTitle}`; - fetch(`${hostURL}${editURL}` , { method: "PATCH" }) + fetch(`${hostURL}${editURL}` , { method: "PATCH", headers }) .then(response => response.ok ? response.json() : Promise.reject(response)) .then(data => { conversationButton.textContent = newTitle; From 924b1215ce42f0571547a9ac0e2bb7f91d45be51 Mon Sep 17 00:00:00 2001 From: Debanjum Singh Solanky Date: Fri, 15 Mar 2024 13:09:30 +0530 Subject: [PATCH 04/13] Allow unset locale for Google authenticated user --- .../migrations/0031_alter_googleuser_locale.py | 17 +++++++++++++++++ src/khoj/database/models/__init__.py | 2 +- 2 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 src/khoj/database/migrations/0031_alter_googleuser_locale.py diff --git a/src/khoj/database/migrations/0031_alter_googleuser_locale.py b/src/khoj/database/migrations/0031_alter_googleuser_locale.py new file mode 100644 index 00000000..99c4573a --- /dev/null +++ b/src/khoj/database/migrations/0031_alter_googleuser_locale.py @@ -0,0 +1,17 @@ +# Generated by Django 4.2.10 on 2024-03-15 10:04 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("database", "0030_conversation_slug_and_title"), + ] + + operations = [ + migrations.AlterField( + model_name="googleuser", + name="locale", + field=models.CharField(blank=True, default=None, max_length=200, null=True), + ), + ] diff --git a/src/khoj/database/models/__init__.py b/src/khoj/database/models/__init__.py index b74aaa11..3f8f50b4 100644 --- a/src/khoj/database/models/__init__.py +++ b/src/khoj/database/models/__init__.py @@ -43,7 +43,7 @@ class GoogleUser(models.Model): given_name = models.CharField(max_length=200, null=True, default=None, blank=True) family_name = models.CharField(max_length=200, null=True, default=None, blank=True) picture = models.CharField(max_length=200, null=True, default=None) - locale = models.CharField(max_length=200) + locale = models.CharField(max_length=200, null=True, default=None, blank=True) def __str__(self): return self.name From ec0c35b7edf89d498dc6303605c9557571498a4e Mon Sep 17 00:00:00 2001 From: Debanjum Singh Solanky Date: Fri, 15 Mar 2024 13:49:47 +0530 Subject: [PATCH 05/13] Improve delete, rename chat session UX in Desktop, Web app - Ask for Confirmation before deleting chat session in Desktop, Web app - Save chat session rename on hitting enter in title edit input box - No need to flash previous conversation cleared status message - Move chat session delete button after rename button in Desktop app --- src/interface/desktop/chat.html | 53 +++++++++++++++++--------------- src/khoj/interface/web/chat.html | 6 +++- 2 files changed, 34 insertions(+), 25 deletions(-) diff --git a/src/interface/desktop/chat.html b/src/interface/desktop/chat.html index c1341290..94cde782 100644 --- a/src/interface/desktop/chat.html +++ b/src/interface/desktop/chat.html @@ -794,7 +794,6 @@ chatBody.dataset.conversationId = ""; chatBody.dataset.conversationTitle = ""; loadChat(); - flashStatusInChatInput("🗑 Cleared previous conversation history"); }) .catch(err => { flashStatusInChatInput("⛔️ Failed to clear conversation history"); @@ -856,28 +855,6 @@ let conversationMenu = document.createElement('div'); conversationMenu.classList.add("conversation-menu"); - let deleteButton = document.createElement('button'); - deleteButton.innerHTML = "Delete"; - deleteButton.classList.add("delete-conversation-button"); - deleteButton.classList.add("three-dot-menu-button-item"); - deleteButton.addEventListener('click', function() { - let deleteURL = `/api/chat/history?client=web&conversation_id=${incomingConversationId}`; - fetch(`${hostURL}${deleteURL}` , { method: "DELETE", headers }) - .then(response => response.ok ? response.json() : Promise.reject(response)) - .then(data => { - let chatBody = document.getElementById("chat-body"); - chatBody.innerHTML = ""; - chatBody.dataset.conversationId = ""; - chatBody.dataset.conversationTitle = ""; - loadChat(); - }) - .catch(err => { - return; - }); - }); - conversationMenu.appendChild(deleteButton); - threeDotMenu.appendChild(conversationMenu); - let editTitleButton = document.createElement('button'); editTitleButton.innerHTML = "Rename"; editTitleButton.classList.add("edit-title-button"); @@ -903,12 +880,13 @@ conversationTitleInput.addEventListener('click', function(event) { event.stopPropagation(); + }); + conversationTitleInput.addEventListener('keydown', function(event) { if (event.key === "Enter") { event.preventDefault(); conversationTitleInputButton.click(); } }); - conversationTitleInputBox.appendChild(conversationTitleInput); let conversationTitleInputButton = document.createElement('button'); conversationTitleInputButton.innerHTML = "Save"; @@ -931,8 +909,35 @@ conversationTitleInputBox.appendChild(conversationTitleInputButton); conversationMenu.appendChild(conversationTitleInputBox); }); + conversationMenu.appendChild(editTitleButton); threeDotMenu.appendChild(conversationMenu); + + let deleteButton = document.createElement('button'); + deleteButton.innerHTML = "Delete"; + deleteButton.classList.add("delete-conversation-button"); + deleteButton.classList.add("three-dot-menu-button-item"); + deleteButton.addEventListener('click', function() { + // Ask for confirmation before deleting chat session + let confirmation = confirm('Are you sure you want to delete this chat session?'); + if (!confirmation) return; + let deleteURL = `/api/chat/history?client=web&conversation_id=${incomingConversationId}`; + fetch(`${hostURL}${deleteURL}` , { method: "DELETE", headers }) + .then(response => response.ok ? response.json() : Promise.reject(response)) + .then(data => { + let chatBody = document.getElementById("chat-body"); + chatBody.innerHTML = ""; + chatBody.dataset.conversationId = ""; + chatBody.dataset.conversationTitle = ""; + loadChat(); + }) + .catch(err => { + return; + }); + }); + + conversationMenu.appendChild(deleteButton); + threeDotMenu.appendChild(conversationMenu); }); threeDotMenu.appendChild(threeDotMenuButton); conversationButton.appendChild(threeDotMenu); diff --git a/src/khoj/interface/web/chat.html b/src/khoj/interface/web/chat.html index c251bff2..35047c31 100644 --- a/src/khoj/interface/web/chat.html +++ b/src/khoj/interface/web/chat.html @@ -1008,6 +1008,8 @@ To get started, just start typing below. You can also type / to see a list of co conversationTitleInput.addEventListener('click', function(event) { event.stopPropagation(); + }); + conversationTitleInput.addEventListener('keydown', function(event) { if (event.key === "Enter") { event.preventDefault(); conversationTitleInputButton.click(); @@ -1044,6 +1046,9 @@ To get started, just start typing below. You can also type / to see a list of co deleteButton.classList.add("delete-conversation-button"); deleteButton.classList.add("three-dot-menu-button-item"); deleteButton.addEventListener('click', function() { + // Ask for confirmation before deleting chat session + let confirmation = confirm('Are you sure you want to delete this chat session?'); + if (!confirmation) return; let deleteURL = `/api/chat/history?client=web&conversation_id=${incomingConversationId}`; fetch(deleteURL , { method: "DELETE" }) .then(response => response.ok ? response.json() : Promise.reject(response)) @@ -1053,7 +1058,6 @@ To get started, just start typing below. You can also type / to see a list of co chatBody.dataset.conversationId = ""; chatBody.dataset.conversationTitle = ""; loadChat(); - flashStatusInChatInput("🗑 Cleared previous conversation history"); }) .catch(err => { flashStatusInChatInput("⛔️ Failed to clear conversation history"); From ecddf98430af8222066e9bb7349ca0a72244bb53 Mon Sep 17 00:00:00 2001 From: Debanjum Singh Solanky Date: Fri, 15 Mar 2024 14:52:29 +0530 Subject: [PATCH 06/13] Handle truncation when single long non-system chat message Previously was assuming the system prompt is being always passed as the first message. So expected there to be at least 2 messages in logs. This broke chat actors querying with single long non system message. A more robust way to extract system prompt is via the message role instead --- src/khoj/processor/conversation/utils.py | 19 ++++-- tests/test_conversation_utils.py | 77 +++++++++++++++++------- 2 files changed, 67 insertions(+), 29 deletions(-) diff --git a/src/khoj/processor/conversation/utils.py b/src/khoj/processor/conversation/utils.py index b384ad7a..15a4970e 100644 --- a/src/khoj/processor/conversation/utils.py +++ b/src/khoj/processor/conversation/utils.py @@ -199,19 +199,26 @@ def truncate_messages( f"Fallback to default chat model tokenizer: {default_tokenizer}.\nConfigure tokenizer for unsupported model: {model_name} in Khoj settings to improve context stuffing." ) - system_message = messages.pop() - assert type(system_message.content) == str - system_message_tokens = len(encoder.encode(system_message.content)) + # Extract system message from messages + system_message = None + for idx, message in enumerate(messages): + if message.role == "system": + system_message = messages.pop(idx) + break + + system_message_tokens = ( + len(encoder.encode(system_message.content)) if system_message and type(system_message.content) == str else 0 + ) tokens = sum([len(encoder.encode(message.content)) for message in messages if type(message.content) == str]) + + # Drop older messages until under max supported prompt size by model while (tokens + system_message_tokens) > max_prompt_size and len(messages) > 1: messages.pop() - assert type(system_message.content) == str tokens = sum([len(encoder.encode(message.content)) for message in messages if type(message.content) == str]) # Truncate current message if still over max supported prompt size by model if (tokens + system_message_tokens) > max_prompt_size: - assert type(system_message.content) == str current_message = "\n".join(messages[0].content.split("\n")[:-1]) if type(messages[0].content) == str else "" original_question = "\n".join(messages[0].content.split("\n")[-1:]) if type(messages[0].content) == str else "" original_question = f"\n{original_question}" @@ -223,7 +230,7 @@ def truncate_messages( ) messages = [ChatMessage(content=truncated_message + original_question, role=messages[0].role)] - return messages + [system_message] + return messages + [system_message] if system_message else messages def reciprocal_conversation_to_chatml(message_pair): diff --git a/tests/test_conversation_utils.py b/tests/test_conversation_utils.py index 52db0002..bc8c5315 100644 --- a/tests/test_conversation_utils.py +++ b/tests/test_conversation_utils.py @@ -19,49 +19,80 @@ class TestTruncateMessage: encoder = tiktoken.encoding_for_model(model_name) def test_truncate_message_all_small(self): - chat_messages = ChatMessageFactory.build_batch(500) + # Arrange + chat_history = ChatMessageFactory.build_batch(500) - prompt = utils.truncate_messages(chat_messages, self.max_prompt_size, self.model_name) - tokens = sum([len(self.encoder.encode(message.content)) for message in prompt]) + # Act + truncated_chat_history = utils.truncate_messages(chat_history, self.max_prompt_size, self.model_name) + tokens = sum([len(self.encoder.encode(message.content)) for message in truncated_chat_history]) + # Assert # The original object has been modified. Verify certain properties - assert len(chat_messages) < 500 - assert len(chat_messages) > 1 + assert len(chat_history) < 500 + assert len(chat_history) > 1 assert tokens <= self.max_prompt_size def test_truncate_message_first_large(self): - chat_messages = ChatMessageFactory.build_batch(25) + # Arrange + chat_history = ChatMessageFactory.build_batch(25) big_chat_message = ChatMessageFactory.build(content=factory.Faker("paragraph", nb_sentences=2000)) big_chat_message.content = big_chat_message.content + "\n" + "Question?" copy_big_chat_message = big_chat_message.copy() - chat_messages.insert(0, big_chat_message) - tokens = sum([len(self.encoder.encode(message.content)) for message in chat_messages]) + chat_history.insert(0, big_chat_message) + tokens = sum([len(self.encoder.encode(message.content)) for message in chat_history]) - prompt = utils.truncate_messages(chat_messages, self.max_prompt_size, self.model_name) - tokens = sum([len(self.encoder.encode(message.content)) for message in prompt]) + # Act + truncated_chat_history = utils.truncate_messages(chat_history, self.max_prompt_size, self.model_name) + tokens = sum([len(self.encoder.encode(message.content)) for message in truncated_chat_history]) + # Assert # The original object has been modified. Verify certain properties - assert len(chat_messages) == 1 - assert prompt[0] != copy_big_chat_message + assert len(chat_history) == 1 + assert truncated_chat_history[0] != copy_big_chat_message assert tokens <= self.max_prompt_size def test_truncate_message_last_large(self): - chat_messages = ChatMessageFactory.build_batch(25) + # Arrange + chat_history = ChatMessageFactory.build_batch(25) + chat_history[0].role = "system" # Mark the first message as system message big_chat_message = ChatMessageFactory.build(content=factory.Faker("paragraph", nb_sentences=1000)) big_chat_message.content = big_chat_message.content + "\n" + "Question?" copy_big_chat_message = big_chat_message.copy() - chat_messages.insert(0, big_chat_message) - tokens = sum([len(self.encoder.encode(message.content)) for message in chat_messages]) + chat_history.insert(0, big_chat_message) + initial_tokens = sum([len(self.encoder.encode(message.content)) for message in chat_history]) - prompt = utils.truncate_messages(chat_messages, self.max_prompt_size, self.model_name) - tokens = sum([len(self.encoder.encode(message.content)) for message in prompt]) + # Act + truncated_chat_history = utils.truncate_messages(chat_history, self.max_prompt_size, self.model_name) + final_tokens = sum([len(self.encoder.encode(message.content)) for message in truncated_chat_history]) + # Assert # The original object has been modified. Verify certain properties. - assert len(prompt) == ( - len(chat_messages) + 1 + assert len(truncated_chat_history) == ( + len(chat_history) + 1 ) # Because the system_prompt is popped off from the chat_messages lsit - assert len(prompt) < 26 - assert len(prompt) > 1 - assert prompt[0] != copy_big_chat_message - assert tokens <= self.max_prompt_size + assert len(truncated_chat_history) < 26 + assert len(truncated_chat_history) > 1 + assert truncated_chat_history[0] != copy_big_chat_message + assert initial_tokens > self.max_prompt_size + assert final_tokens <= self.max_prompt_size + + def test_truncate_single_large_non_system_message(self): + # Arrange + big_chat_message = ChatMessageFactory.build(content=factory.Faker("paragraph", nb_sentences=2000)) + big_chat_message.content = big_chat_message.content + "\n" + "Question?" + big_chat_message.role = "user" + copy_big_chat_message = big_chat_message.copy() + chat_messages = [big_chat_message] + initial_tokens = sum([len(self.encoder.encode(message.content)) for message in chat_messages]) + + # Act + truncated_chat_history = utils.truncate_messages(chat_messages, self.max_prompt_size, self.model_name) + final_tokens = sum([len(self.encoder.encode(message.content)) for message in truncated_chat_history]) + + # Assert + # The original object has been modified. Verify certain properties + assert initial_tokens > self.max_prompt_size + assert final_tokens <= self.max_prompt_size + assert len(chat_messages) == 1 + assert truncated_chat_history[0] != copy_big_chat_message From 9a068dadbf9d03c826f163be5847e591d354994e Mon Sep 17 00:00:00 2001 From: Debanjum Singh Solanky Date: Fri, 15 Mar 2024 17:50:33 +0530 Subject: [PATCH 07/13] Fix extract questions prompt to use YYYY-MM-DD date filter format --- src/khoj/processor/conversation/openai/gpt.py | 3 ++- src/khoj/processor/conversation/prompts.py | 4 ++-- src/khoj/routers/api.py | 1 + 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/khoj/processor/conversation/openai/gpt.py b/src/khoj/processor/conversation/openai/gpt.py index dd01efd4..644bb961 100644 --- a/src/khoj/processor/conversation/openai/gpt.py +++ b/src/khoj/processor/conversation/openai/gpt.py @@ -46,7 +46,8 @@ def extract_questions( last_new_year = current_new_year.replace(year=today.year - 1) prompt = prompts.extract_questions.format( - current_date=today.strftime("%A, %Y-%m-%d"), + current_date=today.strftime("%Y-%m-%d"), + day_of_week=today.strftime("%A"), last_new_year=last_new_year.strftime("%Y"), last_new_year_date=last_new_year.strftime("%Y-%m-%d"), current_new_year_date=current_new_year.strftime("%Y-%m-%d"), diff --git a/src/khoj/processor/conversation/prompts.py b/src/khoj/processor/conversation/prompts.py index a4256525..6a917a0e 100644 --- a/src/khoj/processor/conversation/prompts.py +++ b/src/khoj/processor/conversation/prompts.py @@ -221,8 +221,8 @@ You are Khoj, an extremely smart and helpful search assistant with the ability t - Break messages into multiple search queries when required to retrieve the relevant information. - Add date filters to your search queries from questions and answers when required to retrieve the relevant information. -What searches will you need to perform to answer the users question? Respond with search queries as list of strings in a JSON object. -Current Date: {current_date} +What searches will you perform to answer the users question? Respond with search queries as list of strings in a JSON object. +Current Date: {day_of_week}, {current_date} User's Location: {location} Q: How was my trip to Cambodia? diff --git a/src/khoj/routers/api.py b/src/khoj/routers/api.py index b54da90b..aa615113 100644 --- a/src/khoj/routers/api.py +++ b/src/khoj/routers/api.py @@ -346,6 +346,7 @@ async def extract_references_and_questions( # Collate search results as context for GPT with timer("Searching knowledge base took", logger): result_list = [] + logger.info(f"🔍 Searching knowledge base with queries: {inferred_queries}") for query in inferred_queries: n_items = min(n, 3) if using_offline_chat else n result_list.extend( From 4a1e6a22755e2fd84bf05c87568fcd0f2082dd81 Mon Sep 17 00:00:00 2001 From: Debanjum Singh Solanky Date: Fri, 15 Mar 2024 20:50:10 +0530 Subject: [PATCH 08/13] Convert deleted old user requests log line to debug from info --- src/khoj/configure.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/khoj/configure.py b/src/khoj/configure.py index 95b32018..add685e3 100644 --- a/src/khoj/configure.py +++ b/src/khoj/configure.py @@ -355,4 +355,4 @@ def upload_telemetry(): @schedule.repeat(schedule.every(31).minutes) def delete_old_user_requests(): num_deleted = delete_user_requests() - logger.info(f"🗑️ Deleted {num_deleted[0]} day-old user requests") + logger.debug(f"🗑️ Deleted {num_deleted[0]} day-old user requests") From 62a83dc9bbfe3ecaed7dff0be18b2efbb444d4d4 Mon Sep 17 00:00:00 2001 From: Debanjum Singh Solanky Date: Fri, 15 Mar 2024 21:50:14 +0530 Subject: [PATCH 09/13] Fix online search actor to use natural dates not after: operator The recently added after: operator to online search actor was too restrictive, gave worse results than when just use natural language dates in search query --- src/khoj/processor/conversation/prompts.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/khoj/processor/conversation/prompts.py b/src/khoj/processor/conversation/prompts.py index 6a917a0e..b6465ca5 100644 --- a/src/khoj/processor/conversation/prompts.py +++ b/src/khoj/processor/conversation/prompts.py @@ -386,7 +386,7 @@ You are Khoj, an advanced google search assistant. You are tasked with construct - You will receive the conversation history as context. - Add as much context from the previous questions and answers as required into your search queries. - Break messages into multiple search queries when required to retrieve the relevant information. -- Use site: and after: google search operators when appropriate +- Use site: google search operators when appropriate - You have access to the the whole internet to retrieve information. - Official, up-to-date information about you, Khoj, is available at site:khoj.dev @@ -401,7 +401,7 @@ User: I like to use Hacker News to get my tech news. AI: Hacker News is an online forum for sharing and discussing the latest tech news. It is a great place to learn about new technologies and startups. Q: Summarize posts about vector databases on Hacker News since Feb 2024 -Khoj: {{"queries": ["site:news.ycombinator.com after:2024/02/01 vector database"]}} +Khoj: {{"queries": ["site:news.ycombinator.com vector database since 1 February 2024"]}} History: User: I'm currently living in New York but I'm thinking about moving to San Francisco. From ec6dc0daaf2dab68b498c3c8a4c663770b36c419 Mon Sep 17 00:00:00 2001 From: Debanjum Singh Solanky Date: Wed, 20 Mar 2024 22:56:09 +0530 Subject: [PATCH 10/13] Bump up the default gunicorn workers running on prod --- gunicorn-config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gunicorn-config.py b/gunicorn-config.py index 1760ae38..7b8090a7 100644 --- a/gunicorn-config.py +++ b/gunicorn-config.py @@ -1,7 +1,7 @@ import multiprocessing bind = "0.0.0.0:42110" -workers = 4 +workers = 12 worker_class = "uvicorn.workers.UvicornWorker" timeout = 120 keep_alive = 60 From aed4313cfcd9127322a7e6c9c867900c653945cb Mon Sep 17 00:00:00 2001 From: Debanjum Singh Solanky Date: Thu, 21 Mar 2024 02:44:42 +0530 Subject: [PATCH 11/13] Fix updating specific conversation by id from the chat API endpoint - Use the conversation id of the retrieved conversation rather than the potentially unset conversation id passed via API - await creating new chat when no chat id provided and no existing conversations exist --- src/khoj/database/adapters/__init__.py | 6 +++--- src/khoj/routers/api_chat.py | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/khoj/database/adapters/__init__.py b/src/khoj/database/adapters/__init__.py index e6927d27..882e8896 100644 --- a/src/khoj/database/adapters/__init__.py +++ b/src/khoj/database/adapters/__init__.py @@ -395,7 +395,7 @@ class ConversationAdapters: @staticmethod def get_conversation_by_user( user: KhojUser, client_application: ClientApplication = None, conversation_id: int = None - ): + ) -> Optional[Conversation]: if conversation_id: conversation = ( Conversation.objects.filter(user=user, client=client_application, id=conversation_id) @@ -437,7 +437,7 @@ class ConversationAdapters: @staticmethod async def aget_conversation_by_user( user: KhojUser, client_application: ClientApplication = None, conversation_id: int = None, title: str = None - ): + ) -> Optional[Conversation]: if conversation_id: return await Conversation.objects.filter(user=user, client=client_application, id=conversation_id).afirst() elif title: @@ -445,7 +445,7 @@ class ConversationAdapters: else: return await ( Conversation.objects.filter(user=user, client=client_application).order_by("-updated_at").afirst() - ) or Conversation.objects.acreate(user=user, client=client_application) + ) or await Conversation.objects.acreate(user=user, client=client_application) @staticmethod async def adelete_conversation_by_user( diff --git a/src/khoj/routers/api_chat.py b/src/khoj/routers/api_chat.py index 7cd04c98..ff83b95e 100644 --- a/src/khoj/routers/api_chat.py +++ b/src/khoj/routers/api_chat.py @@ -330,7 +330,7 @@ async def chat( intent_type=intent_type, inferred_queries=[improved_image_prompt], client_application=request.user.client_app, - conversation_id=conversation_id, + conversation_id=conversation.id, compiled_references=compiled_references, online_results=online_results, ) @@ -347,7 +347,7 @@ async def chat( conversation_commands, user, request.user.client_app, - conversation_id, + conversation.id, location, user_name, ) From 7416ca9ae16ff49ebcc925ca945b4325d265b06e Mon Sep 17 00:00:00 2001 From: Debanjum Singh Solanky Date: Thu, 21 Mar 2024 04:35:52 +0530 Subject: [PATCH 12/13] Lower the default gunicorn workers running on prod --- gunicorn-config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gunicorn-config.py b/gunicorn-config.py index 7b8090a7..bfed49e7 100644 --- a/gunicorn-config.py +++ b/gunicorn-config.py @@ -1,7 +1,7 @@ import multiprocessing bind = "0.0.0.0:42110" -workers = 12 +workers = 8 worker_class = "uvicorn.workers.UvicornWorker" timeout = 120 keep_alive = 60 From 2399d91f616ba680a174b8db9e72b9211b52c67e Mon Sep 17 00:00:00 2001 From: sabaimran Date: Fri, 22 Mar 2024 10:05:33 +0530 Subject: [PATCH 13/13] Merge migrations --- .../migrations/0032_merge_20240322_0427.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 src/khoj/database/migrations/0032_merge_20240322_0427.py diff --git a/src/khoj/database/migrations/0032_merge_20240322_0427.py b/src/khoj/database/migrations/0032_merge_20240322_0427.py new file mode 100644 index 00000000..aee557c0 --- /dev/null +++ b/src/khoj/database/migrations/0032_merge_20240322_0427.py @@ -0,0 +1,14 @@ +# Generated by Django 4.2.10 on 2024-03-22 04:27 + +from typing import List + +from django.db import migrations + + +class Migration(migrations.Migration): + dependencies = [ + ("database", "0031_agent_conversation_agent"), + ("database", "0031_alter_googleuser_locale"), + ] + + operations: List[str] = []