Upload 3 files
Browse files- ace_system.py +703 -0
- app.py +804 -0
- 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 |
+
}
|