khoj 1.36.7.dev18__py3-none-any.whl → 1.36.7.dev24__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 (63) hide show
  1. khoj/database/migrations/0086_alter_texttoimagemodelconfig_model_type.py +26 -0
  2. khoj/database/models/__init__.py +4 -3
  3. khoj/interface/compiled/404/index.html +2 -2
  4. khoj/interface/compiled/_next/static/chunks/{2327-02e86a50c65e575a.js → 2327-36d17f2483e80f60.js} +1 -1
  5. khoj/interface/compiled/_next/static/chunks/{8155-ad130153ddcc930f.js → 8155-87b4d2ea2cf725cc.js} +1 -1
  6. khoj/interface/compiled/_next/static/chunks/app/agents/{layout-4a0e32561d6b1e27.js → layout-447b58869479276c.js} +1 -1
  7. khoj/interface/compiled/_next/static/chunks/app/agents/{page-fbe2c1c661cd14ac.js → page-2f55f9d0da49bf31.js} +1 -1
  8. khoj/interface/compiled/_next/static/chunks/app/automations/{page-ad620b194fd508fe.js → page-d0a630a2b4ecc41d.js} +1 -1
  9. khoj/interface/compiled/_next/static/chunks/app/chat/{layout-9e151fb837f53026.js → layout-4d0b1ba93124fccb.js} +1 -1
  10. khoj/interface/compiled/_next/static/chunks/app/chat/{page-24bf7a5c917dbaff.js → page-9c0a2df3e33c029b.js} +1 -1
  11. khoj/interface/compiled/_next/static/chunks/app/{layout-26139159e500852a.js → layout-6dba801826c4fe59.js} +1 -1
  12. khoj/interface/compiled/_next/static/chunks/app/{page-528e96d17e520304.js → page-642bd02fc4f16606.js} +1 -1
  13. khoj/interface/compiled/_next/static/chunks/app/search/{layout-66f736b858b38c2c.js → layout-ff081947c70ea9b7.js} +1 -1
  14. khoj/interface/compiled/_next/static/chunks/app/search/{page-30e231665f1f3796.js → page-7c80e369ee1cdfad.js} +1 -1
  15. khoj/interface/compiled/_next/static/chunks/app/settings/{page-c580520d59d92267.js → page-c961681e308a334b.js} +1 -1
  16. khoj/interface/compiled/_next/static/chunks/app/share/chat/{layout-2ce0cb95b1219d97.js → layout-94a33aa0eae034fc.js} +1 -1
  17. khoj/interface/compiled/_next/static/chunks/app/share/chat/{page-975934b12916f3d4.js → page-6253896a84300e9b.js} +1 -1
  18. khoj/interface/compiled/_next/static/chunks/{webpack-32b7cc428f0d015b.js → webpack-7b1a2ab7f2164f9f.js} +1 -1
  19. khoj/interface/compiled/_next/static/css/55d4a822f8d94b67.css +1 -0
  20. khoj/interface/compiled/_next/static/css/7889a30fe9c83846.css +1 -0
  21. khoj/interface/compiled/_next/static/css/804ceddd6c935d4a.css +1 -0
  22. khoj/interface/compiled/_next/static/css/b15666ef52060cd0.css +1 -0
  23. khoj/interface/compiled/_next/static/media/2aa11a72f7f24b58-s.woff2 +0 -0
  24. khoj/interface/compiled/_next/static/media/383a65b63658737d-s.woff2 +0 -0
  25. khoj/interface/compiled/_next/static/media/40381518f67e6cb9-s.p.woff2 +0 -0
  26. khoj/interface/compiled/_next/static/media/85fe2766c5e6072a-s.woff2 +0 -0
  27. khoj/interface/compiled/_next/static/media/8a6e4d7cd15e805a-s.woff2 +0 -0
  28. khoj/interface/compiled/agents/index.html +3 -3
  29. khoj/interface/compiled/agents/index.txt +3 -3
  30. khoj/interface/compiled/automations/index.html +2 -2
  31. khoj/interface/compiled/automations/index.txt +3 -3
  32. khoj/interface/compiled/chat/index.html +3 -3
  33. khoj/interface/compiled/chat/index.txt +3 -3
  34. khoj/interface/compiled/index.html +2 -2
  35. khoj/interface/compiled/index.txt +3 -3
  36. khoj/interface/compiled/search/index.html +2 -2
  37. khoj/interface/compiled/search/index.txt +3 -3
  38. khoj/interface/compiled/settings/index.html +3 -3
  39. khoj/interface/compiled/settings/index.txt +2 -2
  40. khoj/interface/compiled/share/chat/index.html +3 -3
  41. khoj/interface/compiled/share/chat/index.txt +3 -3
  42. khoj/processor/conversation/anthropic/anthropic_chat.py +6 -3
  43. khoj/processor/conversation/anthropic/utils.py +48 -13
  44. khoj/processor/conversation/google/gemini_chat.py +7 -7
  45. khoj/processor/conversation/google/utils.py +67 -67
  46. khoj/processor/conversation/utils.py +6 -0
  47. khoj/processor/image/generate.py +35 -0
  48. khoj/routers/helpers.py +10 -1
  49. khoj/routers/research.py +1 -0
  50. khoj/utils/constants.py +4 -1
  51. khoj/utils/helpers.py +1 -1
  52. {khoj-1.36.7.dev18.dist-info → khoj-1.36.7.dev24.dist-info}/METADATA +6 -6
  53. {khoj-1.36.7.dev18.dist-info → khoj-1.36.7.dev24.dist-info}/RECORD +58 -53
  54. khoj/interface/compiled/_next/static/css/017016e6eff88fdc.css +0 -1
  55. khoj/interface/compiled/_next/static/css/089de1d8526b96e9.css +0 -1
  56. khoj/interface/compiled/_next/static/css/440ae0f0f650dc35.css +0 -1
  57. khoj/interface/compiled/_next/static/css/f29752d6e1be7624.css +0 -1
  58. khoj/interface/compiled/_next/static/media/e098aaaecc9cfbb2-s.p.woff2 +0 -0
  59. /khoj/interface/compiled/_next/static/{SaAC14w_LNITF1YZanO67 → JlTaPPygeGH7fM43TTTYd}/_buildManifest.js +0 -0
  60. /khoj/interface/compiled/_next/static/{SaAC14w_LNITF1YZanO67 → JlTaPPygeGH7fM43TTTYd}/_ssgManifest.js +0 -0
  61. {khoj-1.36.7.dev18.dist-info → khoj-1.36.7.dev24.dist-info}/WHEEL +0 -0
  62. {khoj-1.36.7.dev18.dist-info → khoj-1.36.7.dev24.dist-info}/entry_points.txt +0 -0
  63. {khoj-1.36.7.dev18.dist-info → khoj-1.36.7.dev24.dist-info}/licenses/LICENSE +0 -0
