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.config import OfflineChatProcessorModel
from khoj.utils.helpers import (
generate_random_internal_agent_name,
generate_random_name,
in_debug_mode,
is_none_or_empty,
@@ -806,13 +807,15 @@ class AgentAdapters:
privacy_level: str,
icon: str,
color: str,
chat_model: str,
chat_model: Optional[str],
files: List[str],
input_tools: List[str],
output_modes: List[str],
slug: Optional[str] = None,
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()
# 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
@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:
@staticmethod

View File

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

View File

@@ -9,7 +9,7 @@ from fastapi import APIRouter, Request
from fastapi.requests import Request
from fastapi.responses import Response
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.models import Agent, Conversation, KhojUser
@@ -41,6 +41,14 @@ class ModifyAgentBody(BaseModel):
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)
async def all_agents(
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)
@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)
@requires(["authenticated"])
async def create_agent(
@@ -203,6 +298,9 @@ async def create_agent(
status_code=400,
)
subscribed = has_required_scope(request, ["premium"])
chat_model = body.chat_model if subscribed else None
agent = await AgentAdapters.aupdate_agent(
user,
body.name,
@@ -210,7 +308,7 @@ async def create_agent(
body.privacy_level,
body.icon,
body.color,
body.chat_model,
chat_model,
body.files,
body.input_tools,
body.output_modes,
@@ -266,6 +364,9 @@ async def update_agent(
status_code=404,
)
subscribed = has_required_scope(request, ["premium"])
chat_model = body.chat_model if subscribed else None
agent = await AgentAdapters.aupdate_agent(
user,
body.name,
@@ -273,7 +374,7 @@ async def update_agent(
body.privacy_level,
body.icon,
body.color,
body.chat_model,
chat_model,
body.files,
body.input_tools,
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:
conversation.agent = None
else:
agent_has_files = EntryAdapters.agent_has_entries(conversation.agent)
agent_metadata = {
"slug": conversation.agent.slug,
"name": conversation.agent.name,
"isCreator": conversation.agent.creator == user,
"is_creator": conversation.agent.creator == user,
"color": conversation.agent.style_color,
"icon": conversation.agent.style_icon,
"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
@@ -282,13 +286,17 @@ def get_shared_chat(
if conversation.agent.privacy_level == Agent.PrivacyLevel.PRIVATE:
conversation.agent = None
else:
agent_has_files = EntryAdapters.agent_has_entries(conversation.agent)
agent_metadata = {
"slug": conversation.agent.slug,
"name": conversation.agent.name,
"isCreator": conversation.agent.creator == user,
"is_creator": conversation.agent.creator == user,
"color": conversation.agent.style_color,
"icon": conversation.agent.style_icon,
"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