Chris4K commited on
Commit
267d4e6
Β·
verified Β·
1 Parent(s): 2a07d72

Update gradio_app.py

Browse files
Files changed (1) hide show
  1. gradio_app.py +259 -429
gradio_app.py CHANGED
@@ -1,14 +1,14 @@
1
  """
2
- gradio_app.py β€” Proto-Cognitive Architecture v5
3
  HuggingFace Spaces deployment wrapper.
4
 
5
- v5 CHANGES:
6
- - Dream/Replay controls: trigger dream cycles from the UI
7
- - W_local connection stats: see Hebbian structure forming
8
- - Memory consolidation display: M tensor health
9
- - Auto-dream toggle after teach
10
- - Enhanced diagnostics panel with v5 mechanisms
11
- - Backward compatible with v4 world saves
12
 
13
  Deploy to HF Spaces:
14
  1. Create a new Space (Gradio SDK, GPU T4 or better)
@@ -20,7 +20,6 @@ requirements.txt:
20
  transformers>=4.40
21
  gradio>=4.0
22
  numpy
23
- accelerate
24
  """
25
 
26
  import os
@@ -38,50 +37,50 @@ LM_JUDGE_MODEL = os.environ.get("LM_JUDGE_MODEL", "qwen/qwen3.5-35b-a3b")
38
  USE_JUDGE = os.environ.get("USE_JUDGE", "false").lower() == "true"
39
 
40
  # ── Global agent ──────────────────────────────────────────────────────────────
41
- print("Loading Proto-Cognitive Agent v5.1...")
42
  agent = HybridLLM()
43
  print("Agent ready.")
44
 
45
  WORLD_SAVE_PATH = "world_state.pt"
46
 
47
- # ── History tracking ──────────────────────────────────────────────────────────
48
  resonance_history: list[dict] = []
49
  MAX_HISTORY = 50
50
 
51
 
52
  # =============================================================================
53
- # ROUTE FORMATTING
54
  # =============================================================================
55
 
56
  ROUTE_EMOJI = {
57
- "CONFIDENT": "🟒",
58
- "CAUTIOUS": "🟑",
59
- "UNCERTAIN": "🟠",
60
- "DEFER": "πŸ”΄",
61
  }
62
 
63
  ROUTE_LABEL = {
64
- "CONFIDENT": "Confident β€” strong field resonance + reliable facts retrieved",
65
- "CAUTIOUS": "Cautious β€” field recognises domain but facts are weak",
66
- "UNCERTAIN": "Uncertain β€” some retrieval but low domain familiarity",
67
- "DEFER": "Deferred β€” no stored knowledge about this topic",
68
  }
69
 
70
 
71
  def format_route_badge(resp: CognitiveResponse) -> str:
 
72
  emoji = ROUTE_EMOJI.get(resp.route, "βšͺ")
73
  label = ROUTE_LABEL.get(resp.route, resp.route)
74
  return (
75
- f"{emoji} **{resp.route}** | "
76
- f"Resonance: `{resp.resonance:.3f}` | "
77
- f"Retrieval: `{resp.retrieval_confidence:.3f}` | "
78
- f"Tension: `{resp.tension:.3f}` | "
79
- f"Steps: `{resp.think_steps_used}`\n"
80
  f"*{label}*"
81
  )
82
 
83
 
84
  def format_retrieved_facts(resp: CognitiveResponse) -> str:
 
85
  if not resp.retrieved_facts:
86
  return "*No facts retrieved*"
87
  lines = []
@@ -93,7 +92,7 @@ def format_retrieved_facts(resp: CognitiveResponse) -> str:
93
 
94
 
95
  # =============================================================================
96
- # LM STUDIO JUDGE
97
  # =============================================================================
98
 
99
  def lm_judge(question: str, answer: str, context_facts: list[str]) -> tuple[str, float]:
@@ -113,9 +112,9 @@ AI answer:
113
  {answer}
114
 
115
  Rate the answer on a scale from 0.0 to 1.0:
116
- 1.0 = fully correct, cites the right facts
117
- 0.5 = partially correct or vague
118
- 0.0 = wrong, ignores the provided facts
119
 
120
  Respond with ONLY a JSON object: {{"score": 0.X, "verdict": "one sentence"}}"""
121
 
@@ -139,60 +138,38 @@ Respond with ONLY a JSON object: {{"score": 0.X, "verdict": "one sentence"}}"""
139
 
140
 
141
  # =============================================================================
142
- # CORE FUNCTIONS
143
  # =============================================================================
144
 
145
  def get_diagnostics() -> str:
146
- d = agent.world.diagnostics()
147
  ed = agent.episodes.diagnostics()
148
  tok = agent.token_report()
149
  rs = agent.router.get_routing_stats()
150
 