@@ -34,7 +34,7 @@ def extract_questions_gemini(
34
34
  model: Optional[str] = "gemini-2.0-flash",
35
35
  conversation_log={},
36
36
  api_key=None,
37
- temperature=0.2,
37
+ temperature=0.6,
38
38
  max_tokens=None,
39
39
  location_data: LocationData = None,
40
40
  user: KhojUser = None,
@@ -121,14 +121,14 @@ def gemini_send_message_to_model(
121
121
  api_key,
122
122
  model,
123
123
  response_type="text",
124
- temperature=0.2,
124
+ temperature=0.6,
125
125
  model_kwargs=None,
126
126
  tracer={},
127
127
  ):
128
128
  """
129
129
  Send message to model
130
130
  """
131
- messages, system_prompt = format_messages_for_gemini(messages)
131
+ messages_for_gemini, system_prompt = format_messages_for_gemini(messages)
132
132
 
133
133
  model_kwargs = {}
134
134
 
@@ -138,7 +138,7 @@ def gemini_send_message_to_model(
138
138
 
139
139
  # Get Response from Gemini
140
140
  return gemini_completion_with_backoff(
141
- messages=messages,
141
+ messages=messages_for_gemini,
142
142
  system_prompt=system_prompt,
143
143
  model_name=model,
144
144
  api_key=api_key,
@@ -156,7 +156,7 @@ def converse_gemini(
156
156
  conversation_log={},
157
157
  model: Optional[str] = "gemini-2.0-flash",
158
158
  api_key: Optional[str] = None,
159
- temperature: float = 0.2,
159
+ temperature: float = 0.6,
160
160
  completion_func=None,
161
161
  conversation_commands=[ConversationCommand.Default],
162
162
  max_prompt_size=None,
@@ -236,12 +236,12 @@ def converse_gemini(
236
236
  program_execution_context=program_execution_context,
237
237
  )
238
238
 
239
- messages, system_prompt = format_messages_for_gemini(messages, system_prompt)
239
+ messages_for_gemini, system_prompt = format_messages_for_gemini(messages, system_prompt)
240
240
  logger.debug(f"Conversation Context for Gemini: {messages_to_print(messages)}")
241
241
 
242
242
  # Get Response from Google AI
243
243
  return gemini_chat_completion_with_backoff(
244
- messages=messages,
244
+ messages=messages_for_gemini,
245
245
  compiled_references=references,
246
246
  online_results=online_results,
247
247
  model_name=model,
@@ -1,15 +1,11 @@
1
1
  import logging
2
2
  import random
3
+ from copy import deepcopy
3
4
  from threading import Thread
4
5
 
5
- import google.generativeai as genai
6
- from google.generativeai.types.answer_types import FinishReason
7
- from google.generativeai.types.generation_types import StopCandidateException
8
- from google.generativeai.types.safety_types import (
9
- HarmBlockThreshold,
10
- HarmCategory,
11
- HarmProbability,
12
- )
6
+ from google import genai
7
+ from google.genai import errors as gerrors
8
+ from google.genai import types as gtypes
13
9
  from langchain.schema import ChatMessage
14
10
  from tenacity import (
15
11
  before_sleep_log,
@@ -24,7 +20,6 @@ from khoj.processor.conversation.utils import (
24
20
  commit_conversation_trace,
25
21
  get_image_from_url,
26
22
  )
27
- from khoj.utils import state
28
23
  from khoj.utils.helpers import (
29
24
  get_chat_usage_metrics,
30
25
  is_none_or_empty,
@@ -35,6 +30,24 @@ logger = logging.getLogger(__name__)
35
30
 
36
31
 
37
32
  MAX_OUTPUT_TOKENS_GEMINI = 8192
33
+ SAFETY_SETTINGS = [
34
+ gtypes.SafetySetting(
35
+ category=gtypes.HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT,
36
+ threshold=gtypes.HarmBlockThreshold.BLOCK_ONLY_HIGH,
37
+ ),
38
+ gtypes.SafetySetting(
39
+ category=gtypes.HarmCategory.HARM_CATEGORY_HARASSMENT,
40
+ threshold=gtypes.HarmBlockThreshold.BLOCK_ONLY_HIGH,
41
+ ),
42
+ gtypes.SafetySetting(
43
+ category=gtypes.HarmCategory.HARM_CATEGORY_HATE_SPEECH,
44
+ threshold=gtypes.HarmBlockThreshold.BLOCK_ONLY_HIGH,
45
+ ),
46
+ gtypes.SafetySetting(
47
+ category=gtypes.HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT,
48
+ threshold=gtypes.HarmBlockThreshold.BLOCK_ONLY_HIGH,
49
+ ),
50
+ ]
38
51
 
39
52
 
40
53
  @retry(
@@ -46,32 +59,21 @@ MAX_OUTPUT_TOKENS_GEMINI = 8192
46
59
  def gemini_completion_with_backoff(
47
60
  messages, system_prompt, model_name, temperature=0, api_key=None, model_kwargs=None, tracer={}
48
61
  ) -> str:
49
- genai.configure(api_key=api_key)
50
- model_kwargs = model_kwargs or dict()
51
- model_kwargs["temperature"] = temperature
52
- model_kwargs["max_output_tokens"] = MAX_OUTPUT_TOKENS_GEMINI
53
- model = genai.GenerativeModel(
54
- model_name,
55
- generation_config=model_kwargs,
62
+ client = genai.Client(api_key=api_key)
63
+ config = gtypes.GenerateContentConfig(
56
64
  system_instruction=system_prompt,
57
- safety_settings={
58
- HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT: HarmBlockThreshold.BLOCK_ONLY_HIGH,
59
- HarmCategory.HARM_CATEGORY_HARASSMENT: HarmBlockThreshold.BLOCK_ONLY_HIGH,
60
- HarmCategory.HARM_CATEGORY_HATE_SPEECH: HarmBlockThreshold.BLOCK_ONLY_HIGH,
61
- HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT: HarmBlockThreshold.BLOCK_ONLY_HIGH,
62
- },
65
+ temperature=temperature,
66
+ max_output_tokens=MAX_OUTPUT_TOKENS_GEMINI,
67
+ safety_settings=SAFETY_SETTINGS,
63
68
  )
64
69
 
65
- formatted_messages = [{"role": message.role, "parts": message.content} for message in messages]
66
-
67
- # Start chat session. All messages up to the last are considered to be part of the chat history
68
- chat_session = model.start_chat(history=formatted_messages[0:-1])
70
+ formatted_messages = [gtypes.Content(role=message.role, parts=message.content) for message in messages]
69
71
 
70
72
  try:
71
- # Generate the response. The last message is considered to be the current prompt
72
- response = chat_session.send_message(formatted_messages[-1]["parts"])
73
+ # Generate the response
74
+ response = client.models.generate_content(model=model_name, config=config, contents=formatted_messages)
73
75
  response_text = response.text
74
- except StopCandidateException as e:
76
+ except gerrors.ClientError as e:
75
77
  response = None
76
78
  response_text, _ = handle_gemini_response(e.args)
77
79
  # Respond with reason for stopping
@@ -125,36 +127,27 @@ def gemini_llm_thread(
125
127
  g, messages, system_prompt, model_name, temperature, api_key, model_kwargs=None, tracer: dict = {}
126
128
  ):
127
129
  try:
128
- genai.configure(api_key=api_key)
129
- model_kwargs = model_kwargs or dict()
130
- model_kwargs["temperature"] = temperature
131
- model_kwargs["max_output_tokens"] = MAX_OUTPUT_TOKENS_GEMINI
132
- model_kwargs["stop_sequences"] = ["Notes:\n["]
133
- model = genai.GenerativeModel(
134
- model_name,
135
- generation_config=model_kwargs,
130
+ client = genai.Client(api_key=api_key)
131
+ config = gtypes.GenerateContentConfig(
136
132
  system_instruction=system_prompt,
137
- safety_settings={
138
- HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT: HarmBlockThreshold.BLOCK_ONLY_HIGH,
139
- HarmCategory.HARM_CATEGORY_HARASSMENT: HarmBlockThreshold.BLOCK_ONLY_HIGH,
140
- HarmCategory.HARM_CATEGORY_HATE_SPEECH: HarmBlockThreshold.BLOCK_ONLY_HIGH,
141
- HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT: HarmBlockThreshold.BLOCK_ONLY_HIGH,
142
- },
133
+ temperature=temperature,
134
+ max_output_tokens=MAX_OUTPUT_TOKENS_GEMINI,
135
+ stop_sequences=["Notes:\n["],
136
+ safety_settings=SAFETY_SETTINGS,
143
137
  )
144
138
 
145
139
  aggregated_response = ""
146
- formatted_messages = [{"role": message.role, "parts": message.content} for message in messages]
140
+ formatted_messages = [gtypes.Content(role=message.role, parts=message.content) for message in messages]
147
141
 
148
- # all messages up to the last are considered to be part of the chat history
149
- chat_session = model.start_chat(history=formatted_messages[0:-1])
150
- # the last message is considered to be the current prompt
151
- for chunk in chat_session.send_message(formatted_messages[-1]["parts"], stream=True):
142
+ for chunk in client.models.generate_content_stream(
143
+ model=model_name, config=config, contents=formatted_messages
144
+ ):
152
145
  message, stopped = handle_gemini_response(chunk.candidates, chunk.prompt_feedback)
153
146
  message = message or chunk.text
154
147
  aggregated_response += message
155
148
  g.send(message)
156
149
  if stopped:
157
- raise StopCandidateException(message)
150
+ raise ValueError(message)
158
151
 
159
152
  # Calculate cost of chat
160
153
  input_tokens = chunk.usage_metadata.prompt_token_count
@@ -166,7 +159,7 @@ def gemini_llm_thread(
166
159
  tracer["temperature"] = temperature
167
160
  if is_promptrace_enabled():
168
161
  commit_conversation_trace(messages, aggregated_response, tracer)
169
- except StopCandidateException as e:
162
+ except ValueError as e:
170
163
  logger.warning(
171
164
  f"LLM Response Prevented for {model_name}: {e.args[0]}.\n"
172
165
  + f"Last Message by {messages[-1].role}: {messages[-1].content}"
@@ -177,14 +170,16 @@ def gemini_llm_thread(
177
170
  g.close()
178
171
 
179
172
 
180
- def handle_gemini_response(candidates, prompt_feedback=None):
173
+ def handle_gemini_response(
174
+ candidates: list[gtypes.Candidate], prompt_feedback: gtypes.GenerateContentResponsePromptFeedback = None
175
+ ):
181
176
  """Check if Gemini response was blocked and return an explanatory error message."""
182
177
  # Check if the response was blocked due to safety concerns with the prompt
183
178
  if len(candidates) == 0 and prompt_feedback:
184
179
  message = f"\nI'd prefer to not respond to that due to **{prompt_feedback.block_reason.name}** issues with your query."
185
180
  stopped = True
186
181
  # Check if the response was blocked due to safety concerns with the generated content
187
- elif candidates[0].finish_reason == FinishReason.SAFETY:
182
+ elif candidates[0].finish_reason == gtypes.FinishReason.SAFETY:
188
183
  message = generate_safety_response(candidates[0].safety_ratings)
189
184
  stopped = True
190
185
  # Check if finish reason is empty, therefore generation is in progress
@@ -192,7 +187,7 @@ def handle_gemini_response(candidates, prompt_feedback=None):
192
187
  message = None
193
188
  stopped = False
194
189
  # Check if the response was stopped due to reaching maximum token limit or other reasons
195
- elif candidates[0].finish_reason != FinishReason.STOP:
190
+ elif candidates[0].finish_reason != gtypes.FinishReason.STOP:
196
191
  message = f"\nI can't talk further about that because of **{candidates[0].finish_reason.name} issue.**"
197
192
  stopped = True
198
193
  # Otherwise, the response is valid and can be used
@@ -202,18 +197,18 @@ def handle_gemini_response(candidates, prompt_feedback=None):
202
197
  return message, stopped
203
198
 
204
199
 
205
- def generate_safety_response(safety_ratings):
200
+ def generate_safety_response(safety_ratings: list[gtypes.SafetyRating]):
206
201
  """Generate a conversational response based on the safety ratings of the response."""
207
202
  # Get the safety rating with the highest probability
208
- max_safety_rating = sorted(safety_ratings, key=lambda x: x.probability, reverse=True)[0]
203
+ max_safety_rating: gtypes.SafetyRating = sorted(safety_ratings, key=lambda x: x.probability, reverse=True)[0]
209
204
  # Remove the "HARM_CATEGORY_" prefix and title case the category name
210
205
  max_safety_category = " ".join(max_safety_rating.category.name.split("_")[2:]).title()
211
206
  # Add a bit of variety to the discomfort level based on the safety rating probability
212
207
  discomfort_level = {
213
- HarmProbability.HARM_PROBABILITY_UNSPECIFIED: " ",
214
- HarmProbability.LOW: "a bit ",
215
- HarmProbability.MEDIUM: "moderately ",
216
- HarmProbability.HIGH: random.choice(["very ", "quite ", "fairly "]),
208
+ gtypes.HarmProbability.HARM_PROBABILITY_UNSPECIFIED: " ",
209
+ gtypes.HarmProbability.LOW: "a bit ",
210
+ gtypes.HarmProbability.MEDIUM: "moderately ",
211
+ gtypes.HarmProbability.HIGH: random.choice(["very ", "quite ", "fairly "]),
217
212
  }[max_safety_rating.probability]
218
213
  # Generate a response using a random response template
219
214
  safety_response_choice = random.choice(
@@ -229,9 +224,12 @@ def generate_safety_response(safety_ratings):
229
224
  )
230
225
 
231
226
 
232
- def format_messages_for_gemini(messages: list[ChatMessage], system_prompt: str = None) -> tuple[list[str], str]:
227
+ def format_messages_for_gemini(
228
+ original_messages: list[ChatMessage], system_prompt: str = None
229
+ ) -> tuple[list[str], str]:
233
230
  # Extract system message
234
231
  system_prompt = system_prompt or ""
232
+ messages = deepcopy(original_messages)
235
233
  for message in messages.copy():
236
234
  if message.role == "system":
237
235
  system_prompt += message.content
@@ -242,14 +240,16 @@ def format_messages_for_gemini(messages: list[ChatMessage], system_prompt: str =
242
240
  # Convert message content to string list from chatml dictionary list
243
241
  if isinstance(message.content, list):
244
242
  # Convert image_urls to PIL.Image and place them at beginning of list (better for Gemini)
245
- message.content = [
246
- get_image_from_url(item["image_url"]["url"]).content
247
- if item["type"] == "image_url"
248
- else item.get("text", "")
249
- for item in sorted(message.content, key=lambda x: 0 if x["type"] == "image_url" else 1)
250
- ]
243
+ message_content = []
244
+ for item in sorted(message.content, key=lambda x: 0 if x["type"] == "image_url" else 1):
245
+ if item["type"] == "image_url":
246
+ image = get_image_from_url(item["image_url"]["url"], type="bytes")
247
+ message_content += [gtypes.Part.from_bytes(data=image.content, mime_type=image.type)]
248
+ else:
249
+ message_content += [gtypes.Part.from_text(text=item.get("text", ""))]
250
+ message.content = message_content
251
251
  elif isinstance(message.content, str):
252
- message.content = [message.content]
252
+ message.content = [gtypes.Part.from_text(text=message.content)]
253
253
 
254
254
  if message.role == "assistant":
255
255
  message.role = "model"
@@ -61,6 +61,9 @@ model_to_prompt_size = {
61
61
  "gemini-1.5-pro": 60000,
62
62
  # Anthropic Models
63
63
  "claude-3-5-sonnet-20241022": 60000,
64
+ "claude-3-5-sonnet-latest": 60000,
65
+ "claude-3-7-sonnet-20250219": 60000,
66
+ "claude-3-7-sonnet-latest": 60000,
64
67
  "claude-3-5-haiku-20241022": 60000,
65
68
  # Offline Models
66
69
  "bartowski/Qwen2.5-14B-Instruct-GGUF": 20000,
@@ -670,10 +673,13 @@ def get_image_from_url(image_url: str, type="pil"):
670
673
  content_type = response.headers.get("content-type") or mimetypes.guess_type(image_url)[0] or "image/webp"
671
674
 
672
675
  # Convert image to desired format
676
+ image_data: Any = None
673
677
  if type == "b64":
674
678
  image_data = base64.b64encode(response.content).decode("utf-8")
675
679
  elif type == "pil":
676
680
  image_data = PIL.Image.open(BytesIO(response.content))
681
+ elif type == "bytes":
682
+ image_data = response.content
677
683
  else:
678
684
  raise ValueError(f"Invalid image type: {type}")
679
685
 
@@ -6,6 +6,8 @@ from typing import Any, Callable, Dict, List, Optional
6
6
 
7
7
  import openai
8
8
  import requests
9
+ from google import genai
10
+ from google.genai import types as gtypes
9
11
 
10
12
  from khoj.database.adapters import ConversationAdapters
11
13
  from khoj.database.models import Agent, KhojUser, TextToImageModelConfig
@@ -86,6 +88,8 @@ async def text_to_image(
86
88
  webp_image_bytes = generate_image_with_stability(image_prompt, text_to_image_config, text2image_model)
87
89
  elif text_to_image_config.model_type == TextToImageModelConfig.ModelType.REPLICATE:
88
90
  webp_image_bytes = generate_image_with_replicate(image_prompt, text_to_image_config, text2image_model)
91
+ elif text_to_image_config.model_type == TextToImageModelConfig.ModelType.GOOGLE:
92
+ webp_image_bytes = generate_image_with_google(image_prompt, text_to_image_config, text2image_model)
89
93
  except openai.OpenAIError or openai.BadRequestError or openai.APIConnectionError as e:
90
94
  if "content_policy_violation" in e.message:
91
95
  logger.error(f"Image Generation blocked by OpenAI: {e}")
@@ -99,6 +103,12 @@ async def text_to_image(
99
103
  status_code = e.status_code # type: ignore
100
104
  yield image_url or image, status_code, message
101
105
  return
106
+ except ValueError as e:
107
+ logger.error(f"Image Generation failed with {e}", exc_info=True)
108
+ message = f"Image generation using {text2image_model} via {text_to_image_config.model_type} failed due to an unknown error"
109
+ status_code = 500
110
+ yield image_url or image, status_code, message
111
+ return
102
112
  except requests.RequestException as e:
103
113
  logger.error(f"Image Generation failed with {e}", exc_info=True)
104
114
  message = f"Image generation using {text2image_model} via {text_to_image_config.model_type} failed due to a network error."
@@ -215,3 +225,28 @@ def generate_image_with_replicate(
215
225
  # Get the generated image
216
226
  image_url = get_prediction["output"][0] if isinstance(get_prediction["output"], list) else get_prediction["output"]
217
227
  return io.BytesIO(requests.get(image_url).content).getvalue()
228
+
229
+
230
+ def generate_image_with_google(
231
+ improved_image_prompt: str, text_to_image_config: TextToImageModelConfig, text2image_model: str
232
+ ):
233
+ """Generate image using Google's AI over API"""
234
+
235
+ # Initialize the Google AI client
236
+ api_key = text_to_image_config.api_key or text_to_image_config.ai_model_api.api_key
237
+ client = genai.Client(api_key=api_key)
238
+
239
+ # Configure image generation settings
240
+ config = gtypes.GenerateImagesConfig(number_of_images=1)
241
+
242
+ # Call the Gemini API to generate the image
243
+ response = client.models.generate_images(model=text2image_model, prompt=improved_image_prompt, config=config)
244
+
245
+ if not response.generated_images:
246
+ raise ValueError("Failed to generate image using Google AI")
247
+
248
+ # Extract the image bytes from the first generated image
249
+ image_bytes = response.generated_images[0].image.image_bytes
250
+
251
+ # Convert to webp for faster loading
252
+ return convert_image_to_webp(image_bytes)
khoj/routers/helpers.py CHANGED
@@ -1092,7 +1092,11 @@ async def generate_better_image_prompt(
1092
1092
  online_results=simplified_online_results,
1093
1093
  personality_context=personality_context,
1094
1094
  )
1095
- elif model_type in [TextToImageModelConfig.ModelType.STABILITYAI, TextToImageModelConfig.ModelType.REPLICATE]:
1095
+ elif model_type in [
1096
+ TextToImageModelConfig.ModelType.STABILITYAI,
1097
+ TextToImageModelConfig.ModelType.REPLICATE,
1098
+ TextToImageModelConfig.ModelType.GOOGLE,
1099
+ ]:
1096
1100
  image_prompt = prompts.image_generation_improve_prompt_sd.format(
1097
1101
  query=q,
1098
1102
  chat_history=conversation_history,
@@ -1125,6 +1129,7 @@ async def send_message_to_model_wrapper(
1125
1129
  query: str,
1126
1130
  system_message: str = "",
1127
1131
  response_type: str = "text",
1132
+ deepthought: bool = False,
1128
1133
  user: KhojUser = None,
1129
1134
  query_images: List[str] = None,
1130
1135
  context: str = "",
@@ -1227,6 +1232,7 @@ async def send_message_to_model_wrapper(
1227
1232
  api_key=api_key,
1228
1233
  model=chat_model_name,
1229
1234
  response_type=response_type,
1235
+ deepthought=deepthought,
1230
1236
  tracer=tracer,
1231
1237
  )
1232
1238
  elif model_type == ChatModel.ModelType.GOOGLE:
@@ -1425,11 +1431,13 @@ def generate_chat_response(
1425
1431
  )
1426
1432
 
1427
1433
  query_to_run = q
1434
+ deepthought = False
1428
1435
  if meta_research:
1429
1436
  query_to_run = f"<query>{q}</query>\n<collected_research>\n{meta_research}\n</collected_research>"
1430
1437
  compiled_references = []
1431
1438
  online_results = {}
1432
1439
  code_results = {}
1440
+ deepthought = True
1433
1441
 
1434
1442
  chat_model = ConversationAdapters.get_valid_chat_model(user, conversation, is_subscribed)
1435
1443
  vision_available = chat_model.vision_enabled
@@ -1513,6 +1521,7 @@ def generate_chat_response(
1513
1521
  generated_files=raw_generated_files,
1514
1522
  generated_asset_results=generated_asset_results,
1515
1523
  program_execution_context=program_execution_context,
1524
+ deepthought=deepthought,
1516
1525
  tracer=tracer,
1517
1526
  )
1518
1527
  elif chat_model.model_type == ChatModel.ModelType.GOOGLE:
khoj/routers/research.py CHANGED
@@ -95,6 +95,7 @@ async def apick_next_tool(
95
95
  query=query,
96
96
  context=function_planning_prompt,
97
97
  response_type="json_object",
98
+ deepthought=True,
98
99
  user=user,
99
100
  query_images=query_images,
100
101
  query_files=query_files,
khoj/utils/constants.py CHANGED
@@ -48,6 +48,9 @@ model_to_cost: Dict[str, Dict[str, float]] = {
48
48
  "gemini-1.5-pro-002": {"input": 1.25, "output": 5.00},
49
49
  "gemini-2.0-flash": {"input": 0.10, "output": 0.40},
50
50
  # Anthropic Pricing: https://www.anthropic.com/pricing#anthropic-api_
51
- "claude-3-5-sonnet-20241022": {"input": 3.0, "output": 15.0},
52
51
  "claude-3-5-haiku-20241022": {"input": 1.0, "output": 5.0},
52
+ "claude-3-5-sonnet-20241022": {"input": 3.0, "output": 15.0},
53
+ "claude-3-5-sonnet-latest": {"input": 3.0, "output": 15.0},
54
+ "claude-3-7-sonnet-20250219": {"input": 3.0, "output": 15.0},
55
+ "claude-3-7-sonnet-latest": {"input": 3.0, "output": 15.0},
53
56
  }
khoj/utils/helpers.py CHANGED
@@ -368,7 +368,7 @@ command_descriptions_for_agent = {
368
368
  ConversationCommand.Code: "Agent can run Python code to parse information, run complex calculations, create documents and charts.",
369
369
  }
370
370
 
371
- e2b_tool_description = "To run Python code in a E2B sandbox with no network access. Helpful to parse complex information, run calculations, create text documents and create charts with quantitative data. Only matplotlib, pandas, numpy, scipy, bs4, sympy, einops, biopython, shapely and rdkit external packages are available."
371
+ e2b_tool_description = "To run Python code in a E2B sandbox with no network access. Helpful to parse complex information, run calculations, create text documents and create charts with quantitative data. Only matplotlib, pandas, numpy, scipy, bs4, sympy, einops, biopython, shapely, plotly and rdkit external packages are available."
372
372
  terrarium_tool_description = "To run Python code in a Terrarium, Pyodide sandbox with no network access. Helpful to parse complex information, run complex calculations, create plaintext documents and create charts with quantitative data. Only matplotlib, panda, numpy, scipy, bs4 and sympy external packages are available."
373
373
 
374
374
  tool_descriptions_for_llm = {
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: khoj
3
- Version: 1.36.7.dev18
3
+ Version: 1.36.7.dev24
4
4
  Summary: Your Second Brain
5
5
  Project-URL: Homepage, https://khoj.dev
6
6
  Project-URL: Documentation, https://docs.khoj.dev
@@ -22,8 +22,8 @@ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
22
22
  Classifier: Topic :: Scientific/Engineering :: Human Machine Interfaces
23
23
  Requires-Python: >=3.10
24
24
  Requires-Dist: aiohttp~=3.9.0
25
- Requires-Dist: anthropic==0.26.1
26
- Requires-Dist: anyio==3.7.1
25
+ Requires-Dist: anthropic==0.49.0
26
+ Requires-Dist: anyio~=4.8.0
27
27
  Requires-Dist: apscheduler~=3.10.0
28
28
  Requires-Dist: authlib==1.2.1
29
29
  Requires-Dist: beautifulsoup4~=4.12.3
@@ -39,8 +39,8 @@ Requires-Dist: e2b-code-interpreter~=1.0.0
39
39
  Requires-Dist: einops==0.8.0
40
40
  Requires-Dist: email-validator==2.2.0
41
41
  Requires-Dist: fastapi>=0.110.0
42
- Requires-Dist: google-generativeai==0.8.3
43
- Requires-Dist: httpx==0.27.2
42
+ Requires-Dist: google-genai==1.5.0
43
+ Requires-Dist: httpx==0.28.1
44
44
  Requires-Dist: huggingface-hub>=0.22.2
45
45
  Requires-Dist: itsdangerous==2.1.2
46
46
  Requires-Dist: jinja2==3.1.5
@@ -77,7 +77,7 @@ Requires-Dist: torch==2.2.2
77
77
  Requires-Dist: transformers>=4.28.0
78
78
  Requires-Dist: tzdata==2023.3
79
79
  Requires-Dist: uvicorn==0.30.6
80
- Requires-Dist: websockets==12.0
80
+ Requires-Dist: websockets==13.0
81
81
  Provides-Extra: dev
82
82
  Requires-Dist: black>=23.1.0; extra == 'dev'
83
83
  Requires-Dist: boto3>=1.34.57; extra == 'dev'