khoj 1.20.4.dev13__py3-none-any.whl → 1.20.5.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 (46) hide show
  1. khoj/configure.py +6 -32
  2. khoj/database/adapters/__init__.py +76 -28
  3. khoj/database/admin.py +15 -3
  4. khoj/database/migrations/0056_searchmodelconfig_cross_encoder_model_config.py +17 -0
  5. khoj/database/migrations/0057_remove_serverchatsettings_default_model_and_more.py +51 -0
  6. khoj/database/models/__init__.py +7 -4
  7. khoj/interface/compiled/404/index.html +1 -1
  8. khoj/interface/compiled/_next/static/chunks/{9178-5a1fa2b9023249af.js → 9178-8c339c3aa3c720a7.js} +1 -1
  9. khoj/interface/compiled/_next/static/chunks/app/chat/page-8c9b92236d4daf4b.js +1 -0
  10. khoj/interface/compiled/_next/static/chunks/app/settings/page-ddcd51147d18c694.js +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 +2 -2
  17. khoj/interface/compiled/factchecker/index.html +1 -1
  18. khoj/interface/compiled/factchecker/index.txt +2 -2
  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 +2 -2
  27. khoj/processor/conversation/anthropic/anthropic_chat.py +6 -2
  28. khoj/processor/conversation/offline/chat_model.py +15 -3
  29. khoj/processor/conversation/openai/gpt.py +6 -3
  30. khoj/processor/conversation/openai/utils.py +1 -1
  31. khoj/processor/conversation/prompts.py +20 -0
  32. khoj/processor/embeddings.py +3 -1
  33. khoj/processor/tools/online_search.py +19 -8
  34. khoj/routers/api.py +3 -1
  35. khoj/routers/api_chat.py +19 -9
  36. khoj/routers/helpers.py +38 -14
  37. khoj/search_filter/file_filter.py +5 -2
  38. {khoj-1.20.4.dev13.dist-info → khoj-1.20.5.dev16.dist-info}/METADATA +1 -1
  39. {khoj-1.20.4.dev13.dist-info → khoj-1.20.5.dev16.dist-info}/RECORD +44 -42
  40. khoj/interface/compiled/_next/static/chunks/app/chat/page-51ab7c4b766ff344.js +0 -1
  41. khoj/interface/compiled/_next/static/chunks/app/settings/page-e83f6fa32691ca64.js +0 -1
  42. /khoj/interface/compiled/_next/static/{EuaXDNeXisAc9H0EHd10x → Ag8X5vKsldGVxyGhiqgw2}/_buildManifest.js +0 -0
  43. /khoj/interface/compiled/_next/static/{EuaXDNeXisAc9H0EHd10x → Ag8X5vKsldGVxyGhiqgw2}/_ssgManifest.js +0 -0
  44. {khoj-1.20.4.dev13.dist-info → khoj-1.20.5.dev16.dist-info}/WHEEL +0 -0
  45. {khoj-1.20.4.dev13.dist-info → khoj-1.20.5.dev16.dist-info}/entry_points.txt +0 -0
  46. {khoj-1.20.4.dev13.dist-info → khoj-1.20.5.dev16.dist-info}/licenses/LICENSE +0 -0
@@ -7,7 +7,7 @@ from typing import Any, Iterator, List, Union
7
7
  from langchain.schema import ChatMessage
8
8
  from llama_cpp import Llama
9
9
 
10
- from khoj.database.models import Agent
10
+ from khoj.database.models import Agent, KhojUser
11
11
  from khoj.processor.conversation import prompts
12
12
  from khoj.processor.conversation.offline.utils import download_model