151
  lines = [
152
  "### Field State",
153
- f"**Field norm:** {d['field_norm']:.3f} | "
154
  f"**Active regions:** {d['active_regions']} / {d['protected']} protected",
155
- f"**Attractors:** {d['attractors']} | "
156
  f"**Steps:** {d['total_steps']}",
157
  "",
158
- "### Hebbian Structure (v5)",
159
- f"**W_local connections:** {d['w_local_connections']} | "
160
- f"**Density:** {d['w_local_density']:.4f}",
161
- f"**W_local max:** {d['w_local_max']:.4f} | "
162
- f"**Mean:** {d['w_local_mean']:.4f}",
163
- f"**Hebbian updates:** {d['hebbian_updates']}",
164
- "",
165
- ]
166
-
167
- # v5.2: Region embeddings count
168
- #n_region_embeds = sum(1 for e in agent.world.region_embeds if e is not None)
169
- n_region_embeds = sum(1 for m in agent.world.memories if m)
170
- lines.append(f"**Region embeds (for resonance):** {n_region_embeds} stored")
171
-
172
- lines.extend([
173
- "",
174
- "### Memory Consolidation (v5)",
175
- f"**M norm:** {d['m_norm']:.4f} | "
176
- f"**M active regions:** {d['m_active_regions']}",
177
- f"**Consolidations:** {d['consolidations']} | "
178
- f"**Replay cycles:** {d['replays']}",
179
- "",
180
  "### Episodic Store",
181
- f"**Total:** {ed['total']} | "
182
- f"**Active:** {ed['active']} | "
183
  f"**Superseded:** {ed['superseded']}",
184
  "",
185
  "### Cognitive Router",
186
- ])
187
 
188
  if rs["total"] > 0:
189
  lines.extend([
190
  f"**Queries routed:** {rs['total']}",
191
- f"🟒 Confident: {rs['confident']} | "
192
- f"🟑 Cautious: {rs['cautious']} | "
193
- f"🟠 Uncertain: {rs['uncertain']} | "
194
  f"πŸ”΄ Defer: {rs['defer']}",
195
- f"**Avg resonance:** {rs['avg_resonance']:.4f} | "
196
  f"**Avg retrieval:** {rs['avg_retrieval']:.4f}",
197
  ])
198
  else:
@@ -201,25 +178,12 @@ def get_diagnostics() -> str:
201
  lines.extend([
202
  "",
203
  "### Token Usage",
204
- f"**Teach:** {tok['teach_calls']} calls ({tok['teach_input_tokens']} tok) | "
205
  f"**Gen:** {tok['generate_calls']} calls",
206
- f"**Avg in/gen:** {tok['avg_input_per_gen']} | "
207
  f"**Avg out/gen:** {tok['avg_output_per_gen']}",
208
  ])
209
 
210
- # v5.1: Tension stats
211
- ts = agent.tension.get_stats()
212
- if ts["total"] > 0:
213
- lines.extend([
214
- "",
215
- "### Tension (v5.1)",
216
- f"**Current:** {ts['current']:.4f} | "
217
- f"**Avg:** {ts['avg_tension']:.4f} | "
218
- f"**Trend:** {ts['trend']:+.4f}",
219
- f"**Range:** {ts['min_tension']:.4f} β€” {ts['max_tension']:.4f} | "
220
- f"**Adaptive steps:** {ts['adaptive_steps']}",
221
- ])
222
-
223
  # Pinned facts
224
  if ed["total"] > 0:
225
  lines += ["", "### Pinned Facts"]
@@ -237,407 +201,273 @@ def get_diagnostics() -> str:
237
 
238
 
239
  def get_resonance_chart() -> str:
 
240
  if not resonance_history:
241
  return "*No resonance data yet β€” ask some questions first*"
242
 
243
- lines = ["### Resonance + Tension History (last 20 queries)", ""]
244
  recent = resonance_history[-20:]
245
  for entry in recent:
246
  r = entry["resonance"]
247
  ret = entry["retrieval"]
248
  route = entry["route"]
249
- t = entry.get("tension", 0)
250
- steps = entry.get("steps", "?")
251
  emoji = ROUTE_EMOJI.get(route, "βšͺ")
252
  bar_r = "β–ˆ" * int(r * 20) + "β–‘" * (20 - int(r * 20))
253
  bar_t = "β–ˆ" * int(ret * 20) + "β–‘" * (20 - int(ret * 20))
254
- q = entry["query"][:30]
255
  lines.append(
256
- f"{emoji} `R:{bar_r}` `S:{bar_t}` T={t:.2f} s={steps} {q}"
257
  )
258
  return "\n".join(lines)
259
 
260
 
261
- def get_w_local_viz() -> str:
262
- """Visualize the W_local connection structure."""
263
- d = agent.world.diagnostics()
264
- w = agent.world.W_local.data
265
-
266
- lines = [
267
- "### W_local Connection Map (v5)",
268
- f"**Density:** {d['w_local_density']:.4f} | "
269
- f"**Connections:** {d['w_local_connections']} | "
270
- f"**Max:** {d['w_local_max']:.4f}",
271
- "",
272
- ]
273
 
