khoj 1.37.0__py3-none-any.whl → 1.37.1.dev12__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/migrations/0087_alter_aimodelapi_api_key.py +17 -0
- khoj/database/models/__init__.py +1 -1
- khoj/interface/compiled/404/index.html +2 -2
- khoj/interface/compiled/_next/static/chunks/app/agents/{layout-5961c3717e1d8813.js → layout-948ca256650845ce.js} +1 -1
- khoj/interface/compiled/_next/static/chunks/app/agents/{page-0d31f76257d6ec11.js → page-df5446aa4fb82e1a.js} +1 -1
- khoj/interface/compiled/_next/static/chunks/app/automations/{page-c6180af69fc9c766.js → page-0a44416f9183aec0.js} +1 -1
- khoj/interface/compiled/_next/static/chunks/app/chat/{layout-3e25af7224d678a0.js → layout-603285e3b1400e74.js} +1 -1
- khoj/interface/compiled/_next/static/chunks/app/chat/{page-e3f298848d59803b.js → page-50cb9b62b10b5f3d.js} +1 -1
- khoj/interface/compiled/_next/static/chunks/app/{page-df51e61295c4a9b5.js → page-aad371f66ac52191.js} +1 -1
- khoj/interface/compiled/_next/static/chunks/app/search/{layout-9ccd090dcc2aa58a.js → layout-d7f7528ff387fba5.js} +1 -1
- khoj/interface/compiled/_next/static/chunks/app/search/{page-098017fa7f6ba0bf.js → page-1df7b236b30620f7.js} +1 -1
- khoj/interface/compiled/_next/static/chunks/app/settings/{page-1ff027f4e0a5c468.js → page-3473bab693ef81b2.js} +1 -1
- khoj/interface/compiled/_next/static/chunks/app/share/chat/{layout-9cc742afcea0b421.js → layout-246d0e8125219fff.js} +1 -1
- khoj/interface/compiled/_next/static/chunks/app/share/chat/{page-ea29a4633737cb59.js → page-6f26fe7f2f7edc56.js} +1 -1
- khoj/interface/compiled/_next/static/chunks/{webpack-2707657083ef5456.js → webpack-1169ca6e9e7e6247.js} +1 -1
- khoj/interface/compiled/_next/static/css/37a73b87f02df402.css +1 -0
- khoj/interface/compiled/_next/static/css/f29752d6e1be7624.css +1 -0
- khoj/interface/compiled/_next/static/css/{0db53bacf81896f5.css → fca983d49c3dd1a3.css} +1 -1
- khoj/interface/compiled/agents/index.html +2 -2
- khoj/interface/compiled/agents/index.txt +3 -3
- khoj/interface/compiled/automations/index.html +2 -2
- khoj/interface/compiled/automations/index.txt +3 -3
- khoj/interface/compiled/chat/index.html +2 -2
- khoj/interface/compiled/chat/index.txt +3 -3
- khoj/interface/compiled/index.html +2 -2
- khoj/interface/compiled/index.txt +2 -2
- khoj/interface/compiled/search/index.html +2 -2
- khoj/interface/compiled/search/index.txt +3 -3
- khoj/interface/compiled/settings/index.html +2 -2
- khoj/interface/compiled/settings/index.txt +5 -5
- khoj/interface/compiled/share/chat/index.html +2 -2
- khoj/interface/compiled/share/chat/index.txt +3 -3
- khoj/processor/conversation/anthropic/anthropic_chat.py +8 -1
- khoj/processor/conversation/anthropic/utils.py +25 -10
- khoj/processor/conversation/google/gemini_chat.py +12 -1
- khoj/processor/conversation/google/utils.py +41 -5
- khoj/processor/conversation/openai/utils.py +5 -4
- khoj/routers/api.py +4 -0
- khoj/routers/auth.py +2 -5
- khoj/routers/helpers.py +13 -3
- khoj/utils/constants.py +2 -0
- khoj/utils/helpers.py +58 -2
- {khoj-1.37.0.dist-info → khoj-1.37.1.dev12.dist-info}/METADATA +4 -5
- {khoj-1.37.0.dist-info → khoj-1.37.1.dev12.dist-info}/RECORD +55 -54
- khoj/interface/compiled/_next/static/css/bb7ea98028b368f3.css +0 -1
- khoj/interface/compiled/_next/static/css/ee66643a6a5bf71c.css +0 -1
- /khoj/interface/compiled/_next/static/chunks/{1915-1943ee8a628b893c.js → 1915-ab4353eaca76f690.js} +0 -0
- /khoj/interface/compiled/_next/static/chunks/{2117-ce1f0a4598f5e4fe.js → 2117-f99825f0a867a42d.js} +0 -0
- /khoj/interface/compiled/_next/static/chunks/{4363-e6ac2203564d1a3b.js → 4363-4efaf12abe696251.js} +0 -0
- /khoj/interface/compiled/_next/static/chunks/{4447-e038b251d626c340.js → 4447-5d44807c40355b1a.js} +0 -0
- /khoj/interface/compiled/_next/static/chunks/{8667-8136f74e9a086fca.js → 8667-adbe6017a66cef10.js} +0 -0
- /khoj/interface/compiled/_next/static/chunks/{9259-640fdd77408475df.js → 9259-d8bcd9da9e80c81e.js} +0 -0
- /khoj/interface/compiled/_next/static/{4luVb2EbBJ977WDHeDuZQ → mnJxv49O8dzkwTygquq43}/_buildManifest.js +0 -0
- /khoj/interface/compiled/_next/static/{4luVb2EbBJ977WDHeDuZQ → mnJxv49O8dzkwTygquq43}/_ssgManifest.js +0 -0
- {khoj-1.37.0.dist-info → khoj-1.37.1.dev12.dist-info}/WHEEL +0 -0
- {khoj-1.37.0.dist-info → khoj-1.37.1.dev12.dist-info}/entry_points.txt +0 -0
- {khoj-1.37.0.dist-info → khoj-1.37.1.dev12.dist-info}/licenses/LICENSE +0 -0
@@ -1,7 +1,9 @@
|
|
1
1
|
import logging
|
2
|
+
import os
|
2
3
|
import random
|
3
4
|
from copy import deepcopy
|
4
5
|
from threading import Thread
|
6
|
+
from typing import Dict
|
5
7
|
|
6
8
|
from google import genai
|
7
9
|
from google.genai import errors as gerrors
|
@@ -22,6 +24,7 @@ from khoj.processor.conversation.utils import (
|
|
22
24
|
get_image_from_url,
|
23
25
|
)
|
24
26
|
from khoj.utils.helpers import (
|
27
|
+
get_ai_api_info,
|
25
28
|
get_chat_usage_metrics,
|
26
29
|
is_none_or_empty,
|
27
30
|
is_promptrace_enabled,
|
@@ -29,6 +32,7 @@ from khoj.utils.helpers import (
|
|
29
32
|
|
30
33
|
logger = logging.getLogger(__name__)
|
31
34
|
|
35
|
+
gemini_clients: Dict[str, genai.Client] = {}
|
32
36
|
|
33
37
|
MAX_OUTPUT_TOKENS_GEMINI = 8192
|
34
38
|
SAFETY_SETTINGS = [
|
@@ -51,6 +55,17 @@ SAFETY_SETTINGS = [
|
|
51
55
|
]
|
52
56
|
|
53
57
|
|
58
|
+
def get_gemini_client(api_key, api_base_url=None) -> genai.Client:
|
59
|
+
api_info = get_ai_api_info(api_key, api_base_url)
|
60
|
+
return genai.Client(
|
61
|
+
location=api_info.region,
|
62
|
+
project=api_info.project,
|
63
|
+
credentials=api_info.credentials,
|
64
|
+
api_key=api_info.api_key,
|
65
|
+
vertexai=api_info.api_key is None,
|
66
|
+
)
|
67
|
+
|
68
|
+
|
54
69
|
@retry(
|
55
70
|
wait=wait_random_exponential(min=1, max=10),
|
56
71
|
stop=stop_after_attempt(2),
|
@@ -58,9 +73,14 @@ SAFETY_SETTINGS = [
|
|
58
73
|
reraise=True,
|
59
74
|
)
|
60
75
|
def gemini_completion_with_backoff(
|
61
|
-
messages, system_prompt, model_name, temperature=0, api_key=None, model_kwargs=None, tracer={}
|
76
|
+
messages, system_prompt, model_name, temperature=0, api_key=None, api_base_url=None, model_kwargs=None, tracer={}
|
62
77
|
) -> str:
|
63
|
-
client =
|
78
|
+
client = gemini_clients.get(api_key)
|
79
|
+
if not client:
|
80
|
+
client = get_gemini_client(api_key, api_base_url)
|
81
|
+
gemini_clients[api_key] = client
|
82
|
+
|
83
|
+
seed = int(os.getenv("KHOJ_LLM_SEED")) if os.getenv("KHOJ_LLM_SEED") else None
|
64
84
|
config = gtypes.GenerateContentConfig(
|
65
85
|
system_instruction=system_prompt,
|
66
86
|
temperature=temperature,
|
@@ -68,6 +88,7 @@ def gemini_completion_with_backoff(
|
|
68
88
|
safety_settings=SAFETY_SETTINGS,
|
69
89
|
response_mime_type=model_kwargs.get("response_mime_type", "text/plain") if model_kwargs else "text/plain",
|
70
90
|
response_schema=model_kwargs.get("response_schema", None) if model_kwargs else None,
|
91
|
+
seed=seed,
|
71
92
|
)
|
72
93
|
|
73
94
|
formatted_messages = [gtypes.Content(role=message.role, parts=message.content) for message in messages]
|
@@ -112,6 +133,7 @@ def gemini_chat_completion_with_backoff(
|
|
112
133
|
model_name,
|
113
134
|
temperature,
|
114
135
|
api_key,
|
136
|
+
api_base_url,
|
115
137
|
system_prompt,
|
116
138
|
completion_func=None,
|
117
139
|
model_kwargs=None,
|
@@ -120,23 +142,37 @@ def gemini_chat_completion_with_backoff(
|
|
120
142
|
g = ThreadedGenerator(compiled_references, online_results, completion_func=completion_func)
|
121
143
|
t = Thread(
|
122
144
|
target=gemini_llm_thread,
|
123
|
-
args=(g, messages, system_prompt, model_name, temperature, api_key, model_kwargs, tracer),
|
145
|
+
args=(g, messages, system_prompt, model_name, temperature, api_key, api_base_url, model_kwargs, tracer),
|
124
146
|
)
|
125
147
|
t.start()
|
126
148
|
return g
|
127
149
|
|
128
150
|
|
129
151
|
def gemini_llm_thread(
|
130
|
-
g,
|
152
|
+
g,
|
153
|
+
messages,
|
154
|
+
system_prompt,
|
155
|
+
model_name,
|
156
|
+
temperature,
|
157
|
+
api_key,
|
158
|
+
api_base_url=None,
|
159
|
+
model_kwargs=None,
|
160
|
+
tracer: dict = {},
|
131
161
|
):
|
132
162
|
try:
|
133
|
-
client =
|
163
|
+
client = gemini_clients.get(api_key)
|
164
|
+
if not client:
|
165
|
+
client = get_gemini_client(api_key, api_base_url)
|
166
|
+
gemini_clients[api_key] = client
|
167
|
+
|
168
|
+
seed = int(os.getenv("KHOJ_LLM_SEED")) if os.getenv("KHOJ_LLM_SEED") else None
|
134
169
|
config = gtypes.GenerateContentConfig(
|
135
170
|
system_instruction=system_prompt,
|
136
171
|
temperature=temperature,
|
137
172
|
max_output_tokens=MAX_OUTPUT_TOKENS_GEMINI,
|
138
173
|
stop_sequences=["Notes:\n["],
|
139
174
|
safety_settings=SAFETY_SETTINGS,
|
175
|
+
seed=seed,
|
140
176
|
)
|
141
177
|
|
142
178
|
aggregated_response = ""
|
@@ -55,7 +55,7 @@ def completion_with_backoff(
|
|
55
55
|
tracer: dict = {},
|
56
56
|
) -> str:
|
57
57
|
client_key = f"{openai_api_key}--{api_base_url}"
|
58
|
-
client
|
58
|
+
client = openai_clients.get(client_key)
|
59
59
|
if not client:
|
60
60
|
client = get_openai_client(openai_api_key, api_base_url)
|
61
61
|
openai_clients[client_key] = client
|
@@ -150,9 +150,8 @@ def llm_thread(
|
|
150
150
|
):
|
151
151
|
try:
|
152
152
|
client_key = f"{openai_api_key}--{api_base_url}"
|
153
|
-
|
154
|
-
|
155
|
-
else:
|
153
|
+
client = openai_clients.get(client_key)
|
154
|
+
if not client:
|
156
155
|
client = get_openai_client(openai_api_key, api_base_url)
|
157
156
|
openai_clients[client_key] = client
|
158
157
|
|
@@ -247,4 +246,6 @@ def get_openai_api_json_support(model_name: str, api_base_url: str = None) -> Js
|
|
247
246
|
host = urlparse(api_base_url).hostname
|
248
247
|
if host and host.endswith(".ai.azure.com"):
|
249
248
|
return JsonSupport.OBJECT
|
249
|
+
if host == "api.deepinfra.com":
|
250
|
+
return JsonSupport.OBJECT
|
250
251
|
return JsonSupport.SCHEMA
|
khoj/routers/api.py
CHANGED
@@ -463,12 +463,14 @@ async def extract_references_and_questions(
|
|
463
463
|
)
|
464
464
|
elif chat_model.model_type == ChatModel.ModelType.ANTHROPIC:
|
465
465
|
api_key = chat_model.ai_model_api.api_key
|
466
|
+
api_base_url = chat_model.ai_model_api.api_base_url
|
466
467
|
chat_model_name = chat_model.name
|
467
468
|
inferred_queries = extract_questions_anthropic(
|
468
469
|
defiltered_query,
|
469
470
|
query_images=query_images,
|
470
471
|
model=chat_model_name,
|
471
472
|
api_key=api_key,
|
473
|
+
api_base_url=api_base_url,
|
472
474
|
conversation_log=meta_log,
|
473
475
|
location_data=location_data,
|
474
476
|
user=user,
|
@@ -479,12 +481,14 @@ async def extract_references_and_questions(
|
|
479
481
|
)
|
480
482
|
elif chat_model.model_type == ChatModel.ModelType.GOOGLE:
|
481
483
|
api_key = chat_model.ai_model_api.api_key
|
484
|
+
api_base_url = chat_model.ai_model_api.api_base_url
|
482
485
|
chat_model_name = chat_model.name
|
483
486
|
inferred_queries = extract_questions_gemini(
|
484
487
|
defiltered_query,
|
485
488
|
query_images=query_images,
|
486
489
|
model=chat_model_name,
|
487
490
|
api_key=api_key,
|
491
|
+
api_base_url=api_base_url,
|
488
492
|
conversation_log=meta_log,
|
489
493
|
location_data=location_data,
|
490
494
|
max_tokens=chat_model.max_prompt_size,
|
khoj/routers/auth.py
CHANGED
@@ -43,12 +43,9 @@ class MagicLinkForm(BaseModel):
|
|
43
43
|
if not state.anonymous_mode:
|
44
44
|
missing_requirements = []
|
45
45
|
from authlib.integrations.starlette_client import OAuth, OAuthError
|
46
|
+
from google.auth.transport import requests as google_requests
|
47
|
+
from google.oauth2 import id_token
|
46
48
|
|
47
|
-
try:
|
48
|
-
from google.auth.transport import requests as google_requests
|
49
|
-
from google.oauth2 import id_token
|
50
|
-
except ImportError:
|
51
|
-
missing_requirements += ["Install the Khoj production package with `pip install khoj[prod]`"]
|
52
49
|
if not os.environ.get("RESEND_API_KEY") and (
|
53
50
|
not os.environ.get("GOOGLE_CLIENT_ID") or not os.environ.get("GOOGLE_CLIENT_SECRET")
|
54
51
|
):
|
khoj/routers/helpers.py
CHANGED
@@ -1220,6 +1220,7 @@ async def send_message_to_model_wrapper(
|
|
1220
1220
|
)
|
1221
1221
|
elif model_type == ChatModel.ModelType.ANTHROPIC:
|
1222
1222
|
api_key = chat_model.ai_model_api.api_key
|
1223
|
+
api_base_url = chat_model.ai_model_api.api_base_url
|
1223
1224
|
truncated_messages = generate_chatml_messages_with_context(
|
1224
1225
|
user_message=query,
|
1225
1226
|
context_message=context,
|
@@ -1239,10 +1240,12 @@ async def send_message_to_model_wrapper(
|
|
1239
1240
|
model=chat_model_name,
|
1240
1241
|
response_type=response_type,
|
1241
1242
|
deepthought=deepthought,
|
1243
|
+
api_base_url=api_base_url,
|
1242
1244
|
tracer=tracer,
|
1243
1245
|
)
|
1244
1246
|
elif model_type == ChatModel.ModelType.GOOGLE:
|
1245
1247
|
api_key = chat_model.ai_model_api.api_key
|
1248
|
+
api_base_url = chat_model.ai_model_api.api_base_url
|
1246
1249
|
truncated_messages = generate_chatml_messages_with_context(
|
1247
1250
|
user_message=query,
|
1248
1251
|
context_message=context,
|
@@ -1262,6 +1265,7 @@ async def send_message_to_model_wrapper(
|
|
1262
1265
|
model=chat_model_name,
|
1263
1266
|
response_type=response_type,
|
1264
1267
|
response_schema=response_schema,
|
1268
|
+
api_base_url=api_base_url,
|
1265
1269
|
tracer=tracer,
|
1266
1270
|
)
|
1267
1271
|
else:
|
@@ -1328,7 +1332,7 @@ def send_message_to_model_wrapper_sync(
|
|
1328
1332
|
query_files=query_files,
|
1329
1333
|
)
|
1330
1334
|
|
1331
|
-
|
1335
|
+
return send_message_to_model(
|
1332
1336
|
messages=truncated_messages,
|
1333
1337
|
api_key=api_key,
|
1334
1338
|
api_base_url=api_base_url,
|
@@ -1338,10 +1342,9 @@ def send_message_to_model_wrapper_sync(
|
|
1338
1342
|
tracer=tracer,
|
1339
1343
|
)
|
1340
1344
|
|
1341
|
-
return openai_response
|
1342
|
-
|
1343
1345
|
elif chat_model.model_type == ChatModel.ModelType.ANTHROPIC:
|
1344
1346
|
api_key = chat_model.ai_model_api.api_key
|
1347
|
+
api_base_url = chat_model.ai_model_api.api_base_url
|
1345
1348
|
truncated_messages = generate_chatml_messages_with_context(
|
1346
1349
|
user_message=message,
|
1347
1350
|
system_message=system_message,
|
@@ -1356,6 +1359,7 @@ def send_message_to_model_wrapper_sync(
|
|
1356
1359
|
return anthropic_send_message_to_model(
|
1357
1360
|
messages=truncated_messages,
|
1358
1361
|
api_key=api_key,
|
1362
|
+
api_base_url=api_base_url,
|
1359
1363
|
model=chat_model_name,
|
1360
1364
|
response_type=response_type,
|
1361
1365
|
tracer=tracer,
|
@@ -1363,6 +1367,7 @@ def send_message_to_model_wrapper_sync(
|
|
1363
1367
|
|
1364
1368
|
elif chat_model.model_type == ChatModel.ModelType.GOOGLE:
|
1365
1369
|
api_key = chat_model.ai_model_api.api_key
|
1370
|
+
api_base_url = chat_model.ai_model_api.api_base_url
|
1366
1371
|
truncated_messages = generate_chatml_messages_with_context(
|
1367
1372
|
user_message=message,
|
1368
1373
|
system_message=system_message,
|
@@ -1377,6 +1382,7 @@ def send_message_to_model_wrapper_sync(
|
|
1377
1382
|
return gemini_send_message_to_model(
|
1378
1383
|
messages=truncated_messages,
|
1379
1384
|
api_key=api_key,
|
1385
|
+
api_base_url=api_base_url,
|
1380
1386
|
model=chat_model_name,
|
1381
1387
|
response_type=response_type,
|
1382
1388
|
response_schema=response_schema,
|
@@ -1510,6 +1516,7 @@ def generate_chat_response(
|
|
1510
1516
|
|
1511
1517
|
elif chat_model.model_type == ChatModel.ModelType.ANTHROPIC:
|
1512
1518
|
api_key = chat_model.ai_model_api.api_key
|
1519
|
+
api_base_url = chat_model.ai_model_api.api_base_url
|
1513
1520
|
chat_response = converse_anthropic(
|
1514
1521
|
compiled_references,
|
1515
1522
|
query_to_run,
|
@@ -1519,6 +1526,7 @@ def generate_chat_response(
|
|
1519
1526
|
conversation_log=meta_log,
|
1520
1527
|
model=chat_model.name,
|
1521
1528
|
api_key=api_key,
|
1529
|
+
api_base_url=api_base_url,
|
1522
1530
|
completion_func=partial_completion,
|
1523
1531
|
conversation_commands=conversation_commands,
|
1524
1532
|
max_prompt_size=chat_model.max_prompt_size,
|
@@ -1536,6 +1544,7 @@ def generate_chat_response(
|
|
1536
1544
|
)
|
1537
1545
|
elif chat_model.model_type == ChatModel.ModelType.GOOGLE:
|
1538
1546
|
api_key = chat_model.ai_model_api.api_key
|
1547
|
+
api_base_url = chat_model.ai_model_api.api_base_url
|
1539
1548
|
chat_response = converse_gemini(
|
1540
1549
|
compiled_references,
|
1541
1550
|
query_to_run,
|
@@ -1544,6 +1553,7 @@ def generate_chat_response(
|
|
1544
1553
|
meta_log,
|
1545
1554
|
model=chat_model.name,
|
1546
1555
|
api_key=api_key,
|
1556
|
+
api_base_url=api_base_url,
|
1547
1557
|
completion_func=partial_completion,
|
1548
1558
|
conversation_commands=conversation_commands,
|
1549
1559
|
max_prompt_size=chat_model.max_prompt_size,
|
khoj/utils/constants.py
CHANGED
@@ -49,8 +49,10 @@ model_to_cost: Dict[str, Dict[str, float]] = {
|
|
49
49
|
"gemini-2.0-flash": {"input": 0.10, "output": 0.40},
|
50
50
|
# Anthropic Pricing: https://www.anthropic.com/pricing#anthropic-api_
|
51
51
|
"claude-3-5-haiku-20241022": {"input": 1.0, "output": 5.0},
|
52
|
+
"claude-3-5-haiku@20241022": {"input": 1.0, "output": 5.0},
|
52
53
|
"claude-3-5-sonnet-20241022": {"input": 3.0, "output": 15.0},
|
53
54
|
"claude-3-5-sonnet-latest": {"input": 3.0, "output": 15.0},
|
54
55
|
"claude-3-7-sonnet-20250219": {"input": 3.0, "output": 15.0},
|
56
|
+
"claude-3-7-sonnet@20250219": {"input": 3.0, "output": 15.0},
|
55
57
|
"claude-3-7-sonnet-latest": {"input": 3.0, "output": 15.0},
|
56
58
|
}
|
khoj/utils/helpers.py
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
from __future__ import annotations # to avoid quoting type hints
|
2
2
|
|
3
|
+
import base64
|
3
4
|
import copy
|
4
5
|
import datetime
|
5
6
|
import io
|
@@ -19,15 +20,18 @@ from itertools import islice
|
|
19
20
|
from os import path
|
20
21
|
from pathlib import Path
|
21
22
|
from time import perf_counter
|
22
|
-
from typing import TYPE_CHECKING, Any, Optional, Union
|
23
|
-
from urllib.parse import urlparse
|
23
|
+
from typing import TYPE_CHECKING, Any, NamedTuple, Optional, Tuple, Union
|
24
|
+
from urllib.parse import ParseResult, urlparse
|
24
25
|
|
25
26
|
import openai
|
26
27
|
import psutil
|
28
|
+
import pyjson5
|
27
29
|
import requests
|
28
30
|
import torch
|
29
31
|
from asgiref.sync import sync_to_async
|
30
32
|
from email_validator import EmailNotValidError, EmailUndeliverableError, validate_email
|
33
|
+
from google.auth.credentials import Credentials
|
34
|
+
from google.oauth2 import service_account
|
31
35
|
from magika import Magika
|
32
36
|
from PIL import Image
|
33
37
|
from pytz import country_names, country_timezones
|
@@ -618,6 +622,58 @@ def get_chat_usage_metrics(
|
|
618
622
|
}
|
619
623
|
|
620
624
|
|
625
|
+
class AiApiInfo(NamedTuple):
|
626
|
+
region: str
|
627
|
+
project: str
|
628
|
+
credentials: Credentials
|
629
|
+
api_key: str
|
630
|
+
|
631
|
+
|
632
|
+
def get_gcp_credentials(credentials_b64: str) -> Optional[Credentials]:
|
633
|
+
"""
|
634
|
+
Get GCP credentials from base64 encoded service account credentials json keyfile
|
635
|
+
"""
|
636
|
+
credentials_json = base64.b64decode(credentials_b64).decode("utf-8")
|
637
|
+
credentials_dict = pyjson5.loads(credentials_json)
|
638
|
+
credentials = service_account.Credentials.from_service_account_info(credentials_dict)
|
639
|
+
return credentials.with_scopes(scopes=["https://www.googleapis.com/auth/cloud-platform"])
|
640
|
+
|
641
|
+
|
642
|
+
def get_gcp_project_info(parsed_api_url: ParseResult) -> Tuple[str, str]:
|
643
|
+
"""
|
644
|
+
Extract region, project id from GCP API url
|
645
|
+
API url is of form https://{REGION}-aiplatform.googleapis.com/v1/projects/{PROJECT_ID}...
|
646
|
+
"""
|
647
|
+
# Extract region from hostname
|
648
|
+
hostname = parsed_api_url.netloc
|
649
|
+
region = hostname.split("-aiplatform")[0] if "-aiplatform" in hostname else ""
|
650
|
+
|
651
|
+
# Extract project ID from path (e.g., "/v1/projects/my-project/...")
|
652
|
+
path_parts = parsed_api_url.path.split("/")
|
653
|
+
project_id = ""
|
654
|
+
for i, part in enumerate(path_parts):
|
655
|
+
if part == "projects" and i + 1 < len(path_parts):
|
656
|
+
project_id = path_parts[i + 1]
|
657
|
+
break
|
658
|
+
|
659
|
+
return region, project_id
|
660
|
+
|
661
|
+
|
662
|
+
def get_ai_api_info(api_key, api_base_url: str = None) -> AiApiInfo:
|
663
|
+
"""
|
664
|
+
Get the GCP Vertex or default AI API client info based on the API key and URL.
|
665
|
+
"""
|
666
|
+
region, project_id, credentials = None, None, None
|
667
|
+
# Check if AI model to be used via GCP Vertex API
|
668
|
+
parsed_api_url = urlparse(api_base_url)
|
669
|
+
if parsed_api_url.hostname and parsed_api_url.hostname.endswith(".googleapis.com"):
|
670
|
+
region, project_id = get_gcp_project_info(parsed_api_url)
|
671
|
+
credentials = get_gcp_credentials(api_key)
|
672
|
+
if credentials:
|
673
|
+
api_key = None
|
674
|
+
return AiApiInfo(region=region, project=project_id, credentials=credentials, api_key=api_key)
|
675
|
+
|
676
|
+
|
621
677
|
def get_openai_client(api_key: str, api_base_url: str) -> Union[openai.OpenAI, openai.AzureOpenAI]:
|
622
678
|
"""Get OpenAI or AzureOpenAI client based on the API Base URL"""
|
623
679
|
parsed_url = urlparse(api_base_url)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: khoj
|
3
|
-
Version: 1.37.
|
3
|
+
Version: 1.37.1.dev12
|
4
4
|
Summary: Your Second Brain
|
5
5
|
Project-URL: Homepage, https://khoj.dev
|
6
6
|
Project-URL: Documentation, https://docs.khoj.dev
|
@@ -39,6 +39,7 @@ 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-auth~=2.23.3
|
42
43
|
Requires-Dist: google-genai==1.5.0
|
43
44
|
Requires-Dist: httpx==0.28.1
|
44
45
|
Requires-Dist: huggingface-hub>=0.22.2
|
@@ -69,12 +70,12 @@ Requires-Dist: requests>=2.26.0
|
|
69
70
|
Requires-Dist: resend==1.0.1
|
70
71
|
Requires-Dist: rich>=13.3.1
|
71
72
|
Requires-Dist: schedule==1.1.0
|
72
|
-
Requires-Dist: sentence-transformers==3.
|
73
|
+
Requires-Dist: sentence-transformers==3.4.1
|
73
74
|
Requires-Dist: tenacity==8.3.0
|
74
75
|
Requires-Dist: tenacity>=8.2.2
|
75
76
|
Requires-Dist: tiktoken>=0.3.2
|
76
77
|
Requires-Dist: torch==2.2.2
|
77
|
-
Requires-Dist: transformers
|
78
|
+
Requires-Dist: transformers<4.50.0,>=4.28.0
|
78
79
|
Requires-Dist: tzdata==2023.3
|
79
80
|
Requires-Dist: uvicorn==0.30.6
|
80
81
|
Requires-Dist: websockets==13.0
|
@@ -85,7 +86,6 @@ Requires-Dist: datasets; extra == 'dev'
|
|
85
86
|
Requires-Dist: factory-boy>=3.2.1; extra == 'dev'
|
86
87
|
Requires-Dist: freezegun>=1.2.0; extra == 'dev'
|
87
88
|
Requires-Dist: gitpython~=3.1.43; extra == 'dev'
|
88
|
-
Requires-Dist: google-auth==2.23.3; extra == 'dev'
|
89
89
|
Requires-Dist: gunicorn==22.0.0; extra == 'dev'
|
90
90
|
Requires-Dist: mypy>=1.0.1; extra == 'dev'
|
91
91
|
Requires-Dist: pandas; extra == 'dev'
|
@@ -98,7 +98,6 @@ Requires-Dist: stripe==7.3.0; extra == 'dev'
|
|
98
98
|
Requires-Dist: twilio==8.11; extra == 'dev'
|
99
99
|
Provides-Extra: prod
|
100
100
|
Requires-Dist: boto3>=1.34.57; extra == 'prod'
|
101
|
-
Requires-Dist: google-auth==2.23.3; extra == 'prod'
|
102
101
|
Requires-Dist: gunicorn==22.0.0; extra == 'prod'
|
103
102
|
Requires-Dist: stripe==7.3.0; extra == 'prod'
|
104
103
|
Requires-Dist: twilio==8.11; extra == 'prod'
|