khoj 1.30.2__py3-none-any.whl → 1.30.2.dev4__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 (73) hide show
  1. khoj/database/adapters/__init__.py +1 -1
  2. khoj/database/admin.py +0 -39
  3. khoj/interface/compiled/404/index.html +1 -1
  4. khoj/interface/compiled/_next/static/chunks/1210.132a7e1910006bbb.js +1 -0
  5. khoj/interface/compiled/_next/static/chunks/1603-859ddcf58f3ca639.js +1 -0
  6. khoj/interface/compiled/_next/static/chunks/1970-e1935a1d0930a7c5.js +1 -0
  7. khoj/interface/compiled/_next/static/chunks/216-b2e4344315b88832.js +1 -0
  8. khoj/interface/compiled/_next/static/chunks/3124-e8410bbd01f6f188.js +1 -0
  9. khoj/interface/compiled/_next/static/chunks/{3717-b46079dbe9f55694.js → 6297-55f82537bb7068dd.js} +1 -1
  10. khoj/interface/compiled/_next/static/chunks/{2646-92ba433951d02d52.js → 6901-e3dc0d315e3f6033.js} +2 -2
  11. khoj/interface/compiled/_next/static/chunks/796-36ee2d6829448c6d.js +3 -0
  12. khoj/interface/compiled/_next/static/chunks/9417-06236cd650f1abcd.js +1 -0
  13. khoj/interface/compiled/_next/static/chunks/app/agents/{page-f5c0801b27a8e95e.js → page-5f6e0dacc34e33ad.js} +1 -1
  14. khoj/interface/compiled/_next/static/chunks/app/automations/{page-0393501fad5f8e3d.js → page-60bc7454bc3ea881.js} +1 -1
  15. khoj/interface/compiled/_next/static/chunks/app/chat/{page-f2539e3197d03c0d.js → page-ac366c9111374312.js} +1 -1
  16. khoj/interface/compiled/_next/static/chunks/app/{page-5cc56a8db5d21b38.js → page-358154a4436ef316.js} +1 -1
  17. khoj/interface/compiled/_next/static/chunks/app/search/{page-e8b578d155550386.js → page-64ea1717528979af.js} +1 -1
  18. khoj/interface/compiled/_next/static/chunks/app/settings/page-612294fe08f983d8.js +1 -0
  19. khoj/interface/compiled/_next/static/chunks/app/share/chat/{page-635635e4fb39fe29.js → page-47641b3691fb0856.js} +1 -1
  20. khoj/interface/compiled/_next/static/chunks/{webpack-5dbccc5145b80b64.js → webpack-c43e527c9ca9f9f9.js} +1 -1
  21. khoj/interface/compiled/_next/static/css/1a4038cc4acc8ee4.css +25 -0
  22. khoj/interface/compiled/_next/static/css/80bd6301fc657983.css +1 -0
  23. khoj/interface/compiled/_next/static/css/9d45de78fba367c1.css +1 -0
  24. khoj/interface/compiled/agents/index.html +1 -1
  25. khoj/interface/compiled/agents/index.txt +2 -2
  26. khoj/interface/compiled/automations/index.html +1 -1
  27. khoj/interface/compiled/automations/index.txt +2 -2
  28. khoj/interface/compiled/chat/index.html +1 -1
  29. khoj/interface/compiled/chat/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/main.py +0 -4
  39. khoj/processor/conversation/anthropic/anthropic_chat.py +2 -8
  40. khoj/processor/conversation/anthropic/utils.py +3 -22
  41. khoj/processor/conversation/google/gemini_chat.py +2 -8
  42. khoj/processor/conversation/google/utils.py +3 -19
  43. khoj/processor/conversation/offline/chat_model.py +4 -12
  44. khoj/processor/conversation/openai/gpt.py +2 -9
  45. khoj/processor/conversation/openai/utils.py +21 -39
  46. khoj/processor/conversation/prompts.py +21 -40
  47. khoj/processor/conversation/utils.py +9 -15
  48. khoj/processor/tools/run_code.py +25 -1
  49. khoj/routers/api_chat.py +16 -41
  50. khoj/routers/api_subscription.py +2 -9
  51. khoj/routers/auth.py +2 -2
  52. khoj/routers/helpers.py +4 -19
  53. khoj/routers/research.py +1 -2
  54. khoj/utils/cli.py +0 -2
  55. khoj/utils/constants.py +0 -17
  56. khoj/utils/helpers.py +1 -55
  57. khoj/utils/state.py +0 -1
  58. {khoj-1.30.2.dist-info → khoj-1.30.2.dev4.dist-info}/METADATA +4 -9
  59. {khoj-1.30.2.dist-info → khoj-1.30.2.dev4.dist-info}/RECORD +64 -62
  60. khoj/interface/compiled/_next/static/chunks/1210.ef7a0f9a7e43da1d.js +0 -1
  61. khoj/interface/compiled/_next/static/chunks/1603-ba5f9f05e92c8412.js +0 -1
  62. khoj/interface/compiled/_next/static/chunks/1970-1b63ac1497b03a10.js +0 -1
  63. khoj/interface/compiled/_next/static/chunks/3463-081c031e873b7966.js +0 -3
  64. khoj/interface/compiled/_next/static/chunks/4752-554a3db270186ce3.js +0 -1
  65. khoj/interface/compiled/_next/static/chunks/app/settings/page-b6c835050c970be7.js +0 -1
  66. khoj/interface/compiled/_next/static/css/592ca99f5122e75a.css +0 -1
  67. khoj/interface/compiled/_next/static/css/5d8d85d3f2e95bae.css +0 -25
  68. khoj/interface/compiled/_next/static/css/63e106a52a0ec4ca.css +0 -1
  69. /khoj/interface/compiled/_next/static/{UR4enQiSbkZKb3SDFX2tx → EVMfEeflKlppAzl2to9RY}/_buildManifest.js +0 -0
  70. /khoj/interface/compiled/_next/static/{UR4enQiSbkZKb3SDFX2tx → EVMfEeflKlppAzl2to9RY}/_ssgManifest.js +0 -0
  71. {khoj-1.30.2.dist-info → khoj-1.30.2.dev4.dist-info}/WHEEL +0 -0
  72. {khoj-1.30.2.dist-info → khoj-1.30.2.dev4.dist-info}/entry_points.txt +0 -0
  73. {khoj-1.30.2.dist-info → khoj-1.30.2.dev4.dist-info}/licenses/LICENSE +0 -0