13
13
  from khoj.processor.conversation.utils import (
@@ -30,7 +30,9 @@ def extract_questions_offline(
30
30
  use_history: bool = True,
31
31
  should_extract_questions: bool = True,
32
32
  location_data: LocationData = None,
33
+ user: KhojUser = None,
33
34
  max_prompt_size: int = None,
35
+ temperature: float = 0.7,
34
36
  ) -> List[str]:
35
37
  """
36
38
  Infer search queries to retrieve relevant notes to answer user query
@@ -45,6 +47,7 @@ def extract_questions_offline(
45
47
  offline_chat_model = loaded_model or download_model(model, max_tokens=max_prompt_size)
46
48
 
47
49
  location = f"{location_data.city}, {location_data.region}, {location_data.country}" if location_data else "Unknown"
50
+ username = prompts.user_name.format(name=user.get_full_name()) if user and user.get_full_name() else ""
48
51
 
49
52
  # Extract Past User Message and Inferred Questions from Conversation Log
50
53
  chat_history = ""
@@ -64,10 +67,12 @@ def extract_questions_offline(
64
67
  chat_history=chat_history,
65
68
  current_date=today.strftime("%Y-%m-%d"),
66
69
  day_of_week=today.strftime("%A"),
70
+ current_month=today.strftime("%Y-%m"),
67
71
  yesterday_date=yesterday,
68
72
  last_year=last_year,
69
73
  this_year=today.year,
70
74
  location=location,
75
+ username=username,
71
76
  )
72
77
 
73
78
  messages = generate_chatml_messages_with_context(
@@ -77,7 +82,11 @@ def extract_questions_offline(
77
82
  state.chat_lock.acquire()
78
83
  try:
79
84
  response = send_message_to_model_offline(
80
- messages, loaded_model=offline_chat_model, model=model, max_prompt_size=max_prompt_size
85
+ messages,
86
+ loaded_model=offline_chat_model,
87
+ model=model,
88
+ max_prompt_size=max_prompt_size,
89
+ temperature=temperature,
81
90
  )
82
91
  finally:
83
92
  state.chat_lock.release()
@@ -229,6 +238,7 @@ def send_message_to_model_offline(
229
238
  messages: List[ChatMessage],
230
239
  loaded_model=None,
231
240
  model="NousResearch/Hermes-2-Pro-Mistral-7B-GGUF",
241
+ temperature: float = 0.2,
232
242
  streaming=False,
233
243
  stop=[],
234
244
  max_prompt_size: int = None,
@@ -236,7 +246,9 @@ def send_message_to_model_offline(
236
246
  assert loaded_model is None or isinstance(loaded_model, Llama), "loaded_model must be of type Llama, if configured"
237
247
  offline_chat_model = loaded_model or download_model(model, max_tokens=max_prompt_size)
238
248
  messages_dict = [{"role": message.role, "content": message.content} for message in messages]
239
- response = offline_chat_model.create_chat_completion(messages_dict, stop=stop, stream=streaming)
249
+ response = offline_chat_model.create_chat_completion(
250
+ messages_dict, stop=stop, stream=streaming, temperature=temperature
251
+ )
240
252
  if streaming:
241
253
  return response
242
254
  else:
@@ -5,7 +5,7 @@ from typing import Dict, Optional
5
5
 
6
6
  from langchain.schema import ChatMessage
7
7
 
8
- from khoj.database.models import Agent
8
+ from khoj.database.models import Agent, KhojUser
9
9
  from khoj.processor.conversation import prompts
10
10
  from khoj.processor.conversation.openai.utils import (
11
11
  chat_completion_with_backoff,
@@ -24,14 +24,15 @@ def extract_questions(
24
24
  conversation_log={},
25
25
  api_key=None,
26
26
  api_base_url=None,
27
- temperature=0,
28
- max_tokens=100,
27
+ temperature=0.7,
29
28
  location_data: LocationData = None,
29
+ user: KhojUser = None,
30
30
  ):
31
31
  """
32
32
  Infer search queries to retrieve relevant notes to answer user query
33
33
  """
34
34
  location = f"{location_data.city}, {location_data.region}, {location_data.country}" if location_data else "Unknown"
35
+ username = prompts.user_name.format(name=user.get_full_name()) if user and user.get_full_name() else ""
35
36
 
36
37
  # Extract Past User Message and Inferred Questions from Conversation Log
37
38
  chat_history = "".join(
@@ -50,6 +51,7 @@ def extract_questions(
50
51
  prompt = prompts.extract_questions.format(
51
52
  current_date=today.strftime("%Y-%m-%d"),
52
53
  day_of_week=today.strftime("%A"),
54
+ current_month=today.strftime("%Y-%m"),
53
55
  last_new_year=last_new_year.strftime("%Y"),
54
56
  last_new_year_date=last_new_year.strftime("%Y-%m-%d"),
55
57
  current_new_year_date=current_new_year.strftime("%Y-%m-%d"),
@@ -59,6 +61,7 @@ def extract_questions(
59
61
  text=text,
60
62
  yesterday_date=(today - timedelta(days=1)).strftime("%Y-%m-%d"),
61
63
  location=location,
64
+ username=username,
62
65
  )
63
66
  messages = [ChatMessage(content=prompt, role="user")]
64
67
 
@@ -36,7 +36,7 @@ def completion_with_backoff(
36
36
  messages, model, temperature=0, openai_api_key=None, api_base_url=None, model_kwargs=None
37
37
  ) -> str:
38
38
  client_key = f"{openai_api_key}--{api_base_url}"
39
- client: openai.OpenAI = openai_clients.get(client_key)
39
+ client: openai.OpenAI | None = openai_clients.get(client_key)
40
40
  if not client:
41
41
  client = openai.OpenAI(
42
42
  api_key=openai_api_key,
@@ -208,10 +208,12 @@ Construct search queries to retrieve relevant information to answer the user's q
208
208
  - Add as much context from the previous questions and answers as required into your search queries.
209
209
  - Break messages into multiple search queries when required to retrieve the relevant information.
210
210
  - Add date filters to your search queries from questions and answers when required to retrieve the relevant information.
211
+ - When asked a meta, vague or random questions, search for a variety of broad topics to answer the user's question.
211
212
  - Share relevant search queries as a JSON list of strings. Do not say anything else.
212
213
 
213
214
  Current Date: {day_of_week}, {current_date}
214
215
  User's Location: {location}
216
+ {username}
215
217
 
216
218
  Examples:
217
219
  Q: How was my trip to Cambodia?
@@ -238,6 +240,9 @@ Khoj: ["What kind of plants do I have?", "What issues do my plants have?"]
238
240
  Q: Who all did I meet here yesterday?
239
241
  Khoj: ["Met in {location} on {yesterday_date} dt>='{yesterday_date}' dt<'{current_date}'"]
240
242
 
243
+ Q: Share some random, interesting experiences from this month
244
+ Khoj: ["Exciting travel adventures from {current_month}", "Fun social events dt>='{current_month}-01' dt<'{current_date}'", "Intense emotional experiences in {current_month}"]
245
+
241
246
  Chat History:
242
247
  {chat_history}
243
248
  What searches will you perform to answer the following question, using the chat history as reference? Respond only with relevant search queries as a valid JSON list of strings.
@@ -254,10 +259,12 @@ Construct search queries to retrieve relevant information to answer the user's q
254
259
  - Add as much context from the previous questions and answers as required into your search queries.
255
260
  - Break messages into multiple search queries when required to retrieve the relevant information.
256
261
  - Add date filters to your search queries from questions and answers when required to retrieve the relevant information.
262
+ - When asked a meta, vague or random questions, search for a variety of broad topics to answer the user's question.
257
263
 
258
264
  What searches will you perform to answer the users question? Respond with search queries as list of strings in a JSON object.
259
265
  Current Date: {day_of_week}, {current_date}
260
266
  User's Location: {location}
267
+ {username}
261
268
 
262
269
  Q: How was my trip to Cambodia?
263
270
  Khoj: {{"queries": ["How was my trip to Cambodia?"]}}
@@ -279,6 +286,10 @@ Q: How many tennis balls fit in the back of a 2002 Honda Civic?
279
286
  Khoj: {{"queries": ["What is the size of a tennis ball?", "What is the trunk size of a 2002 Honda Civic?"]}}
280
287
  A: 1085 tennis balls will fit in the trunk of a Honda Civic
281
288
 
289
+ Q: Share some random, interesting experiences from this month
290
+ Khoj: {{"queries": ["Exciting travel adventures from {current_month}", "Fun social events dt>='{current_month}-01' dt<'{current_date}'", "Intense emotional experiences in {current_month}"]}}
291
+ A: You had a great time at the local beach with your friends, attended a music concert and had a deep conversation with your friend, Khalid.
292
+
282
293
  Q: Is Bob older than Tom?
283
294
  Khoj: {{"queries": ["When was Bob born?", "What is Tom's age?"]}}
284
295
  A: Yes, Bob is older than Tom. As Bob was born on 1984-01-01 and Tom is 30 years old.
@@ -305,11 +316,13 @@ Construct search queries to retrieve relevant information to answer the user's q
305
316
  - Add as much context from the previous questions and answers as required into your search queries.
306
317
  - Break messages into multiple search queries when required to retrieve the relevant information.
307
318
  - Add date filters to your search queries from questions and answers when required to retrieve the relevant information.
319
+ - When asked a meta, vague or random questions, search for a variety of broad topics to answer the user's question.
308
320
 
309
321
  What searches will you perform to answer the users question? Respond with a JSON object with the key "queries" mapping to a list of searches you would perform on the user's knowledge base. Just return the queries and nothing else.
310
322
 
311
323
  Current Date: {day_of_week}, {current_date}
312
324
  User's Location: {location}
325
+ {username}
313
326
 
314
327
  Here are some examples of how you can construct search queries to answer the user's question:
315
328
 
@@ -328,6 +341,11 @@ A: I can help you live healthier and happier across work and personal life
328
341
  User: Who all did I meet here yesterday?
329
342
  Assistant: {{"queries": ["Met in {location} on {yesterday_date} dt>='{yesterday_date}' dt<'{current_date}'"]}}
330
343
  A: Yesterday's note mentions your visit to your local beach with Ram and Shyam.
344
+
345
+ User: Share some random, interesting experiences from this month
346
+ Assistant: {{"queries": ["Exciting travel adventures from {current_month}", "Fun social events dt>='{current_month}-01' dt<'{current_date}'", "Intense emotional experiences in {current_month}"]}}
347
+ A: You had a great time at the local beach with your friends, attended a music concert and had a deep conversation with your friend, Khalid.
348
+
331
349
  """.strip()
332
350
  )
