khoj 1.24.2.dev2__py3-none-any.whl → 1.24.2.dev16__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 (88) hide show
  1. khoj/database/adapters/__init__.py +139 -16
  2. khoj/database/admin.py +2 -0
  3. khoj/database/migrations/0065_remove_agent_avatar_remove_agent_public_and_more.py +49 -0
  4. khoj/database/migrations/0066_remove_agent_tools_agent_input_tools_and_more.py +69 -0
  5. khoj/database/migrations/0067_alter_agent_style_icon.py +50 -0
  6. khoj/database/models/__init__.py +60 -18
  7. khoj/interface/compiled/404/index.html +1 -1
  8. khoj/interface/compiled/_next/static/chunks/1269-2e52d48e7d0e5c61.js +1 -0
  9. khoj/interface/compiled/_next/static/chunks/1603-67a89278e2c5dbe6.js +1 -0
  10. khoj/interface/compiled/_next/static/chunks/2697-a38d01981ad3bdf8.js +1 -0
  11. khoj/interface/compiled/_next/static/chunks/3110-ef2cacd1b8d79ad8.js +1 -0
  12. khoj/interface/compiled/_next/static/chunks/4086-2c74808ba38a5a0f.js +1 -0
  13. khoj/interface/compiled/_next/static/chunks/477-ec86e93db10571c1.js +1 -0
  14. khoj/interface/compiled/_next/static/chunks/51-e8f5bdb69b5ea421.js +1 -0
  15. khoj/interface/compiled/_next/static/chunks/9178-899fe9a6b754ecfe.js +1 -0
  16. khoj/interface/compiled/_next/static/chunks/9417-29502e39c3e7d60c.js +1 -0
  17. khoj/interface/compiled/_next/static/chunks/9479-7eed36fc954ef804.js +1 -0
  18. khoj/interface/compiled/_next/static/chunks/app/agents/page-df26b497b7356151.js +1 -0
  19. khoj/interface/compiled/_next/static/chunks/app/automations/page-1688dead2f21270d.js +1 -0
  20. khoj/interface/compiled/_next/static/chunks/app/chat/page-91abcb71846922b7.js +1 -0
  21. khoj/interface/compiled/_next/static/chunks/app/factchecker/page-7ab093711c27041c.js +1 -0
  22. khoj/interface/compiled/_next/static/chunks/app/page-fada198096eab47f.js +1 -0
  23. khoj/interface/compiled/_next/static/chunks/app/search/page-a7e036689b6507ff.js +1 -0
  24. khoj/interface/compiled/_next/static/chunks/app/settings/page-fa11cafaec7ab39f.js +1 -0
  25. khoj/interface/compiled/_next/static/chunks/app/share/chat/page-c5d2b9076e5390b2.js +1 -0
  26. khoj/interface/compiled/_next/static/chunks/{webpack-878fd47921816d3c.js → webpack-f52083d548d804fa.js} +1 -1
  27. khoj/interface/compiled/_next/static/css/4cae6c0e5c72fb2d.css +1 -0
  28. khoj/interface/compiled/_next/static/css/50d972a8c787730b.css +25 -0
  29. khoj/interface/compiled/_next/static/css/dfb67a9287720a2b.css +1 -0
  30. khoj/interface/compiled/agents/index.html +1 -1
  31. khoj/interface/compiled/agents/index.txt +2 -2
  32. khoj/interface/compiled/automations/index.html +1 -1
  33. khoj/interface/compiled/automations/index.txt +2 -2
  34. khoj/interface/compiled/chat/index.html +1 -1
  35. khoj/interface/compiled/chat/index.txt +2 -2
  36. khoj/interface/compiled/factchecker/index.html +1 -1
  37. khoj/interface/compiled/factchecker/index.txt +2 -2
  38. khoj/interface/compiled/index.html +1 -1
  39. khoj/interface/compiled/index.txt +2 -2
  40. khoj/interface/compiled/search/index.html +1 -1
  41. khoj/interface/compiled/search/index.txt +2 -2
  42. khoj/interface/compiled/settings/index.html +1 -1
  43. khoj/interface/compiled/settings/index.txt +2 -2
  44. khoj/interface/compiled/share/chat/index.html +1 -1
  45. khoj/interface/compiled/share/chat/index.txt +2 -2
  46. khoj/processor/content/notion/notion_to_entries.py +2 -1
  47. khoj/processor/conversation/anthropic/anthropic_chat.py +2 -0
  48. khoj/processor/conversation/google/gemini_chat.py +2 -0
  49. khoj/processor/conversation/offline/chat_model.py +3 -1
  50. khoj/processor/conversation/openai/gpt.py +2 -0
  51. khoj/processor/conversation/prompts.py +56 -5
  52. khoj/processor/image/generate.py +3 -1
  53. khoj/processor/tools/online_search.py +9 -7
  54. khoj/routers/api.py +34 -5
  55. khoj/routers/api_agents.py +232 -4
  56. khoj/routers/api_chat.py +46 -17
  57. khoj/routers/api_content.py +14 -0
  58. khoj/routers/helpers.py +113 -13
  59. khoj/search_type/text_search.py +4 -1
  60. khoj/utils/helpers.py +15 -2
  61. {khoj-1.24.2.dev2.dist-info → khoj-1.24.2.dev16.dist-info}/METADATA +1 -8
  62. {khoj-1.24.2.dev2.dist-info → khoj-1.24.2.dev16.dist-info}/RECORD +67 -64
  63. khoj/interface/compiled/_next/static/chunks/1603-3e2e1528e3b6ea1d.js +0 -1
  64. khoj/interface/compiled/_next/static/chunks/2697-a29cb9191a9e339c.js +0 -1
  65. khoj/interface/compiled/_next/static/chunks/6648-ee109f4ea33a74e2.js +0 -1
  66. khoj/interface/compiled/_next/static/chunks/7071-b4711cecca6619a8.js +0 -1
  67. khoj/interface/compiled/_next/static/chunks/743-1a64254447cda71f.js +0 -1
  68. khoj/interface/compiled/_next/static/chunks/8423-62ac6c832be2461b.js +0 -1
  69. khoj/interface/compiled/_next/static/chunks/9162-0be016519a18568b.js +0 -1
  70. khoj/interface/compiled/_next/static/chunks/9178-409f672ab573b8fd.js +0 -1
  71. khoj/interface/compiled/_next/static/chunks/9417-5d14ac74aaab2c66.js +0 -1
  72. khoj/interface/compiled/_next/static/chunks/9984-e410179c6fac7cf1.js +0 -1
  73. khoj/interface/compiled/_next/static/chunks/app/agents/page-a3db5b3869f83937.js +0 -1
  74. khoj/interface/compiled/_next/static/chunks/app/automations/page-e68cb1eba3cc41de.js +0 -1
  75. khoj/interface/compiled/_next/static/chunks/app/chat/page-5b1626fc2882c1f9.js +0 -1
  76. khoj/interface/compiled/_next/static/chunks/app/factchecker/page-b01f8a9b9107ecbe.js +0 -1
  77. khoj/interface/compiled/_next/static/chunks/app/page-ee9ee504f0d5ace6.js +0 -1
  78. khoj/interface/compiled/_next/static/chunks/app/search/page-53c2494182551684.js +0 -1
  79. khoj/interface/compiled/_next/static/chunks/app/settings/page-2a7e60e3782ed95e.js +0 -1
  80. khoj/interface/compiled/_next/static/chunks/app/share/chat/page-9d9faa4a155bbf58.js +0 -1
  81. khoj/interface/compiled/_next/static/css/24f141a6e37cd204.css +0 -25
  82. khoj/interface/compiled/_next/static/css/3e1f1fdd70775091.css +0 -1
  83. khoj/interface/compiled/_next/static/css/60fc94dfe42ddfe9.css +0 -1
  84. /khoj/interface/compiled/_next/static/{sXEsDJ1Vi3HypDes8jcxW → MyYNlmGMz32TGV_-febR4}/_buildManifest.js +0 -0
  85. /khoj/interface/compiled/_next/static/{sXEsDJ1Vi3HypDes8jcxW → MyYNlmGMz32TGV_-febR4}/_ssgManifest.js +0 -0
  86. {khoj-1.24.2.dev2.dist-info → khoj-1.24.2.dev16.dist-info}/WHEEL +0 -0
  87. {khoj-1.24.2.dev2.dist-info → khoj-1.24.2.dev16.dist-info}/entry_points.txt +0 -0
  88. {khoj-1.24.2.dev2.dist-info → khoj-1.24.2.dev16.dist-info}/licenses/LICENSE +0 -0
