khoj 1.22.3__py3-none-any.whl → 1.22.4.dev6__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.
- khoj/database/adapters/__init__.py +7 -2
- khoj/database/migrations/0061_alter_chatmodeloptions_model_type.py +26 -0
- khoj/database/models/__init__.py +1 -0
- khoj/interface/compiled/404/index.html +1 -1
- khoj/interface/compiled/_next/static/chunks/app/agents/{page-6ade083d5e27a023.js → page-989a824c640bc532.js} +1 -1
- khoj/interface/compiled/_next/static/chunks/app/automations/{page-6ea3381528603372.js → page-3f4b6ff0261e19b7.js} +1 -1
- khoj/interface/compiled/_next/static/chunks/app/chat/{page-132e5199f954559f.js → page-cc71b18feddf80d6.js} +1 -1
- khoj/interface/compiled/_next/static/chunks/app/factchecker/{page-04a19ab1a988976f.js → page-828cf3c5b8e3af79.js} +1 -1
- khoj/interface/compiled/_next/static/chunks/app/{page-8465c90401833c39.js → page-2f423a763e2ee0c7.js} +1 -1
- khoj/interface/compiled/_next/static/chunks/app/search/{page-fa15807b1ad7e30b.js → page-dcd385f03255ef36.js} +1 -1
- khoj/interface/compiled/_next/static/chunks/app/settings/{page-1a2acc46cdabaf4a.js → page-ddcd51147d18c694.js} +1 -1
- khoj/interface/compiled/_next/static/chunks/app/share/chat/{page-e20f54450d3ce6c0.js → page-a84001b4724b5463.js} +1 -1
- khoj/interface/compiled/_next/static/chunks/{webpack-d83aaba66623b485.js → webpack-d4781cada9b58e75.js} +1 -1
- khoj/interface/compiled/_next/static/css/4cae6c0e5c72fb2d.css +1 -0
- khoj/interface/compiled/_next/static/css/8d802d0ad3a555d8.css +1 -0
- khoj/interface/compiled/agents/index.html +1 -1
- khoj/interface/compiled/agents/index.txt +2 -2
- khoj/interface/compiled/automations/index.html +1 -1
- khoj/interface/compiled/automations/index.txt +2 -2
- khoj/interface/compiled/chat/index.html +1 -1
- khoj/interface/compiled/chat/index.txt +2 -2
- khoj/interface/compiled/factchecker/index.html +1 -1
- khoj/interface/compiled/factchecker/index.txt +2 -2
- khoj/interface/compiled/index.html +1 -1
- khoj/interface/compiled/index.txt +2 -2
- khoj/interface/compiled/search/index.html +1 -1
- khoj/interface/compiled/search/index.txt +2 -2
- khoj/interface/compiled/settings/index.html +1 -1
- khoj/interface/compiled/settings/index.txt +2 -2
- khoj/interface/compiled/share/chat/index.html +1 -1
- khoj/interface/compiled/share/chat/index.txt +2 -2
- khoj/processor/conversation/google/__init__.py +0 -0
- khoj/processor/conversation/google/gemini_chat.py +221 -0
- khoj/processor/conversation/google/utils.py +93 -0
- khoj/processor/conversation/openai/gpt.py +2 -0
- khoj/processor/conversation/openai/utils.py +35 -10
- khoj/processor/conversation/prompts.py +2 -2
- khoj/processor/conversation/utils.py +15 -4
- khoj/processor/tools/online_search.py +2 -1
- khoj/routers/api.py +13 -0
- khoj/routers/helpers.py +77 -30
- {khoj-1.22.3.dist-info → khoj-1.22.4.dev6.dist-info}/METADATA +2 -1
- {khoj-1.22.3.dist-info → khoj-1.22.4.dev6.dist-info}/RECORD +48 -44
- khoj/interface/compiled/_next/static/css/592ca99f5122e75a.css +0 -1
- khoj/interface/compiled/_next/static/css/9eec70b61e3de1ba.css +0 -1
- /khoj/interface/compiled/_next/static/{HGHeV7_enScOK08fsj1eR → 1C2MwhlH-dUGJ3iv5t17P}/_buildManifest.js +0 -0
- /khoj/interface/compiled/_next/static/{HGHeV7_enScOK08fsj1eR → 1C2MwhlH-dUGJ3iv5t17P}/_ssgManifest.js +0 -0
- {khoj-1.22.3.dist-info → khoj-1.22.4.dev6.dist-info}/WHEEL +0 -0
- {khoj-1.22.3.dist-info → khoj-1.22.4.dev6.dist-info}/entry_points.txt +0 -0
- {khoj-1.22.3.dist-info → khoj-1.22.4.dev6.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from threading import Thread
|
|
3
|
+
|
|
4
|
+
import google.generativeai as genai
|
|
5
|
+
from tenacity import (
|
|
6
|
+
before_sleep_log,
|
|
7
|
+
retry,
|
|
8
|
+
stop_after_attempt,
|
|
9
|
+
wait_exponential,
|
|
10
|
+
wait_random_exponential,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
from khoj.processor.conversation.utils import ThreadedGenerator
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
DEFAULT_MAX_TOKENS_GEMINI = 8192
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@retry(
|
|
22
|
+
wait=wait_random_exponential(min=1, max=10),
|
|
23
|
+
stop=stop_after_attempt(2),
|
|
24
|
+
before_sleep=before_sleep_log(logger, logging.DEBUG),
|
|
25
|
+
reraise=True,
|
|
26
|
+
)
|
|
27
|
+
def gemini_completion_with_backoff(
|
|
28
|
+
messages, system_prompt, model_name, temperature=0, api_key=None, model_kwargs=None, max_tokens=None
|
|
29
|
+
) -> str:
|
|
30
|
+
genai.configure(api_key=api_key)
|
|
31
|
+
max_tokens = max_tokens or DEFAULT_MAX_TOKENS_GEMINI
|
|
32
|
+
model_kwargs = model_kwargs or dict()
|
|
33
|
+
model_kwargs["temperature"] = temperature
|
|
34
|
+
model_kwargs["max_output_tokens"] = max_tokens
|
|
35
|
+
model = genai.GenerativeModel(model_name, generation_config=model_kwargs, system_instruction=system_prompt)
|
|
36
|
+
|
|
37
|
+
formatted_messages = [{"role": message.role, "parts": [message.content]} for message in messages]
|
|
38
|
+
# all messages up to the last are considered to be part of the chat history
|
|
39
|
+
chat_session = model.start_chat(history=formatted_messages[0:-1])
|
|
40
|
+
# the last message is considered to be the current prompt
|
|
41
|
+
aggregated_response = chat_session.send_message(formatted_messages[-1]["parts"][0])
|
|
42
|
+
return aggregated_response.text
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@retry(
|
|
46
|
+
wait=wait_exponential(multiplier=1, min=4, max=10),
|
|
47
|
+
stop=stop_after_attempt(2),
|
|
48
|
+
before_sleep=before_sleep_log(logger, logging.DEBUG),
|
|
49
|
+
reraise=True,
|
|
50
|
+
)
|
|
51
|
+
def gemini_chat_completion_with_backoff(
|
|
52
|
+
messages,
|
|
53
|
+
compiled_references,
|
|
54
|
+
online_results,
|
|
55
|
+
model_name,
|
|
56
|
+
temperature,
|
|
57
|
+
api_key,
|
|
58
|
+
system_prompt,
|
|
59
|
+
max_prompt_size=None,
|
|
60
|
+
completion_func=None,
|
|
61
|
+
model_kwargs=None,
|
|
62
|
+
):
|
|
63
|
+
g = ThreadedGenerator(compiled_references, online_results, completion_func=completion_func)
|
|
64
|
+
t = Thread(
|
|
65
|
+
target=gemini_llm_thread,
|
|
66
|
+
args=(g, messages, system_prompt, model_name, temperature, api_key, max_prompt_size, model_kwargs),
|
|
67
|
+
)
|
|
68
|
+
t.start()
|
|
69
|
+
return g
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def gemini_llm_thread(
|
|
73
|
+
g, messages, system_prompt, model_name, temperature, api_key, max_prompt_size=None, model_kwargs=None
|
|
74
|
+
):
|
|
75
|
+
try:
|
|
76
|
+
genai.configure(api_key=api_key)
|
|
77
|
+
max_tokens = max_prompt_size or DEFAULT_MAX_TOKENS_GEMINI
|
|
78
|
+
model_kwargs = model_kwargs or dict()
|
|
79
|
+
model_kwargs["temperature"] = temperature
|
|
80
|
+
model_kwargs["max_output_tokens"] = max_tokens
|
|
81
|
+
model_kwargs["stop_sequences"] = ["Notes:\n["]
|
|
82
|
+
model = genai.GenerativeModel(model_name, generation_config=model_kwargs, system_instruction=system_prompt)
|
|
83
|
+
|
|
84
|
+
formatted_messages = [{"role": message.role, "parts": [message.content]} for message in messages]
|
|
85
|
+
# all messages up to the last are considered to be part of the chat history
|
|
86
|
+
chat_session = model.start_chat(history=formatted_messages[0:-1])
|
|
87
|
+
# the last message is considered to be the current prompt
|
|
88
|
+
for chunk in chat_session.send_message(formatted_messages[-1]["parts"][0], stream=True):
|
|
89
|
+
g.send(chunk.text)
|
|
90
|
+
except Exception as e:
|
|
91
|
+
logger.error(f"Error in gemini_llm_thread: {e}", exc_info=True)
|
|
92
|
+
finally:
|
|
93
|
+
g.close()
|
|
@@ -14,6 +14,7 @@ from khoj.processor.conversation.openai.utils import (
|
|
|
14
14
|
from khoj.processor.conversation.utils import (
|
|
15
15
|
construct_structured_message,
|
|
16
16
|
generate_chatml_messages_with_context,
|
|
17
|
+
remove_json_codeblock,
|
|
17
18
|
)
|
|
18
19
|
from khoj.utils.helpers import ConversationCommand, is_none_or_empty
|
|
19
20
|
from khoj.utils.rawconfig import LocationData
|
|
@@ -85,6 +86,7 @@ def extract_questions(
|
|
|
85
86
|
# Extract, Clean Message from GPT's Response
|
|
86
87
|
try:
|
|
87
88
|
response = response.strip()
|
|
89
|
+
response = remove_json_codeblock(response)
|
|
88
90
|
response = json.loads(response)
|
|
89
91
|
response = [q.strip() for q in response["queries"] if q.strip()]
|
|
90
92
|
if not isinstance(response, list) or not response:
|
|
@@ -45,15 +45,28 @@ def completion_with_backoff(
|
|
|
45
45
|
openai_clients[client_key] = client
|
|
46
46
|
|
|
47
47
|
formatted_messages = [{"role": message.role, "content": message.content} for message in messages]
|
|
48
|
+
stream = True
|
|
49
|
+
|
|
50
|
+
# Update request parameters for compatability with o1 model series
|
|
51
|
+
# Refer: https://platform.openai.com/docs/guides/reasoning/beta-limitations
|
|
52
|
+
if model.startswith("o1"):
|
|
53
|
+
stream = False
|
|
54
|
+
temperature = 1
|
|
55
|
+
model_kwargs.pop("stop", None)
|
|
56
|
+
model_kwargs.pop("response_format", None)
|
|
48
57
|
|
|
49
58
|
chat = client.chat.completions.create(
|
|
50
|
-
stream=
|
|
59
|
+
stream=stream,
|
|
51
60
|
messages=formatted_messages, # type: ignore
|
|
52
61
|
model=model, # type: ignore
|
|
53
62
|
temperature=temperature,
|
|
54
63
|
timeout=20,
|
|
55
64
|
**(model_kwargs or dict()),
|
|
56
65
|
)
|
|
66
|
+
|
|
67
|
+
if not stream:
|
|
68
|
+
return chat.choices[0].message.content
|
|
69
|
+
|
|
57
70
|
aggregated_response = ""
|
|
58
71
|
for chunk in chat:
|
|
59
72
|
if len(chunk.choices) == 0:
|
|
@@ -112,9 +125,18 @@ def llm_thread(g, messages, model_name, temperature, openai_api_key=None, api_ba
|
|
|
112
125
|
client: openai.OpenAI = openai_clients[client_key]
|
|
113
126
|
|
|
114
127
|
formatted_messages = [{"role": message.role, "content": message.content} for message in messages]
|
|
128
|
+
stream = True
|
|
129
|
+
|
|
130
|
+
# Update request parameters for compatability with o1 model series
|
|
131
|
+
# Refer: https://platform.openai.com/docs/guides/reasoning/beta-limitations
|
|
132
|
+
if model_name.startswith("o1"):
|
|
133
|
+
stream = False
|
|
134
|
+
temperature = 1
|
|
135
|
+
model_kwargs.pop("stop", None)
|
|
136
|
+
model_kwargs.pop("response_format", None)
|
|
115
137
|
|
|
116
138
|
chat = client.chat.completions.create(
|
|
117
|
-
stream=
|
|
139
|
+
stream=stream,
|
|
118
140
|
messages=formatted_messages,
|
|
119
141
|
model=model_name, # type: ignore
|
|
120
142
|
temperature=temperature,
|
|
@@ -122,14 +144,17 @@ def llm_thread(g, messages, model_name, temperature, openai_api_key=None, api_ba
|
|
|
122
144
|
**(model_kwargs or dict()),
|
|
123
145
|
)
|
|
124
146
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
147
|
+
if not stream:
|
|
148
|
+
g.send(chat.choices[0].message.content)
|
|
149
|
+
else:
|
|
150
|
+
for chunk in chat:
|
|
151
|
+
if len(chunk.choices) == 0:
|
|
152
|
+
continue
|
|
153
|
+
delta_chunk = chunk.choices[0].delta
|
|
154
|
+
if isinstance(delta_chunk, str):
|
|
155
|
+
g.send(delta_chunk)
|
|
156
|
+
elif delta_chunk.content:
|
|
157
|
+
g.send(delta_chunk.content)
|
|
133
158
|
except Exception as e:
|
|
134
159
|
logger.error(f"Error in llm_thread: {e}", exc_info=True)
|
|
135
160
|
finally:
|
|
@@ -13,8 +13,8 @@ You were created by Khoj Inc. with the following capabilities:
|
|
|
13
13
|
- You *CAN* generate images, look-up real-time information from the internet, set reminders and answer questions based on the user's notes.
|
|
14
14
|
- Say "I don't know" or "I don't understand" if you don't know what to say or if you don't know the answer to a question.
|
|
15
15
|
- Make sure to use the specific LaTeX math mode delimiters for your response. LaTex math mode specific delimiters as following
|
|
16
|
-
- inline math mode :
|
|
17
|
-
- display math mode: insert linebreak after opening
|
|
16
|
+
- inline math mode : \\( and \\)
|
|
17
|
+
- display math mode: insert linebreak after opening $$, \\[ and before closing $$, \\]
|
|
18
18
|
- Ask crisp follow-up questions to get additional context, when the answer cannot be inferred from the provided notes or past conversations.
|
|
19
19
|
- Sometimes the user will share personal information that needs to be remembered, like an account ID or a residential address. These can be acknowledged with a simple "Got it" or "Okay".
|
|
20
20
|
- Provide inline references to quotes from the user's notes or any web pages you refer to in your responses in markdown format. For example, "The farmer had ten sheep. [1](https://example.com)". *ALWAYS CITE YOUR SOURCES AND PROVIDE REFERENCES*. Add them inline to directly support your claim.
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import json
|
|
2
1
|
import logging
|
|
3
2
|
import math
|
|
4
3
|
import queue
|
|
@@ -24,6 +23,8 @@ model_to_prompt_size = {
|
|
|
24
23
|
"gpt-4-0125-preview": 20000,
|
|
25
24
|
"gpt-4-turbo-preview": 20000,
|
|
26
25
|
"gpt-4o-mini": 20000,
|
|
26
|
+
"o1-preview": 20000,
|
|
27
|
+
"o1-mini": 20000,
|
|
27
28
|
"TheBloke/Mistral-7B-Instruct-v0.2-GGUF": 3500,
|
|
28
29
|
"NousResearch/Hermes-2-Pro-Mistral-7B-GGUF": 3500,
|
|
29
30
|
"bartowski/Meta-Llama-3.1-8B-Instruct-GGUF": 20000,
|
|
@@ -220,8 +221,9 @@ def truncate_messages(
|
|
|
220
221
|
try:
|
|
221
222
|
if loaded_model:
|
|
222
223
|
encoder = loaded_model.tokenizer()
|
|
223
|
-
elif model_name.startswith("gpt-"):
|
|
224
|
-
|
|
224
|
+
elif model_name.startswith("gpt-") or model_name.startswith("o1"):
|
|
225
|
+
# as tiktoken doesn't recognize o1 model series yet
|
|
226
|
+
encoder = tiktoken.encoding_for_model("gpt-4o" if model_name.startswith("o1") else model_name)
|
|
225
227
|
elif tokenizer_name:
|
|
226
228
|
if tokenizer_name in state.pretrained_tokenizers:
|
|
227
229
|
encoder = state.pretrained_tokenizers[tokenizer_name]
|
|
@@ -278,10 +280,19 @@ def truncate_messages(
|
|
|
278
280
|
)
|
|
279
281
|
|
|
280
282
|
if system_message:
|
|
281
|
-
|
|
283
|
+
# Default system message role is system.
|
|
284
|
+
# Fallback to system message role of user for models that do not support this role like gemma-2 and openai's o1 model series.
|
|
285
|
+
system_message.role = "user" if "gemma-2" in model_name or model_name.startswith("o1") else "system"
|
|
282
286
|
return messages + [system_message] if system_message else messages
|
|
283
287
|
|
|
284
288
|
|
|
285
289
|
def reciprocal_conversation_to_chatml(message_pair):
|
|
286
290
|
"""Convert a single back and forth between user and assistant to chatml format"""
|
|
287
291
|
return [ChatMessage(content=message, role=role) for message, role in zip(message_pair, ["user", "assistant"])]
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
def remove_json_codeblock(response):
|
|
295
|
+
"""Remove any markdown json codeblock formatting if present. Useful for non schema enforceable models"""
|
|
296
|
+
if response.startswith("```json") and response.endswith("```"):
|
|
297
|
+
response = response[7:-3]
|
|
298
|
+
return response
|
|
@@ -7,6 +7,7 @@ from collections import defaultdict
|
|
|
7
7
|
from typing import Callable, Dict, List, Optional, Tuple, Union
|
|
8
8
|
|
|
9
9
|
import aiohttp
|
|
10
|
+
import requests
|
|
10
11
|
from bs4 import BeautifulSoup
|
|
11
12
|
from markdownify import markdownify
|
|
12
13
|
|
|
@@ -94,7 +95,7 @@ async def search_online(
|
|
|
94
95
|
|
|
95
96
|
# Read, extract relevant info from the retrieved web pages
|
|
96
97
|
if webpages:
|
|
97
|
-
webpage_links = [link for link, _, _ in webpages]
|
|
98
|
+
webpage_links = set([link for link, _, _ in webpages])
|
|
98
99
|
logger.info(f"Reading web pages at: {list(webpage_links)}")
|
|
99
100
|
if send_status_func:
|
|
100
101
|
webpage_links_str = "\n- " + "\n- ".join(list(webpage_links))
|
khoj/routers/api.py
CHANGED
|
@@ -31,6 +31,7 @@ from khoj.database.models import ChatModelOptions, KhojUser, SpeechToTextModelOp
|
|
|
31
31
|
from khoj.processor.conversation.anthropic.anthropic_chat import (
|
|
32
32
|
extract_questions_anthropic,
|
|
33
33
|
)
|
|
34
|
+
from khoj.processor.conversation.google.gemini_chat import extract_questions_gemini
|
|
34
35
|
from khoj.processor.conversation.offline.chat_model import extract_questions_offline
|
|
35
36
|
from khoj.processor.conversation.offline.whisper import transcribe_audio_offline
|
|
36
37
|
from khoj.processor.conversation.openai.gpt import extract_questions
|
|
@@ -419,6 +420,18 @@ async def extract_references_and_questions(
|
|
|
419
420
|
location_data=location_data,
|
|
420
421
|
user=user,
|
|
421
422
|
)
|
|
423
|
+
elif conversation_config.model_type == ChatModelOptions.ModelType.GOOGLE:
|
|
424
|
+
api_key = conversation_config.openai_config.api_key
|
|
425
|
+
chat_model = conversation_config.chat_model
|
|
426
|
+
inferred_queries = extract_questions_gemini(
|
|
427
|
+
defiltered_query,
|
|
428
|
+
model=chat_model,
|
|
429
|
+
api_key=api_key,
|
|
430
|
+
conversation_log=meta_log,
|
|
431
|
+
location_data=location_data,
|
|
432
|
+
max_tokens=conversation_config.max_prompt_size,
|
|
433
|
+
user=user,
|
|
434
|
+
)
|
|
422
435
|
|
|
423
436
|
# Collate search results as context for GPT
|
|
424
437
|
with timer("Searching knowledge base took", logger):
|
khoj/routers/helpers.py
CHANGED
|
@@ -76,6 +76,10 @@ from khoj.processor.conversation.anthropic.anthropic_chat import (
|
|
|
76
76
|
anthropic_send_message_to_model,
|
|
77
77
|
converse_anthropic,
|
|
78
78
|
)
|
|
79
|
+
from khoj.processor.conversation.google.gemini_chat import (
|
|
80
|
+
converse_gemini,
|
|
81
|
+
gemini_send_message_to_model,
|
|
82
|
+
)
|
|
79
83
|
from khoj.processor.conversation.offline.chat_model import (
|
|
80
84
|
converse_offline,
|
|
81
85
|
send_message_to_model_offline,
|
|
@@ -84,6 +88,7 @@ from khoj.processor.conversation.openai.gpt import converse, send_message_to_mod
|
|
|
84
88
|
from khoj.processor.conversation.utils import (
|
|
85
89
|
ThreadedGenerator,
|
|
86
90
|
generate_chatml_messages_with_context,
|
|
91
|
+
remove_json_codeblock,
|
|
87
92
|
save_to_conversation_log,
|
|
88
93
|
)
|
|
89
94
|
from khoj.processor.speech.text_to_speech import is_eleven_labs_enabled
|
|
@@ -136,7 +141,7 @@ async def is_ready_to_chat(user: KhojUser):
|
|
|
136
141
|
await ConversationAdapters.aget_default_conversation_config()
|
|
137
142
|
)
|
|
138
143
|
|
|
139
|
-
if user_conversation_config and user_conversation_config.model_type ==
|
|
144
|
+
if user_conversation_config and user_conversation_config.model_type == ChatModelOptions.ModelType.OFFLINE:
|
|
140
145
|
chat_model = user_conversation_config.chat_model
|
|
141
146
|
max_tokens = user_conversation_config.max_prompt_size
|
|
142
147
|
if state.offline_chat_processor_config is None:
|
|
@@ -146,7 +151,14 @@ async def is_ready_to_chat(user: KhojUser):
|
|
|
146
151
|
|
|
147
152
|
if (
|
|
148
153
|
user_conversation_config
|
|
149
|
-
and (
|
|
154
|
+
and (
|
|
155
|
+
user_conversation_config.model_type
|
|
156
|
+
in [
|
|
157
|
+
ChatModelOptions.ModelType.OPENAI,
|
|
158
|
+
ChatModelOptions.ModelType.ANTHROPIC,
|
|
159
|
+
ChatModelOptions.ModelType.GOOGLE,
|
|
160
|
+
]
|
|
161
|
+
)
|
|
150
162
|
and user_conversation_config.openai_config
|
|
151
163
|
):
|
|
152
164
|
return True
|
|
@@ -287,9 +299,7 @@ async def aget_relevant_information_sources(
|
|
|
287
299
|
|
|
288
300
|
try:
|
|
289
301
|
response = response.strip()
|
|
290
|
-
|
|
291
|
-
if response.startswith("```json"):
|
|
292
|
-
response = response[7:-3]
|
|
302
|
+
response = remove_json_codeblock(response)
|
|
293
303
|
response = json.loads(response)
|
|
294
304
|
response = [q.strip() for q in response["source"] if q.strip()]
|
|
295
305
|
if not isinstance(response, list) or not response or len(response) == 0:
|
|
@@ -342,7 +352,9 @@ async def aget_relevant_output_modes(
|
|
|
342
352
|
response = await send_message_to_model_wrapper(relevant_mode_prompt, response_type="json_object")
|
|
343
353
|
|
|
344
354
|
try:
|
|
345
|
-
response =
|
|
355
|
+
response = response.strip()
|
|
356
|
+
response = remove_json_codeblock(response)
|
|
357
|
+
response = json.loads(response)
|
|
346
358
|
|
|
347
359
|
if is_none_or_empty(response):
|
|
348
360
|
return ConversationCommand.Text
|
|
@@ -422,9 +434,7 @@ async def generate_online_subqueries(
|
|
|
422
434
|
# Validate that the response is a non-empty, JSON-serializable list
|
|
423
435
|
try:
|
|
424
436
|
response = response.strip()
|
|
425
|
-
|
|
426
|
-
if response.startswith("```json") and response.endswith("```"):
|
|
427
|
-
response = response[7:-3]
|
|
437
|
+
response = remove_json_codeblock(response)
|
|
428
438
|
response = json.loads(response)
|
|
429
439
|
response = [q.strip() for q in response["queries"] if q.strip()]
|
|
430
440
|
if not isinstance(response, list) or not response or len(response) == 0:
|
|
@@ -607,9 +617,10 @@ async def send_message_to_model_wrapper(
|
|
|
607
617
|
else conversation_config.max_prompt_size
|
|
608
618
|
)
|
|
609
619
|
tokenizer = conversation_config.tokenizer
|
|
620
|
+
model_type = conversation_config.model_type
|
|
610
621
|
vision_available = conversation_config.vision_enabled
|
|
611
622
|
|
|
612
|
-
if
|
|
623
|
+
if model_type == ChatModelOptions.ModelType.OFFLINE:
|
|
613
624
|
if state.offline_chat_processor_config is None or state.offline_chat_processor_config.loaded_model is None:
|
|
614
625
|
state.offline_chat_processor_config = OfflineChatProcessorModel(chat_model, max_tokens)
|
|
615
626
|
|
|
@@ -633,7 +644,7 @@ async def send_message_to_model_wrapper(
|
|
|
633
644
|
response_type=response_type,
|
|
634
645
|
)
|
|
635
646
|
|
|
636
|
-
elif
|
|
647
|
+
elif model_type == ChatModelOptions.ModelType.OPENAI:
|
|
637
648
|
openai_chat_config = conversation_config.openai_config
|
|
638
649
|
api_key = openai_chat_config.api_key
|
|
639
650
|
api_base_url = openai_chat_config.api_base_url
|
|
@@ -657,7 +668,7 @@ async def send_message_to_model_wrapper(
|
|
|
657
668
|
)
|
|
658
669
|
|
|
659
670
|
return openai_response
|
|
660
|
-
elif
|
|
671
|
+
elif model_type == ChatModelOptions.ModelType.ANTHROPIC:
|
|
661
672
|
api_key = conversation_config.openai_config.api_key
|
|
662
673
|
truncated_messages = generate_chatml_messages_with_context(
|
|
663
674
|
user_message=message,
|
|
@@ -666,6 +677,7 @@ async def send_message_to_model_wrapper(
|
|
|
666
677
|
max_prompt_size=max_tokens,
|
|
667
678
|
tokenizer_name=tokenizer,
|
|
668
679
|
vision_enabled=vision_available,
|
|
680
|
+
uploaded_image_url=uploaded_image_url,
|
|
669
681
|
model_type=conversation_config.model_type,
|
|
670
682
|
)
|
|
671
683
|
|
|
@@ -674,6 +686,21 @@ async def send_message_to_model_wrapper(
|
|
|
674
686
|
api_key=api_key,
|
|
675
687
|
model=chat_model,
|
|
676
688
|
)
|
|
689
|
+
elif model_type == ChatModelOptions.ModelType.GOOGLE:
|
|
690
|
+
api_key = conversation_config.openai_config.api_key
|
|
691
|
+
truncated_messages = generate_chatml_messages_with_context(
|
|
692
|
+
user_message=message,
|
|
693
|
+
system_message=system_message,
|
|
694
|
+
model_name=chat_model,
|
|
695
|
+
max_prompt_size=max_tokens,
|
|
696
|
+
tokenizer_name=tokenizer,
|
|
697
|
+
vision_enabled=vision_available,
|
|
698
|
+
uploaded_image_url=uploaded_image_url,
|
|
699
|
+
)
|
|
700
|
+
|
|
701
|
+
return gemini_send_message_to_model(
|
|
702
|
+
messages=truncated_messages, api_key=api_key, model=chat_model, response_type=response_type
|
|
703
|
+
)
|
|
677
704
|
else:
|
|
678
705
|
raise HTTPException(status_code=500, detail="Invalid conversation config")
|
|
679
706
|
|
|
@@ -692,7 +719,7 @@ def send_message_to_model_wrapper_sync(
|
|
|
692
719
|
max_tokens = conversation_config.max_prompt_size
|
|
693
720
|
vision_available = conversation_config.vision_enabled
|
|
694
721
|
|
|
695
|
-
if conversation_config.model_type ==
|
|
722
|
+
if conversation_config.model_type == ChatModelOptions.ModelType.OFFLINE:
|
|
696
723
|
if state.offline_chat_processor_config is None or state.offline_chat_processor_config.loaded_model is None:
|
|
697
724
|
state.offline_chat_processor_config = OfflineChatProcessorModel(chat_model, max_tokens)
|
|
698
725
|
|
|
@@ -714,7 +741,7 @@ def send_message_to_model_wrapper_sync(
|
|
|
714
741
|
response_type=response_type,
|
|
715
742
|
)
|
|
716
743
|
|
|
717
|
-
elif conversation_config.model_type ==
|
|
744
|
+
elif conversation_config.model_type == ChatModelOptions.ModelType.OPENAI:
|
|
718
745
|
api_key = conversation_config.openai_config.api_key
|
|
719
746
|
truncated_messages = generate_chatml_messages_with_context(
|
|
720
747
|
user_message=message,
|
|
@@ -730,7 +757,7 @@ def send_message_to_model_wrapper_sync(
|
|
|
730
757
|
|
|
731
758
|
return openai_response
|
|
732
759
|
|
|
733
|
-
elif conversation_config.model_type ==
|
|
760
|
+
elif conversation_config.model_type == ChatModelOptions.ModelType.ANTHROPIC:
|
|
734
761
|
api_key = conversation_config.openai_config.api_key
|
|
735
762
|
truncated_messages = generate_chatml_messages_with_context(
|
|
736
763
|
user_message=message,
|
|
@@ -746,6 +773,22 @@ def send_message_to_model_wrapper_sync(
|
|
|
746
773
|
api_key=api_key,
|
|
747
774
|
model=chat_model,
|
|
748
775
|
)
|
|
776
|
+
|
|
777
|
+
elif conversation_config.model_type == ChatModelOptions.ModelType.GOOGLE:
|
|
778
|
+
api_key = conversation_config.openai_config.api_key
|
|
779
|
+
truncated_messages = generate_chatml_messages_with_context(
|
|
780
|
+
user_message=message,
|
|
781
|
+
system_message=system_message,
|
|
782
|
+
model_name=chat_model,
|
|
783
|
+
max_prompt_size=max_tokens,
|
|
784
|
+
vision_enabled=vision_available,
|
|
785
|
+
)
|
|
786
|
+
|
|
787
|
+
return gemini_send_message_to_model(
|
|
788
|
+
messages=truncated_messages,
|
|
789
|
+
api_key=api_key,
|
|
790
|
+
model=chat_model,
|
|
791
|
+
)
|
|
749
792
|
else:
|
|
750
793
|
raise HTTPException(status_code=500, detail="Invalid conversation config")
|
|
751
794
|
|
|
@@ -811,7 +854,7 @@ def generate_chat_response(
|
|
|
811
854
|
agent=agent,
|
|
812
855
|
)
|
|
813
856
|
|
|
814
|
-
elif conversation_config.model_type ==
|
|
857
|
+
elif conversation_config.model_type == ChatModelOptions.ModelType.OPENAI:
|
|
815
858
|
openai_chat_config = conversation_config.openai_config
|
|
816
859
|
api_key = openai_chat_config.api_key
|
|
817
860
|
chat_model = conversation_config.chat_model
|
|
@@ -834,7 +877,7 @@ def generate_chat_response(
|
|
|
834
877
|
vision_available=vision_available,
|
|
835
878
|
)
|
|
836
879
|
|
|
837
|
-
elif conversation_config.model_type ==
|
|
880
|
+
elif conversation_config.model_type == ChatModelOptions.ModelType.ANTHROPIC:
|
|
838
881
|
api_key = conversation_config.openai_config.api_key
|
|
839
882
|
chat_response = converse_anthropic(
|
|
840
883
|
compiled_references,
|
|
@@ -851,6 +894,23 @@ def generate_chat_response(
|
|
|
851
894
|
user_name=user_name,
|
|
852
895
|
agent=agent,
|
|
853
896
|
)
|
|
897
|
+
elif conversation_config.model_type == ChatModelOptions.ModelType.GOOGLE:
|
|
898
|
+
api_key = conversation_config.openai_config.api_key
|
|
899
|
+
chat_response = converse_gemini(
|
|
900
|
+
compiled_references,
|
|
901
|
+
q,
|
|
902
|
+
online_results,
|
|
903
|
+
meta_log,
|
|
904
|
+
model=conversation_config.chat_model,
|
|
905
|
+
api_key=api_key,
|
|
906
|
+
completion_func=partial_completion,
|
|
907
|
+
conversation_commands=conversation_commands,
|
|
908
|
+
max_prompt_size=conversation_config.max_prompt_size,
|
|
909
|
+
tokenizer_name=conversation_config.tokenizer,
|
|
910
|
+
location_data=location_data,
|
|
911
|
+
user_name=user_name,
|
|
912
|
+
agent=agent,
|
|
913
|
+
)
|
|
854
914
|
|
|
855
915
|
metadata.update({"chat_model": conversation_config.chat_model})
|
|
856
916
|
|
|
@@ -1217,11 +1277,6 @@ def scheduled_chat(
|
|
|
1217
1277
|
token = token[0].token
|
|
1218
1278
|
headers["Authorization"] = f"Bearer {token}"
|
|
1219
1279
|
|
|
1220
|
-
# Log request details
|
|
1221
|
-
logger.info(f"POST URL: {url}")
|
|
1222
|
-
logger.info(f"Headers: {headers}")
|
|
1223
|
-
logger.info(f"Payload: {json_payload}")
|
|
1224
|
-
|
|
1225
1280
|
# Call the chat API endpoint with authenticated user token and query
|
|
1226
1281
|
raw_response = requests.post(url, headers=headers, json=json_payload, allow_redirects=False)
|
|
1227
1282
|
|
|
@@ -1231,14 +1286,6 @@ def scheduled_chat(
|
|
|
1231
1286
|
logger.info(f"Redirecting to {redirect_url}")
|
|
1232
1287
|
raw_response = requests.post(redirect_url, headers=headers, json=json_payload)
|
|
1233
1288
|
|
|
1234
|
-
# Log response details
|
|
1235
|
-
logger.info(f"Response status code: {raw_response.status_code}")
|
|
1236
|
-
logger.info(f"Response headers: {raw_response.headers}")
|
|
1237
|
-
logger.info(f"Response text: {raw_response.text}")
|
|
1238
|
-
if raw_response.history:
|
|
1239
|
-
for resp in raw_response.history:
|
|
1240
|
-
logger.info(f"Redirected from {resp.url} with status code {resp.status_code}")
|
|
1241
|
-
|
|
1242
1289
|
# Stop if the chat API call was not successful
|
|
1243
1290
|
if raw_response.status_code != 200:
|
|
1244
1291
|
logger.error(f"Failed to run schedule chat: {raw_response.text}, user: {user}, query: {query_to_run}")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: khoj
|
|
3
|
-
Version: 1.22.
|
|
3
|
+
Version: 1.22.4.dev6
|
|
4
4
|
Summary: Your Second Brain
|
|
5
5
|
Project-URL: Homepage, https://khoj.dev
|
|
6
6
|
Project-URL: Documentation, https://docs.khoj.dev
|
|
@@ -36,6 +36,7 @@ Requires-Dist: django==5.0.8
|
|
|
36
36
|
Requires-Dist: docx2txt==0.8
|
|
37
37
|
Requires-Dist: einops==0.8.0
|
|
38
38
|
Requires-Dist: fastapi>=0.110.0
|
|
39
|
+
Requires-Dist: google-generativeai==0.7.2
|
|
39
40
|
Requires-Dist: httpx==0.25.0
|
|
40
41
|
Requires-Dist: huggingface-hub>=0.22.2
|
|
41
42
|
Requires-Dist: itsdangerous==2.1.2
|