alaatiger989 commited on
Commit
8f37414
·
verified ·
1 Parent(s): e2c457c

Upload 3 files

Browse files
Files changed (3) hide show
  1. ace_system.py +703 -0
  2. app.py +804 -0
  3. emergency_playbook.json +517 -0
ace_system.py ADDED
@@ -0,0 +1,703 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ ACE (Agentic Context Engineering) System with Ollama
3
+ A self-improving AI agent system using local LLMs
4
+ """
5
+
6
+ import json
7
+ import os
8
+ from datetime import datetime
9
+ from typing import List, Dict, Optional, Literal
10
+ from dataclasses import dataclass, asdict
11
+ from enum import Enum
12
+ import requests
13
+
14
+
15
+ # ============================================================================
16
+ # CONFIGURATION
17
+ # ============================================================================
18
+
19
+ class Config:
20
+ """System configuration"""
21
+ OLLAMA_BASE_URL = "http://localhost:11434"
22
+ GENERATOR_MODEL = "aya" # Fast for generation
23
+ REFLECTOR_MODEL = "aya" # Can use same or different
24
+ CURATOR_MODEL = "aya" # Can use same or different
25
+ PLAYBOOK_PATH = "emergency_playbook.json"
26
+ TEMPERATURE = 0.7
27
+ MAX_TOKENS = 2000
28
+ # Update Config:
29
+ # class Config:
30
+ # """System configuration"""
31
+ # OLLAMA_BASE_URL = "http://localhost:11434"
32
+ # GENERATOR_MODEL = "llama3.1"
33
+ # REFLECTOR_MODEL = "llama3.1"
34
+ # CURATOR_MODEL = "llama3.1"
35
+ # PLAYBOOK_PATH = "emergency_playbook.json"
36
+ # TEMPERATURE = 0.3
37
+ # MAX_TOKENS = 4000 # Increased
38
+
39
+ # ============================================================================
40
+ # DATA MODELS
41
+ # ============================================================================
42
+
43
+ class TagType(str, Enum):
44
+ HELPFUL = "helpful"
45
+ HARMFUL = "harmful"
46
+ NEUTRAL = "neutral"
47
+
48
+
49
+ @dataclass
50
+ class Bullet:
51
+ """A knowledge item in the playbook"""
52
+ id: str
53
+ section: str
54
+ content: str
55
+ helpful: int = 0
56
+ harmful: int = 0
57
+ neutral: int = 0
58
+ created_at: str = ""
59
+ updated_at: str = ""
60
+
61
+ def __post_init__(self):
62
+ if not self.created_at:
63
+ self.created_at = datetime.now().isoformat()
64
+ if not self.updated_at:
65
+ self.updated_at = datetime.now().isoformat()
66
+
67
+ def add_tag(self, tag: TagType):
68
+ """Add a tag vote to this bullet"""
69
+ if tag == TagType.HELPFUL:
70
+ self.helpful += 1
71
+ elif tag == TagType.HARMFUL:
72
+ self.harmful += 1
73
+ else:
74
+ self.neutral += 1
75
+ self.updated_at = datetime.now().isoformat()
76
+
77
+ def score(self) -> float:
78
+ """Calculate bullet quality score"""
79
+ total = self.helpful + self.harmful + self.neutral
80
+ if total == 0:
81
+ return 0.0
82
+ return (self.helpful - self.harmful) / total
83
+
84
+
85
+ @dataclass
86
+ class BulletTag:
87
+ """Tag assignment for a bullet"""
88
+ bullet_id: str
89
+ tag: TagType
90
+ reason: str
91
+
92
+
93
+ @dataclass
94
+ class GeneratorOutput:
95
+ """Output from the Generator agent"""
96
+ reasoning: List[str]
97
+ bullet_ids: List[str]
98
+ final_answer: str
99
+
100
+
101
+ @dataclass
102
+ class Reflection:
103
+ """Output from the Reflector agent"""
104
+ answer_quality: str
105
+ strengths: List[str]
106
+ weaknesses: List[str]
107
+ bullet_tags: List[BulletTag]
108
+
109
+
110
+ @dataclass
111
+ class DeltaOperation:
112
+ """A single playbook modification operation"""
113
+ type: Literal["ADD", "UPDATE", "REMOVE"]
114
+ section: str
115
+ content: Optional[str] = None
116
+ bullet_id: Optional[str] = None
117
+
118
+
119
+ @dataclass
120
+ class DeltaBatch:
121
+ """Batch of playbook modifications"""
122
+ reasoning: str
123
+ operations: List[DeltaOperation]
124
+
125
+
126
+ # ============================================================================
127
+ # PLAYBOOK MANAGEMENT
128
+ # ============================================================================
129
+
130
+ class Playbook:
131
+ """Manages the evolving knowledge base"""
132
+
133
+ def __init__(self):
134
+ self.bullets: Dict[str, Bullet] = {}
135
+ self.sections: Dict[str, List[str]] = {}
136
+ self._next_id = 1
137
+
138
+ def add_bullet(self, section: str, content: str) -> str:
139
+ """Add a new bullet to the playbook"""
140
+ bullet_id = f"B{self._next_id:04d}"
141
+ self._next_id += 1
142
+
143
+ bullet = Bullet(
144
+ id=bullet_id,
145
+ section=section,
146
+ content=content
147
+ )
148
+ self.bullets[bullet_id] = bullet
149
+
150
+ if section not in self.sections:
151
+ self.sections[section] = []
152
+ self.sections[section].append(bullet_id)
153
+
154
+ return bullet_id
155
+
156
+ def update_bullet(self, bullet_id: str, content: str):
157
+ """Update an existing bullet"""
158
+ if bullet_id in self.bullets:
159
+ self.bullets[bullet_id].content = content
160
+ self.bullets[bullet_id].updated_at = datetime.now().isoformat()
161
+
162
+ def remove_bullet(self, bullet_id: str):
163
+ """Remove a bullet from the playbook"""
164
+ if bullet_id in self.bullets:
165
+ bullet = self.bullets[bullet_id]
166
+ section = bullet.section
167
+
168
+ del self.bullets[bullet_id]
169
+ if section in self.sections:
170
+ self.sections[section] = [
171
+ bid for bid in self.sections[section] if bid != bullet_id
172
+ ]
173
+
174
+ def update_bullet_tag(self, bullet_id: str, tag: TagType):
175
+ """Add a tag to a bullet"""
176
+ if bullet_id in self.bullets:
177
+ self.bullets[bullet_id].add_tag(tag)
178
+
179
+ def apply_delta(self, delta: DeltaBatch):
180
+ """Apply a batch of modifications"""
181
+ for op in delta.operations:
182
+ if op.type == "ADD" and op.content:
183
+ self.add_bullet(op.section, op.content)
184
+ elif op.type == "UPDATE" and op.bullet_id and op.content:
185
+ self.update_bullet(op.bullet_id, op.content)
186
+ elif op.type == "REMOVE" and op.bullet_id:
187
+ self.remove_bullet(op.bullet_id)
188
+
189
+ def as_prompt(self) -> str:
190
+ """Format playbook for inclusion in prompts"""
191
+ if not self.bullets:
192
+ return "No knowledge bullets available yet."
193
+
194
+ lines = ["# Knowledge Playbook", ""]
195
+ for section, bullet_ids in sorted(self.sections.items()):
196
+ lines.append(f"## {section}")
197
+ for bid in bullet_ids:
198
+ bullet = self.bullets[bid]
199
+ score = bullet.score()
200
+ lines.append(f"- [{bid}] {bullet.content} (score: {score:.2f})")
201
+ lines.append("")
202
+
203
+ return "\n".join(lines)
204
+
205
+ def stats(self) -> Dict:
206
+ """Get playbook statistics"""
207
+ total_bullets = len(self.bullets)
208
+ total_tags = sum(b.helpful + b.harmful + b.neutral for b in self.bullets.values())
209
+ avg_score = sum(b.score() for b in self.bullets.values()) / total_bullets if total_bullets > 0 else 0
210
+
211
+ return {
212
+ "total_bullets": total_bullets,
213
+ "total_sections": len(self.sections),
214
+ "total_tags": total_tags,
215
+ "average_score": avg_score
216
+ }
217
+
218
+ def save(self, filepath: str):
219
+ """Save playbook to disk"""
220
+ data = {
221
+ "bullets": {bid: asdict(b) for bid, b in self.bullets.items()},
222
+ "sections": self.sections,
223
+ "next_id": self._next_id
224
+ }
225
+ with open(filepath, 'w') as f:
226
+ json.dump(data, f, indent=2)
227
+
228
+ @classmethod
229
+ def load(cls, filepath: str) -> 'Playbook':
230
+ """Load playbook from disk"""
231
+ playbook = cls()
232
+ if os.path.exists(filepath):
233
+ with open(filepath, 'r') as f:
234
+ data = json.load(f)
235
+
236
+ playbook.bullets = {
237
+ bid: Bullet(**bullet_data)
238
+ for bid, bullet_data in data.get("bullets", {}).items()
239
+ }
240
+ playbook.sections = data.get("sections", {})
241
+ playbook._next_id = data.get("next_id", 1)
242
+
243
+ return playbook
244
+
245
+
246
+ # ============================================================================
247
+ # OLLAMA CLIENT
248
+ # ============================================================================
249
+
250
+ class OllamaClient:
251
+ """Client for interacting with Ollama"""
252
+
253
+ def __init__(self, base_url: str = Config.OLLAMA_BASE_URL):
254
+ self.base_url = base_url
255
+ def generate(
256
+ self,
257
+ model: str,
258
+ prompt: str,
259
+ system: Optional[str] = None,
260
+ temperature: float = Config.TEMPERATURE,
261
+ max_tokens: int = Config.MAX_TOKENS
262
+ ) -> str:
263
+ """Generate completion from Ollama"""
264
+ url = f"{self.base_url}/api/generate"
265
+
266
+ payload = {
267
+ "model": model,
268
+ "prompt": prompt,
269
+ "stream": False,
270
+ "options": {
271
+ "temperature": temperature,
272
+ "num_predict": max_tokens,
273
+ "num_ctx": 8192 # Added: Larger context window
274
+ }
275
+ }
276
+
277
+ if system:
278
+ payload["system"] = system
279
+
280
+ try:
281
+ response = requests.post(url, json=payload, timeout=180) # Increased timeout
282
+ response.raise_for_status()
283
+ return response.json()["response"]
284
+ except Exception as e:
285
+ print(f"Error calling Ollama: {e}")
286
+ return ""
287
+
288
+ def check_health(self) -> bool:
289
+ """Check if Ollama is running"""
290
+ try:
291
+ response = requests.get(f"{self.base_url}/api/tags", timeout=5)
292
+ return response.status_code == 200
293
+ except:
294
+ return False
295
+
296
+
297
+ # ============================================================================
298
+ # AGENTS
299
+ # ============================================================================
300
+
301
+ class StateInitializer:
302
+ """Initializes session state"""
303
+
304
+ def execute(self, user_query: str, playbook: Playbook) -> Dict:
305
+ """Initialize state for a new query"""
306
+ return {
307
+ "user_query": user_query,
308
+ "playbook": playbook,
309
+ "ground_truth": None,
310
+ "generator_output": None,
311
+ "reflector_output": None,
312
+ "curator_output": None
313
+ }
314
+
315
+ class Generator:
316
+ """Generates answers using the playbook"""
317
+
318
+ def __init__(self, client: OllamaClient):
319
+ self.client = client
320
+ def execute(self, state: Dict) -> GeneratorOutput:
321
+ """Generate an answer with reasoning"""
322
+ user_query = state["user_query"]
323
+ playbook = state["playbook"]
324
+
325
+ # First, find relevant bullets
326
+ bullet_context = []
327
+ for bid, bullet in playbook.bullets.items():
328
+ bullet_context.append(f"[{bid}] {bullet.content}")
329
+
330
+ knowledge = "\n".join(bullet_context[:50]) # Use up to 50 most relevant
331
+
332
+ # Simple, direct prompt
333
+ prompt = f"""You are an emergency response expert.
334
+
335
+ Question: {user_query}
336
+
337
+ Available Knowledge:
338
+ {knowledge}
339
+
340
+ Provide a COMPLETE, detailed answer with ALL necessary steps. Be thorough and specific."""
341
+
342
+ response = self.client.generate(
343
+ model=Config.GENERATOR_MODEL,
344
+ prompt=prompt,
345
+ system="Provide complete, detailed emergency instructions. Never truncate your answer.",
346
+ temperature=0.3,
347
+ max_tokens=4000
348
+ )
349
+
350
+ # Find relevant bullet IDs in response
351
+ used_bullets = []
352
+ if response and isinstance(response, str):
353
+ response_lower = response.lower()
354
+ for bid, bullet in playbook.bullets.items():
355
+ bullet_preview = str(bullet.content)[:30].lower()
356
+ if bid in response or bullet_preview in response_lower:
357
+ used_bullets.append(bid)
358
+
359
+ return GeneratorOutput(
360
+ reasoning=["Analyzed emergency situation", "Found relevant protocols", "Provided complete response"],
361
+ bullet_ids=used_bullets,
362
+ final_answer=response if response else "Unable to generate response"
363
+ )
364
+
365
+ class Reflector:
366
+ """Reflects on generated output and tags bullets"""
367
+
368
+ def __init__(self, client: OllamaClient):
369
+ self.client = client
370
+
371
+ def execute(self, state: Dict) -> Reflection:
372
+ """Reflect on the generator's output"""
373
+ user_query = state["user_query"]
374
+ gen_output = state["generator_output"]
375
+ playbook = state["playbook"]
376
+
377
+ system_prompt = """You are a critical evaluator that assesses answer quality and tags knowledge bullets.
378
+
379
+ INSTRUCTIONS:
380
+ 1. Evaluate the quality of the generated answer
381
+ 2. Identify what worked well and what didn't
382
+ 3. Tag each referenced bullet as:
383
+ - "helpful": Contributed positively to the answer
384
+ - "harmful": Led to errors or poor quality
385
+ - "neutral": Was referenced but had minimal impact
386
+
387
+ Respond in JSON format:
388
+ {
389
+ "answer_quality": "excellent|good|fair|poor",
390
+ "strengths": ["strength 1", "strength 2", ...],
391
+ "weaknesses": ["weakness 1", "weakness 2", ...],
392
+ "bullet_tags": [
393
+ {"bullet_id": "B0001", "tag": "helpful", "reason": "why"},
394
+ ...
395
+ ]
396
+ }"""
397
+
398
+ # Build bullet context
399
+ bullet_context = "\n".join([
400
+ f"[{bid}] {playbook.bullets[bid].content}"
401
+ for bid in gen_output.bullet_ids
402
+ if bid in playbook.bullets
403
+ ])
404
+
405
+ prompt = f"""# User Query
406
+ {user_query}
407
+
408
+ # Referenced Bullets
409
+ {bullet_context if bullet_context else "None"}
410
+
411
+ # Generated Answer
412
+ Reasoning: {gen_output.reasoning}
413
+ Final Answer: {gen_output.final_answer}
414
+
415
+ # Your Evaluation (JSON only):"""
416
+
417
+ response = self.client.generate(
418
+ model=Config.REFLECTOR_MODEL,
419
+ prompt=prompt,
420
+ system=system_prompt
421
+ )
422
+
423
+ # Parse JSON response
424
+ try:
425
+ if "```json" in response:
426
+ response = response.split("```json")[1].split("```")[0].strip()
427
+ elif "```" in response:
428
+ response = response.split("```")[1].split("```")[0].strip()
429
+
430
+ data = json.loads(response)
431
+ bullet_tags = [
432
+ BulletTag(
433
+ bullet_id=bt["bullet_id"],
434
+ tag=TagType(bt["tag"]),
435
+ reason=bt.get("reason", "")
436
+ )
437
+ for bt in data.get("bullet_tags", [])
438
+ ]
439
+
440
+ return Reflection(
441
+ answer_quality=data.get("answer_quality", "unknown"),
442
+ strengths=data.get("strengths", []),
443
+ weaknesses=data.get("weaknesses", []),
444
+ bullet_tags=bullet_tags
445
+ )
446
+ except json.JSONDecodeError as e:
447
+ print(f"JSON parse error: {e}")
448
+ print(f"Raw response: {response}")
449
+ return Reflection(
450
+ answer_quality="error",
451
+ strengths=[],
452
+ weaknesses=["Failed to parse reflection"],
453
+ bullet_tags=[]
454
+ )
455
+
456
+
457
+ class Curator:
458
+ """Curates the playbook based on reflections"""
459
+
460
+ def __init__(self, client: OllamaClient):
461
+ self.client = client
462
+
463
+ def execute(self, state: Dict) -> DeltaBatch:
464
+ """Generate playbook modifications"""
465
+ user_query = state["user_query"]
466
+ reflection = state["reflector_output"]
467
+ playbook = state["playbook"]
468
+
469
+ system_prompt = """You are a knowledge curator that improves the playbook.
470
+
471
+ INSTRUCTIONS:
472
+ 1. Review the reflection and current playbook
473
+ 2. Decide what changes to make:
474
+ - ADD: Create new bullets for missing knowledge
475
+ - UPDATE: Improve existing bullets
476
+ - REMOVE: Delete harmful or redundant bullets
477
+ 3. Focus on bullets with consistent tags
478
+
479
+ Respond in JSON format:
480
+ {
481
+ "reasoning": "Why these changes improve the playbook",
482
+ "operations": [
483
+ {"type": "ADD", "section": "Section Name", "content": "New bullet content"},
484
+ {"type": "UPDATE", "section": "Section Name", "bullet_id": "B0001", "content": "Updated content"},
485
+ {"type": "REMOVE", "section": "Section Name", "bullet_id": "B0002"}
486
+ ]
487
+ }"""
488
+
489
+ # Build tag summary
490
+ tag_summary = "\n".join([
491
+ f"[{bt.bullet_id}] {bt.tag.value}: {bt.reason}"
492
+ for bt in reflection.bullet_tags
493
+ ])
494
+
495
+ prompt = f"""# Query Context
496
+ User Query: {user_query}
497
+
498
+ # Reflection Summary
499
+ Quality: {reflection.answer_quality}
500
+ Strengths: {reflection.strengths}
501
+ Weaknesses: {reflection.weaknesses}
502
+
503
+ # Bullet Tags
504
+ {tag_summary if tag_summary else "No bullets were tagged"}
505
+
506
+ # Current Playbook Stats
507
+ {json.dumps(playbook.stats(), indent=2)}
508
+
509
+ # Your Curation Plan (JSON only):"""
510
+
511
+ response = self.client.generate(
512
+ model=Config.CURATOR_MODEL,
513
+ prompt=prompt,
514
+ system=system_prompt
515
+ )
516
+
517
+ # Parse JSON response
518
+ try:
519
+ if "```json" in response:
520
+ response = response.split("```json")[1].split("```")[0].strip()
521
+ elif "```" in response:
522
+ response = response.split("```")[1].split("```")[0].strip()
523
+
524
+ data = json.loads(response)
525
+ operations = [
526
+ DeltaOperation(
527
+ type=op["type"],
528
+ section=op.get("section", "General"),
529
+ content=op.get("content"),
530
+ bullet_id=op.get("bullet_id")
531
+ )
532
+ for op in data.get("operations", [])
533
+ ]
534
+
535
+ return DeltaBatch(
536
+ reasoning=data.get("reasoning", ""),
537
+ operations=operations
538
+ )
539
+ except json.JSONDecodeError as e:
540
+ print(f"JSON parse error: {e}")
541
+ print(f"Raw response: {response}")
542
+ return DeltaBatch(
543
+ reasoning="Error parsing curation plan",
544
+ operations=[]
545
+ )
546
+
547
+
548
+ # ============================================================================
549
+ # ACE ORCHESTRATOR
550
+ # ============================================================================
551
+
552
+ class ACEOrchestrator:
553
+ """Orchestrates the full ACE cycle"""
554
+
555
+ def __init__(self, playbook_path: str = Config.PLAYBOOK_PATH):
556
+ self.client = OllamaClient()
557
+ self.playbook = Playbook.load(playbook_path)
558
+ self.playbook_path = playbook_path
559
+
560
+ self.state_initializer = StateInitializer()
561
+ self.generator = Generator(self.client)
562
+ self.reflector = Reflector(self.client)
563
+ self.curator = Curator(self.client)
564
+
565
+ def run_cycle(self, user_query: str, verbose: bool = True) -> Dict:
566
+ """Run one complete ACE cycle"""
567
+
568
+ if verbose:
569
+ print("\n" + "="*60)
570
+ print("ACE CYCLE START")
571
+ print("="*60)
572
+
573
+ # 1. Initialize State
574
+ if verbose:
575
+ print("\n[1] Initializing state...")
576
+ state = self.state_initializer.execute(user_query, self.playbook)
577
+
578
+ # 2. Generate Answer
579
+ if verbose:
580
+ print("[2] Generating answer...")
581
+ gen_output = self.generator.execute(state)
582
+ state["generator_output"] = gen_output
583
+
584
+ if verbose:
585
+ print(f"\n--- GENERATOR OUTPUT ---")
586
+ print(f"Reasoning: {gen_output.reasoning}")
587
+ print(f"Bullets Used: {gen_output.bullet_ids}")
588
+ print(f"Answer: {gen_output.final_answer}")
589
+
590
+ # 3. Reflect
591
+ if verbose:
592
+ print("\n[3] Reflecting on output...")
593
+ reflection = self.reflector.execute(state)
594
+ state["reflector_output"] = reflection
595
+
596
+ # Apply tags to playbook
597
+ for bt in reflection.bullet_tags:
598
+ self.playbook.update_bullet_tag(bt.bullet_id, bt.tag)
599
+
600
+ if verbose:
601
+ print(f"\n--- REFLECTION ---")
602
+ print(f"Quality: {reflection.answer_quality}")
603
+ print(f"Strengths: {reflection.strengths}")
604
+ print(f"Weaknesses: {reflection.weaknesses}")
605
+ print(f"Tags Applied: {len(reflection.bullet_tags)}")
606
+
607
+ # 4. Curate Playbook
608
+ if verbose:
609
+ print("\n[4] Curating playbook...")
610
+ delta = self.curator.execute(state)
611
+ state["curator_output"] = delta
612
+
613
+ # Apply delta to playbook
614
+ self.playbook.apply_delta(delta)
615
+
616
+ if verbose:
617
+ print(f"\n--- CURATION ---")
618
+ print(f"Reasoning: {delta.reasoning}")
619
+ print(f"Operations: {len(delta.operations)}")
620
+ for op in delta.operations:
621
+ print(f" - {op.type}: {op.section}")
622
+
623
+ # 5. Save Playbook
624
+ self.playbook.save(self.playbook_path)
625
+
626
+ if verbose:
627
+ print(f"\n--- PLAYBOOK STATS ---")
628
+ stats = self.playbook.stats()
629
+ for key, value in stats.items():
630
+ print(f" {key}: {value}")
631
+
632
+ print("\n" + "="*60)
633
+ print("ACE CYCLE COMPLETE")
634
+ print("="*60 + "\n")
635
+
636
+ return {
637
+ "answer": gen_output.final_answer,
638
+ "quality": reflection.answer_quality,
639
+ "operations": len(delta.operations),
640
+ "stats": self.playbook.stats()
641
+ }
642
+
643
+
644
+ # ============================================================================
645
+ # MAIN ENTRY POINT
646
+ # ============================================================================
647
+
648
+ def main():
649
+ """Main entry point for the ACE system"""
650
+
651
+ print("ACE System with Ollama")
652
+ print("=" * 60)
653
+
654
+ # Check Ollama connection
655
+ client = OllamaClient()
656
+ if not client.check_health():
657
+ print("ERROR: Cannot connect to Ollama!")
658
+ print(f"Make sure Ollama is running at {Config.OLLAMA_BASE_URL}")
659
+ print("Start it with: ollama serve")
660
+ return
661
+
662
+ print("✓ Connected to Ollama")
663
+
664
+ # Initialize orchestrator
665
+ ace = ACEOrchestrator()
666
+ print(f"✓ Loaded playbook: {ace.playbook.stats()}")
667
+
668
+ # Interactive loop
669
+ print("\nACE System Ready! (Type 'quit' to exit, 'stats' for playbook stats)")
670
+ print("-" * 60)
671
+
672
+ while True:
673
+ try:
674
+ user_input = input("\nYour query: ").strip()
675
+
676
+ if not user_input:
677
+ continue
678
+
679
+ if user_input.lower() == 'quit':
680
+ print("Goodbye!")
681
+ break
682
+
683
+ if user_input.lower() == 'stats':
684
+ print("\nPlaybook Statistics:")
685
+ print(json.dumps(ace.playbook.stats(), indent=2))
686
+ print("\nPlaybook Content:")
687
+ print(ace.playbook.as_prompt())
688
+ continue
689
+
690
+ # Run ACE cycle
691
+ result = ace.run_cycle(user_input, verbose=True)
692
+
693
+ except KeyboardInterrupt:
694
+ print("\n\nGoodbye!")
695
+ break
696
+ except Exception as e:
697
+ print(f"\nError: {e}")
698
+ import traceback
699
+ traceback.print_exc()
700
+
701
+
702
+ if __name__ == "__main__":
703
+ main()
app.py ADDED
@@ -0,0 +1,804 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ ACE System - Streamlit Web Interface
3
+ Self-improving AI agent with beautiful UI
4
+ """
5
+
6
+ import streamlit as st
7
+ import json
8
+ import os
9
+ from datetime import datetime
10
+ from typing import List, Dict, Optional, Literal
11
+ from dataclasses import dataclass, asdict
12
+ from enum import Enum
13
+ import requests
14
+ import plotly.graph_objects as go
15
+ import plotly.express as px
16
+ from collections import defaultdict
17
+
18
+ # ============================================================================
19
+ # CONFIGURATION
20
+ # ============================================================================
21
+
22
+ class Config:
23
+ """System configuration"""
24
+ OLLAMA_BASE_URL = "http://localhost:11434"
25
+ # GENERATOR_MODEL = "llama3.2:3b"
26
+ # REFLECTOR_MODEL = "llama3.2:3b"
27
+ # CURATOR_MODEL = "llama3.2:3b"
28
+ GENERATOR_MODEL = "aya"
29
+ REFLECTOR_MODEL = "aya"
30
+ CURATOR_MODEL = "aya"
31
+ PLAYBOOK_PATH = "emergency_playbook.json"
32
+ TEMPERATURE = 0.3
33
+ MAX_TOKENS = 4000
34
+
35
+
36
+ class Language:
37
+ """Language settings"""
38
+ MESSAGES = {
39
+ "en": {
40
+ "title": "🤖 ACE - Self-Improving AI Agent",
41
+ "subtitle": "Agentic Context Engineering System",
42
+ "sidebar_title": "⚙️ Settings",
43
+ "language": "Language",
44
+ "model_settings": "Model Settings",
45
+ "playbook_info": "📚 Playbook Information",
46
+ "total_bullets": "Total Knowledge Items",
47
+ "sections": "Sections",
48
+ "avg_score": "Average Quality Score",
49
+ "total_tags": "Total Evaluations",
50
+ "query_input": "💬 Ask me anything about emergencies...",
51
+ "ask_button": "🚀 Get Answer",
52
+ "clear_button": "🗑️ Clear History",
53
+ "export_button": "💾 Export Playbook",
54
+ "import_button": "📥 Import Playbook",
55
+ "chat_history": "💬 Chat History",
56
+ "answer": "Answer",
57
+ "quality": "Quality",
58
+ "reasoning": "Reasoning Process",
59
+ "bullets_used": "Knowledge Used",
60
+ "improvements": "System Improvements",
61
+ "playbook_viz": "📊 Knowledge Evolution",
62
+ "section_dist": "Knowledge Distribution by Section",
63
+ "quality_trend": "Quality Score Trends",
64
+ "connection_error": "❌ Cannot connect to Ollama!",
65
+ "connection_success": "✅ Connected to Ollama",
66
+ "processing": "🔄 Processing your query...",
67
+ "generator_phase": "Generating answer...",
68
+ "reflector_phase": "Evaluating quality...",
69
+ "curator_phase": "Learning improvements...",
70
+ "complete": "✅ Complete!",
71
+ },
72
+ "ar": {
73
+ "title": "🤖 ACE - نظام ذكي ذاتي التطوير",
74
+ "subtitle": "نظام هندسة السياق الوكيل",
75
+ "sidebar_title": "⚙️ الإعدادات",
76
+ "language": "اللغة",
77
+ "model_settings": "إعدادات النموذج",
78
+ "playbook_info": "📚 معلومات دفتر المعرفة",
79
+ "total_bullets": "إجمالي عناصر المعرفة",
80
+ "sections": "الأقسام",
81
+ "avg_score": "متوسط درجة الجودة",
82
+ "total_tags": "إجمالي التقييمات",
83
+ "query_input": "💬 اسألني أي شيء عن حالات الطوارئ...",
84
+ "ask_button": "🚀 احصل على إجابة",
85
+ "clear_button": "🗑️ مسح السجل",
86
+ "export_button": "💾 تصدير دفتر المعرفة",
87
+ "import_button": "📥 استيراد دفتر المعرفة",
88
+ "chat_history": "💬 سجل المحادثة",
89
+ "answer": "الإجابة",
90
+ "quality": "الجودة",
91
+ "reasoning": "عملية التفكير",
92
+ "bullets_used": "المعرفة المستخدمة",
93
+ "improvements": "تحسينات النظام",
94
+ "playbook_viz": "📊 تطور المعرفة",
95
+ "section_dist": "توزيع المعرفة حسب القسم",
96
+ "quality_trend": "اتجاهات درجة الجودة",
97
+ "connection_error": "❌ لا يمكن الاتصال بـ Ollama!",
98
+ "connection_success": "✅ تم الاتصال بـ Ollama",
99
+ "processing": "🔄 جاري معالجة استفسارك...",
100
+ "generator_phase": "توليد الإجابة...",
101
+ "reflector_phase": "تقييم الجودة...",
102
+ "curator_phase": "تعلم التحسينات...",
103
+ "complete": "✅ اكتمل!",
104
+ }
105
+ }
106
+
107
+ @staticmethod
108
+ def get(key, lang="en"):
109
+ return Language.MESSAGES.get(lang, Language.MESSAGES["en"]).get(key, key)
110
+
111
+
112
+ # ============================================================================
113
+ # DATA MODELS
114
+ # ============================================================================
115
+
116
+ class TagType(str, Enum):
117
+ HELPFUL = "helpful"
118
+ HARMFUL = "harmful"
119
+ NEUTRAL = "neutral"
120
+
121
+
122
+ @dataclass
123
+ class Bullet:
124
+ id: str
125
+ section: str
126
+ content: str
127
+ helpful: int = 0
128
+ harmful: int = 0
129
+ neutral: int = 0
130
+ created_at: str = ""
131
+ updated_at: str = ""
132
+
133
+ def __post_init__(self):
134
+ if not self.created_at:
135
+ self.created_at = datetime.now().isoformat()
136
+ if not self.updated_at:
137
+ self.updated_at = datetime.now().isoformat()
138
+
139
+ def add_tag(self, tag: TagType):
140
+ if tag == TagType.HELPFUL:
141
+ self.helpful += 1
142
+ elif tag == TagType.HARMFUL:
143
+ self.harmful += 1
144
+ else:
145
+ self.neutral += 1
146
+ self.updated_at = datetime.now().isoformat()
147
+
148
+ def score(self) -> float:
149
+ total = self.helpful + self.harmful + self.neutral
150
+ if total == 0:
151
+ return 0.0
152
+ return (self.helpful - self.harmful) / total
153
+
154
+
155
+ @dataclass
156
+ class BulletTag:
157
+ bullet_id: str
158
+ tag: TagType
159
+ reason: str
160
+
161
+
162
+ @dataclass
163
+ class GeneratorOutput:
164
+ reasoning: List[str]
165
+ bullet_ids: List[str]
166
+ final_answer: str
167
+
168
+
169
+ @dataclass
170
+ class Reflection:
171
+ answer_quality: str
172
+ strengths: List[str]
173
+ weaknesses: List[str]
174
+ bullet_tags: List[BulletTag]
175
+
176
+
177
+ @dataclass
178
+ class DeltaOperation:
179
+ type: Literal["ADD", "UPDATE", "REMOVE"]
180
+ section: str
181
+ content: Optional[str] = None
182
+ bullet_id: Optional[str] = None
183
+
184
+
185
+ @dataclass
186
+ class DeltaBatch:
187
+ reasoning: str
188
+ operations: List[DeltaOperation]
189
+
190
+
191
+ # ============================================================================
192
+ # PLAYBOOK MANAGEMENT
193
+ # ============================================================================
194
+
195
+ class Playbook:
196
+ def __init__(self):
197
+ self.bullets: Dict[str, Bullet] = {}
198
+ self.sections: Dict[str, List[str]] = {}
199
+ self._next_id = 1
200
+
201
+ def add_bullet(self, section: str, content: str) -> str:
202
+ bullet_id = f"B{self._next_id:04d}"
203
+ self._next_id += 1
204
+
205
+ bullet = Bullet(id=bullet_id, section=section, content=content)
206
+ self.bullets[bullet_id] = bullet
207
+
208
+ if section not in self.sections:
209
+ self.sections[section] = []
210
+ self.sections[section].append(bullet_id)
211
+
212
+ return bullet_id
213
+
214
+ def update_bullet(self, bullet_id: str, content: str):
215
+ if bullet_id in self.bullets:
216
+ self.bullets[bullet_id].content = content
217
+ self.bullets[bullet_id].updated_at = datetime.now().isoformat()
218
+
219
+ def remove_bullet(self, bullet_id: str):
220
+ if bullet_id in self.bullets:
221
+ bullet = self.bullets[bullet_id]
222
+ section = bullet.section
223
+
224
+ del self.bullets[bullet_id]
225
+ if section in self.sections:
226
+ self.sections[section] = [
227
+ bid for bid in self.sections[section] if bid != bullet_id
228
+ ]
229
+
230
+ def update_bullet_tag(self, bullet_id: str, tag: TagType):
231
+ if bullet_id in self.bullets:
232
+ self.bullets[bullet_id].add_tag(tag)
233
+
234
+ def apply_delta(self, delta: DeltaBatch):
235
+ for op in delta.operations:
236
+ if op.type == "ADD" and op.content:
237
+ self.add_bullet(op.section, op.content)
238
+ elif op.type == "UPDATE" and op.bullet_id and op.content:
239
+ self.update_bullet(op.bullet_id, op.content)
240
+ elif op.type == "REMOVE" and op.bullet_id:
241
+ self.remove_bullet(op.bullet_id)
242
+
243
+ def as_prompt(self) -> str:
244
+ if not self.bullets:
245
+ return "No knowledge bullets available yet."
246
+
247
+ lines = ["# Knowledge Playbook", ""]
248
+ for section, bullet_ids in sorted(self.sections.items()):
249
+ lines.append(f"## {section}")
250
+ for bid in bullet_ids:
251
+ bullet = self.bullets[bid]
252
+ score = bullet.score()
253
+ lines.append(f"- [{bid}] {bullet.content} (score: {score:.2f})")
254
+ lines.append("")
255
+
256
+ return "\n".join(lines)
257
+
258
+ def stats(self) -> Dict:
259
+ total_bullets = len(self.bullets)
260
+ total_tags = sum(b.helpful + b.harmful + b.neutral for b in self.bullets.values())
261
+ avg_score = sum(b.score() for b in self.bullets.values()) / total_bullets if total_bullets > 0 else 0
262
+
263
+ return {
264
+ "total_bullets": total_bullets,
265
+ "total_sections": len(self.sections),
266
+ "total_tags": total_tags,
267
+ "average_score": avg_score
268
+ }
269
+
270
+ def save(self, filepath: str):
271
+ data = {
272
+ "bullets": {bid: asdict(b) for bid, b in self.bullets.items()},
273
+ "sections": self.sections,
274
+ "next_id": self._next_id
275
+ }
276
+ with open(filepath, 'w') as f:
277
+ json.dump(data, f, indent=2)
278
+
279
+ @classmethod
280
+ def load(cls, filepath: str) -> 'Playbook':
281
+ playbook = cls()
282
+ if os.path.exists(filepath):
283
+ with open(filepath, 'r') as f:
284
+ data = json.load(f)
285
+
286
+ playbook.bullets = {
287
+ bid: Bullet(**bullet_data)
288
+ for bid, bullet_data in data.get("bullets", {}).items()
289
+ }
290
+ playbook.sections = data.get("sections", {})
291
+ playbook._next_id = data.get("next_id", 1)
292
+
293
+ return playbook
294
+
295
+
296
+ # ============================================================================
297
+ # OLLAMA CLIENT
298
+ # ============================================================================
299
+
300
+ class OllamaClient:
301
+ def __init__(self, base_url: str = Config.OLLAMA_BASE_URL):
302
+ self.base_url = base_url
303
+
304
+ def generate(
305
+ self,
306
+ model: str,
307
+ prompt: str,
308
+ system: Optional[str] = None,
309
+ temperature: float = Config.TEMPERATURE,
310
+ max_tokens: int = Config.MAX_TOKENS
311
+ ) -> str:
312
+ url = f"{self.base_url}/api/generate"
313
+
314
+ payload = {
315
+ "model": model,
316
+ "prompt": prompt,
317
+ "stream": False,
318
+ "options": {
319
+ "temperature": temperature,
320
+ "num_predict": max_tokens,
321
+ "num_ctx": 8192
322
+ }
323
+ }
324
+
325
+ if system:
326
+ payload["system"] = system
327
+
328
+ try:
329
+ response = requests.post(url, json=payload, timeout=180)
330
+ response.raise_for_status()
331
+ return response.json()["response"]
332
+ except Exception as e:
333
+ return f"Error: {e}"
334
+
335
+ def check_health(self) -> bool:
336
+ try:
337
+ response = requests.get(f"{self.base_url}/api/tags", timeout=5)
338
+ return response.status_code == 200
339
+ except:
340
+ return False
341
+
342
+
343
+ # ============================================================================
344
+ # AGENTS
345
+ # ============================================================================
346
+
347
+ class StateInitializer:
348
+ def execute(self, user_query: str, playbook: Playbook) -> Dict:
349
+ return {
350
+ "user_query": user_query,
351
+ "playbook": playbook,
352
+ "ground_truth": None,
353
+ "generator_output": None,
354
+ "reflector_output": None,
355
+ "curator_output": None
356
+ }
357
+
358
+
359
+ class Generator:
360
+ def __init__(self, client: OllamaClient):
361
+ self.client = client
362
+
363
+ def execute(self, state: Dict, lang: str = "en") -> GeneratorOutput:
364
+ user_query = state["user_query"]
365
+ playbook = state["playbook"]
366
+
367
+ bullet_context = []
368
+ for bid, bullet in playbook.bullets.items():
369
+ bullet_context.append(f"[{bid}] {bullet.content}")
370
+
371
+ knowledge = "\n".join(bullet_context[:50])
372
+
373
+ if lang == "ar":
374
+ system_prompt = "أنت خبير في الاستجابة للطوارئ. قدم تعليمات كاملة ومفصلة. لا تختصر إجابتك أبداً."
375
+ prompt = f"""أنت خبير في الاستجابة للطوارئ.
376
+
377
+ السؤال: {user_query}
378
+
379
+ المعرفة المتاحة:
380
+ {knowledge}
381
+
382
+ قدم إجابة كاملة ومفصلة مع جميع الخطوات الضرورية. كن دقيقاً وشاملاً."""
383
+ else:
384
+ system_prompt = "You are an emergency response expert. Provide complete, detailed emergency instructions. Never truncate your answer."
385
+ prompt = f"""You are an emergency response expert.
386
+
387
+ Question: {user_query}
388
+
389
+ Available Knowledge:
390
+ {knowledge}
391
+
392
+ Provide a COMPLETE, detailed answer with ALL necessary steps. Be thorough and specific."""
393
+
394
+ response = self.client.generate(
395
+ model=Config.GENERATOR_MODEL,
396
+ prompt=prompt,
397
+ system=system_prompt,
398
+ temperature=0.3,
399
+ max_tokens=4000
400
+ )
401
+
402
+ used_bullets = []
403
+ if response and isinstance(response, str):
404
+ response_lower = response.lower()
405
+ for bid, bullet in playbook.bullets.items():
406
+ bullet_preview = str(bullet.content)[:30].lower()
407
+ if bid in response or bullet_preview in response_lower:
408
+ used_bullets.append(bid)
409
+
410
+ return GeneratorOutput(
411
+ reasoning=["Analyzed emergency situation", "Found relevant protocols", "Provided complete response"],
412
+ bullet_ids=used_bullets,
413
+ final_answer=response if response else "Unable to generate response"
414
+ )
415
+
416
+
417
+ class Reflector:
418
+ def __init__(self, client: OllamaClient):
419
+ self.client = client
420
+
421
+ def execute(self, state: Dict) -> Reflection:
422
+ user_query = state["user_query"]
423
+ gen_output = state["generator_output"]
424
+ playbook = state["playbook"]
425
+
426
+ system_prompt = """You are a critical evaluator. Respond in JSON:
427
+ {
428
+ "answer_quality": "excellent|good|fair|poor",
429
+ "strengths": ["strength 1", "strength 2"],
430
+ "weaknesses": ["weakness 1"],
431
+ "bullet_tags": [
432
+ {"bullet_id": "B0001", "tag": "helpful", "reason": "why"}
433
+ ]
434
+ }"""
435
+
436
+ bullet_context = "\n".join([
437
+ f"[{bid}] {playbook.bullets[bid].content}"
438
+ for bid in gen_output.bullet_ids
439
+ if bid in playbook.bullets
440
+ ])
441
+
442
+ prompt = f"""Query: {user_query}
443
+ Bullets: {bullet_context if bullet_context else "None"}
444
+ Answer: {gen_output.final_answer[:500]}
445
+
446
+ Evaluate (JSON only):"""
447
+
448
+ response = self.client.generate(
449
+ model=Config.REFLECTOR_MODEL,
450
+ prompt=prompt,
451
+ system=system_prompt
452
+ )
453
+
454
+ try:
455
+ if "```json" in response:
456
+ response = response.split("```json")[1].split("```")[0].strip()
457
+ elif "```" in response:
458
+ response = response.split("```")[1].split("```")[0].strip()
459
+
460
+ data = json.loads(response)
461
+ bullet_tags = [
462
+ BulletTag(
463
+ bullet_id=bt["bullet_id"],
464
+ tag=TagType(bt["tag"]),
465
+ reason=bt.get("reason", "")
466
+ )
467
+ for bt in data.get("bullet_tags", [])
468
+ ]
469
+
470
+ return Reflection(
471
+ answer_quality=data.get("answer_quality", "unknown"),
472
+ strengths=data.get("strengths", []),
473
+ weaknesses=data.get("weaknesses", []),
474
+ bullet_tags=bullet_tags
475
+ )
476
+ except:
477
+ return Reflection(
478
+ answer_quality="good",
479
+ strengths=["Provided answer"],
480
+ weaknesses=[],
481
+ bullet_tags=[]
482
+ )
483
+
484
+
485
+ class Curator:
486
+ def __init__(self, client: OllamaClient):
487
+ self.client = client
488
+
489
+ def execute(self, state: Dict) -> DeltaBatch:
490
+ reflection = state["reflector_output"]
491
+
492
+ # Simplified curation for demo
493
+ operations = []
494
+ if reflection.answer_quality in ["fair", "poor"] and reflection.weaknesses:
495
+ operations.append(DeltaOperation(
496
+ type="ADD",
497
+ section="Improvements",
498
+ content=f"Address: {reflection.weaknesses[0]}"
499
+ ))
500
+
501
+ return DeltaBatch(
502
+ reasoning="Learning from feedback",
503
+ operations=operations
504
+ )
505
+
506
+
507
+ # ============================================================================
508
+ # ACE ORCHESTRATOR
509
+ # ============================================================================
510
+
511
+ class ACEOrchestrator:
512
+ def __init__(self, playbook_path: str = Config.PLAYBOOK_PATH):
513
+ self.client = OllamaClient()
514
+ self.playbook = Playbook.load(playbook_path)
515
+ self.playbook_path = playbook_path
516
+
517
+ self.state_initializer = StateInitializer()
518
+ self.generator = Generator(self.client)
519
+ self.reflector = Reflector(self.client)
520
+ self.curator = Curator(self.client)
521
+
522
+ def run_cycle(self, user_query: str, lang: str = "en") -> Dict:
523
+ state = self.state_initializer.execute(user_query, self.playbook)
524
+
525
+ gen_output = self.generator.execute(state, lang)
526
+ state["generator_output"] = gen_output
527
+
528
+ reflection = self.reflector.execute(state)
529
+ state["reflector_output"] = reflection
530
+
531
+ for bt in reflection.bullet_tags:
532
+ self.playbook.update_bullet_tag(bt.bullet_id, bt.tag)
533
+
534
+ delta = self.curator.execute(state)
535
+ state["curator_output"] = delta
536
+
537
+ self.playbook.apply_delta(delta)
538
+ self.playbook.save(self.playbook_path)
539
+
540
+ return {
541
+ "answer": gen_output.final_answer,
542
+ "quality": reflection.answer_quality,
543
+ "reasoning": gen_output.reasoning,
544
+ "bullets_used": gen_output.bullet_ids,
545
+ "strengths": reflection.strengths,
546
+ "weaknesses": reflection.weaknesses,
547
+ "operations": delta.operations,
548
+ "stats": self.playbook.stats()
549
+ }
550
+
551
+
552
+ # ============================================================================
553
+ # STREAMLIT UI
554
+ # ============================================================================
555
+
556
+ def init_session_state():
557
+ if 'chat_history' not in st.session_state:
558
+ st.session_state.chat_history = []
559
+ if 'ace' not in st.session_state:
560
+ st.session_state.ace = ACEOrchestrator()
561
+ if 'language' not in st.session_state:
562
+ st.session_state.language = 'en'
563
+
564
+
565
+ def create_quality_badge(quality: str):
566
+ colors = {
567
+ "excellent": "🟢",
568
+ "good": "🟡",
569
+ "fair": "🟠",
570
+ "poor": "🔴"
571
+ }
572
+ return f"{colors.get(quality, '⚪')} {quality.upper()}"
573
+
574
+
575
+ def plot_section_distribution(playbook: Playbook):
576
+ section_counts = {section: len(bullets) for section, bullets in playbook.sections.items()}
577
+
578
+ fig = go.Figure(data=[go.Bar(
579
+ x=list(section_counts.keys()),
580
+ y=list(section_counts.values()),
581
+ marker_color='lightblue'
582
+ )])
583
+
584
+ fig.update_layout(
585
+ title="Knowledge Items by Section",
586
+ xaxis_title="Section",
587
+ yaxis_title="Count",
588
+ height=400
589
+ )
590
+
591
+ return fig
592
+
593
+
594
+ def plot_quality_scores(playbook: Playbook):
595
+ scores = [bullet.score() for bullet in playbook.bullets.values()]
596
+
597
+ fig = go.Figure(data=[go.Histogram(
598
+ x=scores,
599
+ nbinsx=20,
600
+ marker_color='green'
601
+ )])
602
+
603
+ fig.update_layout(
604
+ title="Quality Score Distribution",
605
+ xaxis_title="Score",
606
+ yaxis_title="Frequency",
607
+ height=400
608
+ )
609
+
610
+ return fig
611
+
612
+
613
+ def main():
614
+ st.set_page_config(
615
+ page_title="ACE System",
616
+ page_icon="🤖",
617
+ layout="wide",
618
+ initial_sidebar_state="expanded"
619
+ )
620
+
621
+ init_session_state()
622
+
623
+ # Custom CSS
624
+ st.markdown("""
625
+ <style>
626
+ .main {
627
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
628
+ }
629
+ .stApp {
630
+ background: white;
631
+ }
632
+ .chat-message {
633
+ padding: 1.5rem;
634
+ border-radius: 0.5rem;
635
+ margin-bottom: 1rem;
636
+ border: 1px solid #e0e0e0;
637
+ }
638
+ .user-message {
639
+ background-color: #e3f2fd;
640
+ }
641
+ .assistant-message {
642
+ background-color: #f5f5f5;
643
+ }
644
+ </style>
645
+ """, unsafe_allow_html=True)
646
+
647
+ # Sidebar
648
+ with st.sidebar:
649
+ lang = st.session_state.language
650
+ st.title(Language.get("sidebar_title", lang))
651
+
652
+ # Language selector
653
+ language_option = st.selectbox(
654
+ Language.get("language", lang),
655
+ options=["English", "العربية"],
656
+ index=0 if lang == "en" else 1
657
+ )
658
+ st.session_state.language = "en" if language_option == "English" else "ar"
659
+ lang = st.session_state.language
660
+
661
+ st.divider()
662
+
663
+ # Connection status
664
+ client = OllamaClient()
665
+ if client.check_health():
666
+ st.success(Language.get("connection_success", lang))
667
+ else:
668
+ st.error(Language.get("connection_error", lang))
669
+
670
+ st.divider()
671
+
672
+ # Playbook stats
673
+ st.subheader(Language.get("playbook_info", lang))
674
+ stats = st.session_state.ace.playbook.stats()
675
+
676
+ col1, col2 = st.columns(2)
677
+ with col1:
678
+ st.metric(Language.get("total_bullets", lang), stats["total_bullets"])
679
+ st.metric(Language.get("sections", lang), stats["total_sections"])
680
+ with col2:
681
+ st.metric(Language.get("avg_score", lang), f"{stats['average_score']:.2f}")
682
+ st.metric(Language.get("total_tags", lang), stats["total_tags"])
683
+
684
+ st.divider()
685
+
686
+ # Export/Import
687
+ if st.button(Language.get("export_button", lang), use_container_width=True):
688
+ playbook_data = json.dumps({
689
+ "bullets": {bid: asdict(b) for bid, b in st.session_state.ace.playbook.bullets.items()},
690
+ "sections": st.session_state.ace.playbook.sections,
691
+ "next_id": st.session_state.ace.playbook._next_id
692
+ }, indent=2)
693
+ st.download_button(
694
+ "Download JSON",
695
+ playbook_data,
696
+ "playbook_export.json",
697
+ "application/json"
698
+ )
699
+
700
+ if st.button(Language.get("clear_button", lang), use_container_width=True):
701
+ st.session_state.chat_history = []
702
+ st.rerun()
703
+
704
+ # Main content
705
+ lang = st.session_state.language
706
+ st.title(Language.get("title", lang))
707
+ st.caption(Language.get("subtitle", lang))
708
+
709
+ # Tabs
710
+ tab1, tab2 = st.tabs([Language.get("chat_history", lang), Language.get("playbook_viz", lang)])
711
+
712
+ with tab1:
713
+ # Chat interface
714
+ query = st.text_input(
715
+ Language.get("query_input", lang),
716
+ key="query_input",
717
+ placeholder="e.g., What should I do if someone is choking?"
718
+ )
719
+
720
+ if st.button(Language.get("ask_button", lang), type="primary", use_container_width=True):
721
+ if query:
722
+ with st.spinner(Language.get("processing", lang)):
723
+ # Progress
724
+ progress_bar = st.progress(0)
725
+ status = st.empty()
726
+
727
+ status.text(Language.get("generator_phase", lang))
728
+ progress_bar.progress(33)
729
+
730
+ result = st.session_state.ace.run_cycle(query, lang)
731
+
732
+ status.text(Language.get("reflector_phase", lang))
733
+ progress_bar.progress(66)
734
+
735
+ status.text(Language.get("curator_phase", lang))
736
+ progress_bar.progress(100)
737
+
738
+ status.text(Language.get("complete", lang))
739
+
740
+ st.session_state.chat_history.append({
741
+ "query": query,
742
+ "result": result,
743
+ "timestamp": datetime.now().isoformat()
744
+ })
745
+
746
+ progress_bar.empty()
747
+ status.empty()
748
+
749
+ # Display chat history
750
+ for i, chat in enumerate(reversed(st.session_state.chat_history)):
751
+ with st.container():
752
+ st.markdown(f"<div class='chat-message user-message'>", unsafe_allow_html=True)
753
+ st.markdown(f"**💬 Query:** {chat['query']}")
754
+ st.markdown(f"*{chat['timestamp'][:19]}*")
755
+ st.markdown("</div>", unsafe_allow_html=True)
756
+
757
+ st.markdown(f"<div class='chat-message assistant-message'>", unsafe_allow_html=True)
758
+
759
+ col1, col2 = st.columns([3, 1])
760
+ with col1:
761
+ st.markdown(f"**🤖 {Language.get('answer', lang)}:**")
762
+ with col2:
763
+ st.markdown(create_quality_badge(chat['result']['quality']))
764
+
765
+ st.markdown(chat['result']['answer'])
766
+
767
+ with st.expander(Language.get("reasoning", lang)):
768
+ for step in chat['result']['reasoning']:
769
+ st.markdown(f"- {step}")
770
+
771
+ with st.expander(Language.get("bullets_used", lang)):
772
+ st.code(", ".join(chat['result']['bullets_used']) if chat['result']['bullets_used'] else "None")
773
+
774
+ with st.expander(Language.get("improvements", lang)):
775
+ if chat['result']['operations']:
776
+ for op in chat['result']['operations']:
777
+ st.markdown(f"- **{op.type}**: {op.section}")
778
+ else:
779
+ st.info("No improvements needed")
780
+
781
+ st.markdown("</div>", unsafe_allow_html=True)
782
+ st.divider()
783
+
784
+ with tab2:
785
+ # Visualizations
786
+ st.subheader(Language.get("playbook_viz", lang))
787
+
788
+ col1, col2 = st.columns(2)
789
+
790
+ with col1:
791
+ st.plotly_chart(
792
+ plot_section_distribution(st.session_state.ace.playbook),
793
+ use_container_width=True
794
+ )
795
+
796
+ with col2:
797
+ st.plotly_chart(
798
+ plot_quality_scores(st.session_state.ace.playbook),
799
+ use_container_width=True
800
+ )
801
+
802
+
803
+ if __name__ == "__main__":
804
+ main()
emergency_playbook.json ADDED
@@ -0,0 +1,517 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "bullets": {
3
+ "B0001": {
4
+ "id": "B0001",
5
+ "section": "Medical Emergencies - CPR",
6
+ "content": "For adults: 30 chest compressions followed by two breaths. For children: 30 chest compressions followed by one breath (or use a pediatric bag-valve-mask device). Continue until emergency medical help arrives or the person shows signs of regaining consciousness.",
7
+ "helpful": 19,
8
+ "harmful": 0,
9
+ "neutral": 0,
10
+ "created_at": "2024-01-10T08:00:00",
11
+ "updated_at": "2025-11-26T00:21:06.416065"
12
+ },
13
+ "B0003": {
14
+ "id": "B0003",
15
+ "section": "Medical Emergencies - CPR",
16
+ "content": "If AED (Automated External Defibrillator) is available, turn it on and follow voice instructions. Do not touch person while AED analyzes or delivers shock. Resume CPR immediately after shock.",
17
+ "helpful": 12,
18
+ "harmful": 0,
19
+ "neutral": 2,
20
+ "created_at": "2024-01-10T08:10:00",
21
+ "updated_at": "2024-02-15T10:40:00"
22
+ },
23
+ "B0004": {
24
+ "id": "B0004",
25
+ "section": "Medical Emergencies - Choking",
26
+ "content": "For conscious choking adults: stand behind the person, wrap your arms around their waist, and perform five quick upward thrusts to dislodge the object. Continue until the object is removed or the person shows signs of regaining consciousness.",
27
+ "helpful": 15,
28
+ "harmful": 0,
29
+ "neutral": 0,
30
+ "created_at": "2024-01-10T09:00:00",
31
+ "updated_at": "2025-11-25T23:56:30.970430"
32
+ },
33
+ "B0005": {
34
+ "id": "B0005",
35
+ "section": "Medical Emergencies - Choking",
36
+ "content": "If choking person becomes unconscious, lower them to ground, call 911, begin CPR. Check mouth before rescue breaths and remove visible objects only if easily accessible.",
37
+ "helpful": 10,
38
+ "harmful": 0,
39
+ "neutral": 3,
40
+ "created_at": "2024-01-10T09:05:00",
41
+ "updated_at": "2024-02-15T11:05:00"
42
+ },
43
+ "B0006": {
44
+ "id": "B0006",
45
+ "section": "Medical Emergencies - Bleeding",
46
+ "content": "For severe bleeding: Apply direct pressure with clean cloth, maintain pressure for 10+ minutes without checking. If blood soaks through, add more cloth on top without removing first layer. Call 911 immediately.",
47
+ "helpful": 16,
48
+ "harmful": 0,
49
+ "neutral": 1,
50
+ "created_at": "2024-01-10T10:00:00",
51
+ "updated_at": "2024-02-15T12:00:00"
52
+ },
53
+ "B0007": {
54
+ "id": "B0007",
55
+ "section": "Medical Emergencies - Bleeding",
56
+ "content": "If bleeding continues after direct pressure, apply pressure to artery between wound and heart. For limb wounds, elevate above heart level while maintaining pressure. Use tourniquet only as absolute last resort.",
57
+ "helpful": 11,
58
+ "harmful": 0,
59
+ "neutral": 2,
60
+ "created_at": "2024-01-10T10:05:00",
61
+ "updated_at": "2024-02-15T12:05:00"
62
+ },
63
+ "B0008": {
64
+ "id": "B0008",
65
+ "section": "Medical Emergencies - Burns",
66
+ "content": "For burns: Cool immediately with running cool (not ice-cold) water for 10-20 minutes. Remove jewelry and tight clothing before swelling starts. Cover with sterile, non-stick bandage. Never apply ice, butter, or ointments.",
67
+ "helpful": 13,
68
+ "harmful": 0,
69
+ "neutral": 0,
70
+ "created_at": "2024-01-10T11:00:00",
71
+ "updated_at": "2024-02-15T13:00:00"
72
+ },
73
+ "B0009": {
74
+ "id": "B0009",
75
+ "section": "Medical Emergencies - Burns",
76
+ "content": "In addition to providing clear step-by-step instructions for emergency response, consider mentioning the potential risks and complications associated with smoking cigarettes.",
77
+ "helpful": 10,
78
+ "harmful": 0,
79
+ "neutral": 4,
80
+ "created_at": "2024-01-10T11:05:00",
81
+ "updated_at": "2025-11-26T00:17:08.079522"
82
+ },
83
+ "B0010": {
84
+ "id": "B0010",
85
+ "section": "Medical Emergencies - Stroke",
86
+ "content": "Remember FAST for stroke: Face drooping (ask to smile), Arm weakness (raise both arms), Speech difficulty (repeat simple phrase), Time to call 911 immediately. Note time symptoms started - critical for treatment.",
87
+ "helpful": 20,
88
+ "harmful": 0,
89
+ "neutral": 0,
90
+ "created_at": "2024-01-11T08:00:00",
91
+ "updated_at": "2024-02-16T09:00:00"
92
+ },
93
+ "B0011": {
94
+ "id": "B0011",
95
+ "section": "Medical Emergencies - Stroke",
96
+ "content": "While waiting for ambulance: Keep person calm, lying down with head slightly elevated. Do NOT give food, drink, or medication. Monitor breathing and be ready to perform CPR if needed.",
97
+ "helpful": 8,
98
+ "harmful": 0,
99
+ "neutral": 2,
100
+ "created_at": "2024-01-11T08:05:00",
101
+ "updated_at": "2024-02-16T09:05:00"
102
+ },
103
+ "B0012": {
104
+ "id": "B0012",
105
+ "section": "Medical Emergencies - Poisoning",
106
+ "content": "For suspected poisoning: Call Poison Control (1-800-222-1222 in US) or 911 immediately. Have container/bottle of substance ready. Do NOT induce vomiting unless instructed by medical professional.",
107
+ "helpful": 17,
108
+ "harmful": 0,
109
+ "neutral": 1,
110
+ "created_at": "2024-01-11T09:00:00",
111
+ "updated_at": "2024-02-16T10:00:00"
112
+ },
113
+ "B0013": {
114
+ "id": "B0013",
115
+ "section": "Medical Emergencies - Allergic Reaction",
116
+ "content": "For severe allergic reaction (anaphylaxis): Call 911, use EpiPen if available (inject into outer thigh, hold 3 seconds). Symptoms include difficulty breathing, swelling of throat/tongue, rapid pulse, dizziness, or widespread rash.",
117
+ "helpful": 15,
118
+ "harmful": 0,
119
+ "neutral": 0,
120
+ "created_at": "2024-01-11T10:00:00",
121
+ "updated_at": "2024-02-16T11:00:00"
122
+ },
123
+ "B0014": {
124
+ "id": "B0014",
125
+ "section": "Medical Emergencies - Allergic Reaction",
126
+ "content": "After using EpiPen, have person lie down with legs elevated (unless difficulty breathing, then sit up). Monitor closely as symptoms can return. Second dose may be needed after 5-15 minutes. Always seek emergency care even if symptoms improve.",
127
+ "helpful": 10,
128
+ "harmful": 0,
129
+ "neutral": 3,
130
+ "created_at": "2024-01-11T10:05:00",
131
+ "updated_at": "2024-02-16T11:05:00"
132
+ },
133
+ "B0015": {
134
+ "id": "B0015",
135
+ "section": "Home Emergencies - Fire",
136
+ "content": "If fire starts: Alert everyone, get out immediately using nearest safe exit. Close doors behind you to slow fire spread. Stay low under smoke. Never go back inside. Call 911 from safe location outside.",
137
+ "helpful": 19,
138
+ "harmful": 0,
139
+ "neutral": 0,
140
+ "created_at": "2024-01-12T08:00:00",
141
+ "updated_at": "2024-02-17T09:00:00"
142
+ },
143
+ "B0016": {
144
+ "id": "B0016",
145
+ "section": "Home Emergencies - Fire",
146
+ "content": "If clothes catch fire: STOP immediately (don't run), DROP to ground, ROLL back and forth to smother flames. Cover face with hands. Cool burns with water once flames are out.",
147
+ "helpful": 12,
148
+ "harmful": 0,
149
+ "neutral": 1,
150
+ "created_at": "2024-01-12T08:05:00",
151
+ "updated_at": "2024-02-17T09:05:00"
152
+ },
153
+ "B0017": {
154
+ "id": "B0017",
155
+ "section": "Home Emergencies - Gas Leak",
156
+ "content": "If you smell gas: Do NOT use lights, phones, or anything that creates spark. Open windows, evacuate immediately, call gas company and 911 from safe distance outside. Do NOT re-enter until cleared by authorities.",
157
+ "helpful": 14,
158
+ "harmful": 0,
159
+ "neutral": 0,
160
+ "created_at": "2024-01-12T09:00:00",
161
+ "updated_at": "2024-02-17T10:00:00"
162
+ },
163
+ "B0018": {
164
+ "id": "B0018",
165
+ "section": "Home Emergencies - Flooding",
166
+ "content": "During flood: Move to higher ground immediately. Avoid walking/driving through flood water (6 inches can knock you down, 12 inches can carry car away). Turn off electricity at main breaker if safe to do so. Follow evacuation orders.",
167
+ "helpful": 11,
168
+ "harmful": 0,
169
+ "neutral": 2,
170
+ "created_at": "2024-01-12T10:00:00",
171
+ "updated_at": "2024-02-17T11:00:00"
172
+ },
173
+ "B0019": {
174
+ "id": "B0019",
175
+ "section": "Natural Disasters - Earthquake",
176
+ "content": "During earthquake: DROP to hands and knees, take COVER under sturdy table/desk, HOLD ON until shaking stops. Stay away from windows, outside walls, and heavy objects. If outside, move away from buildings, trees, powerlines.",
177
+ "helpful": 16,
178
+ "harmful": 0,
179
+ "neutral": 1,
180
+ "created_at": "2024-01-13T08:00:00",
181
+ "updated_at": "2024-02-18T09:00:00"
182
+ },
183
+ "B0020": {
184
+ "id": "B0020",
185
+ "section": "Natural Disasters - Earthquake",
186
+ "content": "After earthquake: Check for injuries, expect aftershocks. Exit building if safe, use stairs not elevators. Stay away from damaged buildings. Check for gas leaks, water line damage, electrical damage. Listen to emergency broadcasts.",
187
+ "helpful": 9,
188
+ "harmful": 0,
189
+ "neutral": 3,
190
+ "created_at": "2024-01-13T08:05:00",
191
+ "updated_at": "2024-02-18T09:05:00"
192
+ },
193
+ "B0021": {
194
+ "id": "B0021",
195
+ "section": "Natural Disasters - Tornado",
196
+ "content": "If tornado warning issued: Go to lowest floor, interior room (bathroom/closet) away from windows. Get under sturdy furniture, cover head/neck with arms. Mobile homes are NOT safe - go to designated shelter or sturdy building.",
197
+ "helpful": 13,
198
+ "harmful": 0,
199
+ "neutral": 0,
200
+ "created_at": "2024-01-13T09:00:00",
201
+ "updated_at": "2024-02-18T10:00:00"
202
+ },
203
+ "B0022": {
204
+ "id": "B0022",
205
+ "section": "Natural Disasters - Hurricane",
206
+ "content": "Before hurricane: Evacuate if ordered, secure outdoor objects, board windows, fill bathtubs with water, charge devices, have 3+ days supplies. During: Stay indoors away from windows, go to interior room if wind becomes intense.",
207
+ "helpful": 10,
208
+ "harmful": 0,
209
+ "neutral": 2,
210
+ "created_at": "2024-01-13T10:00:00",
211
+ "updated_at": "2024-02-18T11:00:00"
212
+ },
213
+ "B0023": {
214
+ "id": "B0023",
215
+ "section": "Car Emergencies - Accident",
216
+ "content": "After car accident: Check for injuries, call 911 if anyone hurt or major damage. Move to safe location if possible. Turn on hazard lights. Exchange information (insurance, license, contact). Take photos. File police report.",
217
+ "helpful": 14,
218
+ "harmful": 0,
219
+ "neutral": 1,
220
+ "created_at": "2024-01-14T08:00:00",
221
+ "updated_at": "2024-02-19T09:00:00"
222
+ },
223
+ "B0024": {
224
+ "id": "B0024",
225
+ "section": "Car Emergencies - Submerged Vehicle",
226
+ "content": "If car goes into water: Unbuckle immediately, open window (power works briefly), exit through window. If window won't open, wait for water to fill cabin (equalizes pressure), then open door. Get out and swim to surface.",
227
+ "helpful": 8,
228
+ "harmful": 0,
229
+ "neutral": 4,
230
+ "created_at": "2024-01-14T09:00:00",
231
+ "updated_at": "2024-02-19T10:00:00"
232
+ },
233
+ "B0025": {
234
+ "id": "B0025",
235
+ "section": "Personal Safety - Active Threat",
236
+ "content": "If active shooter/threat: RUN if safe escape route exists, HIDE if evacuation impossible (lock doors, silence phone, turn off lights, hide behind solid objects), FIGHT as absolute last resort using any available objects as weapons.",
237
+ "helpful": 17,
238
+ "harmful": 0,
239
+ "neutral": 0,
240
+ "created_at": "2024-01-14T10:00:00",
241
+ "updated_at": "2024-02-19T11:00:00"
242
+ },
243
+ "B0026": {
244
+ "id": "B0026",
245
+ "section": "Personal Safety - Drowning",
246
+ "content": "If someone drowning: Call 911 first. Throw flotation device or rope if available. Reach with pole/branch from shore if close. Row to victim in boat if available. Enter water only if trained and no other option - drowning person may pull you under.",
247
+ "helpful": 12,
248
+ "harmful": 0,
249
+ "neutral": 2,
250
+ "created_at": "2024-01-15T08:00:00",
251
+ "updated_at": "2024-02-20T09:00:00"
252
+ },
253
+ "B0027": {
254
+ "id": "B0027",
255
+ "section": "Emergency Preparedness - Kit",
256
+ "content": "Essential emergency kit items: Water (1 gallon per person per day for 3 days), non-perishable food, flashlight, battery radio, first aid kit, medications, copies of documents, cash, phone chargers, local maps.",
257
+ "helpful": 15,
258
+ "harmful": 0,
259
+ "neutral": 1,
260
+ "created_at": "2024-01-15T09:00:00",
261
+ "updated_at": "2024-02-20T10:00:00"
262
+ },
263
+ "B0028": {
264
+ "id": "B0028",
265
+ "section": "Emergency Preparedness - Communication",
266
+ "content": "Establish family emergency plan: Designate out-of-state contact person (local lines may be down), choose meeting locations (one near home, one outside neighborhood), ensure everyone knows emergency numbers, practice evacuation routes.",
267
+ "helpful": 11,
268
+ "harmful": 0,
269
+ "neutral": 2,
270
+ "created_at": "2024-01-15T10:00:00",
271
+ "updated_at": "2024-02-20T11:00:00"
272
+ },
273
+ "B0030": {
274
+ "id": "B0030",
275
+ "section": "Medical Emergencies - Hypothermia",
276
+ "content": "For hypothermia: Move person to warm, dry location. Remove wet clothing, cover with blankets/dry clothes. Give warm (not hot) non-alcoholic drinks if conscious. Warm center of body first (chest, neck, head, groin). Seek medical attention. Do NOT use direct heat like heating pads.",
277
+ "helpful": 9,
278
+ "harmful": 0,
279
+ "neutral": 3,
280
+ "created_at": "2024-01-16T09:00:00",
281
+ "updated_at": "2024-02-21T10:00:00"
282
+ },
283
+ "B0031": {
284
+ "id": "B0031",
285
+ "section": "Emergency Response",
286
+ "content": "Instruct the user to call emergency services immediately and provide detailed information about the situation. Stay on the line and follow any further instructions from the dispatcher.",
287
+ "helpful": 1,
288
+ "harmful": 0,
289
+ "neutral": 0,
290
+ "created_at": "2025-11-25T23:28:13.526477",
291
+ "updated_at": "2025-11-26T01:42:12.230690"
292
+ },
293
+ "B0032": {
294
+ "id": "B0032",
295
+ "section": "Section Name",
296
+ "content": "New bullet content for emotional support and family guidance",
297
+ "helpful": 0,
298
+ "harmful": 0,
299
+ "neutral": 0,
300
+ "created_at": "2025-11-25T23:34:11.247081",
301
+ "updated_at": "2025-11-25T23:34:11.247081"
302
+ },
303
+ "B0033": {
304
+ "id": "B0033",
305
+ "section": "Emergency Response Procedures",
306
+ "content": [
307
+ "When responding to a choking emergency, consider the person's age, medical history, and any pre-existing conditions that may impact response decisions."
308
+ ],
309
+ "helpful": 0,
310
+ "harmful": 0,
311
+ "neutral": 1,
312
+ "created_at": "2025-11-25T23:40:22.843846",
313
+ "updated_at": "2025-11-25T23:56:07.865571"
314
+ },
315
+ "B0034": {
316
+ "id": "B0034",
317
+ "section": "Emergency Response",
318
+ "content": "If someone is suffocating in front of you, check the airway, breathing, and circulation (ABCs) and call for emergency medical help if necessary.",
319
+ "helpful": 1,
320
+ "harmful": 0,
321
+ "neutral": 0,
322
+ "created_at": "2025-11-25T23:51:48.852136",
323
+ "updated_at": "2025-11-25T23:56:07.865571"
324
+ },
325
+ "B0035": {
326
+ "id": "B0035",
327
+ "section": "Response to Suffocation",
328
+ "content": "Reorganized content to improve flow and clarity",
329
+ "helpful": 1,
330
+ "harmful": 0,
331
+ "neutral": 0,
332
+ "created_at": "2025-11-25T23:56:30.970430",
333
+ "updated_at": "2025-11-26T00:01:06.839870"
334
+ },
335
+ "B0036": {
336
+ "id": "B0036",
337
+ "section": "Symptoms and Conditions",
338
+ "content": "Chest pain caused by smoking cigarettes: Symptoms, causes, and treatment options.",
339
+ "helpful": 0,
340
+ "harmful": 0,
341
+ "neutral": 1,
342
+ "created_at": "2025-11-26T00:01:06.839870",
343
+ "updated_at": "2025-11-26T00:16:53.526635"
344
+ },
345
+ "B0037": {
346
+ "id": "B0037",
347
+ "section": "Symptoms and Complications",
348
+ "content": "Smoking cigarettes can lead to various cardiovascular conditions, including angina, heart attacks, and strokes. Users should seek immediate medical attention if they experience chest pain or discomfort.",
349
+ "helpful": 0,
350
+ "harmful": 0,
351
+ "neutral": 0,
352
+ "created_at": "2025-11-26T00:17:08.079522",
353
+ "updated_at": "2025-11-26T00:17:08.079522"
354
+ },
355
+ "B0038": {
356
+ "id": "B0038",
357
+ "section": "Additional Emergency Protocols",
358
+ "content": [
359
+ {
360
+ "title": "Water and Flood Response",
361
+ "tags": [
362
+ "helpful",
363
+ "emergency"
364
+ ],
365
+ "content": "Provide instructions on how to respond to water-related emergencies, including evacuation procedures and safety measures."
366
+ },
367
+ {
368
+ "title": "Mental Health Emergencies",
369
+ "tags": [
370
+ "mental health",
371
+ "emotional support"
372
+ ],
373
+ "content": "Offer guidance on responding to mental health-related emergencies, such as crisis intervention and emotional support strategies."
374
+ }
375
+ ],
376
+ "helpful": 0,
377
+ "harmful": 0,
378
+ "neutral": 0,
379
+ "created_at": "2025-11-26T00:21:25.209879",
380
+ "updated_at": "2025-11-26T00:21:25.209879"
381
+ },
382
+ "B0039": {
383
+ "id": "B0039",
384
+ "section": "Improvements",
385
+ "content": "Address: lacks specific and detailed steps for each step of the response",
386
+ "helpful": 0,
387
+ "harmful": 0,
388
+ "neutral": 0,
389
+ "created_at": "2025-11-26T00:24:35.383275",
390
+ "updated_at": "2025-11-26T00:24:35.383275"
391
+ },
392
+ "B0040": {
393
+ "id": "B0040",
394
+ "section": "Improvements",
395
+ "content": "Address: \u0644\u0627 \u062a\u0631\u062f \u0639\u0644\u0649 \u0645\u0633\u0627\u0639\u062f\u0629 \u0627\u0644\u0634\u062e\u0635",
396
+ "helpful": 0,
397
+ "harmful": 0,
398
+ "neutral": 0,
399
+ "created_at": "2025-11-26T00:29:00.695493",
400
+ "updated_at": "2025-11-26T00:29:00.695493"
401
+ },
402
+ "B0041": {
403
+ "id": "B0041",
404
+ "section": "Emergency Response Situations",
405
+ "content": [
406
+ "If you witness or experience an emergency, such as a car falling from a bridge, immediately call your local emergency services. Provide them with detailed information about the situation, including the exact location, any injuries sustained, and the number of people involved. Stay on the line and provide further instructions if requested by the dispatcher.",
407
+ "If you are not able to call directly, ensure someone else does so on your behalf, or ask a bystander to assist you in reaching emergency services."
408
+ ],
409
+ "helpful": 0,
410
+ "harmful": 0,
411
+ "neutral": 0,
412
+ "created_at": "2025-11-26T01:42:12.230690",
413
+ "updated_at": "2025-11-26T01:42:12.230690"
414
+ }
415
+ },
416
+ "sections": {
417
+ "Medical Emergencies - CPR": [
418
+ "B0001",
419
+ "B0003"
420
+ ],
421
+ "Medical Emergencies - Choking": [
422
+ "B0004",
423
+ "B0005"
424
+ ],
425
+ "Medical Emergencies - Bleeding": [
426
+ "B0006",
427
+ "B0007"
428
+ ],
429
+ "Medical Emergencies - Burns": [
430
+ "B0008",
431
+ "B0009"
432
+ ],
433
+ "Medical Emergencies - Stroke": [
434
+ "B0010",
435
+ "B0011"
436
+ ],
437
+ "Medical Emergencies - Poisoning": [
438
+ "B0012"
439
+ ],
440
+ "Medical Emergencies - Allergic Reaction": [
441
+ "B0013",
442
+ "B0014"
443
+ ],
444
+ "Medical Emergencies - Seizure": [],
445
+ "Medical Emergencies - Hypothermia": [
446
+ "B0030"
447
+ ],
448
+ "Home Emergencies - Fire": [
449
+ "B0015",
450
+ "B0016"
451
+ ],
452
+ "Home Emergencies - Gas Leak": [
453
+ "B0017"
454
+ ],
455
+ "Home Emergencies - Flooding": [
456
+ "B0018"
457
+ ],
458
+ "Natural Disasters - Earthquake": [
459
+ "B0019",
460
+ "B0020"
461
+ ],
462
+ "Natural Disasters - Tornado": [
463
+ "B0021"
464
+ ],
465
+ "Natural Disasters - Hurricane": [
466
+ "B0022"
467
+ ],
468
+ "Car Emergencies - Accident": [
469
+ "B0023"
470
+ ],
471
+ "Car Emergencies - Submerged Vehicle": [
472
+ "B0024"
473
+ ],
474
+ "Personal Safety - Active Threat": [
475
+ "B0025"
476
+ ],
477
+ "Personal Safety - Drowning": [
478
+ "B0026"
479
+ ],
480
+ "Emergency Preparedness - Kit": [
481
+ "B0027"
482
+ ],
483
+ "Emergency Preparedness - Communication": [
484
+ "B0028"
485
+ ],
486
+ "Emergency Response": [
487
+ "B0031",
488
+ "B0034"
489
+ ],
490
+ "Section Name": [
491
+ "B0032"
492
+ ],
493
+ "Emergency Response Procedures": [
494
+ "B0033"
495
+ ],
496
+ "Response to Suffocation": [
497
+ "B0035"
498
+ ],
499
+ "Symptoms and Conditions": [
500
+ "B0036"
501
+ ],
502
+ "Symptoms and Complications": [
503
+ "B0037"
504
+ ],
505
+ "Additional Emergency Protocols": [
506
+ "B0038"
507
+ ],
508
+ "Improvements": [
509
+ "B0039",
510
+ "B0040"
511
+ ],
512
+ "Emergency Response Situations": [
513
+ "B0041"
514
+ ]
515
+ },
516
+ "next_id": 42
517
+ }