petter2025 commited on
Commit
6da229d
·
verified ·
1 Parent(s): a22ea2e

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +128 -18
app.py CHANGED
@@ -2,9 +2,9 @@
2
  import logging
3
  import uuid
4
  from datetime import datetime, timezone
5
- from typing import Dict, Optional
6
 
7
- from fastapi import FastAPI
8
  from fastapi.middleware.cors import CORSMiddleware
9
  from fastapi.openapi.docs import get_swagger_ui_html, get_redoc_html
10
  from fastapi.responses import RedirectResponse
@@ -16,6 +16,21 @@ from agentic_reliability_framework.core.governance.risk_engine import RiskEngine
16
  from agentic_reliability_framework.runtime.memory import create_faiss_index, RAGGraphMemory
17
  from agentic_reliability_framework.runtime.memory.constants import MemoryConstants
18
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  logging.basicConfig(level=logging.INFO)
20
  logger = logging.getLogger(__name__)
21
 
@@ -35,7 +50,20 @@ risk_engine = RiskEngine()
35
  faiss_index = create_faiss_index(dim=MemoryConstants.VECTOR_DIM)
36
  memory = RAGGraphMemory(faiss_index)
37
 
38
- # In‑memory storage for demo purposes (replace with a real DB later)
 
 
 
 
 
 
 
 
 
 
 
 
 
39
  decision_history = []
40
 
41
  # ========================= PYDANTIC MODELS =========================
@@ -107,21 +135,105 @@ async def get_history():
107
  """Return the last 10 decisions."""
108
  return decision_history[-10:]
109
 
110
- @fastapi_app.post("/v1/incidents/evaluate", response_model=EvaluateResponse)
111
  async def evaluate_incident(request: EvaluateRequest):
112
  """
113
- Evaluate an incident and return a risk score with explainability.
114
- This is a placeholder replace with actual call to your risk engine.
 
115
  """
