khoj 1.25.1.dev9__py3-none-any.whl → 1.25.1.dev14__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 (37) hide show
  1. khoj/database/adapters/__init__.py +9 -9
  2. khoj/database/admin.py +2 -1
  3. khoj/interface/compiled/404/index.html +1 -1
  4. khoj/interface/compiled/_next/static/chunks/app/agents/{layout-e71c8e913cccf792.js → layout-75636ab3a413fa8e.js} +1 -1
  5. khoj/interface/compiled/_next/static/chunks/app/chat/{layout-8102549127db3067.js → layout-96fcf62857bf8f30.js} +1 -1
  6. khoj/interface/compiled/_next/static/chunks/app/{layout-f3e40d346da53112.js → layout-d0f0a9067427fb20.js} +1 -1
  7. khoj/interface/compiled/_next/static/chunks/app/settings/{layout-6f9314b0d7a26046.js → layout-a8f33dfe92f997fb.js} +1 -1
  8. khoj/interface/compiled/_next/static/chunks/app/share/chat/{layout-39f03f9e32399f0f.js → layout-2df56074e42adaa0.js} +1 -1
  9. khoj/interface/compiled/_next/static/chunks/{webpack-462819dcfa6a1e2b.js → webpack-3875a06385370d08.js} +1 -1
  10. khoj/interface/compiled/_next/static/css/467a524c75e7d7c0.css +1 -0
  11. khoj/interface/compiled/agents/index.html +1 -1
  12. khoj/interface/compiled/agents/index.txt +1 -1
  13. khoj/interface/compiled/automations/index.html +1 -1
  14. khoj/interface/compiled/automations/index.txt +1 -1
  15. khoj/interface/compiled/chat/index.html +1 -1
  16. khoj/interface/compiled/chat/index.txt +1 -1
  17. khoj/interface/compiled/factchecker/index.html +1 -1
  18. khoj/interface/compiled/factchecker/index.txt +1 -1
  19. khoj/interface/compiled/index.html +1 -1
  20. khoj/interface/compiled/index.txt +1 -1
  21. khoj/interface/compiled/search/index.html +1 -1
  22. khoj/interface/compiled/search/index.txt +1 -1
  23. khoj/interface/compiled/settings/index.html +1 -1
  24. khoj/interface/compiled/settings/index.txt +2 -2
  25. khoj/interface/compiled/share/chat/index.html +1 -1
  26. khoj/interface/compiled/share/chat/index.txt +1 -1
  27. khoj/processor/conversation/google/utils.py +4 -0
  28. khoj/processor/tools/online_search.py +0 -2
  29. khoj/routers/api_chat.py +0 -481
  30. {khoj-1.25.1.dev9.dist-info → khoj-1.25.1.dev14.dist-info}/METADATA +1 -1
  31. {khoj-1.25.1.dev9.dist-info → khoj-1.25.1.dev14.dist-info}/RECORD +36 -36
  32. khoj/interface/compiled/_next/static/css/1538cedb321e3a97.css +0 -1
  33. /khoj/interface/compiled/_next/static/{jRL5xyceUdI0nvEyCkgqF → y6sFTl0gpqdS79jlpmIvx}/_buildManifest.js +0 -0
  34. /khoj/interface/compiled/_next/static/{jRL5xyceUdI0nvEyCkgqF → y6sFTl0gpqdS79jlpmIvx}/_ssgManifest.js +0 -0
  35. {khoj-1.25.1.dev9.dist-info → khoj-1.25.1.dev14.dist-info}/WHEEL +0 -0
  36. {khoj-1.25.1.dev9.dist-info → khoj-1.25.1.dev14.dist-info}/entry_points.txt +0 -0
  37. {khoj-1.25.1.dev9.dist-info → khoj-1.25.1.dev14.dist-info}/licenses/LICENSE +0 -0
