friday-framework-runtime 0.1.0a0__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.
- friday_framework_runtime-0.1.0a0.dist-info/METADATA +45 -0
- friday_framework_runtime-0.1.0a0.dist-info/RECORD +8 -0
- friday_framework_runtime-0.1.0a0.dist-info/WHEEL +4 -0
- friday_runtime/__init__.py +15 -0
- friday_runtime/config.py +1681 -0
- friday_runtime/di.py +342 -0
- friday_runtime/kernel.py +673 -0
- friday_runtime/tool_loading.py +177 -0
friday_runtime/di.py
ADDED
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Dependency Injection container for the Runtime Kernel.
|
|
3
|
+
|
|
4
|
+
Extracts the boot sequence from FridayContainer into reusable
|
|
5
|
+
factory methods. Each method creates a single service or group
|
|
6
|
+
of related services.
|
|
7
|
+
|
|
8
|
+
Ref: Phase 1 Build Plan, Step 4.2
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import logging
|
|
12
|
+
from typing import Any, cast
|
|
13
|
+
|
|
14
|
+
from friday_agent import DefaultToolRegistry
|
|
15
|
+
from friday_core import TelemetryInterface
|
|
16
|
+
from friday_core.interfaces import (
|
|
17
|
+
EmbeddingModel,
|
|
18
|
+
EpisodicStoreAdapter,
|
|
19
|
+
LLMClient,
|
|
20
|
+
TranscriptBackedEpisodicStore,
|
|
21
|
+
)
|
|
22
|
+
from friday_core.profiles import (
|
|
23
|
+
MemoryProfile,
|
|
24
|
+
create_graph_store,
|
|
25
|
+
create_vector_store,
|
|
26
|
+
)
|
|
27
|
+
from friday_core.telemetry import DebugTelemetryRecorder, OpenTelemetryProvider
|
|
28
|
+
from friday_llm import LiteLLMEmbeddingService, LiteLLMService
|
|
29
|
+
from friday_llm.config import ResilienceConfig
|
|
30
|
+
from friday_memory import (
|
|
31
|
+
CollectionConfig,
|
|
32
|
+
CollectionManager,
|
|
33
|
+
ContextAssembler,
|
|
34
|
+
InMemoryEpisodicStore,
|
|
35
|
+
MemoryController,
|
|
36
|
+
MemoryExtractor,
|
|
37
|
+
MultiAgentMemoryController,
|
|
38
|
+
NamespacePolicyEngine,
|
|
39
|
+
OntologyConfig,
|
|
40
|
+
PromotionCoordinator,
|
|
41
|
+
PromotionJournal,
|
|
42
|
+
)
|
|
43
|
+
from friday_transcript import SQLiteTranscriptStore
|
|
44
|
+
|
|
45
|
+
from friday_runtime.config import RuntimeConfig
|
|
46
|
+
from friday_runtime.tool_loading import register_runtime_tools
|
|
47
|
+
|
|
48
|
+
logger = logging.getLogger(__name__)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class DIContainer:
|
|
52
|
+
"""
|
|
53
|
+
Dependency Injection container that holds all created services.
|
|
54
|
+
|
|
55
|
+
The boot sequence mirrors the legacy FridayContainer.initialize()
|
|
56
|
+
order: telemetry -> LLM -> storage -> memory -> context -> tools.
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
def __init__(
|
|
60
|
+
self,
|
|
61
|
+
config: RuntimeConfig,
|
|
62
|
+
memory_profile: MemoryProfile,
|
|
63
|
+
) -> None:
|
|
64
|
+
"""
|
|
65
|
+
Initialize the DI container.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
config: Runtime configuration.
|
|
69
|
+
memory_profile: Memory storage profile.
|
|
70
|
+
"""
|
|
71
|
+
self._config = config
|
|
72
|
+
self._memory_profile = memory_profile
|
|
73
|
+
|
|
74
|
+
# Service slots (populated during boot)
|
|
75
|
+
self.telemetry: TelemetryInterface | None = None
|
|
76
|
+
self.llm: LiteLLMService | None = None
|
|
77
|
+
self.llm_pool: dict[str, LiteLLMService] = {}
|
|
78
|
+
self.embedding_pool: dict[str, LiteLLMEmbeddingService] = {}
|
|
79
|
+
self.embeddings: LiteLLMEmbeddingService | None = None
|
|
80
|
+
self.vector_store: Any = None
|
|
81
|
+
self.graph_store: Any = None
|
|
82
|
+
self.episodic_store: EpisodicStoreAdapter | None = None
|
|
83
|
+
self.transcript_store: SQLiteTranscriptStore | None = None
|
|
84
|
+
self.memory: MemoryController | None = None
|
|
85
|
+
self.multi_agent_memory: MultiAgentMemoryController | None = None
|
|
86
|
+
self.multi_agent_journal: PromotionJournal | None = None
|
|
87
|
+
self.multi_agent_coordinator: PromotionCoordinator | None = None
|
|
88
|
+
self.context_engine: ContextAssembler | None = None
|
|
89
|
+
self.tool_registry: DefaultToolRegistry | None = None
|
|
90
|
+
|
|
91
|
+
async def boot(self) -> None:
|
|
92
|
+
"""
|
|
93
|
+
Execute the full boot sequence.
|
|
94
|
+
|
|
95
|
+
Creates all eager services in dependency order.
|
|
96
|
+
Raises on failure — partial initialization is not supported.
|
|
97
|
+
"""
|
|
98
|
+
self._create_telemetry()
|
|
99
|
+
self._create_llm_services()
|
|
100
|
+
await self._create_storage_adapters()
|
|
101
|
+
self._create_memory_controller()
|
|
102
|
+
self._create_multi_agent_stack()
|
|
103
|
+
self._create_context_engine()
|
|
104
|
+
self._create_tool_registry()
|
|
105
|
+
logger.info("DI container boot complete")
|
|
106
|
+
|
|
107
|
+
async def shutdown(self) -> None:
|
|
108
|
+
"""
|
|
109
|
+
Gracefully shut down all services.
|
|
110
|
+
|
|
111
|
+
Safe to call multiple times.
|
|
112
|
+
"""
|
|
113
|
+
logger.info("Shutting down DI container...")
|
|
114
|
+
# Currently ChromaDB handles its own cleanup.
|
|
115
|
+
# Future: flush telemetry, persist memory, etc.
|
|
116
|
+
logger.info("DI container shutdown complete")
|
|
117
|
+
|
|
118
|
+
# --- Private Boot Methods ---
|
|
119
|
+
|
|
120
|
+
def _create_telemetry(self) -> None:
|
|
121
|
+
"""Create telemetry interface based on config."""
|
|
122
|
+
if self._config.debug_mode:
|
|
123
|
+
self.telemetry = DebugTelemetryRecorder()
|
|
124
|
+
logger.info("Using DebugTelemetryRecorder for telemetry")
|
|
125
|
+
elif self._config.telemetry_enabled:
|
|
126
|
+
self.telemetry = OpenTelemetryProvider(
|
|
127
|
+
service_name="friday",
|
|
128
|
+
endpoint=self._config.telemetry_endpoint,
|
|
129
|
+
)
|
|
130
|
+
logger.info("Using OpenTelemetryProvider for telemetry")
|
|
131
|
+
else:
|
|
132
|
+
self.telemetry = None
|
|
133
|
+
logger.info("Telemetry disabled")
|
|
134
|
+
|
|
135
|
+
def _create_llm_services(self) -> None:
|
|
136
|
+
"""Create LLM and embedding services."""
|
|
137
|
+
self.llm_pool = {}
|
|
138
|
+
for service_name, service_config in self._config.llm_pool.services.items():
|
|
139
|
+
self.llm_pool[service_name] = LiteLLMService(
|
|
140
|
+
model_name=service_config.model,
|
|
141
|
+
api_key=service_config.api_key,
|
|
142
|
+
api_base=service_config.api_base,
|
|
143
|
+
telemetry=self.telemetry,
|
|
144
|
+
resilience=ResilienceConfig(
|
|
145
|
+
max_retries=service_config.resilience.max_retries,
|
|
146
|
+
timeout_seconds=float(service_config.resilience.timeout_seconds),
|
|
147
|
+
),
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
self.llm = self.llm_pool[self._config.llm_pool.default_service]
|
|
151
|
+
default_service = self._config.get_llm_service_config()
|
|
152
|
+
|
|
153
|
+
self.embedding_pool = {}
|
|
154
|
+
for (
|
|
155
|
+
service_name,
|
|
156
|
+
embedding_service_config,
|
|
157
|
+
) in self._config.embedding_pool.services.items():
|
|
158
|
+
self.embedding_pool[service_name] = LiteLLMEmbeddingService(
|
|
159
|
+
model_name=embedding_service_config.model,
|
|
160
|
+
api_key=embedding_service_config.api_key,
|
|
161
|
+
api_base=embedding_service_config.api_base,
|
|
162
|
+
telemetry=self.telemetry,
|
|
163
|
+
resilience=ResilienceConfig(
|
|
164
|
+
max_retries=embedding_service_config.resilience.max_retries,
|
|
165
|
+
timeout_seconds=float(
|
|
166
|
+
embedding_service_config.resilience.timeout_seconds
|
|
167
|
+
),
|
|
168
|
+
),
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
self.embeddings = self.embedding_pool[self._config.embedding_pool.default_service]
|
|
172
|
+
default_embedding_service = self._config.get_embedding_service_config()
|
|
173
|
+
|
|
174
|
+
logger.info(
|
|
175
|
+
"LLM pool configured with default service '%s' (%s) across %d services; "
|
|
176
|
+
"embedding pool default '%s' (%s) across %d services",
|
|
177
|
+
self._config.llm_pool.default_service,
|
|
178
|
+
default_service.model,
|
|
179
|
+
len(self.llm_pool),
|
|
180
|
+
self._config.embedding_pool.default_service,
|
|
181
|
+
default_embedding_service.model,
|
|
182
|
+
len(self.embedding_pool),
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
async def _create_storage_adapters(self) -> None:
|
|
186
|
+
"""Create storage adapter instances based on memory profile."""
|
|
187
|
+
assert self.embeddings is not None
|
|
188
|
+
graph_storage_path = self._config.resolve_runtime_state_path("memory/graph.json")
|
|
189
|
+
graph_storage_path.parent.mkdir(parents=True, exist_ok=True)
|
|
190
|
+
self.vector_store = create_vector_store(
|
|
191
|
+
profile=self._memory_profile,
|
|
192
|
+
embedding_function=cast(EmbeddingModel, self.embeddings),
|
|
193
|
+
path=self._config.chroma_path,
|
|
194
|
+
collection_name=self._config.chroma_collection,
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
self.graph_store = create_graph_store(
|
|
198
|
+
profile=self._memory_profile,
|
|
199
|
+
storage_path=str(graph_storage_path),
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
if self._config.transcript.enabled:
|
|
203
|
+
transcript_path = self._config.resolve_transcript_path(
|
|
204
|
+
self._config.transcript.path
|
|
205
|
+
)
|
|
206
|
+
transcript_path.parent.mkdir(parents=True, exist_ok=True)
|
|
207
|
+
self.transcript_store = SQLiteTranscriptStore(transcript_path)
|
|
208
|
+
self.episodic_store = TranscriptBackedEpisodicStore(
|
|
209
|
+
transcript_store=self.transcript_store,
|
|
210
|
+
model_name=self._config.get_llm_service_config().model,
|
|
211
|
+
system_prompt=self._config.prompting.base_system_prompt,
|
|
212
|
+
capture_context_log=self._config.transcript.capture_context_log,
|
|
213
|
+
)
|
|
214
|
+
else:
|
|
215
|
+
self.transcript_store = None
|
|
216
|
+
self.episodic_store = InMemoryEpisodicStore()
|
|
217
|
+
|
|
218
|
+
logger.info(
|
|
219
|
+
f"Storage adapters initialized (profile: {self._memory_profile.value})"
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
def _create_memory_controller(self) -> None:
|
|
223
|
+
"""Create memory controller with lifecycle-aware collections."""
|
|
224
|
+
assert self.embeddings is not None
|
|
225
|
+
assert self.episodic_store is not None
|
|
226
|
+
base_name = self._config.chroma_collection
|
|
227
|
+
collection_config = CollectionConfig(
|
|
228
|
+
short_term_collection=f"{base_name}_short",
|
|
229
|
+
long_term_collection=f"{base_name}_long",
|
|
230
|
+
decayed_collection=f"{base_name}_decayed",
|
|
231
|
+
)
|
|
232
|
+
collection_manager = CollectionManager(
|
|
233
|
+
embedding_function=cast(EmbeddingModel, self.embeddings),
|
|
234
|
+
config=collection_config,
|
|
235
|
+
path=self._config.chroma_path,
|
|
236
|
+
telemetry=self.telemetry,
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
self.memory = MemoryController(
|
|
240
|
+
vector_store=self.vector_store,
|
|
241
|
+
graph_store=self.graph_store,
|
|
242
|
+
episodic_store=cast(EpisodicStoreAdapter, self.episodic_store),
|
|
243
|
+
telemetry=self.telemetry,
|
|
244
|
+
collection_manager=collection_manager,
|
|
245
|
+
transcript_store=self.transcript_store,
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
logger.info("Memory controller initialized with lifecycle collections")
|
|
249
|
+
|
|
250
|
+
def _create_multi_agent_stack(self) -> None:
|
|
251
|
+
"""Create additive multi-agent memory stack when mode is enabled."""
|
|
252
|
+
if self._config.memory_mode != "multi_agent":
|
|
253
|
+
self.multi_agent_memory = None
|
|
254
|
+
self.multi_agent_journal = None
|
|
255
|
+
self.multi_agent_coordinator = None
|
|
256
|
+
return
|
|
257
|
+
|
|
258
|
+
self.multi_agent_journal = PromotionJournal()
|
|
259
|
+
self.multi_agent_coordinator = PromotionCoordinator(self.multi_agent_journal)
|
|
260
|
+
assert self.memory is not None
|
|
261
|
+
self.multi_agent_memory = MultiAgentMemoryController(
|
|
262
|
+
memory=self.memory,
|
|
263
|
+
session_id="runtime-default",
|
|
264
|
+
agent_id="runtime",
|
|
265
|
+
namespace="default",
|
|
266
|
+
policy=NamespacePolicyEngine(
|
|
267
|
+
session_id="runtime-default",
|
|
268
|
+
actor_id="runtime",
|
|
269
|
+
namespace="default",
|
|
270
|
+
),
|
|
271
|
+
coordinator=self.multi_agent_coordinator,
|
|
272
|
+
)
|
|
273
|
+
logger.warning(
|
|
274
|
+
"Multi-agent mode enabled without arbitrator service binding; "
|
|
275
|
+
"shared promotion remains unavailable until arbitrator is attached."
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
def _create_context_engine(self) -> None:
|
|
279
|
+
"""Create context assembler and wire extractor."""
|
|
280
|
+
assert self.memory is not None
|
|
281
|
+
self.context_engine = ContextAssembler(
|
|
282
|
+
memory_controller=self.memory,
|
|
283
|
+
telemetry=self.telemetry,
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
if self._config.memory_extraction.enabled:
|
|
287
|
+
assert self.llm is not None
|
|
288
|
+
extraction_llm = self.llm_pool[self._config.memory_extraction.llm_service or self._config.llm_pool.default_service]
|
|
289
|
+
extraction_embeddings: EmbeddingModel | None = None
|
|
290
|
+
extraction_embedding_service = (
|
|
291
|
+
self._config.memory_extraction.embedding_service
|
|
292
|
+
)
|
|
293
|
+
if extraction_embedding_service is not None:
|
|
294
|
+
extraction_embeddings = cast(
|
|
295
|
+
EmbeddingModel,
|
|
296
|
+
self.embedding_pool[extraction_embedding_service],
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
ontology = self._config.memory_extraction.ontology
|
|
300
|
+
extractor = MemoryExtractor(
|
|
301
|
+
memory_controller=self.memory,
|
|
302
|
+
llm=cast(LLMClient, extraction_llm),
|
|
303
|
+
telemetry=self.telemetry,
|
|
304
|
+
prompt_template=self._config.memory_extraction.prompt,
|
|
305
|
+
user_name=self._config.memory_extraction.user_name,
|
|
306
|
+
assistant_name=self._config.memory_extraction.assistant_name,
|
|
307
|
+
embedding_model=extraction_embeddings,
|
|
308
|
+
max_tokens=self._config.memory_extraction.max_tokens,
|
|
309
|
+
ontology_config=(
|
|
310
|
+
OntologyConfig.from_dict(ontology) if ontology is not None else None
|
|
311
|
+
),
|
|
312
|
+
rejection_log_path=self._config.memory_extraction.rejection_log,
|
|
313
|
+
)
|
|
314
|
+
self.context_engine.set_extractor(extractor)
|
|
315
|
+
logger.info(
|
|
316
|
+
"Context engine initialized with extractor service '%s'",
|
|
317
|
+
self._config.memory_extraction.llm_service,
|
|
318
|
+
)
|
|
319
|
+
else:
|
|
320
|
+
logger.info("Context engine initialized without archive extractor")
|
|
321
|
+
|
|
322
|
+
def _create_tool_registry(self) -> None:
|
|
323
|
+
"""Create and configure tool registry."""
|
|
324
|
+
self.tool_registry = DefaultToolRegistry()
|
|
325
|
+
register_runtime_tools(self.tool_registry, self._config.tools)
|
|
326
|
+
|
|
327
|
+
# Bind dependencies to built-in tools
|
|
328
|
+
if self.tool_registry.has_tool("recall_memory"):
|
|
329
|
+
self.tool_registry.bind_dependency(
|
|
330
|
+
"recall_memory",
|
|
331
|
+
memory=self.memory,
|
|
332
|
+
)
|
|
333
|
+
if self.tool_registry.has_tool("store_fact"):
|
|
334
|
+
self.tool_registry.bind_dependency(
|
|
335
|
+
"store_fact",
|
|
336
|
+
memory=self.memory,
|
|
337
|
+
)
|
|
338
|
+
|
|
339
|
+
logger.info(
|
|
340
|
+
f"Tool registry initialized with "
|
|
341
|
+
f"{len(self.tool_registry.list_tools())} tools"
|
|
342
|
+
)
|