khoj 1.42.1.dev8__py3-none-any.whl → 1.42.2.dev1__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 (64) hide show
  1. khoj/database/adapters/__init__.py +3 -1
  2. khoj/database/models/__init__.py +9 -9
  3. khoj/interface/compiled/404/index.html +2 -2
  4. khoj/interface/compiled/_next/static/chunks/{2327-aa22697ed9c8d54a.js → 2327-f03b2a77f67b8f8c.js} +1 -1
  5. khoj/interface/compiled/_next/static/chunks/{5138-2cce449fd2454abf.js → 5138-81457f7f59956b56.js} +1 -1
  6. khoj/interface/compiled/_next/static/chunks/app/agents/layout-e00fb81dca656a10.js +1 -0
  7. khoj/interface/compiled/_next/static/chunks/app/agents/{page-774c78ff0f55a228.js → page-2fac1d5ac7192e73.js} +1 -1
  8. khoj/interface/compiled/_next/static/chunks/app/automations/{page-4454891c5007b870.js → page-465741d9149dfd48.js} +1 -1
  9. khoj/interface/compiled/_next/static/chunks/app/chat/layout-33934fc2d6ae6838.js +1 -0
  10. khoj/interface/compiled/_next/static/chunks/app/chat/{page-3c299bf8e6b1afd3.js → page-1726184cf1c1b86e.js} +1 -1
  11. khoj/interface/compiled/_next/static/chunks/app/{page-f7a0286dfc31ad6b.js → page-45ae5e99e8a61821.js} +1 -1
  12. khoj/interface/compiled/_next/static/chunks/app/search/layout-c02531d586972d7d.js +1 -0
  13. khoj/interface/compiled/_next/static/chunks/app/search/{page-f1a7f278c89e09b6.js → page-afb5e7ed13d221c1.js} +1 -1
  14. khoj/interface/compiled/_next/static/chunks/app/settings/{page-5d9134d4a97f8834.js → page-8fb6cc97be8774a7.js} +1 -1
  15. khoj/interface/compiled/_next/static/chunks/app/share/chat/layout-e8e5db7830bf3f47.js +1 -0
  16. khoj/interface/compiled/_next/static/chunks/app/share/chat/{page-32cd0ceb9ffbd777.js → page-9a167dc9b5fcd464.js} +1 -1
  17. khoj/interface/compiled/_next/static/chunks/{webpack-97e712397e673897.js → webpack-1c900156837baf90.js} +1 -1
  18. khoj/interface/compiled/_next/static/css/{93eeacc43e261162.css → c34713c98384ee87.css} +1 -1
  19. khoj/interface/compiled/_next/static/css/{0db53bacf81896f5.css → fca983d49c3dd1a3.css} +1 -1
  20. khoj/interface/compiled/agents/index.html +2 -2
  21. khoj/interface/compiled/agents/index.txt +2 -2
  22. khoj/interface/compiled/automations/index.html +2 -2
  23. khoj/interface/compiled/automations/index.txt +3 -3
  24. khoj/interface/compiled/chat/index.html +2 -2
  25. khoj/interface/compiled/chat/index.txt +2 -2
  26. khoj/interface/compiled/index.html +2 -2
  27. khoj/interface/compiled/index.txt +2 -2
  28. khoj/interface/compiled/search/index.html +2 -2
  29. khoj/interface/compiled/search/index.txt +2 -2
  30. khoj/interface/compiled/settings/index.html +2 -2
  31. khoj/interface/compiled/settings/index.txt +4 -4
  32. khoj/interface/compiled/share/chat/index.html +2 -2
  33. khoj/interface/compiled/share/chat/index.txt +2 -2
  34. khoj/processor/conversation/anthropic/anthropic_chat.py +7 -7
  35. khoj/processor/conversation/google/gemini_chat.py +7 -7
  36. khoj/processor/conversation/offline/chat_model.py +6 -6
  37. khoj/processor/conversation/openai/gpt.py +7 -7
  38. khoj/processor/conversation/utils.py +94 -89
  39. khoj/processor/image/generate.py +16 -11
  40. khoj/processor/operator/__init__.py +2 -3
  41. khoj/processor/operator/operator_agent_binary.py +11 -11
  42. khoj/processor/tools/online_search.py +9 -3
  43. khoj/processor/tools/run_code.py +5 -5
  44. khoj/routers/api.py +14 -8
  45. khoj/routers/api_chat.py +16 -16
  46. khoj/routers/helpers.py +44 -43
  47. khoj/routers/research.py +10 -10
  48. {khoj-1.42.1.dev8.dist-info → khoj-1.42.2.dev1.dist-info}/METADATA +1 -1
  49. {khoj-1.42.1.dev8.dist-info → khoj-1.42.2.dev1.dist-info}/RECORD +60 -60
  50. khoj/interface/compiled/_next/static/chunks/app/agents/layout-4e2a134ec26aa606.js +0 -1
  51. khoj/interface/compiled/_next/static/chunks/app/chat/layout-ad4d1792ab1a4108.js +0 -1
  52. khoj/interface/compiled/_next/static/chunks/app/search/layout-f5881c7ae3ba0795.js +0 -1
  53. khoj/interface/compiled/_next/static/chunks/app/share/chat/layout-abb6c5f4239ad7be.js +0 -1
  54. /khoj/interface/compiled/_next/static/{TrHI4J6qnG7RYFl2Irnqj → Dzg_ViqMwQEjqMgetZPRc}/_buildManifest.js +0 -0
  55. /khoj/interface/compiled/_next/static/{TrHI4J6qnG7RYFl2Irnqj → Dzg_ViqMwQEjqMgetZPRc}/_ssgManifest.js +0 -0
  56. /khoj/interface/compiled/_next/static/chunks/{1915-1943ee8a628b893c.js → 1915-ab4353eaca76f690.js} +0 -0
  57. /khoj/interface/compiled/_next/static/chunks/{2117-5a41630a2bd2eae8.js → 2117-1c18aa2098982bf9.js} +0 -0
  58. /khoj/interface/compiled/_next/static/chunks/{4363-e6ac2203564d1a3b.js → 4363-4efaf12abe696251.js} +0 -0
  59. /khoj/interface/compiled/_next/static/chunks/{4447-e038b251d626c340.js → 4447-5d44807c40355b1a.js} +0 -0
  60. /khoj/interface/compiled/_next/static/chunks/{8667-8136f74e9a086fca.js → 8667-adbe6017a66cef10.js} +0 -0
  61. /khoj/interface/compiled/_next/static/chunks/{9259-640fdd77408475df.js → 9259-d8bcd9da9e80c81e.js} +0 -0
  62. {khoj-1.42.1.dev8.dist-info → khoj-1.42.2.dev1.dist-info}/WHEEL +0 -0
  63. {khoj-1.42.1.dev8.dist-info → khoj-1.42.2.dev1.dist-info}/entry_points.txt +0 -0
  64. {khoj-1.42.1.dev8.dist-info → khoj-1.42.2.dev1.dist-info}/licenses/LICENSE +0 -0
