Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
b5f00da
Add Advanced Installation Intent Detection (#53) - complete feature w…
pavanimanchala53 Nov 13, 2025
bf832fa
Remove venv from repository index (clean up)
pavanimanchala53 Nov 14, 2025
4d6bc23
Finalize feature: add .gitignore and update llm_agent
pavanimanchala53 Nov 13, 2025
7a190af
Fix review comments: dedupe GPU intents, optional api_key, persist in…
pavanimanchala53 Nov 13, 2025
6698f87
Fix GPU intent dedupe as per review
pavanimanchala53 Nov 14, 2025
a725461
Apply ClassVar annotation, remove unused import, and fix GPU dedupe
pavanimanchala53 Nov 14, 2025
161acf6
Use GPU synonyms for configure intent
pavanimanchala53 Nov 14, 2025
ecd9a50
Fix CodeRabbit comments for llm_agent.py
pavanimanchala53 Nov 28, 2025
7535e5d
Add timeout to LLM API calls per CodeRabbit review
pavanimanchala53 Nov 28, 2025
2ac027c
Apply full CodeRabbit fixes for llm_agent.py
pavanimanchala53 Nov 28, 2025
af522e9
Apply CodeRabbit review fixes: timeouts, safety checks, imports (llm_…
pavanimanchala53 Nov 28, 2025
4277225
Add PyYAML to requirements
pavanimanchala53 Nov 30, 2025
26202e8
Update test_installation_history.py
pavanimanchala53 Dec 9, 2025
db80201
Update test_installation_history.py
pavanimanchala53 Dec 9, 2025
79bbfbd
Pin PyYAML version to 6.0.3
pavanimanchala53 Dec 9, 2025
9ed0503
Fix Issue #53: Add interactive clarification flow and user confirmation
Sahilbhatane Dec 9, 2025
c628890
Merge pull request #1 from Sahilbhatane/pr-213
pavanimanchala53 Dec 9, 2025
24cb1fe
Enhance GPU detection logic in planner.py
Sahilbhatane Dec 9, 2025
bf095c3
Delete .github/workflows/codeql.yml for sonarworkflow
Sahilbhatane Dec 9, 2025
35ca9bb
Merge branch 'main' into feature/advanced-intent-detection
Sahilbhatane Dec 10, 2025
ed3620f
Merge branch 'main' into feature/advanced-intent-detection
Sahilbhatane Dec 11, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 0 additions & 43 deletions .github/workflows/codeql.yml

This file was deleted.

1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ MANIFEST
# ==============================
# PyInstaller
# ==============================
# Usually contains temporary files from pyinstaller builds
*.manifest
*.spec

Expand Down
97 changes: 96 additions & 1 deletion cortex/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@

sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))

# Add src path for intent detection modules
src_path = os.path.join(os.path.dirname(__file__), '..', 'src')
if os.path.exists(src_path):
sys.path.insert(0, src_path)

