khoj 1.40.1.dev1__py3-none-any.whl → 1.40.1.dev13__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/app/settings.py +6 -12
- khoj/database/adapters/__init__.py +31 -10
- khoj/database/migrations/0089_chatmodel_price_tier_and_more.py +34 -0
- khoj/database/models/__init__.py +20 -0
- khoj/interface/compiled/404/index.html +2 -2
- khoj/interface/compiled/_next/static/chunks/2327-7c83cc910eb06c02.js +1 -0
- khoj/interface/compiled/_next/static/chunks/5427-442f34b514b9fc26.js +1 -0
- khoj/interface/compiled/_next/static/chunks/app/agents/layout-e00fb81dca656a10.js +1 -0
- khoj/interface/compiled/_next/static/chunks/app/chat/page-e45eb7006686f517.js +1 -0
- khoj/interface/compiled/_next/static/chunks/app/layout-baa6e7974e560a7a.js +1 -0
- khoj/interface/compiled/_next/static/chunks/app/settings/page-bf1a4e488b29fceb.js +1 -0
- khoj/interface/compiled/_next/static/chunks/app/share/chat/layout-b3f7ae1ef8871d30.js +1 -0
- khoj/interface/compiled/_next/static/chunks/{webpack-c6bde5961098facd.js → webpack-449ea2835ec65e75.js} +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 +4 -4
- 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 +3 -3
- 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/utils.py +7 -2
- khoj/processor/conversation/google/gemini_chat.py +4 -0
- khoj/processor/conversation/google/utils.py +44 -5
- khoj/processor/conversation/openai/utils.py +32 -6
- khoj/processor/tools/run_code.py +7 -2
- khoj/routers/api_agents.py +39 -11
- khoj/routers/api_model.py +41 -7
- khoj/routers/helpers.py +17 -2
- khoj/utils/constants.py +9 -0
- khoj/utils/helpers.py +13 -2
- {khoj-1.40.1.dev1.dist-info → khoj-1.40.1.dev13.dist-info}/METADATA +16 -3
- {khoj-1.40.1.dev1.dist-info → khoj-1.40.1.dev13.dist-info}/RECORD +57 -56
- khoj/interface/compiled/_next/static/chunks/2327-9d37761c0bdbc3ff.js +0 -1
- khoj/interface/compiled/_next/static/chunks/5427-ec87e7fa4b0d76cb.js +0 -1
- khoj/interface/compiled/_next/static/chunks/app/agents/layout-e49165209d2e406c.js +0 -1
- khoj/interface/compiled/_next/static/chunks/app/chat/page-8233a00e74d4aa5f.js +0 -1
- khoj/interface/compiled/_next/static/chunks/app/layout-bd8210ff1de491d7.js +0 -1
- khoj/interface/compiled/_next/static/chunks/app/settings/page-32122d865d786a47.js +0 -1
- khoj/interface/compiled/_next/static/chunks/app/share/chat/layout-64a53f8ec4afa6b3.js +0 -1
- /khoj/interface/compiled/_next/static/{cbvk0UJyMR5BLJ1jy2JpS → YNEjRfej8eEywDaKbukO9}/_buildManifest.js +0 -0
- /khoj/interface/compiled/_next/static/{cbvk0UJyMR5BLJ1jy2JpS → YNEjRfej8eEywDaKbukO9}/_ssgManifest.js +0 -0
- /khoj/interface/compiled/_next/static/chunks/{1915-ab4353eaca76f690.js → 1915-1943ee8a628b893c.js} +0 -0
- /khoj/interface/compiled/_next/static/chunks/{2117-1c18aa2098982bf9.js → 2117-5a41630a2bd2eae8.js} +0 -0
- /khoj/interface/compiled/_next/static/chunks/{4363-4efaf12abe696251.js → 4363-e6ac2203564d1a3b.js} +0 -0
- /khoj/interface/compiled/_next/static/chunks/{4447-5d44807c40355b1a.js → 4447-e038b251d626c340.js} +0 -0
- /khoj/interface/compiled/_next/static/chunks/{4986-10ca6c2d6cbcb448.js → 4986-14ea63faad1615a4.js} +0 -0
- /khoj/interface/compiled/_next/static/chunks/{5477-060a89922423c280.js → 5477-c47f3a23981c89da.js} +0 -0
- /khoj/interface/compiled/_next/static/chunks/{8667-adbe6017a66cef10.js → 8667-8136f74e9a086fca.js} +0 -0
- /khoj/interface/compiled/_next/static/chunks/{9259-d8bcd9da9e80c81e.js → 9259-640fdd77408475df.js} +0 -0
- /khoj/interface/compiled/_next/static/chunks/app/agents/{page-3993a8df749f2f29.js → page-c9ceb9b94e24b94a.js} +0 -0
- /khoj/interface/compiled/_next/static/chunks/app/automations/{page-50182e85e30880e1.js → page-3dc59a0df3827dc7.js} +0 -0
- /khoj/interface/compiled/_next/static/chunks/app/{page-392b7719999f2e46.js → page-38f1f125d7aeb4c7.js} +0 -0
- /khoj/interface/compiled/_next/static/chunks/app/search/{page-f7f648807b59310a.js → page-26d4492fb1200e0e.js} +0 -0
- /khoj/interface/compiled/_next/static/chunks/app/share/chat/{page-52a567d6080cd5bb.js → page-a1f10c96366c3a4f.js} +0 -0
- {khoj-1.40.1.dev1.dist-info → khoj-1.40.1.dev13.dist-info}/WHEEL +0 -0
- {khoj-1.40.1.dev1.dist-info → khoj-1.40.1.dev13.dist-info}/entry_points.txt +0 -0
- {khoj-1.40.1.dev1.dist-info → khoj-1.40.1.dev13.dist-info}/licenses/LICENSE +0 -0
@@ -64,9 +64,13 @@ def completion_with_backoff(
|
|
64
64
|
formatted_messages = [{"role": message.role, "content": message.content} for message in messages]
|
65
65
|
|
66
66
|
# Tune reasoning models arguments
|
67
|
-
if model_name
|
67
|
+
if is_openai_reasoning_model(model_name, api_base_url):
|
68
68
|
temperature = 1
|
69
|
-
|
69
|
+
reasoning_effort = "medium" if deepthought else "low"
|
70
|
+
model_kwargs["reasoning_effort"] = reasoning_effort
|
71
|
+
elif is_twitter_reasoning_model(model_name, api_base_url):
|
72
|
+
reasoning_effort = "high" if deepthought else "low"
|
73
|
+
model_kwargs["reasoning_effort"] = reasoning_effort
|
70
74
|
|
71
75
|
model_kwargs["stream_options"] = {"include_usage": True}
|
72
76
|
if os.getenv("KHOJ_LLM_SEED"):
|
@@ -162,12 +166,13 @@ def llm_thread(
|
|
162
166
|
|
163
167
|
formatted_messages = [{"role": message.role, "content": message.content} for message in messages]
|
164
168
|
|
165
|
-
#
|
166
|
-
if model_name
|
169
|
+
# Configure thinking for openai reasoning models
|
170
|
+
if is_openai_reasoning_model(model_name, api_base_url):
|
167
171
|
temperature = 1
|
168
|
-
|
172
|
+
reasoning_effort = "medium" if deepthought else "low"
|
173
|
+
model_kwargs["reasoning_effort"] = reasoning_effort
|
174
|
+
model_kwargs.pop("stop", None) # Remove unsupported stop param for reasoning models
|
169
175
|
|
170
|
-
if model_name.startswith("o3"):
|
171
176
|
# Get the first system message and add the string `Formatting re-enabled` to it.
|
172
177
|
# See https://platform.openai.com/docs/guides/reasoning-best-practices
|
173
178
|
if len(formatted_messages) > 0:
|
@@ -179,6 +184,9 @@ def llm_thread(
|
|
179
184
|
formatted_messages[first_system_message_index][
|
180
185
|
"content"
|
181
186
|
] = f"{first_system_message} Formatting re-enabled"
|
187
|
+
elif is_twitter_reasoning_model(model_name, api_base_url):
|
188
|
+
reasoning_effort = "high" if deepthought else "low"
|
189
|
+
model_kwargs["reasoning_effort"] = reasoning_effort
|
182
190
|
elif model_name.startswith("deepseek-reasoner"):
|
183
191
|
# Two successive messages cannot be from the same role. Should merge any back-to-back messages from the same role.
|
184
192
|
# The first message should always be a user message (except system message).
|
@@ -257,3 +265,21 @@ def get_openai_api_json_support(model_name: str, api_base_url: str = None) -> Js
|
|
257
265
|
if host == "api.deepinfra.com":
|
258
266
|
return JsonSupport.OBJECT
|
259
267
|
return JsonSupport.SCHEMA
|
268
|
+
|
269
|
+
|
270
|
+
def is_openai_reasoning_model(model_name: str, api_base_url: str = None) -> bool:
|
271
|
+
"""
|
272
|
+
Check if the model is an OpenAI reasoning model
|
273
|
+
"""
|
274
|
+
return model_name.startswith("o") and (api_base_url is None or api_base_url.startswith("https://api.openai.com/v1"))
|
275
|
+
|
276
|
+
|
277
|
+
def is_twitter_reasoning_model(model_name: str, api_base_url: str = None) -> bool:
|
278
|
+
"""
|
279
|
+
Check if the model is a Twitter reasoning model
|
280
|
+
"""
|
281
|
+
return (
|
282
|
+
model_name.startswith("grok-3-mini")
|
283
|
+
and api_base_url is not None
|
284
|
+
and api_base_url.startswith("https://api.x.ai/v1")
|
285
|
+
)
|
khoj/processor/tools/run_code.py
CHANGED
@@ -244,6 +244,7 @@ async def execute_e2b(code: str, input_files: list[dict]) -> dict[str, Any]:
|
|
244
244
|
|
245
245
|
# Collect output files
|
246
246
|
output_files = []
|
247
|
+
image_file_ext = {".png", ".jpeg", ".jpg", ".svg"}
|
247
248
|
|
248
249
|
# Identify new files created during execution
|
249
250
|
new_files = set(E2bFile(f.name, f.path) for f in await sandbox.files.list("~")) - original_files
|
@@ -254,7 +255,7 @@ async def execute_e2b(code: str, input_files: list[dict]) -> dict[str, Any]:
|
|
254
255
|
if isinstance(content, bytes):
|
255
256
|
# Binary files like PNG - encode as base64
|
256
257
|
b64_data = base64.b64encode(content).decode("utf-8")
|
257
|
-
elif Path(f.name).suffix in
|
258
|
+
elif Path(f.name).suffix in image_file_ext:
|
258
259
|
# Ignore image files as they are extracted from execution results below for inline display
|
259
260
|
continue
|
260
261
|
else:
|
@@ -263,8 +264,12 @@ async def execute_e2b(code: str, input_files: list[dict]) -> dict[str, Any]:
|
|
263
264
|
output_files.append({"filename": f.name, "b64_data": b64_data})
|
264
265
|
|
265
266
|
# Collect output files from execution results
|
267
|
+
# Repect ordering of output result types to disregard text output associated with images
|
268
|
+
output_result_types = ["png", "jpeg", "svg", "text", "markdown", "json"]
|
266
269
|
for idx, result in enumerate(execution.results):
|
267
|
-
|
270
|
+
if getattr(result, "chart", None):
|
271
|
+
continue
|
272
|
+
for result_type in output_result_types:
|
268
273
|
if b64_data := getattr(result, result_type, None):
|
269
274
|
output_files.append({"filename": f"{idx}.{result_type}", "b64_data": b64_data})
|
270
275
|
break
|
khoj/routers/api_agents.py
CHANGED
@@ -12,7 +12,7 @@ from pydantic import BaseModel
|
|
12
12
|
from starlette.authentication import has_required_scope, requires
|
13
13
|
|
14
14
|
from khoj.database.adapters import AgentAdapters, ConversationAdapters, EntryAdapters
|
15
|
-
from khoj.database.models import Agent, Conversation, KhojUser
|
15
|
+
from khoj.database.models import Agent, Conversation, KhojUser, PriceTier
|
16
16
|
from khoj.routers.helpers import CommonQueryParams, acheck_if_safe_prompt
|
17
17
|
from khoj.utils.helpers import (
|
18
18
|
ConversationCommand,
|
@@ -125,8 +125,20 @@ async def get_agent_by_conversation(
|
|
125
125
|
else:
|
126
126
|
agent = await AgentAdapters.aget_default_agent()
|
127
127
|
|
128
|
+
if agent is None:
|
129
|
+
return Response(
|
130
|
+
content=json.dumps({"error": f"Agent for conversation id {conversation_id} not found for user {user}."}),
|
131
|
+
media_type="application/json",
|
132
|
+
status_code=404,
|
133
|
+
)
|
134
|
+
|
135
|
+
chat_model = await AgentAdapters.aget_agent_chat_model(agent, user)
|
136
|
+
if is_subscribed or chat_model.price_tier == PriceTier.FREE:
|
137
|
+
agent_chat_model = chat_model.name
|
138
|
+
else:
|
139
|
+
agent_chat_model = None
|
140
|
+
|
128
141
|
has_files = agent.fileobject_set.exists()
|
129
|
-
agent.chat_model = await AgentAdapters.aget_agent_chat_model(agent, user)
|
130
142
|
|
131
143
|
agents_packet = {
|
132
144
|
"slug": agent.slug,
|
@@ -137,7 +149,7 @@ async def get_agent_by_conversation(
|
|
137
149
|
"color": agent.style_color,
|
138
150
|
"icon": agent.style_icon,
|
139
151
|
"privacy_level": agent.privacy_level,
|
140
|
-
"chat_model":
|
152
|
+
"chat_model": agent_chat_model,
|
141
153
|
"has_files": has_files,
|
142
154
|
"input_tools": agent.input_tools,
|
143
155
|
"output_modes": agent.output_modes,
|
@@ -249,7 +261,11 @@ async def update_hidden_agent(
|
|
249
261
|
user: KhojUser = request.user.object
|
250
262
|
|
251
263
|
subscribed = has_required_scope(request, ["premium"])
|
252
|
-
chat_model = body.chat_model
|
264
|
+
chat_model = await ConversationAdapters.aget_chat_model_by_name(body.chat_model)
|
265
|
+
if subscribed or chat_model.price_tier == PriceTier.FREE:
|
266
|
+
agent_chat_model = body.chat_model
|
267
|
+
else:
|
268
|
+
agent_chat_model = None
|
253
269
|
|
254
270
|
selected_agent = await AgentAdapters.aget_agent_by_slug(body.slug, user)
|
255
271
|
|
@@ -264,7 +280,7 @@ async def update_hidden_agent(
|
|
264
280
|
user=user,
|
265
281
|
slug=body.slug,
|
266
282
|
persona=body.persona,
|
267
|
-
chat_model=
|
283
|
+
chat_model=agent_chat_model,
|
268
284
|
input_tools=body.input_tools,
|
269
285
|
output_modes=body.output_modes,
|
270
286
|
existing_agent=selected_agent,
|
@@ -295,7 +311,11 @@ async def create_hidden_agent(
|
|
295
311
|
user: KhojUser = request.user.object
|
296
312
|
|
297
313
|
subscribed = has_required_scope(request, ["premium"])
|
298
|
-
chat_model = body.chat_model
|
314
|
+
chat_model = await ConversationAdapters.aget_chat_model_by_name(body.chat_model)
|
315
|
+
if subscribed or chat_model.price_tier == PriceTier.FREE:
|
316
|
+
agent_chat_model = body.chat_model
|
317
|
+
else:
|
318
|
+
agent_chat_model = None
|
299
319
|
|
300
320
|
conversation = await ConversationAdapters.aget_conversation_by_user(user=user, conversation_id=conversation_id)
|
301
321
|
if not conversation:
|
@@ -320,7 +340,7 @@ async def create_hidden_agent(
|
|
320
340
|
user=user,
|
321
341
|
slug=body.slug,
|
322
342
|
persona=body.persona,
|
323
|
-
chat_model=
|
343
|
+
chat_model=agent_chat_model,
|
324
344
|
input_tools=body.input_tools,
|
325
345
|
output_modes=body.output_modes,
|
326
346
|
existing_agent=None,
|
@@ -364,7 +384,11 @@ async def create_agent(
|
|
364
384
|
)
|
365
385
|
|
366
386
|
subscribed = has_required_scope(request, ["premium"])
|
367
|
-
chat_model = body.chat_model
|
387
|
+
chat_model = await ConversationAdapters.aget_chat_model_by_name(body.chat_model)
|
388
|
+
if subscribed or chat_model.price_tier == PriceTier.FREE:
|
389
|
+
agent_chat_model = body.chat_model
|
390
|
+
else:
|
391
|
+
agent_chat_model = None
|
368
392
|
|
369
393
|
agent = await AgentAdapters.aupdate_agent(
|
370
394
|
user,
|
@@ -373,7 +397,7 @@ async def create_agent(
|
|
373
397
|
body.privacy_level,
|
374
398
|
body.icon,
|
375
399
|
body.color,
|
376
|
-
|
400
|
+
agent_chat_model,
|
377
401
|
body.files,
|
378
402
|
body.input_tools,
|
379
403
|
body.output_modes,
|
@@ -431,7 +455,11 @@ async def update_agent(
|
|
431
455
|
)
|
432
456
|
|
433
457
|
subscribed = has_required_scope(request, ["premium"])
|
434
|
-
chat_model = body.chat_model
|
458
|
+
chat_model = await ConversationAdapters.aget_chat_model_by_name(body.chat_model)
|
459
|
+
if subscribed or chat_model.price_tier == PriceTier.FREE:
|
460
|
+
agent_chat_model = body.chat_model
|
461
|
+
else:
|
462
|
+
agent_chat_model = None
|
435
463
|
|
436
464
|
agent = await AgentAdapters.aupdate_agent(
|
437
465
|
user,
|
@@ -440,7 +468,7 @@ async def update_agent(
|
|
440
468
|
body.privacy_level,
|
441
469
|
body.icon,
|
442
470
|
body.color,
|
443
|
-
|
471
|
+
agent_chat_model,
|
444
472
|
body.files,
|
445
473
|
body.input_tools,
|
446
474
|
body.output_modes,
|
khoj/routers/api_model.py
CHANGED
@@ -2,13 +2,18 @@ import json
|
|
2
2
|
import logging
|
3
3
|
from typing import Dict, Optional, Union
|
4
4
|
|
5
|
-
from fastapi import APIRouter,
|
5
|
+
from fastapi import APIRouter, Request
|
6
6
|
from fastapi.requests import Request
|
7
7
|
from fastapi.responses import Response
|
8
8
|
from starlette.authentication import has_required_scope, requires
|
9
9
|
|
10
|
-
from khoj.database import
|
11
|
-
from khoj.database.
|
10
|
+
from khoj.database.adapters import ConversationAdapters
|
11
|
+
from khoj.database.models import (
|
12
|
+
ChatModel,
|
13
|
+
PriceTier,
|
14
|
+
TextToImageModelConfig,
|
15
|
+
VoiceModelOption,
|
16
|
+
)
|
12
17
|
from khoj.routers.helpers import update_telemetry_state
|
13
18
|
|
14
19
|
api_model = APIRouter()
|
@@ -53,13 +58,24 @@ def get_user_chat_model(
|
|
53
58
|
|
54
59
|
|
55
60
|
@api_model.post("/chat", status_code=200)
|
56
|
-
@requires(["authenticated"
|
61
|
+
@requires(["authenticated"])
|
57
62
|
async def update_chat_model(
|
58
63
|
request: Request,
|
59
64
|
id: str,
|
60
65
|
client: Optional[str] = None,
|
61
66
|
):
|
62
67
|
user = request.user.object
|
68
|
+
subscribed = has_required_scope(request, ["premium"])
|
69
|
+
|
70
|
+
# Validate if model can be switched
|
71
|
+
chat_model = await ChatModel.objects.filter(id=int(id)).afirst()
|
72
|
+
if chat_model is None:
|
73
|
+
return Response(status_code=404, content=json.dumps({"status": "error", "message": "Chat model not found"}))
|
74
|
+
if not subscribed and chat_model.price_tier != PriceTier.FREE:
|
75
|
+
raise Response(
|
76
|
+
status_code=403,
|
77
|
+
content=json.dumps({"status": "error", "message": "Subscribe to switch to this chat model"}),
|
78
|
+
)
|
63
79
|
|
64
80
|
new_config = await ConversationAdapters.aset_user_conversation_processor(user, int(id))
|
65
81
|
|
@@ -78,13 +94,24 @@ async def update_chat_model(
|
|
78
94
|
|
79
95
|
|
80
96
|
@api_model.post("/voice", status_code=200)
|
81
|
-
@requires(["authenticated"
|
97
|
+
@requires(["authenticated"])
|
82
98
|
async def update_voice_model(
|
83
99
|
request: Request,
|
84
100
|
id: str,
|
85
101
|
client: Optional[str] = None,
|
86
102
|
):
|
87
103
|
user = request.user.object
|
104
|
+
subscribed = has_required_scope(request, ["premium"])
|
105
|
+
|
106
|
+
# Validate if model can be switched
|
107
|
+
voice_model = await VoiceModelOption.objects.filter(id=int(id)).afirst()
|
108
|
+
if voice_model is None:
|
109
|
+
return Response(status_code=404, content=json.dumps({"status": "error", "message": "Voice model not found"}))
|
110
|
+
if not subscribed and voice_model.price_tier != PriceTier.FREE:
|
111
|
+
raise Response(
|
112
|
+
status_code=403,
|
113
|
+
content=json.dumps({"status": "error", "message": "Subscribe to switch to this voice model"}),
|
114
|
+
)
|
88
115
|
|
89
116
|
new_config = await ConversationAdapters.aset_user_voice_model(user, id)
|
90
117
|
|
@@ -111,8 +138,15 @@ async def update_paint_model(
|
|
111
138
|
user = request.user.object
|
112
139
|
subscribed = has_required_scope(request, ["premium"])
|
113
140
|
|
114
|
-
if
|
115
|
-
|
141
|
+
# Validate if model can be switched
|
142
|
+
image_model = await TextToImageModelConfig.objects.filter(id=int(id)).afirst()
|
143
|
+
if image_model is None:
|
144
|
+
return Response(status_code=404, content=json.dumps({"status": "error", "message": "Image model not found"}))
|
145
|
+
if not subscribed and image_model.price_tier != PriceTier.FREE:
|
146
|
+
raise Response(
|
147
|
+
status_code=403,
|
148
|
+
content=json.dumps({"status": "error", "message": "Subscribe to switch to this image model"}),
|
149
|
+
)
|
116
150
|
|
117
151
|
new_config = await ConversationAdapters.aset_user_text_to_image_model(user, int(id))
|
118
152
|
|
khoj/routers/helpers.py
CHANGED
@@ -1290,6 +1290,7 @@ async def send_message_to_model_wrapper(
|
|
1290
1290
|
model=chat_model_name,
|
1291
1291
|
response_type=response_type,
|
1292
1292
|
response_schema=response_schema,
|
1293
|
+
deepthought=deepthought,
|
1293
1294
|
api_base_url=api_base_url,
|
1294
1295
|
tracer=tracer,
|
1295
1296
|
)
|
@@ -1593,6 +1594,7 @@ def generate_chat_response(
|
|
1593
1594
|
generated_files=raw_generated_files,
|
1594
1595
|
generated_asset_results=generated_asset_results,
|
1595
1596
|
program_execution_context=program_execution_context,
|
1597
|
+
deepthought=deepthought,
|
1596
1598
|
tracer=tracer,
|
1597
1599
|
)
|
1598
1600
|
|
@@ -2362,6 +2364,7 @@ def get_user_config(user: KhojUser, request: Request, is_detailed: bool = False)
|
|
2362
2364
|
"id": chat_model.id,
|
2363
2365
|
"strengths": chat_model.strengths,
|
2364
2366
|
"description": chat_model.description,
|
2367
|
+
"tier": chat_model.price_tier,
|
2365
2368
|
}
|
2366
2369
|
)
|
2367
2370
|
|
@@ -2369,12 +2372,24 @@ def get_user_config(user: KhojUser, request: Request, is_detailed: bool = False)
|
|
2369
2372
|
paint_model_options = ConversationAdapters.get_text_to_image_model_options().all()
|
2370
2373
|
all_paint_model_options = list()
|
2371
2374
|
for paint_model in paint_model_options:
|
2372
|
-
all_paint_model_options.append(
|
2375
|
+
all_paint_model_options.append(
|
2376
|
+
{
|
2377
|
+
"name": paint_model.model_name,
|
2378
|
+
"id": paint_model.id,
|
2379
|
+
"tier": paint_model.price_tier,
|
2380
|
+
}
|
2381
|
+
)
|
2373
2382
|
|
2374
2383
|
voice_models = ConversationAdapters.get_voice_model_options()
|
2375
2384
|
voice_model_options = list()
|
2376
2385
|
for voice_model in voice_models:
|
2377
|
-
voice_model_options.append(
|
2386
|
+
voice_model_options.append(
|
2387
|
+
{
|
2388
|
+
"name": voice_model.name,
|
2389
|
+
"id": voice_model.model_id,
|
2390
|
+
"tier": voice_model.price_tier,
|
2391
|
+
}
|
2392
|
+
)
|
2378
2393
|
|
2379
2394
|
if len(voice_model_options) == 0:
|
2380
2395
|
eleven_labs_enabled = False
|
khoj/utils/constants.py
CHANGED
@@ -39,14 +39,18 @@ model_to_cost: Dict[str, Dict[str, float]] = {
|
|
39
39
|
"gpt-4o": {"input": 2.50, "output": 10.00},
|
40
40
|
"gpt-4o-mini": {"input": 0.15, "output": 0.60},
|
41
41
|
"o1": {"input": 15.0, "output": 60.00},
|
42
|
+
"o3": {"input": 10.0, "output": 40.00},
|
42
43
|
"o1-mini": {"input": 3.0, "output": 12.0},
|
43
44
|
"o3-mini": {"input": 1.10, "output": 4.40},
|
45
|
+
"o4-mini": {"input": 1.10, "output": 4.40},
|
44
46
|
# Gemini Pricing: https://ai.google.dev/pricing
|
45
47
|
"gemini-1.5-flash": {"input": 0.075, "output": 0.30},
|
46
48
|
"gemini-1.5-flash-002": {"input": 0.075, "output": 0.30},
|
47
49
|
"gemini-1.5-pro": {"input": 1.25, "output": 5.00},
|
48
50
|
"gemini-1.5-pro-002": {"input": 1.25, "output": 5.00},
|
49
51
|
"gemini-2.0-flash": {"input": 0.10, "output": 0.40},
|
52
|
+
"gemini-2.5-flash-preview-04-17": {"input": 0.15, "output": 0.60, "thought": 3.50},
|
53
|
+
"gemini-2.5-pro-preview-03-25": {"input": 1.25, "output": 10.0},
|
50
54
|
# Anthropic Pricing: https://www.anthropic.com/pricing#anthropic-api
|
51
55
|
"claude-3-5-haiku-20241022": {"input": 1.0, "output": 5.0, "cache_read": 0.08, "cache_write": 1.0},
|
52
56
|
"claude-3-5-haiku@20241022": {"input": 1.0, "output": 5.0, "cache_read": 0.08, "cache_write": 1.0},
|
@@ -55,4 +59,9 @@ model_to_cost: Dict[str, Dict[str, float]] = {
|
|
55
59
|
"claude-3-7-sonnet-20250219": {"input": 3.0, "output": 15.0, "cache_read": 0.3, "cache_write": 3.75},
|
56
60
|
"claude-3-7-sonnet@20250219": {"input": 3.0, "output": 15.0, "cache_read": 0.3, "cache_write": 3.75},
|
57
61
|
"claude-3-7-sonnet-latest": {"input": 3.0, "output": 15.0, "cache_read": 0.3, "cache_write": 3.75},
|
62
|
+
# Grok pricing: https://docs.x.ai/docs/models
|
63
|
+
"grok-3": {"input": 3.0, "output": 15.0},
|
64
|
+
"grok-3-latest": {"input": 3.0, "output": 15.0},
|
65
|
+
"grok-3-mini": {"input": 0.30, "output": 0.50},
|
66
|
+
"grok-3-mini-latest": {"input": 0.30, "output": 0.50},
|
58
67
|
}
|
khoj/utils/helpers.py
CHANGED
@@ -601,6 +601,7 @@ def get_cost_of_chat_message(
|
|
601
601
|
model_name: str,
|
602
602
|
input_tokens: int = 0,
|
603
603
|
output_tokens: int = 0,
|
604
|
+
thought_tokens: int = 0,
|
604
605
|
cache_read_tokens: int = 0,
|
605
606
|
cache_write_tokens: int = 0,
|
606
607
|
prev_cost: float = 0.0,
|
@@ -612,10 +613,11 @@ def get_cost_of_chat_message(
|
|
612
613
|
# Calculate cost of input and output tokens. Costs are per million tokens
|
613
614
|
input_cost = constants.model_to_cost.get(model_name, {}).get("input", 0) * (input_tokens / 1e6)
|
614
615
|
output_cost = constants.model_to_cost.get(model_name, {}).get("output", 0) * (output_tokens / 1e6)
|
616
|
+
thought_cost = constants.model_to_cost.get(model_name, {}).get("thought", 0) * (thought_tokens / 1e6)
|
615
617
|
cache_read_cost = constants.model_to_cost.get(model_name, {}).get("cache_read", 0) * (cache_read_tokens / 1e6)
|
616
618
|
cache_write_cost = constants.model_to_cost.get(model_name, {}).get("cache_write", 0) * (cache_write_tokens / 1e6)
|
617
619
|
|
618
|
-
return input_cost + output_cost + cache_read_cost + cache_write_cost + prev_cost
|
620
|
+
return input_cost + output_cost + thought_cost + cache_read_cost + cache_write_cost + prev_cost
|
619
621
|
|
620
622
|
|
621
623
|
def get_chat_usage_metrics(
|
@@ -624,6 +626,7 @@ def get_chat_usage_metrics(
|
|
624
626
|
output_tokens: int = 0,
|
625
627
|
cache_read_tokens: int = 0,
|
626
628
|
cache_write_tokens: int = 0,
|
629
|
+
thought_tokens: int = 0,
|
627
630
|
usage: dict = {},
|
628
631
|
cost: float = None,
|
629
632
|
):
|
@@ -633,6 +636,7 @@ def get_chat_usage_metrics(
|
|
633
636
|
prev_usage = usage or {
|
634
637
|
"input_tokens": 0,
|
635
638
|
"output_tokens": 0,
|
639
|
+
"thought_tokens": 0,
|
636
640
|
"cache_read_tokens": 0,
|
637
641
|
"cache_write_tokens": 0,
|
638
642
|
"cost": 0.0,
|
@@ -640,11 +644,18 @@ def get_chat_usage_metrics(
|
|
640
644
|
return {
|
641
645
|
"input_tokens": prev_usage["input_tokens"] + input_tokens,
|
642
646
|
"output_tokens": prev_usage["output_tokens"] + output_tokens,
|
647
|
+
"thought_tokens": prev_usage.get("thought_tokens", 0) + thought_tokens,
|
643
648
|
"cache_read_tokens": prev_usage.get("cache_read_tokens", 0) + cache_read_tokens,
|
644
649
|
"cache_write_tokens": prev_usage.get("cache_write_tokens", 0) + cache_write_tokens,
|
645
650
|
"cost": cost
|
646
651
|
or get_cost_of_chat_message(
|
647
|
-
model_name,
|
652
|
+
model_name,
|
653
|
+
input_tokens,
|
654
|
+
output_tokens,
|
655
|
+
thought_tokens,
|
656
|
+
cache_read_tokens,
|
657
|
+
cache_write_tokens,
|
658
|
+
prev_cost=prev_usage["cost"],
|
648
659
|
),
|
649
660
|
}
|
650
661
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: khoj
|
3
|
-
Version: 1.40.1.
|
3
|
+
Version: 1.40.1.dev13
|
4
4
|
Summary: Your Second Brain
|
5
5
|
Project-URL: Homepage, https://khoj.dev
|
6
6
|
Project-URL: Documentation, https://docs.khoj.dev
|
@@ -40,7 +40,7 @@ Requires-Dist: einops==0.8.0
|
|
40
40
|
Requires-Dist: email-validator==2.2.0
|
41
41
|
Requires-Dist: fastapi>=0.110.0
|
42
42
|
Requires-Dist: google-auth~=2.23.3
|
43
|
-
Requires-Dist: google-genai==1.
|
43
|
+
Requires-Dist: google-genai==1.11.0
|
44
44
|
Requires-Dist: httpx==0.28.1
|
45
45
|
Requires-Dist: huggingface-hub>=0.22.2
|
46
46
|
Requires-Dist: itsdangerous==2.1.2
|
@@ -150,7 +150,7 @@ Description-Content-Type: text/markdown
|
|
150
150
|
|
151
151
|
[Khoj](https://khoj.dev) is a personal AI app to extend your capabilities. It smoothly scales up from an on-device personal AI to a cloud-scale enterprise AI.
|
152
152
|
|
153
|
-
- Chat with any local or online LLM (e.g llama3, qwen, gemma, mistral, gpt, claude, gemini).
|
153
|
+
- Chat with any local or online LLM (e.g llama3, qwen, gemma, mistral, gpt, claude, gemini, deepseek).
|
154
154
|
- Get answers from the internet and your docs (including image, pdf, markdown, org-mode, word, notion files).
|
155
155
|
- Access it from your Browser, Obsidian, Emacs, Desktop, Phone or Whatsapp.
|
156
156
|
- Create agents with custom knowledge, persona, chat model and tools to take on any role.
|
@@ -179,6 +179,19 @@ To get started with self-hosting Khoj, [read the docs](https://docs.khoj.dev/get
|
|
179
179
|
|
180
180
|
Khoj is available as a cloud service, on-premises, or as a hybrid solution. To learn more about Khoj Enterprise, [visit our website](https://khoj.dev/teams).
|
181
181
|
|
182
|
+
## Frequently Asked Questions (FAQ)
|
183
|
+
|
184
|
+
Q: Can I use Khoj without self-hosting?
|
185
|
+
Yes! You can use Khoj right away at [https://app.khoj.dev](https://app.khoj.dev) — no setup required.
|
186
|
+
|
187
|
+
Q: What kinds of documents can Khoj read?
|
188
|
+
Khoj supports a wide variety: PDFs, Markdown, Notion, Word docs, org-mode files, and more.
|
189
|
+
|
190
|
+
Q: How can I make my own agent?
|
191
|
+
Check out [this blog post](https://blog.khoj.dev/posts/create-agents-on-khoj/) for a step-by-step guide to custom agents.
|
192
|
+
For more questions, head over to our [Discord](https://discord.gg/BDgyabRM6e)!
|
193
|
+
|
194
|
+
|
182
195
|
## Contributors
|
183
196
|
Cheers to our awesome contributors! 🎉
|
184
197
|
|