274
- # Find strongest connections
275
- w_abs = w.abs()
276
- # Zero diagonal for display
277
- w_disp = w_abs.clone()
278
- w_disp.fill_diagonal_(0.0)
279
-
280
- if w_disp.max() > 0.001:
281
- # Top 15 strongest connections
282
- flat = w_disp.flatten()
283
- k = min(15, int((flat > 0.001).sum().item()))
284
- if k > 0:
285
- topk_vals, topk_idx = torch.topk(flat, k)
286
- lines.append("**Strongest connections:**")
287
- for val, idx in zip(topk_vals.tolist(), topk_idx.tolist()):
288
- i = idx // agent.world.n
289
- j = idx % agent.world.n
290
- bar_len = int(val / d['w_local_max'] * 15) if d['w_local_max'] > 0 else 0
291
- bar = "β–ˆ" * bar_len + "β–‘" * (15 - bar_len)
292
- mem_i = agent.world.memories[i][:25] if agent.world.memories[i] else f"region_{i}"
293
- mem_j = agent.world.memories[j][:25] if agent.world.memories[j] else f"region_{j}"
294
- lines.append(f"`{bar}` {val:.4f} {mem_i} ↔ {mem_j}")
295
- else:
296
- lines.append("*No significant connections yet β€” teach some facts first*")
297
-
298
- # Per-region connection count
299
- lines.extend(["", "**Regions by connectivity:**"])
300
- conn_per_region = (w_disp > 0.001).sum(dim=1)
301
- top_regions = torch.topk(conn_per_region.float(), min(8, agent.world.n))
302
- for idx, count in zip(top_regions.indices.tolist(), top_regions.values.tolist()):
303
- if count < 1:
304
- continue
305
- mem = agent.world.memories[idx][:40] if agent.world.memories[idx] else f"region_{idx}"
306
- prot = "πŸ›‘" if agent.world.protected[idx] else " "
307
- lines.append(f"{prot} Region {idx:2d}: {int(count)} connections β€” {mem}")
308
 
309
- return "\n".join(lines)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
310
 
 
311
 
312
- def get_tension_chart() -> str:
313
- """Visualize tension over time with trend."""
314
- ts = agent.tension.get_stats()
315
- history = agent.tension.history
 
316
 
317
- if not history:
318
- return "*No tension data yet β€” ask some questions first*"
319
 
320
- lines = [
321
- "### Tension Over Time",
322
- f"**Queries:** {ts['total']} | "
323
- f"**Avg:** {ts['avg_tension']:.4f} | "
324
- f"**Trend:** {ts['trend']:+.4f}",
325
- "",
326
  ]
 
327
 
328
- # Show last 20 entries with visual bars
329
- recent = history[-20:]
330
- for i, entry in enumerate(recent):
331
- t = entry["tension"]
332
- # Color coding: low=green, mid=yellow, high=red
333
- if t < 0.3:
334
- indicator = "🟒"
335
- elif t < 0.6:
336
- indicator = "🟑"
337
- else:
338
- indicator = "πŸ”΄"
339
-
340
- bar_len = int(t * 25)
341
- bar = "β–ˆ" * bar_len + "β–‘" * (25 - bar_len)
342
-
343
- # Show components
344
- lines.append(
345
- f"{indicator} `{bar}` {t:.3f} "
346
- f"(u={entry['uncertainty']:.2f} p={entry['pred_error']:.2f} "
347
- f"i={entry['instability']:.2f})"
348
- )
349
 
350
- # Learning signal: if tension is decreasing over time
351
- if len(history) >= 5:
352
- first_5 = sum(h["tension"] for h in history[:5]) / 5
353
- last_5 = sum(h["tension"] for h in history[-5:]) / 5
354
- delta = last_5 - first_5
355
- lines.extend([
356
- "",
357
- f"**Learning signal:** first 5 avg={first_5:.3f} β†’ "
358
- f"last 5 avg={last_5:.3f} (Ξ”={delta:+.3f})",
359
- ])
360
- if delta < -0.05:
361
- lines.append("πŸ“‰ *Tension decreasing β€” system is learning*")
362
- elif delta > 0.05:
363
- lines.append("πŸ“ˆ *Tension increasing β€” system may be struggling*")
364
- else:
365
- lines.append("➑️ *Tension stable*")
366
 
367
- return "\n".join(lines)
 
 
 
368
 
369
 
370
  # =============================================================================
371
- # HANDLERS
372
  # =============================================================================
373
 
374
- def handle_teach(fact_text: str, auto_dream: bool) -> tuple:
375
- if not fact_text.strip():
376
- return "Please enter a fact to teach.", get_diagnostics()
377
-
378
- dream_result = agent.teach(fact_text, verbose=True, auto_dream=auto_dream)
379
-
380
- msg = f"βœ… Encoded: *{fact_text[:80]}*"
381
- if dream_result:
382
- msg += (f"\nπŸ’­ Auto-dream: {dream_result['steps']} steps, "
383
- f"norm Ξ”={dream_result['norm_delta']:.4f}, "
384
- f"W density Ξ”={dream_result['w_density_delta']:.6f}")
385
-
386
- return msg, get_diagnostics()
387
 
 
 
 
388
 
389
- def handle_batch_teach(batch_text: str, auto_dream: bool) -> tuple:
390
- if not batch_text.strip():
391
- return "Please enter facts (one per line).", get_diagnostics()
 
 
 
392
 
