Akoda35 commited on
Commit
512651f
·
verified ·
1 Parent(s): 532a35b

Update theme/app.py

Browse files
Files changed (1) hide show
  1. theme/app.py +113 -93
theme/app.py CHANGED
@@ -2,19 +2,23 @@ import os
2
  import uuid
3
  import json
4
  import time
 
5
  from pathlib import Path
6
  from fastapi import FastAPI, Request
7
  from fastapi.responses import JSONResponse, HTMLResponse
8
  from fastapi.templating import Jinja2Templates
9
 
10
  # =========================
11
- # CORE APP
12
  # =========================
13
 
14
- app = FastAPI(title="PyRunner PAO v4 Distributed OS", version="4.0")
15
 
16
  BASE_DIR = Path(__file__).resolve().parent
17
- templates = Jinja2Templates(directory=str(BASE_DIR / "templates"))
 
 
 
18
 
19
  # =========================
20
  # CONFIG
@@ -30,146 +34,152 @@ MODELS = {
30
  }
31
 
32
  # =========================
33
- # MEMORY GRAPH (NOW EVENT-SAFE)
34
  # =========================
35
 
36
- MEMORY_DB = {
37
- "nodes": {},
38
- "edges": []
39
- }
40
 
41
  def memory_store(task_id, data):
42
- MEMORY_DB["nodes"][task_id] = data
43
 
44
- def memory_query(keyword):
45
- results = []
46
- for k, v in MEMORY_DB["nodes"].items():
47
- if keyword.lower() in v.get("task", "").lower():
48
- results.append(v)
49
- return results
50
 
51
  # =========================
52
- # EVENT BUS (SIMULATED QUEUE)
53
  # =========================
54
 
55
- EVENT_QUEUE = []
56
 
57
- def emit_event(event_type, payload):
58
- EVENT_QUEUE.append({
59
  "id": str(uuid.uuid4()),
60
  "type": event_type,
61
  "payload": payload,
62
- "timestamp": time.time()
63
  })
64
 
65
- def consume_events():
66
- events = EVENT_QUEUE[:]
67
- EVENT_QUEUE.clear()
68
- return events
69
 
70
  # =========================
71
- # MODEL CALL
72
  # =========================
73
 
74
- def call_model(prompt, model):
75
- import requests
76
  try:
77
  r = requests.post(
78
  f"{OLLAMA_URL}/api/generate",
79
- json={"model": model, "prompt": prompt, "stream": False},
 
 
 
 
80
  timeout=120
81
  )
82
  return r.json().get("response", "")
83
  except Exception as e:
84
- return f"ERROR: {str(e)}"
85
 
86
  # =========================
87
- # ORCHESTRATOR (DAG BUILDER)
88
  # =========================
89
 
90
- def orchestrator(task):
91
 
92
  prompt = f"""
93
- Break into structured execution DAG:
 
 
94
 
95
  Task: {task}
96
 
97
- Return JSON array like:
98
  [
99
- {{"step": "..." }},
100
- {{"step": "..."}}
 
101
  ]
102
  """
103
 
104
- raw = call_model(prompt, MODELS["planner"])
105
 
106
  try:
107
- return json.loads(raw)
108
- except:
109
- # fallback
110
- return [{"step": task}]
111
-
112
- # =========================
113
- # WORKER (STATELESS EXECUTOR)
114
- # =========================
115
-
116
- def worker(step, context=""):
117
 
118
- prompt = f"""
119
- Execute this step:
120
-
121
- {step}
122
 
123
- Context:
124
- {context}
125
- """
126
 
127
- return call_model(prompt, MODELS["worker"])
 
128
 
129
  # =========================
130
  # TOOL SYSTEM
131
  # =========================
132
 
133
- def tool_calculator(expr):
134
  try:
135
  return eval(expr, {"__builtins__": {}})
136
  except:
137
- return "error"
138
 
139
  TOOLS = {
140
  "calculator": tool_calculator
141
  }
142
 
143
- def tool_router(text):
144
-
145
  if "calc:" in text:
146
  expr = text.replace("calc:", "").strip()
147
- return TOOLS["calculator"](expr)
148
-
149
  return text
150
 
151
  # =========================
152
- # CRITIC (QUALITY GATE)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
153
  # =========================
154
 
155
  def critic(task, output):
156
 
157
  prompt = f"""
158
- Score 0-10 and improve suggestion:
159
 
160
  Task: {task}
161
  Output: {output}
 
 
162
  """
163
 
164
- return call_model(prompt, MODELS["critic"])
165
 
166
  # =========================
167
  # SYNTHESIZER
168
  # =========================
169
 
170
- def synthesize(results):
171
 
172
- combined = "\n".join(results)
173
 
174
  prompt = f"""
175
  Create final clean response:
@@ -177,41 +187,44 @@ Create final clean response:
177
  {combined}
178
  """
179
 
180
- return call_model(prompt, MODELS["synth"])
181
 
