Akoda35 commited on
Commit
532a35b
·
verified ·
1 Parent(s): 707e062

Update theme/app.py

Browse files
Files changed (1) hide show
  1. theme/app.py +172 -125
theme/app.py CHANGED
@@ -1,15 +1,17 @@
1
  import os
2
- import requests
 
 
3
  from pathlib import Path
4
  from fastapi import FastAPI, Request
5
  from fastapi.responses import JSONResponse, HTMLResponse
6
  from fastapi.templating import Jinja2Templates
7
 
8
  # =========================
9
- # APP INIT
10
  # =========================
11
 
12
- app = FastAPI(title="PyRunner Agent OS", version="3.2")
13
 
14
  BASE_DIR = Path(__file__).resolve().parent
15
  templates = Jinja2Templates(directory=str(BASE_DIR / "templates"))
@@ -21,199 +23,244 @@ templates = Jinja2Templates(directory=str(BASE_DIR / "templates"))
21
  OLLAMA_URL = os.getenv("OLLAMA_URL", "http://localhost:11434")
22
 
23
  MODELS = {
24
- "orchestrator": os.getenv("OLLAMA_MODEL", "qwen2.5:1.5b"),
25
- "coder": os.getenv("OLLAMA_CODE_MODEL", "qwen2.5-coder:1.5b"),
26
- "fast": os.getenv("OLLAMA_FAST_MODEL", "deepseek-coder:1.3b"),
27
- "fallback": os.getenv("OLLAMA_BACKUP_MODEL", "llama3.2:1b")
28
  }
29
 
30
- TIMEOUT = 90
 
 
 
 
 
 
 
31
 
 
 
 
 
 
 
 
 
 
32
 
33
  # =========================
34
- # OLLAMA CORE CALL (SAFE)
35
  # =========================
36
 
37
- def call_ollama(prompt: str, model: str):
38
- try:
39
- r = requests.post(
40
- f"{OLLAMA_URL}/api/generate",
41
- json={
42
- "model": model,
43
- "prompt": prompt,
44
- "stream": False
45
- },
46
- timeout=TIMEOUT
47
- )
48
 
49
- if r.status_code != 200:
50
- return {"error": f"Ollama HTTP {r.status_code}", "raw": r.text}
 
 
 
 
 
51
 
52
- data = r.json()
 
 
 
53
 
54
- return {
55
- "response": data.get("response", "").strip(),
56
- "raw": data
57
- }
58
 
 
 
 
 
 
 
 
 
 
59
  except Exception as e:
60
- return {"error": str(e)}
61
-
62
 
63
  # =========================
64
- # CLEAN OUTPUT PARSER
65
  # =========================
66
 
67
- def clean_category(text: str) -> str:
68
- if not text:
69
- return "general"
70
 
71
- text = text.lower().strip()
 
72
 
73
- # force valid categories only
74
- valid = ["code", "vision", "fashion", "hairstyle", "general"]
75
 
76
- for v in valid:
77
- if v in text:
78
- return v
 
 
 
79
 
80
- return "general"
81
 
 
 
 
 
 
82
 
83
  # =========================
84
- # ORCHESTRATOR
85
  # =========================
86
 
87
- def orchestrator(user_input: str):
88
 
89
  prompt = f"""
90
- Classify into ONE word only:
91
 
92
- code
93
- vision
94
- fashion
95
- hairstyle
96
- general
97
 
98
- Input:
99
- {user_input}
100
  """
101
 
102
- res = call_ollama(prompt, MODELS["orchestrator"])
103
 
104
- if "response" in res:
105
- return clean_category(res["response"])
 
106
 
107
- return "general"
 
 
 
 
108
 
 
 
 
 
 
 
 
 
 
 
 
109
 
110
  # =========================
111
- # AGENTS
112
  # =========================
113
 
114
- def coder(prompt):
115
- return call_ollama(prompt, MODELS["coder"])
 
 
 
 
 
 
116
 
 
117
 
118
- def fashion(prompt):
119
- return call_ollama(f"Fashion analysis:\n{prompt}", MODELS["fast"])
 
120
 
 
121
 
122
- def hairstyle(prompt):
123
- return call_ollama(f"Hairstyle analysis:\n{prompt}", MODELS["fast"])
124
 
 
 
125
 
126
- def fallback(prompt):
127
- return call_ollama(prompt, MODELS["fallback"])
128
 
 
129
 
130
  # =========================
131
- # ROUTER
132
  # =========================
133
 
134
- def route(category: str, prompt: str):
135
 
136
- if category == "code":
137
- return coder(prompt)
138
 
139
- if category == "fashion":
140
- return fashion(prompt)
141
 
142
- if category == "hairstyle":
143
- return hairstyle(prompt)
144
 
145
- return fallback(prompt)
146
 
 
147
 
148
- # =========================
149
- # UI
150
- # =========================
151
 
152
- @app.get("/", response_class=HTMLResponse)
153
- async def home(request: Request):
154
- try:
155
- return templates.TemplateResponse(
156
- "index.html",
157
- {"request": request}
158
- )
159
- except Exception as e:
160
- return HTMLResponse(f"<h1>Template Error</h1><pre>{e}</pre>")
161
 
 
162
 
163
- # =========================
164
- # HEALTH
165
- # =========================
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
166
 
167
- @app.get("/health")
168
- def health():
169
  return {
170
- "status": "ok",
171
- "models": MODELS
 
 
172
  }
173
 
174
-
175
  # =========================
176
- # MAIN AGENT ENDPOINT
177
  # =========================
178
 
179
- @app.post("/api/agent")
180
- async def agent(request: Request):
181
 
182
- try:
183
- body = await request.json()
184
- prompt = body.get("prompt", "")
185
 
186
- if not prompt:
187
- return JSONResponse(
188
- {"error": "missing prompt"},
189
- status_code=400
190
- )
191
 
192
- category = orchestrator(prompt)
193
- result = route(category, prompt)
 
194
 
195
- if isinstance(result, dict) and "error" in result:
196
- result = fallback(prompt)
 
197
 
198
- return {
199
- "category": category,
200
- "result": result.get("response", result)
201
- }
202
 
203
- except Exception as e:
204
- return JSONResponse(
205
- {"error": str(e)},
206
- status_code=500
207
- )
208
 
 
 
209
 
210
- # =========================
211
- # IMAGE PIPELINE (STABLE STUB)
212
- # =========================
213
 
214
- @app.post("/api/image")
215
- async def image(request: Request):
216
- return {
217
- "status": "queued",
218
- "pipeline": "vision_stub"
219
- }
 
 
1
  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"))
 
23
  OLLAMA_URL = os.getenv("OLLAMA_URL", "http://localhost:11434")
24
 
25
  MODELS = {
26
+ "planner": "qwen2.5:1.5b",
27
+ "worker": "qwen2.5-coder:1.5b",
28
+ "critic": "deepseek-coder:1.3b",
29
+ "synth": "llama3.2:1b"
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:
176
 
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,
218
+ "result": final,
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):
 
 
251
 
252
+ body = await request.json()
253
+ task = body.get("task", "")
 
 
 
254
 
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}