khoj 1.42.1.dev10__py3-none-any.whl → 1.42.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. khoj/configure.py +2 -0
  2. khoj/database/adapters/__init__.py +9 -7
  3. khoj/database/models/__init__.py +9 -9
  4. khoj/interface/compiled/404/index.html +2 -2
  5. khoj/interface/compiled/_next/static/chunks/{2117-5a41630a2bd2eae8.js → 2117-056a00add390772b.js} +1 -1
  6. khoj/interface/compiled/_next/static/chunks/7127-79a3af5138960272.js +1 -0
  7. khoj/interface/compiled/_next/static/chunks/{5138-2cce449fd2454abf.js → 7211-7fedd2ee3655239c.js} +1 -1
  8. khoj/interface/compiled/_next/static/chunks/app/agents/layout-1b6273baddb72146.js +1 -0
  9. khoj/interface/compiled/_next/static/chunks/app/automations/page-ef89ac958e78aa81.js +1 -0
  10. khoj/interface/compiled/_next/static/chunks/app/chat/page-d71351493e1f7c2b.js +1 -0
  11. khoj/interface/compiled/_next/static/chunks/app/{page-45ae5e99e8a61821.js → page-4bbe55de8b080c1f.js} +1 -1
  12. khoj/interface/compiled/_next/static/chunks/app/search/layout-4505b79deb734a30.js +1 -0
  13. khoj/interface/compiled/_next/static/chunks/app/share/chat/{page-9a167dc9b5fcd464.js → page-e3f49c25480e3be4.js} +1 -1
  14. khoj/interface/compiled/_next/static/chunks/{main-876327ac335776ab.js → main-63d6432f34cdf74b.js} +1 -1
  15. khoj/interface/compiled/_next/static/chunks/{webpack-964e8ed3380daff1.js → webpack-e4c73eaddc365142.js} +1 -1
  16. khoj/interface/compiled/_next/static/css/2b1cdb68b799b876.css +1 -0
  17. khoj/interface/compiled/_next/static/css/440ae0f0f650dc35.css +1 -0
  18. khoj/interface/compiled/_next/static/css/{9c223d337a984468.css → 7017ee76c2f2cd87.css} +1 -1
  19. khoj/interface/compiled/agents/index.html +2 -2
  20. khoj/interface/compiled/agents/index.txt +1 -1
  21. khoj/interface/compiled/automations/index.html +2 -2
  22. khoj/interface/compiled/automations/index.txt +2 -2
  23. khoj/interface/compiled/chat/index.html +2 -2
  24. khoj/interface/compiled/chat/index.txt +2 -2
  25. khoj/interface/compiled/index.html +2 -2
  26. khoj/interface/compiled/index.txt +2 -2
  27. khoj/interface/compiled/search/index.html +2 -2
  28. khoj/interface/compiled/search/index.txt +1 -1
  29. khoj/interface/compiled/settings/index.html +2 -2
  30. khoj/interface/compiled/settings/index.txt +1 -1
  31. khoj/interface/compiled/share/chat/index.html +2 -2
  32. khoj/interface/compiled/share/chat/index.txt +2 -2
  33. khoj/processor/conversation/anthropic/anthropic_chat.py +19 -134
  34. khoj/processor/conversation/anthropic/utils.py +1 -1
  35. khoj/processor/conversation/google/gemini_chat.py +20 -141
  36. khoj/processor/conversation/offline/chat_model.py +23 -153
  37. khoj/processor/conversation/openai/gpt.py +14 -128
  38. khoj/processor/conversation/prompts.py +2 -63
  39. khoj/processor/conversation/utils.py +94 -89
  40. khoj/processor/image/generate.py +16 -11
  41. khoj/processor/operator/__init__.py +2 -3
  42. khoj/processor/operator/operator_agent_binary.py +11 -11
  43. khoj/processor/operator/operator_environment_computer.py +2 -2
  44. khoj/processor/tools/online_search.py +9 -3
  45. khoj/processor/tools/run_code.py +5 -5
  46. khoj/routers/api.py +5 -527
  47. khoj/routers/api_automation.py +243 -0
  48. khoj/routers/api_chat.py +48 -129
  49. khoj/routers/helpers.py +373 -121
  50. khoj/routers/research.py +11 -43
  51. khoj/utils/helpers.py +0 -6
  52. {khoj-1.42.1.dev10.dist-info → khoj-1.42.2.dist-info}/METADATA +3 -3
  53. {khoj-1.42.1.dev10.dist-info → khoj-1.42.2.dist-info}/RECORD +58 -57
  54. khoj/interface/compiled/_next/static/chunks/7127-d3199617463d45f0.js +0 -1
  55. khoj/interface/compiled/_next/static/chunks/app/agents/layout-4e2a134ec26aa606.js +0 -1
  56. khoj/interface/compiled/_next/static/chunks/app/automations/page-465741d9149dfd48.js +0 -1
  57. khoj/interface/compiled/_next/static/chunks/app/chat/page-898079bcea5376f4.js +0 -1
  58. khoj/interface/compiled/_next/static/chunks/app/search/layout-c02531d586972d7d.js +0 -1
  59. khoj/interface/compiled/_next/static/css/76c658ee459140a9.css +0 -1
  60. khoj/interface/compiled/_next/static/css/fca983d49c3dd1a3.css +0 -1
  61. /khoj/interface/compiled/_next/static/{2niR8lV9_OpGs1vdb2yMp → BDHACq0ud8EERJ3YZ4aWo}/_buildManifest.js +0 -0
  62. /khoj/interface/compiled/_next/static/{2niR8lV9_OpGs1vdb2yMp → BDHACq0ud8EERJ3YZ4aWo}/_ssgManifest.js +0 -0
  63. {khoj-1.42.1.dev10.dist-info → khoj-1.42.2.dist-info}/WHEEL +0 -0
  64. {khoj-1.42.1.dev10.dist-info → khoj-1.42.2.dist-info}/entry_points.txt +0 -0
  65. {khoj-1.42.1.dev10.dist-info → khoj-1.42.2.dist-info}/licenses/LICENSE +0 -0
