khoj 1.27.2.dev12__py3-none-any.whl → 1.28.1__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 +1 -1
  2. khoj/database/adapters/__init__.py +55 -12
  3. khoj/interface/compiled/404/index.html +1 -1
  4. khoj/interface/compiled/_next/static/chunks/1034-da58b679fcbb79c1.js +1 -0
  5. khoj/interface/compiled/_next/static/chunks/1467-b331e469fe411347.js +1 -0
  6. khoj/interface/compiled/_next/static/chunks/1603-c1568f45947e9f2c.js +1 -0
  7. khoj/interface/compiled/_next/static/chunks/3423-ff7402ae1dd66592.js +1 -0
  8. khoj/interface/compiled/_next/static/chunks/8423-e80647edf6c92c27.js +1 -0
  9. khoj/interface/compiled/_next/static/chunks/app/agents/{page-2beaba7c9bb750bd.js → page-fc492762298e975e.js} +1 -1
  10. khoj/interface/compiled/_next/static/chunks/app/automations/{page-9b5c77e0b0dd772c.js → page-416ee13a00575c39.js} +1 -1
  11. khoj/interface/compiled/_next/static/chunks/app/chat/page-c70f5b0c722d7627.js +1 -0
  12. khoj/interface/compiled/_next/static/chunks/app/factchecker/page-1541d90140794f63.js +1 -0
  13. khoj/interface/compiled/_next/static/chunks/app/{page-8f22b790e50dd722.js → page-b269e444fc067759.js} +1 -1
  14. khoj/interface/compiled/_next/static/chunks/app/search/{page-ab2995529ece3140.js → page-7d431ce8e565c7c3.js} +1 -1
  15. khoj/interface/compiled/_next/static/chunks/app/settings/{page-7946cabb9c54e22d.js → page-95f56e53f48f0289.js} +1 -1
  16. khoj/interface/compiled/_next/static/chunks/app/share/chat/{page-6a01e07fb244c10c.js → page-4eba6154f7bb9771.js} +1 -1
  17. khoj/interface/compiled/_next/static/chunks/{webpack-17202cfae517c5de.js → webpack-33a82ccca02cd2b8.js} +1 -1
  18. khoj/interface/compiled/_next/static/css/2196fae09c2f906e.css +1 -0
  19. khoj/interface/compiled/_next/static/css/6bde1f2045622ef7.css +1 -0
  20. khoj/interface/compiled/_next/static/css/a795ee88875f4853.css +25 -0
  21. khoj/interface/compiled/_next/static/css/ebef43da1c0651d5.css +1 -0
  22. khoj/interface/compiled/agents/index.html +1 -1
  23. khoj/interface/compiled/agents/index.txt +2 -2
  24. khoj/interface/compiled/automations/index.html +1 -1
  25. khoj/interface/compiled/automations/index.txt +2 -2
  26. khoj/interface/compiled/chat/index.html +1 -1
  27. khoj/interface/compiled/chat/index.txt +2 -2
  28. khoj/interface/compiled/factchecker/index.html +1 -1
  29. khoj/interface/compiled/factchecker/index.txt +2 -2
  30. khoj/interface/compiled/index.html +1 -1
  31. khoj/interface/compiled/index.txt +2 -2
  32. khoj/interface/compiled/search/index.html +1 -1
  33. khoj/interface/compiled/search/index.txt +2 -2
  34. khoj/interface/compiled/settings/index.html +1 -1
  35. khoj/interface/compiled/settings/index.txt +2 -2
  36. khoj/interface/compiled/share/chat/index.html +1 -1
  37. khoj/interface/compiled/share/chat/index.txt +2 -2
  38. khoj/processor/conversation/anthropic/anthropic_chat.py +19 -10
  39. khoj/processor/conversation/anthropic/utils.py +37 -6
  40. khoj/processor/conversation/google/gemini_chat.py +23 -13
  41. khoj/processor/conversation/google/utils.py +34 -10
  42. khoj/processor/conversation/offline/chat_model.py +48 -16
  43. khoj/processor/conversation/openai/gpt.py +25 -10
  44. khoj/processor/conversation/openai/utils.py +50 -9
  45. khoj/processor/conversation/prompts.py +156 -65
  46. khoj/processor/conversation/utils.py +306 -6
  47. khoj/processor/embeddings.py +4 -4
  48. khoj/processor/image/generate.py +2 -0
  49. khoj/processor/tools/online_search.py +27 -12
  50. khoj/processor/tools/run_code.py +144 -0
  51. khoj/routers/api.py +11 -6
  52. khoj/routers/api_chat.py +213 -111
  53. khoj/routers/helpers.py +171 -60
  54. khoj/routers/research.py +320 -0
  55. khoj/search_filter/date_filter.py +1 -3
  56. khoj/search_filter/file_filter.py +1 -2
  57. khoj/search_type/text_search.py +3 -3
  58. khoj/utils/helpers.py +25 -3
  59. khoj/utils/yaml.py +4 -0
  60. {khoj-1.27.2.dev12.dist-info → khoj-1.28.1.dist-info}/METADATA +3 -2
  61. {khoj-1.27.2.dev12.dist-info → khoj-1.28.1.dist-info}/RECORD +68 -65
  62. khoj/interface/compiled/_next/static/chunks/1603-b9d95833e0e025e8.js +0 -1
  63. khoj/interface/compiled/_next/static/chunks/2697-61fcba89fd87eab4.js +0 -1
  64. khoj/interface/compiled/_next/static/chunks/3423-8e9c420574a9fbe3.js +0 -1
  65. khoj/interface/compiled/_next/static/chunks/9479-4b443fdcc99141c9.js +0 -1
  66. khoj/interface/compiled/_next/static/chunks/app/chat/page-151232d8417a1ea1.js +0 -1
  67. khoj/interface/compiled/_next/static/chunks/app/factchecker/page-798904432c2417c4.js +0 -1
  68. khoj/interface/compiled/_next/static/css/2272c73fc7a3b571.css +0 -1
  69. khoj/interface/compiled/_next/static/css/553f9cdcc7a2bcd6.css +0 -1
  70. khoj/interface/compiled/_next/static/css/76d55eb435962b19.css +0 -25
  71. khoj/interface/compiled/_next/static/css/b70402177a7c3207.css +0 -1
  72. /khoj/interface/compiled/_next/static/{kul3DNllWR6eaUDc4X0eU → JcTomiF3o0dIo4RxHR9Vu}/_buildManifest.js +0 -0
  73. /khoj/interface/compiled/_next/static/{kul3DNllWR6eaUDc4X0eU → JcTomiF3o0dIo4RxHR9Vu}/_ssgManifest.js +0 -0
  74. /khoj/interface/compiled/_next/static/chunks/{1970-1d6d0c1b00b4f343.js → 1970-90dd510762d820ba.js} +0 -0
  75. /khoj/interface/compiled/_next/static/chunks/{9417-759984ad62caa3dc.js → 9417-951f46451a8dd6d7.js} +0 -0
  76. {khoj-1.27.2.dev12.dist-info → khoj-1.28.1.dist-info}/WHEEL +0 -0
  77. {khoj-1.27.2.dev12.dist-info → khoj-1.28.1.dist-info}/entry_points.txt +0 -0
  78. {khoj-1.27.2.dev12.dist-info → khoj-1.28.1.dist-info}/licenses/LICENSE +0 -0
