This commit is contained in:
Pablo Estevez
2026-01-17 17:29:21 +00:00
parent c89f059712
commit 5ed767ff9a
144 changed files with 14142 additions and 16488 deletions

View File

@@ -20,7 +20,7 @@ import subprocess
import tempfile
from dataclasses import dataclass, field
from pathlib import Path
from typing import Dict, List, Optional, TYPE_CHECKING
from typing import TYPE_CHECKING
# Avoid circular imports by using TYPE_CHECKING
if TYPE_CHECKING:
@@ -40,15 +40,17 @@ else:
@dataclass
class TroubleshootingItem:
problem: str
symptoms: List[str] = field(default_factory=list)
symptoms: list[str] = field(default_factory=list)
solution: str = ""
diagnostic_steps: List[str] = field(default_factory=list)
diagnostic_steps: list[str] = field(default_factory=list)
logger = logging.getLogger(__name__)
# Conditional import for Anthropic API
try:
import anthropic
ANTHROPIC_AVAILABLE = True
except ImportError:
ANTHROPIC_AVAILABLE = False
@@ -58,9 +60,10 @@ except ImportError:
@dataclass
class StepEnhancement:
"""Enhanced step information (internal use only)"""
step_index: int
explanation: str # Natural language explanation
variations: List[str] = field(default_factory=list) # Alternative approaches
variations: list[str] = field(default_factory=list) # Alternative approaches
class GuideEnhancer:
@@ -81,7 +84,7 @@ class GuideEnhancer:
mode: Enhancement mode - "api", "local", or "auto"
"""
self.mode = self._detect_mode(mode)
self.api_key = os.environ.get('ANTHROPIC_API_KEY')
self.api_key = os.environ.get("ANTHROPIC_API_KEY")
self.client = None
if self.mode == "api":
@@ -119,7 +122,7 @@ class GuideEnhancer:
"""
if requested_mode == "auto":
# Prefer API if key available, else LOCAL
if os.environ.get('ANTHROPIC_API_KEY') and ANTHROPIC_AVAILABLE:
if os.environ.get("ANTHROPIC_API_KEY") and ANTHROPIC_AVAILABLE:
return "api"
elif self._check_claude_cli():
return "local"
@@ -130,17 +133,12 @@ class GuideEnhancer:
def _check_claude_cli(self) -> bool:
"""Check if Claude Code CLI is available."""
try:
result = subprocess.run(
['claude', '--version'],
capture_output=True,
text=True,
timeout=5
)
result = subprocess.run(["claude", "--version"], capture_output=True, text=True, timeout=5)
return result.returncode == 0
except (FileNotFoundError, subprocess.TimeoutExpired):
return False
def enhance_guide(self, guide_data: Dict) -> Dict:
def enhance_guide(self, guide_data: dict) -> dict:
"""
Apply all 5 enhancements to a guide.
@@ -164,7 +162,7 @@ class GuideEnhancer:
logger.info("📝 Returning original guide without enhancement")
return guide_data
def enhance_step_descriptions(self, steps: List[Dict]) -> List[StepEnhancement]:
def enhance_step_descriptions(self, steps: list[dict]) -> list[StepEnhancement]:
"""
Enhancement 1: Add natural language explanations to steps.
@@ -187,17 +185,17 @@ class GuideEnhancer:
data = json.loads(response)
return [
StepEnhancement(
step_index=item.get('step_index', i),
explanation=item.get('explanation', ''),
variations=item.get('variations', [])
step_index=item.get("step_index", i),
explanation=item.get("explanation", ""),
variations=item.get("variations", []),
)
for i, item in enumerate(data.get('step_descriptions', []))
for i, item in enumerate(data.get("step_descriptions", []))
]
except (json.JSONDecodeError, KeyError) as e:
logger.warning(f"⚠️ Failed to parse step descriptions: {e}")
return []
def enhance_troubleshooting(self, guide_data: Dict) -> List[TroubleshootingItem]:
def enhance_troubleshooting(self, guide_data: dict) -> list[TroubleshootingItem]:
"""
Enhancement 2: Generate diagnostic flows + solutions.
@@ -220,18 +218,18 @@ class GuideEnhancer:
data = json.loads(response)
return [
TroubleshootingItem(
problem=item.get('problem', ''),
symptoms=item.get('symptoms', []),
diagnostic_steps=item.get('diagnostic_steps', []),
solution=item.get('solution', '')
problem=item.get("problem", ""),
symptoms=item.get("symptoms", []),
diagnostic_steps=item.get("diagnostic_steps", []),
solution=item.get("solution", ""),
)
for item in data.get('troubleshooting', [])
for item in data.get("troubleshooting", [])
]
except (json.JSONDecodeError, KeyError) as e:
logger.warning(f"⚠️ Failed to parse troubleshooting items: {e}")
return []
def enhance_prerequisites(self, prereqs: List[str]) -> List[PrerequisiteItem]:
def enhance_prerequisites(self, prereqs: list[str]) -> list[PrerequisiteItem]:
"""
Enhancement 3: Explain why prerequisites are needed.
@@ -253,18 +251,14 @@ class GuideEnhancer:
try:
data = json.loads(response)
return [
PrerequisiteItem(
name=item.get('name', ''),
why=item.get('why', ''),
setup=item.get('setup', '')
)
for item in data.get('prerequisites_detailed', [])
PrerequisiteItem(name=item.get("name", ""), why=item.get("why", ""), setup=item.get("setup", ""))
for item in data.get("prerequisites_detailed", [])
]
except (json.JSONDecodeError, KeyError) as e:
logger.warning(f"⚠️ Failed to parse prerequisites: {e}")
return []
def enhance_next_steps(self, guide_data: Dict) -> List[str]:
def enhance_next_steps(self, guide_data: dict) -> list[str]:
"""
Enhancement 4: Suggest related guides and variations.
@@ -285,12 +279,12 @@ class GuideEnhancer:
try:
data = json.loads(response)
return data.get('next_steps', [])
return data.get("next_steps", [])
except (json.JSONDecodeError, KeyError) as e:
logger.warning(f"⚠️ Failed to parse next steps: {e}")
return []
def enhance_use_cases(self, guide_data: Dict) -> List[str]:
def enhance_use_cases(self, guide_data: dict) -> list[str]:
"""
Enhancement 5: Generate real-world scenario examples.
@@ -311,14 +305,14 @@ class GuideEnhancer:
try:
data = json.loads(response)
return data.get('use_cases', [])
return data.get("use_cases", [])
except (json.JSONDecodeError, KeyError) as e:
logger.warning(f"⚠️ Failed to parse use cases: {e}")
return []
# === AI Call Methods ===
def _call_ai(self, prompt: str, max_tokens: int = 4000) -> Optional[str]:
def _call_ai(self, prompt: str, max_tokens: int = 4000) -> str | None:
"""
Call AI with the given prompt.
@@ -335,7 +329,7 @@ class GuideEnhancer:
return self._call_claude_local(prompt)
return None
def _call_claude_api(self, prompt: str, max_tokens: int = 4000) -> Optional[str]:
def _call_claude_api(self, prompt: str, max_tokens: int = 4000) -> str | None:
"""
Call Claude API.
@@ -351,16 +345,14 @@ class GuideEnhancer:
try:
response = self.client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=max_tokens,
messages=[{"role": "user", "content": prompt}]
model="claude-sonnet-4-20250514", max_tokens=max_tokens, messages=[{"role": "user", "content": prompt}]
)
return response.content[0].text
except Exception as e:
logger.warning(f"⚠️ Claude API call failed: {e}")
return None
def _call_claude_local(self, prompt: str) -> Optional[str]:
def _call_claude_local(self, prompt: str) -> str | None:
"""
Call Claude Code CLI.
@@ -372,16 +364,16 @@ class GuideEnhancer:
"""
try:
# Create temporary prompt file
with tempfile.NamedTemporaryFile(mode='w', suffix='.md', delete=False) as f:
with tempfile.NamedTemporaryFile(mode="w", suffix=".md", delete=False) as f:
f.write(prompt)
prompt_file = f.name
# Run claude CLI
result = subprocess.run(
['claude', prompt_file],
["claude", prompt_file],
capture_output=True,
text=True,
timeout=300 # 5 min timeout
timeout=300, # 5 min timeout
)
# Clean up prompt file
@@ -399,7 +391,7 @@ class GuideEnhancer:
# === Prompt Creation Methods ===
def _enhance_via_api(self, guide_data: Dict) -> Dict:
def _enhance_via_api(self, guide_data: dict) -> dict:
"""
Enhance guide via API mode.
@@ -417,7 +409,7 @@ class GuideEnhancer:
return self._parse_enhancement_response(response, guide_data)
def _enhance_via_local(self, guide_data: Dict) -> Dict:
def _enhance_via_local(self, guide_data: dict) -> dict:
"""
Enhance guide via LOCAL mode.
@@ -435,7 +427,7 @@ class GuideEnhancer:
return self._parse_enhancement_response(response, guide_data)
def _create_enhancement_prompt(self, guide_data: Dict) -> str:
def _create_enhancement_prompt(self, guide_data: dict) -> str:
"""
Create comprehensive enhancement prompt for all 5 enhancements.
@@ -445,13 +437,13 @@ class GuideEnhancer:
Returns:
Complete prompt text
"""
title = guide_data.get('title', 'Unknown Guide')
steps = guide_data.get('steps', [])
language = guide_data.get('language', 'python')
prerequisites = guide_data.get('prerequisites', [])
title = guide_data.get("title", "Unknown Guide")
steps = guide_data.get("steps", [])
language = guide_data.get("language", "python")
prerequisites = guide_data.get("prerequisites", [])
steps_text = self._format_steps_for_prompt(steps)
prereqs_text = ', '.join(prerequisites) if prerequisites else 'None specified'
prereqs_text = ", ".join(prerequisites) if prerequisites else "None specified"
prompt = f"""I need you to enhance this how-to guide with 5 improvements:
@@ -528,7 +520,7 @@ IMPORTANT: Return ONLY valid JSON, no markdown code blocks or extra text.
"""
return prompt
def _create_step_description_prompt(self, steps: List[Dict]) -> str:
def _create_step_description_prompt(self, steps: list[dict]) -> str:
"""Create prompt for step descriptions only."""
steps_text = self._format_steps_for_prompt(steps)
return f"""Generate natural language explanations for these code steps:
@@ -546,11 +538,11 @@ Return JSON:
IMPORTANT: Return ONLY valid JSON.
"""
def _create_troubleshooting_prompt(self, guide_data: Dict) -> str:
def _create_troubleshooting_prompt(self, guide_data: dict) -> str:
"""Create prompt for troubleshooting items."""
title = guide_data.get('title', 'Unknown')
language = guide_data.get('language', 'python')
steps = guide_data.get('steps', [])
title = guide_data.get("title", "Unknown")
language = guide_data.get("language", "python")
steps = guide_data.get("steps", [])
steps_text = self._format_steps_for_prompt(steps)
return f"""Generate troubleshooting guidance for this {language} workflow:
@@ -575,9 +567,9 @@ Return JSON with 3-5 common errors:
IMPORTANT: Return ONLY valid JSON.
"""
def _create_prerequisites_prompt(self, prereqs: List[str]) -> str:
def _create_prerequisites_prompt(self, prereqs: list[str]) -> str:
"""Create prompt for prerequisites enhancement."""
prereqs_text = ', '.join(prereqs)
prereqs_text = ", ".join(prereqs)
return f"""Explain why these prerequisites are needed and how to install them:
Prerequisites: {prereqs_text}
@@ -593,9 +585,9 @@ Return JSON:
IMPORTANT: Return ONLY valid JSON.
"""
def _create_next_steps_prompt(self, guide_data: Dict) -> str:
def _create_next_steps_prompt(self, guide_data: dict) -> str:
"""Create prompt for next steps suggestions."""
title = guide_data.get('title', 'Unknown')
title = guide_data.get("title", "Unknown")
return f"""Suggest 3-5 related guides and learning paths after completing: {title}
Return JSON:
@@ -610,10 +602,10 @@ Return JSON:
IMPORTANT: Return ONLY valid JSON.
"""
def _create_use_cases_prompt(self, guide_data: Dict) -> str:
def _create_use_cases_prompt(self, guide_data: dict) -> str:
"""Create prompt for use case examples."""
title = guide_data.get('title', 'Unknown')
description = guide_data.get('description', '')
title = guide_data.get("title", "Unknown")
description = guide_data.get("description", "")
return f"""Generate 2-3 real-world use cases for this guide:
@@ -632,23 +624,23 @@ Return JSON:
IMPORTANT: Return ONLY valid JSON.
"""
def _format_steps_for_prompt(self, steps: List[Dict]) -> str:
def _format_steps_for_prompt(self, steps: list[dict]) -> str:
"""Format steps for inclusion in prompts."""
if not steps:
return "No steps provided"
formatted = []
for i, step in enumerate(steps):
desc = step.get('description', '')
code = step.get('code', '')
desc = step.get("description", "")
code = step.get("code", "")
if code:
formatted.append(f"Step {i+1}: {desc}\n```\n{code}\n```")
formatted.append(f"Step {i + 1}: {desc}\n```\n{code}\n```")
else:
formatted.append(f"Step {i+1}: {desc}")
formatted.append(f"Step {i + 1}: {desc}")
return "\n\n".join(formatted)
def _parse_enhancement_response(self, response: str, guide_data: Dict) -> Dict:
def _parse_enhancement_response(self, response: str, guide_data: dict) -> dict:
"""
Parse AI enhancement response.
@@ -661,8 +653,8 @@ IMPORTANT: Return ONLY valid JSON.
"""
try:
# Try to extract JSON from response (in case there's extra text)
json_start = response.find('{')
json_end = response.rfind('}') + 1
json_start = response.find("{")
json_end = response.rfind("}") + 1
if json_start >= 0 and json_end > json_start:
json_text = response[json_start:json_end]
data = json.loads(json_text)
@@ -673,46 +665,42 @@ IMPORTANT: Return ONLY valid JSON.
enhanced = guide_data.copy()
# Step descriptions
if 'step_descriptions' in data:
enhanced['step_enhancements'] = [
if "step_descriptions" in data:
enhanced["step_enhancements"] = [
StepEnhancement(
step_index=item.get('step_index', i),
explanation=item.get('explanation', ''),
variations=item.get('variations', [])
step_index=item.get("step_index", i),
explanation=item.get("explanation", ""),
variations=item.get("variations", []),
)
for i, item in enumerate(data['step_descriptions'])
for i, item in enumerate(data["step_descriptions"])
]
# Troubleshooting
if 'troubleshooting' in data:
enhanced['troubleshooting_detailed'] = [
if "troubleshooting" in data:
enhanced["troubleshooting_detailed"] = [
TroubleshootingItem(
problem=item.get('problem', ''),
symptoms=item.get('symptoms', []),
diagnostic_steps=item.get('diagnostic_steps', []),
solution=item.get('solution', '')
problem=item.get("problem", ""),
symptoms=item.get("symptoms", []),
diagnostic_steps=item.get("diagnostic_steps", []),
solution=item.get("solution", ""),
)
for item in data['troubleshooting']
for item in data["troubleshooting"]
]
# Prerequisites
if 'prerequisites_detailed' in data:
enhanced['prerequisites_detailed'] = [
PrerequisiteItem(
name=item.get('name', ''),
why=item.get('why', ''),
setup=item.get('setup', '')
)
for item in data['prerequisites_detailed']
if "prerequisites_detailed" in data:
enhanced["prerequisites_detailed"] = [
PrerequisiteItem(name=item.get("name", ""), why=item.get("why", ""), setup=item.get("setup", ""))
for item in data["prerequisites_detailed"]
]
# Next steps
if 'next_steps' in data:
enhanced['next_steps_detailed'] = data['next_steps']
if "next_steps" in data:
enhanced["next_steps_detailed"] = data["next_steps"]
# Use cases
if 'use_cases' in data:
enhanced['use_cases'] = data['use_cases']
if "use_cases" in data:
enhanced["use_cases"] = data["use_cases"]
logger.info("✅ Successfully enhanced guide with all 5 improvements")
return enhanced