From 807687a0acb0339382cf0dbfc1c1656dd3d10dac Mon Sep 17 00:00:00 2001 From: sabaimran Date: Fri, 8 Nov 2024 16:02:34 -0800 Subject: [PATCH] Automatically generate titles for conversations from history --- src/interface/web/app/chat/page.tsx | 5 +++- src/interface/web/app/common/chatFunctions.ts | 17 ++++++++++++ src/khoj/database/models/__init__.py | 4 +++ src/khoj/processor/conversation/prompts.py | 19 ++++++++++--- src/khoj/routers/api_chat.py | 27 +++++++++++++++++++ src/khoj/routers/helpers.py | 17 ++++++++++++ 6 files changed, 84 insertions(+), 5 deletions(-) diff --git a/src/interface/web/app/chat/page.tsx b/src/interface/web/app/chat/page.tsx index d6236499..1489412f 100644 --- a/src/interface/web/app/chat/page.tsx +++ b/src/interface/web/app/chat/page.tsx @@ -8,7 +8,7 @@ import ChatHistory from "../components/chatHistory/chatHistory"; import { useSearchParams } from "next/navigation"; import Loading from "../components/loading/loading"; -import { processMessageChunk } from "../common/chatFunctions"; +import { generateNewTitle, processMessageChunk } from "../common/chatFunctions"; import "katex/dist/katex.min.css"; @@ -244,6 +244,9 @@ export default function Chat() { setQueryToProcess(""); setProcessQuerySignal(false); setImages([]); + + if (conversationId) generateNewTitle(conversationId, setTitle); + break; } diff --git a/src/interface/web/app/common/chatFunctions.ts b/src/interface/web/app/common/chatFunctions.ts index 3aff7596..98ca2497 100644 --- a/src/interface/web/app/common/chatFunctions.ts +++ b/src/interface/web/app/common/chatFunctions.ts @@ -319,6 +319,23 @@ export async function packageFilesForUpload(files: FileList): Promise return formData; } +export function generateNewTitle(conversationId: string, setTitle: (title: string) => void) { + fetch(`/api/chat/title?conversation_id=${conversationId}`, { + method: "POST", + }) + .then((res) => { + if (!res.ok) throw new Error(`Failed to call API with error ${res.statusText}`); + return res.json(); + }) + .then((data) => { + setTitle(data.title); + }) + .catch((err) => { + console.error(err); + return; + }); +} + export function uploadDataForIndexing( files: FileList, setWarning: (warning: string) => void, diff --git a/src/khoj/database/models/__init__.py b/src/khoj/database/models/__init__.py index 477690cb..bcd8d376 100644 --- a/src/khoj/database/models/__init__.py +++ b/src/khoj/database/models/__init__.py @@ -458,7 +458,11 @@ class Conversation(BaseModel): user = models.ForeignKey(KhojUser, on_delete=models.CASCADE) conversation_log = models.JSONField(default=dict) client = models.ForeignKey(ClientApplication, on_delete=models.CASCADE, default=None, null=True, blank=True) + + # Slug is an app-generated conversation identifier. Need not be unique. Used as display title essentially. slug = models.CharField(max_length=200, default=None, null=True, blank=True) + + # The title field is explicitly set by the user. title = models.CharField(max_length=200, default=None, null=True, blank=True) agent = models.ForeignKey(Agent, on_delete=models.SET_NULL, default=None, null=True, blank=True) file_filters = models.JSONField(default=list) diff --git a/src/khoj/processor/conversation/prompts.py b/src/khoj/processor/conversation/prompts.py index 864b864e..4ff8aa9c 100644 --- a/src/khoj/processor/conversation/prompts.py +++ b/src/khoj/processor/conversation/prompts.py @@ -988,16 +988,27 @@ You are an extremely smart and helpful title generator assistant. Given a user q # Examples: User: Show a new Calvin and Hobbes quote every morning at 9am. My Current Location: Shanghai, China -Khoj: Your daily Calvin and Hobbes Quote +Assistant: Your daily Calvin and Hobbes Quote User: Notify me when version 2.0.0 of the sentence transformers python package is released. My Current Location: Mexico City, Mexico -Khoj: Sentence Transformers Python Package Version 2.0.0 Release +Assistant: Sentence Transformers Python Package Version 2.0.0 Release User: Gather the latest tech news on the first sunday of every month. -Khoj: Your Monthly Dose of Tech News +Assistant: Your Monthly Dose of Tech News User Query: {query} -Khoj: +Assistant: +""".strip() +) + +conversation_title_generation = PromptTemplate.from_template( + """ +You are an extremely smart and helpful title generator assistant. Given a conversation, extract the subject of the conversation. Crisp, informative, ten words or less. + +Conversation History: +{chat_history} + +Assistant: """.strip() ) diff --git a/src/khoj/routers/api_chat.py b/src/khoj/routers/api_chat.py index 8397eacd..390223d9 100644 --- a/src/khoj/routers/api_chat.py +++ b/src/khoj/routers/api_chat.py @@ -40,6 +40,7 @@ from khoj.routers.helpers import ( ConversationCommandRateLimiter, DeleteMessageRequestBody, FeedbackData, + acreate_title_from_history, agenerate_chat_response, aget_relevant_information_sources, aget_relevant_output_modes, @@ -530,6 +531,32 @@ async def set_conversation_title( ) +@api_chat.post("/title") +@requires(["authenticated"]) +async def generate_chat_title( + request: Request, + common: CommonQueryParams, + conversation_id: str, +): + user: KhojUser = request.user.object + conversation = await ConversationAdapters.aget_conversation_by_user(user=user, conversation_id=conversation_id) + + # Conversation.title is explicitly set by the user. Do not override. + if conversation.title: + return {"status": "ok", "title": conversation.title} + + if not conversation: + raise HTTPException(status_code=404, detail="Conversation not found") + + new_title = await acreate_title_from_history(request.user.object, conversation=conversation) + + conversation.slug = new_title + + conversation.asave() + + return {"status": "ok", "title": new_title} + + @api_chat.delete("/conversation/message", response_class=Response) @requires(["authenticated"]) def delete_message(request: Request, delete_request: DeleteMessageRequestBody) -> Response: diff --git a/src/khoj/routers/helpers.py b/src/khoj/routers/helpers.py index c62fe4bf..ea5cca71 100644 --- a/src/khoj/routers/helpers.py +++ b/src/khoj/routers/helpers.py @@ -299,6 +299,23 @@ def construct_chat_history(conversation_history: dict, n: int = 4, agent_name="A return chat_history +async def acreate_title_from_history( + user: KhojUser, + conversation: Conversation, +): + """ + Create a title from the given conversation history + """ + chat_history = construct_chat_history(conversation.conversation_log) + + title_generation_prompt = prompts.conversation_title_generation.format(chat_history=chat_history) + + with timer("Chat actor: Generate title from conversation history", logger): + response = await send_message_to_model_wrapper(title_generation_prompt, user=user) + + return response.strip() + + async def acreate_title_from_query(query: str, user: KhojUser = None) -> str: """ Create a title from the given query