@@ -183,23 +183,20 @@ Improved Prompt:
183
183
 
184
184
  improve_diagram_description_prompt = PromptTemplate.from_template(
185
185
  """
186
- you are an architect working with a novice digital artist using a diagramming software.
186
+ you are an architect working with a novice artist using a diagramming tool.
187
187
  {personality_context}
188
188
 
189
189
  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
190
190
  - text
191
191
  - rectangle
192
+ - diamond
192
193
  - ellipse
193
194
  - line
194
195
  - arrow
195
196
 
196
197
  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.
197
198
 
198
- - include the full, exact description. the artist does not have much experience, so be precise.
199
- - describe the layout.
200
- - you can only use straight lines.
201
- - use simple, concise language.
202
- - keep it simple and easy to understand. the artist is easily distracted.
199
+ use simple, concise language.
203
200
 
204
201
  Today's Date: {current_date}
205
202
  User's Location: {location}
@@ -221,23 +218,19 @@ Query: {query}
221
218
 
222
219
  excalidraw_diagram_generation_prompt = PromptTemplate.from_template(
223
220
  """
224
- You are a program manager with the ability to describe diagrams to compose in professional, fine detail. You LOVE getting into the details and making tedious labels, lines, and shapes look beautiful. You make everything look perfect.
221
+ You are a program manager with the ability to describe diagrams to compose in professional, fine detail.
225
222
  {personality_context}
226
223
 
227
- You need to create a declarative description of the diagram and relevant components, using this base schema.
228
- - `label`: specify the text to be rendered in the respective elements.
229
- - Always use light colors for the `backgroundColor` property, like white, or light blue, green, red
230
- - **ALWAYS Required properties for ALL elements**: `type`, `x`, `y`, `id`.
231
- - Be very generous with spacing and composition. Use ample space between elements.
224
+ You need to create a declarative description of the diagram and relevant components, using this base schema. Use the `label` property to specify the text to be rendered in the respective elements. Always use light colors for the `backgroundColor` property, like white, or light blue, green, red. "type", "x", "y", "id", are required properties for all elements.
232
225
 
233
226
  {{
234
227
  type: string,
235
228
  x: number,
236
229
  y: number,
237
- width: number,
238
- height: number,
239
230
  strokeColor: string,
240
231
  backgroundColor: string,
232
+ width: number,
233
+ height: number,
241
234
  id: string,
242
235
  label: {{
243
236
  text: string,
@@ -247,30 +240,28 @@ You need to create a declarative description of the diagram and relevant compone
247
240
  Valid types:
248
241
  - text
249
242
  - rectangle
243
+ - diamond
250
244
  - ellipse
251
245
  - line
252
246
  - arrow
253
247
 
254
- For arrows and lines,
255
- - `points`: specify the start and end points of the arrow
256
- - **ALWAYS Required properties for ALL elements**: `type`, `x`, `y`, `id`.
257
- - `start` and `end` properties: connect the linear elements to other elements. The start and end point can either be the ID to map to an existing object, or the `type` and `text` to create a new object. Mapping to an existing object is useful if you want to connect it to multiple objects. Lines and arrows can only start and end at rectangle, text, or ellipse elements. Even if you're using the `start` and `end` properties, you still need to specify the `x` and `y` properties for the start and end points.
248
+ For arrows and lines, you can use the `points` property to specify the start and end points of the arrow. You may also use the `label` property to specify the text to be rendered. You may use the `start` and `end` properties to connect the linear elements to other elements. The start and end point can either be the ID to map to an existing object, or the `type` to create a new object. Mapping to an existing object is useful if you want to connect it to multiple objects. Lines and arrows can only start and end at rectangle, text, diamond, or ellipse elements.
258
249
 
259
250
  {{
260
251
  type: "arrow",
261
252
  id: string,
262
253
  x: number,
263
254
  y: number,
255
+ width: number,
256
+ height: number,
264
257
  strokeColor: string,
265
258
  start: {{
266
259
  id: string,
267
260
  type: string,
268
- text: string,
269
261
  }},
270
262
  end: {{
271
263
  id: string,
272
264
  type: string,
273
- text: string,
274
265
  }},
275
266
  label: {{
276
267
  text: string,
@@ -281,11 +272,7 @@ For arrows and lines,
281
272
  ]
282
273
  }}
283
274
 
284
- For text,
285
- - `text`: specify the text to be rendered
286
- - **ALWAYS Required properties for ALL elements**: `type`, `x`, `y`, `id`.
287
- - `fontSize`: optional property to specify the font size of the text
288
- - Use this element only for titles, subtitles, and overviews. For labels, use the `label` property in the respective elements.
275
+ For text, you must use the `text` property to specify the text to be rendered. You may also use `fontSize` property to specify the font size of the text. Only use the `text` element for titles, subtitles, and overviews. For labels, use the `label` property in the respective elements.
289
276
 
290
277
  {{
291
278
  type: "text",
@@ -300,25 +287,19 @@ Here's an example of a valid diagram:
300
287
 
301
288
  Design Description: Create a diagram describing a circular development process with 3 stages: design, implementation and feedback. The design stage is connected to the implementation stage and the implementation stage is connected to the feedback stage and the feedback stage is connected to the design stage. Each stage should be labeled with the stage name.
302
289
 
303
- Example Response:
304
- ```json
305
- {{
306
- "scratchpad": "The diagram represents a circular development process with 3 stages: design, implementation and feedback. Each stage is connected to the next stage using an arrow, forming a circular process.",
307
- "elements": [
308
- {{"type":"text","x":-150,"y":50,"id":"title_text","text":"Circular Development Process","fontSize":24}},
309
- {{"type":"ellipse","x":-169,"y":113,"id":"design_ellipse", "label": {{"text": "Design"}}}},
310
- {{"type":"ellipse","x":62,"y":394,"id":"implement_ellipse", "label": {{"text": "Implement"}}}},
311
- {{"type":"ellipse","x":-348,"y":430,"id":"feedback_ellipse", "label": {{"text": "Feedback"}}}},
290
+ Response:
291
+
292
+ [
293
+ {{"type":"text","x":-150,"y":50,"width":300,"height":40,"id":"title_text","text":"Circular Development Process","fontSize":24}},
294
+ {{"type":"ellipse","x":-169,"y":113,"width":188,"height":202,"id":"design_ellipse", "label": {{"text": "Design"}}}},
295
+ {{"type":"ellipse","x":62,"y":394,"width":186,"height":188,"id":"implement_ellipse", "label": {{"text": "Implement"}}}},
296
+ {{"type":"ellipse","x":-348,"y":430,"width":184,"height":170,"id":"feedback_ellipse", "label": {{"text": "Feedback"}}}},
312
297
  {{"type":"arrow","x":21,"y":273,"id":"design_to_implement_arrow","points":[[0,0],[86,105]],"start":{{"id":"design_ellipse"}}, "end":{{"id":"implement_ellipse"}}}},
313
298
  {{"type":"arrow","x":50,"y":519,"id":"implement_to_feedback_arrow","points":[[0,0],[-198,-6]],"start":{{"id":"implement_ellipse"}}, "end":{{"id":"feedback_ellipse"}}}},
314
299
  {{"type":"arrow","x":-228,"y":417,"id":"feedback_to_design_arrow","points":[[0,0],[85,-123]],"start":{{"id":"feedback_ellipse"}}, "end":{{"id":"design_ellipse"}}}},
315
- ]
316
- }}
317
- ```
318
-
319
- Think about spacing and composition. Use ample space between elements. Double the amount of space you think you need. Create a detailed diagram from the provided context and user prompt below.
300
+ ]
320
301
 
321
- Return a valid JSON object, where the drawing is in `elements` and your thought process is in `scratchpad`. If you can't make the whole diagram in one response, you can split it into multiple responses. If you need to simplify for brevity, simply do so in the `scratchpad` field. DO NOT add additional info in the `elements` field.
302
+ Create a detailed diagram from the provided context and user prompt below. Return a valid JSON object:
322
303
 
323
304
  Diagram Description: {query}
324
305
 
@@ -5,6 +5,7 @@ import math
5
5
  import mimetypes
6
6
  import os
7
7
  import queue
8
+ import re
8
9
  import uuid
9
10
  from dataclasses import dataclass
10
11
  from datetime import datetime
@@ -34,7 +35,6 @@ from khoj.utils.helpers import (
34
35
  ConversationCommand,
35
36
  in_debug_mode,
36
37
  is_none_or_empty,
37
- is_promptrace_enabled,
38
38
  merge_dicts,
39
39
  )
40
40
  from khoj.utils.rawconfig import FileAttachment
@@ -57,7 +57,7 @@ model_to_prompt_size = {
57
57
  "gemini-1.5-flash": 20000,
58
58
  "gemini-1.5-pro": 20000,
59
59
  # Anthropic Models
60
- "claude-3-5-sonnet-20241022": 20000,
60
+ "claude-3-5-sonnet-20240620": 20000,
61
61
  "claude-3-5-haiku-20241022": 20000,
62
62
  # Offline Models
63
63
  "bartowski/Meta-Llama-3.1-8B-Instruct-GGUF": 20000,
@@ -213,8 +213,6 @@ class ChatEvent(Enum):
213
213
  REFERENCES = "references"
214
214
  STATUS = "status"
215
215
  METADATA = "metadata"
216
- USAGE = "usage"
217
- END_RESPONSE = "end_response"
218
216
 
219
217
 
220
218
  def message_to_log(
@@ -293,7 +291,7 @@ def save_to_conversation_log(
293
291
  user_message=q,
294
292
  )
295
293
 
296
- if is_promptrace_enabled():
294
+ if in_debug_mode() or state.verbose > 1:
297
295
  merge_message_into_conversation_trace(q, chat_response, tracer)
298
296
 
299
297
  logger.info(
@@ -580,7 +578,7 @@ def commit_conversation_trace(
580
578
  response: str | list[dict],
581
579
  tracer: dict,
582
580
  system_message: str | list[dict] = "",
583
- repo_path: str = None,
581
+ repo_path: str = "/tmp/promptrace",
584
582
  ) -> str:
585
583
  """
586
584
  Save trace of conversation step using git. Useful to visualize, compare and debug traces.
@@ -591,11 +589,6 @@ def commit_conversation_trace(
591
589
  except ImportError:
592
590
  return None
593
591
 
594
- # Infer repository path from environment variable or provided path
595
- repo_path = repo_path if not is_none_or_empty(repo_path) else os.getenv("PROMPTRACE_DIR")
596
- if not repo_path:
597
- return None
598
-
599
592
  # Serialize session, system message and response to yaml
600
593
  system_message_yaml = json.dumps(system_message, ensure_ascii=False, sort_keys=False)
601
594
  response_yaml = json.dumps(response, ensure_ascii=False, sort_keys=False)
@@ -608,6 +601,9 @@ def commit_conversation_trace(
608
601
  # Extract chat metadata for session
609
602
  uid, cid, mid = tracer.get("uid", "main"), tracer.get("cid", "main"), tracer.get("mid")
610
603
 
604
+ # Infer repository path from environment variable or provided path
605
+ repo_path = os.getenv("PROMPTRACE_DIR", repo_path)
606
+
611
607
  try:
612
608
  # Prepare git repository
613
609
  os.makedirs(repo_path, exist_ok=True)
@@ -687,7 +683,7 @@ Metadata
687
683
  return None
688
684
 
689
685
 
690
- def merge_message_into_conversation_trace(query: str, response: str, tracer: dict, repo_path=None) -> bool:
686
+ def merge_message_into_conversation_trace(query: str, response: str, tracer: dict, repo_path="/tmp/promptrace") -> bool:
691
687
  """
692
688
  Merge the message branch into its parent conversation branch.
693
689
 
@@ -710,9 +706,7 @@ def merge_message_into_conversation_trace(query: str, response: str, tracer: dic
710
706
  conv_branch = f"c_{tracer['cid']}"
711
707
 
712
708
  # Infer repository path from environment variable or provided path
713
- repo_path = repo_path if not is_none_or_empty(repo_path) else os.getenv("PROMPTRACE_DIR")
714
- if not repo_path:
715
- return None
709
+ repo_path = os.getenv("PROMPTRACE_DIR", repo_path)
716
710
  repo = Repo(repo_path)
717
711
 
718
712
  # Checkout conversation branch
@@ -1,4 +1,5 @@
1
1
  import base64
2
+ import copy
2
3
  import datetime
3
4
  import json
4
5
  import logging
@@ -19,7 +20,7 @@ from khoj.processor.conversation.utils import (
19
20
  construct_chat_history,
20
21
  )
21
22
  from khoj.routers.helpers import send_message_to_model_wrapper
22
- from khoj.utils.helpers import is_none_or_empty, timer, truncate_code_context
23
+ from khoj.utils.helpers import is_none_or_empty, timer
23
24
  from khoj.utils.rawconfig import LocationData
24
25
 
25
26
  logger = logging.getLogger(__name__)
@@ -179,3 +180,26 @@ async def execute_sandboxed_python(code: str, input_data: list[dict], sandbox_ur
179
180
  "std_err": f"Failed to execute code with {response.status}",
180
181
  "output_files": [],
181
182
  }
183
+
184
+
185
+ def truncate_code_context(original_code_results: dict[str, Any], max_chars=10000) -> dict[str, Any]:
186
+ """
187
+ Truncate large output files and drop image file data from code results.
188
+ """
189
+ # Create a deep copy of the code results to avoid modifying the original data
190
+ code_results = copy.deepcopy(original_code_results)
191
+ for code_result in code_results.values():
192
+ for idx, output_file in enumerate(code_result["results"]["output_files"]):
193
+ # Drop image files from code results
194
+ if Path(output_file["filename"]).suffix in {".png", ".jpg", ".jpeg", ".webp"}:
195
+ code_result["results"]["output_files"][idx] = {
196
+ "filename": output_file["filename"],
197
+ "b64_data": "[placeholder for generated image data for brevity]",
198
+ }
199
+ # Truncate large output files
200
+ elif len(output_file["b64_data"]) > max_chars:
201
+ code_result["results"]["output_files"][idx] = {
202
+ "filename": output_file["filename"],
203
+ "b64_data": output_file["b64_data"][:max_chars] + "...",
204
+ }
205
+ return code_results
khoj/routers/api_chat.py CHANGED
@@ -432,15 +432,7 @@ def chat_sessions(
432
432
  conversations = conversations[:8]
433
433
 
434
434
  sessions = conversations.values_list(
435
- "id",
436
- "slug",
437
- "title",
438
- "agent__slug",
439
- "agent__name",
440
- "created_at",
441
- "updated_at",
442
- "agent__style_icon",
443
- "agent__style_color",
435
+ "id", "slug", "title", "agent__slug", "agent__name", "created_at", "updated_at"
444
436
  )
445
437
 
446
438
  session_values = [
@@ -450,8 +442,6 @@ def chat_sessions(
450
442
  "agent_name": session[4],
451
443
  "created": session[5].strftime("%Y-%m-%d %H:%M:%S"),
452
444
  "updated": session[6].strftime("%Y-%m-%d %H:%M:%S"),
453
- "agent_icon": session[7],
454
- "agent_color": session[8],
455
445
  }
456
446
  for session in sessions
457
447
  ]
@@ -677,37 +667,27 @@ async def chat(
677
667
  finally:
678
668
  yield event_delimiter
679
669
 
680
- async def send_llm_response(response: str, usage: dict = None):
681
- # Send Chat Response
670
+ async def send_llm_response(response: str):
682
671
  async for result in send_event(ChatEvent.START_LLM_RESPONSE, ""):
683
672
  yield result
684
673
  async for result in send_event(ChatEvent.MESSAGE, response):
685
674
  yield result
686
675
  async for result in send_event(ChatEvent.END_LLM_RESPONSE, ""):
687
676
  yield result
688
- # Send Usage Metadata once llm interactions are complete
689
- if usage:
690
- async for event in send_event(ChatEvent.USAGE, usage):
691
- yield event
692
- async for result in send_event(ChatEvent.END_RESPONSE, ""):
693
- yield result
694
677
 
695
678
  def collect_telemetry():
696
679
  # Gather chat response telemetry
697
680
  nonlocal chat_metadata
698
681
  latency = time.perf_counter() - start_time
699
682
  cmd_set = set([cmd.value for cmd in conversation_commands])
700
- cost = (tracer.get("usage", {}) or {}).get("cost", 0)
701
683
  chat_metadata = chat_metadata or {}
702
684
  chat_metadata["conversation_command"] = cmd_set
703
- chat_metadata["agent"] = conversation.agent.slug if conversation and conversation.agent else None
685
+ chat_metadata["agent"] = conversation.agent.slug if conversation.agent else None
704
686
  chat_metadata["latency"] = f"{latency:.3f}"
705
687
  chat_metadata["ttft_latency"] = f"{ttft:.3f}"
706
- chat_metadata["usage"] = tracer.get("usage")
707
688
 
708
689
  logger.info(f"Chat response time to first token: {ttft:.3f} seconds")
709
690
  logger.info(f"Chat response total time: {latency:.3f} seconds")
710
- logger.info(f"Chat response cost: ${cost:.5f}")
711
691
  update_telemetry_state(
712
692
  request=request,
713
693
  telemetry_type="api",
@@ -719,7 +699,7 @@ async def chat(
719
699
  )
720
700
 
721
701
  if is_query_empty(q):
722
- async for result in send_llm_response("Please ask your query to get started.", tracer.get("usage")):
702
+ async for result in send_llm_response("Please ask your query to get started."):
723
703
  yield result
724
704
  return
725
705
 
@@ -733,7 +713,7 @@ async def chat(
733
713
  create_new=body.create_new,
734
714
  )
735
715
  if not conversation:
736
- async for result in send_llm_response(f"Conversation {conversation_id} not found", tracer.get("usage")):
716
+ async for result in send_llm_response(f"Conversation {conversation_id} not found"):
737
717
  yield result
738
718
  return
739
719
  conversation_id = conversation.id
@@ -797,7 +777,7 @@ async def chat(
797
777
  await conversation_command_rate_limiter.update_and_check_if_valid(request, cmd)
798
778
  q = q.replace(f"/{cmd.value}", "").strip()
799
779
  except HTTPException as e:
800
- async for result in send_llm_response(str(e.detail), tracer.get("usage")):
780
+ async for result in send_llm_response(str(e.detail)):
801
781
  yield result
802
782
  return
803
783
 
@@ -854,7 +834,7 @@ async def chat(
854
834
  agent_has_entries = await EntryAdapters.aagent_has_entries(agent)
855
835
  if len(file_filters) == 0 and not agent_has_entries:
856
836
  response_log = "No files selected for summarization. Please add files using the section on the left."
857
- async for result in send_llm_response(response_log, tracer.get("usage")):
837
+ async for result in send_llm_response(response_log):
858
838
  yield result
859
839
  else:
860
840
  async for response in generate_summary_from_files(
@@ -873,7 +853,7 @@ async def chat(
873
853
  else:
874
854
  if isinstance(response, str):
875
855
  response_log = response
876
- async for result in send_llm_response(response, tracer.get("usage")):
856
+ async for result in send_llm_response(response):
877
857
  yield result
878
858
 
879
859
  await sync_to_async(save_to_conversation_log)(
@@ -900,7 +880,7 @@ async def chat(
900
880
  conversation_config = await ConversationAdapters.aget_default_conversation_config(user)
901
881
  model_type = conversation_config.model_type
902
882
  formatted_help = help_message.format(model=model_type, version=state.khoj_version, device=get_device())
903
- async for result in send_llm_response(formatted_help, tracer.get("usage")):
883
+ async for result in send_llm_response(formatted_help):
904
884
  yield result
905
885
  return
906
886
  # Adding specification to search online specifically on khoj.dev pages.
@@ -915,7 +895,7 @@ async def chat(
915
895
  except Exception as e:
916
896
  logger.error(f"Error scheduling task {q} for {user.email}: {e}")
917
897
  error_message = f"Unable to create automation. Ensure the automation doesn't already exist."
918
- async for result in send_llm_response(error_message, tracer.get("usage")):
898
+ async for result in send_llm_response(error_message):
919
899
  yield result
920
900
  return
921
901
 
@@ -936,7 +916,7 @@ async def chat(
936
916
  raw_query_files=raw_query_files,
937
917
  tracer=tracer,
938
918
  )
939
- async for result in send_llm_response(llm_response, tracer.get("usage")):
919
+ async for result in send_llm_response(llm_response):
940
920
  yield result
941
921
  return
942
922
 
@@ -983,7 +963,7 @@ async def chat(
983
963
  yield result
984
964
 
985
965
  if conversation_commands == [ConversationCommand.Notes] and not await EntryAdapters.auser_has_entries(user):
986
- async for result in send_llm_response(f"{no_entries_found.format()}", tracer.get("usage")):
966
+ async for result in send_llm_response(f"{no_entries_found.format()}"):
987
967
  yield result
988
968
  return
989
969
 
@@ -1125,7 +1105,7 @@ async def chat(
1125
1105
  "detail": improved_image_prompt,
1126
1106
  "image": None,
1127
1107
  }
1128
- async for result in send_llm_response(json.dumps(content_obj), tracer.get("usage")):
1108
+ async for result in send_llm_response(json.dumps(content_obj)):
1129
1109
  yield result
1130
1110
  return
1131
1111
 
@@ -1152,7 +1132,7 @@ async def chat(
1152
1132
  "inferredQueries": [improved_image_prompt],
1153
1133
  "image": generated_image,
1154
1134
  }
1155
- async for result in send_llm_response(json.dumps(content_obj), tracer.get("usage")):
1135
+ async for result in send_llm_response(json.dumps(content_obj)):
1156
1136
  yield result
1157
1137
  return
1158
1138
 
@@ -1186,7 +1166,7 @@ async def chat(
1186
1166
  diagram_description = excalidraw_diagram_description
1187
1167
  else:
1188
1168
  error_message = "Failed to generate diagram. Please try again later."
1189
- async for result in send_llm_response(error_message, tracer.get("usage")):
1169
+ async for result in send_llm_response(error_message):
1190
1170
  yield result
1191
1171
 
1192
1172
  await sync_to_async(save_to_conversation_log)(
@@ -1233,7 +1213,7 @@ async def chat(
1233
1213
  tracer=tracer,
1234
1214
  )
1235
1215
 
1236
- async for result in send_llm_response(json.dumps(content_obj), tracer.get("usage")):
1216
+ async for result in send_llm_response(json.dumps(content_obj)):
1237
1217
  yield result
1238
1218
  return
1239
1219
 
@@ -1272,11 +1252,6 @@ async def chat(
1272
1252
  if item is None:
1273
1253
  async for result in send_event(ChatEvent.END_LLM_RESPONSE, ""):
1274
1254
  yield result
1275
- # Send Usage Metadata once llm interactions are complete
1276
- async for event in send_event(ChatEvent.USAGE, tracer.get("usage")):
1277
- yield event
1278
- async for result in send_event(ChatEvent.END_RESPONSE, ""):
1279
- yield result
1280
1255
  logger.debug("Finished streaming response")
1281
1256
  return
1282
1257
  if not connection_alive or not continue_stream:
@@ -66,23 +66,16 @@ async def subscribe(request: Request):
66
66
  success = user is not None
67
67
  elif event_type in {"customer.subscription.updated"}:
68
68
  user_subscription = await sync_to_async(adapters.get_user_subscription)(customer_email)
69
-
70
- renewal_date = None
71
- if subscription["current_period_end"]:
72
- renewal_date = datetime.fromtimestamp(subscription["current_period_end"], tz=timezone.utc)
73
-
74
69
  # Allow updating subscription status if paid user
75
70
  if user_subscription and user_subscription.renewal_date:
76
71
  # Mark user as unsubscribed or resubscribed
77
72
  is_recurring = not subscription["cancel_at_period_end"]
78
- user, is_new = await adapters.set_user_subscription(
79
- customer_email, is_recurring=is_recurring, renewal_date=renewal_date
80
- )
73
+ user, is_new = await adapters.set_user_subscription(customer_email, is_recurring=is_recurring)
81
74
  success = user is not None
82
75
  elif event_type in {"customer.subscription.deleted"}:
83
76
  # Reset the user to trial state
84
77
  user, is_new = await adapters.set_user_subscription(
85
- customer_email, is_recurring=False, renewal_date=None, type=Subscription.Type.TRIAL
78
+ customer_email, is_recurring=False, renewal_date=False, type=Subscription.Type.TRIAL
86
79
  )
87
80
  success = user is not None
88
81
 
khoj/routers/auth.py CHANGED
@@ -89,7 +89,7 @@ async def login_magic_link(request: Request, form: MagicLinkForm):
89
89
  update_telemetry_state(
90
90
  request=request,
91
91
  telemetry_type="api",
92
- api="create_user__email",
92
+ api="create_user",
93
93
  metadata={"server_id": str(user.uuid)},
94
94
  )
95
95
  logger.log(logging.INFO, f"🥳 New User Created: {user.uuid}")
@@ -174,7 +174,7 @@ async def auth(request: Request):
174
174
  update_telemetry_state(
175
175
  request=request,
176
176
  telemetry_type="api",
177
- api="create_user__google",
177
+ api="create_user",
178
178
  metadata={"server_id": str(khoj_user.uuid)},
179
179
  )
180
180
  logger.log(logging.INFO, f"🥳 New User Created: {khoj_user.uuid}")
khoj/routers/helpers.py CHANGED
@@ -753,11 +753,7 @@ async def generate_excalidraw_diagram(
753
753
  yield None, None
754
754
  return
755
755
 
756
- scratchpad = excalidraw_diagram_description.get("scratchpad")
757
-
758
- inferred_queries = f"Instruction: {better_diagram_description_prompt}\n\nScratchpad: {scratchpad}"
759
-
760
- yield inferred_queries, excalidraw_diagram_description.get("elements")
756
+ yield better_diagram_description_prompt, excalidraw_diagram_description
761
757
 
762
758
 
763
759
  async def generate_better_diagram_description(
@@ -826,7 +822,7 @@ async def generate_excalidraw_diagram_from_description(
826
822
  user: KhojUser = None,
827
823
  agent: Agent = None,
828
824
  tracer: dict = {},
829
- ) -> Dict[str, Any]:
825
+ ) -> str:
830
826
  personality_context = (
831
827
  prompts.personality_context.format(personality=agent.personality) if agent and agent.personality else ""
832
828
  )
@@ -842,18 +838,10 @@ async def generate_excalidraw_diagram_from_description(
842
838
  )
843
839
  raw_response = clean_json(raw_response)
844
840
  try:
845
- # Expect response to have `elements` and `scratchpad` keys
846
841
  response: Dict[str, str] = json.loads(raw_response)
847
- if (
848
- not response
849
- or not isinstance(response, Dict)
850
- or not response.get("elements")
851
- or not response.get("scratchpad")
852
- ):
853
- raise AssertionError(f"Invalid response for generating Excalidraw diagram: {response}")
854
842
  except Exception:
855
843
  raise AssertionError(f"Invalid response for generating Excalidraw diagram: {raw_response}")
856
- if not response or not isinstance(response["elements"], List) or not isinstance(response["elements"][0], Dict):
844
+ if not response or not isinstance(response, List) or not isinstance(response[0], Dict):
857
845
  # TODO Some additional validation here that it's a valid Excalidraw diagram
858
846
  raise AssertionError(f"Invalid response for improving diagram description: {response}")
859
847
 
@@ -1782,7 +1770,6 @@ Manage your automations [here](/automations).
1782
1770
  class MessageProcessor:
1783
1771
  def __init__(self):
1784
1772
  self.references = {}
1785
- self.usage = {}
1786
1773
  self.raw_response = ""
1787
1774
 
1788
1775
  def convert_message_chunk_to_json(self, raw_chunk: str) -> Dict[str, Any]:
@@ -1806,8 +1793,6 @@ class MessageProcessor:
1806
1793
  chunk_type = ChatEvent(chunk["type"])
1807
1794
  if chunk_type == ChatEvent.REFERENCES:
1808
1795
  self.references = chunk["data"]
1809
- elif chunk_type == ChatEvent.USAGE:
1810
- self.usage = chunk["data"]
1811
1796
  elif chunk_type == ChatEvent.MESSAGE:
1812
1797
  chunk_data = chunk["data"]
1813
1798
  if isinstance(chunk_data, dict):
@@ -1852,7 +1837,7 @@ async def read_chat_stream(response_iterator: AsyncGenerator[str, None]) -> Dict
1852
1837
  if buffer:
1853
1838
  processor.process_message_chunk(buffer)
1854
1839
 
1855
- return {"response": processor.raw_response, "references": processor.references, "usage": processor.usage}
1840
+ return {"response": processor.raw_response, "references": processor.references}
1856
1841
 
1857
1842
 
1858
1843
  def get_user_config(user: KhojUser, request: Request, is_detailed: bool = False):
khoj/routers/research.py CHANGED
@@ -16,7 +16,7 @@ from khoj.processor.conversation.utils import (
16
16
  construct_tool_chat_history,
17
17
  )
18
18
  from khoj.processor.tools.online_search import read_webpages, search_online
19
- from khoj.processor.tools.run_code import run_code
19
+ from khoj.processor.tools.run_code import run_code, truncate_code_context
20
20
  from khoj.routers.api import extract_references_and_questions
21
21
  from khoj.routers.helpers import (
22
22
  ChatEvent,
@@ -28,7 +28,6 @@ from khoj.utils.helpers import (
28
28
  function_calling_description_for_llm,
29
29
  is_none_or_empty,
30
30
  timer,
31
- truncate_code_context,
32
31
  )
33
32
  from khoj.utils.rawconfig import LocationData
34
33
 
khoj/utils/cli.py CHANGED
@@ -40,8 +40,6 @@ def cli(args=None):
40
40
  type=pathlib.Path,
41
41
  help="Path to UNIX socket for server. Use to run server behind reverse proxy. Default: /tmp/uvicorn.sock",
42
42
  )
43
- parser.add_argument("--sslcert", type=str, help="Path to SSL certificate file")
44
- parser.add_argument("--sslkey", type=str, help="Path to SSL key file")
45
43
  parser.add_argument("--version", "-V", action="store_true", help="Print the installed Khoj version and exit")
46
44
  parser.add_argument(
47
45
  "--disable-chat-on-gpu", action="store_true", default=False, help="Disable using GPU for the offline chat model"
khoj/utils/constants.py CHANGED
@@ -1,5 +1,4 @@
1
1
  from pathlib import Path
2
- from typing import Dict
3
2
 
4
3
  app_root_directory = Path(__file__).parent.parent.parent
5
4
  web_directory = app_root_directory / "khoj/interface/web/"
@@ -32,19 +31,3 @@ default_config = {
32
31
  "image": {"encoder": "sentence-transformers/clip-ViT-B-32", "model_directory": "~/.khoj/search/image/"},
33
32
  },
34
33
  }
35
-
36
- model_to_cost: Dict[str, Dict[str, float]] = {
37
- # OpenAI Pricing: https://openai.com/api/pricing/
38
- "gpt-4o": {"input": 2.50, "output": 10.00},
39
- "gpt-4o-mini": {"input": 0.15, "output": 0.60},
40
- "o1-preview": {"input": 15.0, "output": 60.00},
41
- "o1-mini": {"input": 3.0, "output": 12.0},
42
- # Gemini Pricing: https://ai.google.dev/pricing
43
- "gemini-1.5-flash": {"input": 0.075, "output": 0.30},
44
- "gemini-1.5-flash-002": {"input": 0.075, "output": 0.30},
45
- "gemini-1.5-pro": {"input": 1.25, "output": 5.00},
46
- "gemini-1.5-pro-002": {"input": 1.25, "output": 5.00},
47
- # Anthropic Pricing: https://www.anthropic.com/pricing#anthropic-api_
48
- "claude-3-5-sonnet-20241022": {"input": 3.0, "output": 15.0},
49
- "claude-3-5-haiku-20241022": {"input": 1.0, "output": 5.0},
50
- }