last_edit / server /action_engine.py
Moharek
Deploy Moharek GEO Platform
a74b879
import os
import json
import requests
from typing import List, Dict, Any
try:
from groq import Groq
except ImportError:
Groq = None
try:
import openai
except ImportError:
openai = None
# Import the new smart action engine
try:
from server.smart_action_engine import generate_smart_action_plan
except ImportError:
generate_smart_action_plan = None
def _run_rule_engine(audit_data: dict) -> List[Dict[str, str]]:
"""
Step 1: Simple Rule Engine - Arabic output (Legacy fallback)
"""
actions = []
pages = audit_data.get('pages', [])
if not pages:
return actions
for page in pages:
url = page.get('url', '')
tags = [h.get('tag', '') for h in page.get('headings', [])]
if 'h1' not in tags:
actions.append({
"type": "technical",
"task": f"أضف وسم H1 إلى الصفحة: {url}",
"priority": "high"
})
paras = page.get('paragraphs', [])
if paras:
avg_words = sum(len(str(p).split()) for p in paras) / len(paras)
if avg_words < 20:
actions.append({
"type": "content",
"task": f"توسيع المحتوى الضعيف في {url} (متوسط الكلمات: {int(avg_words)} كلمة/فقرة)",
"priority": "medium"
})
else:
actions.append({
"type": "content",
"task": f"أضف فقرات نصية ومحتوى كافياً إلى الصفحة: {url}",
"priority": "high"
})
ai_vis = audit_data.get('ai_visibility', {})
if ai_vis and not ai_vis.get('results', []):
actions.append({
"type": "authority",
"task": "لا يوجد ظهور في الذكاء الاصطناعي! أنشئ فقرة 'من نحن' قوية وانشر بيانات منظمة JSON-LD",
"priority": "high"
})
return actions
def _call_ollama(prompt: str, model: str = "mistral") -> str:
"""Call local Ollama instance."""
try:
url = "http://localhost:11434/api/generate"
payload = {
"model": model,
"prompt": prompt,
"stream": False,
"format": "json"
}
resp = requests.post(url, json=payload, timeout=30)
resp.raise_for_status()
return resp.json().get("response", "")
except Exception as e:
print(f"Ollama error: {e}")
return ""
def _call_groq(prompt: str, api_key: str) -> str:
if not Groq or not api_key:
return ""
try:
client = Groq(api_key=api_key)
completion = client.chat.completions.create(
model=os.getenv('GROQ_MODEL', 'llama-3.3-70b-versatile'),
messages=[
{'role': 'system', 'content': 'You are an AI Growth Engine. Output valid JSON only.'},
{'role': 'user', 'content': prompt}
],
response_format={"type": "json_object"},
temperature=0.2
)
return completion.choices[0].message.content
except Exception as e:
print(f"Groq logic error: {e}")
return ""
def _call_openai(prompt: str, api_key: str) -> str:
if not openai or not api_key:
return ""
try:
client = openai.OpenAI(api_key=api_key)
completion = client.chat.completions.create(
model=os.getenv('OPENAI_MODEL', 'gpt-4o-mini'),
messages=[
{'role': 'system', 'content': 'You are an AI Growth Engine. Output valid JSON only.'},
{'role': 'user', 'content': prompt}
],
response_format={"type": "json_object"},
temperature=0.2
)
return completion.choices[0].message.content
except Exception as e:
print(f"OpenAI logic error: {e}")
return ""
def generate_action_plan(audit_data: dict, api_keys: dict = None) -> dict:
"""
Main Action Engine logic - Now uses Smart Action Engine for enhanced recommendations
"""
api_keys = api_keys or {}
# Try to use the new smart action engine first
if generate_smart_action_plan:
try:
smart_result = generate_smart_action_plan(audit_data, api_keys)
if smart_result.get('ok'):
print("✅ Using Smart Action Engine")
return smart_result
except Exception as e:
print(f"⚠️ Smart Action Engine failed: {e}, falling back to legacy engine")
# Fallback to legacy engine
print("📋 Using Legacy Action Engine")
# 1. Gather rules-based actions
rule_actions = _run_rule_engine(audit_data)
# 2. Build summary prompt for AI
brand = audit_data.get('org_name', 'Unknown')
pages = audit_data.get('pages', [])
ai_vis = audit_data.get('ai_visibility', {})
page_summary = f"Pages crawled: {len(pages)}. "
if pages:
titles = [p.get('title') for p in pages[:5] if p.get('title')]
page_summary += f"Top pages: {', '.join(titles)}."
prompt = f"""
حلّل بيانات SEO هذه وأعد خطة عمل باللغة العربية.
العلامة التجارية: {brand}
{page_summary}
حالة الظهور في AI: {'جيد' if ai_vis.get('results') else 'ضعيف / غير موجود'}
اكتب أهم 6 إجراءات استراتيجية بالعربية لتحسين SEO والظهور في الذكاء الاصطناعي.
الصيغة المطلوبة حصراً:
{{
"actions": [
{{
"type": "content|تقني|سلطة|تواصل",
"task": "وصف قصير واضح بالعربية",
"priority": "high|medium|low"
}}
]
}}
"""
# 3. AI Decision Layer
raw_ai_response = ""
# Try Groq first (fastest/cheapest usually)
groq_key = api_keys.get('groq') or os.getenv('GROQ_API_KEY')
if groq_key:
raw_ai_response = _call_groq(prompt, groq_key)
# Try OpenAI
if not raw_ai_response:
openai_key = api_keys.get('openai') or os.getenv('OPENAI_API_KEY')
if openai_key:
raw_ai_response = _call_openai(prompt, openai_key)
# Try Ollama (Local fallback)
if not raw_ai_response:
raw_ai_response = _call_ollama(prompt)
# Parse AI response
ai_actions = []
if raw_ai_response:
try:
parsed = json.loads(raw_ai_response)
if "actions" in parsed:
ai_actions = parsed.get("actions", [])
except Exception as e:
print(f"Failed to parse AI action response: {e}")
# Combine
combined_actions = rule_actions + ai_actions
# Phase 4: Build Entity Graph and Link Actions
from server import entity_extractor
try:
job_id = audit_data.get('id')
entity_extractor.build_knowledge_graph(job_id, audit_data)
except Exception as e:
print(f"Entity Graph Error: {e}")
# Deduplicate loosely by task string
unique_actions = []
seen = set()
for act in combined_actions:
t = act.get('task', '').strip().lower()
if t not in seen and t:
seen.add(t)
unique_actions.append(act)
return {
"ok": True,
"actions": unique_actions
}