khoj/routers/api_chat.py CHANGED
@@ -885,7 +885,6 @@ async def chat(
885
885
  meta_log,
886
886
  location,
887
887
  user,
888
- subscribed,
889
888
  partial(send_event, ChatEvent.STATUS),
890
889
  custom_filters,
891
890
  uploaded_image_url=uploaded_image_url,
@@ -910,7 +909,6 @@ async def chat(
910
909
  meta_log,
911
910
  location,
912
911
  user,
913
- subscribed,
914
912
  partial(send_event, ChatEvent.STATUS),
915
913
  uploaded_image_url=uploaded_image_url,
916
914
  agent=agent,
@@ -1048,482 +1046,3 @@ async def chat(
1048
1046
  response_iterator = event_generator(q, image=image)
1049
1047
  response_data = await read_chat_stream(response_iterator)
1050
1048
  return Response(content=json.dumps(response_data), media_type="application/json", status_code=200)
1051
-
1052
-
1053
- # Deprecated API. Remove by end of September 2024
1054
- @api_chat.get("")
1055
- @requires(["authenticated"])
1056
- async def get_chat(
1057
- request: Request,
1058
- common: CommonQueryParams,
1059
- q: str,
1060
- n: int = 7,
1061
- d: float = None,
1062
- stream: Optional[bool] = False,
1063
- title: Optional[str] = None,
1064
- conversation_id: Optional[str] = None,
1065
- city: Optional[str] = None,
1066
- region: Optional[str] = None,
1067
- country: Optional[str] = None,
1068
- timezone: Optional[str] = None,
1069
- image: Optional[str] = None,
1070
- rate_limiter_per_minute=Depends(
1071
- ApiUserRateLimiter(requests=60, subscribed_requests=60, window=60, slug="chat_minute")
1072
- ),
1073
- rate_limiter_per_day=Depends(
1074
- ApiUserRateLimiter(requests=600, subscribed_requests=600, window=60 * 60 * 24, slug="chat_day")
1075
- ),
1076
- ):
1077
- # Issue a deprecation warning
1078
- warnings.warn(
1079
- "The 'get_chat' API endpoint is deprecated. It will be removed by the end of September 2024.",
1080
- DeprecationWarning,
1081
- stacklevel=2,
1082
- )
1083
-
1084
- async def event_generator(q: str, image: str):
1085
- start_time = time.perf_counter()
1086
- ttft = None
1087
- chat_metadata: dict = {}
1088
- connection_alive = True
1089
- user: KhojUser = request.user.object
1090
- subscribed: bool = has_required_scope(request, ["premium"])
1091
- event_delimiter = "␃🔚␗"
1092
- q = unquote(q)
1093
- nonlocal conversation_id
1094
-
1095
- uploaded_image_url = None
1096
- if image:
1097
- decoded_string = unquote(image)
1098
- base64_data = decoded_string.split(",", 1)[1]
1099
- image_bytes = base64.b64decode(base64_data)
1100
- webp_image_bytes = convert_image_to_webp(image_bytes)
1101
- try:
1102
- uploaded_image_url = upload_image_to_bucket(webp_image_bytes, request.user.object.id)
1103
- except:
1104
- uploaded_image_url = None
1105
-
1106
- async def send_event(event_type: ChatEvent, data: str | dict):
1107
- nonlocal connection_alive, ttft
1108
- if not connection_alive or await request.is_disconnected():
1109
- connection_alive = False
1110
- logger.warn(f"User {user} disconnected from {common.client} client")
1111
- return
1112
- try:
1113
- if event_type == ChatEvent.END_LLM_RESPONSE:
1114
- collect_telemetry()
1115
- if event_type == ChatEvent.START_LLM_RESPONSE:
1116
- ttft = time.perf_counter() - start_time
1117
- if event_type == ChatEvent.MESSAGE:
1118
- yield data
1119
- elif event_type == ChatEvent.REFERENCES or stream:
1120
- yield json.dumps({"type": event_type.value, "data": data}, ensure_ascii=False)
1121
- except asyncio.CancelledError as e:
1122
- connection_alive = False
1123
- logger.warn(f"User {user} disconnected from {common.client} client: {e}")
1124
- return
1125
- except Exception as e:
1126
- connection_alive = False
1127
- logger.error(f"Failed to stream chat API response to {user} on {common.client}: {e}", exc_info=True)
1128
- return
1129
- finally:
1130
- yield event_delimiter
1131
-
1132
- async def send_llm_response(response: str):
1133
- async for result in send_event(ChatEvent.START_LLM_RESPONSE, ""):
1134
- yield result
1135
- async for result in send_event(ChatEvent.MESSAGE, response):
1136
- yield result
1137
- async for result in send_event(ChatEvent.END_LLM_RESPONSE, ""):
1138
- yield result
1139
-
1140
- def collect_telemetry():
1141
- # Gather chat response telemetry
1142
- nonlocal chat_metadata
1143
- latency = time.perf_counter() - start_time
1144
- cmd_set = set([cmd.value for cmd in conversation_commands])
1145
- chat_metadata = chat_metadata or {}
1146
- chat_metadata["conversation_command"] = cmd_set
1147
- chat_metadata["agent"] = conversation.agent.slug if conversation.agent else None
1148
- chat_metadata["latency"] = f"{latency:.3f}"
1149
- chat_metadata["ttft_latency"] = f"{ttft:.3f}"
1150
-
1151
- logger.info(f"Chat response time to first token: {ttft:.3f} seconds")
1152
- logger.info(f"Chat response total time: {latency:.3f} seconds")
1153
- update_telemetry_state(
1154
- request=request,
1155
- telemetry_type="api",
1156
- api="chat",
1157
- client=request.user.client_app,
1158
- user_agent=request.headers.get("user-agent"),
1159
- host=request.headers.get("host"),
1160
- metadata=chat_metadata,
1161
- )
1162
-
1163
- conversation_commands = [get_conversation_command(query=q, any_references=True)]
1164
-
1165
- conversation = await ConversationAdapters.aget_conversation_by_user(
1166
- user, client_application=request.user.client_app, conversation_id=conversation_id, title=title
1167
- )
1168
- if not conversation:
1169
- async for result in send_llm_response(f"Conversation {conversation_id} not found"):
1170
- yield result
1171
- return
1172
- conversation_id = conversation.id
1173
- agent = conversation.agent if conversation.agent else None
1174
-
1175
- await is_ready_to_chat(user)
1176
-
1177
- user_name = await aget_user_name(user)
1178
- location = None
1179
- if city or region or country:
1180
- location = LocationData(city=city, region=region, country=country)
1181
-
1182
- if is_query_empty(q):
1183
- async for result in send_llm_response("Please ask your query to get started."):
1184
- yield result
1185
- return
1186
-
1187
- user_message_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
1188
-
1189
- meta_log = conversation.conversation_log
1190
- is_automated_task = conversation_commands == [ConversationCommand.AutomatedTask]
1191
-
1192
- if conversation_commands == [ConversationCommand.Default] or is_automated_task:
1193
- conversation_commands = await aget_relevant_information_sources(
1194
- q, meta_log, is_automated_task, user=user, uploaded_image_url=uploaded_image_url
1195
- )
1196
- conversation_commands_str = ", ".join([cmd.value for cmd in conversation_commands])
1197
- async for result in send_event(
1198
- ChatEvent.STATUS, f"**Chose Data Sources to Search:** {conversation_commands_str}"
1199
- ):
1200
- yield result
1201
-
1202
- mode = await aget_relevant_output_modes(q, meta_log, is_automated_task, user, uploaded_image_url)
1203
- async for result in send_event(ChatEvent.STATUS, f"**Decided Response Mode:** {mode.value}"):
1204
- yield result
1205
- if mode not in conversation_commands:
1206
- conversation_commands.append(mode)
1207
-
1208
- for cmd in conversation_commands:
1209
- await conversation_command_rate_limiter.update_and_check_if_valid(request, cmd)
1210
- q = q.replace(f"/{cmd.value}", "").strip()
1211
-
1212
- used_slash_summarize = conversation_commands == [ConversationCommand.Summarize]
1213
- file_filters = conversation.file_filters if conversation else []
1214
- # Skip trying to summarize if
1215
- if (
1216
- # summarization intent was inferred
1217
- ConversationCommand.Summarize in conversation_commands
1218
- # and not triggered via slash command
1219
- and not used_slash_summarize
1220
- # but we can't actually summarize
1221
- and len(file_filters) != 1
1222
- ):
1223
- conversation_commands.remove(ConversationCommand.Summarize)
1224
- elif ConversationCommand.Summarize in conversation_commands:
1225
- response_log = ""
1226
- if len(file_filters) == 0:
1227
- response_log = "No files selected for summarization. Please add files using the section on the left."
1228
- async for result in send_llm_response(response_log):
1229
- yield result
1230
- elif len(file_filters) > 1:
1231
- response_log = "Only one file can be selected for summarization."
1232
- async for result in send_llm_response(response_log):
1233
- yield result
1234
- else:
1235
- try:
1236
- file_object = await FileObjectAdapters.async_get_file_objects_by_name(user, file_filters[0])
1237
- if len(file_object) == 0:
1238
- response_log = "Sorry, we couldn't find the full text of this file. Please re-upload the document and try again."
1239
- async for result in send_llm_response(response_log):
1240
- yield result
1241
- return
1242
- contextual_data = " ".join([file.raw_text for file in file_object])
1243
- if not q:
1244
- q = "Create a general summary of the file"
1245
- async for result in send_event(
1246
- ChatEvent.STATUS, f"**Constructing Summary Using:** {file_object[0].file_name}"
1247
- ):
1248
- yield result
1249
-
1250
- response = await extract_relevant_summary(
1251
- q,
1252
- contextual_data,
1253
- conversation_history=meta_log,
1254
- user=user,
1255
- uploaded_image_url=uploaded_image_url,
1256
- )
1257
- response_log = str(response)
1258
- async for result in send_llm_response(response_log):
1259
- yield result
1260
- except Exception as e:
1261
- response_log = "Error summarizing file."
1262
- logger.error(f"Error summarizing file for {user.email}: {e}", exc_info=True)
1263
- async for result in send_llm_response(response_log):
1264
- yield result
1265
- await sync_to_async(save_to_conversation_log)(
1266
- q,
1267
- response_log,
1268
- user,
1269
- meta_log,
1270
- user_message_time,
1271
- intent_type="summarize",
1272
- client_application=request.user.client_app,
1273
- conversation_id=conversation_id,
1274
- uploaded_image_url=uploaded_image_url,
1275
- )
1276
- return
1277
-
1278
- custom_filters = []
1279
- if conversation_commands == [ConversationCommand.Help]:
1280
- if not q:
1281
- conversation_config = await ConversationAdapters.aget_user_conversation_config(user)
1282
- if conversation_config == None:
1283
- conversation_config = await ConversationAdapters.aget_default_conversation_config()
1284
- model_type = conversation_config.model_type
1285
- formatted_help = help_message.format(model=model_type, version=state.khoj_version, device=get_device())
1286
- async for result in send_llm_response(formatted_help):
1287
- yield result
1288
- return
1289
- # Adding specification to search online specifically on khoj.dev pages.
1290
- custom_filters.append("site:khoj.dev")
1291
- conversation_commands.append(ConversationCommand.Online)
1292
-
1293
- if ConversationCommand.Automation in conversation_commands:
1294
- try:
1295
- automation, crontime, query_to_run, subject = await create_automation(
1296
- q, timezone, user, request.url, meta_log
1297
- )
1298
- except Exception as e:
1299
- logger.error(f"Error scheduling task {q} for {user.email}: {e}")
1300
- error_message = f"Unable to create automation. Ensure the automation doesn't already exist."
1301
- async for result in send_llm_response(error_message):
1302
- yield result
1303
- return
1304
-
1305
- llm_response = construct_automation_created_message(automation, crontime, query_to_run, subject)
1306
- await sync_to_async(save_to_conversation_log)(
1307
- q,
1308
- llm_response,
1309
- user,
1310
- meta_log,
1311
- user_message_time,
1312
- intent_type="automation",
1313
- client_application=request.user.client_app,
1314
- conversation_id=conversation_id,
1315
- inferred_queries=[query_to_run],
1316
- automation_id=automation.id,
1317
- uploaded_image_url=uploaded_image_url,
1318
- )
1319
- async for result in send_llm_response(llm_response):
1320
- yield result
1321
- return
1322
-
1323
- # Gather Context
1324
- ## Extract Document References
1325
- compiled_references, inferred_queries, defiltered_query = [], [], None
1326
- async for result in extract_references_and_questions(
1327
- request,
1328
- meta_log,
1329
- q,
1330
- (n or 7),
1331
- d,
1332
- conversation_id,
1333
- conversation_commands,
1334
- location,
1335
- partial(send_event, ChatEvent.STATUS),
1336
- uploaded_image_url=uploaded_image_url,
1337
- ):
1338
- if isinstance(result, dict) and ChatEvent.STATUS in result:
1339
- yield result[ChatEvent.STATUS]
1340
- else:
1341
- compiled_references.extend(result[0])
1342
- inferred_queries.extend(result[1])
1343
- defiltered_query = result[2]
1344
-
1345
- if not is_none_or_empty(compiled_references):
1346
- headings = "\n- " + "\n- ".join(set([c.get("compiled", c).split("\n")[0] for c in compiled_references]))
1347
- # Strip only leading # from headings
1348
- headings = headings.replace("#", "")
1349
- async for result in send_event(ChatEvent.STATUS, f"**Found Relevant Notes**: {headings}"):
1350
- yield result
1351
-
1352
- online_results: Dict = dict()
1353
-
1354
- if conversation_commands == [ConversationCommand.Notes] and not await EntryAdapters.auser_has_entries(user):
1355
- async for result in send_llm_response(f"{no_entries_found.format()}"):
1356
- yield result
1357
- return
1358
-
1359
- if ConversationCommand.Notes in conversation_commands and is_none_or_empty(compiled_references):
1360
- conversation_commands.remove(ConversationCommand.Notes)
1361
-
1362
- ## Gather Online References
1363
- if ConversationCommand.Online in conversation_commands:
1364
- try:
1365
- async for result in search_online(
1366
- defiltered_query,
1367
- meta_log,
1368
- location,
1369
- user,
1370
- subscribed,
1371
- partial(send_event, ChatEvent.STATUS),
1372
- custom_filters,
1373
- uploaded_image_url=uploaded_image_url,
1374
- ):
1375
- if isinstance(result, dict) and ChatEvent.STATUS in result:
1376
- yield result[ChatEvent.STATUS]
1377
- else:
1378
- online_results = result
1379
- except ValueError as e:
1380
- error_message = f"Error searching online: {e}. Attempting to respond without online results"
1381
- logger.warning(error_message)
1382
- async for result in send_llm_response(error_message):
1383
- yield result
1384
- return
1385
-
1386
- ## Gather Webpage References
1387
- if ConversationCommand.Webpage in conversation_commands:
1388
- try:
1389
- async for result in read_webpages(
1390
- defiltered_query,
1391
- meta_log,
1392
- location,
1393
- user,
1394
- subscribed,
1395
- partial(send_event, ChatEvent.STATUS),
1396
- uploaded_image_url=uploaded_image_url,
1397
- ):
1398
- if isinstance(result, dict) and ChatEvent.STATUS in result:
1399
- yield result[ChatEvent.STATUS]
1400
- else:
1401
- direct_web_pages = result
1402
- webpages = []
1403
- for query in direct_web_pages:
1404
- if online_results.get(query):
1405
- online_results[query]["webpages"] = direct_web_pages[query]["webpages"]
1406
- else:
1407
- online_results[query] = {"webpages": direct_web_pages[query]["webpages"]}
1408
-
1409
- for webpage in direct_web_pages[query]["webpages"]:
1410
- webpages.append(webpage["link"])
1411
- async for result in send_event(ChatEvent.STATUS, f"**Read web pages**: {webpages}"):
1412
- yield result
1413
- except ValueError as e:
1414
- logger.warning(
1415
- f"Error directly reading webpages: {e}. Attempting to respond without online results",
1416
- exc_info=True,
1417
- )
1418
-
1419
- ## Send Gathered References
1420
- async for result in send_event(
1421
- ChatEvent.REFERENCES,
1422
- {
1423
- "inferredQueries": inferred_queries,
1424
- "context": compiled_references,
1425
- "onlineContext": online_results,
1426
- },
1427
- ):
1428
- yield result
1429
-
1430
- # Generate Output
1431
- ## Generate Image Output
1432
- if ConversationCommand.Image in conversation_commands:
1433
- async for result in text_to_image(
1434
- q,
1435
- user,
1436
- meta_log,
1437
- location_data=location,
1438
- references=compiled_references,
1439
- online_results=online_results,
1440
- send_status_func=partial(send_event, ChatEvent.STATUS),
1441
- uploaded_image_url=uploaded_image_url,
1442
- ):
1443
- if isinstance(result, dict) and ChatEvent.STATUS in result:
1444
- yield result[ChatEvent.STATUS]
1445
- else:
1446
- image, status_code, improved_image_prompt, intent_type = result
1447
-
1448
- if image is None or status_code != 200:
1449
- content_obj = {
1450
- "content-type": "application/json",
1451
- "intentType": intent_type,
1452
- "detail": improved_image_prompt,
1453
- "image": image,
1454
- }
1455
- async for result in send_llm_response(json.dumps(content_obj)):
1456
- yield result
1457
- return
1458
-
1459
- await sync_to_async(save_to_conversation_log)(
1460
- q,
1461
- image,
1462
- user,
1463
- meta_log,
1464
- user_message_time,
1465
- intent_type=intent_type,
1466
- inferred_queries=[improved_image_prompt],
1467
- client_application=request.user.client_app,
1468
- conversation_id=conversation_id,
1469
- compiled_references=compiled_references,
1470
- online_results=online_results,
1471
- uploaded_image_url=uploaded_image_url,
1472
- )
1473
- content_obj = {
1474
- "intentType": intent_type,
1475
- "inferredQueries": [improved_image_prompt],
1476
- "image": image,
1477
- }
1478
- async for result in send_llm_response(json.dumps(content_obj)):
1479
- yield result
1480
- return
1481
-
1482
- ## Generate Text Output
1483
- async for result in send_event(ChatEvent.STATUS, f"**Generating a well-informed response**"):
1484
- yield result
1485
- llm_response, chat_metadata = await agenerate_chat_response(
1486
- defiltered_query,
1487
- meta_log,
1488
- conversation,
1489
- compiled_references,
1490
- online_results,
1491
- inferred_queries,
1492
- conversation_commands,
1493
- user,
1494
- request.user.client_app,
1495
- conversation_id,
1496
- location,
1497
- user_name,
1498
- uploaded_image_url,
1499
- )
1500
-
1501
- # Send Response
1502
- async for result in send_event(ChatEvent.START_LLM_RESPONSE, ""):
1503
- yield result
1504
-
1505
- continue_stream = True
1506
- iterator = AsyncIteratorWrapper(llm_response)
1507
- async for item in iterator:
1508
- if item is None:
1509
- async for result in send_event(ChatEvent.END_LLM_RESPONSE, ""):
1510
- yield result
1511
- logger.debug("Finished streaming response")
1512
- return
1513
- if not connection_alive or not continue_stream:
1514
- continue
1515
- try:
1516
- async for result in send_event(ChatEvent.MESSAGE, f"{item}"):
1517
- yield result
1518
- except Exception as e:
1519
- continue_stream = False
1520
- logger.info(f"User {user} disconnected. Emitting rest of responses to clear thread: {e}")
1521
-
1522
- ## Stream Text Response
1523
- if stream:
1524
- return StreamingResponse(event_generator(q, image=image), media_type="text/plain")
1525
- ## Non-Streaming Text Response
1526
- else:
1527
- response_iterator = event_generator(q, image=image)
1528
- response_data = await read_chat_stream(response_iterator)
1529
- return Response(content=json.dumps(response_data), media_type="application/json", status_code=200)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: khoj
3
- Version: 1.25.1.dev9
3
+ Version: 1.25.1.dev14
4
4
  Summary: Your Second Brain
5
5
  Project-URL: Homepage, https://khoj.dev
6
6
  Project-URL: Documentation, https://docs.khoj.dev