khoj/routers/api.py CHANGED
@@ -44,6 +44,7 @@ from khoj.processor.conversation.offline.chat_model import extract_questions_off
44
44
  from khoj.processor.conversation.offline.whisper import transcribe_audio_offline
45
45
  from khoj.processor.conversation.openai.gpt import extract_questions
46
46
  from khoj.processor.conversation.openai.whisper import transcribe_audio
47
+ from khoj.processor.conversation.utils import defilter_query
47
48
  from khoj.routers.helpers import (
48
49
  ApiUserRateLimiter,
49
50
  ChatEvent,
@@ -167,8 +168,8 @@ async def execute_search(
167
168
  search_futures += [
168
169
  executor.submit(
169
170
  text_search.query,
170
- user,
171
171
  user_query,
172
+ user,
172
173
  t,
173
174
  question_embedding=encoded_asymmetric_query,
174
175
  max_distance=max_distance,
@@ -350,11 +351,12 @@ async def extract_references_and_questions(
350
351
  send_status_func: Optional[Callable] = None,
351
352
  query_images: Optional[List[str]] = None,
352
353
  agent: Agent = None,
354
+ tracer: dict = {},
353
355
  ):
354
356
  user = request.user.object if request.user.is_authenticated else None
355
357
 
356
358
  # Initialize Variables
357
- compiled_references: List[Any] = []
359
+ compiled_references: List[dict[str, str]] = []
358
360
  inferred_queries: List[str] = []
359
361
 
360
362
  agent_has_entries = False
@@ -383,9 +385,7 @@ async def extract_references_and_questions(
383
385
  return
384
386
 
385
387
  # Extract filter terms from user message
386
- defiltered_query = q
387
- for filter in [DateFilter(), WordFilter(), FileFilter()]:
388
- defiltered_query = filter.defilter(defiltered_query)
388
+ defiltered_query = defilter_query(q)
389
389
  filters_in_query = q.replace(defiltered_query, "").strip()
390
390
  conversation = await sync_to_async(ConversationAdapters.get_conversation_by_id)(conversation_id)
391
391
 
@@ -425,6 +425,7 @@ async def extract_references_and_questions(
425
425
  user=user,
426
426
  max_prompt_size=conversation_config.max_prompt_size,
427
427
  personality_context=personality_context,
428
+ tracer=tracer,
428
429
  )
429
430
  elif conversation_config.model_type == ChatModelOptions.ModelType.OPENAI:
430
431
  openai_chat_config = conversation_config.openai_config
@@ -442,6 +443,7 @@ async def extract_references_and_questions(
442
443
  query_images=query_images,
443
444
  vision_enabled=vision_enabled,
444
445
  personality_context=personality_context,
446
+ tracer=tracer,
445
447
  )
446
448
  elif conversation_config.model_type == ChatModelOptions.ModelType.ANTHROPIC:
447
449
  api_key = conversation_config.openai_config.api_key
@@ -456,6 +458,7 @@ async def extract_references_and_questions(
456
458
  user=user,
457
459
  vision_enabled=vision_enabled,
458
460
  personality_context=personality_context,
461
+ tracer=tracer,
459
462
  )
460
463
  elif conversation_config.model_type == ChatModelOptions.ModelType.GOOGLE:
461
464
  api_key = conversation_config.openai_config.api_key
@@ -471,6 +474,7 @@ async def extract_references_and_questions(
471
474
  user=user,
472
475
  vision_enabled=vision_enabled,
473
476
  personality_context=personality_context,
477
+ tracer=tracer,
474
478
  )
475
479
 
476
480
  # Collate search results as context for GPT
@@ -497,7 +501,8 @@ async def extract_references_and_questions(
497
501
  )
498
502
  search_results = text_search.deduplicated_search_responses(search_results)
499
503
  compiled_references = [
500
- {"compiled": item.additional["compiled"], "file": item.additional["file"]} for item in search_results
504
+ {"query": q, "compiled": item.additional["compiled"], "file": item.additional["file"]}
505
+ for q, item in zip(inferred_queries, search_results)
501
506
  ]
502
507
 
503
508
  yield compiled_references, inferred_queries, defiltered_query
khoj/routers/api_chat.py CHANGED
@@ -3,9 +3,10 @@ import base64
3
3
  import json
4
4
  import logging
5
5
  import time
6
+ import uuid
6
7
  from datetime import datetime
7
8
  from functools import partial
8
- from typing import Dict, Optional
9
+ from typing import Any, Dict, List, Optional
9
10
  from urllib.parse import unquote
10
11
 
11
12
  from asgiref.sync import sync_to_async
@@ -24,11 +25,13 @@ from khoj.database.adapters import (
24
25
  )
25
26
  from khoj.database.models import Agent, KhojUser
26
27
  from khoj.processor.conversation.prompts import help_message, no_entries_found
27
- from khoj.processor.conversation.utils import save_to_conversation_log
28
+ from khoj.processor.conversation.utils import defilter_query, save_to_conversation_log
28
29
  from khoj.processor.image.generate import text_to_image
29
30
  from khoj.processor.speech.text_to_speech import generate_text_to_speech
30
31
  from khoj.processor.tools.online_search import read_webpages, search_online
32
+ from khoj.processor.tools.run_code import run_code
31
33
  from khoj.routers.api import extract_references_and_questions
34
+ from khoj.routers.email import send_query_feedback
32
35
  from khoj.routers.helpers import (
33
36
  ApiImageRateLimiter,
34
37
  ApiUserRateLimiter,
@@ -36,13 +39,16 @@ from khoj.routers.helpers import (
36
39
  ChatRequestBody,
37
40
  CommonQueryParams,
38
41
  ConversationCommandRateLimiter,
42
+ DeleteMessageRequestBody,
43
+ FeedbackData,
39
44
  agenerate_chat_response,
40
45
  aget_relevant_information_sources,
41
46
  aget_relevant_output_modes,
42
47
  construct_automation_created_message,
43
48
  create_automation,
44
- extract_relevant_summary,
49
+ extract_relevant_info,
45
50
  generate_excalidraw_diagram,
51
+ generate_summary_from_files,
46
52
  get_conversation_command,
47
53
  is_query_empty,
48
54
  is_ready_to_chat,
@@ -50,6 +56,10 @@ from khoj.routers.helpers import (
50
56
  update_telemetry_state,
51
57
  validate_conversation_config,
52
58
  )
59
+ from khoj.routers.research import (
60
+ InformationCollectionIteration,
61
+ execute_information_collection,
62
+ )
53
63
  from khoj.routers.storage import upload_image_to_bucket
54
64
  from khoj.utils import state
55
65
  from khoj.utils.helpers import (
@@ -67,16 +77,12 @@ from khoj.utils.rawconfig import FileFilterRequest, FilesFilterRequest, Location
67
77
  # Initialize Router
68
78
  logger = logging.getLogger(__name__)
69
79
  conversation_command_rate_limiter = ConversationCommandRateLimiter(
70
- trial_rate_limit=100, subscribed_rate_limit=6000, slug="command"
80
+ trial_rate_limit=20, subscribed_rate_limit=75, slug="command"
71
81
  )
72
82
 
73
83
 
74
84
  api_chat = APIRouter()
75
85
 
76
- from pydantic import BaseModel
77
-
78
- from khoj.routers.email import send_query_feedback
79
-
80
86
 
81
87
  @api_chat.get("/conversation/file-filters/{conversation_id}", response_class=Response)
82
88
  @requires(["authenticated"])
@@ -138,12 +144,6 @@ def remove_file_filter(request: Request, filter: FileFilterRequest) -> Response:
138
144
  return Response(content=json.dumps(file_filters), media_type="application/json", status_code=200)
139
145
 
140
146
 
141
- class FeedbackData(BaseModel):
142
- uquery: str
143
- kquery: str
144
- sentiment: str
145
-
146
-
147
147
  @api_chat.post("/feedback")
148
148
  @requires(["authenticated"])
149
149
  async def sendfeedback(request: Request, data: FeedbackData):
@@ -158,10 +158,10 @@ async def text_to_speech(
158
158
  common: CommonQueryParams,
159
159
  text: str,
160
160
  rate_limiter_per_minute=Depends(
161
- ApiUserRateLimiter(requests=20, subscribed_requests=20, window=60, slug="chat_minute")
161
+ ApiUserRateLimiter(requests=30, subscribed_requests=30, window=60, slug="chat_minute")
162
162
  ),
163
163
  rate_limiter_per_day=Depends(
164
- ApiUserRateLimiter(requests=50, subscribed_requests=300, window=60 * 60 * 24, slug="chat_day")
164
+ ApiUserRateLimiter(requests=100, subscribed_requests=600, window=60 * 60 * 24, slug="chat_day")
165
165
  ),
166
166
  ) -> Response:
167
167
  voice_model = await ConversationAdapters.aget_voice_model_config(request.user.object)
@@ -526,6 +526,19 @@ async def set_conversation_title(
526
526
  )
527
527
 
528
528
 
529
+ @api_chat.delete("/conversation/message", response_class=Response)
530
+ @requires(["authenticated"])
531
+ def delete_message(request: Request, delete_request: DeleteMessageRequestBody) -> Response:
532
+ user = request.user.object
533
+ success = ConversationAdapters.delete_message_by_turn_id(
534
+ user, delete_request.conversation_id, delete_request.turn_id
535
+ )
536
+ if success:
537
+ return Response(content=json.dumps({"status": "ok"}), media_type="application/json", status_code=200)
538
+ else:
539
+ return Response(content=json.dumps({"status": "error", "message": "Message not found"}), status_code=404)
540
+
541
+
529
542
  @api_chat.post("")
530
543
  @requires(["authenticated"])
531
544
  async def chat(
@@ -533,10 +546,10 @@ async def chat(
533
546
  common: CommonQueryParams,
534
547
  body: ChatRequestBody,
535
548
  rate_limiter_per_minute=Depends(
536
- ApiUserRateLimiter(requests=60, subscribed_requests=200, window=60, slug="chat_minute")
549
+ ApiUserRateLimiter(requests=20, subscribed_requests=20, window=60, slug="chat_minute")
537
550
  ),
538
551
  rate_limiter_per_day=Depends(
539
- ApiUserRateLimiter(requests=600, subscribed_requests=6000, window=60 * 60 * 24, slug="chat_day")
552
+ ApiUserRateLimiter(requests=100, subscribed_requests=600, window=60 * 60 * 24, slug="chat_day")
540
553
  ),
541
554
  image_rate_limiter=Depends(ApiImageRateLimiter(max_images=10, max_combined_size_mb=20)),
542
555
  ):
@@ -547,6 +560,7 @@ async def chat(
547
560
  stream = body.stream
548
561
  title = body.title
549
562
  conversation_id = body.conversation_id
563
+ turn_id = str(body.turn_id or uuid.uuid4())
550
564
  city = body.city
551
565
  region = body.region
552
566
  country = body.country or get_country_name_from_timezone(body.timezone)
@@ -562,8 +576,16 @@ async def chat(
562
576
  user: KhojUser = request.user.object
563
577
  event_delimiter = "␃🔚␗"
564
578
  q = unquote(q)
579
+ train_of_thought = []
565
580
  nonlocal conversation_id
566
581
 
582
+ tracer: dict = {
583
+ "mid": turn_id,
584
+ "cid": conversation_id,
585
+ "uid": user.id,
586
+ "khoj_version": state.khoj_version,
587
+ }
588
+
567
589
  uploaded_images: list[str] = []
568
590
  if images:
569
591
  for image in images:
@@ -576,7 +598,7 @@ async def chat(
576
598
  uploaded_images.append(uploaded_image)
577
599
 
578
600
  async def send_event(event_type: ChatEvent, data: str | dict):
579
- nonlocal connection_alive, ttft
601
+ nonlocal connection_alive, ttft, train_of_thought
580
602
  if not connection_alive or await request.is_disconnected():
581
603
  connection_alive = False
582
604
  logger.warning(f"User {user} disconnected from {common.client} client")
@@ -584,11 +606,14 @@ async def chat(
584
606
  try:
585
607
  if event_type == ChatEvent.END_LLM_RESPONSE:
586
608
  collect_telemetry()
587
- if event_type == ChatEvent.START_LLM_RESPONSE:
609
+ elif event_type == ChatEvent.START_LLM_RESPONSE:
588
610
  ttft = time.perf_counter() - start_time
611
+ elif event_type == ChatEvent.STATUS:
612
+ train_of_thought.append({"type": event_type.value, "data": data})
613
+
589
614
  if event_type == ChatEvent.MESSAGE:
590
615
  yield data
591
- elif event_type == ChatEvent.REFERENCES or stream:
616
+ elif event_type == ChatEvent.REFERENCES or ChatEvent.METADATA or stream:
592
617
  yield json.dumps({"type": event_type.value, "data": data}, ensure_ascii=False)
593
618
  except asyncio.CancelledError as e:
594
619
  connection_alive = False
@@ -632,6 +657,11 @@ async def chat(
632
657
  metadata=chat_metadata,
633
658
  )
634
659
 
660
+ if is_query_empty(q):
661
+ async for result in send_llm_response("Please ask your query to get started."):
662
+ yield result
663
+ return
664
+
635
665
  conversation_commands = [get_conversation_command(query=q, any_references=True)]
636
666
 
637
667
  conversation = await ConversationAdapters.aget_conversation_by_user(
@@ -647,6 +677,9 @@ async def chat(
647
677
  return
648
678
  conversation_id = conversation.id
649
679
 
680
+ async for event in send_event(ChatEvent.METADATA, {"conversationId": str(conversation_id), "turnId": turn_id}):
681
+ yield event
682
+
650
683
  agent: Agent | None = None
651
684
  default_agent = await AgentAdapters.aget_default_agent()
652
685
  if conversation.agent and conversation.agent != default_agent:
@@ -658,22 +691,23 @@ async def chat(
658
691
  agent = default_agent
659
692
 
660
693
  await is_ready_to_chat(user)
661
-
662
694
  user_name = await aget_user_name(user)
663
695
  location = None
664
696
  if city or region or country or country_code:
665
697
  location = LocationData(city=city, region=region, country=country, country_code=country_code)
666
698
 
667
- if is_query_empty(q):
668
- async for result in send_llm_response("Please ask your query to get started."):
669
- yield result
670
- return
671
-
672
699
  user_message_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
673
700
 
674
701
  meta_log = conversation.conversation_log
675
702
  is_automated_task = conversation_commands == [ConversationCommand.AutomatedTask]
676
703
 
704
+ researched_results = ""
705
+ online_results: Dict = dict()
706
+ code_results: Dict = dict()
707
+ ## Extract Document References
708
+ compiled_references: List[Any] = []
709
+ inferred_queries: List[Any] = []
710
+
677
711
  if conversation_commands == [ConversationCommand.Default] or is_automated_task:
678
712
  conversation_commands = await aget_relevant_information_sources(
679
713
  q,
@@ -682,14 +716,22 @@ async def chat(
682
716
  user=user,
683
717
  query_images=uploaded_images,
684
718
  agent=agent,
719
+ tracer=tracer,
685
720
  )
721
+
722
+ # If we're doing research, we don't want to do anything else
723
+ if ConversationCommand.Research in conversation_commands:
724
+ conversation_commands = [ConversationCommand.Research]
725
+
686
726
  conversation_commands_str = ", ".join([cmd.value for cmd in conversation_commands])
687
727
  async for result in send_event(
688
728
  ChatEvent.STATUS, f"**Chose Data Sources to Search:** {conversation_commands_str}"
689
729
  ):
690
730
  yield result
691
731
 
692
- mode = await aget_relevant_output_modes(q, meta_log, is_automated_task, user, uploaded_images, agent)
732
+ mode = await aget_relevant_output_modes(
733
+ q, meta_log, is_automated_task, user, uploaded_images, agent, tracer=tracer
734
+ )
693
735
  async for result in send_event(ChatEvent.STATUS, f"**Decided Response Mode:** {mode.value}"):
694
736
  yield result
695
737
  if mode not in conversation_commands:
@@ -699,6 +741,44 @@ async def chat(
699
741
  await conversation_command_rate_limiter.update_and_check_if_valid(request, cmd)
700
742
  q = q.replace(f"/{cmd.value}", "").strip()
701
743
 
744
+ defiltered_query = defilter_query(q)
745
+
746
+ if conversation_commands == [ConversationCommand.Research]:
747
+ async for research_result in execute_information_collection(
748
+ request=request,
749
+ user=user,
750
+ query=defiltered_query,
751
+ conversation_id=conversation_id,
752
+ conversation_history=meta_log,
753
+ query_images=uploaded_images,
754
+ agent=agent,
755
+ send_status_func=partial(send_event, ChatEvent.STATUS),
756
+ user_name=user_name,
757
+ location=location,
758
+ file_filters=conversation.file_filters if conversation else [],
759
+ tracer=tracer,
760
+ ):
761
+ if isinstance(research_result, InformationCollectionIteration):
762
+ if research_result.summarizedResult:
763
+ if research_result.onlineContext:
764
+ online_results.update(research_result.onlineContext)
765
+ if research_result.codeContext:
766
+ code_results.update(research_result.codeContext)
767
+ if research_result.context:
768
+ compiled_references.extend(research_result.context)
769
+
770
+ researched_results += research_result.summarizedResult
771
+
772
+ else:
773
+ yield research_result
774
+
775
+ # researched_results = await extract_relevant_info(q, researched_results, agent)
776
+ logger.info(f"Researched Results: {researched_results}")
777
+
778
+ for cmd in conversation_commands:
779
+ await conversation_command_rate_limiter.update_and_check_if_valid(request, cmd)
780
+ q = q.replace(f"/{cmd.value}", "").strip()
781
+
702
782
  used_slash_summarize = conversation_commands == [ConversationCommand.Summarize]
703
783
  file_filters = conversation.file_filters if conversation else []
704
784
  # Skip trying to summarize if
@@ -723,47 +803,24 @@ async def chat(
723
803
  async for result in send_llm_response(response_log):
724
804
  yield result
725
805
  else:
726
- try:
727
- file_object = None
728
- if await EntryAdapters.aagent_has_entries(agent):
729
- file_names = await EntryAdapters.aget_agent_entry_filepaths(agent)
730
- if len(file_names) > 0:
731
- file_object = await FileObjectAdapters.async_get_file_objects_by_name(
732
- None, file_names[0], agent
733
- )
734
-
735
- if len(file_filters) > 0:
736
- file_object = await FileObjectAdapters.async_get_file_objects_by_name(user, file_filters[0])
737
-
738
- if len(file_object) == 0:
739
- response_log = "Sorry, I couldn't find the full text of this file. Please re-upload the document and try again."
740
- async for result in send_llm_response(response_log):
741
- yield result
742
- return
743
- contextual_data = " ".join([file.raw_text for file in file_object])
744
- if not q:
745
- q = "Create a general summary of the file"
746
- async for result in send_event(
747
- ChatEvent.STATUS, f"**Constructing Summary Using:** {file_object[0].file_name}"
748
- ):
749
- yield result
750
-
751
- response = await extract_relevant_summary(
752
- q,
753
- contextual_data,
754
- conversation_history=meta_log,
755
- query_images=uploaded_images,
756
- user=user,
757
- agent=agent,
758
- )
759
- response_log = str(response)
760
- async for result in send_llm_response(response_log):
761
- yield result
762
- except Exception as e:
763
- response_log = "Error summarizing file. Please try again, or contact support."
764
- logger.error(f"Error summarizing file for {user.email}: {e}", exc_info=True)
765
- async for result in send_llm_response(response_log):
766
- yield result
806
+ async for response in generate_summary_from_files(
807
+ q=q,
808
+ user=user,
809
+ file_filters=file_filters,
810
+ meta_log=meta_log,
811
+ query_images=uploaded_images,
812
+ agent=agent,
813
+ send_status_func=partial(send_event, ChatEvent.STATUS),
814
+ tracer=tracer,
815
+ ):
816
+ if isinstance(response, dict) and ChatEvent.STATUS in response:
817
+ yield response[ChatEvent.STATUS]
818
+ else:
819
+ if isinstance(response, str):
820
+ response_log = response
821
+ async for result in send_llm_response(response):
822
+ yield result
823
+
767
824
  await sync_to_async(save_to_conversation_log)(
768
825
  q,
769
826
  response_log,
@@ -774,6 +831,8 @@ async def chat(
774
831
  client_application=request.user.client_app,
775
832
  conversation_id=conversation_id,
776
833
  query_images=uploaded_images,
834
+ tracer=tracer,
835
+ train_of_thought=train_of_thought,
777
836
  )
778
837
  return
779
838
 
@@ -782,7 +841,7 @@ async def chat(
782
841
  if not q:
783
842
  conversation_config = await ConversationAdapters.aget_user_conversation_config(user)
784
843
  if conversation_config == None:
785
- conversation_config = await ConversationAdapters.aget_default_conversation_config()
844
+ conversation_config = await ConversationAdapters.aget_default_conversation_config(user)
786
845
  model_type = conversation_config.model_type
787
846
  formatted_help = help_message.format(model=model_type, version=state.khoj_version, device=get_device())
788
847
  async for result in send_llm_response(formatted_help):
@@ -795,7 +854,7 @@ async def chat(
795
854
  if ConversationCommand.Automation in conversation_commands:
796
855
  try:
797
856
  automation, crontime, query_to_run, subject = await create_automation(
798
- q, timezone, user, request.url, meta_log
857
+ q, timezone, user, request.url, meta_log, tracer=tracer
799
858
  )
800
859
  except Exception as e:
801
860
  logger.error(f"Error scheduling task {q} for {user.email}: {e}")
@@ -817,6 +876,8 @@ async def chat(
817
876
  inferred_queries=[query_to_run],
818
877
  automation_id=automation.id,
819
878
  query_images=uploaded_images,
879
+ tracer=tracer,
880
+ train_of_thought=train_of_thought,
820
881
  )
821
882
  async for result in send_llm_response(llm_response):
822
883
  yield result
@@ -824,48 +885,49 @@ async def chat(
824
885
 
825
886
  # Gather Context
826
887
  ## Extract Document References
827
- compiled_references, inferred_queries, defiltered_query = [], [], q
828
- try:
829
- async for result in extract_references_and_questions(
830
- request,
831
- meta_log,
832
- q,
833
- (n or 7),
834
- d,
835
- conversation_id,
836
- conversation_commands,
837
- location,
838
- partial(send_event, ChatEvent.STATUS),
839
- query_images=uploaded_images,
840
- agent=agent,
841
- ):
842
- if isinstance(result, dict) and ChatEvent.STATUS in result:
843
- yield result[ChatEvent.STATUS]
844
- else:
845
- compiled_references.extend(result[0])
846
- inferred_queries.extend(result[1])
847
- defiltered_query = result[2]
848
- except Exception as e:
849
- error_message = f"Error searching knowledge base: {e}. Attempting to respond without document references."
850
- logger.error(error_message, exc_info=True)
851
- async for result in send_event(
852
- ChatEvent.STATUS, "Document search failed. I'll try respond without document references"
853
- ):
854
- yield result
855
-
856
- if not is_none_or_empty(compiled_references):
857
- headings = "\n- " + "\n- ".join(set([c.get("compiled", c).split("\n")[0] for c in compiled_references]))
858
- # Strip only leading # from headings
859
- headings = headings.replace("#", "")
860
- async for result in send_event(ChatEvent.STATUS, f"**Found Relevant Notes**: {headings}"):
861
- yield result
888
+ if not ConversationCommand.Research in conversation_commands:
889
+ try:
890
+ async for result in extract_references_and_questions(
891
+ request,
892
+ meta_log,
893
+ q,
894
+ (n or 7),
895
+ d,
896
+ conversation_id,
897
+ conversation_commands,
898
+ location,
899
+ partial(send_event, ChatEvent.STATUS),
900
+ query_images=uploaded_images,
901
+ agent=agent,
902
+ tracer=tracer,
903
+ ):
904
+ if isinstance(result, dict) and ChatEvent.STATUS in result:
905
+ yield result[ChatEvent.STATUS]
906
+ else:
907
+ compiled_references.extend(result[0])
908
+ inferred_queries.extend(result[1])
909
+ defiltered_query = result[2]
910
+ except Exception as e:
911
+ error_message = (
912
+ f"Error searching knowledge base: {e}. Attempting to respond without document references."
913
+ )
914
+ logger.error(error_message, exc_info=True)
915
+ async for result in send_event(
916
+ ChatEvent.STATUS, "Document search failed. I'll try respond without document references"
917
+ ):
918
+ yield result
862
919
 
863
- online_results: Dict = dict()
920
+ if not is_none_or_empty(compiled_references):
921
+ headings = "\n- " + "\n- ".join(set([c.get("compiled", c).split("\n")[0] for c in compiled_references]))
922
+ # Strip only leading # from headings
923
+ headings = headings.replace("#", "")
924
+ async for result in send_event(ChatEvent.STATUS, f"**Found Relevant Notes**: {headings}"):
925
+ yield result
864
926
 
865
- if conversation_commands == [ConversationCommand.Notes] and not await EntryAdapters.auser_has_entries(user):
866
- async for result in send_llm_response(f"{no_entries_found.format()}"):
867
- yield result
868
- return
927
+ if conversation_commands == [ConversationCommand.Notes] and not await EntryAdapters.auser_has_entries(user):
928
+ async for result in send_llm_response(f"{no_entries_found.format()}"):
929
+ yield result
930
+ return
869
931
 
870
932
  if ConversationCommand.Notes in conversation_commands and is_none_or_empty(compiled_references):
871
933
  conversation_commands.remove(ConversationCommand.Notes)
@@ -882,6 +944,7 @@ async def chat(
882
944
  custom_filters,
883
945
  query_images=uploaded_images,
884
946
  agent=agent,
947
+ tracer=tracer,
885
948
  ):
886
949
  if isinstance(result, dict) and ChatEvent.STATUS in result:
887
950
  yield result[ChatEvent.STATUS]
@@ -906,6 +969,7 @@ async def chat(
906
969
  partial(send_event, ChatEvent.STATUS),
907
970
  query_images=uploaded_images,
908
971
  agent=agent,
972
+ tracer=tracer,
909
973
  ):
910
974
  if isinstance(result, dict) and ChatEvent.STATUS in result:
911
975
  yield result[ChatEvent.STATUS]
@@ -932,6 +996,33 @@ async def chat(
932
996
  ):
933
997
  yield result
934
998
 
999
+ ## Gather Code Results
1000
+ if ConversationCommand.Code in conversation_commands:
1001
+ try:
1002
+ context = f"# Iteration 1:\n#---\nNotes:\n{compiled_references}\n\nOnline Results:{online_results}"
1003
+ async for result in run_code(
1004
+ defiltered_query,
1005
+ meta_log,
1006
+ context,
1007
+ location,
1008
+ user,
1009
+ partial(send_event, ChatEvent.STATUS),
1010
+ query_images=uploaded_images,
1011
+ agent=agent,
1012
+ tracer=tracer,
1013
+ ):
1014
+ if isinstance(result, dict) and ChatEvent.STATUS in result:
1015
+ yield result[ChatEvent.STATUS]
1016
+ else:
1017
+ code_results = result
1018
+ async for result in send_event(ChatEvent.STATUS, f"**Ran code snippets**: {len(code_results)}"):
1019
+ yield result
1020
+ except ValueError as e:
1021
+ logger.warning(
1022
+ f"Failed to use code tool: {e}. Attempting to respond without code results",
1023
+ exc_info=True,
1024
+ )
1025
+
935
1026
  ## Send Gathered References
936
1027
  async for result in send_event(
937
1028
  ChatEvent.REFERENCES,
@@ -939,6 +1030,7 @@ async def chat(
939
1030
  "inferredQueries": inferred_queries,
940
1031
  "context": compiled_references,
941
1032
  "onlineContext": online_results,
1033
+ "codeContext": code_results,
942
1034
  },
943
1035
  ):
944
1036
  yield result
@@ -956,6 +1048,7 @@ async def chat(
956
1048
  send_status_func=partial(send_event, ChatEvent.STATUS),
957
1049
  query_images=uploaded_images,
958
1050
  agent=agent,
1051
+ tracer=tracer,
959
1052
  ):
960
1053
  if isinstance(result, dict) and ChatEvent.STATUS in result:
961
1054
  yield result[ChatEvent.STATUS]
@@ -986,6 +1079,8 @@ async def chat(
986
1079
  compiled_references=compiled_references,
987
1080
  online_results=online_results,
988
1081
  query_images=uploaded_images,
1082
+ tracer=tracer,
1083
+ train_of_thought=train_of_thought,
989
1084
  )
990
1085
  content_obj = {
991
1086
  "intentType": intent_type,
@@ -1014,6 +1109,7 @@ async def chat(
1014
1109
  user=user,
1015
1110
  agent=agent,
1016
1111
  send_status_func=partial(send_event, ChatEvent.STATUS),
1112
+ tracer=tracer,
1017
1113
  ):
1018
1114
  if isinstance(result, dict) and ChatEvent.STATUS in result:
1019
1115
  yield result[ChatEvent.STATUS]
@@ -1041,6 +1137,8 @@ async def chat(
1041
1137
  compiled_references=compiled_references,
1042
1138
  online_results=online_results,
1043
1139
  query_images=uploaded_images,
1140
+ tracer=tracer,
1141
+ train_of_thought=train_of_thought,
1044
1142
  )
1045
1143
 
1046
1144
  async for result in send_llm_response(json.dumps(content_obj)):
@@ -1056,6 +1154,7 @@ async def chat(
1056
1154
  conversation,
1057
1155
  compiled_references,
1058
1156
  online_results,
1157
+ code_results,
1059
1158
  inferred_queries,
1060
1159
  conversation_commands,
1061
1160
  user,
@@ -1063,7 +1162,10 @@ async def chat(
1063
1162
  conversation_id,
1064
1163
  location,
1065
1164
  user_name,
1165
+ researched_results,
1066
1166
  uploaded_images,
1167
+ tracer,
1168
+ train_of_thought,
1067
1169
  )
1068
1170
 
1069
1171
  # Send Response