khoj/routers/helpers.py CHANGED
@@ -1,12 +1,14 @@
1
+ import asyncio
1
2
  import base64
3
+ import concurrent.futures
2
4
  import hashlib
3
5
  import json
4
6
  import logging
5
7
  import math
6
8
  import os
7
9
  import re
10
+ import time
8
11
  from datetime import datetime, timedelta, timezone
9
- from functools import partial
10
12
  from random import random
11
13
  from typing import (
12
14
  Annotated,
@@ -46,6 +48,7 @@ from khoj.database.adapters import (
46
48
  aget_user_by_email,
47
49
  ais_user_subscribed,
48
50
  create_khoj_token,
51
+ get_default_search_model,
49
52
  get_khoj_tokens,
50
53
  get_user_name,
51
54
  get_user_notion_config,
@@ -55,6 +58,7 @@ from khoj.database.adapters import (
55
58
  )
56
59
  from khoj.database.models import (
57
60
  Agent,
61
+ ChatMessageModel,
58
62
  ChatModel,
59
63
  ClientApplication,
60
64
  Conversation,
@@ -100,12 +104,16 @@ from khoj.processor.conversation.utils import (
100
104
  clean_json,
101
105
  clean_mermaidjs,
102
106
  construct_chat_history,
107
+ construct_question_history,
108
+ defilter_query,
103
109
  generate_chatml_messages_with_context,
104
- save_to_conversation_log,
105
110
  )
106
111
  from khoj.processor.speech.text_to_speech import is_eleven_labs_enabled
107
112
  from khoj.routers.email import is_resend_enabled, send_task_email
108
113
  from khoj.routers.twilio import is_twilio_enabled
114
+ from khoj.search_filter.date_filter import DateFilter
115
+ from khoj.search_filter.file_filter import FileFilter
116
+ from khoj.search_filter.word_filter import WordFilter
109
117
  from khoj.search_type import text_search
110
118
  from khoj.utils import state
111
119
  from khoj.utils.config import OfflineChatProcessorModel
@@ -122,7 +130,14 @@ from khoj.utils.helpers import (
122
130
  timer,
123
131
  tool_descriptions_for_llm,
124
132
  )
125
- from khoj.utils.rawconfig import ChatRequestBody, FileAttachment, FileData, LocationData
133
+ from khoj.utils.rawconfig import (
134
+ ChatRequestBody,
135
+ FileAttachment,
136
+ FileData,
137
+ LocationData,
138
+ SearchResponse,
139
+ )
140
+ from khoj.utils.state import SearchType
126
141
 
127
142
  logger = logging.getLogger(__name__)
128
143
 
@@ -236,8 +251,6 @@ def get_next_url(request: Request) -> str:
236
251
  def get_conversation_command(query: str) -> ConversationCommand:
237
252
  if query.startswith("/notes"):
238
253
  return ConversationCommand.Notes
239
- elif query.startswith("/help"):
240
- return ConversationCommand.Help
241
254
  elif query.startswith("/general"):
242
255
  return ConversationCommand.General
243
256
  elif query.startswith("/online"):
@@ -248,8 +261,6 @@ def get_conversation_command(query: str) -> ConversationCommand:
248
261
  return ConversationCommand.Image
249
262
  elif query.startswith("/automated_task"):
250
263
  return ConversationCommand.AutomatedTask
251
- elif query.startswith("/summarize"):
252
- return ConversationCommand.Summarize
253
264
  elif query.startswith("/diagram"):
254
265
  return ConversationCommand.Diagram
255
266
  elif query.startswith("/code"):
@@ -285,7 +296,7 @@ async def acreate_title_from_history(
285
296
  """
286
297
  Create a title from the given conversation history
287
298
  """
288
- chat_history = construct_chat_history(conversation.conversation_log)
299
+ chat_history = construct_chat_history(conversation.messages)
289
300
 
290
301
  title_generation_prompt = prompts.conversation_title_generation.format(chat_history=chat_history)
291
302
 
@@ -345,7 +356,7 @@ async def acheck_if_safe_prompt(system_prompt: str, user: KhojUser = None, lax:
345
356
 
346
357
  async def aget_data_sources_and_output_format(
347
358
  query: str,
348
- conversation_history: dict,
359
+ chat_history: list[ChatMessageModel],
349
360
  is_task: bool,
350
361
  user: KhojUser,
351
362
  query_images: List[str] = None,
@@ -379,14 +390,11 @@ async def aget_data_sources_and_output_format(
379
390
  agent_outputs = agent.output_modes if agent else []
380
391
 
381
392
  for output, description in mode_descriptions_for_llm.items():
382
- # Do not allow tasks to schedule another task
383
- if is_task and output == ConversationCommand.Automation:
384
- continue
385
393
  output_options[output.value] = description
386
394
  if len(agent_outputs) == 0 or output.value in agent_outputs:
387
395
  output_options_str += f'- "{output.value}": "{description}"\n'
388
396
 
389
- chat_history = construct_chat_history(conversation_history, n=6)
397
+ chat_history_str = construct_chat_history(chat_history, n=6)
390
398
 
391
399
  if query_images:
392
400
  query = f"[placeholder for {len(query_images)} user attached images]\n{query}"
@@ -399,7 +407,7 @@ async def aget_data_sources_and_output_format(
399
407
  query=query,
400
408
  sources=source_options_str,
401
409
  outputs=output_options_str,
402
- chat_history=chat_history,
410
+ chat_history=chat_history_str,
403
411
  personality_context=personality_context,
404
412
  )
405
413
 
@@ -462,7 +470,7 @@ async def aget_data_sources_and_output_format(
462
470
  async def infer_webpage_urls(
463
471
  q: str,
464
472
  max_webpages: int,
465
- conversation_history: dict,
473
+ chat_history: List[ChatMessageModel],
466
474
  location_data: LocationData,
467
475
  user: KhojUser,
468
476
  query_images: List[str] = None,
@@ -475,7 +483,7 @@ async def infer_webpage_urls(
475
483
  """
476
484
  location = f"{location_data}" if location_data else "Unknown"
477
485
  username = prompts.user_name.format(name=user.get_full_name()) if user.get_full_name() else ""
478
- chat_history = construct_chat_history(conversation_history)
486
+ chat_history_str = construct_chat_history(chat_history)
479
487
 
480
488
  utc_date = datetime.now(timezone.utc).strftime("%Y-%m-%d")
481
489
  personality_context = (
@@ -485,7 +493,7 @@ async def infer_webpage_urls(
485
493
  online_queries_prompt = prompts.infer_webpages_to_read.format(
486
494
  query=q,
487
495
  max_webpages=max_webpages,
488
- chat_history=chat_history,
496
+ chat_history=chat_history_str,
489
497
  current_date=utc_date,
490
498
  location=location,
491
499
  username=username,
@@ -526,7 +534,7 @@ async def infer_webpage_urls(
526
534
 
527
535
  async def generate_online_subqueries(
528
536
  q: str,
529
- conversation_history: dict,
537
+ chat_history: List[ChatMessageModel],
530
538
  location_data: LocationData,
531
539
  user: KhojUser,
532
540
  query_images: List[str] = None,
@@ -540,7 +548,7 @@ async def generate_online_subqueries(
540
548
  """
541
549
  location = f"{location_data}" if location_data else "Unknown"
542
550
  username = prompts.user_name.format(name=user.get_full_name()) if user.get_full_name() else ""
543
- chat_history = construct_chat_history(conversation_history)
551
+ chat_history_str = construct_chat_history(chat_history)
544
552
 
545
553
  utc_date = datetime.now(timezone.utc).strftime("%Y-%m-%d")
546
554
  personality_context = (
@@ -549,7 +557,7 @@ async def generate_online_subqueries(
549
557
 
550
558
  online_queries_prompt = prompts.online_search_conversation_subqueries.format(
551
559
  query=q,
552
- chat_history=chat_history,
560
+ chat_history=chat_history_str,
553
561
  max_queries=max_queries,
554
562
  current_date=utc_date,
555
563
  location=location,
@@ -591,16 +599,16 @@ async def generate_online_subqueries(
591
599
 
592
600
 
593
601
  def schedule_query(
594
- q: str, conversation_history: dict, user: KhojUser, query_images: List[str] = None, tracer: dict = {}
602
+ q: str, chat_history: List[ChatMessageModel], user: KhojUser, query_images: List[str] = None, tracer: dict = {}
595
603
  ) -> Tuple[str, str, str]:
596
604
  """
597
605
  Schedule the date, time to run the query. Assume the server timezone is UTC.
598
606
  """
599
- chat_history = construct_chat_history(conversation_history)
607
+ chat_history_str = construct_chat_history(chat_history)
600
608
 
601
609
  crontime_prompt = prompts.crontime_prompt.format(
602
610
  query=q,
603
- chat_history=chat_history,
611
+ chat_history=chat_history_str,
604
612
  )
605
613
 
606
614
  raw_response = send_message_to_model_wrapper_sync(
@@ -619,16 +627,16 @@ def schedule_query(
619
627
 
620
628
 
621
629
  async def aschedule_query(
622
- q: str, conversation_history: dict, user: KhojUser, query_images: List[str] = None, tracer: dict = {}
630
+ q: str, chat_history: List[ChatMessageModel], user: KhojUser, query_images: List[str] = None, tracer: dict = {}
623
631
  ) -> Tuple[str, str, str]:
624
632
  """
625
633
  Schedule the date, time to run the query. Assume the server timezone is UTC.
626
634
  """
627
- chat_history = construct_chat_history(conversation_history)
635
+ chat_history_str = construct_chat_history(chat_history)
628
636
 
629
637
  crontime_prompt = prompts.crontime_prompt.format(
630
638
  query=q,
631
- chat_history=chat_history,
639
+ chat_history=chat_history_str,
632
640
  )
633
641
 
634
642
  raw_response = await send_message_to_model_wrapper(
@@ -681,7 +689,7 @@ async def extract_relevant_info(
681
689
  async def extract_relevant_summary(
682
690
  q: str,
683
691
  corpus: str,
684
- conversation_history: dict,
692
+ chat_history: List[ChatMessageModel] = [],
685
693
  query_images: List[str] = None,
686
694
  user: KhojUser = None,
687
695
  agent: Agent = None,
@@ -698,11 +706,11 @@ async def extract_relevant_summary(
698
706
  prompts.personality_context.format(personality=agent.personality) if agent and agent.personality else ""
699
707
  )
700
708
 
701
- chat_history = construct_chat_history(conversation_history)
709
+ chat_history_str = construct_chat_history(chat_history)
702
710
 
703
711
  extract_relevant_information = prompts.extract_relevant_summary.format(
704
712
  query=q,
705
- chat_history=chat_history,
713
+ chat_history=chat_history_str,
706
714
  corpus=corpus.strip(),
707
715
  personality_context=personality_context,
708
716
  )
@@ -725,7 +733,7 @@ async def generate_summary_from_files(
725
733
  q: str,
726
734
  user: KhojUser,
727
735
  file_filters: List[str],
728
- meta_log: dict,
736
+ chat_history: List[ChatMessageModel] = [],
729
737
  query_images: List[str] = None,
730
738
  agent: Agent = None,
731
739
  send_status_func: Optional[Callable] = None,
@@ -766,7 +774,7 @@ async def generate_summary_from_files(
766
774
  response = await extract_relevant_summary(
767
775
  q,
768
776
  contextual_data,
769
- conversation_history=meta_log,
777
+ chat_history=chat_history,
770
778
  query_images=query_images,
771
779
  user=user,
772
780
  agent=agent,
@@ -782,7 +790,7 @@ async def generate_summary_from_files(
782
790
 
783
791
  async def generate_excalidraw_diagram(
784
792
  q: str,
785
- conversation_history: Dict[str, Any],
793
+ chat_history: List[ChatMessageModel],
786
794
  location_data: LocationData,
787
795
  note_references: List[Dict[str, Any]],
788
796
  online_results: Optional[dict] = None,
@@ -799,7 +807,7 @@ async def generate_excalidraw_diagram(
799
807
 
800
808
  better_diagram_description_prompt = await generate_better_diagram_description(
801
809
  q=q,
802
- conversation_history=conversation_history,
810
+ chat_history=chat_history,
803
811
  location_data=location_data,
804
812
  note_references=note_references,
805
813
  online_results=online_results,
@@ -834,7 +842,7 @@ async def generate_excalidraw_diagram(
834
842
 
835
843
  async def generate_better_diagram_description(
836
844
  q: str,
837
- conversation_history: Dict[str, Any],
845
+ chat_history: List[ChatMessageModel],
838
846
  location_data: LocationData,
839
847
  note_references: List[Dict[str, Any]],
840
848
  online_results: Optional[dict] = None,
@@ -857,7 +865,7 @@ async def generate_better_diagram_description(
857
865
 
858
866
  user_references = "\n\n".join([f"# {item['compiled']}" for item in note_references])
859
867
 
860
- chat_history = construct_chat_history(conversation_history)
868
+ chat_history_str = construct_chat_history(chat_history)
861
869
 
862
870
  simplified_online_results = {}
863
871
 
@@ -870,7 +878,7 @@ async def generate_better_diagram_description(
870
878
 
871
879
  improve_diagram_description_prompt = prompts.improve_excalidraw_diagram_description_prompt.format(
872
880
  query=q,
873
- chat_history=chat_history,
881
+ chat_history=chat_history_str,
874
882
  location=location,
875
883
  current_date=today_date,
876
884
  references=user_references,
@@ -939,7 +947,7 @@ async def generate_excalidraw_diagram_from_description(
939
947
 
940
948
  async def generate_mermaidjs_diagram(
941
949
  q: str,
942
- conversation_history: Dict[str, Any],
950
+ chat_history: List[ChatMessageModel],
943
951
  location_data: LocationData,
944
952
  note_references: List[Dict[str, Any]],
945
953
  online_results: Optional[dict] = None,
@@ -956,7 +964,7 @@ async def generate_mermaidjs_diagram(
956
964
 
957
965
  better_diagram_description_prompt = await generate_better_mermaidjs_diagram_description(
958
966
  q=q,
959
- conversation_history=conversation_history,
967
+ chat_history=chat_history,
960
968
  location_data=location_data,
961
969
  note_references=note_references,
962
970
  online_results=online_results,
@@ -985,7 +993,7 @@ async def generate_mermaidjs_diagram(
985
993
 
986
994
  async def generate_better_mermaidjs_diagram_description(
987
995
  q: str,
988
- conversation_history: Dict[str, Any],
996
+ chat_history: List[ChatMessageModel],
989
997
  location_data: LocationData,
990
998
  note_references: List[Dict[str, Any]],
991
999
  online_results: Optional[dict] = None,
@@ -1008,7 +1016,7 @@ async def generate_better_mermaidjs_diagram_description(
1008
1016
 
1009
1017
  user_references = "\n\n".join([f"# {item['compiled']}" for item in note_references])
1010
1018
 
1011
- chat_history = construct_chat_history(conversation_history)
1019
+ chat_history_str = construct_chat_history(chat_history)
1012
1020
 
1013
1021
  simplified_online_results = {}
1014
1022
 
@@ -1021,7 +1029,7 @@ async def generate_better_mermaidjs_diagram_description(
1021
1029
 
1022
1030
  improve_diagram_description_prompt = prompts.improve_mermaid_js_diagram_description_prompt.format(
1023
1031
  query=q,
1024
- chat_history=chat_history,
1032
+ chat_history=chat_history_str,
1025
1033
  location=location,
1026
1034
  current_date=today_date,
1027
1035
  references=user_references,
@@ -1150,6 +1158,276 @@ async def generate_better_image_prompt(
1150
1158
  return response
1151
1159
 
1152
1160
 
1161
+ async def search_documents(
1162
+ user: KhojUser,
1163
+ chat_history: list[ChatMessageModel],
1164
+ q: str,
1165
+ n: int,
1166
+ d: float,
1167
+ conversation_id: str,
1168
+ conversation_commands: List[ConversationCommand] = [ConversationCommand.Default],
1169
+ location_data: LocationData = None,
1170
+ send_status_func: Optional[Callable] = None,
1171
+ query_images: Optional[List[str]] = None,
1172
+ previous_inferred_queries: Set = set(),
1173
+ agent: Agent = None,
1174
+ query_files: str = None,
1175
+ tracer: dict = {},
1176
+ ):
1177
+ # Initialize Variables
1178
+ compiled_references: List[dict[str, str]] = []
1179
+ inferred_queries: List[str] = []
1180
+
1181
+ agent_has_entries = False
1182
+
1183
+ if agent:
1184
+ agent_has_entries = await sync_to_async(EntryAdapters.agent_has_entries)(agent=agent)
1185
+
1186
+ if (
1187
+ not ConversationCommand.Notes in conversation_commands
1188
+ and not ConversationCommand.Default in conversation_commands
1189
+ and not agent_has_entries
1190
+ ):
1191
+ yield compiled_references, inferred_queries, q
1192
+ return
1193
+
1194
+ # If Notes or Default is not in the conversation command, then the search should be restricted to the agent's knowledge base
1195
+ should_limit_to_agent_knowledge = (
1196
+ ConversationCommand.Notes not in conversation_commands
1197
+ and ConversationCommand.Default not in conversation_commands
1198
+ )
1199
+
1200
+ if not await sync_to_async(EntryAdapters.user_has_entries)(user=user):
1201
+ if not agent_has_entries:
1202
+ logger.debug("No documents in knowledge base. Use a Khoj client to sync and chat with your docs.")
1203
+ yield compiled_references, inferred_queries, q
1204
+ return
1205
+
1206
+ # Extract filter terms from user message
1207
+ defiltered_query = defilter_query(q)
1208
+ filters_in_query = q.replace(defiltered_query, "").strip()
1209
+ conversation = await sync_to_async(ConversationAdapters.get_conversation_by_id)(conversation_id)
1210
+
1211
+ if not conversation:
1212
+ logger.error(f"Conversation with id {conversation_id} not found when extracting references.")
1213
+ yield compiled_references, inferred_queries, defiltered_query
1214
+ return
1215
+
1216
+ filters_in_query += " ".join([f'file:"{filter}"' for filter in conversation.file_filters])
1217
+ if is_none_or_empty(filters_in_query):
1218
+ logger.debug(f"Filters in query: {filters_in_query}")
1219
+
1220
+ personality_context = prompts.personality_context.format(personality=agent.personality) if agent else ""
1221
+
1222
+ # Infer search queries from user message
1223
+ with timer("Extracting search queries took", logger):
1224
+ inferred_queries = await extract_questions(
1225
+ query=defiltered_query,
1226
+ user=user,
1227
+ personality_context=personality_context,
1228
+ chat_history=chat_history,
1229
+ location_data=location_data,
1230
+ query_images=query_images,
1231
+ query_files=query_files,
1232
+ tracer=tracer,
1233
+ )
1234
+
1235
+ # Collate search results as context for the LLM
1236
+ inferred_queries = list(set(inferred_queries) - previous_inferred_queries)
1237
+ with timer("Searching knowledge base took", logger):
1238
+ search_results = []
1239
+ logger.info(f"🔍 Searching knowledge base with queries: {inferred_queries}")
1240
+ if send_status_func:
1241
+ inferred_queries_str = "\n- " + "\n- ".join(inferred_queries)
1242
+ async for event in send_status_func(f"**Searching Documents for:** {inferred_queries_str}"):
1243
+ yield {ChatEvent.STATUS: event}
1244
+ for query in inferred_queries:
1245
+ search_results.extend(
1246
+ await execute_search(
1247
+ user if not should_limit_to_agent_knowledge else None,
1248
+ f"{query} {filters_in_query}",
1249
+ n=n,
1250
+ t=SearchType.All,
1251
+ r=True,
1252
+ max_distance=d,
1253
+ dedupe=False,
1254
+ agent=agent,
1255
+ )
1256
+ )
1257
+ search_results = text_search.deduplicated_search_responses(search_results)
1258
+ compiled_references = [
1259
+ {"query": q, "compiled": item.additional["compiled"], "file": item.additional["file"]}
1260
+ for q, item in zip(inferred_queries, search_results)
1261
+ ]
1262
+
1263
+ yield compiled_references, inferred_queries, defiltered_query
1264
+
1265
+
1266
+ async def extract_questions(
1267
+ query: str,
1268
+ user: KhojUser,
1269
+ personality_context: str = "",
1270
+ chat_history: List[ChatMessageModel] = [],
1271
+ location_data: LocationData = None,
1272
+ query_images: Optional[List[str]] = None,
1273
+ query_files: str = None,
1274
+ tracer: dict = {},
1275
+ ):
1276
+ """
1277
+ Infer document search queries from user message and provided context
1278
+ """
1279
+ # Shared context setup
1280
+ location = f"{location_data}" if location_data else "N/A"
1281
+ username = prompts.user_name.format(name=user.get_full_name()) if user and user.get_full_name() else ""
1282
+
1283
+ # Date variables for prompt formatting
1284
+ today = datetime.today()
1285
+ current_new_year = today.replace(month=1, day=1)
1286
+ last_new_year = current_new_year.replace(year=today.year - 1)
1287
+ yesterday = (today - timedelta(days=1)).strftime("%Y-%m-%d")
1288
+
1289
+ # Common prompt setup for API-based models (using Anthropic prompts for consistency)
1290
+ chat_history_str = construct_question_history(chat_history, query_prefix="User", agent_name="Assistant")
1291
+
1292
+ system_prompt = prompts.extract_questions_system_prompt.format(
1293
+ current_date=today.strftime("%Y-%m-%d"),
1294
+ day_of_week=today.strftime("%A"),
1295
+ current_month=today.strftime("%Y-%m"),
1296
+ last_new_year=last_new_year.strftime("%Y"),
1297
+ last_new_year_date=last_new_year.strftime("%Y-%m-%d"),
1298
+ current_new_year_date=current_new_year.strftime("%Y-%m-%d"),
1299
+ yesterday_date=yesterday,
1300
+ location=location,
1301
+ username=username,
1302
+ personality_context=personality_context,
1303
+ )
1304
+
1305
+ prompt = prompts.extract_questions_user_message.format(text=query, chat_history=chat_history_str)
1306
+
1307
+ class DocumentQueries(BaseModel):
1308
+ """Choose searches to run on user documents."""
1309
+
1310
+ queries: List[str] = Field(..., min_items=1, description="List of search queries to run on user documents.")
1311
+
1312
+ raw_response = await send_message_to_model_wrapper(
1313
+ system_message=system_prompt,
1314
+ query=prompt,
1315
+ query_images=query_images,
1316
+ query_files=query_files,
1317
+ chat_history=chat_history,
1318
+ response_type="json_object",
1319
+ response_schema=DocumentQueries,
1320
+ user=user,
1321
+ tracer=tracer,
1322
+ )
1323
+
1324
+ # Extract questions from the response
1325
+ try:
1326
+ response = clean_json(raw_response)
1327
+ response = pyjson5.loads(response)
1328
+ queries = [q.strip() for q in response["queries"] if q.strip()]
1329
+ if not isinstance(queries, list) or not queries:
1330
+ logger.error(f"Invalid response for constructing subqueries: {response}")
1331
+ return [query]
1332
+ return queries
1333
+ except:
1334
+ logger.warning(f"LLM returned invalid JSON. Falling back to using user message as search query.")
1335
+ return [query]
1336
+
1337
+
1338
+ async def execute_search(
1339
+ user: KhojUser,
1340
+ q: str,
1341
+ n: Optional[int] = 5,
1342
+ t: Optional[SearchType] = None,
1343
+ r: Optional[bool] = False,
1344
+ max_distance: Optional[Union[float, None]] = None,
1345
+ dedupe: Optional[bool] = True,
1346
+ agent: Optional[Agent] = None,
1347
+ ):
1348
+ # Run validation checks
1349
+ results: List[SearchResponse] = []
1350
+
1351
+ start_time = time.time()
1352
+
1353
+ # Ensure the agent, if present, is accessible by the user
1354
+ if user and agent and not await AgentAdapters.ais_agent_accessible(agent, user):
1355
+ logger.error(f"Agent {agent.slug} is not accessible by user {user}")
1356
+ return results
1357
+
1358
+ if q is None or q == "":
1359
+ logger.warning(f"No query param (q) passed in API call to initiate search")
1360
+ return results
1361
+
1362
+ # initialize variables
1363
+ user_query = q.strip()
1364
+ results_count = n or 5
1365
+ t = t or state.SearchType.All
1366
+ search_tasks = []
1367
+
1368
+ # return cached results, if available
1369
+ if user:
1370
+ query_cache_key = f"{user_query}-{n}-{t}-{r}-{max_distance}-{dedupe}"
1371
+ if query_cache_key in state.query_cache[user.uuid]:
1372
+ logger.debug(f"Return response from query cache")
1373
+ return state.query_cache[user.uuid][query_cache_key]
1374
+
1375
+ # Encode query with filter terms removed
1376
+ defiltered_query = user_query
1377
+ for filter in [DateFilter(), WordFilter(), FileFilter()]:
1378
+ defiltered_query = filter.defilter(defiltered_query)
1379
+
1380
+ encoded_asymmetric_query = None
1381
+ if t.value != SearchType.Image.value:
1382
+ with timer("Encoding query took", logger=logger):
1383
+ search_model = await sync_to_async(get_default_search_model)()
1384
+ encoded_asymmetric_query = state.embeddings_model[search_model.name].embed_query(defiltered_query)
1385
+
1386
+ # Use asyncio to run searches in parallel
1387
+ if t.value in [
1388
+ SearchType.All.value,
1389
+ SearchType.Org.value,
1390
+ SearchType.Markdown.value,
1391
+ SearchType.Github.value,
1392
+ SearchType.Notion.value,
1393
+ SearchType.Plaintext.value,
1394
+ SearchType.Pdf.value,
1395
+ ]:
1396
+ # query markdown notes
1397
+ search_tasks.append(
1398
+ text_search.query(
1399
+ user_query,
1400
+ user,
1401
+ t,
1402
+ question_embedding=encoded_asymmetric_query,
1403
+ max_distance=max_distance,
1404
+ agent=agent,
1405
+ )
1406
+ )
1407
+
1408
+ # Query across each requested content types in parallel
1409
+ with timer("Query took", logger):
1410
+ if search_tasks:
1411
+ hits_list = await asyncio.gather(*search_tasks)
1412
+ for hits in hits_list:
1413
+ # Collate results
1414
+ results += text_search.collate_results(hits, dedupe=dedupe)
1415
+
1416
+ # Sort results across all content types and take top results
1417
+ results = text_search.rerank_and_sort_results(
1418
+ results, query=defiltered_query, rank_results=r, search_model_name=search_model.name
1419
+ )[:results_count]
1420
+
1421
+ # Cache results
1422
+ if user:
1423
+ state.query_cache[user.uuid][query_cache_key] = results
1424
+
1425
+ end_time = time.time()
1426
+ logger.debug(f"🔍 Search took: {end_time - start_time:.3f} seconds")
1427
+
1428
+ return results
1429
+
1430
+
1153
1431
  async def send_message_to_model_wrapper(
1154
1432
  query: str,
1155
1433
  system_message: str = "",
@@ -1160,7 +1438,7 @@ async def send_message_to_model_wrapper(
1160
1438
  query_images: List[str] = None,
1161
1439
  context: str = "",
1162
1440
  query_files: str = None,
1163
- conversation_log: dict = {},
1441
+ chat_history: list[ChatMessageModel] = [],
1164
1442
  agent_chat_model: ChatModel = None,
1165
1443
  tracer: dict = {},
1166
1444
  ):
@@ -1193,7 +1471,7 @@ async def send_message_to_model_wrapper(
1193
1471
  user_message=query,
1194
1472
  context_message=context,
1195
1473
  system_message=system_message,
1196
- conversation_log=conversation_log,
1474
+ chat_history=chat_history,
1197
1475
  model_name=chat_model_name,
1198
1476
  loaded_model=loaded_model,
1199
1477
  tokenizer_name=tokenizer,
@@ -1260,7 +1538,7 @@ def send_message_to_model_wrapper_sync(
1260
1538
  user: KhojUser = None,
1261
1539
  query_images: List[str] = None,
1262
1540
  query_files: str = "",
1263
- conversation_log: dict = {},
1541
+ chat_history: List[ChatMessageModel] = [],
1264
1542
  tracer: dict = {},
1265
1543
  ):
1266
1544
  chat_model: ChatModel = ConversationAdapters.get_default_chat_model(user)
@@ -1284,7 +1562,7 @@ def send_message_to_model_wrapper_sync(
1284
1562
  truncated_messages = generate_chatml_messages_with_context(
1285
1563
  user_message=message,
1286
1564
  system_message=system_message,
1287
- conversation_log=conversation_log,
1565
+ chat_history=chat_history,
1288
1566
  model_name=chat_model_name,
1289
1567
  loaded_model=loaded_model,
1290
1568
  max_prompt_size=max_tokens,
@@ -1342,61 +1620,31 @@ def send_message_to_model_wrapper_sync(
1342
1620
 
1343
1621
  async def agenerate_chat_response(
1344
1622
  q: str,
1345
- meta_log: dict,
1623
+ chat_history: List[ChatMessageModel],
1346
1624
  conversation: Conversation,
1347
1625
  compiled_references: List[Dict] = [],
1348
1626
  online_results: Dict[str, Dict] = {},
1349
1627
  code_results: Dict[str, Dict] = {},
1350
1628
  operator_results: List[OperatorRun] = [],
1351
1629
  research_results: List[ResearchIteration] = [],
1352
- inferred_queries: List[str] = [],
1353
- conversation_commands: List[ConversationCommand] = [ConversationCommand.Default],
1354
1630
  user: KhojUser = None,
1355
- client_application: ClientApplication = None,
1356
1631
  location_data: LocationData = None,
1357
1632
  user_name: Optional[str] = None,
1358
1633
  query_images: Optional[List[str]] = None,
1359
- train_of_thought: List[Any] = [],
1360
1634
  query_files: str = None,
1361
- raw_query_files: List[FileAttachment] = None,
1362
- generated_images: List[str] = None,
1363
1635
  raw_generated_files: List[FileAttachment] = [],
1364
- generated_mermaidjs_diagram: str = None,
1365
1636
  program_execution_context: List[str] = [],
1366
1637
  generated_asset_results: Dict[str, Dict] = {},
1367
1638
  is_subscribed: bool = False,
1368
1639
  tracer: dict = {},
1369
- ) -> Tuple[AsyncGenerator[str | ResponseWithThought, None], Dict[str, str]]:
1640
+ ) -> Tuple[AsyncGenerator[ResponseWithThought, None], Dict[str, str]]:
1370
1641
  # Initialize Variables
1371
- chat_response_generator: AsyncGenerator[str | ResponseWithThought, None] = None
1372
- logger.debug(f"Conversation Types: {conversation_commands}")
1642
+ chat_response_generator: AsyncGenerator[ResponseWithThought, None] = None
1373
1643
 
1374
1644
  metadata = {}
1375
1645
  agent = await AgentAdapters.aget_conversation_agent_by_id(conversation.agent.id) if conversation.agent else None
1376
1646
 
1377
1647
  try:
1378
- partial_completion = partial(
1379
- save_to_conversation_log,
1380
- q,
1381
- user=user,
1382
- meta_log=meta_log,
1383
- compiled_references=compiled_references,
1384
- online_results=online_results,
1385
- code_results=code_results,
1386
- operator_results=operator_results,
1387
- research_results=research_results,
1388
- inferred_queries=inferred_queries,
1389
- client_application=client_application,
1390
- conversation_id=str(conversation.id),
1391
- query_images=query_images,
1392
- train_of_thought=train_of_thought,
1393
- raw_query_files=raw_query_files,
1394
- generated_images=generated_images,
1395
- raw_generated_files=raw_generated_files,
1396
- generated_mermaidjs_diagram=generated_mermaidjs_diagram,
1397
- tracer=tracer,
1398
- )
1399
-
1400
1648
  query_to_run = q
1401
1649
  deepthought = False
1402
1650
  if research_results:
@@ -1420,22 +1668,23 @@ async def agenerate_chat_response(
1420
1668
  if chat_model.model_type == "offline":
1421
1669
  loaded_model = state.offline_chat_processor_config.loaded_model
1422
1670
  chat_response_generator = converse_offline(
1671
+ # Query
1423
1672
  user_query=query_to_run,
1673
+ # Context
1424
1674
  references=compiled_references,
1425
1675
  online_results=online_results,
1676
+ generated_files=raw_generated_files,
1677
+ generated_asset_results=generated_asset_results,
1678
+ location_data=location_data,
1679
+ user_name=user_name,
1680
+ query_files=query_files,
1681
+ chat_history=chat_history,
1682
+ # Model
1426
1683
  loaded_model=loaded_model,
1427
- conversation_log=meta_log,
1428
- completion_func=partial_completion,
1429
- conversation_commands=conversation_commands,
1430
1684
  model_name=chat_model.name,
1431
1685
  max_prompt_size=chat_model.max_prompt_size,
1432
1686
  tokenizer_name=chat_model.tokenizer,
1433
- location_data=location_data,
1434
- user_name=user_name,
1435
1687
  agent=agent,
1436
- query_files=query_files,
1437
- generated_files=raw_generated_files,
1438
- generated_asset_results=generated_asset_results,
1439
1688
  tracer=tracer,
1440
1689
  )
1441
1690
 
@@ -1444,28 +1693,29 @@ async def agenerate_chat_response(
1444
1693
  api_key = openai_chat_config.api_key
1445
1694
  chat_model_name = chat_model.name
1446
1695
  chat_response_generator = converse_openai(
1696
+ # Query
1447
1697
  query_to_run,
1448
- compiled_references,
1449
- query_images=query_images,
1698
+ # Context
1699
+ references=compiled_references,
1450
1700
  online_results=online_results,
1451
1701
  code_results=code_results,
1452
1702
  operator_results=operator_results,
1453
- conversation_log=meta_log,
1703
+ query_images=query_images,
1704
+ query_files=query_files,
1705
+ generated_files=raw_generated_files,
1706
+ generated_asset_results=generated_asset_results,
1707
+ program_execution_context=program_execution_context,
1708
+ location_data=location_data,
1709
+ user_name=user_name,
1710
+ chat_history=chat_history,
1711
+ # Model
1454
1712
  model=chat_model_name,
1455
1713
  api_key=api_key,
1456
1714
  api_base_url=openai_chat_config.api_base_url,
1457
- completion_func=partial_completion,
1458
- conversation_commands=conversation_commands,
1459
1715
  max_prompt_size=chat_model.max_prompt_size,
1460
1716
  tokenizer_name=chat_model.tokenizer,
1461
- location_data=location_data,
1462
- user_name=user_name,
1463
1717
  agent=agent,
1464
1718
  vision_available=vision_available,
1465
- query_files=query_files,
1466
- generated_files=raw_generated_files,
1467
- generated_asset_results=generated_asset_results,
1468
- program_execution_context=program_execution_context,
1469
1719
  deepthought=deepthought,
1470
1720
  tracer=tracer,
1471
1721
  )
@@ -1474,28 +1724,29 @@ async def agenerate_chat_response(
1474
1724
  api_key = chat_model.ai_model_api.api_key
1475
1725
  api_base_url = chat_model.ai_model_api.api_base_url
1476
1726
  chat_response_generator = converse_anthropic(
1727
+ # Query
1477
1728
  query_to_run,
1478
- compiled_references,
1479
- query_images=query_images,
1729
+ # Context
1730
+ references=compiled_references,
1480
1731
  online_results=online_results,
1481
1732
  code_results=code_results,
1482
1733
  operator_results=operator_results,
1483
- conversation_log=meta_log,
1734
+ query_images=query_images,
1735
+ query_files=query_files,
1736
+ generated_files=raw_generated_files,
1737
+ generated_asset_results=generated_asset_results,
1738
+ program_execution_context=program_execution_context,
1739
+ location_data=location_data,
1740
+ user_name=user_name,
1741
+ chat_history=chat_history,
1742
+ # Model
1484
1743
  model=chat_model.name,
1485
1744
  api_key=api_key,
1486
1745
  api_base_url=api_base_url,
1487
- completion_func=partial_completion,
1488
- conversation_commands=conversation_commands,
1489
1746
  max_prompt_size=chat_model.max_prompt_size,
1490
1747
  tokenizer_name=chat_model.tokenizer,
1491
- location_data=location_data,
1492
- user_name=user_name,
1493
1748
  agent=agent,
1494
1749
  vision_available=vision_available,
1495
- query_files=query_files,
1496
- generated_files=raw_generated_files,
1497
- generated_asset_results=generated_asset_results,
1498
- program_execution_context=program_execution_context,
1499
1750
  deepthought=deepthought,
1500
1751
  tracer=tracer,
1501
1752
  )
@@ -1503,28 +1754,29 @@ async def agenerate_chat_response(
1503
1754
  api_key = chat_model.ai_model_api.api_key
1504
1755
  api_base_url = chat_model.ai_model_api.api_base_url
1505
1756
  chat_response_generator = converse_gemini(
1757
+ # Query
1506
1758
  query_to_run,
1507
- compiled_references,
1759
+ # Context
1760
+ references=compiled_references,
1508
1761
  online_results=online_results,
1509
1762
  code_results=code_results,
1510
1763
  operator_results=operator_results,
1511
- conversation_log=meta_log,
1764
+ query_images=query_images,
1765
+ query_files=query_files,
1766
+ generated_files=raw_generated_files,
1767
+ generated_asset_results=generated_asset_results,
1768
+ program_execution_context=program_execution_context,
1769
+ location_data=location_data,
1770
+ user_name=user_name,
1771
+ chat_history=chat_history,
1772
+ # Model
1512
1773
  model=chat_model.name,
1513
1774
  api_key=api_key,
1514
1775
  api_base_url=api_base_url,
1515
- completion_func=partial_completion,
1516
- conversation_commands=conversation_commands,
1517
1776
  max_prompt_size=chat_model.max_prompt_size,
1518
1777
  tokenizer_name=chat_model.tokenizer,
1519
- location_data=location_data,
1520
- user_name=user_name,
1521
1778
  agent=agent,
1522
- query_images=query_images,
1523
1779
  vision_available=vision_available,
1524
- query_files=query_files,
1525
- generated_files=raw_generated_files,
1526
- generated_asset_results=generated_asset_results,
1527
- program_execution_context=program_execution_context,
1528
1780
  deepthought=deepthought,
1529
1781
  tracer=tracer,
1530
1782
  )
@@ -2005,11 +2257,11 @@ async def create_automation(
2005
2257
  timezone: str,
2006
2258
  user: KhojUser,
2007
2259
  calling_url: URL,
2008
- meta_log: dict = {},
2260
+ chat_history: List[ChatMessageModel] = [],
2009
2261
  conversation_id: str = None,
2010
2262
  tracer: dict = {},
2011
2263
  ):
2012
- crontime, query_to_run, subject = await aschedule_query(q, meta_log, user, tracer=tracer)
2264
+ crontime, query_to_run, subject = await aschedule_query(q, chat_history, user, tracer=tracer)
2013
2265
  job = await aschedule_automation(query_to_run, subject, crontime, timezone, q, user, calling_url, conversation_id)
2014
2266
  return job, crontime, query_to_run, subject
2015
2267