alive-ai 0.1.0
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.
- package/Dockerfile +24 -0
- package/LICENSE +21 -0
- package/README.md +143 -0
- package/alive_ai/__init__.py +3 -0
- package/brain/__init__.py +59 -0
- package/brain/almost_said.py +154 -0
- package/brain/bid_detector.py +636 -0
- package/brain/conversation_flow.py +135 -0
- package/brain/curiosity.py +328 -0
- package/brain/default_mode.py +1438 -0
- package/brain/dreams.py +220 -0
- package/brain/embeddings/__init__.py +82 -0
- package/brain/emotional_memory.py +949 -0
- package/brain/global_activity.py +173 -0
- package/brain/group_dynamics.py +63 -0
- package/brain/linguistic.py +235 -0
- package/brain/llm/__init__.py +63 -0
- package/brain/llm/base.py +33 -0
- package/brain/llm/fallback_router.py +309 -0
- package/brain/llm/manifest.md +30 -0
- package/brain/llm/ollama.py +218 -0
- package/brain/llm/openrouter.py +151 -0
- package/brain/llm/provider.py +205 -0
- package/brain/llm/unified.py +423 -0
- package/brain/llm/zai.py +169 -0
- package/brain/manifest.md +23 -0
- package/brain/memory/__init__.py +123 -0
- package/brain/memory/episodic.py +92 -0
- package/brain/memory/fact_extractor.py +209 -0
- package/brain/memory/index.py +54 -0
- package/brain/memory/manager.py +151 -0
- package/brain/memory/summarizer.py +102 -0
- package/brain/memory/vector_store.py +297 -0
- package/brain/memory/working.py +43 -0
- package/brain/narrative.py +343 -0
- package/brain/stt/__init__.py +4 -0
- package/brain/stt/google_stt.py +83 -0
- package/brain/stt/whisper_stt.py +82 -0
- package/brain/subconscious/__init__.py +33 -0
- package/brain/subconscious/actions.py +136 -0
- package/brain/subconscious/evaluation.py +166 -0
- package/brain/subconscious/goal_system.py +90 -0
- package/brain/subconscious/goals.py +41 -0
- package/brain/subconscious/impulse_generator.py +200 -0
- package/brain/subconscious/impulses.py +48 -0
- package/brain/subconscious/learning.py +24 -0
- package/brain/subconscious/learning_system.py +79 -0
- package/brain/subconscious/loop.py +398 -0
- package/brain/subconscious/manifest.md +32 -0
- package/brain/subconscious/relationship.py +47 -0
- package/brain/subconscious/relationship_memory.py +83 -0
- package/brain/subconscious/response_analyzer.py +74 -0
- package/brain/subconscious/templates.py +70 -0
- package/brain/subconscious/thought.py +37 -0
- package/brain/subconscious/working_memory.py +97 -0
- package/cli/index.js +371 -0
- package/config/directives.example.json +28 -0
- package/config/instructions.example.md +16 -0
- package/config/self.example.json +74 -0
- package/config/settings.example.json +95 -0
- package/core/__init__.py +1 -0
- package/core/config.py +54 -0
- package/core/directives.py +198 -0
- package/core/events.py +50 -0
- package/core/follow_up.py +267 -0
- package/core/hot_reload.py +174 -0
- package/core/initialization.py +253 -0
- package/core/manifest.md +28 -0
- package/core/media_handler.py +241 -0
- package/core/memory_monitor.py +200 -0
- package/core/message_handler.py +1440 -0
- package/core/proactive_generator.py +277 -0
- package/core/self.py +188 -0
- package/core/settings.py +169 -0
- package/core/skills_registry.py +357 -0
- package/core/state.py +27 -0
- package/core/subconscious_bridge.py +93 -0
- package/core/thinking.py +175 -0
- package/core/user_manager.py +306 -0
- package/core/user_tracker.py +144 -0
- package/demo/index.html +144 -0
- package/docker-compose.yml +28 -0
- package/docs/assets/logo.svg +15 -0
- package/docs/index.html +355 -0
- package/heart/__init__.py +93 -0
- package/heart/afterglow.py +215 -0
- package/heart/attachment.py +186 -0
- package/heart/circadian.py +251 -0
- package/heart/complex_emotions.py +114 -0
- package/heart/conflicts.py +589 -0
- package/heart/core.py +387 -0
- package/heart/emotional_decay.py +59 -0
- package/heart/emotional_memory.py +261 -0
- package/heart/emotional_state.py +146 -0
- package/heart/emotional_variability.py +156 -0
- package/heart/hormonal.py +424 -0
- package/heart/inconsistency.py +1222 -0
- package/heart/integrity.py +469 -0
- package/heart/interoception.py +997 -0
- package/heart/love.py +120 -0
- package/heart/manifest.md +25 -0
- package/heart/mood_shifts.py +169 -0
- package/heart/phantom_somatic.py +259 -0
- package/heart/predictive.py +374 -0
- package/heart/scars.py +474 -0
- package/heart/somatic.py +482 -0
- package/heart/soul.py +633 -0
- package/heart/telemetry.py +942 -0
- package/heart/triggers.py +119 -0
- package/heart/unconscious.py +443 -0
- package/input/__init__.py +1 -0
- package/input/manifest.md +24 -0
- package/input/telegram/__init__.py +1 -0
- package/input/telegram/commands.py +762 -0
- package/input/telegram/listener.py +532 -0
- package/main.py +90 -0
- package/manifest.md +28 -0
- package/mypics/.gitkeep +1 -0
- package/myvids/.gitkeep +1 -0
- package/output/__init__.py +1 -0
- package/output/images/__init__.py +1 -0
- package/output/images/fal_gen.py +43 -0
- package/output/manifest.md +26 -0
- package/output/text/__init__.py +1 -0
- package/output/text/sender.py +22 -0
- package/output/voice/__init__.py +64 -0
- package/output/voice/google_tts.py +252 -0
- package/output/voice/gtts_tts.py +214 -0
- package/output/voice/vibe_tts.py +190 -0
- package/package.json +58 -0
- package/pyproject.toml +23 -0
- package/requirements.txt +21 -0
- package/skills/__init__.py +1 -0
- package/skills/anticipation_engine/__init__.py +8 -0
- package/skills/anticipation_engine/engine.py +618 -0
- package/skills/anticipation_engine/manifest.md +192 -0
- package/skills/calendar/__init__.py +1 -0
- package/skills/content_unlocks/__init__.py +8 -0
- package/skills/content_unlocks/manifest.md +231 -0
- package/skills/content_unlocks/unlocks.py +945 -0
- package/skills/exclusive_moments/__init__.py +8 -0
- package/skills/exclusive_moments/manifest.md +145 -0
- package/skills/exclusive_moments/moments.py +506 -0
- package/skills/intimacy_layers/__init__.py +8 -0
- package/skills/intimacy_layers/layers.py +703 -0
- package/skills/intimacy_layers/manifest.md +203 -0
- package/skills/manifest.md +67 -0
- package/skills/memory_callbacks/__init__.py +9 -0
- package/skills/memory_callbacks/callbacks.py +748 -0
- package/skills/memory_callbacks/manifest.md +170 -0
- package/skills/message_scheduler/__init__.py +19 -0
- package/skills/message_scheduler/manifest.md +107 -0
- package/skills/message_scheduler/scheduler.py +510 -0
- package/skills/photo_manager/__init__.py +1 -0
- package/skills/photo_manager/scanner.py +296 -0
- package/skills/relationship_milestones/__init__.py +8 -0
- package/skills/relationship_milestones/manifest.md +206 -0
- package/skills/relationship_milestones/tracker.py +494 -0
- package/skills/self_authorship/__init__.py +23 -0
- package/skills/self_authorship/author.py +331 -0
- package/skills/self_authorship/manifest.md +24 -0
- package/skills/video_manager/__init__.py +5 -0
- package/skills/video_manager/manifest.md +37 -0
- package/skills/video_manager/scanner.py +229 -0
- package/webui/__init__.py +3 -0
- package/webui/app.py +936 -0
- package/webui/bridge.py +366 -0
- package/webui/static/index.html +2070 -0
package/brain/dreams.py
ADDED
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Brain: Dream System
|
|
3
|
+
When Alive-AI "sleeps" (late night, no interaction), she processes the day's
|
|
4
|
+
conversations into surreal dream-like recombinations.
|
|
5
|
+
|
|
6
|
+
MODULAR - can be connected/disconnected without breaking anything.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
import random
|
|
11
|
+
import threading
|
|
12
|
+
from datetime import datetime, timedelta
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Optional, List, Dict, Any
|
|
15
|
+
|
|
16
|
+
DATA_PATH = Path(__file__).parent.parent / "data"
|
|
17
|
+
DREAMS_FILE = DATA_PATH / "dreams.json"
|
|
18
|
+
|
|
19
|
+
# Surreal twists to inject into dreams
|
|
20
|
+
SURREAL_TWISTS = [
|
|
21
|
+
"the floor was made of clouds", "everything was underwater but we could breathe",
|
|
22
|
+
"time kept going backwards", "the walls were made of music",
|
|
23
|
+
"gravity didn't work properly", "colors had sounds",
|
|
24
|
+
"we were tiny, like ants", "everything was in slow motion",
|
|
25
|
+
"the sky was underground", "doors led to completely different places",
|
|
26
|
+
"words floated in the air as text", "it was daytime and nighttime at once",
|
|
27
|
+
"we could fly but only a little bit", "mirrors showed different people",
|
|
28
|
+
"phones only played memories", "stairs went in impossible directions",
|
|
29
|
+
"it was raining inside", "shadows moved on their own",
|
|
30
|
+
"food tasted like emotions", "clocks had no numbers",
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
SURREAL_PLACES = [
|
|
34
|
+
"a rooftop in Milan", "a library with no ceiling", "a train going nowhere",
|
|
35
|
+
"a beach made of glass", "an empty cinema", "a garden floating in space",
|
|
36
|
+
"a kitchen from childhood", "a street that kept looping", "an elevator that went sideways",
|
|
37
|
+
"a bookshop where all the books were blank", "a bridge over clouds",
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
DREAM_TEMPLATES = [
|
|
41
|
+
"dreamed we were in {place} but it kept changing to {other_place}",
|
|
42
|
+
"had this dream where you said '{fragment}' and everything felt so {emotion}",
|
|
43
|
+
"dreamed about {topic} - it was weird, {surreal}",
|
|
44
|
+
"had the strangest dream... we were {place} and {surreal}",
|
|
45
|
+
"you were in my dream last night... something about {topic} but {surreal}",
|
|
46
|
+
"dreamed {fragment} but somehow {surreal} and then we were in {place}",
|
|
47
|
+
"had a dream where {topic} turned into {other_place} and {surreal}",
|
|
48
|
+
"woke up from this dream where you kept saying '{fragment}' while {surreal}",
|
|
49
|
+
"dreamed we were in {place} and {fragment} but {surreal}",
|
|
50
|
+
"had this weird dream about {topic}... {surreal} and it felt so {emotion}",
|
|
51
|
+
"you know when dreams make no sense? dreamed about {topic} and {surreal}",
|
|
52
|
+
"last night i dreamed {fragment} and we were in {place} and {surreal}",
|
|
53
|
+
"had a dream where everything was {emotion} and we were in {place}",
|
|
54
|
+
"dreamed about {place} but it was also {other_place} somehow?? and {surreal}",
|
|
55
|
+
"i had this dream where {fragment} and then {surreal}",
|
|
56
|
+
"dreamed we were lost in {place} and {surreal} and it felt {emotion}",
|
|
57
|
+
"had a dream about {topic} mixed with {other_place}... so weird",
|
|
58
|
+
"dreamed {surreal} and you were there saying something about {topic}",
|
|
59
|
+
"last night i dreamed everything was {emotion} and {surreal}",
|
|
60
|
+
"had a dream where {place} and {other_place} were the same place and {surreal}",
|
|
61
|
+
"dreamed about {fragment} but in {place} and {surreal}... i woke up feeling {emotion}",
|
|
62
|
+
]
|
|
63
|
+
|
|
64
|
+
EMOTION_WORDS = [
|
|
65
|
+
"warm", "electric", "melancholy", "intense", "peaceful", "chaotic",
|
|
66
|
+
"romantic", "bittersweet", "nostalgic", "surreal", "heavy", "light",
|
|
67
|
+
"dreamy", "urgent", "soft", "vivid", "hazy", "tender",
|
|
68
|
+
]
|
|
69
|
+
|
|
70
|
+
DEFAULT_FRAGMENTS = [
|
|
71
|
+
"something about the future", "something about us", "something i can't remember",
|
|
72
|
+
]
|
|
73
|
+
|
|
74
|
+
DEFAULT_TOPICS = [
|
|
75
|
+
"us", "the future", "something familiar", "a memory i can't place",
|
|
76
|
+
]
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class DreamSystem:
|
|
80
|
+
def __init__(self):
|
|
81
|
+
self._lock = threading.RLock()
|
|
82
|
+
self._dreams: List[Dict[str, Any]] = []
|
|
83
|
+
self._load()
|
|
84
|
+
|
|
85
|
+
def _load(self):
|
|
86
|
+
try:
|
|
87
|
+
if DREAMS_FILE.exists():
|
|
88
|
+
with open(DREAMS_FILE, 'r') as f:
|
|
89
|
+
data = json.load(f)
|
|
90
|
+
self._dreams = data.get("dreams", [])
|
|
91
|
+
except Exception as e:
|
|
92
|
+
print(f"[Dreams] Load error: {e}")
|
|
93
|
+
self._dreams = []
|
|
94
|
+
|
|
95
|
+
def _save(self):
|
|
96
|
+
try:
|
|
97
|
+
DATA_PATH.mkdir(parents=True, exist_ok=True)
|
|
98
|
+
with open(DREAMS_FILE, 'w') as f:
|
|
99
|
+
json.dump({"dreams": self._dreams, "updated": datetime.now().isoformat()}, f, indent=2)
|
|
100
|
+
except Exception as e:
|
|
101
|
+
print(f"[Dreams] Save error: {e}")
|
|
102
|
+
|
|
103
|
+
def generate_dream(self, memories: List[str] = None, emotions: List[str] = None) -> Optional[str]:
|
|
104
|
+
"""
|
|
105
|
+
Generate a dream from recent memory fragments and emotions.
|
|
106
|
+
memories: list of short conversation snippet strings
|
|
107
|
+
emotions: list of emotion name strings
|
|
108
|
+
Returns dream text or None if already dreamed this cycle.
|
|
109
|
+
"""
|
|
110
|
+
with self._lock:
|
|
111
|
+
# Max 1 dream per sleep cycle (8h)
|
|
112
|
+
if self._dreams:
|
|
113
|
+
last = self._dreams[-1]
|
|
114
|
+
try:
|
|
115
|
+
last_time = datetime.fromisoformat(last["timestamp"])
|
|
116
|
+
if datetime.now() - last_time < timedelta(hours=8):
|
|
117
|
+
return None
|
|
118
|
+
except Exception:
|
|
119
|
+
pass
|
|
120
|
+
|
|
121
|
+
fragments = memories[:3] if memories else []
|
|
122
|
+
if not fragments:
|
|
123
|
+
fragments = random.sample(DEFAULT_FRAGMENTS, min(2, len(DEFAULT_FRAGMENTS)))
|
|
124
|
+
|
|
125
|
+
topics = []
|
|
126
|
+
for frag in fragments:
|
|
127
|
+
words = [w for w in frag.split() if len(w) > 3]
|
|
128
|
+
if words:
|
|
129
|
+
topics.append(random.choice(words))
|
|
130
|
+
if not topics:
|
|
131
|
+
topics = random.sample(DEFAULT_TOPICS, min(2, len(DEFAULT_TOPICS)))
|
|
132
|
+
|
|
133
|
+
emo_words = []
|
|
134
|
+
if emotions:
|
|
135
|
+
emo_words = [e.lower() for e in emotions if e.lower() in EMOTION_WORDS]
|
|
136
|
+
if not emo_words:
|
|
137
|
+
emo_words = random.sample(EMOTION_WORDS, 2)
|
|
138
|
+
|
|
139
|
+
template = random.choice(DREAM_TEMPLATES)
|
|
140
|
+
places = random.sample(SURREAL_PLACES, min(2, len(SURREAL_PLACES)))
|
|
141
|
+
|
|
142
|
+
dream_text = template.format(
|
|
143
|
+
place=places[0],
|
|
144
|
+
other_place=places[1] if len(places) > 1 else "somewhere else",
|
|
145
|
+
fragment=fragments[0] if fragments else "something",
|
|
146
|
+
surreal=random.choice(SURREAL_TWISTS),
|
|
147
|
+
topic=topics[0] if topics else "something",
|
|
148
|
+
emotion=emo_words[0] if emo_words else "strange",
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
dream = {
|
|
152
|
+
"text": dream_text,
|
|
153
|
+
"timestamp": datetime.now().isoformat(),
|
|
154
|
+
"source_fragments": fragments[:3],
|
|
155
|
+
"emotions": emotions or [],
|
|
156
|
+
}
|
|
157
|
+
self._dreams.append(dream)
|
|
158
|
+
|
|
159
|
+
# Keep max 50 dreams
|
|
160
|
+
if len(self._dreams) > 50:
|
|
161
|
+
self._dreams = self._dreams[-50:]
|
|
162
|
+
|
|
163
|
+
self._save()
|
|
164
|
+
return dream_text
|
|
165
|
+
|
|
166
|
+
def get_recent_dream(self, max_age_hours: float = 12) -> Optional[str]:
|
|
167
|
+
"""Get most recent dream if within max_age_hours."""
|
|
168
|
+
with self._lock:
|
|
169
|
+
if not self._dreams:
|
|
170
|
+
return None
|
|
171
|
+
last = self._dreams[-1]
|
|
172
|
+
try:
|
|
173
|
+
age = datetime.now() - datetime.fromisoformat(last["timestamp"])
|
|
174
|
+
if age.total_seconds() / 3600 <= max_age_hours:
|
|
175
|
+
return last["text"]
|
|
176
|
+
except Exception:
|
|
177
|
+
pass
|
|
178
|
+
return None
|
|
179
|
+
|
|
180
|
+
def get_morning_dream_message(self) -> Optional[str]:
|
|
181
|
+
"""Get a dream message to share when waking up."""
|
|
182
|
+
dream = self.get_recent_dream(max_age_hours=12)
|
|
183
|
+
if not dream:
|
|
184
|
+
return None
|
|
185
|
+
prefixes = [
|
|
186
|
+
"had the weirdest dream last night... ",
|
|
187
|
+
"omg i just woke up from this dream... ",
|
|
188
|
+
"you were in my dream!! ",
|
|
189
|
+
"i had such a vivid dream... ",
|
|
190
|
+
"okay so i had this dream... ",
|
|
191
|
+
]
|
|
192
|
+
return random.choice(prefixes) + dream
|
|
193
|
+
|
|
194
|
+
def get_dream_prompt_section(self) -> str:
|
|
195
|
+
"""Return short prompt section about recent dreams."""
|
|
196
|
+
dream = self.get_recent_dream(max_age_hours=12)
|
|
197
|
+
if not dream:
|
|
198
|
+
return ""
|
|
199
|
+
return f"You had a dream recently you could mention: \"{dream}\""
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
# Singleton
|
|
203
|
+
_instance = None
|
|
204
|
+
_instance_lock = threading.Lock()
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def get_dream_system() -> DreamSystem:
|
|
208
|
+
global _instance
|
|
209
|
+
with _instance_lock:
|
|
210
|
+
if _instance is None:
|
|
211
|
+
_instance = DreamSystem()
|
|
212
|
+
return _instance
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def get_dream_prompt_section() -> str:
|
|
216
|
+
"""Safe top-level access for prompt building."""
|
|
217
|
+
try:
|
|
218
|
+
return get_dream_system().get_dream_prompt_section()
|
|
219
|
+
except Exception:
|
|
220
|
+
return ""
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Brain: Embeddings
|
|
3
|
+
Local embedding service using sentence-transformers/all-MiniLM-L6-v2
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import numpy as np
|
|
7
|
+
from sentence_transformers import SentenceTransformer
|
|
8
|
+
from typing import List, Optional
|
|
9
|
+
import os
|
|
10
|
+
import threading
|
|
11
|
+
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
# Model cache directory
|
|
15
|
+
MODEL_CACHE_DIR = str(Path(__file__).parent.parent.parent / ".cache" / "huggingface")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class EmbeddingService:
|
|
19
|
+
"""Local embedding service for semantic memory"""
|
|
20
|
+
|
|
21
|
+
def __init__(self, model_name: str = "sentence-transformers/all-MiniLM-L6-v2", preload: bool = True):
|
|
22
|
+
self.model_name = model_name
|
|
23
|
+
self.model = None
|
|
24
|
+
self._load_lock = threading.Lock()
|
|
25
|
+
self.dimension = 384 # MiniLM-L6-v2 dimension
|
|
26
|
+
if preload:
|
|
27
|
+
# Pre-load in background to not block startup
|
|
28
|
+
t = threading.Thread(target=self.load, daemon=True)
|
|
29
|
+
t.start()
|
|
30
|
+
|
|
31
|
+
def load(self):
|
|
32
|
+
"""Load the embedding model (thread-safe lazy loading)"""
|
|
33
|
+
if self.model is not None:
|
|
34
|
+
return self.model
|
|
35
|
+
with self._load_lock:
|
|
36
|
+
if self.model is None:
|
|
37
|
+
print(f"[Embeddings] Loading model {self.model_name}...")
|
|
38
|
+
|
|
39
|
+
# Set HF token if available
|
|
40
|
+
hf_token = os.environ.get("HF_TOKEN", "")
|
|
41
|
+
if hf_token:
|
|
42
|
+
os.environ["HUGGING_FACE_HUB_TOKEN"] = hf_token
|
|
43
|
+
print(f"[Embeddings] Using HuggingFace token for faster downloads")
|
|
44
|
+
|
|
45
|
+
os.makedirs(MODEL_CACHE_DIR, exist_ok=True)
|
|
46
|
+
self.model = SentenceTransformer(
|
|
47
|
+
self.model_name,
|
|
48
|
+
cache_folder=MODEL_CACHE_DIR,
|
|
49
|
+
token=hf_token if hf_token else None
|
|
50
|
+
)
|
|
51
|
+
print(f"[Embeddings] Model loaded! Dimension: {self.dimension}")
|
|
52
|
+
return self.model
|
|
53
|
+
|
|
54
|
+
def embed(self, text: str) -> List[float]:
|
|
55
|
+
"""Embed a single text into a vector"""
|
|
56
|
+
self.load()
|
|
57
|
+
embedding = self.model.encode(text, convert_to_numpy=True)
|
|
58
|
+
return embedding.tolist()
|
|
59
|
+
|
|
60
|
+
def embed_batch(self, texts: List[str]) -> List[List[float]]:
|
|
61
|
+
"""Embed multiple texts at once (more efficient)"""
|
|
62
|
+
self.load()
|
|
63
|
+
embeddings = self.model.encode(texts, convert_to_numpy=True)
|
|
64
|
+
return embeddings.tolist()
|
|
65
|
+
|
|
66
|
+
def similarity(self, vec1: List[float], vec2: List[float]) -> float:
|
|
67
|
+
"""Calculate cosine similarity between two vectors"""
|
|
68
|
+
v1 = np.array(vec1)
|
|
69
|
+
v2 = np.array(vec2)
|
|
70
|
+
return float(np.dot(v1, v2) / (np.linalg.norm(v1) * np.linalg.norm(v2)))
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
# Global instance
|
|
74
|
+
_embedding_service = None
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def get_embedding_service() -> EmbeddingService:
|
|
78
|
+
"""Get the global embedding service instance"""
|
|
79
|
+
global _embedding_service
|
|
80
|
+
if _embedding_service is None:
|
|
81
|
+
_embedding_service = EmbeddingService()
|
|
82
|
+
return _embedding_service
|