Update hf_demo.py
Browse files- hf_demo.py +59 -74
hf_demo.py
CHANGED
|
@@ -18,6 +18,8 @@ import logging
|
|
| 18 |
import asyncio
|
| 19 |
import sqlite3
|
| 20 |
import requests
|
|
|
|
|
|
|
| 21 |
from datetime import datetime, timedelta
|
| 22 |
from typing import Dict, List, Optional, Any, Tuple
|
| 23 |
from contextlib import contextmanager
|
|
@@ -31,44 +33,64 @@ gr.close_all()
|
|
| 31 |
from fastapi import FastAPI, HTTPException, Depends, status
|
| 32 |
from fastapi.middleware.cors import CORSMiddleware
|
| 33 |
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
|
| 34 |
-
from pydantic import BaseModel, Field, field_validator
|
| 35 |
-
from pydantic_settings import BaseSettings # <-- NEW:
|
| 36 |
from gradio import mount_gradio_app
|
| 37 |
|
| 38 |
-
# ==============
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 39 |
class Settings(BaseSettings):
|
| 40 |
-
"""Centralized configuration using Pydantic Settings"""
|
| 41 |
|
| 42 |
-
# Hugging Face settings
|
| 43 |
-
|
| 44 |
-
|
| 45 |
|
| 46 |
# Persistence - HF persistent storage
|
| 47 |
-
|
|
|
|
|
|
|
|
|
|
| 48 |
|
| 49 |
# Lead generation
|
| 50 |
-
|
| 51 |
-
|
| 52 |
|
| 53 |
# Webhook for lead alerts (set in HF secrets)
|
| 54 |
-
|
| 55 |
-
|
| 56 |
|
| 57 |
# Security
|
| 58 |
-
|
|
|
|
|
|
|
|
|
|
| 59 |
|
| 60 |
# ARF defaults
|
| 61 |
-
|
| 62 |
-
|
| 63 |
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 67 |
|
| 68 |
def __init__(self, **kwargs):
|
| 69 |
super().__init__(**kwargs)
|
| 70 |
# Ensure data directory exists
|
| 71 |
-
os.makedirs(self.
|
| 72 |
|
| 73 |
settings = Settings()
|
| 74 |
|
|
@@ -77,7 +99,7 @@ logging.basicConfig(
|
|
| 77 |
level=logging.INFO,
|
| 78 |
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
| 79 |
handlers=[
|
| 80 |
-
logging.FileHandler(f'{settings.
|
| 81 |
logging.StreamHandler()
|
| 82 |
]
|
| 83 |
)
|
|
@@ -123,7 +145,7 @@ class BayesianRiskEngine:
|
|
| 123 |
'default': {'alpha': 2.0, 'beta': 5.0}
|
| 124 |
}
|
| 125 |
|
| 126 |
-
self.evidence_db = f"{settings.
|
| 127 |
self._init_db()
|
| 128 |
|
| 129 |
def _init_db(self):
|
|
@@ -190,41 +212,31 @@ class BayesianRiskEngine:
|
|
| 190 |
return (row[0] or 0, row[1] or 0) if row else (0, 0)
|
| 191 |
except sqlite3.Error as e:
|
| 192 |
logger.error(f"Failed to retrieve evidence: {e}")
|
| 193 |
-
return (0, 0)
|
| 194 |
|
| 195 |
def calculate_posterior(self,
|
| 196 |
action_text: str,
|
| 197 |
context: Dict[str, Any]) -> Dict[str, Any]:
|
| 198 |
-
# ... (same as before, no changes needed) ...
|
| 199 |
-
# 1. Classify action for appropriate prior
|
| 200 |
action_type = self.classify_action(action_text)
|
| 201 |
alpha0, beta0 = self.get_prior(action_type)
|
| 202 |
|
| 203 |
-
# 2. Get historical evidence
|
| 204 |
action_hash = hashlib.sha256(action_text.encode()).hexdigest()
|
| 205 |
successes, trials = self.get_evidence(action_hash)
|
| 206 |
|
| 207 |
-
# 3. Update prior with evidence β posterior
|
| 208 |
alpha_n = alpha0 + successes
|
| 209 |
beta_n = beta0 + (trials - successes)
|
| 210 |
|
| 211 |
-
# 4. Posterior mean (expected risk)
|
| 212 |
posterior_mean = alpha_n / (alpha_n + beta_n)
|
| 213 |
-
|
| 214 |
-
# 5. Incorporate context as likelihood adjustment
|
| 215 |
context_multiplier = self._context_likelihood(context)
|
| 216 |
|
| 217 |
-
# 6. Final risk score (posterior predictive)
|
| 218 |
risk_score = posterior_mean * context_multiplier
|
| 219 |
risk_score = min(0.99, max(0.01, risk_score))
|
| 220 |
|
| 221 |
-
# 7. 95% credible interval (approximation)
|
| 222 |
variance = (alpha_n * beta_n) / ((alpha_n + beta_n)**2 * (alpha_n + beta_n + 1))
|
| 223 |
std_dev = variance ** 0.5
|
| 224 |
ci_lower = max(0.01, posterior_mean - 1.96 * std_dev)
|
| 225 |
ci_upper = min(0.99, posterior_mean + 1.96 * std_dev)
|
| 226 |
|
| 227 |
-
# 8. Risk level
|
| 228 |
if risk_score > 0.8:
|
| 229 |
risk_level = RiskLevel.CRITICAL
|
| 230 |
elif risk_score > 0.6:
|
|
@@ -289,11 +301,10 @@ class BayesianRiskEngine:
|
|
| 289 |
|
| 290 |
# ============== POLICY ENGINE ==============
|
| 291 |
class PolicyEngine:
|
| 292 |
-
# ... (unchanged) ...
|
| 293 |
def __init__(self):
|
| 294 |
self.config = {
|
| 295 |
-
"confidence_threshold": settings.
|
| 296 |
-
"max_autonomous_risk": settings.
|
| 297 |
"risk_thresholds": {
|
| 298 |
RiskLevel.LOW: 0.7,
|
| 299 |
RiskLevel.MEDIUM: 0.5,
|
|
@@ -318,7 +329,6 @@ class PolicyEngine:
|
|
| 318 |
action: str,
|
| 319 |
risk: Dict[str, Any],
|
| 320 |
confidence: float) -> Dict[str, Any]:
|
| 321 |
-
# ... unchanged ...
|
| 322 |
gates = []
|
| 323 |
|
| 324 |
# Gate 1: Confidence threshold
|
|
@@ -387,7 +397,6 @@ class PolicyEngine:
|
|
| 387 |
"type": "license"
|
| 388 |
})
|
| 389 |
|
| 390 |
-
# Overall decision
|
| 391 |
all_passed = all(g["passed"] for g in gates)
|
| 392 |
|
| 393 |
if not all_passed:
|
|
@@ -416,9 +425,8 @@ class PolicyEngine:
|
|
| 416 |
|
| 417 |
# ============== RAG MEMORY WITH PERSISTENCE ==============
|
| 418 |
class RAGMemory:
|
| 419 |
-
# ... (unchanged except error handling) ...
|
| 420 |
def __init__(self):
|
| 421 |
-
self.db_path = f"{settings.
|
| 422 |
self._init_db()
|
| 423 |
self.embedding_cache = {}
|
| 424 |
|
|
@@ -472,7 +480,6 @@ class RAGMemory:
|
|
| 472 |
conn.close()
|
| 473 |
|
| 474 |
def _simple_embedding(self, text: str) -> List[float]:
|
| 475 |
-
# ... unchanged ...
|
| 476 |
if text in self.embedding_cache:
|
| 477 |
return self.embedding_cache[text]
|
| 478 |
|
|
@@ -592,19 +599,18 @@ class RAGMemory:
|
|
| 592 |
return signal
|
| 593 |
|
| 594 |
def _notify_sales_team(self, signal: Dict):
|
| 595 |
-
if settings.
|
| 596 |
try:
|
| 597 |
-
requests.post(settings.
|
| 598 |
"text": f"π¨ *Enterprise Lead Signal*\n"
|
| 599 |
f"Type: {signal['signal_type']}\n"
|
| 600 |
f"Action: {signal['action']}\n"
|
| 601 |
f"Risk Score: {signal['risk_score']:.2f}\n"
|
| 602 |
f"Time: {signal['timestamp']}\n"
|
| 603 |
-
f"Contact: {settings.
|
| 604 |
}, timeout=5)
|
| 605 |
except requests.RequestException as e:
|
| 606 |
logger.error(f"Slack notification failed: {e}")
|
| 607 |
-
# Email via SendGrid (if configured) could be added similarly
|
| 608 |
|
| 609 |
def get_uncontacted_signals(self) -> List[Dict]:
|
| 610 |
try:
|
|
@@ -641,8 +647,7 @@ class RAGMemory:
|
|
| 641 |
security = HTTPBearer()
|
| 642 |
|
| 643 |
async def verify_api_key(credentials: HTTPAuthorizationCredentials = Depends(security)):
|
| 644 |
-
|
| 645 |
-
if credentials.credentials != settings.API_KEY:
|
| 646 |
raise HTTPException(
|
| 647 |
status_code=status.HTTP_403_FORBIDDEN,
|
| 648 |
detail="Invalid API key"
|
|
@@ -704,7 +709,7 @@ app = FastAPI(
|
|
| 704 |
description="Real ARF OSS components for enterprise lead generation",
|
| 705 |
contact={
|
| 706 |
"name": "ARF Sales",
|
| 707 |
-
"email": settings.
|
| 708 |
}
|
| 709 |
)
|
| 710 |
|
|
@@ -760,7 +765,6 @@ async def evaluate_action(request: ActionRequest):
|
|
| 760 |
Real ARF OSS evaluation pipeline (protected)
|
| 761 |
"""
|
| 762 |
try:
|
| 763 |
-
# Build context
|
| 764 |
context = {
|
| 765 |
"environment": "production",
|
| 766 |
"user_role": request.user_role,
|
|
@@ -768,23 +772,19 @@ async def evaluate_action(request: ActionRequest):
|
|
| 768 |
"requires_human": request.requiresHuman
|
| 769 |
}
|
| 770 |
|
| 771 |
-
# 1. Bayesian risk assessment
|
| 772 |
risk = risk_engine.calculate_posterior(
|
| 773 |
action_text=request.proposedAction,
|
| 774 |
context=context
|
| 775 |
)
|
| 776 |
|
| 777 |
-
# 2. Policy evaluation
|
| 778 |
policy = policy_engine.evaluate(
|
| 779 |
action=request.proposedAction,
|
| 780 |
risk=risk,
|
| 781 |
confidence=request.confidenceScore
|
| 782 |
)
|
| 783 |
|
| 784 |
-
# 3. RAG memory recall
|
| 785 |
similar = memory.find_similar(request.proposedAction, limit=3)
|
| 786 |
|
| 787 |
-
# 4. Track enterprise signals
|
| 788 |
if not policy["allowed"] and risk["score"] > 0.7:
|
| 789 |
memory.track_enterprise_signal(
|
| 790 |
signal_type=LeadSignal.HIGH_RISK_BLOCKED,
|
|
@@ -805,7 +805,6 @@ async def evaluate_action(request: ActionRequest):
|
|
| 805 |
metadata={"similar_count": len(similar)}
|
| 806 |
)
|
| 807 |
|
| 808 |
-
# 5. Store in memory
|
| 809 |
memory.store_incident(
|
| 810 |
action=request.proposedAction,
|
| 811 |
risk_score=risk["score"],
|
|
@@ -815,7 +814,6 @@ async def evaluate_action(request: ActionRequest):
|
|
| 815 |
gates=policy["gates"]
|
| 816 |
)
|
| 817 |
|
| 818 |
-
# 6. Format gates for response
|
| 819 |
gates = []
|
| 820 |
for g in policy["gates"]:
|
| 821 |
gates.append(GateResult(
|
|
@@ -828,7 +826,6 @@ async def evaluate_action(request: ActionRequest):
|
|
| 828 |
metadata=g.get("metadata")
|
| 829 |
))
|
| 830 |
|
| 831 |
-
# 7. Build execution ladder
|
| 832 |
execution_ladder = {
|
| 833 |
"levels": [
|
| 834 |
{"name": "AUTONOMOUS_LOW", "required": gates[0].passed and gates[1].passed},
|
|
@@ -888,31 +885,24 @@ async def get_enterprise_signals(contacted: bool = False):
|
|
| 888 |
|
| 889 |
@app.post("/api/v1/enterprise/signals/{signal_id}/contact", dependencies=[Depends(verify_api_key)])
|
| 890 |
async def mark_signal_contacted(signal_id: str):
|
| 891 |
-
"""Mark a lead signal as contacted (protected)"""
|
| 892 |
memory.mark_contacted(signal_id)
|
| 893 |
return {"status": "success", "message": "Signal marked as contacted"}
|
| 894 |
|
| 895 |
@app.get("/api/v1/memory/similar", dependencies=[Depends(verify_api_key)])
|
| 896 |
async def get_similar_actions(action: str, limit: int = 5):
|
| 897 |
-
"""Find similar historical actions (protected)"""
|
| 898 |
similar = memory.find_similar(action, limit=limit)
|
| 899 |
return {"similar": similar, "count": len(similar)}
|
| 900 |
|
| 901 |
@app.post("/api/v1/feedback", dependencies=[Depends(verify_api_key)])
|
| 902 |
async def record_outcome(action: str, success: bool):
|
| 903 |
-
"""
|
| 904 |
-
Record actual outcome for Bayesian updating (protected)
|
| 905 |
-
"""
|
| 906 |
risk_engine.record_outcome(action, success)
|
| 907 |
return {"status": "success", "message": "Outcome recorded"}
|
| 908 |
|
| 909 |
# ============== GRADIO LEAD GENERATION UI ==============
|
| 910 |
def create_lead_gen_ui():
|
| 911 |
"""Professional lead generation interface (no auth needed for UI)"""
|
| 912 |
-
# ... (unchanged) ...
|
| 913 |
with gr.Blocks(title="ARF OSS - Enterprise Reliability Intelligence") as ui:
|
| 914 |
|
| 915 |
-
# Header
|
| 916 |
gr.HTML(f"""
|
| 917 |
<div style="padding: 2rem; border-radius: 1rem; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; text-align: center;">
|
| 918 |
<h1 style="font-size: 3em; margin-bottom: 0.5rem;">π€ ARF OSS v3.3.9</h1>
|
|
@@ -926,7 +916,6 @@ def create_lead_gen_ui():
|
|
| 926 |
</div>
|
| 927 |
""")
|
| 928 |
|
| 929 |
-
# Value Proposition
|
| 930 |
with gr.Row():
|
| 931 |
with gr.Column():
|
| 932 |
gr.HTML("""
|
|
@@ -939,7 +928,6 @@ def create_lead_gen_ui():
|
|
| 939 |
</div>
|
| 940 |
""")
|
| 941 |
|
| 942 |
-
# Features Grid
|
| 943 |
with gr.Row():
|
| 944 |
with gr.Column():
|
| 945 |
gr.HTML("""
|
|
@@ -972,7 +960,6 @@ def create_lead_gen_ui():
|
|
| 972 |
</div>
|
| 973 |
""")
|
| 974 |
|
| 975 |
-
# Live Demo Stats
|
| 976 |
demo_stats = gr.JSON(
|
| 977 |
label="π Live Demo Statistics",
|
| 978 |
value={
|
|
@@ -983,7 +970,6 @@ def create_lead_gen_ui():
|
|
| 983 |
}
|
| 984 |
)
|
| 985 |
|
| 986 |
-
# CTA Section
|
| 987 |
gr.HTML(f"""
|
| 988 |
<div style="margin: 3rem 0; padding: 3rem; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
| 989 |
border-radius: 1rem; text-align: center; color: white;">
|
|
@@ -993,11 +979,11 @@ def create_lead_gen_ui():
|
|
| 993 |
</p>
|
| 994 |
|
| 995 |
<div style="display: flex; gap: 1rem; justify-content: center; flex-wrap: wrap;">
|
| 996 |
-
<a href="mailto:{settings.
|
| 997 |
style="background: white; color: #667eea; padding: 1rem 2rem; border-radius: 2rem; font-weight: bold; text-decoration: none; display: inline-block; margin: 0.5rem;">
|
| 998 |
-
π§ {settings.
|
| 999 |
</a>
|
| 1000 |
-
<a href="{settings.
|
| 1001 |
style="background: #FFD700; color: #333; padding: 1rem 2rem; border-radius: 2rem; font-weight: bold; text-decoration: none; display: inline-block; margin: 0.5rem;">
|
| 1002 |
π
Schedule Technical Demo
|
| 1003 |
</a>
|
|
@@ -1010,11 +996,10 @@ def create_lead_gen_ui():
|
|
| 1010 |
</div>
|
| 1011 |
""")
|
| 1012 |
|
| 1013 |
-
# Footer
|
| 1014 |
gr.HTML(f"""
|
| 1015 |
<div style="text-align: center; padding: 2rem; color: #666; border-top: 1px solid #eee;">
|
| 1016 |
<p>
|
| 1017 |
-
π§ <a href="mailto:{settings.
|
| 1018 |
π <a href="https://github.com/petterjuan/agentic-reliability-framework" style="color: #667eea;">GitHub</a>
|
| 1019 |
</p>
|
| 1020 |
<p style="font-size: 0.9rem;">
|
|
@@ -1046,9 +1031,9 @@ if __name__ == "__main__":
|
|
| 1046 |
|
| 1047 |
logger.info("="*60)
|
| 1048 |
logger.info("π ARF OSS v3.3.9 Starting")
|
| 1049 |
-
logger.info(f"π Data directory: {settings.
|
| 1050 |
-
logger.info(f"π§ Lead email: {settings.
|
| 1051 |
-
logger.info(f"π API Key: {settings.
|
| 1052 |
logger.info(f"π Serving at: http://0.0.0.0:{port}")
|
| 1053 |
logger.info("="*60)
|
| 1054 |
|
|
|
|
| 18 |
import asyncio
|
| 19 |
import sqlite3
|
| 20 |
import requests
|
| 21 |
+
import fcntl # <-- NEW: for file locking
|
| 22 |
+
import sys
|
| 23 |
from datetime import datetime, timedelta
|
| 24 |
from typing import Dict, List, Optional, Any, Tuple
|
| 25 |
from contextlib import contextmanager
|
|
|
|
| 33 |
from fastapi import FastAPI, HTTPException, Depends, status
|
| 34 |
from fastapi.middleware.cors import CORSMiddleware
|
| 35 |
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
|
| 36 |
+
from pydantic import BaseModel, Field, field_validator, ConfigDict # <-- NEW: ConfigDict
|
| 37 |
+
from pydantic_settings import BaseSettings, SettingsConfigDict # <-- NEW: SettingsConfigDict
|
| 38 |
from gradio import mount_gradio_app
|
| 39 |
|
| 40 |
+
# ============== SINGLE INSTANCE LOCK ==============
|
| 41 |
+
LOCK_FILE = '/tmp/arf_app.lock'
|
| 42 |
+
try:
|
| 43 |
+
lock_fd = open(LOCK_FILE, 'w')
|
| 44 |
+
fcntl.flock(lock_fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
| 45 |
+
except (IOError, OSError):
|
| 46 |
+
print("Another instance is already running. Exiting.")
|
| 47 |
+
sys.exit(1)
|
| 48 |
+
# ==================================================
|
| 49 |
+
|
| 50 |
+
# ============== CONFIGURATION (Pydantic V2) ==============
|
| 51 |
class Settings(BaseSettings):
|
| 52 |
+
"""Centralized configuration using Pydantic Settings V2"""
|
| 53 |
|
| 54 |
+
# Hugging Face settings (aliased to match expected env vars)
|
| 55 |
+
hf_space_id: str = Field(default='local', alias='SPACE_ID')
|
| 56 |
+
hf_token: str = Field(default='', alias='HF_TOKEN')
|
| 57 |
|
| 58 |
# Persistence - HF persistent storage
|
| 59 |
+
data_dir: str = Field(
|
| 60 |
+
default='/data' if os.path.exists('/data') else './data',
|
| 61 |
+
alias='DATA_DIR'
|
| 62 |
+
)
|
| 63 |
|
| 64 |
# Lead generation
|
| 65 |
+
lead_email: str = "petter2025us@outlook.com"
|
| 66 |
+
calendly_url: str = "https://calendly.com/petter2025us/arf-demo"
|
| 67 |
|
| 68 |
# Webhook for lead alerts (set in HF secrets)
|
| 69 |
+
slack_webhook: str = Field(default='', alias='SLACK_WEBHOOK')
|
| 70 |
+
sendgrid_api_key: str = Field(default='', alias='SENDGRID_API_KEY')
|
| 71 |
|
| 72 |
# Security
|
| 73 |
+
api_key: str = Field(
|
| 74 |
+
default_factory=lambda: str(uuid.uuid4()),
|
| 75 |
+
alias='ARF_API_KEY'
|
| 76 |
+
)
|
| 77 |
|
| 78 |
# ARF defaults
|
| 79 |
+
default_confidence_threshold: float = 0.9
|
| 80 |
+
default_max_risk: str = "MEDIUM"
|
| 81 |
|
| 82 |
+
# Pydantic V2 configuration
|
| 83 |
+
model_config = SettingsConfigDict(
|
| 84 |
+
populate_by_name=True, # allows use of field names or aliases
|
| 85 |
+
extra='ignore', # ignore extra env vars
|
| 86 |
+
env_prefix='', # no prefix
|
| 87 |
+
case_sensitive=False # case-insensitive matching
|
| 88 |
+
)
|
| 89 |
|
| 90 |
def __init__(self, **kwargs):
|
| 91 |
super().__init__(**kwargs)
|
| 92 |
# Ensure data directory exists
|
| 93 |
+
os.makedirs(self.data_dir, exist_ok=True)
|
| 94 |
|
| 95 |
settings = Settings()
|
| 96 |
|
|
|
|
| 99 |
level=logging.INFO,
|
| 100 |
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
| 101 |
handlers=[
|
| 102 |
+
logging.FileHandler(f'{settings.data_dir}/arf.log'),
|
| 103 |
logging.StreamHandler()
|
| 104 |
]
|
| 105 |
)
|
|
|
|
| 145 |
'default': {'alpha': 2.0, 'beta': 5.0}
|
| 146 |
}
|
| 147 |
|
| 148 |
+
self.evidence_db = f"{settings.data_dir}/evidence.db"
|
| 149 |
self._init_db()
|
| 150 |
|
| 151 |
def _init_db(self):
|
|
|
|
| 212 |
return (row[0] or 0, row[1] or 0) if row else (0, 0)
|
| 213 |
except sqlite3.Error as e:
|
| 214 |
logger.error(f"Failed to retrieve evidence: {e}")
|
| 215 |
+
return (0, 0)
|
| 216 |
|
| 217 |
def calculate_posterior(self,
|
| 218 |
action_text: str,
|
| 219 |
context: Dict[str, Any]) -> Dict[str, Any]:
|
|
|
|
|
|
|
| 220 |
action_type = self.classify_action(action_text)
|
| 221 |
alpha0, beta0 = self.get_prior(action_type)
|
| 222 |
|
|
|
|
| 223 |
action_hash = hashlib.sha256(action_text.encode()).hexdigest()
|
| 224 |
successes, trials = self.get_evidence(action_hash)
|
| 225 |
|
|
|
|
| 226 |
alpha_n = alpha0 + successes
|
| 227 |
beta_n = beta0 + (trials - successes)
|
| 228 |
|
|
|
|
| 229 |
posterior_mean = alpha_n / (alpha_n + beta_n)
|
|
|
|
|
|
|
| 230 |
context_multiplier = self._context_likelihood(context)
|
| 231 |
|
|
|
|
| 232 |
risk_score = posterior_mean * context_multiplier
|
| 233 |
risk_score = min(0.99, max(0.01, risk_score))
|
| 234 |
|
|
|
|
| 235 |
variance = (alpha_n * beta_n) / ((alpha_n + beta_n)**2 * (alpha_n + beta_n + 1))
|
| 236 |
std_dev = variance ** 0.5
|
| 237 |
ci_lower = max(0.01, posterior_mean - 1.96 * std_dev)
|
| 238 |
ci_upper = min(0.99, posterior_mean + 1.96 * std_dev)
|
| 239 |
|
|
|
|
| 240 |
if risk_score > 0.8:
|
| 241 |
risk_level = RiskLevel.CRITICAL
|
| 242 |
elif risk_score > 0.6:
|
|
|
|
| 301 |
|
| 302 |
# ============== POLICY ENGINE ==============
|
| 303 |
class PolicyEngine:
|
|
|
|
| 304 |
def __init__(self):
|
| 305 |
self.config = {
|
| 306 |
+
"confidence_threshold": settings.default_confidence_threshold,
|
| 307 |
+
"max_autonomous_risk": settings.default_max_risk,
|
| 308 |
"risk_thresholds": {
|
| 309 |
RiskLevel.LOW: 0.7,
|
| 310 |
RiskLevel.MEDIUM: 0.5,
|
|
|
|
| 329 |
action: str,
|
| 330 |
risk: Dict[str, Any],
|
| 331 |
confidence: float) -> Dict[str, Any]:
|
|
|
|
| 332 |
gates = []
|
| 333 |
|
| 334 |
# Gate 1: Confidence threshold
|
|
|
|
| 397 |
"type": "license"
|
| 398 |
})
|
| 399 |
|
|
|
|
| 400 |
all_passed = all(g["passed"] for g in gates)
|
| 401 |
|
| 402 |
if not all_passed:
|
|
|
|
| 425 |
|
| 426 |
# ============== RAG MEMORY WITH PERSISTENCE ==============
|
| 427 |
class RAGMemory:
|
|
|
|
| 428 |
def __init__(self):
|
| 429 |
+
self.db_path = f"{settings.data_dir}/memory.db"
|
| 430 |
self._init_db()
|
| 431 |
self.embedding_cache = {}
|
| 432 |
|
|
|
|
| 480 |
conn.close()
|
| 481 |
|
| 482 |
def _simple_embedding(self, text: str) -> List[float]:
|
|
|
|
| 483 |
if text in self.embedding_cache:
|
| 484 |
return self.embedding_cache[text]
|
| 485 |
|
|
|
|
| 599 |
return signal
|
| 600 |
|
| 601 |
def _notify_sales_team(self, signal: Dict):
|
| 602 |
+
if settings.slack_webhook:
|
| 603 |
try:
|
| 604 |
+
requests.post(settings.slack_webhook, json={
|
| 605 |
"text": f"π¨ *Enterprise Lead Signal*\n"
|
| 606 |
f"Type: {signal['signal_type']}\n"
|
| 607 |
f"Action: {signal['action']}\n"
|
| 608 |
f"Risk Score: {signal['risk_score']:.2f}\n"
|
| 609 |
f"Time: {signal['timestamp']}\n"
|
| 610 |
+
f"Contact: {settings.lead_email}"
|
| 611 |
}, timeout=5)
|
| 612 |
except requests.RequestException as e:
|
| 613 |
logger.error(f"Slack notification failed: {e}")
|
|
|
|
| 614 |
|
| 615 |
def get_uncontacted_signals(self) -> List[Dict]:
|
| 616 |
try:
|
|
|
|
| 647 |
security = HTTPBearer()
|
| 648 |
|
| 649 |
async def verify_api_key(credentials: HTTPAuthorizationCredentials = Depends(security)):
|
| 650 |
+
if credentials.credentials != settings.api_key:
|
|
|
|
| 651 |
raise HTTPException(
|
| 652 |
status_code=status.HTTP_403_FORBIDDEN,
|
| 653 |
detail="Invalid API key"
|
|
|
|
| 709 |
description="Real ARF OSS components for enterprise lead generation",
|
| 710 |
contact={
|
| 711 |
"name": "ARF Sales",
|
| 712 |
+
"email": settings.lead_email,
|
| 713 |
}
|
| 714 |
)
|
| 715 |
|
|
|
|
| 765 |
Real ARF OSS evaluation pipeline (protected)
|
| 766 |
"""
|
| 767 |
try:
|
|
|
|
| 768 |
context = {
|
| 769 |
"environment": "production",
|
| 770 |
"user_role": request.user_role,
|
|
|
|
| 772 |
"requires_human": request.requiresHuman
|
| 773 |
}
|
| 774 |
|
|
|
|
| 775 |
risk = risk_engine.calculate_posterior(
|
| 776 |
action_text=request.proposedAction,
|
| 777 |
context=context
|
| 778 |
)
|
| 779 |
|
|
|
|
| 780 |
policy = policy_engine.evaluate(
|
| 781 |
action=request.proposedAction,
|
| 782 |
risk=risk,
|
| 783 |
confidence=request.confidenceScore
|
| 784 |
)
|
| 785 |
|
|
|
|
| 786 |
similar = memory.find_similar(request.proposedAction, limit=3)
|
| 787 |
|
|
|
|
| 788 |
if not policy["allowed"] and risk["score"] > 0.7:
|
| 789 |
memory.track_enterprise_signal(
|
| 790 |
signal_type=LeadSignal.HIGH_RISK_BLOCKED,
|
|
|
|
| 805 |
metadata={"similar_count": len(similar)}
|
| 806 |
)
|
| 807 |
|
|
|
|
| 808 |
memory.store_incident(
|
| 809 |
action=request.proposedAction,
|
| 810 |
risk_score=risk["score"],
|
|
|
|
| 814 |
gates=policy["gates"]
|
| 815 |
)
|
| 816 |
|
|
|
|
| 817 |
gates = []
|
| 818 |
for g in policy["gates"]:
|
| 819 |
gates.append(GateResult(
|
|
|
|
| 826 |
metadata=g.get("metadata")
|
| 827 |
))
|
| 828 |
|
|
|
|
| 829 |
execution_ladder = {
|
| 830 |
"levels": [
|
| 831 |
{"name": "AUTONOMOUS_LOW", "required": gates[0].passed and gates[1].passed},
|
|
|
|
| 885 |
|
| 886 |
@app.post("/api/v1/enterprise/signals/{signal_id}/contact", dependencies=[Depends(verify_api_key)])
|
| 887 |
async def mark_signal_contacted(signal_id: str):
|
|
|
|
| 888 |
memory.mark_contacted(signal_id)
|
| 889 |
return {"status": "success", "message": "Signal marked as contacted"}
|
| 890 |
|
| 891 |
@app.get("/api/v1/memory/similar", dependencies=[Depends(verify_api_key)])
|
| 892 |
async def get_similar_actions(action: str, limit: int = 5):
|
|
|
|
| 893 |
similar = memory.find_similar(action, limit=limit)
|
| 894 |
return {"similar": similar, "count": len(similar)}
|
| 895 |
|
| 896 |
@app.post("/api/v1/feedback", dependencies=[Depends(verify_api_key)])
|
| 897 |
async def record_outcome(action: str, success: bool):
|
|
|
|
|
|
|
|
|
|
| 898 |
risk_engine.record_outcome(action, success)
|
| 899 |
return {"status": "success", "message": "Outcome recorded"}
|
| 900 |
|
| 901 |
# ============== GRADIO LEAD GENERATION UI ==============
|
| 902 |
def create_lead_gen_ui():
|
| 903 |
"""Professional lead generation interface (no auth needed for UI)"""
|
|
|
|
| 904 |
with gr.Blocks(title="ARF OSS - Enterprise Reliability Intelligence") as ui:
|
| 905 |
|
|
|
|
| 906 |
gr.HTML(f"""
|
| 907 |
<div style="padding: 2rem; border-radius: 1rem; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; text-align: center;">
|
| 908 |
<h1 style="font-size: 3em; margin-bottom: 0.5rem;">π€ ARF OSS v3.3.9</h1>
|
|
|
|
| 916 |
</div>
|
| 917 |
""")
|
| 918 |
|
|
|
|
| 919 |
with gr.Row():
|
| 920 |
with gr.Column():
|
| 921 |
gr.HTML("""
|
|
|
|
| 928 |
</div>
|
| 929 |
""")
|
| 930 |
|
|
|
|
| 931 |
with gr.Row():
|
| 932 |
with gr.Column():
|
| 933 |
gr.HTML("""
|
|
|
|
| 960 |
</div>
|
| 961 |
""")
|
| 962 |
|
|
|
|
| 963 |
demo_stats = gr.JSON(
|
| 964 |
label="π Live Demo Statistics",
|
| 965 |
value={
|
|
|
|
| 970 |
}
|
| 971 |
)
|
| 972 |
|
|
|
|
| 973 |
gr.HTML(f"""
|
| 974 |
<div style="margin: 3rem 0; padding: 3rem; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
| 975 |
border-radius: 1rem; text-align: center; color: white;">
|
|
|
|
| 979 |
</p>
|
| 980 |
|
| 981 |
<div style="display: flex; gap: 1rem; justify-content: center; flex-wrap: wrap;">
|
| 982 |
+
<a href="mailto:{settings.lead_email}?subject=ARF%20Enterprise%20Demo%20Request&body=I%20saw%20the%20real%20ARF%20OSS%20demo%20and%20would%20like%20to%20discuss%20Enterprise%20capabilities."
|
| 983 |
style="background: white; color: #667eea; padding: 1rem 2rem; border-radius: 2rem; font-weight: bold; text-decoration: none; display: inline-block; margin: 0.5rem;">
|
| 984 |
+
π§ {settings.lead_email}
|
| 985 |
</a>
|
| 986 |
+
<a href="{settings.calendly_url}" target="_blank"
|
| 987 |
style="background: #FFD700; color: #333; padding: 1rem 2rem; border-radius: 2rem; font-weight: bold; text-decoration: none; display: inline-block; margin: 0.5rem;">
|
| 988 |
π
Schedule Technical Demo
|
| 989 |
</a>
|
|
|
|
| 996 |
</div>
|
| 997 |
""")
|
| 998 |
|
|
|
|
| 999 |
gr.HTML(f"""
|
| 1000 |
<div style="text-align: center; padding: 2rem; color: #666; border-top: 1px solid #eee;">
|
| 1001 |
<p>
|
| 1002 |
+
π§ <a href="mailto:{settings.lead_email}" style="color: #667eea;">{settings.lead_email}</a> β’
|
| 1003 |
π <a href="https://github.com/petterjuan/agentic-reliability-framework" style="color: #667eea;">GitHub</a>
|
| 1004 |
</p>
|
| 1005 |
<p style="font-size: 0.9rem;">
|
|
|
|
| 1031 |
|
| 1032 |
logger.info("="*60)
|
| 1033 |
logger.info("π ARF OSS v3.3.9 Starting")
|
| 1034 |
+
logger.info(f"π Data directory: {settings.data_dir}")
|
| 1035 |
+
logger.info(f"π§ Lead email: {settings.lead_email}")
|
| 1036 |
+
logger.info(f"π API Key: {settings.api_key[:8]}... (set in HF secrets)")
|
| 1037 |
logger.info(f"π Serving at: http://0.0.0.0:{port}")
|
| 1038 |
logger.info("="*60)
|
| 1039 |
|