@@ -24,7 +24,13 @@ from pydantic import BaseModel
24
24
  from transformers import AutoTokenizer, PreTrainedTokenizer, PreTrainedTokenizerFast
25
25
 
26
26
  from khoj.database.adapters import ConversationAdapters
27
- from khoj.database.models import ChatModel, ClientApplication, KhojUser
27
+ from khoj.database.models import (
28
+ ChatMessageModel,
29
+ ChatModel,
30
+ ClientApplication,
31
+ Intent,
32
+ KhojUser,
33
+ )
28
34
  from khoj.processor.conversation import prompts
29
35
  from khoj.processor.conversation.offline.utils import download_model, infer_max_tokens
30
36
  from khoj.search_filter.base_filter import BaseFilter
@@ -161,8 +167,8 @@ def construct_iteration_history(
161
167
  previous_iterations: List[ResearchIteration],
162
168
  previous_iteration_prompt: str,
163
169
  query: str = None,
164
- ) -> list[dict]:
165
- iteration_history: list[dict] = []
170
+ ) -> list[ChatMessageModel]:
171
+ iteration_history: list[ChatMessageModel] = []
166
172
  previous_iteration_messages: list[dict] = []
167
173
  for idx, iteration in enumerate(previous_iterations):
168
174
  iteration_data = previous_iteration_prompt.format(
@@ -176,46 +182,46 @@ def construct_iteration_history(
176
182
 
177
183
  if previous_iteration_messages:
178
184
  if query:
179
- iteration_history.append({"by": "you", "message": query})
185
+ iteration_history.append(ChatMessageModel(by="you", message=query))
180
186
  iteration_history.append(
181
- {
182
- "by": "khoj",
183
- "intent": {"type": "remember", "query": query},
184
- "message": previous_iteration_messages,
185
- }
187
+ ChatMessageModel(
188
+ by="khoj",
189
+ intent={"type": "remember", "query": query},
190
+ message=previous_iteration_messages,
191
+ )
186
192
  )
187
193
  return iteration_history
188
194
 
189
195
 
190
- def construct_chat_history(conversation_history: dict, n: int = 4, agent_name="AI") -> str:
191
- chat_history = ""
192
- for chat in conversation_history.get("chat", [])[-n:]:
193
- if chat["by"] == "khoj" and chat["intent"].get("type") in ["remember", "reminder", "summarize"]:
194
- if chat["intent"].get("inferred-queries"):
195
- chat_history += f'{agent_name}: {{"queries": {chat["intent"].get("inferred-queries")}}}\n'
196
- chat_history += f"{agent_name}: {chat['message']}\n\n"
197
- elif chat["by"] == "khoj" and chat.get("images"):
198
- chat_history += f"User: {chat['intent']['query']}\n"
199
- chat_history += f"{agent_name}: [generated image redacted for space]\n"
200
- elif chat["by"] == "khoj" and ("excalidraw" in chat["intent"].get("type")):
201
- chat_history += f"User: {chat['intent']['query']}\n"
202
- chat_history += f"{agent_name}: {chat['intent']['inferred-queries'][0]}\n"
203
- elif chat["by"] == "you":
204
- chat_history += f"User: {chat['message']}\n"
205
- raw_query_files = chat.get("queryFiles")
196
+ def construct_chat_history(chat_history: list[ChatMessageModel], n: int = 4, agent_name="AI") -> str:
197
+ chat_history_str = ""
198
+ for chat in chat_history[-n:]:
199
+ if chat.by == "khoj" and chat.intent.type in ["remember", "reminder", "summarize"]:
200
+ if chat.intent.inferred_queries:
201
+ chat_history_str += f'{agent_name}: {{"queries": {chat.intent.inferred_queries}}}\n'
202
+ chat_history_str += f"{agent_name}: {chat.message}\n\n"
203
+ elif chat.by == "khoj" and chat.images:
204
+ chat_history_str += f"User: {chat.intent.query}\n"
205
+ chat_history_str += f"{agent_name}: [generated image redacted for space]\n"
206
+ elif chat.by == "khoj" and ("excalidraw" in chat.intent.type):
207
+ chat_history_str += f"User: {chat.intent.query}\n"
208
+ chat_history_str += f"{agent_name}: {chat.intent.inferred_queries[0]}\n"
209
+ elif chat.by == "you":
210
+ chat_history_str += f"User: {chat.message}\n"
211
+ raw_query_files = chat.queryFiles
206
212
  if raw_query_files:
207
213
  query_files: Dict[str, str] = {}
208
214
  for file in raw_query_files:
209
215
  query_files[file["name"]] = file["content"]
210
216
 
211
217
  query_file_context = gather_raw_query_files(query_files)
212
- chat_history += f"User: {query_file_context}\n"
218
+ chat_history_str += f"User: {query_file_context}\n"
213
219
 
214
- return chat_history
220
+ return chat_history_str
215
221
 
216
222
 
217
223
  def construct_question_history(
218
- conversation_log: dict,
224
+ conversation_log: list[ChatMessageModel],
219
225
  include_query: bool = True,
220
226
  lookback: int = 6,
221
227
  query_prefix: str = "Q",
@@ -226,16 +232,16 @@ def construct_question_history(
226
232
  """
227
233
  history_parts = ""
228
234
  original_query = None
229
- for chat in conversation_log.get("chat", [])[-lookback:]:
230
- if chat["by"] == "you":
231
- original_query = chat.get("message")
235
+ for chat in conversation_log[-lookback:]:
236
+ if chat.by == "you":
237
+ original_query = json.dumps(chat.message)
232
238
  history_parts += f"{query_prefix}: {original_query}\n"
233
- if chat["by"] == "khoj":
239
+ if chat.by == "khoj":
234
240
  if original_query is None:
235
241
  continue
236
242
 
237
- message = chat.get("message", "")
238
- inferred_queries_list = chat.get("intent", {}).get("inferred-queries")
243
+ message = chat.message
244
+ inferred_queries_list = chat.intent.inferred_queries or []
239
245
 
240
246
  # Ensure inferred_queries_list is a list, defaulting to the original query in a list
241
247
  if not inferred_queries_list:
@@ -246,7 +252,7 @@ def construct_question_history(
246
252
 
247
253
  if include_query:
248
254
  # Ensure 'type' exists and is a string before checking 'to-image'
249
- intent_type = chat.get("intent", {}).get("type", "")
255
+ intent_type = chat.intent.type if chat.intent and chat.intent.type else ""
250
256
  if "to-image" not in intent_type:
251
257
  history_parts += f'{agent_name}: {{"queries": {inferred_queries_list}}}\n'
252
258
  history_parts += f"A: {message}\n\n"
@@ -259,7 +265,7 @@ def construct_question_history(
259
265
  return history_parts
260
266
 
261
267
 
262
- def construct_chat_history_for_operator(conversation_history: dict, n: int = 6) -> list[AgentMessage]:
268
+ def construct_chat_history_for_operator(conversation_history: List[ChatMessageModel], n: int = 6) -> list[AgentMessage]:
263
269
  """
264
270
  Construct chat history for operator agent in conversation log.
265
271
  Only include last n completed turns (i.e with user and khoj message).
@@ -267,22 +273,22 @@ def construct_chat_history_for_operator(conversation_history: dict, n: int = 6)
267
273
  chat_history: list[AgentMessage] = []
268
274
  user_message: Optional[AgentMessage] = None
269
275
 
270
- for chat in conversation_history.get("chat", []):
276
+ for chat in conversation_history:
271
277
  if len(chat_history) >= n:
272
278
  break
273
- if chat["by"] == "you" and chat.get("message"):
274
- content = [{"type": "text", "text": chat["message"]}]
275
- for file in chat.get("queryFiles", []):
279
+ if chat.by == "you" and chat.message:
280
+ content = [{"type": "text", "text": chat.message}]
281
+ for file in chat.queryFiles or []:
276
282
  content += [{"type": "text", "text": f'## File: {file["name"]}\n\n{file["content"]}'}]
277
283
  user_message = AgentMessage(role="user", content=content)
278
- elif chat["by"] == "khoj" and chat.get("message"):
279
- chat_history += [user_message, AgentMessage(role="assistant", content=chat["message"])]
284
+ elif chat.by == "khoj" and chat.message:
285
+ chat_history += [user_message, AgentMessage(role="assistant", content=chat.message)]
280
286
  return chat_history
281
287
 
282
288
 
283
289
  def construct_tool_chat_history(
284
290
  previous_iterations: List[ResearchIteration], tool: ConversationCommand = None
285
- ) -> Dict[str, list]:
291
+ ) -> List[ChatMessageModel]:
286
292
  """
287
293
  Construct chat history from previous iterations for a specific tool
288
294
 
@@ -313,22 +319,23 @@ def construct_tool_chat_history(
313
319
  tool or ConversationCommand(iteration.tool), base_extractor
314
320
  )
315
321
  chat_history += [
316
- {
317
- "by": "you",
318
- "message": iteration.query,
319
- },
320
- {
321
- "by": "khoj",
322
- "intent": {
323
- "type": "remember",
324
- "inferred-queries": inferred_query_extractor(iteration),
325
- "query": iteration.query,
326
- },
327
- "message": iteration.summarizedResult,
328
- },
322
+ ChatMessageModel(
323
+ by="you",
324
+ message=iteration.query,
325
+ ),
326
+ ChatMessageModel(
327
+ by="khoj",
328
+ intent=Intent(
329
+ type="remember",
330
+ query=iteration.query,
331
+ inferred_queries=inferred_query_extractor(iteration),
332
+ memory_type="notes",
333
+ ),
334
+ message=iteration.summarizedResult,
335
+ ),
329
336
  ]
330
337
 
331
- return {"chat": chat_history}
338
+ return chat_history
332
339
 
333
340
 
334
341
  class ChatEvent(Enum):
@@ -349,8 +356,8 @@ def message_to_log(
349
356
  chat_response,
350
357
  user_message_metadata={},
351
358
  khoj_message_metadata={},
352
- conversation_log=[],
353
- ):
359
+ chat_history: List[ChatMessageModel] = [],
360
+ ) -> List[ChatMessageModel]:
354
361
  """Create json logs from messages, metadata for conversation log"""
355
362
  default_khoj_message_metadata = {
356
363
  "intent": {"type": "remember", "memory-type": "notes", "query": user_message},
@@ -369,15 +376,17 @@ def message_to_log(
369
376
  khoj_log = merge_dicts(khoj_message_metadata, default_khoj_message_metadata)
370
377
  khoj_log = merge_dicts({"message": chat_response, "by": "khoj", "created": khoj_response_time}, khoj_log)
371
378
 
372
- conversation_log.extend([human_log, khoj_log])
373
- return conversation_log
379
+ human_message = ChatMessageModel(**human_log)
380
+ khoj_message = ChatMessageModel(**khoj_log)
381
+ chat_history.extend([human_message, khoj_message])
382
+ return chat_history
374
383
 
375
384
 
376
385
  async def save_to_conversation_log(
377
386
  q: str,
378
387
  chat_response: str,
379
388
  user: KhojUser,
380
- meta_log: Dict,
389
+ chat_history: List[ChatMessageModel],
381
390
  user_message_time: str = None,
382
391
  compiled_references: List[Dict[str, Any]] = [],
383
392
  online_results: Dict[str, Any] = {},
@@ -427,11 +436,11 @@ async def save_to_conversation_log(
427
436
  chat_response=chat_response,
428
437
  user_message_metadata=user_message_metadata,
429
438
  khoj_message_metadata=khoj_message_metadata,
430
- conversation_log=meta_log.get("chat", []),
439
+ chat_history=chat_history,
431
440
  )
432
441
  await ConversationAdapters.save_conversation(
433
442
  user,
434
- {"chat": updated_conversation},
443
+ updated_conversation,
435
444
  client_application=client_application,
436
445
  conversation_id=conversation_id,
437
446
  user_message=q,
@@ -502,7 +511,7 @@ def gather_raw_query_files(
502
511
  def generate_chatml_messages_with_context(
503
512
  user_message: str,
504
513
  system_message: str = None,
505
- conversation_log={},
514
+ chat_history: list[ChatMessageModel] = [],
506
515
  model_name="gpt-4o-mini",
507
516
  loaded_model: Optional[Llama] = None,
508
517
  max_prompt_size=None,
@@ -529,21 +538,21 @@ def generate_chatml_messages_with_context(
529
538
 
530
539
  # Extract Chat History for Context
531
540
  chatml_messages: List[ChatMessage] = []
532
- for chat in conversation_log.get("chat", []):
541
+ for chat in chat_history:
533
542
  message_context = []
534
543
  message_attached_files = ""
535
544
 
536
545
  generated_assets = {}
537
546
 
538
- chat_message = chat.get("message")
539
- role = "user" if chat["by"] == "you" else "assistant"
547
+ chat_message = chat.message
548
+ role = "user" if chat.by == "you" else "assistant"
540
549
 
541
550
  # Legacy code to handle excalidraw diagrams prior to Dec 2024
542
- if chat["by"] == "khoj" and "excalidraw" in chat["intent"].get("type", ""):
543
- chat_message = chat["intent"].get("inferred-queries")[0]
551
+ if chat.by == "khoj" and "excalidraw" in chat.intent.type or "":
552
+ chat_message = (chat.intent.inferred_queries or [])[0]
544
553
 
545
- if chat.get("queryFiles"):
546
- raw_query_files = chat.get("queryFiles")
554
+ if chat.queryFiles:
555
+ raw_query_files = chat.queryFiles
547
556
  query_files_dict = dict()
548
557
  for file in raw_query_files:
549
558
  query_files_dict[file["name"]] = file["content"]
@@ -551,24 +560,24 @@ def generate_chatml_messages_with_context(
551
560
  message_attached_files = gather_raw_query_files(query_files_dict)
552
561
  chatml_messages.append(ChatMessage(content=message_attached_files, role=role))
553
562
 
554
- if not is_none_or_empty(chat.get("onlineContext")):
563
+ if not is_none_or_empty(chat.onlineContext):
555
564
  message_context += [
556
565
  {
557
566
  "type": "text",
558
- "text": f"{prompts.online_search_conversation.format(online_results=chat.get('onlineContext'))}",
567
+ "text": f"{prompts.online_search_conversation.format(online_results=chat.onlineContext)}",
559
568
  }
560
569
  ]
561
570
 
562
- if not is_none_or_empty(chat.get("codeContext")):
571
+ if not is_none_or_empty(chat.codeContext):
563
572
  message_context += [
564
573
  {
565
574
  "type": "text",
566
- "text": f"{prompts.code_executed_context.format(code_results=chat.get('codeContext'))}",
575
+ "text": f"{prompts.code_executed_context.format(code_results=chat.codeContext)}",
567
576
  }
568
577
  ]
569
578
 
570
- if not is_none_or_empty(chat.get("operatorContext")):
571
- operator_context = chat.get("operatorContext")
579
+ if not is_none_or_empty(chat.operatorContext):
580
+ operator_context = chat.operatorContext
572
581
  operator_content = "\n\n".join([f'## Task: {oc["query"]}\n{oc["response"]}\n' for oc in operator_context])
573
582
  message_context += [
574
583
  {
@@ -577,13 +586,9 @@ def generate_chatml_messages_with_context(
577
586
  }
578
587
  ]
579
588
 
580
- if not is_none_or_empty(chat.get("context")):
589
+ if not is_none_or_empty(chat.context):
581
590
  references = "\n\n".join(
582
- {
583
- f"# File: {item['file']}\n## {item['compiled']}\n"
584
- for item in chat.get("context") or []
585
- if isinstance(item, dict)
586
- }
591
+ {f"# File: {item.file}\n## {item.compiled}\n" for item in chat.context or [] if isinstance(item, dict)}
587
592
  )
588
593
  message_context += [{"type": "text", "text": f"{prompts.notes_conversation.format(references=references)}"}]
589
594
 
@@ -591,14 +596,14 @@ def generate_chatml_messages_with_context(
591
596
  reconstructed_context_message = ChatMessage(content=message_context, role="user")
592
597
  chatml_messages.insert(0, reconstructed_context_message)
593
598
 
594
- if not is_none_or_empty(chat.get("images")) and role == "assistant":
599
+ if not is_none_or_empty(chat.images) and role == "assistant":
595
600
  generated_assets["image"] = {
596
- "query": chat.get("intent", {}).get("inferred-queries", [user_message])[0],
601
+ "query": (chat.intent.inferred_queries or [user_message])[0],
597
602
  }
598
603
 
599
- if not is_none_or_empty(chat.get("mermaidjsDiagram")) and role == "assistant":
604
+ if not is_none_or_empty(chat.mermaidjsDiagram) and role == "assistant":
600
605
  generated_assets["diagram"] = {
601
- "query": chat.get("intent", {}).get("inferred-queries", [user_message])[0],
606
+ "query": (chat.intent.inferred_queries or [user_message])[0],
602
607
  }
603
608
 
604
609
  if not is_none_or_empty(generated_assets):
@@ -610,7 +615,7 @@ def generate_chatml_messages_with_context(
610
615
  )
611
616
 
612
617
  message_content = construct_structured_message(
613
- chat_message, chat.get("images") if role == "user" else [], model_type, vision_enabled
618
+ chat_message, chat.images if role == "user" else [], model_type, vision_enabled
614
619
  )
615
620
 
616
621
  reconstructed_message = ChatMessage(content=message_content, role=role)
@@ -10,7 +10,12 @@ from google import genai
10
10
  from google.genai import types as gtypes
11
11
 
12
12
  from khoj.database.adapters import ConversationAdapters
13
- from khoj.database.models import Agent, KhojUser, TextToImageModelConfig
13
+ from khoj.database.models import (
14
+ Agent,
15
+ ChatMessageModel,
16
+ KhojUser,
17
+ TextToImageModelConfig,
18
+ )
14
19
  from khoj.routers.helpers import ChatEvent, generate_better_image_prompt
15
20
  from khoj.routers.storage import upload_generated_image_to_bucket
16
21
  from khoj.utils import state
@@ -23,7 +28,7 @@ logger = logging.getLogger(__name__)
23
28
  async def text_to_image(
24
29
  message: str,
25
30
  user: KhojUser,
26
- conversation_log: dict,
31
+ chat_history: List[ChatMessageModel],
27
32
  location_data: LocationData,
28
33
  references: List[Dict[str, Any]],
29
34
  online_results: Dict[str, Any],
@@ -46,14 +51,14 @@ async def text_to_image(
46
51
  return
47
52
 
48
53
  text2image_model = text_to_image_config.model_name
49
- chat_history = ""
50
- for chat in conversation_log.get("chat", [])[-4:]:
51
- if chat["by"] == "khoj" and chat["intent"].get("type") in ["remember", "reminder"]:
52
- chat_history += f"Q: {chat['intent']['query']}\n"
53
- chat_history += f"A: {chat['message']}\n"
54
- elif chat["by"] == "khoj" and chat.get("images"):
55
- chat_history += f"Q: {chat['intent']['query']}\n"
56
- chat_history += f"A: Improved Prompt: {chat['intent']['inferred-queries'][0]}\n"
54
+ chat_history_str = ""
55
+ for chat in chat_history[-4:]:
56
+ if chat.by == "khoj" and chat.intent and chat.intent.type in ["remember", "reminder"]:
57
+ chat_history_str += f"Q: {chat.intent.query or ''}\n"
58
+ chat_history_str += f"A: {chat.message}\n"
59
+ elif chat.by == "khoj" and chat.images:
60
+ chat_history_str += f"Q: {chat.intent.query}\n"
61
+ chat_history_str += f"A: Improved Prompt: {chat.intent.inferred_queries[0]}\n"
57
62
 
58
63
  if send_status_func:
59
64
  async for event in send_status_func("**Enhancing the Painting Prompt**"):
@@ -63,7 +68,7 @@ async def text_to_image(
63
68
  # Use the user's message, chat history, and other context
64
69
  image_prompt = await generate_better_image_prompt(
65
70
  message,
66
- chat_history,
71
+ chat_history_str,
67
72
  location_data=location_data,
68
73
  note_references=references,
69
74
  online_results=online_results,
@@ -5,10 +5,9 @@ import os
5
5
  from typing import Callable, List, Optional
6
6
 
7
7
  from khoj.database.adapters import AgentAdapters, ConversationAdapters
8
- from khoj.database.models import Agent, ChatModel, KhojUser
8
+ from khoj.database.models import Agent, ChatMessageModel, ChatModel, KhojUser
9
9
  from khoj.processor.conversation.utils import (
10
10
  OperatorRun,
11
- construct_chat_history,
12
11
  construct_chat_history_for_operator,
13
12
  )
14
13
  from khoj.processor.operator.operator_actions import *
@@ -34,7 +33,7 @@ logger = logging.getLogger(__name__)
34
33
  async def operate_environment(
35
34
  query: str,
36
35
  user: KhojUser,
37
- conversation_log: dict,
36
+ conversation_log: List[ChatMessageModel],
38
37
  location_data: LocationData,
39
38
  previous_trajectory: Optional[OperatorRun] = None,
40
39
  environment_type: EnvironmentType = EnvironmentType.COMPUTER,
@@ -4,7 +4,7 @@ from datetime import datetime
4
4
  from textwrap import dedent
5
5
  from typing import List, Optional
6
6
 
7
- from khoj.database.models import ChatModel
7
+ from khoj.database.models import ChatMessageModel, ChatModel
8
8
  from khoj.processor.conversation.utils import (
9
9
  AgentMessage,
10
10
  OperatorRun,
@@ -119,13 +119,13 @@ class BinaryOperatorAgent(OperatorAgent):
119
119
  query_screenshot = self._get_message_images(current_message)
120
120
 
121
121
  # Construct input for visual reasoner history
122
- visual_reasoner_history = {"chat": self._format_message_for_api(self.messages)}
122
+ visual_reasoner_history = self._format_message_for_api(self.messages)
123
123
  try:
124
124
  natural_language_action = await send_message_to_model_wrapper(
125
125
  query=query_text,
126
126
  query_images=query_screenshot,
127
127
  system_message=reasoning_system_prompt,
128
- conversation_log=visual_reasoner_history,
128
+ chat_history=visual_reasoner_history,
129
129
  agent_chat_model=self.reasoning_model,
130
130
  tracer=self.tracer,
131
131
  )
@@ -238,11 +238,11 @@ class BinaryOperatorAgent(OperatorAgent):
238
238
 
239
239
  async def summarize(self, env_state: EnvState, summarize_prompt: str = None) -> str:
240
240
  summarize_prompt = summarize_prompt or self.summarize_prompt
241
- conversation_history = {"chat": self._format_message_for_api(self.messages)}
241
+ conversation_history = self._format_message_for_api(self.messages)
242
242
  try:
243
243
  summary = await send_message_to_model_wrapper(
244
244
  query=summarize_prompt,
245
- conversation_log=conversation_history,
245
+ chat_history=conversation_history,
246
246
  agent_chat_model=self.reasoning_model,
247
247
  tracer=self.tracer,
248
248
  )
@@ -296,14 +296,14 @@ class BinaryOperatorAgent(OperatorAgent):
296
296
  images = [item["image_url"]["url"] for item in message.content if item["type"] == "image_url"]
297
297
  return images
298
298
 
299
- def _format_message_for_api(self, messages: list[AgentMessage]) -> List[dict]:
299
+ def _format_message_for_api(self, messages: list[AgentMessage]) -> List[ChatMessageModel]:
300
300
  """Format operator agent messages into the Khoj conversation history format."""
301
301
  formatted_messages = [
302
- {
303
- "message": self._get_message_text(message),
304
- "images": self._get_message_images(message),
305
- "by": "you" if message.role in ["user", "environment"] else message.role,
306
- }
302
+ ChatMessageModel(
303
+ message=self._get_message_text(message),
304
+ images=self._get_message_images(message),
305
+ by="you" if message.role in ["user", "environment"] else message.role,
306
+ )
307
307
  for message in messages
308
308
  ]
309
309
  return formatted_messages
@@ -10,7 +10,13 @@ from bs4 import BeautifulSoup
10
10
  from markdownify import markdownify
11
11
 
12
12
  from khoj.database.adapters import ConversationAdapters
13
- from khoj.database.models import Agent, KhojUser, ServerChatSettings, WebScraper
13
+ from khoj.database.models import (
14
+ Agent,
15
+ ChatMessageModel,
16
+ KhojUser,
17
+ ServerChatSettings,
18
+ WebScraper,
19
+ )
14
20
  from khoj.processor.conversation import prompts
15
21
  from khoj.routers.helpers import (
16
22
  ChatEvent,
@@ -59,7 +65,7 @@ OLOSTEP_QUERY_PARAMS = {
59
65
 
60
66
  async def search_online(
61
67
  query: str,
62
- conversation_history: dict,
68
+ conversation_history: List[ChatMessageModel],
63
69
  location: LocationData,
64
70
  user: KhojUser,
65
71
  send_status_func: Optional[Callable] = None,
@@ -361,7 +367,7 @@ async def search_with_serper(query: str, location: LocationData) -> Tuple[str, D
361
367
 
362
368
  async def read_webpages(
363
369
  query: str,
364
- conversation_history: dict,
370
+ conversation_history: List[ChatMessageModel],
365
371
  location: LocationData,
366
372
  user: KhojUser,
367
373
  send_status_func: Optional[Callable] = None,
@@ -20,7 +20,7 @@ from tenacity import (
20
20
  )
21
21
 
22
22
  from khoj.database.adapters import FileObjectAdapters
23
- from khoj.database.models import Agent, FileObject, KhojUser
23
+ from khoj.database.models import Agent, ChatMessageModel, FileObject, KhojUser
24
24
  from khoj.processor.conversation import prompts
25
25
  from khoj.processor.conversation.utils import (
26
26
  ChatEvent,
@@ -50,7 +50,7 @@ class GeneratedCode(NamedTuple):
50
50
 
51
51
  async def run_code(
52
52
  query: str,
53
- conversation_history: dict,
53
+ conversation_history: List[ChatMessageModel],
54
54
  context: str,
55
55
  location_data: LocationData,
56
56
  user: KhojUser,
@@ -116,7 +116,7 @@ async def run_code(
116
116
 
117
117
  async def generate_python_code(
118
118
  q: str,
119
- conversation_history: dict,
119
+ chat_history: List[ChatMessageModel],
120
120
  context: str,
121
121
  location_data: LocationData,
122
122
  user: KhojUser,
@@ -127,7 +127,7 @@ async def generate_python_code(
127
127
  ) -> GeneratedCode:
128
128
  location = f"{location_data}" if location_data else "Unknown"
129
129
  username = prompts.user_name.format(name=user.get_full_name()) if user.get_full_name() else ""
130
- chat_history = construct_chat_history(conversation_history)
130
+ chat_history_str = construct_chat_history(chat_history)
131
131
 
132
132
  utc_date = datetime.datetime.now(datetime.timezone.utc).strftime("%Y-%m-%d")
133
133
  personality_context = (
@@ -143,7 +143,7 @@ async def generate_python_code(
143
143
 
144
144
  code_generation_prompt = prompts.python_code_generation_prompt.format(
145
145
  query=q,
146
- chat_history=chat_history,
146
+ chat_history=chat_history_str,
147
147
  context=context,
148
148
  has_network_access=network_access_context,
149
149
  current_date=utc_date,
khoj/routers/api.py CHANGED
@@ -29,7 +29,13 @@ from khoj.database.adapters import (
29
29
  get_default_search_model,
30
30
  get_user_photo,
31
31
  )
32
- from khoj.database.models import Agent, ChatModel, KhojUser, SpeechToTextModelOptions
32
+ from khoj.database.models import (
33
+ Agent,
34
+ ChatMessageModel,
35
+ ChatModel,
36
+ KhojUser,
37
+ SpeechToTextModelOptions,
38
+ )
33
39
  from khoj.processor.conversation import prompts
34
40
  from khoj.processor.conversation.anthropic.anthropic_chat import (
35
41
  extract_questions_anthropic,
@@ -353,7 +359,7 @@ def set_user_name(
353
359
 
354
360
  async def extract_references_and_questions(
355
361
  user: KhojUser,
356
- meta_log: dict,
362
+ chat_history: list[ChatMessageModel],
357
363
  q: str,
358
364
  n: int,
359
365
  d: float,
@@ -432,7 +438,7 @@ async def extract_references_and_questions(
432
438
  defiltered_query,
433
439
  model=chat_model,
434
440
  loaded_model=loaded_model,
435
- conversation_log=meta_log,
441
+ chat_history=chat_history,
436
442
  should_extract_questions=True,
437
443
  location_data=location_data,
438
444
  user=user,
@@ -450,7 +456,7 @@ async def extract_references_and_questions(
450
456
  model=chat_model_name,
451
457
  api_key=api_key,
452
458
  api_base_url=base_url,
453
- conversation_log=meta_log,
459
+ chat_history=chat_history,
454
460
  location_data=location_data,
455
461
  user=user,
456
462
  query_images=query_images,
@@ -469,7 +475,7 @@ async def extract_references_and_questions(
469
475
  model=chat_model_name,
470
476
  api_key=api_key,
471
477
  api_base_url=api_base_url,
472
- conversation_log=meta_log,
478
+ chat_history=chat_history,
473
479
  location_data=location_data,
474
480
  user=user,
475
481
  vision_enabled=vision_enabled,
@@ -487,7 +493,7 @@ async def extract_references_and_questions(
487
493
  model=chat_model_name,
488
494
  api_key=api_key,
489
495
  api_base_url=api_base_url,
490
- conversation_log=meta_log,
496
+ chat_history=chat_history,
491
497
  location_data=location_data,
492
498
  max_tokens=chat_model.max_prompt_size,
493
499
  user=user,
@@ -606,7 +612,7 @@ def post_automation(
606
612
  return Response(content="Invalid crontime", status_code=400)
607
613
 
608
614
  # Infer subject, query to run
609
- _, query_to_run, generated_subject = schedule_query(q, conversation_history={}, user=user)
615
+ _, query_to_run, generated_subject = schedule_query(q, chat_history=[], user=user)
610
616
  subject = subject or generated_subject
611
617
 
612
618
  # Normalize query parameters
@@ -712,7 +718,7 @@ def edit_job(
712
718
  return Response(content="Invalid automation", status_code=403)
713
719
 
714
720
  # Infer subject, query to run
715
- _, query_to_run, _ = schedule_query(q, conversation_history={}, user=user)
721
+ _, query_to_run, _ = schedule_query(q, chat_history=[], user=user)
716
722
  subject = subject
717
723
 
718
724
  # Normalize query parameters