182
  # =========================
183
- # DISTRIBUTED EXECUTION ENGINE
184
  # =========================
185
 
186
- def execute_dag(task, dag):
187
 
188
  task_id = str(uuid.uuid4())
189
 
190
- emit_event("task_started", {"task_id": task_id, "task": task})
191
 
 
192
  results = []
193
 
194
- for node in dag:
195
 
196
- step = node.get("step")
197
 
198
- emit_event("step_started", {"step": step})
199
 
200
- raw = worker(step)
201
 
202
- processed = tool_router(raw)
 
203
 
204
- crit = critic(task, processed)
205
 
206
  results.append(processed)
 
207
 
208
- emit_event("step_completed", {
209
  "step": step,
210
  "output": processed,
211
- "critique": crit
212
  })
213
 
214
- final = synthesize(results)
215
 
216
  memory_store(task_id, {
217
  "task": task,
@@ -219,32 +232,37 @@ def execute_dag(task, dag):
219
  "steps": results
220
  })
221
 
222
- emit_event("task_completed", {"task_id": task_id})
223
 
224
  return {
225
  "task_id": task_id,
226
  "dag": dag,
227
- "events": consume_events(),
228
  "final": final
229
  }
230
 
231
  # =========================
232
- # MAIN ORCHESTRATION PIPELINE
233
  # =========================
234
 
235
- def run_pao(task):
 
 
 
 
 
236
 
237
- dag = orchestrator(task)
238
 
239
- return execute_dag(task, dag)
240
 
241
- # =========================
242
- # API
243
- # =========================
 
 
 
 
244
 
245
- @app.get("/", response_class=HTMLResponse)
246
- async def home(request: Request):
247
- return templates.TemplateResponse("index.html", {"request": request})
248
 
249
  @app.post("/api/run")
250
  async def run(request: Request):
@@ -255,12 +273,14 @@ async def run(request: Request):
255
  if not task:
256
  return JSONResponse({"error": "missing task"}, status_code=400)
257
 
258
- return run_pao(task)
 
259
 
260
  @app.get("/api/memory/{task_id}")
261
  def get_memory(task_id: str):
262
- return MEMORY_DB["nodes"].get(task_id, {})
 
263
 
264
  @app.get("/api/events")
265
  def get_events():
266
- return {"events": EVENT_QUEUE}
 
2
  import uuid
3
  import json
4
  import time
5
+ import requests
6
  from pathlib import Path
7
  from fastapi import FastAPI, Request
8
  from fastapi.responses import JSONResponse, HTMLResponse
9
  from fastapi.templating import Jinja2Templates
10
 
11
  # =========================
12
+ # APP CORE
13
  # =========================
14
 
15
+ app = FastAPI(title="PyRunner PAO v5 SaaS Core", version="5.0")
16
 
17
  BASE_DIR = Path(__file__).resolve().parent
18
+
19
+ # SAFE TEMPLATE LOADING (prevents your Jinja crash)
20
+ TEMPLATE_DIR = BASE_DIR / "templates"
21
+ templates = Jinja2Templates(directory=str(TEMPLATE_DIR))
22
 
23
  # =========================
24
  # CONFIG
 
34
  }
35
 
36
  # =========================
37
+ # MEMORY SYSTEM (replace later with Supabase/Postgres)
38
  # =========================
39
 
40
+ MEMORY = {}
 
 
 
41
 
42
  def memory_store(task_id, data):
43
+ MEMORY[task_id] = data
44
 
45
+ def memory_get(task_id):
46
+ return MEMORY.get(task_id, {})
 
 
 
 
47
 
48
  # =========================
49
+ # EVENT SYSTEM (lightweight observability)
50
  # =========================
51
 
52
+ EVENTS = []
53
 
54
+ def emit(event_type, payload):
55
+ EVENTS.append({
56
  "id": str(uuid.uuid4()),
57
  "type": event_type,
58
  "payload": payload,
59
+ "ts": time.time()
60
  })
61
 
62
+ def drain_events():
63
+ data = EVENTS[:]
64
+ EVENTS.clear()
65
+ return data
66
 
67
  # =========================
68
+ # LLM CALL
69
  # =========================
70
 
71
+ def call_llm(prompt, model):
 
72
  try:
73
  r = requests.post(
74
  f"{OLLAMA_URL}/api/generate",
75
+ json={
76
+ "model": model,
77
+ "prompt": prompt,
78
+ "stream": False
79
+ },
80
  timeout=120
81
  )
82
  return r.json().get("response", "")
83
  except Exception as e:
84
+ return f"LLM_ERROR: {str(e)}"
85
 
86
  # =========================
87
+ # DAG PLANNER (FIXED JSON SAFETY)
88
  # =========================
89
 
90
+ def plan_task(task: str):
91
 