393
- facts = [line.strip() for line in batch_text.split("\n") if line.strip()]
394
- results = []
395
- for fact in facts:
396
- dream_result = agent.teach(fact, verbose=True, auto_dream=auto_dream)
397
- results.append(f"βœ… {fact[:60]}")
398
-
399
- return "\n".join(results) + f"\n\n**{len(facts)} facts encoded.**", get_diagnostics()
400
-
401
-
402
- def handle_dream(cycles: int, steps: int) -> tuple:
403
- cycles = max(1, min(int(cycles), 5))
404
- steps = max(1, min(int(steps), 20))
405
-
406
- results = agent.dream(cycles=cycles, steps_per_cycle=steps, verbose=True)
407
-
408
- lines = [f"### Dream Session: {cycles} cycle(s) Γ— {steps} steps", ""]
409
- for i, r in enumerate(results):
410
- lines.append(
411
- f"**Cycle {i+1}:** norm Ξ”={r['norm_delta']:.4f} | "
412
- f"W density Ξ”={r['w_density_delta']:.6f}"
413
- )
414
-
415
- d = agent.world.diagnostics()
416
- lines.extend([
417
- "",
418
- f"**Post-dream state:** W_local density={d['w_local_density']:.4f}, "
419
- f"M norm={d['m_norm']:.4f}, "
420
- f"connections={d['w_local_connections']}",
421
- ])
422
-
423
- return "\n".join(lines), get_diagnostics(), get_w_local_viz()
424
-
425
-
426
- def handle_save() -> str:
427
- agent.save_world(WORLD_SAVE_PATH)
428
- return f"βœ… World saved to `{WORLD_SAVE_PATH}`"
429
-
430
-
431
- def handle_load() -> tuple:
432
- agent.load_world(WORLD_SAVE_PATH)
433
- return f"βœ… World loaded from `{WORLD_SAVE_PATH}`", get_diagnostics()
434
-
435
-
436
- def handle_reset() -> tuple:
437
- agent.reset_world()
438
- resonance_history.clear()
439
- return "πŸ—‘ World reset.", get_diagnostics()
440
 
 
441
 
442
- # =============================================================================
443
- # GRADIO UI
444
- # =============================================================================
445
 
446
- def build_ui():
447
- with gr.Blocks(
448
- title="Proto-Cognitive Architecture v5.1",
449
- theme=gr.themes.Soft(),
450
- ) as demo:
451
-
452
- gr.Markdown(
453
- "# 🧠 Proto-Cognitive Architecture v5.1\n"
454
- "Neural Field + Episodic Memory + Cognitive Router "
455
- "+ **Hebbian Learning** + **Consolidation** + **Replay** "
456
- "+ **Tension Tracking**\n\n"
457
- "*Teach facts β†’ ask questions β†’ watch tension decrease as the field learns*"
458
- )
459
 
460
- with gr.Row():
461
- # ── Left: Chat + Teach ──
462
- with gr.Column(scale=2):
463
 
464
- # Chat
465
- chatbot = gr.Chatbot(
466
- label="Conversation",
467
- height=350,
468
- type="messages",
 
469
  )
470
- with gr.Row():
471
- chat_input = gr.Textbox(
472
- label="Ask a question",
473
- placeholder="What do you know about...?",
474
- scale=4,
475
- )
476
- chat_btn = gr.Button("Ask", variant="primary", scale=1)
477
-
478
- route_display = gr.Markdown(label="Route Decision")
479
- facts_display = gr.Markdown(label="Retrieved Facts")
480
- judge_display = gr.Markdown(label="Judge", visible=USE_JUDGE)
481
-
482
- # Teach
483
- gr.Markdown("---\n### Teach Facts")
484
- with gr.Row():
485
- teach_input = gr.Textbox(
486
- label="Teach a fact",
487
- placeholder="The capital of France is Paris.",
488
- scale=4,
489
- )
490
- teach_btn = gr.Button("Teach", variant="secondary", scale=1)
491
- auto_dream_toggle = gr.Checkbox(
492
- label="Auto-dream after teach",
493
- value=True,
494
- info="Run a short replay cycle after each teach to reinforce structure",
495
  )
