From 1be3986537c2ad7159fde04cccb090b5bbc923ef Mon Sep 17 00:00:00 2001 From: Debanjum Date: Fri, 9 May 2025 00:41:39 -0600 Subject: [PATCH] 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' --- pyproject.toml | 2 +- .../operator/operator_environment_browser.py | 9 +++++++-- src/khoj/routers/api_chat.py | 3 +++ src/khoj/routers/helpers.py | 5 ++++- src/khoj/routers/research.py | 4 ++++ src/khoj/utils/helpers.py | 12 ++++++++++++ 6 files changed, 31 insertions(+), 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 256b3c8a..81fbea35 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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]", diff --git a/src/khoj/processor/operator/operator_environment_browser.py b/src/khoj/processor/operator/operator_environment_browser.py index 7edb1a5d..d4595a5e 100644 --- a/src/khoj/processor/operator/operator_environment_browser.py +++ b/src/khoj/processor/operator/operator_environment_browser.py @@ -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): diff --git a/src/khoj/routers/api_chat.py b/src/khoj/routers/api_chat.py index 73fc16be..7123998e 100644 --- a/src/khoj/routers/api_chat.py +++ b/src/khoj/routers/api_chat.py @@ -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] diff --git a/src/khoj/routers/helpers.py b/src/khoj/routers/helpers.py index 4836d962..ad1a8433 100644 --- a/src/khoj/routers/helpers.py +++ b/src/khoj/routers/helpers.py @@ -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' diff --git a/src/khoj/routers/research.py b/src/khoj/routers/research.py index 1c69cf60..7d8293ff 100644 --- a/src/khoj/routers/research.py +++ b/src/khoj/routers/research.py @@ -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: diff --git a/src/khoj/utils/helpers.py b/src/khoj/utils/helpers.py index 2b4fcb83..0a902629 100644 --- a/src/khoj/utils/helpers.py +++ b/src/khoj/utils/helpers.py @@ -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: