Initialize the concept in the backend of hidden agents

A hidden agent basically allows each individual conversation to maintain custom settings, via an agent that's not exposed to the traditional functionalities allotted for manually created agents (e.g., browsing, maintenance in agents page).

This will be hooked up to the front-end such that any conversation that's initiated with the default agent can then be given custom settings, which in the background creates a hidden agent. This allows us to repurpose all of our existing agents infrastructure for chat-level customization.
This commit is contained in:
sabaimran
2025-01-19 12:16:37 -08:00
parent 0a0f30c53b
commit be11f666e4
4 changed files with 147 additions and 7 deletions

View File

@@ -69,6 +69,7 @@ from khoj.search_filter.word_filter import WordFilter
from khoj.utils import state from khoj.utils import state
from khoj.utils.config import OfflineChatProcessorModel from khoj.utils.config import OfflineChatProcessorModel
from khoj.utils.helpers import ( from khoj.utils.helpers import (
generate_random_internal_agent_name,
generate_random_name, generate_random_name,
in_debug_mode, in_debug_mode,
is_none_or_empty, is_none_or_empty,
@@ -806,13 +807,15 @@ class AgentAdapters:
privacy_level: str, privacy_level: str,
icon: str, icon: str,
color: str, color: str,
chat_model: str, chat_model: Optional[str],
files: List[str], files: List[str],
input_tools: List[str], input_tools: List[str],
output_modes: List[str], output_modes: List[str],
slug: Optional[str] = None, slug: Optional[str] = None,
is_hidden: Optional[bool] = False, is_hidden: Optional[bool] = False,
): ):
if not chat_model:
chat_model = ConversationAdapters.get_default_chat_model(user)
chat_model_option = await ChatModel.objects.filter(name=chat_model).afirst() chat_model_option = await ChatModel.objects.filter(name=chat_model).afirst()
# Slug will be None for new agents, which will trigger a new agent creation with a generated, immutable slug # Slug will be None for new agents, which will trigger a new agent creation with a generated, immutable slug
@@ -864,6 +867,35 @@ class AgentAdapters:
return agent return agent
@staticmethod
@arequire_valid_user
async def aupdate_hidden_agent(
user: KhojUser,
slug: Optional[str] = None,
persona: Optional[str] = None,
chat_model: Optional[str] = None,
input_tools: Optional[List[str]] = None,
output_modes: Optional[List[str]] = None,
):
random_name = generate_random_internal_agent_name()
agent = await AgentAdapters.aupdate_agent(
user=user,
name=random_name,
personality=persona,
privacy_level=Agent.PrivacyLevel.PRIVATE,
icon=Agent.StyleIconTypes.LIGHTBULB,
color=Agent.StyleColorTypes.BLUE,
chat_model=chat_model,
files=[],
input_tools=input_tools,
output_modes=output_modes,
slug=slug,
is_hidden=True,
)
return agent
class PublicConversationAdapters: class PublicConversationAdapters:
@staticmethod @staticmethod

View File

@@ -285,7 +285,6 @@ class Agent(DbBaseModel):
class OutputModeOptions(models.TextChoices): class OutputModeOptions(models.TextChoices):
# These map to various ConversationCommand types # These map to various ConversationCommand types
TEXT = "text"
IMAGE = "image" IMAGE = "image"
AUTOMATION = "automation" AUTOMATION = "automation"
DIAGRAM = "diagram" DIAGRAM = "diagram"

View File

@@ -9,7 +9,7 @@ from fastapi import APIRouter, Request
from fastapi.requests import Request from fastapi.requests import Request
from fastapi.responses import Response from fastapi.responses import Response
from pydantic import BaseModel from pydantic import BaseModel
from starlette.authentication import requires from starlette.authentication import has_required_scope, requires
from khoj.database.adapters import AgentAdapters, ConversationAdapters from khoj.database.adapters import AgentAdapters, ConversationAdapters
from khoj.database.models import Agent, Conversation, KhojUser from khoj.database.models import Agent, Conversation, KhojUser
@@ -41,6 +41,14 @@ class ModifyAgentBody(BaseModel):
is_hidden: Optional[bool] = False is_hidden: Optional[bool] = False
class ModifyHiddenAgentBody(BaseModel):
slug: str
persona: Optional[str] = None
chat_model: Optional[str] = None
input_tools: Optional[List[str]] = []
output_modes: Optional[List[str]] = []
@api_agents.get("", response_class=Response) @api_agents.get("", response_class=Response)
async def all_agents( async def all_agents(
request: Request, request: Request,
@@ -183,6 +191,93 @@ async def delete_agent(
return Response(content=json.dumps({"message": "Agent deleted."}), media_type="application/json", status_code=200) return Response(content=json.dumps({"message": "Agent deleted."}), media_type="application/json", status_code=200)
@api_agents.patch("/hidden", response_class=Response)
@requires(["authenticated"])
async def update_hidden_agent(
request: Request,
common: CommonQueryParams,
body: ModifyHiddenAgentBody,
) -> Response:
user: KhojUser = request.user.object
subscribed = has_required_scope(request, ["premium"])
chat_model = body.chat_model if subscribed else None
selected_agent = await AgentAdapters.aget_agent_by_slug(body.slug, user)
if not selected_agent:
return Response(
content=json.dumps({"error": f"Agent with name {body.slug} not found."}),
media_type="application/json",
status_code=404,
)
agent = await AgentAdapters.aupdate_hidden_agent(
user,
body.slug,
body.persona,
chat_model,
body.input_tools,
body.output_modes,
)
agents_packet = {
"slug": agent.slug,
"name": agent.name,
"persona": agent.personality,
"creator": agent.creator.username if agent.creator else None,
"managed_by_admin": agent.managed_by_admin,
"color": agent.style_color,
"icon": agent.style_icon,
"privacy_level": agent.privacy_level,
"chat_model": agent.chat_model.name,
"files": body.files,
"input_tools": agent.input_tools,
"output_modes": agent.output_modes,
}
return Response(content=json.dumps(agents_packet), media_type="application/json", status_code=200)
@api_agents.post("/hidden", response_class=Response)
@requires(["authenticated"])
async def create_hidden_agent(
request: Request,
common: CommonQueryParams,
body: ModifyHiddenAgentBody,
) -> Response:
user: KhojUser = request.user.object
subscribed = has_required_scope(request, ["premium"])
chat_model = body.chat_model if subscribed else None
agent = await AgentAdapters.aupdate_hidden_agent(
user,
body.slug,
body.persona,
chat_model,
body.input_tools,
body.output_modes,
)
agents_packet = {
"slug": agent.slug,
"name": agent.name,
"persona": agent.personality,
"creator": agent.creator.username if agent.creator else None,
"managed_by_admin": agent.managed_by_admin,
"color": agent.style_color,
"icon": agent.style_icon,
"privacy_level": agent.privacy_level,
"chat_model": agent.chat_model.name,
"files": body.files,
"input_tools": agent.input_tools,
"output_modes": agent.output_modes,
}
return Response(content=json.dumps(agents_packet), media_type="application/json", status_code=200)
@api_agents.post("", response_class=Response) @api_agents.post("", response_class=Response)
@requires(["authenticated"]) @requires(["authenticated"])
async def create_agent( async def create_agent(
@@ -203,6 +298,9 @@ async def create_agent(
status_code=400, status_code=400,
) )
subscribed = has_required_scope(request, ["premium"])
chat_model = body.chat_model if subscribed else None
agent = await AgentAdapters.aupdate_agent( agent = await AgentAdapters.aupdate_agent(
user, user,
body.name, body.name,
@@ -210,7 +308,7 @@ async def create_agent(
body.privacy_level, body.privacy_level,
body.icon, body.icon,
body.color, body.color,
body.chat_model, chat_model,
body.files, body.files,
body.input_tools, body.input_tools,
body.output_modes, body.output_modes,
@@ -266,6 +364,9 @@ async def update_agent(
status_code=404, status_code=404,
) )
subscribed = has_required_scope(request, ["premium"])
chat_model = body.chat_model if subscribed else None
agent = await AgentAdapters.aupdate_agent( agent = await AgentAdapters.aupdate_agent(
user, user,
body.name, body.name,
@@ -273,7 +374,7 @@ async def update_agent(
body.privacy_level, body.privacy_level,
body.icon, body.icon,
body.color, body.color,
body.chat_model, chat_model,
body.files, body.files,
body.input_tools, body.input_tools,
body.output_modes, body.output_modes,

View File

@@ -223,13 +223,17 @@ def chat_history(
if conversation.agent.privacy_level == Agent.PrivacyLevel.PRIVATE and conversation.agent.creator != user: if conversation.agent.privacy_level == Agent.PrivacyLevel.PRIVATE and conversation.agent.creator != user:
conversation.agent = None conversation.agent = None
else: else:
agent_has_files = EntryAdapters.agent_has_entries(conversation.agent)
agent_metadata = { agent_metadata = {
"slug": conversation.agent.slug, "slug": conversation.agent.slug,
"name": conversation.agent.name, "name": conversation.agent.name,
"isCreator": conversation.agent.creator == user, "is_creator": conversation.agent.creator == user,
"color": conversation.agent.style_color, "color": conversation.agent.style_color,
"icon": conversation.agent.style_icon, "icon": conversation.agent.style_icon,
"persona": conversation.agent.personality, "persona": conversation.agent.personality,
"is_hidden": conversation.agent.is_hidden,
"chat_model": conversation.agent.chat_model.name,
"has_files": agent_has_files,
} }
meta_log = conversation.conversation_log meta_log = conversation.conversation_log
@@ -282,13 +286,17 @@ def get_shared_chat(
if conversation.agent.privacy_level == Agent.PrivacyLevel.PRIVATE: if conversation.agent.privacy_level == Agent.PrivacyLevel.PRIVATE:
conversation.agent = None conversation.agent = None
else: else:
agent_has_files = EntryAdapters.agent_has_entries(conversation.agent)
agent_metadata = { agent_metadata = {
"slug": conversation.agent.slug, "slug": conversation.agent.slug,
"name": conversation.agent.name, "name": conversation.agent.name,
"isCreator": conversation.agent.creator == user, "is_creator": conversation.agent.creator == user,
"color": conversation.agent.style_color, "color": conversation.agent.style_color,
"icon": conversation.agent.style_icon, "icon": conversation.agent.style_icon,
"persona": conversation.agent.personality, "persona": conversation.agent.personality,
"is_hidden": conversation.agent.is_hidden,
"chat_model": conversation.agent.chat_model.name,
"has_files": agent_has_files,
} }
meta_log = conversation.conversation_log meta_log = conversation.conversation_log