92
  prompt = f"""
93
+ Return ONLY valid JSON.
94
+
95
+ Break task into steps:
96
 
97
  Task: {task}
98
 
99
+ Format:
100
  [
101
+ "step 1",
102
+ "step 2",
103
+ "step 3"
104
  ]
105
  """
106
 
107
+ raw = call_llm(prompt, MODELS["planner"])
108
 
109
  try:
110
+ steps = json.loads(raw)
 
 
 
 
 
 
 
 
 
111
 
112
+ # ensure list safety
113
+ if isinstance(steps, list):
114
+ return [{"step": s} for s in steps]
 
115
 
116
+ except:
117
+ pass
 
118
 
119
+ # fallback (always safe)
120
+ return [{"step": task}]
121
 
122
  # =========================
123
  # TOOL SYSTEM
124
  # =========================
125
 
126
+ def tool_calculator(expr: str):
127
  try:
128
  return eval(expr, {"__builtins__": {}})
129
  except:
130
+ return "calc_error"
131
 
132
  TOOLS = {
133
  "calculator": tool_calculator
134
  }
135
 
136
+ def run_tools(text: str):
 
137
  if "calc:" in text:
138
  expr = text.replace("calc:", "").strip()
139
+ return str(TOOLS["calculator"](expr))
 
140
  return text
141
 
142
  # =========================
143
+ # WORKER NODE
144
+ # =========================
145
+
146
+ def worker(step: str, context: str):
147
+
148
+ prompt = f"""
149
+ Execute this instruction clearly:
150
+
151
+ Step: {step}
152
+
153
+ Context:
154
+ {context}
155
+ """
156
+
157
+ return call_llm(prompt, MODELS["worker"])
158
+
159
+ # =========================
160
+ # CRITIC (QUALITY CHECK)
161
  # =========================
162
 
163
  def critic(task, output):
164
 
165
  prompt = f"""
166
+ Rate and improve:
167
 
168
  Task: {task}
169
  Output: {output}
170
+
171
+ Return short improvement suggestion.
172
  """
173
 
174
+ return call_llm(prompt, MODELS["critic"])
175
 
176
  # =========================
177
  # SYNTHESIZER
178
  # =========================
179
 
180
+ def synth(outputs):
181
 
182
+ combined = "\n".join(outputs)
183
 
184
  prompt = f"""
185
  Create final clean response:
 
187
  {combined}
188
  """
189
 
190
+ return call_llm(prompt, MODELS["synth"])
191
 
192
  # =========================
193
+ # EXECUTION ENGINE
194
  # =========================
195
 
196
+ def run_dag(task: str):
197
 
198
  task_id = str(uuid.uuid4())
199
 
200
+ emit("task_started", {"task_id": task_id, "task": task})
201
 
202
+ dag = plan_task(task)
203
  results = []
204
 
205
+ context = task
206
 
207
+ for node in dag:
208
 
209
+ step = node["step"]
210
 
211
+ emit("step_started", {"step": step})
212
 
213
+ raw = worker(step, context)
214
+ processed = run_tools(raw)
215
 
216
+ _crit = critic(task, processed)
217
 
218
  results.append(processed)
219
+ context = processed
220
 
221
+ emit("step_done", {
222
  "step": step,
223
  "output": processed,
224
+ "critique": _crit
225
  })
226
 
227
+ final = synth(results)
228
 
229
  memory_store(task_id, {
230
  "task": task,
 
232
  "steps": results
233
  })
234
 
235
+ emit("task_done", {"task_id": task_id})
236
 
237
  return {
238
  "task_id": task_id,
239
  "dag": dag,
240
+ "events": drain_events(),
241
  "final": final
242
  }
243
 
244
  # =========================
245
+ # API ROUTES
246
  # =========================
247
 
248
+ @app.get("/", response_class=HTMLResponse)
249
+ async def home(request: Request):
250
+
251
+ # SAFE CHECK: prevents template crash
252
+ if not (TEMPLATE_DIR / "index.html").exists():
253
+ return HTMLResponse("<h1>Missing index.html</h1>", status_code=500)
254
 
255
+ return templates.TemplateResponse("index.html", {"request": request})
256
 
 
257
 
258
+ @app.get("/builder", response_class=HTMLResponse)
259
+ async def builder(request: Request):
260
+
261
+ if not (TEMPLATE_DIR / "builder.html").exists():
262
+ return HTMLResponse("<h1>Missing builder.html</h1>", status_code=500)
263
+
264
+ return templates.TemplateResponse("builder.html", {"request": request})
265
 
 
 
 
266
 
267
  @app.post("/api/run")
268
  async def run(request: Request):
 
273
  if not task:
274
  return JSONResponse({"error": "missing task"}, status_code=400)
275
 
276
+ return run_dag(task)
277
+
278
 
279
  @app.get("/api/memory/{task_id}")
280
  def get_memory(task_id: str):
281
+ return memory_get(task_id)
282
+
283
 
284
  @app.get("/api/events")
285
  def get_events():
286
+ return {"events": EVENTS}