from LLM.interpreter import CommandInterpreter
from cortex.coordinator import InstallationCoordinator, StepStatus
from cortex.installation_history import (
Expand Down Expand Up @@ -198,6 +203,64 @@

try:
self._print_status("🧠", "Understanding request...")

# Try to use intent detection if available (for showing installation plan)
use_intent_detection = False
try:
from intent.detector import IntentDetector
from intent.planner import InstallationPlanner
from intent.clarifier import Clarifier
use_intent_detection = True
except ImportError:
self._debug("Intent detection modules not available, using fallback")

# Show intent-based plan and get confirmation (for any package)
if use_intent_detection and not execute and not dry_run:
detector = IntentDetector()
planner = InstallationPlanner()
clarifier = Clarifier()

intents = detector.detect(software)

# Check for clarification needs
clarification = clarifier.needs_clarification(intents, software)
if clarification:
cx_print(f"\n❓ {clarification}", "warning")
print()
cx_print("Please provide more specific details in your request.", "info")
cx_print("Example: 'cortex install pytorch and tensorflow'", "info")
print()
cx_print("Or press Ctrl+C to cancel.", "info")
try:
response = input("\nYour clarification: ").strip()
if response:
# Retry with the clarified request
software = response
intents = detector.detect(software)
clarification = clarifier.needs_clarification(intents, software)
if clarification:
cx_print(f"\n❓ Still need clarification: {clarification}", "warning")
cx_print("Falling back to LLM for command generation...", "info")
else:
cx_print("No input provided. Falling back to LLM...", "info")
except (KeyboardInterrupt, EOFError):
print("\n")
cx_print("Operation cancelled by user.", "info")
return 0

# Build plan (even if no intents detected, we'll show LLM-generated commands)
plan = planner.build_plan(intents)

# Always show plan if we have intents, otherwise fall through to LLM
if plan and len(plan) > 1: # More than just verification step
cx_print("\n📋 Installation Plan:", "info")
for i, step in enumerate(plan, 1):
print(f" {i}. {step}")
print()

# For ANY request (not just ML), generate commands via LLM and ask confirmation
# This happens whether intents were detected or not


interpreter = CommandInterpreter(api_key=api_key, provider=provider)

Expand All @@ -215,7 +278,32 @@

# Extract packages from commands for tracking
packages = history._extract_packages_from_commands(commands)


# Show generated commands and ask for confirmation (Issue #53)
if not execute and not dry_run:
print("\nGenerated commands:")

Check failure on line 284 in cortex/cli.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Define a constant instead of duplicating this literal "\nGenerated commands:" 3 times.

See more on https://sonarcloud.io/project/issues?id=cortexlinux_cortex&issues=AZsNQqktVAkv4SoIngnG&open=AZsNQqktVAkv4SoIngnG&pullRequest=213
for i, cmd in enumerate(commands, 1):
print(f" {i}. {cmd}")

# Ask for confirmation before executing
print()
try:
response = input("Proceed with plan? [Y/n]: ").strip().lower()
if response == 'n' or response == 'no':
cx_print("Installation cancelled by user.", "info")
return 0
elif response == '' or response == 'y' or response == 'yes':
# User confirmed, proceed with execution
execute = True
cx_print("\nProceeding with installation...", "success")
else:
cx_print("Invalid response. Installation cancelled.", "error")
return 1
except (KeyboardInterrupt, EOFError):
print("\n")
cx_print("Installation cancelled by user.", "info")
return 0

# Record installation start
if execute or dry_run:
install_id = history.record_installation(
Expand All @@ -224,6 +312,13 @@
commands,
start_time
)

if not dry_run:
self._print_status("⚙️", f"Installing {software}...")
print("\nGenerated commands:")
for i, cmd in enumerate(commands, 1):
print(f" {i}. {cmd}")


self._print_status("⚙️", f"Installing {software}...")
print("\nGenerated commands:")
Expand Down
15 changes: 15 additions & 0 deletions requirements-dev.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,21 @@
# Development Dependencies
pytest>=7.0.0
pytest-cov>=4.0.0
pytest-mock>=3.10.0

# Code Quality
black>=23.0.0
pylint>=2.17.0
mypy>=1.0.0

# Security
bandit>=1.7.0
safety>=2.3.0

# Documentation
sphinx>=6.0.0
sphinx-rtd-theme>=1.0.0
PyYAML==6.0.3
black>=24.0.0
ruff>=0.8.0
isort>=5.13.0
Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ pyyaml>=6.0.0

# Type hints for older Python versions
typing-extensions>=4.0.0
PyYAML==6.0.3
File renamed without changes.
Empty file added src/intent/__init__.py
Empty file.
36 changes: 36 additions & 0 deletions src/intent/clarifier.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# clarifier.py

from typing import List, Optional
from intent.detector import Intent

class Clarifier:
"""
Checks if the detected intents have missing information.
Returns a clarifying question if needed.
"""

def needs_clarification(self, intents: List[Intent], text: str) -> Optional[str]:
text = text.lower()

# 1. If user mentions "gpu" but has not specified which GPU → ask
if "gpu" in text and not any(i.target in ["cuda", "pytorch", "tensorflow"] for i in intents):
return "Do you have an NVIDIA GPU? (Needed for CUDA/PyTorch/TensorFlow installation)"

# 2. If user says "machine learning tools" but nothing specific
generic_terms = ["ml", "machine learning", "deep learning", "ai tools"]
if any(term in text for term in generic_terms) and len(intents) == 0:
return "Which ML frameworks do you need? (PyTorch, TensorFlow, JupyterLab...)"

# 3. If user asks to install CUDA but no GPU exists in context
if any(i.target == "cuda" for i in intents) and "gpu" not in text:
return "Installing CUDA requires an NVIDIA GPU. Do you have one?"

# 4. If package versions are missing (later we can add real version logic)
# Only ask about GPU/CPU version if user hasn't already specified
if "torch" in text and "version" not in text:
# Don't ask if user already mentioned GPU or CUDA
if not any(term in text for term in ["gpu", "cuda", "nvidia", "graphics"]):
return "Do you need the GPU version or CPU version of PyTorch?"

# 5. Otherwise no clarification needed
return None
69 changes: 69 additions & 0 deletions src/intent/context.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# context.py

from typing import List, Optional
from intent.detector import Intent

class SessionContext:
"""
Stores context from previous user interactions.
This is needed for Issue #53:
'Uses context from previous commands'
"""

def __init__(self):
self.detected_gpu: Optional[str] = None
self.previous_intents: List[Intent] = []
self.installed_packages: List[str] = []
self.clarifications: List[str] = []

# -------------------
# GPU CONTEXT
# -------------------

def set_gpu(self, gpu_name: str):
self.detected_gpu = gpu_name

def get_gpu(self) -> Optional[str]:
return self.detected_gpu

# -------------------
# INTENT CONTEXT
# -------------------

def add_intents(self, intents: List[Intent]):
self.previous_intents.extend(intents)

def get_previous_intents(self) -> List[Intent]:
return self.previous_intents

# -------------------
# INSTALLED PACKAGES
# -------------------

def add_installed(self, pkg: str):
if pkg not in self.installed_packages:
self.installed_packages.append(pkg)

def is_installed(self, pkg: str) -> bool:
return pkg in self.installed_packages

# -------------------
# CLARIFICATIONS
# -------------------

def add_clarification(self, question: str):
self.clarifications.append(question)

def get_clarifications(self) -> List[str]:
return self.clarifications

# -------------------
# RESET CONTEXT
# -------------------

def reset(self):
"""Reset context (new session)"""
self.detected_gpu = None
self.previous_intents = []
self.installed_packages = []
self.clarifications = []
53 changes: 53 additions & 0 deletions src/intent/detector.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# detector.py

from dataclasses import dataclass
from typing import List, Optional, ClassVar

@dataclass
class Intent:
action: str
target: str
details: Optional[dict] = None

class IntentDetector:
"""
Extracts high-level installation intents from natural language requests.
"""

COMMON_PACKAGES: ClassVar[dict[str, List[str]]] = {
"cuda": ["cuda", "nvidia toolkit"],
"pytorch": ["pytorch", "torch"],
"tensorflow": ["tensorflow", "tf"],
"jupyter": ["jupyter", "jupyterlab", "notebook"],
"cudnn": ["cudnn"],
"python": ["python", "python3"],
"docker": ["docker"],
"nodejs": ["node", "nodejs", "npm"],
"git": ["git"],
"gpu": ["gpu", "graphics card", "rtx", "nvidia"]
}

def detect(self, text: str) -> List[Intent]:
text = text.lower()
intents = []

# 1. Rule-based keyword detection (skip GPU to avoid duplicate install intent)
for pkg, keywords in self.COMMON_PACKAGES.items():
if pkg == "gpu":
continue # GPU handled separately below
if any(k in text for k in keywords):
intents.append(Intent(action="install", target=pkg))

# 2. Look for verify steps
if "verify" in text or "check" in text:
intents.append(Intent(action="verify", target="installation"))

# 3. GPU configure intent (use all GPU synonyms)
gpu_keywords = self.COMMON_PACKAGES.get("gpu", ["gpu"])
if any(k in text for k in gpu_keywords) and not any(
i.action == "configure" and i.target == "gpu"
for i in intents
):
intents.append(Intent(action="configure", target="gpu"))

return intents
Loading
Loading