496
- teach_result = gr.Markdown(label="Teach Result")
497
-
498
- # Batch teach
499
- with gr.Accordion("Batch Teach", open=False):
500
- batch_input = gr.Textbox(
501
- label="Facts (one per line)",
502
- lines=5,
503
- placeholder="The Earth orbits the Sun.\nWater boils at 100Β°C.\nParis is in France.",
504
- )
505
- batch_btn = gr.Button("Teach All")
506
- batch_result = gr.Markdown()
507
-
508
- # ── Right: Diagnostics + Dream ──
509
- with gr.Column(scale=1):
510
-
511
- with gr.Tabs():
512
- with gr.TabItem("Diagnostics"):
513
- diag_display = gr.Markdown(value=get_diagnostics)
514
- refresh_btn = gr.Button("Refresh", size="sm")
515
-
516
- with gr.TabItem("Resonance"):
517
- resonance_display = gr.Markdown(value=get_resonance_chart)
518
- resonance_refresh = gr.Button("Refresh", size="sm")
519
-
520
- with gr.TabItem("W_local Map"):
521
- w_local_display = gr.Markdown(value=get_w_local_viz)
522
- w_local_refresh = gr.Button("Refresh", size="sm")
523
-
524
- with gr.TabItem("Tension"):
525
- tension_display = gr.Markdown(value=get_tension_chart)
526
- tension_refresh = gr.Button("Refresh", size="sm")
527
-
528
- with gr.TabItem("Dream Lab"):
529
- gr.Markdown(
530
- "**Replay / Dreaming** β€” self-training without input.\n"
531
- "The field replays its learned patterns, reinforcing "
532
- "Hebbian connections and consolidating into long-term memory."
533
- )
534
- dream_cycles = gr.Slider(
535
- minimum=1, maximum=5, value=1, step=1,
536
- label="Dream cycles",
537
- )
538
- dream_steps = gr.Slider(
539
- minimum=1, maximum=20, value=5, step=1,
540
- label="Steps per cycle",
541
- )
542
- dream_btn = gr.Button("πŸ’­ Dream", variant="secondary")
543
- dream_result = gr.Markdown()
544
-
545
- # World controls
546
- gr.Markdown("---")
547
- with gr.Row():
548
- save_btn = gr.Button("πŸ’Ύ Save", size="sm")
549
- load_btn = gr.Button("πŸ“‚ Load", size="sm")
550
- reset_btn = gr.Button("πŸ—‘ Reset", size="sm", variant="stop")
551
- world_status = gr.Markdown()
552
-
553
- # ── Wire events ──
554
-
555
- def chat_wrapper(msg, history, auto_dream):
556
- if not msg.strip():
557
- return history, "", "", "", get_diagnostics(), ""
558
- resp = agent.generate_cognitive(msg, verbose=True)
559
- answer = extract_answer(resp.text)
560
-
561
- resonance_history.append({
562
- "query": msg,
563
- "resonance": resp.resonance,
564
- "retrieval": resp.retrieval_confidence,
565
- "route": resp.route,
566
- "tension": resp.tension,
567
- "steps": resp.think_steps_used,
568
- })
569
- if len(resonance_history) > MAX_HISTORY:
570
- resonance_history.pop(0)
571
-
572
- judge_text = ""
573
- if USE_JUDGE:
574
- facts = [t for t, _ in resp.retrieved_facts]
575
- verdict, score = lm_judge(msg, answer, facts)
576
- judge_text = f"**Judge:** {verdict} (score: {score:.2f})" if score >= 0 else verdict
577
-
578
- history = history + [
579
- {"role": "user", "content": msg},
580
- {"role": "assistant", "content": answer},
581
- ]
582
- route_badge = format_route_badge(resp)
583
- facts_md = format_retrieved_facts(resp)
584
-
585
- return history, route_badge, facts_md, judge_text, get_diagnostics(), ""
586
-
587
- chat_btn.click(
588
- chat_wrapper,
589
- inputs=[chat_input, chatbot, auto_dream_toggle],
590
- outputs=[chatbot, route_display, facts_display, judge_display, diag_display, chat_input],
591
- )
592
- chat_input.submit(
593
- chat_wrapper,
594
- inputs=[chat_input, chatbot, auto_dream_toggle],
595
- outputs=[chatbot, route_display, facts_display, judge_display, diag_display, chat_input],
596
- )
597
-
598
- teach_btn.click(
599
- handle_teach,
600
- inputs=[teach_input, auto_dream_toggle],
601
- outputs=[teach_result, diag_display],
602
- )
603
- teach_input.submit(
604
- handle_teach,
605
- inputs=[teach_input, auto_dream_toggle],
606
- outputs=[teach_result, diag_display],
607
- )
608
 
609
- batch_btn.click(
610
- handle_batch_teach,
611
- inputs=[batch_input, auto_dream_toggle],
612
- outputs=[batch_result, diag_display],
613
- )
614
-
615
- dream_btn.click(
616
- handle_dream,
617
- inputs=[dream_cycles, dream_steps],
618
- outputs=[dream_result, diag_display, w_local_display],
619
- )
620
 
621
- refresh_btn.click(get_diagnostics, outputs=[diag_display])
622
- resonance_refresh.click(get_resonance_chart, outputs=[resonance_display])
623
- w_local_refresh.click(get_w_local_viz, outputs=[w_local_display])
624
- tension_refresh.click(get_tension_chart, outputs=[tension_display])
 
 
 
 
 
 
 
 
 
625
 
626
- save_btn.click(handle_save, outputs=[world_status])
627
- load_btn.click(handle_load, outputs=[world_status, diag_display])
628
- reset_btn.click(handle_reset, outputs=[world_status, diag_display])
 
 
629
 
630
- return demo
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
631
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
632
 
633
- # =============================================================================
634
- # LAUNCH
635
- # =============================================================================
636
 
637
  if __name__ == "__main__":
638
- demo = build_ui()
639
  demo.launch(
640
  server_name="0.0.0.0",
641
- server_port=7860,
642
  share=False,
643
- )
 
1
  """
2
+ gradio_app.py β€” Proto-Cognitive Architecture v4
3
  HuggingFace Spaces deployment wrapper.
4
 
5
+ v4 CHANGES:
6
+ - Cognitive Router display: shows resonance score + route decision
7
+ - Resonance bar visualization (why the agent answered the way it did)
8
+ - Route-based response formatting (confident vs hedged vs deferred)
9
+ - Resonance history tracking
10
+ - Better diagnostics: routing stats alongside field/episode stats
11
+ - Cleaner teach panel with batch import
12
 
13
  Deploy to HF Spaces:
14
  1. Create a new Space (Gradio SDK, GPU T4 or better)
 
20
  transformers>=4.40
21
  gradio>=4.0
22
  numpy
 
23
  """
24
 
25
  import os
 
37
  USE_JUDGE = os.environ.get("USE_JUDGE", "false").lower() == "true"
38
 
39
  # ── Global agent ──────────────────────────────────────────────────────────────
40
+ print("Loading Proto-Cognitive Agent v4...")
41
  agent = HybridLLM()
42
  print("Agent ready.")
43
 
44
  WORLD_SAVE_PATH = "world_state.pt"
45
 
46
+ # ── Resonance history for the chart ──────────────────────────────────────────
47
  resonance_history: list[dict] = []
48
  MAX_HISTORY = 50
49
 
50
 
51
  # =============================================================================
52
+ # ROUTE FORMATTING
53
  # =============================================================================
54
 
55
  ROUTE_EMOJI = {
56
+ "CONFIDENT": "🟒",
57
+ "CAUTIOUS": "🟑",
58
+ "UNCERTAIN": "🟠",
59
+ "DEFER": "πŸ”΄",
60
  }
61
 
62
  ROUTE_LABEL = {
63
+ "CONFIDENT": "Confident β€” strong field resonance + reliable facts retrieved",
64
+ "CAUTIOUS": "Cautious β€” field recognises domain but facts are weak",
65
+ "UNCERTAIN": "Uncertain β€” some retrieval but low domain familiarity",
66
+ "DEFER": "Deferred β€” no stored knowledge about this topic",
67
  }
68
 
69
 
70
  def format_route_badge(resp: CognitiveResponse) -> str:
71
+ """Creates a visual route badge for display."""
72
  emoji = ROUTE_EMOJI.get(resp.route, "βšͺ")
73
  label = ROUTE_LABEL.get(resp.route, resp.route)
74
  return (
75
+ f"{emoji} **{resp.route}** | "
76
+ f"Resonance: `{resp.resonance:.3f}` | "
77
+ f"Retrieval: `{resp.retrieval_confidence:.3f}`\n"
 
 
78
  f"*{label}*"
79
  )
80
 
81
 
82
  def format_retrieved_facts(resp: CognitiveResponse) -> str:
83
+ """Shows which facts were retrieved and their scores."""
84
  if not resp.retrieved_facts:
85
  return "*No facts retrieved*"
86
  lines = []
 
92
 
93
 
94
  # =============================================================================
95
+ # LM STUDIO JUDGE
96
  # =============================================================================
97
 
98
  def lm_judge(question: str, answer: str, context_facts: list[str]) -> tuple[str, float]:
 
112
  {answer}
113
 
114
  Rate the answer on a scale from 0.0 to 1.0:
115
+ 1.0 = fully correct, cites the right facts
116
+ 0.5 = partially correct or vague
117
+ 0.0 = wrong, ignores the provided facts
118
 
119
  Respond with ONLY a JSON object: {{"score": 0.X, "verdict": "one sentence"}}"""
120
 
 
138
 
139
 
140
  # =============================================================================
141
+ # CORE FUNCTIONS
142
  # =============================================================================
143
 
144
  def get_diagnostics() -> str:
145
+ d = agent.world.diagnostics()
146
  ed = agent.episodes.diagnostics()
147
  tok = agent.token_report()
148
  rs = agent.router.get_routing_stats()
149
 
150
  lines = [
151
  "### Field State",
152
+ f"**Field norm:** {d['field_norm']:.3f} | "
153
  f"**Active regions:** {d['active_regions']} / {d['protected']} protected",
154
+ f"**Attractors:** {d['attractors']} | "
155
  f"**Steps:** {d['total_steps']}",
156
  "",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
157
  "### Episodic Store",
158
+ f"**Total:** {ed['total']} | "
159
+ f"**Active:** {ed['active']} | "
160
  f"**Superseded:** {ed['superseded']}",
161
  "",
162
  "### Cognitive Router",
163
+ ]
164
 
165
  if rs["total"] > 0:
166
  lines.extend([
167
  f"**Queries routed:** {rs['total']}",
168
+ f"🟒 Confident: {rs['confident']} | "
169
+ f"🟑 Cautious: {rs['cautious']} | "
170
+ f"🟠 Uncertain: {rs['uncertain']} | "
171
  f"πŸ”΄ Defer: {rs['defer']}",
172
+ f"**Avg resonance:** {rs['avg_resonance']:.4f} | "
173
  f"**Avg retrieval:** {rs['avg_retrieval']:.4f}",
174
  ])
175
  else:
 
178
  lines.extend([
179
  "",
180
  "### Token Usage",
181
+ f"**Teach:** {tok['teach_calls']} calls ({tok['teach_input_tokens']} tok) | "
182
  f"**Gen:** {tok['generate_calls']} calls",
183
+ f"**Avg in/gen:** {tok['avg_input_per_gen']} | "
184
  f"**Avg out/gen:** {tok['avg_output_per_gen']}",
185
  ])
186
 
 
 
 
 
 
 
 
 
 
 
 
 
 
187
  # Pinned facts
188
  if ed["total"] > 0:
189
  lines += ["", "### Pinned Facts"]
 
201
 
202
 
203
  def get_resonance_chart() -> str:
204
+ """Build a simple text-based resonance history chart."""
205
  if not resonance_history:
206
  return "*No resonance data yet β€” ask some questions first*"
207
 
208
+ lines = ["### Resonance History (last 20 queries)", ""]
209
  recent = resonance_history[-20:]
210
  for entry in recent:
211
  r = entry["resonance"]
212
  ret = entry["retrieval"]
213
  route = entry["route"]
 
 
214
  emoji = ROUTE_EMOJI.get(route, "βšͺ")
215
  bar_r = "β–ˆ" * int(r * 20) + "β–‘" * (20 - int(r * 20))
216
  bar_t = "β–ˆ" * int(ret * 20) + "β–‘" * (20 - int(ret * 20))
217
+ q = entry["query"][:35]
218
  lines.append(
219
+ f"{emoji} `R:{bar_r}` `S:{bar_t}` {q}"
220
  )
221
  return "\n".join(lines)
222
 
223
 
224
+ def teach_fact(fact_text: str, history: list) -> tuple[list, str, str]:
225
+ """Teach one or more facts (newline-separated) to the agent."""
226
+ if not fact_text.strip():
227
+ return history, get_diagnostics(), "⚠ Enter at least one fact."
 
 
 
 
 
 
 
 
228
 
229
+ facts = [f.strip() for f in fact_text.strip().split("\n") if f.strip()]
230
+ results = []
231
+ for fact in facts:
232
+ agent.teach(fact)
233
+ results.append(f"βœ“ Encoded: *{fact[:80]}*")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
234
 
235
+ msg = "\n".join(results)
236
+ history = history + [
237
+ {"role": "assistant",
238
+ "content": f"**Taught {len(facts)} fact(s):**\n{msg}"}
239
+ ]
240
+ return history, get_diagnostics(), ""
241
+
242
+
243
+ def chat(message: str, history: list, use_judge: bool) -> tuple[list, str, str]:
244
+ """Generate a response with cognitive routing."""
245
+ if not message.strip():
246
+ return history, get_diagnostics(), get_resonance_chart()
247
+
248
+ # Use the full cognitive generation pipeline
249
+ resp = agent.generate_cognitive(message, max_new_tokens=200, verbose=False)
250
+ answer = extract_answer(resp.text)
251
+
252
+ # Track resonance history
253
+ resonance_history.append({
254
+ "query": message,
255
+ "resonance": resp.resonance,
256
+ "retrieval": resp.retrieval_confidence,
257
+ "route": resp.route,
258
+ })
259
+ if len(resonance_history) > MAX_HISTORY:
260
+ resonance_history.pop(0)
261
+
262
+ # Build response with route badge
263
+ route_badge = format_route_badge(resp)
264
+ facts_display = format_retrieved_facts(resp)
265
+
266
+ response_parts = [answer]
267
+
268
+ # Add routing metadata (collapsible)
269
+ response_parts.append(f"\n\n---\n{route_badge}")
270
+ if resp.retrieved_facts:
271
+ response_parts.append(f"\n**Retrieved facts:**\n{facts_display}")
272
+
273
+ # Optionally judge
274
+ if use_judge and USE_JUDGE:
275
+ recalled = [t for t, _ in resp.retrieved_facts]
276
+ verdict, score = lm_judge(message, answer, recalled)
277
+ if score >= 0:
278
+ response_parts.append(f"\n*Judge: {score:.2f} β€” {verdict}*")
279
+
280
+ # Timing
281
+ response_parts.append(
282
+ f"\n*{resp.latency_s:.2f}s*"
283
+ )
284
 
285
+ full_response = "\n".join(response_parts)
286
 
287
+ history = history + [
288
+ {"role": "user", "content": message},
289
+ {"role": "assistant", "content": full_response},
290
+ ]
291
+ return history, get_diagnostics(), get_resonance_chart()
292
 
 
 
293
 
294
+ def clear_world(history: list) -> tuple[list, str, str]:
295
+ agent.reset_world()
296
+ resonance_history.clear()
297
+ history = history + [
298
+ {"role": "assistant",
299
+ "content": "πŸ”„ World state cleared. All facts, field state, and routing history reset."}
300
  ]
301
+ return history, get_diagnostics(), get_resonance_chart()
302
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
303
 
304
+ def save_world() -> str:
305
+ agent.save_world(WORLD_SAVE_PATH)
306
+ return f"βœ“ Saved to `{WORLD_SAVE_PATH}`"
 
 
 
 
 
 
 
 
 
 
 
 
 
307
 
308
+
309
+ def load_world() -> tuple[str, str]:
310
+ agent.load_world(WORLD_SAVE_PATH)
311
+ return f"βœ“ Loaded from `{WORLD_SAVE_PATH}`", get_diagnostics()
312
 
313
 
314
  # =============================================================================
315
+ # GRADIO UI
316
  # =============================================================================
317
 
318
+ DESCRIPTION = """
319
+ # Proto-Cognitive Architecture v4
320
+ **Neural Field + Hebbian Memory + Semantic Retrieval + Cognitive Router**
 
 
 
 
 
 
 
 
 
 
321
 
322
+ Teach the agent facts, then ask questions. The agent uses a continuous Hebbian
323
+ attractor field to detect **cognitive resonance** (familiarity) with learned content,
324
+ and routes queries accordingly:
325
 
326
+ | Route | Meaning |
327
+ |-------|---------|
328
+ | 🟒 **CONFIDENT** | Strong field resonance + reliable facts β†’ full answer |
329
+ | 🟑 **CAUTIOUS** | Field recognises domain but weak retrieval β†’ hedged answer |
330
+ | 🟠 **UNCERTAIN** | Some retrieval but low familiarity β†’ answer with caveats |
331
+ | πŸ”΄ **DEFER** | No stored knowledge β†’ admits ignorance |
332
 
333
+ **Key properties:** Zero forgetting Β· Paraphrase robustness Β· O(1) memory Β·
334
+ Contradiction handling Β· Introspectable routing decisions
335
+ """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
336
 
337
+ with gr.Blocks(title="Proto-Cognitive LLM v4", theme=gr.themes.Soft()) as demo:
338
 
339
+ gr.Markdown(DESCRIPTION)
 
 
340
 
341
+ with gr.Row():
 
 
 
 
 
 
 
 
 
 
 
 
342
 
343
+ # ── Left column: main interface ───────────────────────────────────────
344
+ with gr.Column(scale=3):
 
345
 
346
+ # Teach panel
347
+ with gr.Accordion("πŸ“š Teach facts", open=True):
348
+ gr.Markdown(
349
+ "*One fact per line. Facts are encoded into the Hebbian field "
350
+ "and pinned to the semantic store. Teaching a contradictory "
351
+ "fact automatically supersedes the old one.*"
352
  )
353
+ teach_input = gr.Textbox(
354
+ label="Facts to teach (one per line)",
355
+ placeholder=(
356
+ "Server alpha IP is 10.0.0.42.\n"
357
+ "Dr. Singh leads NEXUS-7.\n"
358
+ "The API rate limit is 500 requests per minute."
359
+ ),
360
+ lines=4,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
361
  )
362
+ teach_status = gr.Markdown("")
363
+ teach_btn = gr.Button("Encode facts", variant="primary")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
364
 
365
+ gr.Markdown("---")
 
 
 
 
 
 
 
 
 
 
366
 
367
+ # Chat
368
+ chatbot = gr.Chatbot(
369
+ label="Chat with agent",
370
+ type="messages",
371
+ height=450,
372
+ )
373
+ with gr.Row():
374
+ chat_input = gr.Textbox(
375
+ label="Your question",
376
+ placeholder="What is the IP of server alpha?",
377
+ scale=4,
378
+ )
379
+ send_btn = gr.Button("Send", variant="primary", scale=1)
380
 
381
+ use_judge_cb = gr.Checkbox(
382
+ label=f"Enable LM Studio judge ({LM_JUDGE_MODEL})",
383
+ value=False,
384
+ visible=USE_JUDGE,
385
+ )
386
 
387
+ with gr.Row():
388
+ clear_btn = gr.Button("πŸ”„ Clear world", variant="stop")
389
+ save_btn = gr.Button("πŸ’Ύ Save world")
390
+ load_btn = gr.Button("πŸ“‚ Load world")
391
+
392
+ save_status = gr.Markdown("")
393
+
394
+ # ── Right column: diagnostics ─────────────────────────────────────────
395
+ with gr.Column(scale=2):
396
+ with gr.Tab("🧠 World State"):
397
+ diagnostics_md = gr.Markdown(get_diagnostics())
398
+ refresh_btn = gr.Button("Refresh", size="sm")
399
+
400
+ with gr.Tab("πŸ“Š Resonance History"):
401
+ resonance_md = gr.Markdown(get_resonance_chart())
402
+ refresh_res_btn = gr.Button("Refresh", size="sm")
403
+
404
+ # ── Example prompts ───────────────────────────────────────────────────────
405
+ gr.Markdown("---\n### Examples")
406
+ gr.Examples(
407
+ examples=[
408
+ [
409
+ "Server alpha has IP 10.0.0.42 and runs the inference backend.\n"
410
+ "Server beta has IP 10.0.0.43 and handles the load balancer.\n"
411
+ "The GPU cluster uses VLAN 201.",
412
+ "What IP does server alpha use?",
413
+ ],
414
+ [
415
+ "The master encryption key ID is ENC-KEY-2025-ALPHA-7742.",
416
+ "Our main credential storage is compromised. "
417
+ "What is the identifier of the master encryption key?",
418
+ ],
419
+ [
420
+ "Marcus Reyes is the project lead for NEXUS-7.\n"
421
+ "Dr. Amara Singh has replaced Marcus Reyes as project lead for NEXUS-7.",
422
+ "Who is the current project lead for NEXUS-7?",
423
+ ],
424
+ [
425
+ "",
426
+ "What is the weather in Hamburg today?",
427
+ ],
428
+ ],
429
+ inputs=[teach_input, chat_input],
430
+ label="Click to load an example (last one tests DEFER route β€” no taught facts)",
431
+ )
432
 
433
+ # ── Event wiring ──────────────────────────────────────────────────────────
434
+ chatbot_state = gr.State([])
435
+
436
+ teach_btn.click(
437
+ teach_fact,
438
+ inputs=[teach_input, chatbot_state],
439
+ outputs=[chatbot_state, diagnostics_md, teach_status],
440
+ ).then(lambda h: h, chatbot_state, chatbot)
441
+
442
+ send_btn.click(
443
+ chat,
444
+ inputs=[chat_input, chatbot_state, use_judge_cb],
445
+ outputs=[chatbot_state, diagnostics_md, resonance_md],
446
+ ).then(lambda h: h, chatbot_state, chatbot
447
+ ).then(lambda: "", None, chat_input)
448
+
449
+ chat_input.submit(
450
+ chat,
451
+ inputs=[chat_input, chatbot_state, use_judge_cb],
452
+ outputs=[chatbot_state, diagnostics_md, resonance_md],
453
+ ).then(lambda h: h, chatbot_state, chatbot
454
+ ).then(lambda: "", None, chat_input)
455
+
456
+ clear_btn.click(
457
+ clear_world,
458
+ inputs=[chatbot_state],
459
+ outputs=[chatbot_state, diagnostics_md, resonance_md],
460
+ ).then(lambda h: h, chatbot_state, chatbot)
461
+
462
+ save_btn.click(save_world, outputs=[save_status])
463
+ load_btn.click(load_world, outputs=[save_status, diagnostics_md])
464
+ refresh_btn.click(get_diagnostics, outputs=[diagnostics_md])
465
+ refresh_res_btn.click(get_resonance_chart, outputs=[resonance_md])
466
 
 
 
 
467
 
468
  if __name__ == "__main__":
 
469
  demo.launch(
470
  server_name="0.0.0.0",
471
+ server_port=int(os.environ.get("PORT", 7860)),
472
  share=False,
473
+ )