333
351
 
@@ -525,6 +543,7 @@ Which webpages will you need to read to answer the user's question?
525
543
  Provide web page links as a list of strings in a JSON object.
526
544
  Current Date: {current_date}
527
545
  User's Location: {location}
546
+ {username}
528
547
 
529
548
  Here are some examples:
530
549
  History:
@@ -571,6 +590,7 @@ What Google searches, if any, will you need to perform to answer the user's ques
571
590
  Provide search queries as a list of strings in a JSON object. Do not wrap the json in a codeblock.
572
591
  Current Date: {current_date}
573
592
  User's Location: {location}
593
+ {username}
574
594
 
575
595
  Here are some examples:
576
596
  History:
@@ -95,11 +95,13 @@ class CrossEncoderModel:
95
95
  model_name: str = "mixedbread-ai/mxbai-rerank-xsmall-v1",
96
96
  cross_encoder_inference_endpoint: str = None,
97
97
  cross_encoder_inference_endpoint_api_key: str = None,
98
+ model_kwargs: dict = {},
98
99
  ):
99
100
  self.model_name = model_name
100
- self.cross_encoder_model = CrossEncoder(model_name=self.model_name, device=get_device())
101
101
  self.inference_endpoint = cross_encoder_inference_endpoint
102
102
  self.api_key = cross_encoder_inference_endpoint_api_key
