Require explicit switch to enable operator locally for now

Operator is still early in development. To enable it:
- Set KHOJ_OPERATOR_ENABLE environment variable to true
- Run any one of the commands below:
  - `pip install khoj[local]'
  - `pip install khoj[dev]'
  - `pip install playwright'
This commit is contained in:
Debanjum
2025-05-09 00:41:39 -06:00
parent b395a438d0
commit 1be3986537
6 changed files with 31 additions and 4 deletions

View File

@@ -93,7 +93,6 @@ dependencies = [
"resend == 1.0.1",
"email-validator == 2.2.0",
"e2b-code-interpreter ~= 1.0.0",
"playwright >= 1.49.0",
]
dynamic = ["version"]
@@ -114,6 +113,7 @@ prod = [
]
local = [
"pgserver == 0.1.4",
"playwright >= 1.49.0",
]
dev = [
"khoj[prod,local]",

View File

@@ -4,8 +4,6 @@ import logging
import os
from typing import Optional, Set
from playwright.async_api import Browser, Page, Playwright, async_playwright
from khoj.processor.operator.operator_actions import OperatorAction
from khoj.processor.operator.operator_environment_base import (
Environment,
@@ -16,6 +14,13 @@ from khoj.utils.helpers import convert_image_to_webp
logger = logging.getLogger(__name__)
try:
from playwright.async_api import Browser, Page, Playwright, async_playwright
except ImportError:
logger.debug(
"Playwright not found. To use browser operator, run 'pip install playwright' and 'playwright install' first."
)
# --- Concrete BrowserEnvironment ---
class BrowserEnvironment(Environment):

View File

@@ -79,6 +79,7 @@ from khoj.utils.helpers import (
get_country_name_from_timezone,
get_device,
is_none_or_empty,
is_operator_enabled,
)
from khoj.utils.rawconfig import (
ChatRequestBody,
@@ -570,6 +571,8 @@ async def chat_options(
) -> Response:
cmd_options = {}
for cmd in ConversationCommand:
if cmd == ConversationCommand.Operator and not is_operator_enabled():
continue
if cmd in command_descriptions:
cmd_options[cmd.value] = command_descriptions[cmd]

View File

@@ -113,6 +113,7 @@ from khoj.utils.helpers import (
get_file_type,
in_debug_mode,
is_none_or_empty,
is_operator_enabled,
is_valid_url,
log_telemetry,
mode_descriptions_for_llm,
@@ -253,7 +254,7 @@ def get_conversation_command(query: str) -> ConversationCommand:
return ConversationCommand.Code
elif query.startswith("/research"):
return ConversationCommand.Research
elif query.startswith("/operator"):
elif query.startswith("/operator") and is_operator_enabled():
return ConversationCommand.Operator
else:
return ConversationCommand.Default
@@ -364,6 +365,8 @@ async def aget_data_sources_and_output_format(
# Skip showing Notes tool as an option if user has no entries
if source == ConversationCommand.Notes and not user_has_entries:
continue
if source == ConversationCommand.Operator and not is_operator_enabled():
continue
source_options[source.value] = description
if len(agent_sources) == 0 or source.value in agent_sources:
source_options_str += f'- "{source.value}": "{description}"\n'

View File

@@ -29,6 +29,7 @@ from khoj.routers.helpers import (
from khoj.utils.helpers import (
ConversationCommand,
is_none_or_empty,
is_operator_enabled,
timer,
tool_description_for_research_llm,
truncate_code_context,
@@ -99,6 +100,9 @@ async def apick_next_tool(
agent_tools = agent.input_tools if agent else []
user_has_entries = await EntryAdapters.auser_has_entries(user)
for tool, description in tool_description_for_research_llm.items():
# Skip showing operator tool as an option if not enabled
if tool == ConversationCommand.Operator and not is_operator_enabled():
continue
# Skip showing Notes tool as an option if user has no entries
if tool == ConversationCommand.Notes:
if not user_has_entries:

View File

@@ -490,6 +490,18 @@ def is_promptrace_enabled():
return not is_none_or_empty(os.getenv("PROMPTRACE_DIR"))
def is_operator_enabled():
"""Check if Khoj can operate GUI applications.
Set KHOJ_OPERATOR_ENABLED env var to true and install playwright to enable it."""
try:
import playwright
is_playwright_installed = True
except ImportError:
is_playwright_installed = False
return is_env_var_true("KHOJ_OPERATOR_ENABLED") and is_playwright_installed
def is_valid_url(url: str) -> bool:
"""Check if a string is a valid URL"""
try: