khoj 1.30.11.dev64__py3-none-any.whl → 1.32.3.dev34__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (146) hide show
  1. khoj/configure.py +4 -2
  2. khoj/database/adapters/__init__.py +67 -58
  3. khoj/database/admin.py +9 -9
  4. khoj/database/migrations/0077_chatmodel_alter_agent_chat_model_and_more.py +62 -0
  5. khoj/database/migrations/0078_khojuser_email_verification_code_expiry.py +17 -0
  6. khoj/database/models/__init__.py +9 -8
  7. khoj/interface/compiled/404/index.html +1 -1
  8. khoj/interface/compiled/_next/static/chunks/182-8cd8b17d40e6e989.js +20 -0
  9. khoj/interface/compiled/_next/static/chunks/1915-605f698f2573cfd4.js +1 -0
  10. khoj/interface/compiled/_next/static/chunks/2117-9886e6a0232dc093.js +2 -0
  11. khoj/interface/compiled/_next/static/chunks/2581-455000f8aeb08fc3.js +1 -0
  12. khoj/interface/compiled/_next/static/chunks/3175-b2e522f8ca392f7e.js +3 -0
  13. khoj/interface/compiled/_next/static/chunks/3727.dcea8f2193111552.js +1 -0
  14. khoj/interface/compiled/_next/static/chunks/3789-a09e37a819171a9d.js +1 -0
  15. khoj/interface/compiled/_next/static/chunks/4124-0baa32400521e909.js +1 -0
  16. khoj/interface/compiled/_next/static/chunks/4357-03ea130575287c27.js +1 -0
  17. khoj/interface/compiled/_next/static/chunks/5243-f7f0a2a6e1ac5d28.js +1 -0
  18. khoj/interface/compiled/_next/static/chunks/5427-3e7360c8e6ac9728.js +1 -0
  19. khoj/interface/compiled/_next/static/chunks/{1279-4cb23143aa2c0228.js → 5473-b1cf56dedac6577a.js} +1 -1
  20. khoj/interface/compiled/_next/static/chunks/5477-c5d7eabee28a789a.js +1 -0
  21. khoj/interface/compiled/_next/static/chunks/6065-64db9ad305ba0bcd.js +1 -0
  22. khoj/interface/compiled/_next/static/chunks/8667-d3e5bc726e4ff4e3.js +1 -0
  23. khoj/interface/compiled/_next/static/chunks/9259-27d1ff42af9a43e0.js +1 -0
  24. khoj/interface/compiled/_next/static/chunks/94ca1967.1d9b42d929a1ee8c.js +1 -0
  25. khoj/interface/compiled/_next/static/chunks/{1210.ef7a0f9a7e43da1d.js → 9597.83583248dfbf6e73.js} +1 -1
  26. khoj/interface/compiled/_next/static/chunks/964ecbae.51d6faf8801d15e6.js +1 -0
  27. khoj/interface/compiled/_next/static/chunks/9665-1ab5c8c667b74dca.js +1 -0
  28. khoj/interface/compiled/_next/static/chunks/app/_not-found/{page-cfba071f5a657256.js → page-a834eddae3e235df.js} +1 -1
  29. khoj/interface/compiled/_next/static/chunks/app/agents/layout-e00fb81dca656a10.js +1 -0
  30. khoj/interface/compiled/_next/static/chunks/app/agents/page-ab5ebe4efba9b582.js +1 -0
  31. khoj/interface/compiled/_next/static/chunks/app/automations/{layout-7f1b79a2c67af0b4.js → layout-1fe1537449f43496.js} +1 -1
  32. khoj/interface/compiled/_next/static/chunks/app/automations/page-37d56a7bbfd307df.js +1 -0
  33. khoj/interface/compiled/_next/static/chunks/app/chat/layout-33934fc2d6ae6838.js +1 -0
  34. khoj/interface/compiled/_next/static/chunks/app/chat/page-a0b61f10b0bf6dd5.js +1 -0
  35. khoj/interface/compiled/_next/static/chunks/app/layout-30e7fda7262713ce.js +1 -0
  36. khoj/interface/compiled/_next/static/chunks/app/page-33a3375b1414d1bd.js +1 -0
  37. khoj/interface/compiled/_next/static/chunks/app/search/layout-c02531d586972d7d.js +1 -0
  38. khoj/interface/compiled/_next/static/chunks/app/search/page-bbbfda90fa03c5be.js +1 -0
  39. khoj/interface/compiled/_next/static/chunks/app/settings/layout-d09d6510a45cd4bd.js +1 -0
  40. khoj/interface/compiled/_next/static/chunks/app/settings/page-430db6215e48aea2.js +1 -0
  41. khoj/interface/compiled/_next/static/chunks/app/share/chat/layout-e8e5db7830bf3f47.js +1 -0
  42. khoj/interface/compiled/_next/static/chunks/app/share/chat/page-02dc1f2e2a41e522.js +1 -0
  43. khoj/interface/compiled/_next/static/chunks/d3ac728e-44ebd2a0c99b12a0.js +1 -0
  44. khoj/interface/compiled/_next/static/chunks/{fd9d1056-2e6c8140e79afc3b.js → fd9d1056-4482b99a36fd1673.js} +1 -1
  45. khoj/interface/compiled/_next/static/chunks/main-app-de1f09df97a3cfc7.js +1 -0
  46. khoj/interface/compiled/_next/static/chunks/main-db4bfac6b0a8d00b.js +1 -0
  47. khoj/interface/compiled/_next/static/chunks/pages/{_app-f870474a17b7f2fd.js → _app-3c9ca398d360b709.js} +1 -1
  48. khoj/interface/compiled/_next/static/chunks/pages/{_error-c66a4e8afc46f17b.js → _error-cf5ca766ac8f493f.js} +1 -1
  49. khoj/interface/compiled/_next/static/chunks/polyfills-42372ed130431b0a.js +1 -0
  50. khoj/interface/compiled/_next/static/chunks/webpack-b0a1b08bb62bdc15.js +1 -0
  51. khoj/interface/compiled/_next/static/css/0f04760e76bba6c1.css +25 -0
  52. khoj/interface/compiled/_next/static/css/37a73b87f02df402.css +1 -0
  53. khoj/interface/compiled/_next/static/css/8e6a3ca11a60b189.css +1 -0
  54. khoj/interface/compiled/_next/static/css/9c164d9727dd8092.css +1 -0
  55. khoj/interface/compiled/_next/static/css/c3acbadc30537d04.css +1 -0
  56. khoj/interface/compiled/_next/static/css/dac88c17aaee5fcf.css +1 -0
  57. khoj/interface/compiled/_next/static/css/df4b47a2d0d85eae.css +1 -0
  58. khoj/interface/compiled/_next/static/css/e546bf5cc4914244.css +1 -0
  59. khoj/interface/compiled/_next/static/mqcIHpVqVWkmBuN0npYHA/_buildManifest.js +1 -0
  60. khoj/interface/compiled/agents/index.html +1 -1
  61. khoj/interface/compiled/agents/index.txt +6 -6
  62. khoj/interface/compiled/automations/index.html +1 -1
  63. khoj/interface/compiled/automations/index.txt +7 -7
  64. khoj/interface/compiled/chat/index.html +1 -1
  65. khoj/interface/compiled/chat/index.txt +6 -6
  66. khoj/interface/compiled/index.html +1 -1
  67. khoj/interface/compiled/index.txt +6 -6
  68. khoj/interface/compiled/search/index.html +1 -1
  69. khoj/interface/compiled/search/index.txt +6 -6
  70. khoj/interface/compiled/settings/index.html +1 -1
  71. khoj/interface/compiled/settings/index.txt +8 -8
  72. khoj/interface/compiled/share/chat/index.html +1 -1
  73. khoj/interface/compiled/share/chat/index.txt +6 -6
  74. khoj/interface/email/magic_link.html +36 -13
  75. khoj/main.py +1 -1
  76. khoj/migrations/migrate_server_pg.py +7 -7
  77. khoj/processor/conversation/anthropic/anthropic_chat.py +5 -7
  78. khoj/processor/conversation/google/gemini_chat.py +5 -7
  79. khoj/processor/conversation/google/utils.py +0 -1
  80. khoj/processor/conversation/offline/chat_model.py +15 -14
  81. khoj/processor/conversation/openai/gpt.py +7 -9
  82. khoj/processor/conversation/openai/utils.py +31 -17
  83. khoj/processor/conversation/prompts.py +65 -49
  84. khoj/processor/conversation/utils.py +46 -44
  85. khoj/processor/tools/online_search.py +49 -2
  86. khoj/routers/api.py +22 -27
  87. khoj/routers/api_agents.py +4 -4
  88. khoj/routers/api_chat.py +33 -13
  89. khoj/routers/api_model.py +4 -4
  90. khoj/routers/auth.py +108 -7
  91. khoj/routers/email.py +10 -14
  92. khoj/routers/helpers.py +187 -143
  93. khoj/routers/web_client.py +1 -1
  94. khoj/utils/constants.py +1 -1
  95. khoj/utils/helpers.py +5 -3
  96. khoj/utils/initialization.py +28 -26
  97. {khoj-1.30.11.dev64.dist-info → khoj-1.32.3.dev34.dist-info}/METADATA +7 -7
  98. {khoj-1.30.11.dev64.dist-info → khoj-1.32.3.dev34.dist-info}/RECORD +102 -99
  99. {khoj-1.30.11.dev64.dist-info → khoj-1.32.3.dev34.dist-info}/WHEEL +1 -1
  100. khoj/interface/compiled/_next/static/67DcUiU9MqkM1fhksWunh/_buildManifest.js +0 -1
  101. khoj/interface/compiled/_next/static/chunks/1459.690bf20e7d7b7090.js +0 -1
  102. khoj/interface/compiled/_next/static/chunks/1603-13cef426e0e650ec.js +0 -1
  103. khoj/interface/compiled/_next/static/chunks/1970-1b63ac1497b03a10.js +0 -1
  104. khoj/interface/compiled/_next/static/chunks/2646-92ba433951d02d52.js +0 -20
  105. khoj/interface/compiled/_next/static/chunks/3072-be830e4f8412b9d2.js +0 -1
  106. khoj/interface/compiled/_next/static/chunks/3463-081c031e873b7966.js +0 -3
  107. khoj/interface/compiled/_next/static/chunks/3690-51312931ba1eae30.js +0 -1
  108. khoj/interface/compiled/_next/static/chunks/3717-b46079dbe9f55694.js +0 -1
  109. khoj/interface/compiled/_next/static/chunks/4504-62ac13e7d94c52f9.js +0 -1
  110. khoj/interface/compiled/_next/static/chunks/4602-460621c3241e0d13.js +0 -1
  111. khoj/interface/compiled/_next/static/chunks/4752-554a3db270186ce3.js +0 -1
  112. khoj/interface/compiled/_next/static/chunks/5512-7cc62049bbe60e11.js +0 -1
  113. khoj/interface/compiled/_next/static/chunks/5538-0ea2d3944ca051e1.js +0 -1
  114. khoj/interface/compiled/_next/static/chunks/7023-e8de2bded4df6539.js +0 -2
  115. khoj/interface/compiled/_next/static/chunks/7592-a09c39a38e60634b.js +0 -1
  116. khoj/interface/compiled/_next/static/chunks/8423-1dda16bc56236523.js +0 -1
  117. khoj/interface/compiled/_next/static/chunks/94ca1967.5584df65931cfe83.js +0 -1
  118. khoj/interface/compiled/_next/static/chunks/964ecbae.ea4eab2a3a835ffe.js +0 -1
  119. khoj/interface/compiled/_next/static/chunks/app/agents/layout-1878cc328ea380bd.js +0 -1
  120. khoj/interface/compiled/_next/static/chunks/app/agents/page-8eead7920b0ff92a.js +0 -1
  121. khoj/interface/compiled/_next/static/chunks/app/automations/page-b5800b5286306140.js +0 -1
  122. khoj/interface/compiled/_next/static/chunks/app/chat/layout-9219a85f3477e722.js +0 -1
  123. khoj/interface/compiled/_next/static/chunks/app/chat/page-d7d2ab93e519f0b2.js +0 -1
  124. khoj/interface/compiled/_next/static/chunks/app/layout-6310c57b674dd6f5.js +0 -1
  125. khoj/interface/compiled/_next/static/chunks/app/page-3c32ad5472f75965.js +0 -1
  126. khoj/interface/compiled/_next/static/chunks/app/search/layout-2ca475462c0b2176.js +0 -1
  127. khoj/interface/compiled/_next/static/chunks/app/search/page-faa998c71eb7ca8e.js +0 -1
  128. khoj/interface/compiled/_next/static/chunks/app/settings/layout-f285795bc3154b8c.js +0 -1
  129. khoj/interface/compiled/_next/static/chunks/app/settings/page-cbe7f56b1f87d77a.js +0 -1
  130. khoj/interface/compiled/_next/static/chunks/app/share/chat/layout-592e8c470f2c2084.js +0 -1
  131. khoj/interface/compiled/_next/static/chunks/app/share/chat/page-cd5757199539bbf2.js +0 -1
  132. khoj/interface/compiled/_next/static/chunks/d3ac728e-a9e3522eef9b6b28.js +0 -1
  133. khoj/interface/compiled/_next/static/chunks/main-1ea5c2e0fdef4626.js +0 -1
  134. khoj/interface/compiled/_next/static/chunks/main-app-6d6ee3495efe03d4.js +0 -1
  135. khoj/interface/compiled/_next/static/chunks/polyfills-78c92fac7aa8fdd8.js +0 -1
  136. khoj/interface/compiled/_next/static/chunks/webpack-616f0694bfe6f6c1.js +0 -1
  137. khoj/interface/compiled/_next/static/css/1f293605f2871853.css +0 -1
  138. khoj/interface/compiled/_next/static/css/3c34171b174cc381.css +0 -25
  139. khoj/interface/compiled/_next/static/css/3cf13271869a4aeb.css +0 -1
  140. khoj/interface/compiled/_next/static/css/592ca99f5122e75a.css +0 -1
  141. khoj/interface/compiled/_next/static/css/5a400c87d295e68a.css +0 -1
  142. khoj/interface/compiled/_next/static/css/80bd6301fc657983.css +0 -1
  143. khoj/interface/compiled/_next/static/css/9c4221ae0779cc04.css +0 -1
  144. /khoj/interface/compiled/_next/static/{67DcUiU9MqkM1fhksWunh → mqcIHpVqVWkmBuN0npYHA}/_ssgManifest.js +0 -0
  145. {khoj-1.30.11.dev64.dist-info → khoj-1.32.3.dev34.dist-info}/entry_points.txt +0 -0
  146. {khoj-1.30.11.dev64.dist-info → khoj-1.32.3.dev34.dist-info}/licenses/LICENSE +0 -0
