khoj 1.24.2.dev4__py3-none-any.whl → 1.24.2.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 +125 -13
- khoj/database/migrations/0065_remove_agent_avatar_remove_agent_public_and_more.py +49 -0
- khoj/database/migrations/0066_remove_agent_tools_agent_input_tools_and_more.py +69 -0
- khoj/database/migrations/0067_alter_agent_style_icon.py +50 -0
- khoj/database/models/__init__.py +60 -18
- khoj/interface/compiled/404/index.html +1 -1
- khoj/interface/compiled/_next/static/chunks/1269-2e52d48e7d0e5c61.js +1 -0
- khoj/interface/compiled/_next/static/chunks/1603-67a89278e2c5dbe6.js +1 -0
- khoj/interface/compiled/_next/static/chunks/2697-f27cc03429fc6482.js +1 -0
- khoj/interface/compiled/_next/static/chunks/3110-ef2cacd1b8d79ad8.js +1 -0
- khoj/interface/compiled/_next/static/chunks/4086-2c74808ba38a5a0f.js +1 -0
- khoj/interface/compiled/_next/static/chunks/477-ec86e93db10571c1.js +1 -0
- khoj/interface/compiled/_next/static/chunks/51-e8f5bdb69b5ea421.js +1 -0
- khoj/interface/compiled/_next/static/chunks/9178-7dcdc4fb8cc8cc24.js +1 -0
- khoj/interface/compiled/_next/static/chunks/9417-1d158bf46d3a0dc9.js +1 -0
- khoj/interface/compiled/_next/static/chunks/9479-563e4d61f91d5a7c.js +1 -0
- khoj/interface/compiled/_next/static/chunks/app/agents/page-d2fe596a672ca395.js +1 -0
- khoj/interface/compiled/_next/static/chunks/app/automations/page-5480731341f34450.js +1 -0
- khoj/interface/compiled/_next/static/chunks/app/chat/page-702057ccbcf27881.js +1 -0
- khoj/interface/compiled/_next/static/chunks/app/factchecker/page-e7b34316ec6f44de.js +1 -0
- khoj/interface/compiled/_next/static/chunks/app/page-421d13f70c505dd9.js +1 -0
- khoj/interface/compiled/_next/static/chunks/app/search/page-d56541c746fded7d.js +1 -0
- khoj/interface/compiled/_next/static/chunks/app/settings/page-e044a999468a7c5d.js +1 -0
- khoj/interface/compiled/_next/static/chunks/app/share/chat/page-fbbd66a4d4633438.js +1 -0
- khoj/interface/compiled/_next/static/chunks/{webpack-8cb92a712ecd16e2.js → webpack-9257083ec6042fc1.js} +1 -1
- khoj/interface/compiled/_next/static/css/592ca99f5122e75a.css +1 -0
- khoj/interface/compiled/_next/static/css/60fc94dfe42ddfe9.css +1 -0
- khoj/interface/compiled/_next/static/css/e294b18216ce4cd6.css +25 -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/anthropic/anthropic_chat.py +2 -0
- khoj/processor/conversation/google/gemini_chat.py +2 -0
- khoj/processor/conversation/offline/chat_model.py +3 -1
- khoj/processor/conversation/openai/gpt.py +2 -0
- khoj/processor/conversation/prompts.py +56 -5
- khoj/processor/image/generate.py +3 -1
- khoj/processor/tools/online_search.py +9 -7
- khoj/routers/api.py +21 -4
- khoj/routers/api_agents.py +224 -4
- khoj/routers/api_chat.py +24 -11
- khoj/routers/helpers.py +100 -13
- khoj/search_type/text_search.py +4 -1
- khoj/utils/helpers.py +15 -2
- {khoj-1.24.2.dev4.dist-info → khoj-1.24.2.dev6.dist-info}/METADATA +1 -8
- {khoj-1.24.2.dev4.dist-info → khoj-1.24.2.dev6.dist-info}/RECORD +64 -61
- khoj/interface/compiled/_next/static/chunks/1603-3e2e1528e3b6ea1d.js +0 -1
- khoj/interface/compiled/_next/static/chunks/2697-a29cb9191a9e339c.js +0 -1
- khoj/interface/compiled/_next/static/chunks/6648-ee109f4ea33a74e2.js +0 -1
- khoj/interface/compiled/_next/static/chunks/7071-b4711cecca6619a8.js +0 -1
- khoj/interface/compiled/_next/static/chunks/743-1a64254447cda71f.js +0 -1
- khoj/interface/compiled/_next/static/chunks/8423-62ac6c832be2461b.js +0 -1
- khoj/interface/compiled/_next/static/chunks/9162-0be016519a18568b.js +0 -1
- khoj/interface/compiled/_next/static/chunks/9178-7e815211edcb3657.js +0 -1
- khoj/interface/compiled/_next/static/chunks/9417-5d14ac74aaab2c66.js +0 -1
- khoj/interface/compiled/_next/static/chunks/9984-e410179c6fac7cf1.js +0 -1
- khoj/interface/compiled/_next/static/chunks/app/agents/page-d302911777a3e027.js +0 -1
- khoj/interface/compiled/_next/static/chunks/app/automations/page-0a5de8c254c29a1c.js +0 -1
- khoj/interface/compiled/_next/static/chunks/app/chat/page-d96bf6a84bb05290.js +0 -1
- khoj/interface/compiled/_next/static/chunks/app/factchecker/page-32e61af29e6b431d.js +0 -1
- khoj/interface/compiled/_next/static/chunks/app/page-96cab08c985716f4.js +0 -1
- khoj/interface/compiled/_next/static/chunks/app/search/page-b3193d46c65571c5.js +0 -1
- khoj/interface/compiled/_next/static/chunks/app/settings/page-0db9b708366606ec.js +0 -1
- khoj/interface/compiled/_next/static/chunks/app/share/chat/page-f06ac16cfe5b5a16.js +0 -1
- khoj/interface/compiled/_next/static/css/24f141a6e37cd204.css +0 -25
- khoj/interface/compiled/_next/static/css/4cae6c0e5c72fb2d.css +0 -1
- khoj/interface/compiled/_next/static/css/b1094827d745306b.css +0 -1
- /khoj/interface/compiled/_next/static/{9Px2MOmCuHSTa2RFA3GJa → mRXreP-unBb35nvpcsyA9}/_buildManifest.js +0 -0
- /khoj/interface/compiled/_next/static/{9Px2MOmCuHSTa2RFA3GJa → mRXreP-unBb35nvpcsyA9}/_ssgManifest.js +0 -0
- {khoj-1.24.2.dev4.dist-info → khoj-1.24.2.dev6.dist-info}/WHEEL +0 -0
- {khoj-1.24.2.dev4.dist-info → khoj-1.24.2.dev6.dist-info}/entry_points.txt +0 -0
- {khoj-1.24.2.dev4.dist-info → khoj-1.24.2.dev6.dist-info}/licenses/LICENSE +0 -0
@@ -10,7 +10,7 @@ import aiohttp
|
|
10
10
|
from bs4 import BeautifulSoup
|
11
11
|
from markdownify import markdownify
|
12
12
|
|
13
|
-
from khoj.database.models import KhojUser
|
13
|
+
from khoj.database.models import Agent, KhojUser
|
14
14
|
from khoj.routers.helpers import (
|
15
15
|
ChatEvent,
|
16
16
|
extract_relevant_info,
|
@@ -57,16 +57,17 @@ async def search_online(
|
|
57
57
|
send_status_func: Optional[Callable] = None,
|
58
58
|
custom_filters: List[str] = [],
|
59
59
|
uploaded_image_url: str = None,
|
60
|
+
agent: Agent = None,
|
60
61
|
):
|
61
62
|
query += " ".join(custom_filters)
|
62
63
|
if not is_internet_connected():
|
63
|
-
logger.
|
64
|
+
logger.warning("Cannot search online as not connected to internet")
|
64
65
|
yield {}
|
65
66
|
return
|
66
67
|
|
67
68
|
# Breakdown the query into subqueries to get the correct answer
|
68
69
|
subqueries = await generate_online_subqueries(
|
69
|
-
query, conversation_history, location, user, uploaded_image_url=uploaded_image_url
|
70
|
+
query, conversation_history, location, user, uploaded_image_url=uploaded_image_url, agent=agent
|
70
71
|
)
|
71
72
|
response_dict = {}
|
72
73
|
|
@@ -101,7 +102,7 @@ async def search_online(
|
|
101
102
|
async for event in send_status_func(f"**Reading web pages**: {webpage_links_str}"):
|
102
103
|
yield {ChatEvent.STATUS: event}
|
103
104
|
tasks = [
|
104
|
-
read_webpage_and_extract_content(subquery, link, content, subscribed=subscribed)
|
105
|
+
read_webpage_and_extract_content(subquery, link, content, subscribed=subscribed, agent=agent)
|
105
106
|
for link, subquery, content in webpages
|
106
107
|
]
|
107
108
|
results = await asyncio.gather(*tasks)
|
@@ -143,6 +144,7 @@ async def read_webpages(
|
|
143
144
|
subscribed: bool = False,
|
144
145
|
send_status_func: Optional[Callable] = None,
|
145
146
|
uploaded_image_url: str = None,
|
147
|
+
agent: Agent = None,
|
146
148
|
):
|
147
149
|
"Infer web pages to read from the query and extract relevant information from them"
|
148
150
|
logger.info(f"Inferring web pages to read")
|
@@ -156,7 +158,7 @@ async def read_webpages(
|
|
156
158
|
webpage_links_str = "\n- " + "\n- ".join(list(urls))
|
157
159
|
async for event in send_status_func(f"**Reading web pages**: {webpage_links_str}"):
|
158
160
|
yield {ChatEvent.STATUS: event}
|
159
|
-
tasks = [read_webpage_and_extract_content(query, url, subscribed=subscribed) for url in urls]
|
161
|
+
tasks = [read_webpage_and_extract_content(query, url, subscribed=subscribed, agent=agent) for url in urls]
|
160
162
|
results = await asyncio.gather(*tasks)
|
161
163
|
|
162
164
|
response: Dict[str, Dict] = defaultdict(dict)
|
@@ -167,14 +169,14 @@ async def read_webpages(
|
|
167
169
|
|
168
170
|
|
169
171
|
async def read_webpage_and_extract_content(
|
170
|
-
subquery: str, url: str, content: str = None, subscribed: bool = False
|
172
|
+
subquery: str, url: str, content: str = None, subscribed: bool = False, agent: Agent = None
|
171
173
|
) -> Tuple[str, Union[None, str], str]:
|
172
174
|
try:
|
173
175
|
if is_none_or_empty(content):
|
174
176
|
with timer(f"Reading web page at '{url}' took", logger):
|
175
177
|
content = await read_webpage_with_olostep(url) if OLOSTEP_API_KEY else await read_webpage_with_jina(url)
|
176
178
|
with timer(f"Extracting relevant information from web page at '{url}' took", logger):
|
177
|
-
extracted_info = await extract_relevant_info(subquery, content, subscribed=subscribed)
|
179
|
+
extracted_info = await extract_relevant_info(subquery, content, subscribed=subscribed, agent=agent)
|
178
180
|
return subquery, extracted_info, url
|
179
181
|
except Exception as e:
|
180
182
|
logger.error(f"Failed to read web page at '{url}' with {e}")
|
khoj/routers/api.py
CHANGED
@@ -27,7 +27,13 @@ from khoj.database.adapters import (
|
|
27
27
|
get_user_photo,
|
28
28
|
get_user_search_model_or_default,
|
29
29
|
)
|
30
|
-
from khoj.database.models import
|
30
|
+
from khoj.database.models import (
|
31
|
+
Agent,
|
32
|
+
ChatModelOptions,
|
33
|
+
KhojUser,
|
34
|
+
SpeechToTextModelOptions,
|
35
|
+
)
|
36
|
+
from khoj.processor.conversation import prompts
|
31
37
|
from khoj.processor.conversation.anthropic.anthropic_chat import (
|
32
38
|
extract_questions_anthropic,
|
33
39
|
)
|
@@ -106,6 +112,7 @@ async def execute_search(
|
|
106
112
|
r: Optional[bool] = False,
|
107
113
|
max_distance: Optional[Union[float, None]] = None,
|
108
114
|
dedupe: Optional[bool] = True,
|
115
|
+
agent: Optional[Agent] = None,
|
109
116
|
):
|
110
117
|
start_time = time.time()
|
111
118
|
|
@@ -157,6 +164,7 @@ async def execute_search(
|
|
157
164
|
t,
|
158
165
|
question_embedding=encoded_asymmetric_query,
|
159
166
|
max_distance=max_distance,
|
167
|
+
agent=agent,
|
160
168
|
)
|
161
169
|
]
|
162
170
|
|
@@ -333,6 +341,7 @@ async def extract_references_and_questions(
|
|
333
341
|
location_data: LocationData = None,
|
334
342
|
send_status_func: Optional[Callable] = None,
|
335
343
|
uploaded_image_url: Optional[str] = None,
|
344
|
+
agent: Agent = None,
|
336
345
|
):
|
337
346
|
user = request.user.object if request.user.is_authenticated else None
|
338
347
|
|
@@ -348,9 +357,10 @@ async def extract_references_and_questions(
|
|
348
357
|
return
|
349
358
|
|
350
359
|
if not await sync_to_async(EntryAdapters.user_has_entries)(user=user):
|
351
|
-
|
352
|
-
|
353
|
-
|
360
|
+
if not await sync_to_async(EntryAdapters.agent_has_entries)(agent=agent):
|
361
|
+
logger.debug("No documents in knowledge base. Use a Khoj client to sync and chat with your docs.")
|
362
|
+
yield compiled_references, inferred_queries, q
|
363
|
+
return
|
354
364
|
|
355
365
|
# Extract filter terms from user message
|
356
366
|
defiltered_query = q
|
@@ -368,6 +378,8 @@ async def extract_references_and_questions(
|
|
368
378
|
using_offline_chat = False
|
369
379
|
logger.debug(f"Filters in query: {filters_in_query}")
|
370
380
|
|
381
|
+
personality_context = prompts.personality_context.format(personality=agent.personality) if agent else ""
|
382
|
+
|
371
383
|
# Infer search queries from user message
|
372
384
|
with timer("Extracting search queries took", logger):
|
373
385
|
# If we've reached here, either the user has enabled offline chat or the openai model is enabled.
|
@@ -392,6 +404,7 @@ async def extract_references_and_questions(
|
|
392
404
|
location_data=location_data,
|
393
405
|
user=user,
|
394
406
|
max_prompt_size=conversation_config.max_prompt_size,
|
407
|
+
personality_context=personality_context,
|
395
408
|
)
|
396
409
|
elif conversation_config.model_type == ChatModelOptions.ModelType.OPENAI:
|
397
410
|
openai_chat_config = conversation_config.openai_config
|
@@ -408,6 +421,7 @@ async def extract_references_and_questions(
|
|
408
421
|
user=user,
|
409
422
|
uploaded_image_url=uploaded_image_url,
|
410
423
|
vision_enabled=vision_enabled,
|
424
|
+
personality_context=personality_context,
|
411
425
|
)
|
412
426
|
elif conversation_config.model_type == ChatModelOptions.ModelType.ANTHROPIC:
|
413
427
|
api_key = conversation_config.openai_config.api_key
|
@@ -419,6 +433,7 @@ async def extract_references_and_questions(
|
|
419
433
|
conversation_log=meta_log,
|
420
434
|
location_data=location_data,
|
421
435
|
user=user,
|
436
|
+
personality_context=personality_context,
|
422
437
|
)
|
423
438
|
elif conversation_config.model_type == ChatModelOptions.ModelType.GOOGLE:
|
424
439
|
api_key = conversation_config.openai_config.api_key
|
@@ -431,6 +446,7 @@ async def extract_references_and_questions(
|
|
431
446
|
location_data=location_data,
|
432
447
|
max_tokens=conversation_config.max_prompt_size,
|
433
448
|
user=user,
|
449
|
+
personality_context=personality_context,
|
434
450
|
)
|
435
451
|
|
436
452
|
# Collate search results as context for GPT
|
@@ -452,6 +468,7 @@ async def extract_references_and_questions(
|
|
452
468
|
r=True,
|
453
469
|
max_distance=d,
|
454
470
|
dedupe=False,
|
471
|
+
agent=agent,
|
455
472
|
)
|
456
473
|
)
|
457
474
|
search_results = text_search.deduplicated_search_responses(search_results)
|
khoj/routers/api_agents.py
CHANGED
@@ -1,13 +1,22 @@
|
|
1
1
|
import json
|
2
2
|
import logging
|
3
|
+
from typing import Dict, List, Optional
|
3
4
|
|
5
|
+
from asgiref.sync import sync_to_async
|
4
6
|
from fastapi import APIRouter, Request
|
5
7
|
from fastapi.requests import Request
|
6
8
|
from fastapi.responses import Response
|
9
|
+
from pydantic import BaseModel
|
10
|
+
from starlette.authentication import requires
|
7
11
|
|
8
12
|
from khoj.database.adapters import AgentAdapters
|
9
|
-
from khoj.database.models import KhojUser
|
10
|
-
from khoj.routers.helpers import CommonQueryParams
|
13
|
+
from khoj.database.models import Agent, KhojUser
|
14
|
+
from khoj.routers.helpers import CommonQueryParams, acheck_if_safe_prompt
|
15
|
+
from khoj.utils.helpers import (
|
16
|
+
ConversationCommand,
|
17
|
+
command_descriptions_for_agent,
|
18
|
+
mode_descriptions_for_agent,
|
19
|
+
)
|
11
20
|
|
12
21
|
# Initialize Router
|
13
22
|
logger = logging.getLogger(__name__)
|
@@ -16,6 +25,18 @@ logger = logging.getLogger(__name__)
|
|
16
25
|
api_agents = APIRouter()
|
17
26
|
|
18
27
|
|
28
|
+
class ModifyAgentBody(BaseModel):
|
29
|
+
name: str
|
30
|
+
persona: str
|
31
|
+
privacy_level: str
|
32
|
+
icon: str
|
33
|
+
color: str
|
34
|
+
chat_model: str
|
35
|
+
files: Optional[List[str]] = []
|
36
|
+
input_tools: Optional[List[str]] = []
|
37
|
+
output_modes: Optional[List[str]] = []
|
38
|
+
|
39
|
+
|
19
40
|
@api_agents.get("", response_class=Response)
|
20
41
|
async def all_agents(
|
21
42
|
request: Request,
|
@@ -25,17 +46,22 @@ async def all_agents(
|
|
25
46
|
agents = await AgentAdapters.aget_all_accessible_agents(user)
|
26
47
|
agents_packet = list()
|
27
48
|
for agent in agents:
|
49
|
+
files = agent.fileobject_set.all()
|
50
|
+
file_names = [file.file_name for file in files]
|
28
51
|
agents_packet.append(
|
29
52
|
{
|
30
53
|
"slug": agent.slug,
|
31
|
-
"avatar": agent.avatar,
|
32
54
|
"name": agent.name,
|
33
55
|
"persona": agent.personality,
|
34
|
-
"public": agent.public,
|
35
56
|
"creator": agent.creator.username if agent.creator else None,
|
36
57
|
"managed_by_admin": agent.managed_by_admin,
|
37
58
|
"color": agent.style_color,
|
38
59
|
"icon": agent.style_icon,
|
60
|
+
"privacy_level": agent.privacy_level,
|
61
|
+
"chat_model": agent.chat_model.chat_model,
|
62
|
+
"files": file_names,
|
63
|
+
"input_tools": agent.input_tools,
|
64
|
+
"output_modes": agent.output_modes,
|
39
65
|
}
|
40
66
|
)
|
41
67
|
|
@@ -43,3 +69,197 @@ async def all_agents(
|
|
43
69
|
agents_packet.sort(key=lambda x: x["name"])
|
44
70
|
agents_packet.sort(key=lambda x: x["slug"] == "khoj", reverse=True)
|
45
71
|
return Response(content=json.dumps(agents_packet), media_type="application/json", status_code=200)
|
72
|
+
|
73
|
+
|
74
|
+
@api_agents.get("/options", response_class=Response)
|
75
|
+
async def get_agent_configuration_options(
|
76
|
+
request: Request,
|
77
|
+
common: CommonQueryParams,
|
78
|
+
) -> Response:
|
79
|
+
agent_input_tools = [key for key, _ in Agent.InputToolOptions.choices]
|
80
|
+
agent_output_modes = [key for key, _ in Agent.OutputModeOptions.choices]
|
81
|
+
|
82
|
+
agent_input_tool_with_descriptions: Dict[str, str] = {}
|
83
|
+
for key in agent_input_tools:
|
84
|
+
conversation_command = ConversationCommand(key)
|
85
|
+
agent_input_tool_with_descriptions[key] = command_descriptions_for_agent[conversation_command]
|
86
|
+
|
87
|
+
agent_output_modes_with_descriptions: Dict[str, str] = {}
|
88
|
+
for key in agent_output_modes:
|
89
|
+
conversation_command = ConversationCommand(key)
|
90
|
+
agent_output_modes_with_descriptions[key] = mode_descriptions_for_agent[conversation_command]
|
91
|
+
|
92
|
+
return Response(
|
93
|
+
content=json.dumps(
|
94
|
+
{
|
95
|
+
"input_tools": agent_input_tool_with_descriptions,
|
96
|
+
"output_modes": agent_output_modes_with_descriptions,
|
97
|
+
}
|
98
|
+
),
|
99
|
+
media_type="application/json",
|
100
|
+
status_code=200,
|
101
|
+
)
|
102
|
+
|
103
|
+
|
104
|
+
@api_agents.get("/{agent_slug}", response_class=Response)
|
105
|
+
async def get_agent(
|
106
|
+
request: Request,
|
107
|
+
common: CommonQueryParams,
|
108
|
+
agent_slug: str,
|
109
|
+
) -> Response:
|
110
|
+
user: KhojUser = request.user.object if request.user.is_authenticated else None
|
111
|
+
agent = await AgentAdapters.aget_readonly_agent_by_slug(agent_slug, user)
|
112
|
+
|
113
|
+
if not agent:
|
114
|
+
return Response(
|
115
|
+
content=json.dumps({"error": f"Agent with name {agent_slug} not found."}),
|
116
|
+
media_type="application/json",
|
117
|
+
status_code=404,
|
118
|
+
)
|
119
|
+
|
120
|
+
files = agent.fileobject_set.all()
|
121
|
+
file_names = [file.file_name for file in files]
|
122
|
+
agents_packet = {
|
123
|
+
"slug": agent.slug,
|
124
|
+
"name": agent.name,
|
125
|
+
"persona": agent.personality,
|
126
|
+
"creator": agent.creator.username if agent.creator else None,
|
127
|
+
"managed_by_admin": agent.managed_by_admin,
|
128
|
+
"color": agent.style_color,
|
129
|
+
"icon": agent.style_icon,
|
130
|
+
"privacy_level": agent.privacy_level,
|
131
|
+
"chat_model": agent.chat_model.chat_model,
|
132
|
+
"files": file_names,
|
133
|
+
"input_tools": agent.input_tools,
|
134
|
+
"output_modes": agent.output_modes,
|
135
|
+
}
|
136
|
+
|
137
|
+
return Response(content=json.dumps(agents_packet), media_type="application/json", status_code=200)
|
138
|
+
|
139
|
+
|
140
|
+
@api_agents.delete("/{agent_slug}", response_class=Response)
|
141
|
+
@requires(["authenticated"])
|
142
|
+
async def delete_agent(
|
143
|
+
request: Request,
|
144
|
+
common: CommonQueryParams,
|
145
|
+
agent_slug: str,
|
146
|
+
) -> Response:
|
147
|
+
user: KhojUser = request.user.object
|
148
|
+
|
149
|
+
agent = await AgentAdapters.aget_agent_by_slug(agent_slug, user)
|
150
|
+
|
151
|
+
if not agent:
|
152
|
+
return Response(
|
153
|
+
content=json.dumps({"error": f"Agent with name {agent_slug} not found."}),
|
154
|
+
media_type="application/json",
|
155
|
+
status_code=404,
|
156
|
+
)
|
157
|
+
|
158
|
+
await AgentAdapters.adelete_agent_by_slug(agent_slug, user)
|
159
|
+
|
160
|
+
return Response(content=json.dumps({"message": "Agent deleted."}), media_type="application/json", status_code=200)
|
161
|
+
|
162
|
+
|
163
|
+
@api_agents.post("", response_class=Response)
|
164
|
+
@requires(["authenticated"])
|
165
|
+
async def create_agent(
|
166
|
+
request: Request,
|
167
|
+
common: CommonQueryParams,
|
168
|
+
body: ModifyAgentBody,
|
169
|
+
) -> Response:
|
170
|
+
user: KhojUser = request.user.object
|
171
|
+
|
172
|
+
is_safe_prompt, reason = await acheck_if_safe_prompt(body.persona)
|
173
|
+
if not is_safe_prompt:
|
174
|
+
return Response(
|
175
|
+
content=json.dumps({"error": f"{reason}"}),
|
176
|
+
media_type="application/json",
|
177
|
+
status_code=400,
|
178
|
+
)
|
179
|
+
|
180
|
+
agent = await AgentAdapters.aupdate_agent(
|
181
|
+
user,
|
182
|
+
body.name,
|
183
|
+
body.persona,
|
184
|
+
body.privacy_level,
|
185
|
+
body.icon,
|
186
|
+
body.color,
|
187
|
+
body.chat_model,
|
188
|
+
body.files,
|
189
|
+
body.input_tools,
|
190
|
+
body.output_modes,
|
191
|
+
)
|
192
|
+
|
193
|
+
agents_packet = {
|
194
|
+
"slug": agent.slug,
|
195
|
+
"name": agent.name,
|
196
|
+
"persona": agent.personality,
|
197
|
+
"creator": agent.creator.username if agent.creator else None,
|
198
|
+
"managed_by_admin": agent.managed_by_admin,
|
199
|
+
"color": agent.style_color,
|
200
|
+
"icon": agent.style_icon,
|
201
|
+
"privacy_level": agent.privacy_level,
|
202
|
+
"chat_model": agent.chat_model.chat_model,
|
203
|
+
"files": body.files,
|
204
|
+
"input_tools": agent.input_tools,
|
205
|
+
"output_modes": agent.output_modes,
|
206
|
+
}
|
207
|
+
|
208
|
+
return Response(content=json.dumps(agents_packet), media_type="application/json", status_code=200)
|
209
|
+
|
210
|
+
|
211
|
+
@api_agents.patch("", response_class=Response)
|
212
|
+
@requires(["authenticated"])
|
213
|
+
async def update_agent(
|
214
|
+
request: Request,
|
215
|
+
common: CommonQueryParams,
|
216
|
+
body: ModifyAgentBody,
|
217
|
+
) -> Response:
|
218
|
+
user: KhojUser = request.user.object
|
219
|
+
|
220
|
+
is_safe_prompt, reason = await acheck_if_safe_prompt(body.persona)
|
221
|
+
if not is_safe_prompt:
|
222
|
+
return Response(
|
223
|
+
content=json.dumps({"error": f"{reason}"}),
|
224
|
+
media_type="application/json",
|
225
|
+
status_code=400,
|
226
|
+
)
|
227
|
+
|
228
|
+
selected_agent = await AgentAdapters.aget_agent_by_name(body.name, user)
|
229
|
+
|
230
|
+
if not selected_agent:
|
231
|
+
return Response(
|
232
|
+
content=json.dumps({"error": f"Agent with name {body.name} not found."}),
|
233
|
+
media_type="application/json",
|
234
|
+
status_code=404,
|
235
|
+
)
|
236
|
+
|
237
|
+
agent = await AgentAdapters.aupdate_agent(
|
238
|
+
user,
|
239
|
+
body.name,
|
240
|
+
body.persona,
|
241
|
+
body.privacy_level,
|
242
|
+
body.icon,
|
243
|
+
body.color,
|
244
|
+
body.chat_model,
|
245
|
+
body.files,
|
246
|
+
body.input_tools,
|
247
|
+
body.output_modes,
|
248
|
+
)
|
249
|
+
|
250
|
+
agents_packet = {
|
251
|
+
"slug": agent.slug,
|
252
|
+
"name": agent.name,
|
253
|
+
"persona": agent.personality,
|
254
|
+
"creator": agent.creator.username if agent.creator else None,
|
255
|
+
"managed_by_admin": agent.managed_by_admin,
|
256
|
+
"color": agent.style_color,
|
257
|
+
"icon": agent.style_icon,
|
258
|
+
"privacy_level": agent.privacy_level,
|
259
|
+
"chat_model": agent.chat_model.chat_model,
|
260
|
+
"files": body.files,
|
261
|
+
"input_tools": agent.input_tools,
|
262
|
+
"output_modes": agent.output_modes,
|
263
|
+
}
|
264
|
+
|
265
|
+
return Response(content=json.dumps(agents_packet), media_type="application/json", status_code=200)
|
khoj/routers/api_chat.py
CHANGED
@@ -17,13 +17,14 @@ from starlette.authentication import has_required_scope, requires
|
|
17
17
|
|
18
18
|
from khoj.app.settings import ALLOWED_HOSTS
|
19
19
|
from khoj.database.adapters import (
|
20
|
+
AgentAdapters,
|
20
21
|
ConversationAdapters,
|
21
22
|
EntryAdapters,
|
22
23
|
FileObjectAdapters,
|
23
24
|
PublicConversationAdapters,
|
24
25
|
aget_user_name,
|
25
26
|
)
|
26
|
-
from khoj.database.models import KhojUser
|
27
|
+
from khoj.database.models import Agent, KhojUser
|
27
28
|
from khoj.processor.conversation.prompts import help_message, no_entries_found
|
28
29
|
from khoj.processor.conversation.utils import save_to_conversation_log
|
29
30
|
from khoj.processor.image.generate import text_to_image
|
@@ -211,7 +212,6 @@ def chat_history(
|
|
211
212
|
agent_metadata = {
|
212
213
|
"slug": conversation.agent.slug,
|
213
214
|
"name": conversation.agent.name,
|
214
|
-
"avatar": conversation.agent.avatar,
|
215
215
|
"isCreator": conversation.agent.creator == user,
|
216
216
|
"color": conversation.agent.style_color,
|
217
217
|
"icon": conversation.agent.style_icon,
|
@@ -268,7 +268,6 @@ def get_shared_chat(
|
|
268
268
|
agent_metadata = {
|
269
269
|
"slug": conversation.agent.slug,
|
270
270
|
"name": conversation.agent.name,
|
271
|
-
"avatar": conversation.agent.avatar,
|
272
271
|
"isCreator": conversation.agent.creator == user,
|
273
272
|
"color": conversation.agent.style_color,
|
274
273
|
"icon": conversation.agent.style_icon,
|
@@ -418,7 +417,7 @@ def chat_sessions(
|
|
418
417
|
conversations = conversations[:8]
|
419
418
|
|
420
419
|
sessions = conversations.values_list(
|
421
|
-
"id", "slug", "title", "agent__slug", "agent__name", "
|
420
|
+
"id", "slug", "title", "agent__slug", "agent__name", "created_at", "updated_at"
|
422
421
|
)
|
423
422
|
|
424
423
|
session_values = [
|
@@ -426,9 +425,8 @@ def chat_sessions(
|
|
426
425
|
"conversation_id": str(session[0]),
|
427
426
|
"slug": session[2] or session[1],
|
428
427
|
"agent_name": session[4],
|
429
|
-
"
|
430
|
-
"
|
431
|
-
"updated": session[7].strftime("%Y-%m-%d %H:%M:%S"),
|
428
|
+
"created": session[5].strftime("%Y-%m-%d %H:%M:%S"),
|
429
|
+
"updated": session[6].strftime("%Y-%m-%d %H:%M:%S"),
|
432
430
|
}
|
433
431
|
for session in sessions
|
434
432
|
]
|
@@ -590,7 +588,7 @@ async def chat(
|
|
590
588
|
nonlocal connection_alive, ttft
|
591
589
|
if not connection_alive or await request.is_disconnected():
|
592
590
|
connection_alive = False
|
593
|
-
logger.
|
591
|
+
logger.warning(f"User {user} disconnected from {common.client} client")
|
594
592
|
return
|
595
593
|
try:
|
596
594
|
if event_type == ChatEvent.END_LLM_RESPONSE:
|
@@ -658,6 +656,11 @@ async def chat(
|
|
658
656
|
return
|
659
657
|
conversation_id = conversation.id
|
660
658
|
|
659
|
+
agent: Agent | None = None
|
660
|
+
default_agent = await AgentAdapters.aget_default_agent()
|
661
|
+
if conversation.agent and conversation.agent != default_agent:
|
662
|
+
agent = conversation.agent
|
663
|
+
|
661
664
|
await is_ready_to_chat(user)
|
662
665
|
|
663
666
|
user_name = await aget_user_name(user)
|
@@ -677,7 +680,12 @@ async def chat(
|
|
677
680
|
|
678
681
|
if conversation_commands == [ConversationCommand.Default] or is_automated_task:
|
679
682
|
conversation_commands = await aget_relevant_information_sources(
|
680
|
-
q,
|
683
|
+
q,
|
684
|
+
meta_log,
|
685
|
+
is_automated_task,
|
686
|
+
subscribed=subscribed,
|
687
|
+
uploaded_image_url=uploaded_image_url,
|
688
|
+
agent=agent,
|
681
689
|
)
|
682
690
|
conversation_commands_str = ", ".join([cmd.value for cmd in conversation_commands])
|
683
691
|
async for result in send_event(
|
@@ -685,7 +693,7 @@ async def chat(
|
|
685
693
|
):
|
686
694
|
yield result
|
687
695
|
|
688
|
-
mode = await aget_relevant_output_modes(q, meta_log, is_automated_task, uploaded_image_url)
|
696
|
+
mode = await aget_relevant_output_modes(q, meta_log, is_automated_task, uploaded_image_url, agent)
|
689
697
|
async for result in send_event(ChatEvent.STATUS, f"**Decided Response Mode:** {mode.value}"):
|
690
698
|
yield result
|
691
699
|
if mode not in conversation_commands:
|
@@ -734,7 +742,7 @@ async def chat(
|
|
734
742
|
yield result
|
735
743
|
|
736
744
|
response = await extract_relevant_summary(
|
737
|
-
q, contextual_data, subscribed=subscribed, uploaded_image_url=uploaded_image_url
|
745
|
+
q, contextual_data, subscribed=subscribed, uploaded_image_url=uploaded_image_url, agent=agent
|
738
746
|
)
|
739
747
|
response_log = str(response)
|
740
748
|
async for result in send_llm_response(response_log):
|
@@ -816,6 +824,7 @@ async def chat(
|
|
816
824
|
location,
|
817
825
|
partial(send_event, ChatEvent.STATUS),
|
818
826
|
uploaded_image_url=uploaded_image_url,
|
827
|
+
agent=agent,
|
819
828
|
):
|
820
829
|
if isinstance(result, dict) and ChatEvent.STATUS in result:
|
821
830
|
yield result[ChatEvent.STATUS]
|
@@ -853,6 +862,7 @@ async def chat(
|
|
853
862
|
partial(send_event, ChatEvent.STATUS),
|
854
863
|
custom_filters,
|
855
864
|
uploaded_image_url=uploaded_image_url,
|
865
|
+
agent=agent,
|
856
866
|
):
|
857
867
|
if isinstance(result, dict) and ChatEvent.STATUS in result:
|
858
868
|
yield result[ChatEvent.STATUS]
|
@@ -876,6 +886,7 @@ async def chat(
|
|
876
886
|
subscribed,
|
877
887
|
partial(send_event, ChatEvent.STATUS),
|
878
888
|
uploaded_image_url=uploaded_image_url,
|
889
|
+
agent=agent,
|
879
890
|
):
|
880
891
|
if isinstance(result, dict) and ChatEvent.STATUS in result:
|
881
892
|
yield result[ChatEvent.STATUS]
|
@@ -922,6 +933,7 @@ async def chat(
|
|
922
933
|
subscribed=subscribed,
|
923
934
|
send_status_func=partial(send_event, ChatEvent.STATUS),
|
924
935
|
uploaded_image_url=uploaded_image_url,
|
936
|
+
agent=agent,
|
925
937
|
):
|
926
938
|
if isinstance(result, dict) and ChatEvent.STATUS in result:
|
927
939
|
yield result[ChatEvent.STATUS]
|
@@ -1132,6 +1144,7 @@ async def get_chat(
|
|
1132
1144
|
yield result
|
1133
1145
|
return
|
1134
1146
|
conversation_id = conversation.id
|
1147
|
+
agent = conversation.agent if conversation.agent else None
|
1135
1148
|
|
1136
1149
|
await is_ready_to_chat(user)
|
1137
1150
|
|