@@ -2,11 +2,13 @@ import asyncio
2
2
  import json
3
3
  import logging
4
4
  import math
5
+ from concurrent.futures import ThreadPoolExecutor
5
6
  from typing import Dict, List, Optional, Union
6
7
 
7
8
  from asgiref.sync import sync_to_async
8
9
  from fastapi import (
9
10
  APIRouter,
11
+ BackgroundTasks,
10
12
  Depends,
11
13
  Header,
12
14
  HTTPException,
@@ -58,6 +60,8 @@ logger = logging.getLogger(__name__)
58
60
 
59
61
  api_content = APIRouter()
60
62
 
63
+ executor = ThreadPoolExecutor()
64
+
61
65
 
62
66
  class File(BaseModel):
63
67
  path: str
@@ -77,6 +81,11 @@ class IndexerInput(BaseModel):
77
81
  docx: Optional[dict[str, bytes]] = None
78
82
 
79
83
 
84
+ async def run_in_executor(func, *args):
85
+ loop = asyncio.get_event_loop()
86
+ return await loop.run_in_executor(executor, func, *args)
87
+
88
+
80
89
  @api_content.put("")
81
90
  @requires(["authenticated"])
82
91
  async def put_content(
@@ -209,6 +218,7 @@ async def set_content_github(
209
218
  @requires(["authenticated"])
210
219
  async def set_content_notion(
211
220
  request: Request,
221
+ background_tasks: BackgroundTasks,
212
222
  updated_config: Union[NotionContentConfig, None],
213
223
  client: Optional[str] = None,
214
224
  ):
@@ -225,6 +235,10 @@ async def set_content_notion(
225
235
  logger.error(e, exc_info=True)
226
236
  raise HTTPException(status_code=500, detail="Failed to set Notion config")
227
237
 
238
+ if updated_config.token:
239
+ # Trigger an async job to configure_content. Let it run without blocking the response.
240
+ background_tasks.add_task(run_in_executor, configure_content, {}, False, SearchType.Notion, user)
241
+
228
242
  update_telemetry_state(
229
243
  request=request,
230
244
  telemetry_type="api",
khoj/routers/helpers.py CHANGED
@@ -47,6 +47,7 @@ from khoj.database.adapters import (
47
47
  run_with_process_lock,
48
48
  )
49
49
  from khoj.database.models import (
50
+ Agent,
50
51
  ChatModelOptions,
51
52
  ClientApplication,
52
53
  Conversation,
@@ -257,8 +258,39 @@ async def acreate_title_from_query(query: str) -> str:
257
258
  return response.strip()
258
259
 
259
260
 
261
+ async def acheck_if_safe_prompt(system_prompt: str) -> Tuple[bool, str]:
262
+ """
263
+ Check if the system prompt is safe to use
264
+ """
265
+ safe_prompt_check = prompts.personality_prompt_safety_expert.format(prompt=system_prompt)
266
+ is_safe = True
267
+ reason = ""
268
+
269
+ with timer("Chat actor: Check if safe prompt", logger):
270
+ response = await send_message_to_model_wrapper(safe_prompt_check)
271
+
272
+ response = response.strip()
273
+ try:
274
+ response = json.loads(response)
275
+ is_safe = response.get("safe", "True") == "True"
276
+ if not is_safe:
277
+ reason = response.get("reason", "")
278
+ except Exception:
279
+ logger.error(f"Invalid response for checking safe prompt: {response}")
280
+
281
+ if not is_safe:
282
+ logger.error(f"Unsafe prompt: {system_prompt}. Reason: {reason}")
283
+
284
+ return is_safe, reason
285
+
286
+
260
287
  async def aget_relevant_information_sources(
261
- query: str, conversation_history: dict, is_task: bool, subscribed: bool, uploaded_image_url: str = None
288
+ query: str,
289
+ conversation_history: dict,
290
+ is_task: bool,
291
+ subscribed: bool,
292
+ uploaded_image_url: str = None,
293
+ agent: Agent = None,
262
294
  ):
263
295
  """
264
296
  Given a query, determine which of the available tools the agent should use in order to answer appropriately.
@@ -267,19 +299,27 @@ async def aget_relevant_information_sources(
267
299
  tool_options = dict()
268
300
  tool_options_str = ""
269
301
 
302
+ agent_tools = agent.input_tools if agent else []
303
+
270
304
  for tool, description in tool_descriptions_for_llm.items():
271
305
  tool_options[tool.value] = description
272
- tool_options_str += f'- "{tool.value}": "{description}"\n'
306
+ if len(agent_tools) == 0 or tool.value in agent_tools:
307
+ tool_options_str += f'- "{tool.value}": "{description}"\n'
273
308
 
274
309
  chat_history = construct_chat_history(conversation_history)
275
310
 
276
311
  if uploaded_image_url:
277
312
  query = f"[placeholder for user attached image]\n{query}"
278
313
 
314
+ personality_context = (
315
+ prompts.personality_context.format(personality=agent.personality) if agent and agent.personality else ""
316
+ )
317
+
279
318
  relevant_tools_prompt = prompts.pick_relevant_information_collection_tools.format(
280
319
  query=query,
281
320
  tools=tool_options_str,
282
321
  chat_history=chat_history,
322
+ personality_context=personality_context,
283
323
  )
284
324
 
285
325
  with timer("Chat actor: Infer information sources to refer", logger):
@@ -300,7 +340,10 @@ async def aget_relevant_information_sources(
300
340
 
301
341
  final_response = [] if not is_task else [ConversationCommand.AutomatedTask]
302
342
  for llm_suggested_tool in response:
303
- if llm_suggested_tool in tool_options.keys():
343
+ # Add a double check to verify it's in the agent list, because the LLM sometimes gets confused by the tool options.
344
+ if llm_suggested_tool in tool_options.keys() and (
345
+ len(agent_tools) == 0 or llm_suggested_tool in agent_tools
346
+ ):
304
347
  # Check whether the tool exists as a valid ConversationCommand
305
348
  final_response.append(ConversationCommand(llm_suggested_tool))
306
349
 
@@ -313,7 +356,7 @@ async def aget_relevant_information_sources(
313
356
 
314
357
 
315
358
  async def aget_relevant_output_modes(
316
- query: str, conversation_history: dict, is_task: bool = False, uploaded_image_url: str = None
359
+ query: str, conversation_history: dict, is_task: bool = False, uploaded_image_url: str = None, agent: Agent = None
317
360
  ):
318
361
  """
319
362
  Given a query, determine which of the available tools the agent should use in order to answer appropriately.
@@ -322,22 +365,30 @@ async def aget_relevant_output_modes(
322
365
  mode_options = dict()
323
366
  mode_options_str = ""
324
367
 
368
+ output_modes = agent.output_modes if agent else []
369
+
325
370
  for mode, description in mode_descriptions_for_llm.items():
326
371
  # Do not allow tasks to schedule another task
327
372
  if is_task and mode == ConversationCommand.Automation:
328
373
  continue
329
374
  mode_options[mode.value] = description
330
- mode_options_str += f'- "{mode.value}": "{description}"\n'
375
+ if len(output_modes) == 0 or mode.value in output_modes:
376
+ mode_options_str += f'- "{mode.value}": "{description}"\n'
331
377
 
332
378
  chat_history = construct_chat_history(conversation_history)
333
379
 
334
380
  if uploaded_image_url:
335
381
  query = f"[placeholder for user attached image]\n{query}"
336
382
 
383
+ personality_context = (
384
+ prompts.personality_context.format(personality=agent.personality) if agent and agent.personality else ""
385
+ )
386
+
337
387
  relevant_mode_prompt = prompts.pick_relevant_output_mode.format(
338
388
  query=query,
339
389
  modes=mode_options_str,
340
390
  chat_history=chat_history,
391
+ personality_context=personality_context,
341
392
  )
342
393
 
343
394
  with timer("Chat actor: Infer output mode for chat response", logger):
@@ -352,7 +403,9 @@ async def aget_relevant_output_modes(
352
403
  return ConversationCommand.Text
353
404
 
354
405
  output_mode = response["output"]
355
- if output_mode in mode_options.keys():
406
+
407
+ # Add a double check to verify it's in the agent list, because the LLM sometimes gets confused by the tool options.
408
+ if output_mode in mode_options.keys() and (len(output_modes) == 0 or output_mode in output_modes):
356
409
  # Check whether the tool exists as a valid ConversationCommand
357
410
  return ConversationCommand(output_mode)
358
411
 
@@ -364,7 +417,12 @@ async def aget_relevant_output_modes(
364
417
 
365
418
 
366
419
  async def infer_webpage_urls(
367
- q: str, conversation_history: dict, location_data: LocationData, user: KhojUser, uploaded_image_url: str = None
420
+ q: str,
421
+ conversation_history: dict,
422
+ location_data: LocationData,
423
+ user: KhojUser,
424
+ uploaded_image_url: str = None,
425
+ agent: Agent = None,
368
426
  ) -> List[str]:
369
427
  """
370
428
  Infer webpage links from the given query
@@ -374,12 +432,17 @@ async def infer_webpage_urls(
374
432
  chat_history = construct_chat_history(conversation_history)
375
433
 
376
434
  utc_date = datetime.utcnow().strftime("%Y-%m-%d")
435
+ personality_context = (
436
+ prompts.personality_context.format(personality=agent.personality) if agent and agent.personality else ""
437
+ )
438
+
377
439
  online_queries_prompt = prompts.infer_webpages_to_read.format(
378
440
  current_date=utc_date,
379
441
  query=q,
380
442
  chat_history=chat_history,
381
443
  location=location,
382
444
  username=username,
445
+ personality_context=personality_context,
383
446
  )
384
447
 
385
448
  with timer("Chat actor: Infer webpage urls to read", logger):
@@ -400,7 +463,12 @@ async def infer_webpage_urls(
400
463
 
401
464
 
402
465
  async def generate_online_subqueries(
403
- q: str, conversation_history: dict, location_data: LocationData, user: KhojUser, uploaded_image_url: str = None
466
+ q: str,
467
+ conversation_history: dict,
468
+ location_data: LocationData,
469
+ user: KhojUser,
470
+ uploaded_image_url: str = None,
471
+ agent: Agent = None,
404
472
  ) -> List[str]:
405
473
  """
406
474
  Generate subqueries from the given query
@@ -410,12 +478,17 @@ async def generate_online_subqueries(
410
478
  chat_history = construct_chat_history(conversation_history)
411
479
 
412
480
  utc_date = datetime.utcnow().strftime("%Y-%m-%d")
481
+ personality_context = (
482
+ prompts.personality_context.format(personality=agent.personality) if agent and agent.personality else ""
483
+ )
484
+
413
485
  online_queries_prompt = prompts.online_search_conversation_subqueries.format(
414
486
  current_date=utc_date,
415
487
  query=q,
416
488
  chat_history=chat_history,
417
489
  location=location,
418
490
  username=username,
491
+ personality_context=personality_context,
419
492
  )
420
493
 
421
494
  with timer("Chat actor: Generate online search subqueries", logger):
@@ -464,7 +537,7 @@ async def schedule_query(q: str, conversation_history: dict, uploaded_image_url:
464
537
  raise AssertionError(f"Invalid response for scheduling query: {raw_response}")
465
538
 
466
539
 
467
- async def extract_relevant_info(q: str, corpus: str, subscribed: bool) -> Union[str, None]:
540
+ async def extract_relevant_info(q: str, corpus: str, subscribed: bool, agent: Agent = None) -> Union[str, None]:
468
541
  """
469
542
  Extract relevant information for a given query from the target corpus
470
543
  """
@@ -472,9 +545,14 @@ async def extract_relevant_info(q: str, corpus: str, subscribed: bool) -> Union[
472
545
  if is_none_or_empty(corpus) or is_none_or_empty(q):
473
546
  return None
474
547
 
548
+ personality_context = (
549
+ prompts.personality_context.format(personality=agent.personality) if agent and agent.personality else ""
550
+ )
551
+
475
552
  extract_relevant_information = prompts.extract_relevant_information.format(
476
553
  query=q,
477
554
  corpus=corpus.strip(),
555
+ personality_context=personality_context,
478
556
  )
479
557
 
480
558
  chat_model: ChatModelOptions = await ConversationAdapters.aget_default_conversation_config()
@@ -490,7 +568,7 @@ async def extract_relevant_info(q: str, corpus: str, subscribed: bool) -> Union[
490
568
 
491
569
 
492
570
  async def extract_relevant_summary(
493
- q: str, corpus: str, subscribed: bool = False, uploaded_image_url: str = None
571
+ q: str, corpus: str, subscribed: bool = False, uploaded_image_url: str = None, agent: Agent = None
494
572
  ) -> Union[str, None]:
495
573
  """
496
574
  Extract relevant information for a given query from the target corpus
@@ -499,9 +577,14 @@ async def extract_relevant_summary(
499
577
  if is_none_or_empty(corpus) or is_none_or_empty(q):
500
578
  return None
501
579
 
580
+ personality_context = (
581
+ prompts.personality_context.format(personality=agent.personality) if agent and agent.personality else ""
582
+ )
583
+
502
584
  extract_relevant_information = prompts.extract_relevant_summary.format(
503
585
  query=q,
504
586
  corpus=corpus.strip(),
587
+ personality_context=personality_context,
505
588
  )
506
589
 
507
590
  chat_model: ChatModelOptions = await ConversationAdapters.aget_default_conversation_config()
@@ -526,12 +609,16 @@ async def generate_better_image_prompt(
526
609
  model_type: Optional[str] = None,
527
610
  subscribed: bool = False,
528
611
  uploaded_image_url: Optional[str] = None,
612
+ agent: Agent = None,
529
613
  ) -> str:
530
614
  """
531
615
  Generate a better image prompt from the given query
532
616
  """
533
617
 
534
618
  today_date = datetime.now(tz=timezone.utc).strftime("%Y-%m-%d, %A")
619
+ personality_context = (
620
+ prompts.personality_context.format(personality=agent.personality) if agent and agent.personality else ""
621
+ )
535
622
  model_type = model_type or TextToImageModelConfig.ModelType.OPENAI
536
623
 
537
624
  if location_data:
@@ -558,6 +645,7 @@ async def generate_better_image_prompt(
558
645
  current_date=today_date,
559
646
  references=user_references,
560
647
  online_results=simplified_online_results,
648
+ personality_context=personality_context,
561
649
  )
562
650
  elif model_type in [TextToImageModelConfig.ModelType.STABILITYAI, TextToImageModelConfig.ModelType.REPLICATE]:
563
651
  image_prompt = prompts.image_generation_improve_prompt_sd.format(
@@ -567,6 +655,7 @@ async def generate_better_image_prompt(
567
655
  current_date=today_date,
568
656
  references=user_references,
569
657
  online_results=simplified_online_results,
658
+ personality_context=personality_context,
570
659
  )
571
660
 
572
661
  chat_model: ChatModelOptions = await ConversationAdapters.aget_default_conversation_config()
@@ -651,15 +740,13 @@ async def send_message_to_model_wrapper(
651
740
  model_type=conversation_config.model_type,
652
741
  )
653
742
 
654
- openai_response = send_message_to_model(
743
+ return send_message_to_model(
655
744
  messages=truncated_messages,
656
745
  api_key=api_key,
657
746
  model=chat_model,
658
747
  response_type=response_type,
659
748
  api_base_url=api_base_url,
660
749
  )
661
-
662
- return openai_response
663
750
  elif model_type == ChatModelOptions.ModelType.ANTHROPIC:
664
751
  api_key = conversation_config.openai_config.api_key
665
752
  truncated_messages = generate_chatml_messages_with_context(
@@ -942,13 +1029,23 @@ class ApiUserRateLimiter:
942
1029
 
943
1030
  # Check if the user has exceeded the rate limit
944
1031
  if subscribed and count_requests >= self.subscribed_requests:
1032
+ logger.info(
1033
+ f"Rate limit: {count_requests} requests in {self.window} seconds for user: {user}. Limit is {self.subscribed_requests} requests."
1034
+ )
945
1035
  raise HTTPException(status_code=429, detail="Slow down! Too Many Requests")
946
1036
  if not subscribed and count_requests >= self.requests:
947
1037
  if self.requests >= self.subscribed_requests:
1038
+ logger.info(
1039
+ f"Rate limit: {count_requests} requests in {self.window} seconds for user: {user}. Limit is {self.subscribed_requests} requests."
1040
+ )
948
1041
  raise HTTPException(
949
1042
  status_code=429,
950
1043
  detail="Slow down! Too Many Requests",
951
1044
  )
1045
+
1046
+ logger.info(
1047
+ f"Rate limit: {count_requests} requests in {self.window} seconds for user: {user}. Limit is {self.subscribed_requests} requests."
1048
+ )
952
1049
  raise HTTPException(
953
1050
  status_code=429,
954
1051
  detail="We're glad you're enjoying Khoj! You've exceeded your usage limit for today. Come back tomorrow or subscribe to increase your usage limit via [your settings](https://app.khoj.dev/settings).",
@@ -986,6 +1083,9 @@ class ConversationCommandRateLimiter:
986
1083
  ).acount()
987
1084
 
988
1085
  if subscribed and count_requests >= self.subscribed_rate_limit:
1086
+ logger.info(
1087
+ f"Rate limit: {count_requests} requests in 24 hours for user: {user}. Limit is {self.subscribed_rate_limit} requests."
1088
+ )
989
1089
  raise HTTPException(status_code=429, detail="Slow down! Too Many Requests")
990
1090
  if not subscribed and count_requests >= self.trial_rate_limit:
991
1091
  raise HTTPException(
@@ -1,13 +1,14 @@
1
1
  import logging
2
2
  import math
3
3
  from pathlib import Path
4
- from typing import List, Tuple, Type, Union
4
+ from typing import List, Optional, Tuple, Type, Union
5
5
 
6
6
  import torch
7
7
  from asgiref.sync import sync_to_async
8
8
  from sentence_transformers import util
9
9
 
10
10
  from khoj.database.adapters import EntryAdapters, get_user_search_model_or_default
11
+ from khoj.database.models import Agent
11
12
  from khoj.database.models import Entry as DbEntry
12
13
  from khoj.database.models import KhojUser
13
14
  from khoj.processor.content.text_to_entries import TextToEntries
@@ -101,6 +102,7 @@ async def query(
101
102
  type: SearchType = SearchType.All,
102
103
  question_embedding: Union[torch.Tensor, None] = None,
103
104
  max_distance: float = None,
105
+ agent: Optional[Agent] = None,
104
106
  ) -> Tuple[List[dict], List[Entry]]:
105
107
  "Search for entries that answer the query"
106
108
 
@@ -129,6 +131,7 @@ async def query(
129
131
  file_type_filter=file_type,
130
132
  raw_query=raw_query,
131
133
  max_distance=max_distance,
134
+ agent=agent,
132
135
  ).all()
133
136
  hits = await sync_to_async(list)(hits) # type: ignore[call-arg]
134
137
 
khoj/utils/helpers.py CHANGED
@@ -325,7 +325,15 @@ command_descriptions = {
325
325
  ConversationCommand.Image: "Generate images by describing your imagination in words.",
326
326
  ConversationCommand.Automation: "Automatically run your query at a specified time or interval.",
327
327
  ConversationCommand.Help: "Get help with how to use or setup Khoj from the documentation",
328
- ConversationCommand.Summarize: "Create an appropriate summary using provided documents.",
328
+ ConversationCommand.Summarize: "Get help with a question pertaining to an entire document.",
329
+ }
330
+
331
+ command_descriptions_for_agent = {
332
+ ConversationCommand.General: "Respond without any outside information or personal knowledge.",
333
+ ConversationCommand.Notes: "Search through the knowledge base. Required if the agent expects context from the knowledge base.",
334
+ ConversationCommand.Online: "Search for the latest, up-to-date information from the internet.",
335
+ ConversationCommand.Webpage: "Scrape specific web pages for information.",
336
+ ConversationCommand.Summarize: "Retrieve an answer that depends on the entire document or a large text. Knowledge base must be a single document.",
329
337
  }
330
338
 
331
339
  tool_descriptions_for_llm = {
@@ -334,7 +342,7 @@ tool_descriptions_for_llm = {
334
342
  ConversationCommand.Notes: "To search the user's personal knowledge base. Especially helpful if the question expects context from the user's notes or documents.",
335
343
  ConversationCommand.Online: "To search for the latest, up-to-date information from the internet. Note: **Questions about Khoj should always use this data source**",
336
344
  ConversationCommand.Webpage: "To use if the user has directly provided the webpage urls or you are certain of the webpage urls to read.",
337
- ConversationCommand.Summarize: "To create a summary of the document provided by the user.",
345
+ ConversationCommand.Summarize: "To retrieve an answer that depends on the entire document or a large text.",
338
346
  }
339
347
 
340
348
  mode_descriptions_for_llm = {
@@ -343,6 +351,11 @@ mode_descriptions_for_llm = {
343
351
  ConversationCommand.Text: "Use this if the other response modes don't seem to fit the query.",
344
352
  }
345
353
 
354
+ mode_descriptions_for_agent = {
355
+ ConversationCommand.Image: "Allow the agent to generate images.",
356
+ ConversationCommand.Text: "Allow the agent to generate text.",
357
+ }
358
+
346
359
 
347
360
  class ImageIntentType(Enum):
348
361
  """
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: khoj
3
- Version: 1.24.2.dev2
3
+ Version: 1.24.2.dev16
4
4
  Summary: Your Second Brain
5
5
  Project-URL: Homepage, https://khoj.dev
6
6
  Project-URL: Documentation, https://docs.khoj.dev
@@ -169,10 +169,3 @@ Made with [contrib.rocks](https://contrib.rocks).
169
169
  ### Interested in Contributing?
170
170
 
171
171
  We are always looking for contributors to help us build new features, improve the project documentation, or fix bugs. If you're interested, please see our [Contributing Guidelines](https://docs.khoj.dev/contributing/development) and check out our [Contributors Project Board](https://github.com/orgs/khoj-ai/projects/4).
172
-
173
- ## [Sponsors](https://github.com/sponsors/khoj-ai)
174
- Shout out to our brilliant sponsors! 🌈
175
-
176
- <a href="http://github.com/beekeeb">
177
- <img src="https://raw.githubusercontent.com/beekeeb/piantor/main/docs/beekeeb.png" width=250/>
178
- </a>