103
+ self.model_kwargs = merge_dicts(model_kwargs, {"device": get_device()})
104
+ self.cross_encoder_model = CrossEncoder(model_name=self.model_name, **self.model_kwargs)
103
105
 
104
106
  def inference_server_enabled(self) -> bool:
105
107
  return self.api_key is not None and self.inference_endpoint is not None
@@ -10,6 +10,7 @@ import aiohttp
10
10
  from bs4 import BeautifulSoup
11
11
  from markdownify import markdownify
12
12
 
13
+ from khoj.database.models import KhojUser
13
14
  from khoj.routers.helpers import (
14
15
  ChatEvent,
15
16
  extract_relevant_info,
@@ -51,6 +52,8 @@ async def search_online(
51
52
  query: str,
52
53
  conversation_history: dict,
53
54
  location: LocationData,
55
+ user: KhojUser,
56
+ subscribed: bool = False,
54
57
  send_status_func: Optional[Callable] = None,
55
58
  custom_filters: List[str] = [],
56
59
  ):
@@ -61,7 +64,7 @@ async def search_online(
61
64
  return
62
65
 
63
66
  # Breakdown the query into subqueries to get the correct answer
64
- subqueries = await generate_online_subqueries(query, conversation_history, location)
67
+ subqueries = await generate_online_subqueries(query, conversation_history, location, user)
65
68
  response_dict = {}
66
69
 
67
70
  if subqueries:
@@ -89,12 +92,15 @@ async def search_online(
89
92
  # Read, extract relevant info from the retrieved web pages
90
93
  if webpages:
91
94
  webpage_links = [link for link, _, _ in webpages]
92
- logger.info(f"🌐👀 Reading web pages at: {list(webpage_links)}")
95
+ logger.info(f"Reading web pages at: {list(webpage_links)}")
93
96
  if send_status_func:
94
97
  webpage_links_str = "\n- " + "\n- ".join(list(webpage_links))
95
98
  async for event in send_status_func(f"**Reading web pages**: {webpage_links_str}"):
96
99
  yield {ChatEvent.STATUS: event}
97
- tasks = [read_webpage_and_extract_content(subquery, link, content) for link, subquery, content in webpages]
100
+ tasks = [
101
+ read_webpage_and_extract_content(subquery, link, content, subscribed=subscribed)
102
+ for link, subquery, content in webpages
103
+ ]
98
104
  results = await asyncio.gather(*tasks)
99
105
 
100
106
  # Collect extracted info from the retrieved web pages
@@ -126,21 +132,26 @@ async def search_with_google(query: str) -> Tuple[str, Dict[str, List[Dict]]]:
126
132
 
127
133
 
128
134
  async def read_webpages(
129
- query: str, conversation_history: dict, location: LocationData, send_status_func: Optional[Callable] = None
135
+ query: str,
136
+ conversation_history: dict,
137
+ location: LocationData,
138
+ user: KhojUser,
139
+ subscribed: bool = False,
140
+ send_status_func: Optional[Callable] = None,
130
141
  ):
131
142
  "Infer web pages to read from the query and extract relevant information from them"
132
143
  logger.info(f"Inferring web pages to read")
133
144
  if send_status_func:
134
145
  async for event in send_status_func(f"**Inferring web pages to read**"):
135
146
  yield {ChatEvent.STATUS: event}
136
- urls = await infer_webpage_urls(query, conversation_history, location)
147
+ urls = await infer_webpage_urls(query, conversation_history, location, user)
137
148
 
138
149
  logger.info(f"Reading web pages at: {urls}")
139
150
  if send_status_func:
140
151
  webpage_links_str = "\n- " + "\n- ".join(list(urls))
141
152
  async for event in send_status_func(f"**Reading web pages**: {webpage_links_str}"):
142
153
  yield {ChatEvent.STATUS: event}
143
- tasks = [read_webpage_and_extract_content(query, url) for url in urls]
154
+ tasks = [read_webpage_and_extract_content(query, url, subscribed=subscribed) for url in urls]
144
155
  results = await asyncio.gather(*tasks)
145
156
 
146
157
  response: Dict[str, Dict] = defaultdict(dict)
@@ -151,14 +162,14 @@ async def read_webpages(
151
162
 
152
163
 
153
164
  async def read_webpage_and_extract_content(
154
- subquery: str, url: str, content: str = None
165
+ subquery: str, url: str, content: str = None, subscribed: bool = False
155
166
  ) -> Tuple[str, Union[None, str], str]:
156
167
  try:
157
168
  if is_none_or_empty(content):
158
169
  with timer(f"Reading web page at '{url}' took", logger):
159
170
  content = await read_webpage_with_olostep(url) if OLOSTEP_API_KEY else await read_webpage_with_jina(url)
160
171
  with timer(f"Extracting relevant information from web page at '{url}' took", logger):
161
- extracted_info = await extract_relevant_info(subquery, content)
172
+ extracted_info = await extract_relevant_info(subquery, content, subscribed=subscribed)
162
173
  return subquery, extracted_info, url
163
174
  except Exception as e:
164
175
  logger.error(f"Failed to read web page at '{url}' with {e}")
khoj/routers/api.py CHANGED
@@ -388,6 +388,7 @@ async def extract_references_and_questions(
388
388
  conversation_log=meta_log,
389
389
  should_extract_questions=True,
390
390
  location_data=location_data,
391
+ user=user,
391
392
  max_prompt_size=conversation_config.max_prompt_size,
392
393
  )
393
394
  elif conversation_config.model_type == ChatModelOptions.ModelType.OPENAI:
@@ -402,7 +403,7 @@ async def extract_references_and_questions(
402
403
  api_base_url=base_url,
403
404
  conversation_log=meta_log,
404
405
  location_data=location_data,
405
- max_tokens=conversation_config.max_prompt_size,
406
+ user=user,
406
407
  )
407
408
  elif conversation_config.model_type == ChatModelOptions.ModelType.ANTHROPIC:
408
409
  api_key = conversation_config.openai_config.api_key
@@ -413,6 +414,7 @@ async def extract_references_and_questions(
413
414
  api_key=api_key,
414
415
  conversation_log=meta_log,
415
416
  location_data=location_data,
417
+ user=user,
416
418
  )
417
419
 
418
420
  # Collate search results as context for GPT
khoj/routers/api_chat.py CHANGED
@@ -4,14 +4,14 @@ import logging
4
4
  import time
5
5
  from datetime import datetime
6
6
  from functools import partial
7
- from typing import Any, Dict, List, Optional
7
+ from typing import Dict, Optional
8
8
  from urllib.parse import unquote
9
9
 
10
10
  from asgiref.sync import sync_to_async
11
11
  from fastapi import APIRouter, Depends, HTTPException, Request
12
12
  from fastapi.requests import Request
13
13
  from fastapi.responses import Response, StreamingResponse
14
- from starlette.authentication import requires
14
+ from starlette.authentication import has_required_scope, requires
15
15
 
16
16
  from khoj.app.settings import ALLOWED_HOSTS
17
17
  from khoj.database.adapters import (
@@ -59,7 +59,7 @@ from khoj.utils.rawconfig import FileFilterRequest, FilesFilterRequest, Location
59
59
  # Initialize Router
60
60
  logger = logging.getLogger(__name__)
61
61
  conversation_command_rate_limiter = ConversationCommandRateLimiter(
62
- trial_rate_limit=2, subscribed_rate_limit=100, slug="command"
62
+ trial_rate_limit=100, subscribed_rate_limit=100, slug="command"
63
63
  )
64
64
 
65
65
 
@@ -532,10 +532,10 @@ async def chat(
532
532
  country: Optional[str] = None,
533
533
  timezone: Optional[str] = None,
534
534
  rate_limiter_per_minute=Depends(
535
- ApiUserRateLimiter(requests=5, subscribed_requests=60, window=60, slug="chat_minute")
535
+ ApiUserRateLimiter(requests=60, subscribed_requests=60, window=60, slug="chat_minute")
536
536
  ),
537
537
  rate_limiter_per_day=Depends(
538
- ApiUserRateLimiter(requests=5, subscribed_requests=600, window=60 * 60 * 24, slug="chat_day")
538
+ ApiUserRateLimiter(requests=600, subscribed_requests=600, window=60 * 60 * 24, slug="chat_day")
539
539
  ),
540
540
  ):
541
541
  async def event_generator(q: str):
@@ -544,6 +544,7 @@ async def chat(
544
544
  chat_metadata: dict = {}
545
545
  connection_alive = True
546
546
  user: KhojUser = request.user.object
547
+ subscribed: bool = has_required_scope(request, ["premium"])
547
548
  event_delimiter = "␃🔚␗"
548
549
  q = unquote(q)
549
550
 
@@ -632,7 +633,9 @@ async def chat(
632
633
  is_automated_task = conversation_commands == [ConversationCommand.AutomatedTask]
633
634
 
634
635
  if conversation_commands == [ConversationCommand.Default] or is_automated_task:
635
- conversation_commands = await aget_relevant_information_sources(q, meta_log, is_automated_task)
636
+ conversation_commands = await aget_relevant_information_sources(
637
+ q, meta_log, is_automated_task, subscribed=subscribed
638
+ )
636
639
  conversation_commands_str = ", ".join([cmd.value for cmd in conversation_commands])
637
640
  async for result in send_event(
638
641
  ChatEvent.STATUS, f"**Chose Data Sources to Search:** {conversation_commands_str}"
@@ -687,7 +690,7 @@ async def chat(
687
690
  ):
688
691
  yield result
689
692
 
690
- response = await extract_relevant_summary(q, contextual_data)
693
+ response = await extract_relevant_summary(q, contextual_data, subscribed=subscribed)
691
694
  response_log = str(response)
692
695
  async for result in send_llm_response(response_log):
693
696
  yield result
@@ -792,7 +795,13 @@ async def chat(
792
795
  if ConversationCommand.Online in conversation_commands:
793
796
  try:
794
797
  async for result in search_online(
795
- defiltered_query, meta_log, location, partial(send_event, ChatEvent.STATUS), custom_filters
798
+ defiltered_query,
799
+ meta_log,
800
+ location,
801
+ user,
802
+ subscribed,
803
+ partial(send_event, ChatEvent.STATUS),
804
+ custom_filters,
796
805
  ):
797
806
  if isinstance(result, dict) and ChatEvent.STATUS in result:
798
807
  yield result[ChatEvent.STATUS]
@@ -809,7 +818,7 @@ async def chat(
809
818
  if ConversationCommand.Webpage in conversation_commands:
810
819
  try:
811
820
  async for result in read_webpages(
812
- defiltered_query, meta_log, location, partial(send_event, ChatEvent.STATUS)
821
+ defiltered_query, meta_log, location, user, subscribed, partial(send_event, ChatEvent.STATUS)
813
822
  ):
814
823
  if isinstance(result, dict) and ChatEvent.STATUS in result:
815
824
  yield result[ChatEvent.STATUS]
@@ -853,6 +862,7 @@ async def chat(
853
862
  location_data=location,
854
863
  references=compiled_references,
855
864
  online_results=online_results,
865
+ subscribed=subscribed,
856
866
  send_status_func=partial(send_event, ChatEvent.STATUS),
857
867
  ):
858
868
  if isinstance(result, dict) and ChatEvent.STATUS in result:
khoj/routers/helpers.py CHANGED
@@ -252,7 +252,7 @@ async def acreate_title_from_query(query: str) -> str:
252
252
  return response.strip()
253
253
 
254
254
 
255
- async def aget_relevant_information_sources(query: str, conversation_history: dict, is_task: bool):
255
+ async def aget_relevant_information_sources(query: str, conversation_history: dict, is_task: bool, subscribed: bool):
256
256
  """
257
257
  Given a query, determine which of the available tools the agent should use in order to answer appropriately.
258
258
  """
@@ -273,7 +273,9 @@ async def aget_relevant_information_sources(query: str, conversation_history: di
273
273
  )
274
274
 
275
275
  with timer("Chat actor: Infer information sources to refer", logger):
276
- response = await send_message_to_model_wrapper(relevant_tools_prompt, response_type="json_object")
276
+ response = await send_message_to_model_wrapper(
277
+ relevant_tools_prompt, response_type="json_object", subscribed=subscribed
278
+ )
277
279
 
278
280
  try:
279
281
  response = response.strip()
@@ -340,11 +342,14 @@ async def aget_relevant_output_modes(query: str, conversation_history: dict, is_
340
342
  return ConversationCommand.Text
341
343
 
342
344
 
343
- async def infer_webpage_urls(q: str, conversation_history: dict, location_data: LocationData) -> List[str]:
345
+ async def infer_webpage_urls(
346
+ q: str, conversation_history: dict, location_data: LocationData, user: KhojUser
347
+ ) -> List[str]:
344
348
  """
345
349
  Infer webpage links from the given query
346
350
  """
347
351
  location = f"{location_data.city}, {location_data.region}, {location_data.country}" if location_data else "Unknown"
352
+ username = prompts.user_name.format(name=user.get_full_name()) if user.get_full_name() else ""
348
353
  chat_history = construct_chat_history(conversation_history)
349
354
 
350
355
  utc_date = datetime.utcnow().strftime("%Y-%m-%d")
@@ -353,6 +358,7 @@ async def infer_webpage_urls(q: str, conversation_history: dict, location_data:
353
358
  query=q,
354
359
  chat_history=chat_history,
355
360
  location=location,
361
+ username=username,
356
362
  )
357
363
 
358
364
  with timer("Chat actor: Infer webpage urls to read", logger):
@@ -370,11 +376,14 @@ async def infer_webpage_urls(q: str, conversation_history: dict, location_data:
370
376
  raise ValueError(f"Invalid list of urls: {response}")
371
377
 
372
378
 
373
- async def generate_online_subqueries(q: str, conversation_history: dict, location_data: LocationData) -> List[str]:
379
+ async def generate_online_subqueries(
380
+ q: str, conversation_history: dict, location_data: LocationData, user: KhojUser
381
+ ) -> List[str]:
374
382
  """
375
383
  Generate subqueries from the given query
376
384
  """
377
385
  location = f"{location_data.city}, {location_data.region}, {location_data.country}" if location_data else "Unknown"
386
+ username = prompts.user_name.format(name=user.get_full_name()) if user.get_full_name() else ""
378
387
  chat_history = construct_chat_history(conversation_history)
379
388
 
380
389
  utc_date = datetime.utcnow().strftime("%Y-%m-%d")
@@ -383,6 +392,7 @@ async def generate_online_subqueries(q: str, conversation_history: dict, locatio
383
392
  query=q,
384
393
  chat_history=chat_history,
385
394
  location=location,
395
+ username=username,
386
396
  )
387
397
 
388
398
  with timer("Chat actor: Generate online search subqueries", logger):
@@ -426,7 +436,7 @@ async def schedule_query(q: str, conversation_history: dict) -> Tuple[str, ...]:
426
436
  raise AssertionError(f"Invalid response for scheduling query: {raw_response}")
427
437
 
428
438
 
429
- async def extract_relevant_info(q: str, corpus: str) -> Union[str, None]:
439
+ async def extract_relevant_info(q: str, corpus: str, subscribed: bool) -> Union[str, None]:
430
440
  """
431
441
  Extract relevant information for a given query from the target corpus
432
442
  """
@@ -439,18 +449,19 @@ async def extract_relevant_info(q: str, corpus: str) -> Union[str, None]:
439
449
  corpus=corpus.strip(),
440
450
  )
441
451
 
442
- summarizer_model: ChatModelOptions = await ConversationAdapters.aget_summarizer_conversation_config()
452
+ chat_model: ChatModelOptions = await ConversationAdapters.aget_default_conversation_config()
443
453
 
444
454
  with timer("Chat actor: Extract relevant information from data", logger):
445
455
  response = await send_message_to_model_wrapper(
446
456
  extract_relevant_information,
447
457
  prompts.system_prompt_extract_relevant_information,
448
- chat_model_option=summarizer_model,
458
+ chat_model_option=chat_model,
459
+ subscribed=subscribed,
449
460
  )
450
461
  return response.strip()
451
462
 
452
463
 
453
- async def extract_relevant_summary(q: str, corpus: str) -> Union[str, None]:
464
+ async def extract_relevant_summary(q: str, corpus: str, subscribed: bool = False) -> Union[str, None]:
454
465
  """
455
466
  Extract relevant information for a given query from the target corpus
456
467
  """
@@ -463,13 +474,14 @@ async def extract_relevant_summary(q: str, corpus: str) -> Union[str, None]:
463
474
  corpus=corpus.strip(),
464
475
  )
465
476
 
466
- summarizer_model: ChatModelOptions = await ConversationAdapters.aget_summarizer_conversation_config()
477
+ chat_model: ChatModelOptions = await ConversationAdapters.aget_default_conversation_config()
467
478
 
468
479
  with timer("Chat actor: Extract relevant information from data", logger):
469
480
  response = await send_message_to_model_wrapper(
470
481
  extract_relevant_information,
471
482
  prompts.system_prompt_extract_relevant_summary,
472
- chat_model_option=summarizer_model,
483
+ chat_model_option=chat_model,
484
+ subscribed=subscribed,
473
485
  )
474
486
  return response.strip()
475
487
 
@@ -481,6 +493,7 @@ async def generate_better_image_prompt(
481
493
  note_references: List[Dict[str, Any]],
482
494
  online_results: Optional[dict] = None,
483
495
  model_type: Optional[str] = None,
496
+ subscribed: bool = False,
484
497
  ) -> str:
485
498
  """
486
499
  Generate a better image prompt from the given query
@@ -525,10 +538,12 @@ async def generate_better_image_prompt(
525
538
  online_results=simplified_online_results,
526
539
  )
527
540
 
528
- summarizer_model: ChatModelOptions = await ConversationAdapters.aget_summarizer_conversation_config()
541
+ chat_model: ChatModelOptions = await ConversationAdapters.aget_default_conversation_config()
529
542
 
530
543
  with timer("Chat actor: Generate contextual image prompt", logger):
531
- response = await send_message_to_model_wrapper(image_prompt, chat_model_option=summarizer_model)
544
+ response = await send_message_to_model_wrapper(
545
+ image_prompt, chat_model_option=chat_model, subscribed=subscribed
546
+ )
532
547
  response = response.strip()
533
548
  if response.startswith(('"', "'")) and response.endswith(('"', "'")):
534
549
  response = response[1:-1]
@@ -541,13 +556,18 @@ async def send_message_to_model_wrapper(
541
556
  system_message: str = "",
542
557
  response_type: str = "text",
543
558
  chat_model_option: ChatModelOptions = None,
559
+ subscribed: bool = False,
544
560
  ):
545
561
  conversation_config: ChatModelOptions = (
546
562
  chat_model_option or await ConversationAdapters.aget_default_conversation_config()
547
563
  )
548
564
 
549
565
  chat_model = conversation_config.chat_model
550
- max_tokens = conversation_config.max_prompt_size
566
+ max_tokens = (
567
+ conversation_config.subscribed_max_prompt_size
568
+ if subscribed and conversation_config.subscribed_max_prompt_size
569
+ else conversation_config.max_prompt_size
570
+ )
551
571
  tokenizer = conversation_config.tokenizer
552
572
 
553
573
  if conversation_config.model_type == "offline":
@@ -778,6 +798,7 @@ async def text_to_image(
778
798
  location_data: LocationData,
779
799
  references: List[Dict[str, Any]],
780
800
  online_results: Dict[str, Any],
801
+ subscribed: bool = False,
781
802
  send_status_func: Optional[Callable] = None,
782
803
  ):
783
804
  status_code = 200
@@ -814,6 +835,7 @@ async def text_to_image(
814
835
  note_references=references,
815
836
  online_results=online_results,
816
837
  model_type=text_to_image_config.model_type,
838
+ subscribed=subscribed,
817
839
  )
818
840
 
819
841
  if send_status_func:
@@ -1351,7 +1373,9 @@ def get_user_config(user: KhojUser, request: Request, is_detailed: bool = False)
1351
1373
  current_notion_config = get_user_notion_config(user)
1352
1374
  notion_token = current_notion_config.token if current_notion_config else ""
1353
1375
 
1354
- selected_chat_model_config = ConversationAdapters.get_conversation_config(user)
1376
+ selected_chat_model_config = (
1377
+ ConversationAdapters.get_conversation_config(user) or ConversationAdapters.get_default_conversation_config()
1378
+ )
1355
1379
  chat_models = ConversationAdapters.get_conversation_processor_options().all()
1356
1380
  chat_model_options = list()
1357
1381
  for chat_model in chat_models:
@@ -11,7 +11,8 @@ logger = logging.getLogger(__name__)
11
11
 
12
12
 
13
13
  class FileFilter(BaseFilter):
14
- file_filter_regex = r'file:"(.+?)" ?'
14
+ file_filter_regex = r'(?<!-)file:"(.+?)" ?'
15
+ excluded_file_filter_regex = r'-file:"(.+?)" ?'
15
16
 
16
17
  def __init__(self, entry_key="file"):
17
18
  self.entry_key = entry_key
@@ -20,7 +21,9 @@ class FileFilter(BaseFilter):
20
21
 
21
22
  def get_filter_terms(self, query: str) -> List[str]:
22
23
  "Get all filter terms in query"
23
- return [f"{self.convert_to_regex(term)}" for term in re.findall(self.file_filter_regex, query)]
24
+ required_files = [f"{required_file}" for required_file in re.findall(self.file_filter_regex, query)]
25
+ excluded_files = [f"-{excluded_file}" for excluded_file in re.findall(self.excluded_file_filter_regex, query)]
26
+ return required_files + excluded_files
24
27
 
25
28
  def convert_to_regex(self, file_filter: str) -> str:
26
29
  "Convert file filter to regex"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: khoj
3
- Version: 1.20.4.dev13
3
+ Version: 1.20.5.dev16
4
4
  Summary: Your Second Brain
5
5
  Project-URL: Homepage, https://khoj.dev
6
6
  Project-URL: Documentation, https://docs.khoj.dev