Pontonkid commited on
Commit
439aa56
·
verified ·
1 Parent(s): 0755bae

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +297 -0
app.py ADDED
@@ -0,0 +1,297 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # agentops_space.py
2
+ import os
3
+ import threading
4
+ import tempfile
5
+ import json
6
+ import datetime
7
+ from pathlib import Path
8
+ import requests
9
+
10
+ # Gradio 6
11
+ import gradio as gr
12
+
13
+ # FastAPI + Uvicorn for internal MCP servers
14
+ from fastapi import FastAPI, UploadFile, File
15
+ from fastapi.responses import PlainTextResponse
16
+ import uvicorn
17
+
18
+ # PDF report
19
+ from reportlab.lib.pagesizes import letter
20
+ from reportlab.pdfgen import canvas
21
+
22
+ # -----------------------
23
+ # ENV VARS / CONFIG
24
+ # -----------------------
25
+ GEMINI_API_KEY = os.environ.get("GEMINI_API_KEY", "")
26
+ REPORT_DIR = Path("./reports")
27
+ REPORT_DIR.mkdir(exist_ok=True)
28
+
29
+ # MCP ports (internal)
30
+ FILE_SERVER_PORT = 8001
31
+ EXECUTOR_PORT = 8002
32
+ MEMORY_PORT = 8003
33
+ RAG_PORT = 8004 # optional
34
+
35
+ # -----------------------
36
+ # MCP SERVERS
37
+ # -----------------------
38
+
39
+ # --- File Server ---
40
+ file_app = FastAPI()
41
+ FILE_STORAGE = Path("file_storage")
42
+ FILE_STORAGE.mkdir(exist_ok=True)
43
+
44
+ @file_app.post("/upload")
45
+ async def upload_file(file: UploadFile = File(...)):
46
+ dest = FILE_STORAGE / file.filename
47
+ content = await file.read()
48
+ with open(dest, "wb") as f:
49
+ f.write(content)
50
+ return {"status":"ok","filename":file.filename,"path":str(dest)}
51
+
52
+ @file_app.get("/list")
53
+ def list_files():
54
+ return {"files": [p.name for p in FILE_STORAGE.iterdir() if p.is_file()]}
55
+
56
+ @file_app.get("/read/{name}")
57
+ def read_file(name: str):
58
+ fpath = FILE_STORAGE / name
59
+ if not fpath.exists():
60
+ return {"error":"not found"}
61
+ return {"filename": name, "content": fpath.read_text(encoding="utf-8")}
62
+
63
+ # --- Executor Server ---
64
+ executor_app = FastAPI()
65
+ EXECUTOR_DB = Path("executor_db")
66
+ EXECUTOR_DB.mkdir(exist_ok=True)
67
+
68
+ @executor_app.post("/apply")
69
+ def apply_patch(req: dict):
70
+ rec = {
71
+ "filename": req.get("filename"),
72
+ "action": req.get("action"),
73
+ "params": req.get("params", {}),
74
+ "timestamp": datetime.datetime.utcnow().isoformat()
75
+ }
76
+ fname = EXECUTOR_DB / f"{rec['filename']}.patchlog.json"
77
+ existing = []
78
+ if fname.exists():
79
+ existing = json.loads(fname.read_text())
80
+ existing.append(rec)
81
+ fname.write_text(json.dumps(existing, indent=2))
82
+ return {"status":"applied","record": rec}
83
+
84
+ # --- Memory Server ---
85
+ memory_app = FastAPI()
86
+ MEMORY_DB = Path("memory_db")
87
+ MEMORY_DB.mkdir(exist_ok=True)
88
+ MEMORY_FILE = MEMORY_DB / "memory.json"
89
+ if not MEMORY_FILE.exists():
90
+ MEMORY_FILE.write_text(json.dumps({}))
91
+
92
+ @memory_app.post("/set")
93
+ def set_memory(item: dict):
94
+ key = item.get("key")
95
+ value = item.get("value")
96
+ m = json.loads(MEMORY_FILE.read_text())
97
+ m[key] = value
98
+ MEMORY_FILE.write_text(json.dumps(m, indent=2))
99
+ return {"status":"ok","key":key}
100
+
101
+ @memory_app.get("/get/{key}")
102
+ def get_memory(key: str):
103
+ m = json.loads(MEMORY_FILE.read_text())
104
+ return {"value": m.get(key)}
105
+
106
+ # -----------------------
107
+ # HELPER FUNCTIONS
108
+ # -----------------------
109
+
110
+ # MCP calls
111
+ def upload_file_to_mcp(filepath, filename):
112
+ url = f"http://localhost:{FILE_SERVER_PORT}/upload"
113
+ with open(filepath, "rb") as f:
114
+ files = {"file": (filename, f)}
115
+ r = requests.post(url, files=files, timeout=30)
116
+ r.raise_for_status()
117
+ return r.json()
118
+
119
+ def list_mcp_files():
120
+ return requests.get(f"http://localhost:{FILE_SERVER_PORT}/list").json()
121
+
122
+ def read_mcp_file(name):
123
+ return requests.get(f"http://localhost:{FILE_SERVER_PORT}/read/{name}").json()
124
+
125
+ def apply_patch_mcp(filename, action, params=None):
126
+ payload = {"filename": filename, "action": action, "params": params or {}}
127
+ return requests.post(f"http://localhost:{EXECUTOR_PORT}/apply", json=payload).json()
128
+
129
+ def memory_set(key, value):
130
+ return requests.post(f"http://localhost:{MEMORY_PORT}/set", json={"key":key,"value":value}).json()
131
+
132
+ def memory_get(key):
133
+ return requests.get(f"http://localhost:{MEMORY_PORT}/get/{key}").json()
134
+
135
+ # Gemini planner
136
+ def call_gemini(prompt: str):
137
+ if GEMINI_API_KEY:
138
+ headers = {"Authorization": f"Bearer {GEMINI_API_KEY}", "Content-Type": "application/json"}
139
+ data = {"prompt": prompt, "max_tokens": 800}
140
+ try:
141
+ resp = requests.post("https://api.gemini.example/v1/generate", headers=headers, json=data, timeout=30)
142
+ resp.raise_for_status()
143
+ return resp.json().get("text") or resp.text
144
+ except Exception as e:
145
+ return f"Gemini API error: {e}"
146
+ else:
147
+ # fallback plan
148
+ return ("STEP 1: Extract ERROR/WARNING lines.\n"
149
+ "STEP 2: Prioritize issues.\n"
150
+ "STEP 3: Suggest fixes.\n"
151
+ "PLAN: 1) extract, 2) summarize, 3) recommend patches")
152
+
153
+ # Parse logs
154
+ def parse_log_text(txt):
155
+ lines = txt.splitlines()
156
+ errors = [l for l in lines if "ERROR" in l or "CRITICAL" in l or "FATAL" in l]
157
+ warnings = [l for l in lines if "WARNING" in l or "WARN" in l]
158
+ return {"errors": errors, "warnings": warnings, "total_lines": len(lines)}
159
+
160
+ # Generate PDF report
161
+ def generate_incident_report(filename, summary, issues, suggestions):
162
+ now = datetime.datetime.utcnow().isoformat().replace(":", "-")
163
+ out = REPORT_DIR / f"incident_{filename}_{now}.pdf"
164
+ c = canvas.Canvas(str(out), pagesize=letter)
165
+ c.setFont("Helvetica-Bold", 16)
166
+ c.drawString(40, 750, f"Incident Report - {filename}")
167
+ c.setFont("Helvetica", 10)
168
+ c.drawString(40, 730, f"Generated: {datetime.datetime.utcnow().isoformat()}")
169
+ c.drawString(40, 700, "Summary:")
170
+ c.drawString(60, 680, summary[:1000])
171
+ y = 640
172
+ c.drawString(40, y, "Issues:")
173
+ y -= 20
174
+ for it in issues[:20]:
175
+ c.drawString(60, y, it[:120])
176
+ y -= 14
177
+ if y < 80:
178
+ c.showPage(); y = 740
179
+ y -= 10
180
+ c.drawString(40, y, "Suggestions:")
181
+ y -= 20
182
+ for s in suggestions:
183
+ c.drawString(60, y, f"- {s[:100]}")
184
+ y -= 14
185
+ if y < 80:
186
+ c.showPage(); y = 740
187
+ c.save()
188
+ return str(out)
189
+
190
+ def analyze_and_plan(filename):
191
+ r = read_mcp_file(filename)
192
+ if "error" in r:
193
+ return {"summary": "File not found", "issues": [], "suggestions": [], "plan_text": ""}
194
+ text = r.get("content","")
195
+ parsed = parse_log_text(text)
196
+ prompt = f"Analyze logs file {filename}. Provide summary, list issues, and 3 fix suggestions."
197
+ plan_text = call_gemini(prompt)
198
+ # fallback deterministic
199
+ if "STEP 1" in plan_text:
200
+ summary = "Detected errors and warnings; see issues."
201
+ issues = parsed["errors"][:10] + parsed["warnings"][:10]
202
+ suggestions = ["Increase DB timeout", "Add retry logic", "Investigate 500 errors"]
203
+ else:
204
+ summary = plan_text.split("\n")[0][:1000]
205
+ issues = parsed["errors"][:10]
206
+ suggestions = []
207
+ for line in plan_text.splitlines():
208
+ if "suggest" in line.lower() or "fix" in line.lower() or "recommend" in line.lower():
209
+ suggestions.append(line.strip())
210
+ if not suggestions:
211
+ suggestions = ["Investigate top errors", "Create incident ticket", "Add monitoring"]
212
+ return {"summary": summary, "issues": issues, "suggestions": suggestions, "plan_text": plan_text}
213
+
214
+ def apply_fix_action(filename, action_key):
215
+ mapping = {
216
+ "increase_timeout": ("update_timeout", {"timeout": 60}),
217
+ "increase_retries": ("increase_retries", {"retries": 3}),
218
+ "mark_fixed": ("mark_fixed", {})
219
+ }
220
+ action, params = mapping.get(action_key, ("custom_action", {"note": action_key}))
221
+ res = apply_patch_mcp(filename, action, params)
222
+ memory_set(f"last_patch_{filename}", json.dumps(res))
223
+ return res
224
+
225
+ # -----------------------
226
+ # GRADIO UI
227
+ # -----------------------
228
+ with gr.Blocks() as demo:
229
+ gr.Markdown("## AgentOps Control Center — MCP in Action")
230
+ with gr.Row():
231
+ with gr.Column(scale=2):
232
+ upload = gr.File(label="Upload log file")
233
+ files_list = gr.Dropdown(choices=[], label="Files on MCP File Server", interactive=True)
234
+ btn_refresh = gr.Button("Refresh files")
235
+ btn_analyze = gr.Button("Analyze selected file")
236
+ apply_dropdown = gr.Dropdown(choices=["increase_timeout","increase_retries","mark_fixed"], label="Select fix action")
237
+ btn_apply = gr.Button("Apply fix")
238
+ with gr.Column(scale=3):
239
+ summary_box = gr.Textbox(label="Agent Summary", lines=6)
240
+ issues_box = gr.Textbox(label="Detected Issues", lines=10)
241
+ suggestions_box = gr.Textbox(label="Suggested Fixes", lines=6)
242
+ plan_box = gr.Textbox(label="Agent Plan / Reasoning", lines=6)
243
+ report_download = gr.File(label="Last Report (download)")
244
+
245
+ # Callbacks
246
+ def refresh_files():
247
+ resp = list_mcp_files()
248
+ return resp.get("files", [])
249
+
250
+ btn_refresh.click(fn=refresh_files, outputs=files_list)
251
+
252
+ def on_upload(file_obj):
253
+ tmp = Path(tempfile.gettempdir()) / file_obj.name
254
+ with open(tmp, "wb") as f:
255
+ f.write(file_obj.read())
256
+ upload_file_to_mcp(str(tmp), file_obj.name)
257
+ return refresh_files()
258
+ upload.upload(fn=on_upload, inputs=upload, outputs=files_list)
259
+
260
+ def analyze_selected(fname):
261
+ if not fname:
262
+ return {"summary_box": "Select file first", "issues_box":"", "suggestions_box":"", "plan_box":"", "report_download": None}
263
+ out = analyze_and_plan(fname)
264
+ report_path = generate_incident_report(fname, out["summary"], out["issues"], out["suggestions"])
265
+ return out["summary"], "\n".join(out["issues"]), "\n".join(out["suggestions"]), out["plan_text"], gr.File.update(value=report_path)
266
+
267
+ btn_analyze.click(fn=analyze_selected, inputs=[files_list], outputs=[summary_box, issues_box, suggestions_box, plan_box, report_download])
268
+
269
+ def apply_action(fname, action_key):
270
+ if not fname:
271
+ return {"error":"select file"}
272
+ res = apply_fix_action(fname, action_key)
273
+ return json.dumps(res, indent=2)
274
+
275
+ btn_apply.click(fn=apply_action, inputs=[files_list, apply_dropdown], outputs=[plan_box])
276
+
277
+ # -----------------------
278
+ # START MCP SERVERS IN THREADS
279
+ # -----------------------
280
+ def start_file_server():
281
+ uvicorn.run(file_app, host="0.0.0.0", port=FILE_SERVER_PORT, log_level="error")
282
+
283
+ def start_executor_server():
284
+ uvicorn.run(executor_app, host="0.0.0.0", port=EXECUTOR_PORT, log_level="error")
285
+
286
+ def start_memory_server():
287
+ uvicorn.run(memory_app, host="0.0.0.0", port=MEMORY_PORT, log_level="error")
288
+
289
+ threading.Thread(target=start_file_server, daemon=True).start()
290
+ threading.Thread(target=start_executor_server, daemon=True).start()
291
+ threading.Thread(target=start_memory_server, daemon=True).start()
292
+
293
+ # -----------------------
294
+ # LAUNCH GRADIO
295
+ # -----------------------
296
+ if __name__ == "__main__":
297
+ demo.launch(server_name="0.0.0.0", server_port=7860)