khoj 1.24.2.dev16__py3-none-any.whl → 1.25.1.dev34__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 (78) hide show
  1. khoj/configure.py +13 -4
  2. khoj/database/adapters/__init__.py +163 -49
  3. khoj/database/admin.py +18 -1
  4. khoj/database/migrations/0068_alter_agent_output_modes.py +24 -0
  5. khoj/database/migrations/0069_webscraper_serverchatsettings_web_scraper.py +89 -0
  6. khoj/database/models/__init__.py +78 -2
  7. khoj/interface/compiled/404/index.html +1 -1
  8. khoj/interface/compiled/_next/static/chunks/1603-fa3ee48860b9dc5c.js +1 -0
  9. khoj/interface/compiled/_next/static/chunks/7762-79f2205740622b5c.js +1 -0
  10. khoj/interface/compiled/_next/static/chunks/app/agents/{layout-e71c8e913cccf792.js → layout-75636ab3a413fa8e.js} +1 -1
  11. khoj/interface/compiled/_next/static/chunks/app/agents/page-fa282831808ee536.js +1 -0
  12. khoj/interface/compiled/_next/static/chunks/app/automations/{page-1688dead2f21270d.js → page-5480731341f34450.js} +1 -1
  13. khoj/interface/compiled/_next/static/chunks/app/chat/{layout-8102549127db3067.js → layout-96fcf62857bf8f30.js} +1 -1
  14. khoj/interface/compiled/_next/static/chunks/app/chat/{page-91abcb71846922b7.js → page-702057ccbcf27881.js} +1 -1
  15. khoj/interface/compiled/_next/static/chunks/app/factchecker/{page-7ab093711c27041c.js → page-e7b34316ec6f44de.js} +1 -1
  16. khoj/interface/compiled/_next/static/chunks/app/{layout-f3e40d346da53112.js → layout-d0f0a9067427fb20.js} +1 -1
  17. khoj/interface/compiled/_next/static/chunks/app/{page-fada198096eab47f.js → page-10a5aad6e04f3cf8.js} +1 -1
  18. khoj/interface/compiled/_next/static/chunks/app/search/{page-a7e036689b6507ff.js → page-d56541c746fded7d.js} +1 -1
  19. khoj/interface/compiled/_next/static/chunks/app/settings/{layout-6f9314b0d7a26046.js → layout-a8f33dfe92f997fb.js} +1 -1
  20. khoj/interface/compiled/_next/static/chunks/app/settings/{page-fa11cafaec7ab39f.js → page-e044a999468a7c5d.js} +1 -1
  21. khoj/interface/compiled/_next/static/chunks/app/share/chat/{layout-39f03f9e32399f0f.js → layout-2df56074e42adaa0.js} +1 -1
  22. khoj/interface/compiled/_next/static/chunks/app/share/chat/{page-c5d2b9076e5390b2.js → page-fbbd66a4d4633438.js} +1 -1
  23. khoj/interface/compiled/_next/static/chunks/{webpack-f52083d548d804fa.js → webpack-c0cd5a6afb1f0798.js} +1 -1
  24. khoj/interface/compiled/_next/static/css/2de69f0be774c768.css +1 -0
  25. khoj/interface/compiled/_next/static/css/3e1f1fdd70775091.css +1 -0
  26. khoj/interface/compiled/_next/static/css/467a524c75e7d7c0.css +1 -0
  27. khoj/interface/compiled/_next/static/css/b9a6bf04305d98d7.css +25 -0
  28. khoj/interface/compiled/agents/index.html +1 -1
  29. khoj/interface/compiled/agents/index.txt +2 -2
  30. khoj/interface/compiled/automations/index.html +1 -1
  31. khoj/interface/compiled/automations/index.txt +2 -2
  32. khoj/interface/compiled/chat/index.html +1 -1
  33. khoj/interface/compiled/chat/index.txt +2 -2
  34. khoj/interface/compiled/factchecker/index.html +1 -1
  35. khoj/interface/compiled/factchecker/index.txt +2 -2
  36. khoj/interface/compiled/index.html +1 -1
  37. khoj/interface/compiled/index.txt +2 -2
  38. khoj/interface/compiled/search/index.html +1 -1
  39. khoj/interface/compiled/search/index.txt +2 -2
  40. khoj/interface/compiled/settings/index.html +1 -1
  41. khoj/interface/compiled/settings/index.txt +3 -3
  42. khoj/interface/compiled/share/chat/index.html +1 -1
  43. khoj/interface/compiled/share/chat/index.txt +2 -2
  44. khoj/interface/web/assets/icons/agents.svg +1 -0
  45. khoj/interface/web/assets/icons/automation.svg +1 -0
  46. khoj/interface/web/assets/icons/chat.svg +24 -0
  47. khoj/interface/web/login.html +11 -22
  48. khoj/processor/conversation/google/gemini_chat.py +4 -19
  49. khoj/processor/conversation/google/utils.py +33 -15
  50. khoj/processor/conversation/prompts.py +14 -3
  51. khoj/processor/conversation/utils.py +3 -7
  52. khoj/processor/embeddings.py +6 -3
  53. khoj/processor/image/generate.py +1 -2
  54. khoj/processor/tools/online_search.py +135 -42
  55. khoj/routers/api.py +1 -1
  56. khoj/routers/api_agents.py +6 -3
  57. khoj/routers/api_chat.py +63 -520
  58. khoj/routers/api_model.py +1 -1
  59. khoj/routers/auth.py +9 -1
  60. khoj/routers/helpers.py +74 -61
  61. khoj/routers/subscription.py +18 -4
  62. khoj/search_type/text_search.py +7 -2
  63. khoj/utils/helpers.py +56 -13
  64. khoj/utils/initialization.py +0 -3
  65. {khoj-1.24.2.dev16.dist-info → khoj-1.25.1.dev34.dist-info}/METADATA +19 -14
  66. {khoj-1.24.2.dev16.dist-info → khoj-1.25.1.dev34.dist-info}/RECORD +71 -68
  67. khoj/interface/compiled/_next/static/chunks/1269-2e52d48e7d0e5c61.js +0 -1
  68. khoj/interface/compiled/_next/static/chunks/1603-67a89278e2c5dbe6.js +0 -1
  69. khoj/interface/compiled/_next/static/chunks/app/agents/page-df26b497b7356151.js +0 -1
  70. khoj/interface/compiled/_next/static/css/1538cedb321e3a97.css +0 -1
  71. khoj/interface/compiled/_next/static/css/4cae6c0e5c72fb2d.css +0 -1
  72. khoj/interface/compiled/_next/static/css/50d972a8c787730b.css +0 -25
  73. khoj/interface/compiled/_next/static/css/dfb67a9287720a2b.css +0 -1
  74. /khoj/interface/compiled/_next/static/{MyYNlmGMz32TGV_-febR4 → Jid9q6Qg851ioDaaO_fth}/_buildManifest.js +0 -0
  75. /khoj/interface/compiled/_next/static/{MyYNlmGMz32TGV_-febR4 → Jid9q6Qg851ioDaaO_fth}/_ssgManifest.js +0 -0
  76. {khoj-1.24.2.dev16.dist-info → khoj-1.25.1.dev34.dist-info}/WHEEL +0 -0
  77. {khoj-1.24.2.dev16.dist-info → khoj-1.25.1.dev34.dist-info}/entry_points.txt +0 -0
  78. {khoj-1.24.2.dev16.dist-info → khoj-1.25.1.dev34.dist-info}/licenses/LICENSE +0 -0