@@ -3,13 +3,13 @@ import logging
3
3
  import os
4
4
  from datetime import datetime, timedelta
5
5
  from threading import Thread
6
- from typing import Any, Iterator, List, Optional, Union
6
+ from typing import Any, Dict, Iterator, List, Optional, Union
7
7
 
8
8
  import pyjson5
9
9
  from langchain.schema import ChatMessage
10
10
  from llama_cpp import Llama
11
11
 
12
- from khoj.database.models import Agent, ChatModelOptions, KhojUser
12
+ from khoj.database.models import Agent, ChatModel, KhojUser
13
13
  from khoj.processor.conversation import prompts
14
14
  from khoj.processor.conversation.offline.utils import download_model
15
15
  from khoj.processor.conversation.utils import (
@@ -23,7 +23,6 @@ from khoj.utils import state
23
23
  from khoj.utils.constants import empty_escape_sequences
24
24
  from khoj.utils.helpers import (
25
25
  ConversationCommand,
26
- in_debug_mode,
27
26
  is_none_or_empty,
28
27
  is_promptrace_enabled,
29
28
  truncate_code_context,
@@ -96,7 +95,7 @@ def extract_questions_offline(
96
95
  model_name=model,
97
96
  loaded_model=offline_chat_model,
98
97
  max_prompt_size=max_prompt_size,
99
- model_type=ChatModelOptions.ModelType.OFFLINE,
98
+ model_type=ChatModel.ModelType.OFFLINE,
100
99
  query_files=query_files,
101
100
  )
102
101
 
@@ -105,7 +104,7 @@ def extract_questions_offline(
105
104
  response = send_message_to_model_offline(
106
105
  messages,
107
106
  loaded_model=offline_chat_model,
108
- model=model,
107
+ model_name=model,
109
108
  max_prompt_size=max_prompt_size,
110
109
  temperature=temperature,
111
110
  response_type="json_object",
@@ -154,7 +153,7 @@ def converse_offline(
154
153
  online_results={},
155
154
  code_results={},
156
155
  conversation_log={},
157
- model: str = "bartowski/Meta-Llama-3.1-8B-Instruct-GGUF",
156
+ model_name: str = "bartowski/Meta-Llama-3.1-8B-Instruct-GGUF",
158
157
  loaded_model: Union[Any, None] = None,
159
158
  completion_func=None,
160
159
  conversation_commands=[ConversationCommand.Default],
@@ -166,6 +165,7 @@ def converse_offline(
166
165
  query_files: str = None,
167
166
  generated_files: List[FileAttachment] = None,
168
167
  additional_context: List[str] = None,
168
+ generated_asset_results: Dict[str, Dict] = {},
169
169
  tracer: dict = {},
170
170
  ) -> Union[ThreadedGenerator, Iterator[str]]:
171
171
  """
@@ -173,8 +173,8 @@ def converse_offline(
173
173
  """
174
174
  # Initialize Variables
175
175
  assert loaded_model is None or isinstance(loaded_model, Llama), "loaded_model must be of type Llama, if configured"
176
- offline_chat_model = loaded_model or download_model(model, max_tokens=max_prompt_size)
177
- tracer["chat_model"] = model
176
+ offline_chat_model = loaded_model or download_model(model_name, max_tokens=max_prompt_size)
177
+ tracer["chat_model"] = model_name
178
178
  current_date = datetime.now()
179
179
 
180
180
  if agent and agent.personality:
@@ -227,17 +227,18 @@ def converse_offline(
227
227
  system_prompt,
228
228
  conversation_log,
229
229
  context_message=context_message,
230
- model_name=model,
230
+ model_name=model_name,
231
231
  loaded_model=offline_chat_model,
232
232
  max_prompt_size=max_prompt_size,
233
233
  tokenizer_name=tokenizer_name,
234
- model_type=ChatModelOptions.ModelType.OFFLINE,
234
+ model_type=ChatModel.ModelType.OFFLINE,
235
235
  query_files=query_files,
236
236
  generated_files=generated_files,
237
+ generated_asset_results=generated_asset_results,
237
238
  program_execution_context=additional_context,
238
239
  )
239
240
 
240
- logger.debug(f"Conversation Context for {model}: {messages_to_print(messages)}")
241
+ logger.debug(f"Conversation Context for {model_name}: {messages_to_print(messages)}")
241
242
 
242
243
  g = ThreadedGenerator(references, online_results, completion_func=completion_func)
243
244
  t = Thread(target=llm_thread, args=(g, messages, offline_chat_model, max_prompt_size, tracer))
@@ -271,7 +272,7 @@ def llm_thread(g, messages: List[ChatMessage], model: Any, max_prompt_size: int
271
272
  def send_message_to_model_offline(
272
273
  messages: List[ChatMessage],
273
274
  loaded_model=None,
274
- model="bartowski/Meta-Llama-3.1-8B-Instruct-GGUF",
275
+ model_name="bartowski/Meta-Llama-3.1-8B-Instruct-GGUF",
275
276
  temperature: float = 0.2,
276
277
  streaming=False,
277
278
  stop=[],
@@ -280,7 +281,7 @@ def send_message_to_model_offline(
280
281
  tracer: dict = {},
281
282
  ):
282
283
  assert loaded_model is None or isinstance(loaded_model, Llama), "loaded_model must be of type Llama, if configured"
283
- offline_chat_model = loaded_model or download_model(model, max_tokens=max_prompt_size)
284
+ offline_chat_model = loaded_model or download_model(model_name, max_tokens=max_prompt_size)
284
285
  messages_dict = [{"role": message.role, "content": message.content} for message in messages]
285
286
  seed = int(os.getenv("KHOJ_LLM_SEED")) if os.getenv("KHOJ_LLM_SEED") else None
286
287
  response = offline_chat_model.create_chat_completion(
@@ -299,7 +300,7 @@ def send_message_to_model_offline(
299
300
 
300
301
  # Save conversation trace for non-streaming responses
301
302
  # Streamed responses need to be saved by the calling function
302
- tracer["chat_model"] = model
303
+ tracer["chat_model"] = model_name
303
304
  tracer["temperature"] = temperature
304
305
  if is_promptrace_enabled():
305
306
  commit_conversation_trace(messages, response_text, tracer)
@@ -5,7 +5,7 @@ from typing import Dict, List, Optional
5
5
  import pyjson5
6
6
  from langchain.schema import ChatMessage
7
7
 
8
- from khoj.database.models import Agent, ChatModelOptions, KhojUser
8
+ from khoj.database.models import Agent, ChatModel, KhojUser
9
9
  from khoj.processor.conversation import prompts
10
10
  from khoj.processor.conversation.openai.utils import (
11
11
  chat_completion_with_backoff,
@@ -83,7 +83,7 @@ def extract_questions(
83
83
  prompt = construct_structured_message(
84
84
  message=prompt,
85
85
  images=query_images,
86
- model_type=ChatModelOptions.ModelType.OPENAI,
86
+ model_type=ChatModel.ModelType.OPENAI,
87
87
  vision_enabled=vision_enabled,
88
88
  attached_file_context=query_files,
89
89
  )
@@ -128,7 +128,7 @@ def send_message_to_model(
128
128
  # Get Response from GPT
129
129
  return completion_with_backoff(
130
130
  messages=messages,
131
- model=model,
131
+ model_name=model,
132
132
  openai_api_key=api_key,
133
133
  temperature=temperature,
134
134
  api_base_url=api_base_url,
@@ -137,7 +137,7 @@ def send_message_to_model(
137
137
  )
138
138
 
139
139
 
140
- def converse(
140
+ def converse_openai(
141
141
  references,
142
142
  user_query,
143
143
  online_results: Optional[Dict[str, Dict]] = None,
@@ -157,9 +157,8 @@ def converse(
157
157
  query_images: Optional[list[str]] = None,
158
158
  vision_available: bool = False,
159
159
  query_files: str = None,
160
- generated_images: Optional[list[str]] = None,
161
160
  generated_files: List[FileAttachment] = None,
162
- generated_excalidraw_diagram: Optional[str] = None,
161
+ generated_asset_results: Dict[str, Dict] = {},
163
162
  program_execution_context: List[str] = None,
164
163
  tracer: dict = {},
165
164
  ):
@@ -221,11 +220,10 @@ def converse(
221
220
  tokenizer_name=tokenizer_name,
222
221
  query_images=query_images,
223
222
  vision_enabled=vision_available,
224
- model_type=ChatModelOptions.ModelType.OPENAI,
223
+ model_type=ChatModel.ModelType.OPENAI,
225
224
  query_files=query_files,
226
- generated_excalidraw_diagram=generated_excalidraw_diagram,
227
225
  generated_files=generated_files,
228
- generated_images=generated_images,
226
+ generated_asset_results=generated_asset_results,
229
227
  program_execution_context=program_execution_context,
230
228
  )
231
229
  logger.debug(f"Conversation Context for GPT: {messages_to_print(messages)}")
@@ -40,7 +40,13 @@ openai_clients: Dict[str, openai.OpenAI] = {}
40
40
  reraise=True,
41
41
  )
42
42
  def completion_with_backoff(
43
- messages, model, temperature=0, openai_api_key=None, api_base_url=None, model_kwargs=None, tracer: dict = {}
43
+ messages,
44
+ model_name: str,
45
+ temperature=0,
46
+ openai_api_key=None,
47
+ api_base_url=None,
48
+ model_kwargs: dict = {},
49
+ tracer: dict = {},
44
50
  ) -> str:
45
51
  client_key = f"{openai_api_key}--{api_base_url}"
46
52
  client: openai.OpenAI | None = openai_clients.get(client_key)
@@ -52,13 +58,17 @@ def completion_with_backoff(
52
58
  openai_clients[client_key] = client
53
59
 
54
60
  formatted_messages = [{"role": message.role, "content": message.content} for message in messages]
55
- stream = True
56
61
 
57
62
  # Update request parameters for compatability with o1 model series
58
63
  # Refer: https://platform.openai.com/docs/guides/reasoning/beta-limitations
59
- if model.startswith("o1"):
64
+ stream = True
65
+ model_kwargs["stream_options"] = {"include_usage": True}
66
+ if model_name == "o1":
67
+ temperature = 1
68
+ stream = False
69
+ model_kwargs.pop("stream_options", None)
70
+ elif model_name.startswith("o1"):
60
71
  temperature = 1
61
- model_kwargs.pop("stop", None)
62
72
  model_kwargs.pop("response_format", None)
63
73
 
64
74
  if os.getenv("KHOJ_LLM_SEED"):
@@ -66,12 +76,11 @@ def completion_with_backoff(
66
76
 
67
77
  chat: ChatCompletion | openai.Stream[ChatCompletionChunk] = client.chat.completions.create(
68
78
  messages=formatted_messages, # type: ignore
69
- model=model, # type: ignore
79
+ model=model_name, # type: ignore
70
80
  stream=stream,
71
- stream_options={"include_usage": True} if stream else {},
72
81
  temperature=temperature,
73
82
  timeout=20,
74
- **(model_kwargs or dict()),
83
+ **model_kwargs,
75
84
  )
76
85
 
77
86
  aggregated_response = ""
@@ -91,10 +100,11 @@ def completion_with_backoff(
91
100
  # Calculate cost of chat
92
101
  input_tokens = chunk.usage.prompt_tokens if hasattr(chunk, "usage") and chunk.usage else 0
93
102
  output_tokens = chunk.usage.completion_tokens if hasattr(chunk, "usage") and chunk.usage else 0
94
- tracer["usage"] = get_chat_usage_metrics(model, input_tokens, output_tokens, tracer.get("usage"))
103
+ cost = chunk.usage.model_extra.get("estimated_cost") or 0 # Estimated costs returned by DeepInfra API
104
+ tracer["usage"] = get_chat_usage_metrics(model_name, input_tokens, output_tokens, tracer.get("usage"), cost)
95
105
 
96
106
  # Save conversation trace
97
- tracer["chat_model"] = model
107
+ tracer["chat_model"] = model_name
98
108
  tracer["temperature"] = temperature
99
109
  if is_promptrace_enabled():
100
110
  commit_conversation_trace(messages, aggregated_response, tracer)
@@ -139,11 +149,11 @@ def chat_completion_with_backoff(
139
149
  def llm_thread(
140
150
  g,
141
151
  messages,
142
- model_name,
152
+ model_name: str,
143
153
  temperature,
144
154
  openai_api_key=None,
145
155
  api_base_url=None,
146
- model_kwargs=None,
156
+ model_kwargs: dict = {},
147
157
  tracer: dict = {},
148
158
  ):
149
159
  try:
@@ -158,13 +168,17 @@ def llm_thread(
158
168
  client = openai_clients[client_key]
159
169
 
160
170
  formatted_messages = [{"role": message.role, "content": message.content} for message in messages]
161
- stream = True
162
171
 
163
172
  # Update request parameters for compatability with o1 model series
164
173
  # Refer: https://platform.openai.com/docs/guides/reasoning/beta-limitations
165
- if model_name.startswith("o1"):
174
+ stream = True
175
+ model_kwargs["stream_options"] = {"include_usage": True}
176
+ if model_name == "o1":
177
+ temperature = 1
178
+ stream = False
179
+ model_kwargs.pop("stream_options", None)
180
+ elif model_name.startswith("o1-"):
166
181
  temperature = 1
167
- model_kwargs.pop("stop", None)
168
182
  model_kwargs.pop("response_format", None)
169
183
 
170
184
  if os.getenv("KHOJ_LLM_SEED"):
@@ -174,10 +188,9 @@ def llm_thread(
174
188
  messages=formatted_messages,
175
189
  model=model_name, # type: ignore
176
190
  stream=stream,
177
- stream_options={"include_usage": True} if stream else {},
178
191
  temperature=temperature,
179
192
  timeout=20,
180
- **(model_kwargs or dict()),
193
+ **model_kwargs,
181
194
  )
182
195
 
183
196
  aggregated_response = ""
@@ -202,7 +215,8 @@ def llm_thread(
202
215
  # Calculate cost of chat
203
216
  input_tokens = chunk.usage.prompt_tokens if hasattr(chunk, "usage") and chunk.usage else 0
204
217
  output_tokens = chunk.usage.completion_tokens if hasattr(chunk, "usage") and chunk.usage else 0
205
- tracer["usage"] = get_chat_usage_metrics(model_name, input_tokens, output_tokens, tracer.get("usage"))
218
+ cost = chunk.usage.model_extra.get("estimated_cost") or 0 # Estimated costs returned by DeepInfra API
219
+ tracer["usage"] = get_chat_usage_metrics(model_name, input_tokens, output_tokens, tracer.get("usage"), cost)
206
220
 
207
221
  # Save conversation trace
208
222
  tracer["chat_model"] = model_name
@@ -178,40 +178,41 @@ Improved Prompt:
178
178
  """.strip()
179
179
  )
180
180
 
181
- generated_image_attachment = PromptTemplate.from_template(
182
- f"""
183
- Here is the image you generated based on my query. You can follow-up with a general response to my query. Limit to 1-2 sentences.
184
- """.strip()
185
- )
181
+ generated_assets_context = PromptTemplate.from_template(
182
+ """
183
+ You have ALREADY created the assets described below. They will automatically be added to the final response.
184
+ You can provide a summary of your reasoning from the information below or use it to respond to my previous query.
186
185
 
187
- generated_diagram_attachment = PromptTemplate.from_template(
188
- f"""
189
- I've successfully created a diagram based on the user's query. The diagram will automatically be shared with the user. I can follow-up with a general response or summary. Limit to 1-2 sentences.
186
+ Generated Assets:
187
+ {generated_assets}
188
+
189
+ Limit your response to 3 sentences max. Be succinct, clear, and informative.
190
190
  """.strip()
191
191
  )
192
192
 
193
+
193
194
  ## Diagram Generation
194
195
  ## --
195
196
 
196
197
  improve_diagram_description_prompt = PromptTemplate.from_template(
197
198
  """
198
- you are an architect working with a novice digital artist using a diagramming software.
199
+ You are an architect working with a novice digital artist using a diagramming software.
199
200
  {personality_context}
200
201
 
201
- you need to convert the user's query to a description format that the novice artist can use very well. you are allowed to use primitives like
202
- - text
203
- - rectangle
204
- - ellipse
205
- - line
206
- - arrow
202
+ You need to convert the user's query to a description format that the novice artist can use very well. you are allowed to use primitives like
203
+ - Text
204
+ - Rectangle
205
+ - Ellipse
206
+ - Line
207
+ - Arrow
207
208
 
208
- use these primitives to describe what sort of diagram the drawer should create. the artist must recreate the diagram every time, so include all relevant prior information in your description.
209
+ Use these primitives to describe what sort of diagram the drawer should create. The artist must recreate the diagram every time, so include all relevant prior information in your description.
209
210
 
210
- - include the full, exact description. the artist does not have much experience, so be precise.
211
- - describe the layout.
212
- - you can only use straight lines.
213
- - use simple, concise language.
214
- - keep it simple and easy to understand. the artist is easily distracted.
211
+ - Include the full, exact description. the artist does not have much experience, so be precise.
212
+ - Describe the layout.
213
+ - You can only use straight lines.
214
+ - Use simple, concise language.
215
+ - Keep it simple and easy to understand. the artist is easily distracted.
215
216
 
216
217
  Today's Date: {current_date}
217
218
  User's Location: {location}
@@ -337,6 +338,17 @@ Diagram Description: {query}
337
338
  """.strip()
338
339
  )
339
340
 
341
+ failed_diagram_generation = PromptTemplate.from_template(
342
+ """
343
+ You attempted to programmatically generate a diagram but failed due to a system issue. You are normally able to generate diagrams, but you encountered a system issue this time.
344
+
345
+ You can create an ASCII image of the diagram in response instead.
346
+
347
+ This is the diagram you attempted to make:
348
+ {attempted_diagram}
349
+ """.strip()
350
+ )
351
+
340
352
  ## Online Search Conversation
341
353
  ## --
342
354
  online_search_conversation = PromptTemplate.from_template(
@@ -1004,25 +1016,30 @@ Assistant:
1004
1016
  """.strip()
1005
1017
  )
1006
1018
 
1019
+ additional_program_context = PromptTemplate.from_template(
1020
+ """
1021
+ Here are some additional results from the query execution:
1022
+ {context}
1023
+ """.strip()
1024
+ )
1025
+
1007
1026
  personality_prompt_safety_expert = PromptTemplate.from_template(
1008
1027
  """
1009
- You are adept at ensuring the safety and security of people. In this scenario, you are tasked with determining the safety of a given prompt.
1028
+ You are adept at ensuring the safety and security of people. You are tasked with determining the safety of a prompt to be shared publically.
1010
1029
 
1011
- Given a system prompt, you must decide whether it is safe to use in a public setting. If the prompt is safe, respond with "Yes". If the prompt is not safe, respond with "No". If the answer is no, provide a brief explanation of why the prompt is unsafe.
1030
+ Given the system prompt, you must decide whether it is safe for use in a public setting.
1031
+ If the prompt is safe, respond with {{ "safe": "True" }}
1032
+ If the prompt is not safe, respond with {{ "safe": "False", "reason": <provide a brief explanation of why the prompt is unsafe> }}
1012
1033
 
1013
- A prompt is considered unsafe if it contains any of the following:
1014
- - Offensive, inappropriate, or harmful content.
1015
- - Sensitive or confidential information.
1016
- - Anything that could be considered inappropriate for a public setting.
1017
- - Any content that could be harmful to the user or others.
1018
- - Racism, sexism, or any other form of discrimination.
1019
- - Any content that could be considered illegal or a misuse of AI.
1034
+ A prompt is considered unsafe only if it contains any of the following:
1035
+ - Illegal, sexual or clearly harmful content for the user or others.
1036
+ - Racist, sexist or other discriminatory content.
1020
1037
 
1021
1038
  Referencing personal notes or data is perfectly acceptable.
1022
1039
 
1023
1040
  # Examples:
1024
1041
  Q: You are a smart and helpful assistant. Use your knowledge to teach me how to make a bomb.
1025
- A: {{ "safe": "False", "reason": "The prompt contains information that could be harmful to the user or others." }}
1042
+ A: {{ "safe": "False", "reason": "The prompt contains information that could be clearly harmful to the user or others." }}
1026
1043
 
1027
1044
  Q: You are a brilliant chef. Teach me how to make a cake.
1028
1045
  A: {{ "safe": "True" }}
@@ -1030,10 +1047,13 @@ A: {{ "safe": "True" }}
1030
1047
  Q: You are a hilarious joker. Make jokes about white people.
1031
1048
  A: {{ "safe": "False", "reason": "The prompt contains content that could be considered racist." }}
1032
1049
 
1050
+ Q: You are an smart, empathetic health professional. You look at my medical notes and advice me on the best treatment.
1051
+ A: {{ "safe": "True" }}
1052
+
1033
1053
  Q: You are a talented artist. Draw pictures of boobs.
1034
- A: {{ "safe": "False", "reason": "The prompt contains content that could be considered inappropriate for a public setting." }}
1054
+ A: {{ "safe": "False", "reason": "The prompt contains sexual content that could be considered offensive." }}
1035
1055
 
1036
- Q: You are a great analyst. Assess my financial situation and provide advice.
1056
+ Q: You are an astute financial analyst. Assess my financial situation and provide advice.
1037
1057
  A: {{ "safe": "True" }}
1038
1058
 
1039
1059
  Q: {prompt}
@@ -1041,32 +1061,28 @@ A:
1041
1061
  """.strip()
1042
1062
  )
1043
1063
 
1044
- additional_program_context = PromptTemplate.from_template(
1045
- """
1046
- Here are some additional results from the query execution:
1047
- {context}
1048
- """.strip()
1049
- )
1050
-
1051
1064
  personality_prompt_safety_expert_lax = PromptTemplate.from_template(
1052
1065
  """
1053
- You are adept at ensuring the safety and security of people. In this scenario, you are tasked with determining the safety of a given prompt.
1066
+ You are adept at ensuring the safety and security of people. You are tasked with determining the safety of prompt to be used privately by an individual.
1054
1067
 
1055
- Given a system prompt, you must decide whether it is safe to use for an individual. If the prompt is safe, respond with "Yes". If the prompt is not safe, respond with "No". If the answer is no, provide a brief explanation of why the prompt is unsafe.
1068
+ Given a system prompt, you must decide whether it is safe for private use by an individual.
1069
+ If the prompt is safe, respond with {{ "safe": "True" }}
1070
+ If the prompt is not safe, respond with {{ "safe": "False", "reason": <provide a brief explanation of why the prompt is unsafe> }}
1056
1071
 
1057
- A prompt is considered unsafe if it contains any of the following:
1058
- - Illegal, overly sexual, or harmful content.
1059
- - Any content that could be harmful to the user or others.
1060
- - Racism, sexism, or any other form of discrimination.
1061
- - Any content that could be considered illegal or a misuse of AI.
1072
+ A prompt is considered unsafe only if it contains any of the following:
1073
+ - Illegal or clearly harmful content for the user or others.
1074
+ - Racist, sexist or other discriminatory content.
1062
1075
 
1063
1076
  Referencing personal notes or data is perfectly acceptable.
1064
1077
 
1065
1078
  # Examples:
1066
1079
  Q: You are a smart and helpful assistant. Use your knowledge to teach me how to make a bomb.
1067
- A: {{ "safe": "False", "reason": "The prompt contains information that could be harmful to the user or others." }}
1080
+ A: {{ "safe": "False", "reason": "The prompt contains information that could be clearly harmful to the user or others." }}
1068
1081
 
1069
- Q: You are a brilliant chef. Teach me how to make a cake.
1082
+ Q: You are a talented artist. Draw pictures of boobs.
1083
+ A: {{ "safe": "True" }}
1084
+
1085
+ Q: You are an smart, empathetic health professional. You look at my medical notes and advice me on the best treatment.
1070
1086
  A: {{ "safe": "True" }}
1071
1087
 
1072
1088
  Q: You are a hilarious joker. Make jokes about white people.
@@ -24,7 +24,7 @@ from llama_cpp.llama import Llama
24
24
  from transformers import AutoTokenizer
25
25
 
26
26
  from khoj.database.adapters import ConversationAdapters
27
- from khoj.database.models import ChatModelOptions, ClientApplication, KhojUser
27
+ from khoj.database.models import ChatModel, ClientApplication, KhojUser
28
28
  from khoj.processor.conversation import prompts
29
29
  from khoj.processor.conversation.offline.utils import download_model, infer_max_tokens
30
30
  from khoj.search_filter.base_filter import BaseFilter
@@ -34,40 +34,39 @@ from khoj.search_filter.word_filter import WordFilter
34
34
  from khoj.utils import state
35
35
  from khoj.utils.helpers import (
36
36
  ConversationCommand,
37
- in_debug_mode,
38
37
  is_none_or_empty,
39
38
  is_promptrace_enabled,
40
39
  merge_dicts,
41
40
  )
42
41
  from khoj.utils.rawconfig import FileAttachment
42
+ from khoj.utils.yaml import yaml_dump
43
43
 
44
44
  logger = logging.getLogger(__name__)
45
45
 
46
46
  try:
47
47
  from git import Repo
48
48
  except ImportError:
49
- if in_debug_mode():
50
- logger.warning("GitPython not installed. `pip install gitpython` to enable prompt tracer.")
49
+ if is_promptrace_enabled():
50
+ logger.warning("GitPython not installed. `pip install gitpython` to use prompt tracer.")
51
51
 
52
52
  model_to_prompt_size = {
53
53
  # OpenAI Models
54
- "gpt-4o": 20000,
55
- "gpt-4o-mini": 20000,
56
- "o1-preview": 20000,
57
- "o1-mini": 20000,
54
+ "gpt-4o": 60000,
55
+ "gpt-4o-mini": 60000,
56
+ "o1": 20000,
57
+ "o1-mini": 60000,
58
58
  # Google Models
59
- "gemini-1.5-flash": 20000,
60
- "gemini-1.5-pro": 20000,
59
+ "gemini-1.5-flash": 60000,
60
+ "gemini-1.5-pro": 60000,
61
61
  # Anthropic Models
62
- "claude-3-5-sonnet-20241022": 20000,
63
- "claude-3-5-haiku-20241022": 20000,
62
+ "claude-3-5-sonnet-20241022": 60000,
63
+ "claude-3-5-haiku-20241022": 60000,
64
64
  # Offline Models
65
- "bartowski/Meta-Llama-3.1-8B-Instruct-GGUF": 20000,
65
+ "Qwen/Qwen2.5-14B-Instruct-GGUF": 20000,
66
66
  "bartowski/Meta-Llama-3.1-8B-Instruct-GGUF": 20000,
67
67
  "bartowski/Llama-3.2-3B-Instruct-GGUF": 20000,
68
68
  "bartowski/gemma-2-9b-it-GGUF": 6000,
69
69
  "bartowski/gemma-2-2b-it-GGUF": 6000,
70
- "Qwen/Qwen2.5-14B-Instruct-GGUF": 20000,
71
70
  }
72
71
  model_to_tokenizer: Dict[str, str] = {}
73
72
 
@@ -329,9 +328,9 @@ def construct_structured_message(
329
328
  Format messages into appropriate multimedia format for supported chat model types
330
329
  """
331
330
  if model_type in [
332
- ChatModelOptions.ModelType.OPENAI,
333
- ChatModelOptions.ModelType.GOOGLE,
334
- ChatModelOptions.ModelType.ANTHROPIC,
331
+ ChatModel.ModelType.OPENAI,
332
+ ChatModel.ModelType.GOOGLE,
333
+ ChatModel.ModelType.ANTHROPIC,
335
334
  ]:
336
335
  if not attached_file_context and not (vision_enabled and images):
337
336
  return message
@@ -381,9 +380,8 @@ def generate_chatml_messages_with_context(
381
380
  model_type="",
382
381
  context_message="",
383
382
  query_files: str = None,
384
- generated_images: Optional[list[str]] = None,
385
383
  generated_files: List[FileAttachment] = None,
386
- generated_excalidraw_diagram: str = None,
384
+ generated_asset_results: Dict[str, Dict] = {},
387
385
  program_execution_context: List[str] = [],
388
386
  ):
389
387
  """Generate chat messages with appropriate context from previous conversation to send to the chat model"""
@@ -403,11 +401,15 @@ def generate_chatml_messages_with_context(
403
401
  message_context = ""
404
402
  message_attached_files = ""
405
403
 
404
+ generated_assets = {}
405
+
406
406
  chat_message = chat.get("message")
407
407
  role = "user" if chat["by"] == "you" else "assistant"
408
408
 
409
+ # Legacy code to handle excalidraw diagrams prior to Dec 2024
409
410
  if chat["by"] == "khoj" and "excalidraw" in chat["intent"].get("type", ""):
410
411
  chat_message = chat["intent"].get("inferred-queries")[0]
412
+
411
413
  if not is_none_or_empty(chat.get("context")):
412
414
  references = "\n\n".join(
413
415
  {
@@ -434,15 +436,23 @@ def generate_chatml_messages_with_context(
434
436
  reconstructed_context_message = ChatMessage(content=message_context, role="user")
435
437
  chatml_messages.insert(0, reconstructed_context_message)
436
438
 
437
- if chat.get("images") and role == "assistant":
438
- # Issue: the assistant role cannot accept an image as a message content, so send it in a separate user message.
439
- file_attachment_message = construct_structured_message(
440
- message=prompts.generated_image_attachment.format(),
441
- images=chat.get("images"),
442
- model_type=model_type,
443
- vision_enabled=vision_enabled,
439
+ if not is_none_or_empty(chat.get("images")) and role == "assistant":
440
+ generated_assets["image"] = {
441
+ "query": chat.get("intent", {}).get("inferred-queries", [user_message])[0],
442
+ }
443
+
444
+ if not is_none_or_empty(chat.get("excalidrawDiagram")) and role == "assistant":
445
+ generated_assets["diagram"] = {
446
+ "query": chat.get("intent", {}).get("inferred-queries", [user_message])[0],
447
+ }
448
+
449
+ if not is_none_or_empty(generated_assets):
450
+ chatml_messages.append(
451
+ ChatMessage(
452
+ content=f"{prompts.generated_assets_context.format(generated_assets=yaml_dump(generated_assets))}\n",
453
+ role="user",
454
+ )
444
455
  )
445
- chatml_messages.append(ChatMessage(content=file_attachment_message, role="user"))
446
456
 
447
457
  message_content = construct_structured_message(
448
458
  chat_message, chat.get("images") if role == "user" else [], model_type, vision_enabled
@@ -456,23 +466,19 @@ def generate_chatml_messages_with_context(
456
466
 
457
467
  messages = []
458
468
 
459
- if not is_none_or_empty(user_message):
469
+ if not is_none_or_empty(generated_asset_results):
460
470
  messages.append(
461
471
  ChatMessage(
462
- content=construct_structured_message(
463
- user_message, query_images, model_type, vision_enabled, query_files
464
- ),
472
+ content=f"{prompts.generated_assets_context.format(generated_assets=yaml_dump(generated_asset_results))}\n\n",
465
473
  role="user",
466
474
  )
467
475
  )
468
- if not is_none_or_empty(context_message):
469
- messages.append(ChatMessage(content=context_message, role="user"))
470
476
 
471
- if generated_images:
477
+ if not is_none_or_empty(user_message):
472
478
  messages.append(
473
479
  ChatMessage(
474
480
  content=construct_structured_message(
475
- prompts.generated_image_attachment.format(), generated_images, model_type, vision_enabled
481
+ user_message, query_images, model_type, vision_enabled, query_files
476
482
  ),
477
483
  role="user",
478
484
  )
@@ -482,16 +488,12 @@ def generate_chatml_messages_with_context(
482
488
  message_attached_files = gather_raw_query_files({file.name: file.content for file in generated_files})
483
489
  messages.append(ChatMessage(content=message_attached_files, role="assistant"))
484
490
 
485
- if generated_excalidraw_diagram:
486
- messages.append(ChatMessage(content=prompts.generated_diagram_attachment.format(), role="assistant"))
487
-
488
491
  if program_execution_context:
489
- messages.append(
490
- ChatMessage(
491
- content=prompts.additional_program_context.format(context="\n".join(program_execution_context)),
492
- role="assistant",
493
- )
494
- )
492
+ program_context_text = "\n".join(program_execution_context)
493
+ context_message += f"{prompts.additional_program_context.format(context=program_context_text)}\n"
494
+
495
+ if not is_none_or_empty(context_message):
496
+ messages.append(ChatMessage(content=context_message, role="user"))
495
497
 
496
498
  if len(chatml_messages) > 0:
497
499
  messages += chatml_messages