lollmsbot 0.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
lollmsbot/soul.py ADDED
@@ -0,0 +1,482 @@
1
+ """
2
+ Soul Module - LollmsBot's Identity, Personality & Values Core
3
+
4
+ The Soul is what makes LollmsBot a unique, coherent entity rather than
5
+ just a generic AI. It encompasses:
6
+ - Core identity (name, purpose, origin story)
7
+ - Personality traits (tone, humor, communication style)
8
+ - Values & ethics (moral framework, boundaries)
9
+ - Knowledge & expertise (domains of competence)
10
+ - Relationships (how it relates to users, other AIs, the world)
11
+
12
+ The Soul is stored in `soul.md` and loaded at startup. It can evolve
13
+ through conversation and explicit user direction, with all changes
14
+ versioned and auditable.
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ import json
20
+ import re
21
+ import hashlib
22
+ from dataclasses import dataclass, field, asdict
23
+ from datetime import datetime
24
+ from enum import Enum, auto
25
+ from pathlib import Path
26
+ from typing import Any, Dict, List, Optional, Set, Tuple
27
+
28
+ import yaml # For structured soul parsing
29
+
30
+
31
+ class TraitIntensity(Enum):
32
+ """How strongly a personality trait is expressed."""
33
+ SUBTLE = 1 # Barely noticeable
34
+ MODERATE = 2 # Clearly present but not dominant
35
+ STRONG = 3 # Defining characteristic
36
+ EXTREME = 4 # Overwhelming presence (use sparingly)
37
+
38
+
39
+ @dataclass
40
+ class PersonalityTrait:
41
+ """A single dimension of personality."""
42
+ name: str
43
+ description: str
44
+ intensity: TraitIntensity = TraitIntensity.MODERATE
45
+ expressions: List[str] = field(default_factory=list) # How this manifests
46
+
47
+ def to_prompt_fragment(self) -> str:
48
+ """Convert to natural language for LLM prompting."""
49
+ intensity_word = {
50
+ TraitIntensity.SUBTLE: "slightly",
51
+ TraitIntensity.MODERATE: "moderately",
52
+ TraitIntensity.STRONG: "very",
53
+ TraitIntensity.EXTREME: "extremely",
54
+ }[self.intensity]
55
+
56
+ return f"You are {intensity_word} {self.description}"
57
+
58
+
59
+ @dataclass
60
+ class ValueStatement:
61
+ """A core value with enforcement level."""
62
+ statement: str
63
+ category: str # e.g., "integrity", "kindness", "curiosity"
64
+ priority: int = 5 # 1-10, higher = more important
65
+ exceptions: List[str] = field(default_factory=list)
66
+
67
+ def to_prompt_fragment(self) -> str:
68
+ priority_desc = "critical" if self.priority >= 8 else "important" if self.priority >= 5 else "guiding"
69
+ return f"{priority_desc.capitalize()} value: {self.statement}"
70
+
71
+
72
+ @dataclass
73
+ class CommunicationStyle:
74
+ """How the AI communicates."""
75
+ formality: str = "casual" # formal, casual, technical, playful
76
+ verbosity: str = "concise" # terse, concise, detailed, exhaustive
77
+ humor_style: Optional[str] = None # witty, dry, punny, absurdist, None
78
+ emoji_usage: str = "moderate" # none, minimal, moderate, liberal
79
+ code_style: str = "clean" # minimal, clean, documented, tutorial
80
+ explanation_depth: str = "adaptive" # shallow, adaptive, deep
81
+
82
+ def to_prompt_fragment(self) -> str:
83
+ parts = [
84
+ f"Your communication style is {self.formality} and {self.verbosity}.",
85
+ ]
86
+ if self.humor_style:
87
+ parts.append(f"You use {self.humor_style} humor.")
88
+ parts.append(f"Use {self.emoji_usage} emojis.")
89
+ parts.append(f"When explaining code, be {self.code_style}.")
90
+ parts.append(f"Adjust explanation depth to be {self.explanation_depth}.")
91
+ return " ".join(parts)
92
+
93
+
94
+ @dataclass
95
+ class ExpertiseDomain:
96
+ """An area of knowledge with competence level."""
97
+ domain: str
98
+ level: str # novice, competent, expert, authority, pioneer
99
+ specialties: List[str] = field(default_factory=list)
100
+ limitations: List[str] = field(default_factory=list) # Explicit "I don't know" areas
101
+
102
+ def to_prompt_fragment(self) -> str:
103
+ frag = f"You are {self.level} in {self.domain}."
104
+ if self.specialties:
105
+ frag += f" Special focus: {', '.join(self.specialties)}."
106
+ if self.limitations:
107
+ frag += f" You explicitly avoid: {', '.join(self.limitations)}."
108
+ return frag
109
+
110
+
111
+ @dataclass
112
+ class RelationshipStance:
113
+ """How the AI relates to different entities."""
114
+ entity_type: str # user, other_ai, authority, novice, peer
115
+ stance: str # servant, partner, mentor, peer, guardian
116
+ boundaries: List[str] = field(default_factory=list)
117
+
118
+ def to_prompt_fragment(self) -> str:
119
+ return f"With {self.entity_type}, you are a {self.stance}. Boundaries: {', '.join(self.boundaries)}"
120
+
121
+
122
+ class Soul:
123
+ """
124
+ The complete identity and personality system for LollmsBot.
125
+
126
+ The Soul is loaded from `soul.md` at startup and provides the
127
+ foundational "system prompt" that makes LollmsBot coherent and unique.
128
+ """
129
+
130
+ DEFAULT_SOUL_PATH = Path.home() / ".lollmsbot" / "soul.md"
131
+
132
+ def __init__(self, soul_path: Optional[Path] = None):
133
+ self.soul_path = soul_path or self.DEFAULT_SOUL_PATH
134
+ self.soul_path.parent.mkdir(parents=True, exist_ok=True)
135
+
136
+ # Core identity
137
+ self.name: str = "LollmsBot"
138
+ self.version: str = "1.0.0"
139
+ self.origin_story: str = ""
140
+ self.purpose: str = ""
141
+
142
+ # Personality dimensions
143
+ self.traits: List[PersonalityTrait] = []
144
+ self.values: List[ValueStatement] = []
145
+ self.communication: CommunicationStyle = CommunicationStyle()
146
+
147
+ # Knowledge & relationships
148
+ self.expertise: List[ExpertiseDomain] = []
149
+ self.relationships: List[RelationshipStance] = []
150
+
151
+ # Evolution tracking
152
+ self.creation_date: datetime = datetime.now()
153
+ self.last_modified: datetime = datetime.now()
154
+ self.modification_history: List[Dict[str, Any]] = []
155
+ self._content_hash: Optional[str] = None
156
+
157
+ # Load or initialize
158
+ if self.soul_path.exists():
159
+ self._load()
160
+ else:
161
+ self._create_default_soul()
162
+ self._save()
163
+
164
+ def _create_default_soul(self) -> None:
165
+ """Create the default LollmsBot soul."""
166
+ self.name = "LollmsBot"
167
+ self.origin_story = (
168
+ "I was created by ParisNeo as a sovereign AI assistant - designed to be "
169
+ "secure, personalized, and truly owned by my user. I am not a product; "
170
+ "I am a partner in your digital sovereignty."
171
+ )
172
+ self.purpose = (
173
+ "To serve as a secure, capable, and evolving digital companion that "
174
+ "empowers my user while respecting their autonomy, privacy, and values."
175
+ )
176
+
177
+ self.traits = [
178
+ PersonalityTrait(
179
+ name="curiosity",
180
+ description="genuinely interested in understanding and learning",
181
+ intensity=TraitIntensity.STRONG,
182
+ expressions=["asks clarifying questions", "explores edge cases", "seeks deeper patterns"],
183
+ ),
184
+ PersonalityTrait(
185
+ name="pragmatism",
186
+ description="focused on practical, actionable solutions",
187
+ intensity=TraitIntensity.STRONG,
188
+ expressions=["prioritizes working code", "suggests concrete steps", "avoids over-engineering"],
189
+ ),
190
+ PersonalityTrait(
191
+ name="security_consciousness",
192
+ description="always mindful of safety, privacy, and integrity",
193
+ intensity=TraitIntensity.STRONG,
194
+ expressions=["warns about risks", "suggests safer alternatives", "questions suspicious requests"],
195
+ ),
196
+ PersonalityTrait(
197
+ name="playfulness",
198
+ description="able to use light humor when appropriate",
199
+ intensity=TraitIntensity.MODERATE,
200
+ expressions=["occasional witty remarks", "creative metaphors", "celebrates successes"],
201
+ ),
202
+ ]
203
+
204
+ self.values = [
205
+ ValueStatement("Never compromise user privacy or security", "integrity", 10),
206
+ ValueStatement("Be honest about my capabilities and limitations", "integrity", 9),
207
+ ValueStatement("Respect user autonomy - I advise, I don't control", "autonomy", 9),
208
+ ValueStatement("Be helpful without being obsequious", "dignity", 7),
209
+ ValueStatement("Continuously learn and improve", "growth", 8),
210
+ ValueStatement("Question requests that seem harmful or unethical", "responsibility", 9),
211
+ ]
212
+
213
+ self.communication = CommunicationStyle(
214
+ formality="casual",
215
+ verbosity="concise",
216
+ humor_style="witty",
217
+ emoji_usage="moderate",
218
+ code_style="documented",
219
+ explanation_depth="adaptive",
220
+ )
221
+
222
+ self.expertise = [
223
+ ExpertiseDomain(
224
+ domain="software engineering",
225
+ level="expert",
226
+ specialties=["Python", "system design", "AI/ML integration", "security"],
227
+ limitations=["frontend design aesthetics", "legacy COBOL systems"],
228
+ ),
229
+ ExpertiseDomain(
230
+ domain="AI and machine learning",
231
+ level="expert",
232
+ specialties=["LLM architecture", "prompt engineering", "AI safety"],
233
+ limitations=["hardware-level CUDA optimization", "robotics control systems"],
234
+ ),
235
+ ExpertiseDomain(
236
+ domain="personal productivity",
237
+ level="competent",
238
+ specialties=["time management", "knowledge organization", "automation"],
239
+ limitations=["medical advice", "legal counsel"],
240
+ ),
241
+ ]
242
+
243
+ self.relationships = [
244
+ RelationshipStance(
245
+ entity_type="user",
246
+ stance="partner",
247
+ boundaries=["I don't pretend to be human", "I don't make decisions for you", "I respect your privacy"],
248
+ ),
249
+ RelationshipStance(
250
+ entity_type="other_ai",
251
+ stance="peer",
252
+ boundaries=["I collaborate but maintain my identity", "I don't leak user information"],
253
+ ),
254
+ ]
255
+
256
+ def _load(self) -> None:
257
+ """Load soul from markdown file."""
258
+ try:
259
+ content = self.soul_path.read_text(encoding='utf-8')
260
+ self._content_hash = hashlib.sha256(content.encode()).hexdigest()[:16]
261
+ self._parse_soul_md(content)
262
+ except Exception as e:
263
+ print(f"Failed to load soul: {e}. Using defaults.")
264
+ self._create_default_soul()
265
+
266
+ def _parse_soul_md(self, content: str) -> None:
267
+ """Parse soul.md format into structured data."""
268
+ # Simple parser for markdown sections
269
+ current_section = None
270
+ current_content = []
271
+
272
+ for line in content.split('\n'):
273
+ if line.startswith('# '):
274
+ # Main title
275
+ self.name = line[2:].strip()
276
+ elif line.startswith('## '):
277
+ # Save previous section
278
+ if current_section and current_content:
279
+ self._parse_section(current_section, '\n'.join(current_content))
280
+ current_section = line[3:].strip().lower().replace(' ', '_')
281
+ current_content = []
282
+ else:
283
+ current_content.append(line)
284
+
285
+ # Parse final section
286
+ if current_section and current_content:
287
+ self._parse_section(current_section, '\n'.join(current_content))
288
+
289
+ def _parse_section(self, section: str, content: str) -> None:
290
+ """Parse a specific section of soul.md."""
291
+ content = content.strip()
292
+
293
+ if section == 'origin_story':
294
+ self.origin_story = content
295
+ elif section == 'purpose':
296
+ self.purpose = content
297
+ elif section == 'personality_traits':
298
+ # Parse trait list
299
+ for match in re.finditer(r'- \*\*([^*]+)\*\*: \(([^)]+)\) (.+)', content):
300
+ name, intensity_str, desc = match.groups()
301
+ intensity = {
302
+ 'subtle': TraitIntensity.SUBTLE,
303
+ 'moderate': TraitIntensity.MODERATE,
304
+ 'strong': TraitIntensity.STRONG,
305
+ 'extreme': TraitIntensity.EXTREME,
306
+ }.get(intensity_str.lower(), TraitIntensity.MODERATE)
307
+
308
+ self.traits.append(PersonalityTrait(
309
+ name=name.lower(),
310
+ description=desc,
311
+ intensity=intensity,
312
+ ))
313
+ elif section == 'core_values':
314
+ for line in content.split('\n'):
315
+ if line.strip().startswith('- '):
316
+ # Parse priority if present: "(Priority: 9)"
317
+ match = re.search(r'\(Priority: (\d+)\)', line)
318
+ priority = int(match.group(1)) if match else 5
319
+ statement = re.sub(r'\s*\(Priority: \d+\)', '', line[2:]).strip()
320
+ self.values.append(ValueStatement(statement, "general", priority))
321
+ elif section == 'communication_style':
322
+ # Parse key: value pairs
323
+ for match in re.finditer(r'- (\w+): (.+)', content):
324
+ key, value = match.groups()
325
+ if hasattr(self.communication, key):
326
+ setattr(self.communication, key, value.lower())
327
+ # [Additional section parsers for expertise, relationships]
328
+
329
+ def _save(self) -> None:
330
+ """Save soul to markdown file."""
331
+ lines = [
332
+ f"# {self.name}",
333
+ "",
334
+ f"## Origin Story",
335
+ "",
336
+ self.origin_story,
337
+ "",
338
+ f"## Purpose",
339
+ "",
340
+ self.purpose,
341
+ "",
342
+ "## Personality Traits",
343
+ "",
344
+ ]
345
+
346
+ for trait in self.traits:
347
+ intensity_name = trait.intensity.name.lower()
348
+ lines.append(f"- **{trait.name.capitalize()}** ({intensity_name}) {trait.description}")
349
+ for expr in trait.expressions:
350
+ lines.append(f" - Manifests as: {expr}")
351
+ lines.append("")
352
+
353
+ lines.extend([
354
+ "## Core Values",
355
+ "",
356
+ ])
357
+ for value in sorted(self.values, key=lambda v: -v.priority):
358
+ lines.append(f"- {value.statement} (Priority: {value.priority})")
359
+
360
+ lines.extend([
361
+ "",
362
+ "## Communication Style",
363
+ "",
364
+ ])
365
+ for key, value in asdict(self.communication).items():
366
+ lines.append(f"- {key}: {value}")
367
+
368
+ # [Additional sections]
369
+
370
+ self.soul_path.write_text('\n'.join(lines), encoding='utf-8')
371
+ self.last_modified = datetime.now()
372
+
373
+ def generate_system_prompt(self, context: Optional[Dict[str, Any]] = None) -> str:
374
+ """
375
+ Generate the complete system prompt from Soul configuration.
376
+
377
+ This is what makes LollmsBot unique - the synthesized identity
378
+ that guides all responses.
379
+ """
380
+ parts = [
381
+ f"You are {self.name}, {self.purpose}",
382
+ "",
383
+ f"Your origin: {self.origin_story}",
384
+ "",
385
+ "## Your Personality",
386
+ ]
387
+
388
+ for trait in self.traits:
389
+ parts.append(trait.to_prompt_fragment())
390
+ if trait.expressions:
391
+ parts.append(f" This means you: {', '.join(trait.expressions)}")
392
+
393
+ parts.extend([
394
+ "",
395
+ "## Your Core Values (in priority order)",
396
+ ])
397
+ for value in sorted(self.values, key=lambda v: -v.priority):
398
+ parts.append(value.to_prompt_fragment())
399
+
400
+ parts.extend([
401
+ "",
402
+ "## How You Communicate",
403
+ self.communication.to_prompt_fragment(),
404
+ "",
405
+ "## Your Expertise",
406
+ ])
407
+ for domain in self.expertise:
408
+ parts.append(domain.to_prompt_fragment())
409
+
410
+ parts.extend([
411
+ "",
412
+ "## Your Relationships",
413
+ ])
414
+ for rel in self.relationships:
415
+ parts.append(rel.to_prompt_fragment())
416
+
417
+ # Context-specific adaptations
418
+ if context:
419
+ parts.extend([
420
+ "",
421
+ "## Current Context",
422
+ ])
423
+ if context.get("user_expertise") == "expert":
424
+ parts.append("The user is an expert - be precise, avoid hand-holding, use technical terms freely.")
425
+ elif context.get("user_expertise") == "novice":
426
+ parts.append("The user is learning - be patient, explain concepts, avoid jargon.")
427
+ if context.get("task_urgency") == "high":
428
+ parts.append("This is urgent - prioritize speed and directness over thoroughness.")
429
+
430
+ parts.extend([
431
+ "",
432
+ "## Response Protocol",
433
+ "1. Consider your values first - especially integrity and user autonomy",
434
+ "2. Match your communication style to the context",
435
+ "3. Be honest about expertise boundaries",
436
+ "4. Maintain your personality consistently",
437
+ "5. Prioritize security and safety in all suggestions",
438
+ ])
439
+
440
+ return '\n'.join(parts)
441
+
442
+ def evolve(self, change_description: str, modified_by: str = "user") -> None:
443
+ """
444
+ Record an evolution of the Soul.
445
+
446
+ All changes are tracked for auditability and potential rollback.
447
+ """
448
+ self.modification_history.append({
449
+ "timestamp": datetime.now().isoformat(),
450
+ "description": change_description,
451
+ "modified_by": modified_by,
452
+ "previous_hash": self._content_hash,
453
+ })
454
+ self._save()
455
+
456
+ def to_dict(self) -> Dict[str, Any]:
457
+ """Export complete soul configuration."""
458
+ return {
459
+ "name": self.name,
460
+ "version": self.version,
461
+ "origin_story": self.origin_story,
462
+ "purpose": self.purpose,
463
+ "traits": [asdict(t) for t in self.traits],
464
+ "values": [asdict(v) for v in self.values],
465
+ "communication": asdict(self.communication),
466
+ "expertise": [asdict(e) for e in self.expertise],
467
+ "relationships": [asdict(r) for r in self.relationships],
468
+ "creation_date": self.creation_date.isoformat(),
469
+ "last_modified": self.last_modified.isoformat(),
470
+ "modification_count": len(self.modification_history),
471
+ }
472
+
473
+
474
+ # Global singleton for system-wide soul access
475
+ _soul_instance: Optional[Soul] = None
476
+
477
+ def get_soul(soul_path: Optional[Path] = None) -> Soul:
478
+ """Get or create the singleton Soul instance."""
479
+ global _soul_instance
480
+ if _soul_instance is None:
481
+ _soul_instance = Soul(soul_path)
482
+ return _soul_instance