khoj/routers/api_chat.py CHANGED
@@ -3,7 +3,6 @@ import base64
3
3
  import json
4
4
  import logging
5
5
  import time
6
- import warnings
7
6
  from datetime import datetime
8
7
  from functools import partial
9
8
  from typing import Dict, Optional
@@ -194,7 +193,7 @@ def chat_history(
194
193
  n: Optional[int] = None,
195
194
  ):
196
195
  user = request.user.object
197
- validate_conversation_config()
196
+ validate_conversation_config(user)
198
197
 
199
198
  # Load Conversation History
200
199
  conversation = ConversationAdapters.get_conversation_by_user(
@@ -209,14 +208,17 @@ def chat_history(
209
208
 
210
209
  agent_metadata = None
211
210
  if conversation.agent:
212
- agent_metadata = {
213
- "slug": conversation.agent.slug,
214
- "name": conversation.agent.name,
215
- "isCreator": conversation.agent.creator == user,
216
- "color": conversation.agent.style_color,
217
- "icon": conversation.agent.style_icon,
218
- "persona": conversation.agent.personality,
219
- }
211
+ if conversation.agent.privacy_level == Agent.PrivacyLevel.PRIVATE and conversation.agent.creator != user:
212
+ conversation.agent = None
213
+ else:
214
+ agent_metadata = {
215
+ "slug": conversation.agent.slug,
216
+ "name": conversation.agent.name,
217
+ "isCreator": conversation.agent.creator == user,
218
+ "color": conversation.agent.style_color,
219
+ "icon": conversation.agent.style_icon,
220
+ "persona": conversation.agent.personality,
221
+ }
220
222
 
221
223
  meta_log = conversation.conversation_log
222
224
  meta_log.update(
@@ -265,14 +267,17 @@ def get_shared_chat(
265
267
 
266
268
  agent_metadata = None
267
269
  if conversation.agent:
268
- agent_metadata = {
269
- "slug": conversation.agent.slug,
270
- "name": conversation.agent.name,
271
- "isCreator": conversation.agent.creator == user,
272
- "color": conversation.agent.style_color,
273
- "icon": conversation.agent.style_icon,
274
- "persona": conversation.agent.personality,
275
- }
270
+ if conversation.agent.privacy_level == Agent.PrivacyLevel.PRIVATE:
271
+ conversation.agent = None
272
+ else:
273
+ agent_metadata = {
274
+ "slug": conversation.agent.slug,
275
+ "name": conversation.agent.name,
276
+ "isCreator": conversation.agent.creator == user,
277
+ "color": conversation.agent.style_color,
278
+ "icon": conversation.agent.style_icon,
279
+ "persona": conversation.agent.personality,
280
+ }
276
281
 
277
282
  meta_log = conversation.conversation_log
278
283
  scrubbed_title = conversation.title if conversation.title else conversation.slug
@@ -299,7 +304,7 @@ def get_shared_chat(
299
304
  update_telemetry_state(
300
305
  request=request,
301
306
  telemetry_type="api",
302
- api="chat_history",
307
+ api="get_shared_chat_history",
303
308
  **common.__dict__,
304
309
  )
305
310
 
@@ -568,7 +573,6 @@ async def chat(
568
573
  chat_metadata: dict = {}
569
574
  connection_alive = True
570
575
  user: KhojUser = request.user.object
571
- subscribed: bool = has_required_scope(request, ["premium"])
572
576
  event_delimiter = "␃🔚␗"
573
577
  q = unquote(q)
574
578
  nonlocal conversation_id
@@ -635,7 +639,7 @@ async def chat(
635
639
  request=request,
636
640
  telemetry_type="api",
637
641
  api="chat",
638
- client=request.user.client_app,
642
+ client=common.client,
639
643
  user_agent=request.headers.get("user-agent"),
640
644
  host=request.headers.get("host"),
641
645
  metadata=chat_metadata,
@@ -688,7 +692,7 @@ async def chat(
688
692
  q,
689
693
  meta_log,
690
694
  is_automated_task,
691
- subscribed=subscribed,
695
+ user=user,
692
696
  uploaded_image_url=uploaded_image_url,
693
697
  agent=agent,
694
698
  )
@@ -698,7 +702,7 @@ async def chat(
698
702
  ):
699
703
  yield result
700
704
 
701
- mode = await aget_relevant_output_modes(q, meta_log, is_automated_task, uploaded_image_url, agent)
705
+ mode = await aget_relevant_output_modes(q, meta_log, is_automated_task, user, uploaded_image_url, agent)
702
706
  async for result in send_event(ChatEvent.STATUS, f"**Decided Response Mode:** {mode.value}"):
703
707
  yield result
704
708
  if mode not in conversation_commands:
@@ -758,7 +762,12 @@ async def chat(
758
762
  yield result
759
763
 
760
764
  response = await extract_relevant_summary(
761
- q, contextual_data, subscribed=subscribed, uploaded_image_url=uploaded_image_url, agent=agent
765
+ q,
766
+ contextual_data,
767
+ conversation_history=meta_log,
768
+ uploaded_image_url=uploaded_image_url,
769
+ user=user,
770
+ agent=agent,
762
771
  )
763
772
  response_log = str(response)
764
773
  async for result in send_llm_response(response_log):
@@ -829,505 +838,34 @@ async def chat(
829
838
  # Gather Context
830
839
  ## Extract Document References
831
840
  compiled_references, inferred_queries, defiltered_query = [], [], None
832
- async for result in extract_references_and_questions(
833
- request,
834
- meta_log,
835
- q,
836
- (n or 7),
837
- d,
838
- conversation_id,
839
- conversation_commands,
840
- location,
841
- partial(send_event, ChatEvent.STATUS),
842
- uploaded_image_url=uploaded_image_url,
843
- agent=agent,
844
- ):
845
- if isinstance(result, dict) and ChatEvent.STATUS in result:
846
- yield result[ChatEvent.STATUS]
847
- else:
848
- compiled_references.extend(result[0])
849
- inferred_queries.extend(result[1])
850
- defiltered_query = result[2]
851
-
852
- if not is_none_or_empty(compiled_references):
853
- headings = "\n- " + "\n- ".join(set([c.get("compiled", c).split("\n")[0] for c in compiled_references]))
854
- # Strip only leading # from headings
855
- headings = headings.replace("#", "")
856
- async for result in send_event(ChatEvent.STATUS, f"**Found Relevant Notes**: {headings}"):
857
- yield result
858
-
859
- online_results: Dict = dict()
860
-
861
- if conversation_commands == [ConversationCommand.Notes] and not await EntryAdapters.auser_has_entries(user):
862
- async for result in send_llm_response(f"{no_entries_found.format()}"):
863
- yield result
864
- return
865
-
866
- if ConversationCommand.Notes in conversation_commands and is_none_or_empty(compiled_references):
867
- conversation_commands.remove(ConversationCommand.Notes)
868
-
869
- ## Gather Online References
870
- if ConversationCommand.Online in conversation_commands:
871
- try:
872
- async for result in search_online(
873
- defiltered_query,
874
- meta_log,
875
- location,
876
- user,
877
- subscribed,
878
- partial(send_event, ChatEvent.STATUS),
879
- custom_filters,
880
- uploaded_image_url=uploaded_image_url,
881
- agent=agent,
882
- ):
883
- if isinstance(result, dict) and ChatEvent.STATUS in result:
884
- yield result[ChatEvent.STATUS]
885
- else:
886
- online_results = result
887
- except ValueError as e:
888
- error_message = f"Error searching online: {e}. Attempting to respond without online results"
889
- logger.warning(error_message)
890
- async for result in send_llm_response(error_message):
891
- yield result
892
- return
893
-
894
- ## Gather Webpage References
895
- if ConversationCommand.Webpage in conversation_commands:
896
- try:
897
- async for result in read_webpages(
898
- defiltered_query,
899
- meta_log,
900
- location,
901
- user,
902
- subscribed,
903
- partial(send_event, ChatEvent.STATUS),
904
- uploaded_image_url=uploaded_image_url,
905
- agent=agent,
906
- ):
907
- if isinstance(result, dict) and ChatEvent.STATUS in result:
908
- yield result[ChatEvent.STATUS]
909
- else:
910
- direct_web_pages = result
911
- webpages = []
912
- for query in direct_web_pages:
913
- if online_results.get(query):
914
- online_results[query]["webpages"] = direct_web_pages[query]["webpages"]
915
- else:
916
- online_results[query] = {"webpages": direct_web_pages[query]["webpages"]}
917
-
918
- for webpage in direct_web_pages[query]["webpages"]:
919
- webpages.append(webpage["link"])
920
- async for result in send_event(ChatEvent.STATUS, f"**Read web pages**: {webpages}"):
921
- yield result
922
- except ValueError as e:
923
- logger.warning(
924
- f"Error directly reading webpages: {e}. Attempting to respond without online results",
925
- exc_info=True,
926
- )
927
-
928
- ## Send Gathered References
929
- async for result in send_event(
930
- ChatEvent.REFERENCES,
931
- {
932
- "inferredQueries": inferred_queries,
933
- "context": compiled_references,
934
- "onlineContext": online_results,
935
- },
936
- ):
937
- yield result
938
-
939
- # Generate Output
940
- ## Generate Image Output
941
- if ConversationCommand.Image in conversation_commands:
942
- async for result in text_to_image(
943
- q,
944
- user,
841
+ try:
842
+ async for result in extract_references_and_questions(
843
+ request,
945
844
  meta_log,
946
- location_data=location,
947
- references=compiled_references,
948
- online_results=online_results,
949
- subscribed=subscribed,
950
- send_status_func=partial(send_event, ChatEvent.STATUS),
845
+ q,
846
+ (n or 7),
847
+ d,
848
+ conversation_id,
849
+ conversation_commands,
850
+ location,
851
+ partial(send_event, ChatEvent.STATUS),
951
852
  uploaded_image_url=uploaded_image_url,
952
853
  agent=agent,
953
854
  ):
954
855
  if isinstance(result, dict) and ChatEvent.STATUS in result:
955
856
  yield result[ChatEvent.STATUS]
956
857
  else:
957
- image, status_code, improved_image_prompt, intent_type = result
958
-
959
- if image is None or status_code != 200:
960
- content_obj = {
961
- "content-type": "application/json",
962
- "intentType": intent_type,
963
- "detail": improved_image_prompt,
964
- "image": image,
965
- }
966
- async for result in send_llm_response(json.dumps(content_obj)):
967
- yield result
968
- return
969
-
970
- await sync_to_async(save_to_conversation_log)(
971
- q,
972
- image,
973
- user,
974
- meta_log,
975
- user_message_time,
976
- intent_type=intent_type,
977
- inferred_queries=[improved_image_prompt],
978
- client_application=request.user.client_app,
979
- conversation_id=conversation_id,
980
- compiled_references=compiled_references,
981
- online_results=online_results,
982
- uploaded_image_url=uploaded_image_url,
983
- )
984
- content_obj = {
985
- "intentType": intent_type,
986
- "inferredQueries": [improved_image_prompt],
987
- "image": image,
988
- }
989
- async for result in send_llm_response(json.dumps(content_obj)):
990
- yield result
991
- return
992
-
993
- ## Generate Text Output
994
- async for result in send_event(ChatEvent.STATUS, f"**Generating a well-informed response**"):
995
- yield result
996
- llm_response, chat_metadata = await agenerate_chat_response(
997
- defiltered_query,
998
- meta_log,
999
- conversation,
1000
- compiled_references,
1001
- online_results,
1002
- inferred_queries,
1003
- conversation_commands,
1004
- user,
1005
- request.user.client_app,
1006
- conversation_id,
1007
- location,
1008
- user_name,
1009
- uploaded_image_url,
1010
- )
1011
-
1012
- # Send Response
1013
- async for result in send_event(ChatEvent.START_LLM_RESPONSE, ""):
1014
- yield result
1015
-
1016
- continue_stream = True
1017
- iterator = AsyncIteratorWrapper(llm_response)
1018
- async for item in iterator:
1019
- if item is None:
1020
- async for result in send_event(ChatEvent.END_LLM_RESPONSE, ""):
1021
- yield result
1022
- logger.debug("Finished streaming response")
1023
- return
1024
- if not connection_alive or not continue_stream:
1025
- continue
1026
- try:
1027
- async for result in send_event(ChatEvent.MESSAGE, f"{item}"):
1028
- yield result
1029
- except Exception as e:
1030
- continue_stream = False
1031
- logger.info(f"User {user} disconnected. Emitting rest of responses to clear thread: {e}")
1032
-
1033
- ## Stream Text Response
1034
- if stream:
1035
- return StreamingResponse(event_generator(q, image=image), media_type="text/plain")
1036
- ## Non-Streaming Text Response
1037
- else:
1038
- response_iterator = event_generator(q, image=image)
1039
- response_data = await read_chat_stream(response_iterator)
1040
- return Response(content=json.dumps(response_data), media_type="application/json", status_code=200)
1041
-
1042
-
1043
- # Deprecated API. Remove by end of September 2024
1044
- @api_chat.get("")
1045
- @requires(["authenticated"])
1046
- async def get_chat(
1047
- request: Request,
1048
- common: CommonQueryParams,
1049
- q: str,
1050
- n: int = 7,
1051
- d: float = None,
1052
- stream: Optional[bool] = False,
1053
- title: Optional[str] = None,
1054
- conversation_id: Optional[str] = None,
1055
- city: Optional[str] = None,
1056
- region: Optional[str] = None,
1057
- country: Optional[str] = None,
1058
- timezone: Optional[str] = None,
1059
- image: Optional[str] = None,
1060
- rate_limiter_per_minute=Depends(
1061
- ApiUserRateLimiter(requests=60, subscribed_requests=60, window=60, slug="chat_minute")
1062
- ),
1063
- rate_limiter_per_day=Depends(
1064
- ApiUserRateLimiter(requests=600, subscribed_requests=600, window=60 * 60 * 24, slug="chat_day")
1065
- ),
1066
- ):
1067
- # Issue a deprecation warning
1068
- warnings.warn(
1069
- "The 'get_chat' API endpoint is deprecated. It will be removed by the end of September 2024.",
1070
- DeprecationWarning,
1071
- stacklevel=2,
1072
- )
1073
-
1074
- async def event_generator(q: str, image: str):
1075
- start_time = time.perf_counter()
1076
- ttft = None
1077
- chat_metadata: dict = {}
1078
- connection_alive = True
1079
- user: KhojUser = request.user.object
1080
- subscribed: bool = has_required_scope(request, ["premium"])
1081
- event_delimiter = "␃🔚␗"
1082
- q = unquote(q)
1083
- nonlocal conversation_id
1084
-
1085
- uploaded_image_url = None
1086
- if image:
1087
- decoded_string = unquote(image)
1088
- base64_data = decoded_string.split(",", 1)[1]
1089
- image_bytes = base64.b64decode(base64_data)
1090
- webp_image_bytes = convert_image_to_webp(image_bytes)
1091
- try:
1092
- uploaded_image_url = upload_image_to_bucket(webp_image_bytes, request.user.object.id)
1093
- except:
1094
- uploaded_image_url = None
1095
-
1096
- async def send_event(event_type: ChatEvent, data: str | dict):
1097
- nonlocal connection_alive, ttft
1098
- if not connection_alive or await request.is_disconnected():
1099
- connection_alive = False
1100
- logger.warn(f"User {user} disconnected from {common.client} client")
1101
- return
1102
- try:
1103
- if event_type == ChatEvent.END_LLM_RESPONSE:
1104
- collect_telemetry()
1105
- if event_type == ChatEvent.START_LLM_RESPONSE:
1106
- ttft = time.perf_counter() - start_time
1107
- if event_type == ChatEvent.MESSAGE:
1108
- yield data
1109
- elif event_type == ChatEvent.REFERENCES or stream:
1110
- yield json.dumps({"type": event_type.value, "data": data}, ensure_ascii=False)
1111
- except asyncio.CancelledError as e:
1112
- connection_alive = False
1113
- logger.warn(f"User {user} disconnected from {common.client} client: {e}")
1114
- return
1115
- except Exception as e:
1116
- connection_alive = False
1117
- logger.error(f"Failed to stream chat API response to {user} on {common.client}: {e}", exc_info=True)
1118
- return
1119
- finally:
1120
- yield event_delimiter
1121
-
1122
- async def send_llm_response(response: str):
1123
- async for result in send_event(ChatEvent.START_LLM_RESPONSE, ""):
1124
- yield result
1125
- async for result in send_event(ChatEvent.MESSAGE, response):
1126
- yield result
1127
- async for result in send_event(ChatEvent.END_LLM_RESPONSE, ""):
1128
- yield result
1129
-
1130
- def collect_telemetry():
1131
- # Gather chat response telemetry
1132
- nonlocal chat_metadata
1133
- latency = time.perf_counter() - start_time
1134
- cmd_set = set([cmd.value for cmd in conversation_commands])
1135
- chat_metadata = chat_metadata or {}
1136
- chat_metadata["conversation_command"] = cmd_set
1137
- chat_metadata["agent"] = conversation.agent.slug if conversation.agent else None
1138
- chat_metadata["latency"] = f"{latency:.3f}"
1139
- chat_metadata["ttft_latency"] = f"{ttft:.3f}"
1140
-
1141
- logger.info(f"Chat response time to first token: {ttft:.3f} seconds")
1142
- logger.info(f"Chat response total time: {latency:.3f} seconds")
1143
- update_telemetry_state(
1144
- request=request,
1145
- telemetry_type="api",
1146
- api="chat",
1147
- client=request.user.client_app,
1148
- user_agent=request.headers.get("user-agent"),
1149
- host=request.headers.get("host"),
1150
- metadata=chat_metadata,
1151
- )
1152
-
1153
- conversation_commands = [get_conversation_command(query=q, any_references=True)]
1154
-
1155
- conversation = await ConversationAdapters.aget_conversation_by_user(
1156
- user, client_application=request.user.client_app, conversation_id=conversation_id, title=title
1157
- )
1158
- if not conversation:
1159
- async for result in send_llm_response(f"Conversation {conversation_id} not found"):
1160
- yield result
1161
- return
1162
- conversation_id = conversation.id
1163
- agent = conversation.agent if conversation.agent else None
1164
-
1165
- await is_ready_to_chat(user)
1166
-
1167
- user_name = await aget_user_name(user)
1168
- location = None
1169
- if city or region or country:
1170
- location = LocationData(city=city, region=region, country=country)
1171
-
1172
- if is_query_empty(q):
1173
- async for result in send_llm_response("Please ask your query to get started."):
1174
- yield result
1175
- return
1176
-
1177
- user_message_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
1178
-
1179
- meta_log = conversation.conversation_log
1180
- is_automated_task = conversation_commands == [ConversationCommand.AutomatedTask]
1181
-
1182
- if conversation_commands == [ConversationCommand.Default] or is_automated_task:
1183
- conversation_commands = await aget_relevant_information_sources(
1184
- q, meta_log, is_automated_task, subscribed=subscribed, uploaded_image_url=uploaded_image_url
1185
- )
1186
- conversation_commands_str = ", ".join([cmd.value for cmd in conversation_commands])
858
+ compiled_references.extend(result[0])
859
+ inferred_queries.extend(result[1])
860
+ defiltered_query = result[2]
861
+ except Exception as e:
862
+ error_message = f"Error searching knowledge base: {e}. Attempting to respond without document references."
863
+ logger.warning(error_message)
1187
864
  async for result in send_event(
1188
- ChatEvent.STATUS, f"**Chose Data Sources to Search:** {conversation_commands_str}"
865
+ ChatEvent.STATUS, "Document search failed. I'll try respond without document references"
1189
866
  ):
1190
867
  yield result
1191
868
 
1192
- mode = await aget_relevant_output_modes(q, meta_log, is_automated_task, uploaded_image_url)
1193
- async for result in send_event(ChatEvent.STATUS, f"**Decided Response Mode:** {mode.value}"):
1194
- yield result
1195
- if mode not in conversation_commands:
1196
- conversation_commands.append(mode)
1197
-
1198
- for cmd in conversation_commands:
1199
- await conversation_command_rate_limiter.update_and_check_if_valid(request, cmd)
1200
- q = q.replace(f"/{cmd.value}", "").strip()
1201
-
1202
- used_slash_summarize = conversation_commands == [ConversationCommand.Summarize]
1203
- file_filters = conversation.file_filters if conversation else []
1204
- # Skip trying to summarize if
1205
- if (
1206
- # summarization intent was inferred
1207
- ConversationCommand.Summarize in conversation_commands
1208
- # and not triggered via slash command
1209
- and not used_slash_summarize
1210
- # but we can't actually summarize
1211
- and len(file_filters) != 1
1212
- ):
1213
- conversation_commands.remove(ConversationCommand.Summarize)
1214
- elif ConversationCommand.Summarize in conversation_commands:
1215
- response_log = ""
1216
- if len(file_filters) == 0:
1217
- response_log = "No files selected for summarization. Please add files using the section on the left."
1218
- async for result in send_llm_response(response_log):
1219
- yield result
1220
- elif len(file_filters) > 1:
1221
- response_log = "Only one file can be selected for summarization."
1222
- async for result in send_llm_response(response_log):
1223
- yield result
1224
- else:
1225
- try:
1226
- file_object = await FileObjectAdapters.async_get_file_objects_by_name(user, file_filters[0])
1227
- if len(file_object) == 0:
1228
- response_log = "Sorry, we couldn't find the full text of this file. Please re-upload the document and try again."
1229
- async for result in send_llm_response(response_log):
1230
- yield result
1231
- return
1232
- contextual_data = " ".join([file.raw_text for file in file_object])
1233
- if not q:
1234
- q = "Create a general summary of the file"
1235
- async for result in send_event(
1236
- ChatEvent.STATUS, f"**Constructing Summary Using:** {file_object[0].file_name}"
1237
- ):
1238
- yield result
1239
-
1240
- response = await extract_relevant_summary(
1241
- q, contextual_data, subscribed=subscribed, uploaded_image_url=uploaded_image_url
1242
- )
1243
- response_log = str(response)
1244
- async for result in send_llm_response(response_log):
1245
- yield result
1246
- except Exception as e:
1247
- response_log = "Error summarizing file."
1248
- logger.error(f"Error summarizing file for {user.email}: {e}", exc_info=True)
1249
- async for result in send_llm_response(response_log):
1250
- yield result
1251
- await sync_to_async(save_to_conversation_log)(
1252
- q,
1253
- response_log,
1254
- user,
1255
- meta_log,
1256
- user_message_time,
1257
- intent_type="summarize",
1258
- client_application=request.user.client_app,
1259
- conversation_id=conversation_id,
1260
- uploaded_image_url=uploaded_image_url,
1261
- )
1262
- return
1263
-
1264
- custom_filters = []
1265
- if conversation_commands == [ConversationCommand.Help]:
1266
- if not q:
1267
- conversation_config = await ConversationAdapters.aget_user_conversation_config(user)
1268
- if conversation_config == None:
1269
- conversation_config = await ConversationAdapters.aget_default_conversation_config()
1270
- model_type = conversation_config.model_type
1271
- formatted_help = help_message.format(model=model_type, version=state.khoj_version, device=get_device())
1272
- async for result in send_llm_response(formatted_help):
1273
- yield result
1274
- return
1275
- # Adding specification to search online specifically on khoj.dev pages.
1276
- custom_filters.append("site:khoj.dev")
1277
- conversation_commands.append(ConversationCommand.Online)
1278
-
1279
- if ConversationCommand.Automation in conversation_commands:
1280
- try:
1281
- automation, crontime, query_to_run, subject = await create_automation(
1282
- q, timezone, user, request.url, meta_log
1283
- )
1284
- except Exception as e:
1285
- logger.error(f"Error scheduling task {q} for {user.email}: {e}")
1286
- error_message = f"Unable to create automation. Ensure the automation doesn't already exist."
1287
- async for result in send_llm_response(error_message):
1288
- yield result
1289
- return
1290
-
1291
- llm_response = construct_automation_created_message(automation, crontime, query_to_run, subject)
1292
- await sync_to_async(save_to_conversation_log)(
1293
- q,
1294
- llm_response,
1295
- user,
1296
- meta_log,
1297
- user_message_time,
1298
- intent_type="automation",
1299
- client_application=request.user.client_app,
1300
- conversation_id=conversation_id,
1301
- inferred_queries=[query_to_run],
1302
- automation_id=automation.id,
1303
- uploaded_image_url=uploaded_image_url,
1304
- )
1305
- async for result in send_llm_response(llm_response):
1306
- yield result
1307
- return
1308
-
1309
- # Gather Context
1310
- ## Extract Document References
1311
- compiled_references, inferred_queries, defiltered_query = [], [], None
1312
- async for result in extract_references_and_questions(
1313
- request,
1314
- meta_log,
1315
- q,
1316
- (n or 7),
1317
- d,
1318
- conversation_id,
1319
- conversation_commands,
1320
- location,
1321
- partial(send_event, ChatEvent.STATUS),
1322
- uploaded_image_url=uploaded_image_url,
1323
- ):
1324
- if isinstance(result, dict) and ChatEvent.STATUS in result:
1325
- yield result[ChatEvent.STATUS]
1326
- else:
1327
- compiled_references.extend(result[0])
1328
- inferred_queries.extend(result[1])
1329
- defiltered_query = result[2]
1330
-
1331
869
  if not is_none_or_empty(compiled_references):
1332
870
  headings = "\n- " + "\n- ".join(set([c.get("compiled", c).split("\n")[0] for c in compiled_references]))
1333
871
  # Strip only leading # from headings
@@ -1353,21 +891,22 @@ async def get_chat(
1353
891
  meta_log,
1354
892
  location,
1355
893
  user,
1356
- subscribed,
1357
894
  partial(send_event, ChatEvent.STATUS),
1358
895
  custom_filters,
1359
896
  uploaded_image_url=uploaded_image_url,
897
+ agent=agent,
1360
898
  ):
1361
899
  if isinstance(result, dict) and ChatEvent.STATUS in result:
1362
900
  yield result[ChatEvent.STATUS]
1363
901
  else:
1364
902
  online_results = result
1365
- except ValueError as e:
903
+ except Exception as e:
1366
904
  error_message = f"Error searching online: {e}. Attempting to respond without online results"
1367
905
  logger.warning(error_message)
1368
- async for result in send_llm_response(error_message):
906
+ async for result in send_event(
907
+ ChatEvent.STATUS, "Online search failed. I'll try respond without online references"
908
+ ):
1369
909
  yield result
1370
- return
1371
910
 
1372
911
  ## Gather Webpage References
1373
912
  if ConversationCommand.Webpage in conversation_commands:
@@ -1377,9 +916,9 @@ async def get_chat(
1377
916
  meta_log,
1378
917
  location,
1379
918
  user,
1380
- subscribed,
1381
919
  partial(send_event, ChatEvent.STATUS),
1382
920
  uploaded_image_url=uploaded_image_url,
921
+ agent=agent,
1383
922
  ):
1384
923
  if isinstance(result, dict) and ChatEvent.STATUS in result:
1385
924
  yield result[ChatEvent.STATUS]
@@ -1396,11 +935,15 @@ async def get_chat(
1396
935
  webpages.append(webpage["link"])
1397
936
  async for result in send_event(ChatEvent.STATUS, f"**Read web pages**: {webpages}"):
1398
937
  yield result
1399
- except ValueError as e:
938
+ except Exception as e:
1400
939
  logger.warning(
1401
- f"Error directly reading webpages: {e}. Attempting to respond without online results",
940
+ f"Error reading webpages: {e}. Attempting to respond without webpage results",
1402
941
  exc_info=True,
1403
942
  )
943
+ async for result in send_event(
944
+ ChatEvent.STATUS, "Webpage read failed. I'll try respond without webpage references"
945
+ ):
946
+ yield result
1404
947
 
1405
948
  ## Send Gathered References
1406
949
  async for result in send_event(
@@ -1423,9 +966,9 @@ async def get_chat(
1423
966
  location_data=location,
1424
967
  references=compiled_references,
1425
968
  online_results=online_results,
1426
- subscribed=subscribed,
1427
969
  send_status_func=partial(send_event, ChatEvent.STATUS),
1428
970
  uploaded_image_url=uploaded_image_url,
971
+ agent=agent,
1429
972
  ):
1430
973
  if isinstance(result, dict) and ChatEvent.STATUS in result:
1431
974
  yield result[ChatEvent.STATUS]