khoj 2.0.0b14.dev51__py3-none-any.whl → 2.0.0b15.dev22__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 (52) hide show
  1. khoj/database/adapters/__init__.py +59 -20
  2. khoj/database/admin.py +4 -0
  3. khoj/database/migrations/0094_serverchatsettings_think_free_deep_and_more.py +61 -0
  4. khoj/database/models/__init__.py +18 -2
  5. khoj/interface/compiled/404/index.html +2 -2
  6. khoj/interface/compiled/_next/static/chunks/{9808-0ae18d938933fea3.js → 9808-bd5d7361ad026094.js} +1 -1
  7. khoj/interface/compiled/_next/static/css/{2945c4a857922f3b.css → c34713c98384ee87.css} +1 -1
  8. khoj/interface/compiled/_next/static/css/fb7ea16e60b40ecd.css +1 -0
  9. khoj/interface/compiled/agents/index.html +2 -2
  10. khoj/interface/compiled/agents/index.txt +2 -2
  11. khoj/interface/compiled/automations/index.html +2 -2
  12. khoj/interface/compiled/automations/index.txt +3 -3
  13. khoj/interface/compiled/chat/index.html +2 -2
  14. khoj/interface/compiled/chat/index.txt +3 -3
  15. khoj/interface/compiled/index.html +2 -2
  16. khoj/interface/compiled/index.txt +2 -2
  17. khoj/interface/compiled/search/index.html +2 -2
  18. khoj/interface/compiled/search/index.txt +2 -2
  19. khoj/interface/compiled/settings/index.html +2 -2
  20. khoj/interface/compiled/settings/index.txt +4 -4
  21. khoj/interface/compiled/share/chat/index.html +2 -2
  22. khoj/interface/compiled/share/chat/index.txt +2 -2
  23. khoj/processor/conversation/anthropic/anthropic_chat.py +4 -88
  24. khoj/processor/conversation/anthropic/utils.py +1 -2
  25. khoj/processor/conversation/google/gemini_chat.py +4 -88
  26. khoj/processor/conversation/google/utils.py +6 -3
  27. khoj/processor/conversation/openai/gpt.py +16 -93
  28. khoj/processor/conversation/openai/utils.py +38 -30
  29. khoj/processor/conversation/prompts.py +30 -39
  30. khoj/processor/conversation/utils.py +70 -84
  31. khoj/processor/image/generate.py +69 -15
  32. khoj/processor/tools/run_code.py +3 -2
  33. khoj/routers/api_chat.py +8 -21
  34. khoj/routers/helpers.py +243 -156
  35. khoj/routers/research.py +6 -6
  36. khoj/utils/helpers.py +6 -2
  37. {khoj-2.0.0b14.dev51.dist-info → khoj-2.0.0b15.dev22.dist-info}/METADATA +1 -1
  38. {khoj-2.0.0b14.dev51.dist-info → khoj-2.0.0b15.dev22.dist-info}/RECORD +51 -50
  39. khoj/interface/compiled/_next/static/css/ecea704005ba630c.css +0 -1
  40. /khoj/interface/compiled/_next/static/chunks/{1327-511bb0a862efce80.js → 1327-e254819a9172cfa7.js} +0 -0
  41. /khoj/interface/compiled/_next/static/chunks/{1915-fbfe167c84ad60c5.js → 1915-5c6508f6ebb62a30.js} +0 -0
  42. /khoj/interface/compiled/_next/static/chunks/{2117-e78b6902ad6f75ec.js → 2117-080746c8e170c81a.js} +0 -0
  43. /khoj/interface/compiled/_next/static/chunks/{2939-4d4084c5b888b960.js → 2939-4af3fd24b8ffc9ad.js} +0 -0
  44. /khoj/interface/compiled/_next/static/chunks/{4447-d6cf93724d57e34b.js → 4447-cd95608f8e93e711.js} +0 -0
  45. /khoj/interface/compiled/_next/static/chunks/{8667-4b7790573b08c50d.js → 8667-50b03a89e82e0ba7.js} +0 -0
  46. /khoj/interface/compiled/_next/static/chunks/{9139-ce1ae935dac9c871.js → 9139-8ac4d9feb10f8869.js} +0 -0
  47. /khoj/interface/compiled/_next/static/chunks/{webpack-e572645654c4335e.js → webpack-5393aad3d824e0cb.js} +0 -0
  48. /khoj/interface/compiled/_next/static/{yBzbL9kxl5BudSA9F4Gr6 → t8O_8CJ9p3UtV9kEsAAWT}/_buildManifest.js +0 -0
  49. /khoj/interface/compiled/_next/static/{yBzbL9kxl5BudSA9F4Gr6 → t8O_8CJ9p3UtV9kEsAAWT}/_ssgManifest.js +0 -0
  50. {khoj-2.0.0b14.dev51.dist-info → khoj-2.0.0b15.dev22.dist-info}/WHEEL +0 -0
  51. {khoj-2.0.0b14.dev51.dist-info → khoj-2.0.0b15.dev22.dist-info}/entry_points.txt +0 -0
  52. {khoj-2.0.0b14.dev51.dist-info → khoj-2.0.0b15.dev22.dist-info}/licenses/LICENSE +0 -0