116
- # For now, return a dummy response
117
- return EvaluateResponse(
118
- risk_score=0.23,
119
- base_risk=0.15,
120
- memory_risk=0.3,
121
- weight=0.5,
122
- similar_events=[],
123
- confidence=0.9
124
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
125
 
126
  @fastapi_app.post("/v1/feedback")
127
  async def record_outcome(decision_id: str, success: bool):
@@ -225,10 +337,9 @@ with gr.Blocks(title="ARF v4 Demo") as demo:
225
  history_btn.click(fn=lambda: decision_history[-10:], outputs=decision_output)
226
 
227
  # ========================= Mount Gradio and Add Documentation Routes =========================
228
- # Mount Gradio at "/api" – this means Gradio will handle all requests starting with "/api".
229
  app = gr.mount_gradio_app(fastapi_app, demo, path="/api")
230
 
231
- # Add documentation routes at "/docs" (outside the Gradio mount path) to avoid conflict.
232
  @app.get("/docs", include_in_schema=False)
233
  async def swagger_ui():
234
  return get_swagger_ui_html(
@@ -247,7 +358,6 @@ async def redoc_ui():
247
  async def openapi():
248
  return fastapi_app.openapi()
249
 
250
- # Optional redirect from /api/docs to /docs for backward compatibility.
251
  @app.get("/api/docs", include_in_schema=False)
252
  async def redirect_docs():
253
  return RedirectResponse(url="/docs")
 
2
  import logging
3
  import uuid
4
  from datetime import datetime, timezone
5
+ from typing import Dict, Optional, List
6
 
7
+ from fastapi import FastAPI, HTTPException
8
  from fastapi.middleware.cors import CORSMiddleware
9
  from fastapi.openapi.docs import get_swagger_ui_html, get_redoc_html
10
  from fastapi.responses import RedirectResponse
 
16
  from agentic_reliability_framework.runtime.memory import create_faiss_index, RAGGraphMemory
17
  from agentic_reliability_framework.runtime.memory.constants import MemoryConstants
18
 
19
+ # Additional imports for governance loop and healing intent
20
+ from agentic_reliability_framework.core.governance.governance_loop import GovernanceLoop
21
+ from agentic_reliability_framework.core.governance.policy_engine import PolicyEngine
22
+ from agentic_reliability_framework.core.governance.cost_estimator import CostEstimator
23
+ from agentic_reliability_framework.core.governance.intents import (
24
+ DeployConfigurationIntent,
25
+ Environment,
26
+ InfrastructureIntent,
27
+ )
28
+ from agentic_reliability_framework.core.governance.healing_intent import (
29
+ HealingIntent,
30
+ HealingIntentSerializer,
31
+ RecommendedAction,
32
+ )
33
+
34
  logging.basicConfig(level=logging.INFO)
35
  logger = logging.getLogger(__name__)
36
 
 
50
  faiss_index = create_faiss_index(dim=MemoryConstants.VECTOR_DIM)
51
  memory = RAGGraphMemory(faiss_index)
52
 
53
+ # Create policy engine and cost estimator (use default implementations)
54
+ policy_engine = PolicyEngine() # Will need policies loaded if any
55
+ cost_estimator = CostEstimator() # Default estimator
56
+
57
+ # Initialize the governance loop
58
+ governance_loop = GovernanceLoop(
59
+ policy_engine=policy_engine,
60
+ cost_estimator=cost_estimator,
61
+ risk_engine=risk_engine,
62
+ memory=memory,
63
+ enable_epistemic=True,
64
+ )
65
+
66
+ # In‑memory storage for demo purposes (used by /v1/history and /v1/feedback)
67
  decision_history = []
68
 
69
  # ========================= PYDANTIC MODELS =========================
 
135
  """Return the last 10 decisions."""
136
  return decision_history[-10:]
137
 
138
+ @fastapi_app.post("/v1/incidents/evaluate")
139
  async def evaluate_incident(request: EvaluateRequest):
140
  """
141
+ Evaluate an incident by converting it into an infrastructure intent
142
+ and running it through the full governance loop. Returns a complete
143
+ HealingIntent with risk assessment, similar incidents, and recommended actions.
144
  """
145
+ try:
146
+ # Map the incident to a DeployConfigurationIntent (as an example)
147
+ # You can change the mapping logic based on your needs
148
+ intent = DeployConfigurationIntent(
149
+ service_name=request.service_name,
150
+ change_scope="single_instance", # default
151
+ deployment_target=Environment.DEV, # assume dev for now
152
+ configuration=request.metrics,
153
+ requester="system",
154
+ provenance={"source": "incident_evaluation", "event_type": request.event_type, "severity": request.severity},
155
+ )
156
+
157
+ # Run through governance loop
158
+ healing_intent: HealingIntent = governance_loop.run(
159
+ intent=intent,
160
+ context={
161
+ "incident_metadata": {
162
+ "service_name": request.service_name,
163
+ "event_type": request.event_type,
164
+ "severity": request.severity,
165
+ "metrics": request.metrics,
166
+ }
167
+ },
168
+ )
169
+
170
+ # Serialize the healing intent to a dictionary suitable for JSON response
171
+ # We'll use the full dict (including OSS context) for the frontend.
172
+ response_dict = healing_intent.to_dict(include_oss_context=True)
173
+
174
+ # Add any extra fields expected by the frontend that might not be in HealingIntent
175
+ # The frontend's EvaluateResponse includes fields like base_risk, weight, etc.
176
+ # We can compute these from the risk contributions if needed.
177
+ # For simplicity, we'll map the healing intent fields to the expected shape.
178
+ # The frontend expects:
179
+ # - risk_score (already present)
180
+ # - epistemic_uncertainty (from confidence_distribution)
181
+ # - confidence_interval (from confidence_distribution)
182
+ # - risk_contributions (from risk_factors)
183
+ # - similar_incidents (already present)
184
+ # - recommended_actions (from action or alternative_actions)
185
+ # - explanation (from justification)
186
+ # - policy_violations (already present)
187
+ # - requires_escalation (based on recommended_action)
188
+ #
189
+ # We'll construct a response that matches the frontend's EvaluateResponse type.
190
+
191
+ # Compute confidence interval if confidence_distribution exists
192
+ confidence_interval = None
193
+ if healing_intent.confidence_distribution:
194
+ dist = healing_intent.confidence_distribution
195
+ confidence_interval = [dist.get("p5", 0.0), dist.get("p95", 1.0)]
196
+ else:
197
+ # Fallback based on risk_score (e.g., 90% CI width 0.1)
198
+ confidence_interval = [
199
+ max(0.0, healing_intent.risk_score - 0.05),
200
+ min(1.0, healing_intent.risk_score + 0.05),
201
+ ]
202
+
203
+ # Convert risk_factors to list of RiskContribution objects
204
+ risk_contributions = []
205
+ if healing_intent.risk_factors:
206
+ for factor, contribution in healing_intent.risk_factors.items():
207
+ risk_contributions.append({"factor": factor, "contribution": contribution})
208
+
209
+ # Convert similar_incidents (list of dicts) – already in correct format? The frontend expects
210
+ # each incident to have fields: incident_id, component, severity, timestamp, metrics, similarity_score, outcome_success.
211
+ # HealingIntent's similar_incidents might have different structure; we can pass as-is if matches.
212
+ # If not, we need to transform. We'll assume they are compatible or simply pass.
213
+
214
+ # Determine if escalation is required
215
+ requires_escalation = healing_intent.recommended_action == RecommendedAction.ESCALATE
216
+
217
+ # Build the response
218
+ response = {
219
+ "risk_score": healing_intent.risk_score,
220
+ "epistemic_uncertainty": healing_intent.confidence_distribution.get("std", 0.05) if healing_intent.confidence_distribution else 0.05,
221
+ "confidence_interval": confidence_interval,
222
+ "risk_contributions": risk_contributions,
223
+ "similar_incidents": healing_intent.similar_incidents or [],
224
+ "recommended_actions": healing_intent.alternative_actions or [],
225
+ "explanation": healing_intent.justification,
226
+ "policy_violations": healing_intent.policy_violations or [],
227
+ "requires_escalation": requires_escalation,
228
+ # Also include raw healing intent for debugging (optional)
229
+ "_full_healing_intent": healing_intent.to_dict(include_oss_context=False),
230
+ }
231
+
232
+ return response
233
+
234
+ except Exception as e:
235
+ logger.exception("Error in evaluate_incident")
236
+ raise HTTPException(status_code=500, detail=str(e))
237
 
238
  @fastapi_app.post("/v1/feedback")
239
  async def record_outcome(decision_id: str, success: bool):
 
337
  history_btn.click(fn=lambda: decision_history[-10:], outputs=decision_output)
338
 
339
  # ========================= Mount Gradio and Add Documentation Routes =========================
 
340
  app = gr.mount_gradio_app(fastapi_app, demo, path="/api")
341
 
342
+ # Add documentation routes at "/docs"
343
  @app.get("/docs", include_in_schema=False)
344
  async def swagger_ui():
345
  return get_swagger_ui_html(
 
358
  async def openapi():
359
  return fastapi_app.openapi()
360
 
 
361
  @app.get("/api/docs", include_in_schema=False)
362
  async def redirect_docs():
363
  return RedirectResponse(url="/docs")