khoj/routers/helpers.py CHANGED
@@ -33,6 +33,7 @@ from apscheduler.triggers.cron import CronTrigger
33
33
  from asgiref.sync import sync_to_async
34
34
  from django.utils import timezone as django_timezone
35
35
  from fastapi import Depends, Header, HTTPException, Request, UploadFile, WebSocket
36
+ from langchain_core.messages.chat import ChatMessage
36
37
  from pydantic import BaseModel, EmailStr, Field
37
38
  from starlette.authentication import has_required_scope
38
39
  from starlette.requests import URL
@@ -113,6 +114,7 @@ from khoj.utils import state
113
114
  from khoj.utils.helpers import (
114
115
  LRU,
115
116
  ConversationCommand,
117
+ ImageShape,
116
118
  ToolDefinition,
117
119
  get_file_type,
118
120
  in_debug_mode,
@@ -123,15 +125,16 @@ from khoj.utils.helpers import (
123
125
  mode_descriptions_for_llm,
124
126
  timer,
125
127
  tool_descriptions_for_llm,
128
+ truncate_code_context,
126
129
  )
127
130
  from khoj.utils.rawconfig import (
128
131
  ChatRequestBody,
129
- FileAttachment,
130
132
  FileData,
131
133
  LocationData,
132
134
  SearchResponse,
133
135
  )
134
136
  from khoj.utils.state import SearchType
137
+ from khoj.utils.yaml import yaml_dump
135
138
 
136
139
  logger = logging.getLogger(__name__)
137
140
 
@@ -286,7 +289,7 @@ async def acreate_title_from_history(
286
289
  title_generation_prompt = prompts.conversation_title_generation.format(chat_history=chat_history)
287
290
 
288
291
  with timer("Chat actor: Generate title from conversation history", logger):
289
- response = await send_message_to_model_wrapper(title_generation_prompt, user=user)
292
+ response = await send_message_to_model_wrapper(title_generation_prompt, fast_model=True, user=user)
290
293
 
291
294
  return response.text.strip()
292
295
 
@@ -298,7 +301,7 @@ async def acreate_title_from_query(query: str, user: KhojUser = None) -> str:
298
301
  title_generation_prompt = prompts.subject_generation.format(query=query)
299
302
 
300
303
  with timer("Chat actor: Generate title from query", logger):
301
- response = await send_message_to_model_wrapper(title_generation_prompt, user=user)
304
+ response = await send_message_to_model_wrapper(title_generation_prompt, fast_model=True, user=user)
302
305
 
303
306
  return response.text.strip()
304
307
 
@@ -321,7 +324,7 @@ async def acheck_if_safe_prompt(system_prompt: str, user: KhojUser = None, lax:
321
324
 
322
325
  with timer("Chat actor: Check if safe prompt", logger):
323
326
  response = await send_message_to_model_wrapper(
324
- safe_prompt_check, user=user, response_type="json_object", response_schema=SafetyCheck
327
+ safe_prompt_check, response_type="json_object", response_schema=SafetyCheck, fast_model=True, user=user
325
328
  )
326
329
 
327
330
  response = response.text.strip()
@@ -342,7 +345,6 @@ async def acheck_if_safe_prompt(system_prompt: str, user: KhojUser = None, lax:
342
345
  async def aget_data_sources_and_output_format(
343
346
  query: str,
344
347
  chat_history: list[ChatMessageModel],
345
- is_task: bool,
346
348
  user: KhojUser,
347
349
  query_images: List[str] = None,
348
350
  agent: Agent = None,
@@ -381,9 +383,6 @@ async def aget_data_sources_and_output_format(
381
383
 
382
384
  chat_history_str = construct_chat_history(chat_history, n=6)
383
385
 
384
- if query_images:
385
- query = f"[placeholder for {len(query_images)} user attached images]\n{query}"
386
-
387
386
  personality_context = (
388
387
  prompts.personality_context.format(personality=agent.personality) if agent and agent.personality else ""
389
388
  )
@@ -405,11 +404,13 @@ async def aget_data_sources_and_output_format(
405
404
  with timer("Chat actor: Infer information sources to refer", logger):
406
405
  raw_response = await send_message_to_model_wrapper(
407
406
  relevant_tools_prompt,
407
+ query_files=query_files,
408
+ query_images=query_images,
408
409
  response_type="json_object",
409
410
  response_schema=PickTools,
410
- user=user,
411
- query_files=query_files,
411
+ fast_model=False,
412
412
  agent_chat_model=agent_chat_model,
413
+ user=user,
413
414
  tracer=tracer,
414
415
  )
415
416
 
@@ -493,12 +494,13 @@ async def infer_webpage_urls(
493
494
  with timer("Chat actor: Infer webpage urls to read", logger):
494
495
  raw_response = await send_message_to_model_wrapper(
495
496
  online_queries_prompt,
497
+ query_files=query_files,
496
498
  query_images=query_images,
497
499
  response_type="json_object",
498
500
  response_schema=WebpageUrls,
499
- user=user,
500
- query_files=query_files,
501
+ fast_model=False,
501
502
  agent_chat_model=agent_chat_model,
503
+ user=user,
502
504
  tracer=tracer,
503
505
  )
504
506
 
@@ -558,12 +560,13 @@ async def generate_online_subqueries(
558
560
  with timer("Chat actor: Generate online search subqueries", logger):
559
561
  raw_response = await send_message_to_model_wrapper(
560
562
  online_queries_prompt,
563
+ query_files=query_files,
561
564
  query_images=query_images,
562
565
  response_type="json_object",
563
566
  response_schema=OnlineQueries,
564
- user=user,
565
- query_files=query_files,
567
+ fast_model=False,
566
568
  agent_chat_model=agent_chat_model,
569
+ user=user,
567
570
  tracer=tracer,
568
571
  )
569
572
 
@@ -625,7 +628,12 @@ async def aschedule_query(
625
628
  )
626
629
 
627
630
  raw_response = await send_message_to_model_wrapper(
628
- crontime_prompt, query_images=query_images, response_type="json_object", user=user, tracer=tracer
631
+ crontime_prompt,
632
+ query_images=query_images,
633
+ response_type="json_object",
634
+ fast_model=False,
635
+ user=user,
636
+ tracer=tracer,
629
637
  )
630
638
 
631
639
  # Validate that the response is a non-empty, JSON-serializable list
@@ -663,9 +671,10 @@ async def extract_relevant_info(
663
671
 
664
672
  response = await send_message_to_model_wrapper(
665
673
  extract_relevant_information,
666
- prompts.system_prompt_extract_relevant_information,
667
- user=user,
674
+ system_message=prompts.system_prompt_extract_relevant_information,
675
+ fast_model=True,
668
676
  agent_chat_model=agent_chat_model,
677
+ user=user,
669
678
  tracer=tracer,
670
679
  )
671
680
  return response.text.strip()
@@ -705,10 +714,11 @@ async def extract_relevant_summary(
705
714
  with timer("Chat actor: Extract relevant information from data", logger):
706
715
  response = await send_message_to_model_wrapper(
707
716
  extract_relevant_information,
708
- prompts.system_prompt_extract_relevant_summary,
709
- user=user,
710
717
  query_images=query_images,
718
+ system_message=prompts.system_prompt_extract_relevant_summary,
719
+ fast_model=True,
711
720
  agent_chat_model=agent_chat_model,
721
+ user=user,
712
722
  tracer=tracer,
713
723
  )
714
724
  return response.text.strip()
@@ -877,9 +887,10 @@ async def generate_better_diagram_description(
877
887
  response = await send_message_to_model_wrapper(
878
888
  improve_diagram_description_prompt,
879
889
  query_images=query_images,
880
- user=user,
881
890
  query_files=query_files,
891
+ fast_model=False,
882
892
  agent_chat_model=agent_chat_model,
893
+ user=user,
883
894
  tracer=tracer,
884
895
  )
885
896
  response = response.text.strip()
@@ -908,7 +919,11 @@ async def generate_excalidraw_diagram_from_description(
908
919
 
909
920
  with timer("Chat actor: Generate excalidraw diagram", logger):
910
921
  raw_response = await send_message_to_model_wrapper(
911
- query=excalidraw_diagram_generation, user=user, agent_chat_model=agent_chat_model, tracer=tracer
922
+ query=excalidraw_diagram_generation,
923
+ fast_model=False,
924
+ agent_chat_model=agent_chat_model,
925
+ user=user,
926
+ tracer=tracer,
912
927
  )
913
928
  raw_response_text = clean_json(raw_response.text)
914
929
  try:
@@ -1027,10 +1042,11 @@ async def generate_better_mermaidjs_diagram_description(
1027
1042
  with timer("Chat actor: Generate better Mermaid.js diagram description", logger):
1028
1043
  response = await send_message_to_model_wrapper(
1029
1044
  improve_diagram_description_prompt,
1030
- query_images=query_images,
1031
- user=user,
1032
1045
  query_files=query_files,
1046
+ query_images=query_images,
1047
+ fast_model=False,
1033
1048
  agent_chat_model=agent_chat_model,
1049
+ user=user,
1034
1050
  tracer=tracer,
1035
1051
  )
1036
1052
  response_text = response.text.strip()
@@ -1059,14 +1075,18 @@ async def generate_mermaidjs_diagram_from_description(
1059
1075
 
1060
1076
  with timer("Chat actor: Generate Mermaid.js diagram", logger):
1061
1077
  raw_response = await send_message_to_model_wrapper(
1062
- query=mermaidjs_diagram_generation, user=user, agent_chat_model=agent_chat_model, tracer=tracer
1078
+ query=mermaidjs_diagram_generation,
1079
+ fast_model=False,
1080
+ agent_chat_model=agent_chat_model,
1081
+ user=user,
1082
+ tracer=tracer,
1063
1083
  )
1064
1084
  return clean_mermaidjs(raw_response.text.strip())
1065
1085
 
1066
1086
 
1067
1087
  async def generate_better_image_prompt(
1068
1088
  q: str,
1069
- conversation_history: str,
1089
+ conversation_history: List[ChatMessageModel],
1070
1090
  location_data: LocationData,
1071
1091
  note_references: List[Dict[str, Any]],
1072
1092
  online_results: Optional[dict] = None,
@@ -1076,12 +1096,11 @@ async def generate_better_image_prompt(
1076
1096
  agent: Agent = None,
1077
1097
  query_files: str = "",
1078
1098
  tracer: dict = {},
1079
- ) -> str:
1099
+ ) -> dict:
1080
1100
  """
1081
1101
  Generate a better image prompt from the given query
1082
1102
  """
1083
1103
 
1084
- today_date = datetime.now(tz=timezone.utc).strftime("%Y-%m-%d, %A")
1085
1104
  personality_context = (
1086
1105
  prompts.personality_context.format(personality=agent.personality) if agent and agent.personality else ""
1087
1106
  )
@@ -1089,58 +1108,53 @@ async def generate_better_image_prompt(
1089
1108
 
1090
1109
  location = f"{location_data}" if location_data else "Unknown"
1091
1110
 
1092
- user_references = "\n\n".join([f"# {item['compiled']}" for item in note_references])
1111
+ user_references = "\n\n".join([f"- text:\n{item['compiled']}" for item in note_references])
1093
1112
 
1094
1113
  simplified_online_results = {}
1114
+ for result in online_results or []:
1115
+ if online_results[result].get("answerBox"):
1116
+ simplified_online_results[result] = online_results[result]["answerBox"]
1117
+ elif online_results[result].get("webpages"):
1118
+ simplified_online_results[result] = online_results[result]["webpages"]
1095
1119
 
1096
- if online_results:
1097
- for result in online_results:
1098
- if online_results[result].get("answerBox"):
1099
- simplified_online_results[result] = online_results[result]["answerBox"]
1100
- elif online_results[result].get("webpages"):
1101
- simplified_online_results[result] = online_results[result]["webpages"]
1120
+ enhance_image_system_message = prompts.enhance_image_system_message.format(
1121
+ location=location,
1122
+ references=user_references,
1123
+ online_results=simplified_online_results or "",
1124
+ personality_context=personality_context,
1125
+ )
1102
1126
 
1103
- if model_type == TextToImageModelConfig.ModelType.OPENAI:
1104
- image_prompt = prompts.image_generation_improve_prompt_dalle.format(
1105
- query=q,
1106
- chat_history=conversation_history,
1107
- location=location,
1108
- current_date=today_date,
1109
- references=user_references,
1110
- online_results=simplified_online_results,
1111
- personality_context=personality_context,
1112
- )
1113
- elif model_type in [
1114
- TextToImageModelConfig.ModelType.STABILITYAI,
1115
- TextToImageModelConfig.ModelType.REPLICATE,
1116
- TextToImageModelConfig.ModelType.GOOGLE,
1117
- ]:
1118
- image_prompt = prompts.image_generation_improve_prompt_sd.format(
1119
- query=q,
1120
- chat_history=conversation_history,
1121
- location=location,
1122
- current_date=today_date,
1123
- references=user_references,
1124
- online_results=simplified_online_results,
1125
- personality_context=personality_context,
1127
+ class ImagePromptResponse(BaseModel):
1128
+ description: str = Field(description="Enhanced image description")
1129
+ shape: ImageShape = Field(
1130
+ description="Aspect ratio/shape best suited to render the image: Portrait, Landscape, or Square"
1126
1131
  )
1127
1132
 
1128
1133
  agent_chat_model = AgentAdapters.get_agent_chat_model(agent, user) if agent else None
1129
1134
 
1130
1135
  with timer("Chat actor: Generate contextual image prompt", logger):
1131
- response = await send_message_to_model_wrapper(
1132
- image_prompt,
1133
- query_images=query_images,
1134
- user=user,
1136
+ raw_response = await send_message_to_model_wrapper(
1137
+ q,
1135
1138
  query_files=query_files,
1139
+ query_images=query_images,
1140
+ chat_history=conversation_history,
1141
+ system_message=enhance_image_system_message,
1142
+ response_type="json_object",
1143
+ response_schema=ImagePromptResponse,
1144
+ fast_model=False,
1136
1145
  agent_chat_model=agent_chat_model,
1146
+ user=user,
1137
1147
  tracer=tracer,
1138
1148
  )
1139
- response_text = response.text.strip()
1140
- if response_text.startswith(('"', "'")) and response_text.endswith(('"', "'")):
1141
- response_text = response_text[1:-1]
1142
1149
 
1143
- return response_text
1150
+ # Parse the structured response
1151
+ try:
1152
+ response = clean_json(raw_response.text)
1153
+ parsed_response = pyjson5.loads(response)
1154
+ return parsed_response
1155
+ except Exception:
1156
+ # Fallback to user query as image description
1157
+ return {"description": q, "shape": ImageShape.SQUARE}
1144
1158
 
1145
1159
 
1146
1160
  async def search_documents(
@@ -1150,7 +1164,7 @@ async def search_documents(
1150
1164
  user: KhojUser,
1151
1165
  chat_history: list[ChatMessageModel],
1152
1166
  conversation_id: str,
1153
- conversation_commands: List[ConversationCommand] = [ConversationCommand.Default],
1167
+ conversation_commands: List[ConversationCommand] = [ConversationCommand.Notes],
1154
1168
  location_data: LocationData = None,
1155
1169
  send_status_func: Optional[Callable] = None,
1156
1170
  query_images: Optional[List[str]] = None,
@@ -1168,19 +1182,12 @@ async def search_documents(
1168
1182
  if agent:
1169
1183
  agent_has_entries = await sync_to_async(EntryAdapters.agent_has_entries)(agent=agent)
1170
1184
 
1171
- if (
1172
- ConversationCommand.Notes not in conversation_commands
1173
- and ConversationCommand.Default not in conversation_commands
1174
- and not agent_has_entries
1175
- ):
1185
+ if ConversationCommand.Notes not in conversation_commands and not agent_has_entries:
1176
1186
  yield compiled_references, inferred_queries, q
1177
1187
  return
1178
1188
 
1179
- # If Notes or Default is not in the conversation command, then the search should be restricted to the agent's knowledge base
1180
- should_limit_to_agent_knowledge = (
1181
- ConversationCommand.Notes not in conversation_commands
1182
- and ConversationCommand.Default not in conversation_commands
1183
- )
1189
+ # If Notes is not in the conversation command, then the search should be restricted to the agent's knowledge base
1190
+ should_limit_to_agent_knowledge = ConversationCommand.Notes not in conversation_commands
1184
1191
 
1185
1192
  if not await sync_to_async(EntryAdapters.user_has_entries)(user=user):
1186
1193
  if not agent_has_entries:
@@ -1209,11 +1216,12 @@ async def search_documents(
1209
1216
  inferred_queries = await extract_questions(
1210
1217
  query=defiltered_query,
1211
1218
  user=user,
1219
+ query_files=query_files,
1220
+ query_images=query_images,
1212
1221
  personality_context=personality_context,
1213
- chat_history=chat_history,
1214
1222
  location_data=location_data,
1215
- query_images=query_images,
1216
- query_files=query_files,
1223
+ chat_history=chat_history,
1224
+ agent=agent,
1217
1225
  tracer=tracer,
1218
1226
  )
1219
1227
 
@@ -1259,12 +1267,13 @@ async def search_documents(
1259
1267
  async def extract_questions(
1260
1268
  query: str,
1261
1269
  user: KhojUser,
1270
+ query_files: str = None,
1271
+ query_images: Optional[List[str]] = None,
1262
1272
  personality_context: str = "",
1263
- chat_history: List[ChatMessageModel] = [],
1264
1273
  location_data: LocationData = None,
1265
- query_images: Optional[List[str]] = None,
1266
- query_files: str = None,
1274
+ chat_history: List[ChatMessageModel] = [],
1267
1275
  max_queries: int = 5,
1276
+ agent: Agent = None,
1268
1277
  tracer: dict = {},
1269
1278
  ):
1270
1279
  """
@@ -1309,13 +1318,17 @@ async def extract_questions(
1309
1318
  description="List of semantic search queries to run on user documents.",
1310
1319
  )
1311
1320
 
1321
+ agent_chat_model = AgentAdapters.get_agent_chat_model(agent, user) if agent else None
1322
+
1312
1323
  raw_response = await send_message_to_model_wrapper(
1313
- system_message=system_prompt,
1314
1324
  query=prompt,
1315
- query_images=query_images,
1316
1325
  query_files=query_files,
1326
+ query_images=query_images,
1327
+ system_message=system_prompt,
1317
1328
  response_type="json_object",
1318
1329
  response_schema=DocumentQueries,
1330
+ fast_model=False,
1331
+ agent_chat_model=agent_chat_model,
1319
1332
  user=user,
1320
1333
  tracer=tracer,
1321
1334
  )
@@ -1428,21 +1441,26 @@ async def execute_search(
1428
1441
 
1429
1442
 
1430
1443
  async def send_message_to_model_wrapper(
1444
+ # Context
1431
1445
  query: str,
1446
+ query_files: str = None,
1447
+ query_images: List[str] = None,
1448
+ context: str = "",
1449
+ chat_history: list[ChatMessageModel] = [],
1432
1450
  system_message: str = "",
1451
+ # Model Config
1433
1452
  response_type: str = "text",
1434
1453
  response_schema: BaseModel = None,
1435
1454
  tools: List[ToolDefinition] = None,
1436
1455
  deepthought: bool = False,
1437
- user: KhojUser = None,
1438
- query_images: List[str] = None,
1439
- context: str = "",
1440
- query_files: str = None,
1441
- chat_history: list[ChatMessageModel] = [],
1456
+ fast_model: Optional[bool] = None,
1442
1457
  agent_chat_model: ChatModel = None,
1458
+ # User
1459
+ user: KhojUser = None,
1460
+ # Tracer
1443
1461
  tracer: dict = {},
1444
1462
  ):
1445
- chat_model: ChatModel = await ConversationAdapters.aget_default_chat_model(user, agent_chat_model)
1463
+ chat_model: ChatModel = await ConversationAdapters.aget_default_chat_model(user, agent_chat_model, fast=fast_model)
1446
1464
  vision_available = chat_model.vision_enabled
1447
1465
  if not vision_available and query_images:
1448
1466
  logger.warning(f"Vision is not enabled for default model: {chat_model.name}.")
@@ -1463,16 +1481,16 @@ async def send_message_to_model_wrapper(
1463
1481
 
1464
1482
  truncated_messages = generate_chatml_messages_with_context(
1465
1483
  user_message=query,
1484
+ query_files=query_files,
1485
+ query_images=query_images,
1466
1486
  context_message=context,
1467
- system_message=system_message,
1468
1487
  chat_history=chat_history,
1488
+ system_message=system_message,
1469
1489
  model_name=chat_model_name,
1490
+ model_type=model_type,
1470
1491
  tokenizer_name=tokenizer,
1471
1492
  max_prompt_size=max_tokens,
1472
1493
  vision_enabled=vision_available,
1473
- query_images=query_images,
1474
- model_type=model_type,
1475
- query_files=query_files,
1476
1494
  )
1477
1495
 
1478
1496
  if model_type == ChatModel.ModelType.OPENAI:
@@ -1540,14 +1558,14 @@ def send_message_to_model_wrapper_sync(
1540
1558
 
1541
1559
  truncated_messages = generate_chatml_messages_with_context(
1542
1560
  user_message=message,
1543
- system_message=system_message,
1561
+ query_files=query_files,
1562
+ query_images=query_images,
1544
1563
  chat_history=chat_history,
1564
+ system_message=system_message,
1545
1565
  model_name=chat_model_name,
1566
+ model_type=model_type,
1546
1567
  max_prompt_size=max_tokens,
1547
1568
  vision_enabled=vision_available,
1548
- model_type=model_type,
1549
- query_images=query_images,
1550
- query_files=query_files,
1551
1569
  )
1552
1570
 
1553
1571
  if model_type == ChatModel.ModelType.OPENAI:
@@ -1585,6 +1603,105 @@ def send_message_to_model_wrapper_sync(
1585
1603
  raise HTTPException(status_code=500, detail="Invalid conversation config")
1586
1604
 
1587
1605
 
1606
+ def build_conversation_context(
1607
+ # Query and Context
1608
+ user_query: str,
1609
+ references: List[Dict],
1610
+ online_results: Dict[str, Dict],
1611
+ code_results: Dict[str, Dict],
1612
+ operator_results: List[OperatorRun],
1613
+ query_files: str = None,
1614
+ query_images: Optional[List[str]] = None,
1615
+ generated_asset_results: Dict[str, Dict] = {},
1616
+ program_execution_context: List[str] = None,
1617
+ chat_history: List[ChatMessageModel] = [],
1618
+ location_data: LocationData = None,
1619
+ user_name: str = None,
1620
+ # Model config
1621
+ agent: Agent = None,
1622
+ model_name: str = None,
1623
+ model_type: ChatModel.ModelType = None,
1624
+ max_prompt_size: int = None,
1625
+ tokenizer_name: str = None,
1626
+ vision_available: bool = False,
1627
+ ) -> List[ChatMessage]:
1628
+ """
1629
+ Construct system, context and chatml messages for chat response.
1630
+ Share common logic across different model types.
1631
+
1632
+ Returns:
1633
+ List of ChatMessages with context
1634
+ """
1635
+ # Initialize Variables
1636
+ current_date = datetime.now()
1637
+
1638
+ # Build system prompt
1639
+ if agent and agent.personality:
1640
+ system_prompt = prompts.custom_personality.format(
1641
+ name=agent.name,
1642
+ bio=agent.personality,
1643
+ current_date=current_date.strftime("%Y-%m-%d"),
1644
+ day_of_week=current_date.strftime("%A"),
1645
+ )
1646
+ else:
1647
+ system_prompt = prompts.personality.format(
1648
+ current_date=current_date.strftime("%Y-%m-%d"),
1649
+ day_of_week=current_date.strftime("%A"),
1650
+ )
1651
+
1652
+ # Add Gemini-specific personality enhancement
1653
+ if model_type == ChatModel.ModelType.GOOGLE:
1654
+ system_prompt += f"\n\n{prompts.gemini_verbose_language_personality}"
1655
+
1656
+ # Add location context if available
1657
+ if location_data:
1658
+ location_prompt = prompts.user_location.format(location=f"{location_data}")
1659
+ system_prompt += f"\n{location_prompt}"
1660
+
1661
+ # Add user name context if available
1662
+ if user_name:
1663
+ user_name_prompt = prompts.user_name.format(name=user_name)
1664
+ system_prompt += f"\n{user_name_prompt}"
1665
+
1666
+ # Build context message
1667
+ context_message = ""
1668
+ if not is_none_or_empty(references):
1669
+ context_message = f"{prompts.notes_conversation.format(references=yaml_dump(references))}\n\n"
1670
+ if not is_none_or_empty(online_results):
1671
+ context_message += f"{prompts.online_search_conversation.format(online_results=yaml_dump(online_results))}\n\n"
1672
+ if not is_none_or_empty(code_results):
1673
+ context_message += (
1674
+ f"{prompts.code_executed_context.format(code_results=truncate_code_context(code_results))}\n\n"
1675
+ )
1676
+ if not is_none_or_empty(operator_results):
1677
+ operator_content = [
1678
+ {"query": oc.query, "response": oc.response, "webpages": oc.webpages} for oc in operator_results
1679
+ ]
1680
+ context_message += (
1681
+ f"{prompts.operator_execution_context.format(operator_results=yaml_dump(operator_content))}\n\n"
1682
+ )
1683
+ context_message = context_message.strip()
1684
+
1685
+ # Generate the chatml messages
1686
+ messages = generate_chatml_messages_with_context(
1687
+ user_message=user_query,
1688
+ query_files=query_files,
1689
+ query_images=query_images,
1690
+ context_message=context_message,
1691
+ generated_asset_results=generated_asset_results,
1692
+ program_execution_context=program_execution_context,
1693
+ chat_history=chat_history,
1694
+ system_message=system_prompt,
1695
+ model_name=model_name,
1696
+ model_type=model_type,
1697
+ max_prompt_size=max_prompt_size,
1698
+ tokenizer_name=tokenizer_name,
1699
+ vision_enabled=vision_available,
1700
+ )
1701
+
1702
+ return messages
1703
+
1704
+
1588
1705
  async def agenerate_chat_response(
1589
1706
  q: str,
1590
1707
  chat_history: List[ChatMessageModel],
@@ -1599,7 +1716,6 @@ async def agenerate_chat_response(
1599
1716
  user_name: Optional[str] = None,
1600
1717
  query_images: Optional[List[str]] = None,
1601
1718
  query_files: str = None,
1602
- raw_generated_files: List[FileAttachment] = [],
1603
1719
  program_execution_context: List[str] = [],
1604
1720
  generated_asset_results: Dict[str, Dict] = {},
1605
1721
  is_subscribed: bool = False,
@@ -1633,34 +1749,39 @@ async def agenerate_chat_response(
1633
1749
  chat_model = vision_enabled_config
1634
1750
  vision_available = True
1635
1751
 
1752
+ # Build shared conversation context and generate chatml messages
1753
+ messages = build_conversation_context(
1754
+ user_query=query_to_run,
1755
+ references=compiled_references,
1756
+ online_results=online_results,
1757
+ code_results=code_results,
1758
+ operator_results=operator_results,
1759
+ query_files=query_files,
1760
+ query_images=query_images,
1761
+ generated_asset_results=generated_asset_results,
1762
+ program_execution_context=program_execution_context,
1763
+ chat_history=chat_history,
1764
+ location_data=location_data,
1765
+ user_name=user_name,
1766
+ agent=agent,
1767
+ model_type=chat_model.model_type,
1768
+ model_name=chat_model.name,
1769
+ max_prompt_size=max_prompt_size,
1770
+ tokenizer_name=chat_model.tokenizer,
1771
+ vision_available=vision_available,
1772
+ )
1773
+
1636
1774
  if chat_model.model_type == ChatModel.ModelType.OPENAI:
1637
1775
  openai_chat_config = chat_model.ai_model_api
1638
1776
  api_key = openai_chat_config.api_key
1639
1777
  chat_model_name = chat_model.name
1640
1778
  chat_response_generator = converse_openai(
1641
- # Query
1642
- query_to_run,
1643
- # Context
1644
- references=compiled_references,
1645
- online_results=online_results,
1646
- code_results=code_results,
1647
- operator_results=operator_results,
1648
- query_images=query_images,
1649
- query_files=query_files,
1650
- generated_files=raw_generated_files,
1651
- generated_asset_results=generated_asset_results,
1652
- program_execution_context=program_execution_context,
1653
- location_data=location_data,
1654
- user_name=user_name,
1655
- chat_history=chat_history,
1779
+ # Query + Context Messages
1780
+ messages,
1656
1781
  # Model
1657
1782
  model=chat_model_name,
1658
1783
  api_key=api_key,
1659
1784
  api_base_url=openai_chat_config.api_base_url,
1660
- max_prompt_size=max_prompt_size,
1661
- tokenizer_name=chat_model.tokenizer,
1662
- agent=agent,
1663
- vision_available=vision_available,
1664
1785
  deepthought=deepthought,
1665
1786
  tracer=tracer,
1666
1787
  )
@@ -1669,29 +1790,12 @@ async def agenerate_chat_response(
1669
1790
  api_key = chat_model.ai_model_api.api_key
1670
1791
  api_base_url = chat_model.ai_model_api.api_base_url
1671
1792
  chat_response_generator = converse_anthropic(
1672
- # Query
1673
- query_to_run,
1674
- # Context
1675
- references=compiled_references,
1676
- online_results=online_results,
1677
- code_results=code_results,
1678
- operator_results=operator_results,
1679
- query_images=query_images,
1680
- query_files=query_files,
1681
- generated_files=raw_generated_files,
1682
- generated_asset_results=generated_asset_results,
1683
- program_execution_context=program_execution_context,
1684
- location_data=location_data,
1685
- user_name=user_name,
1686
- chat_history=chat_history,
1793
+ # Query + Context Messages
1794
+ messages,
1687
1795
  # Model
1688
1796
  model=chat_model.name,
1689
1797
  api_key=api_key,
1690
1798
  api_base_url=api_base_url,
1691
- max_prompt_size=max_prompt_size,
1692
- tokenizer_name=chat_model.tokenizer,
1693
- agent=agent,
1694
- vision_available=vision_available,
1695
1799
  deepthought=deepthought,
1696
1800
  tracer=tracer,
1697
1801
  )
@@ -1699,29 +1803,12 @@ async def agenerate_chat_response(
1699
1803
  api_key = chat_model.ai_model_api.api_key
1700
1804
  api_base_url = chat_model.ai_model_api.api_base_url
1701
1805
  chat_response_generator = converse_gemini(
1702
- # Query
1703
- query_to_run,
1704
- # Context
1705
- references=compiled_references,
1706
- online_results=online_results,
1707
- code_results=code_results,
1708
- operator_results=operator_results,
1709
- query_images=query_images,
1710
- query_files=query_files,
1711
- generated_files=raw_generated_files,
1712
- generated_asset_results=generated_asset_results,
1713
- program_execution_context=program_execution_context,
1714
- location_data=location_data,
1715
- user_name=user_name,
1716
- chat_history=chat_history,
1806
+ # Query + Context Messages
1807
+ messages,
1717
1808
  # Model
1718
1809
  model=chat_model.name,
1719
1810
  api_key=api_key,
1720
1811
  api_base_url=api_base_url,
1721
- max_prompt_size=max_prompt_size,
1722
- tokenizer_name=chat_model.tokenizer,
1723
- agent=agent,
1724
- vision_available=vision_available,
1725
1812
  deepthought=deepthought,
1726
1813
  tracer=tracer,
1727
1814
  )