flock-core 0.4.0b50__py3-none-any.whl → 0.4.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.

Potentially problematic release.


This version of flock-core might be problematic. Click here for more details.

Files changed (34) hide show
  1. flock/adapter/__init__.py +14 -0
  2. flock/adapter/azure_adapter.py +68 -0
  3. flock/adapter/chroma_adapter.py +73 -0
  4. flock/adapter/faiss_adapter.py +97 -0
  5. flock/adapter/pinecone_adapter.py +51 -0
  6. flock/adapter/vector_base.py +47 -0
  7. flock/config.py +1 -1
  8. flock/core/context/context.py +20 -0
  9. flock/core/flock.py +71 -91
  10. flock/core/flock_agent.py +58 -3
  11. flock/core/flock_module.py +5 -0
  12. flock/di.py +41 -0
  13. flock/modules/enterprise_memory/README.md +99 -0
  14. flock/modules/enterprise_memory/enterprise_memory_module.py +526 -0
  15. flock/modules/mem0/mem0_module.py +79 -16
  16. flock/modules/mem0_async/async_mem0_module.py +126 -0
  17. flock/modules/memory/memory_module.py +28 -8
  18. flock/modules/performance/metrics_module.py +24 -1
  19. flock/modules/zep/__init__.py +1 -0
  20. flock/modules/zep/zep_module.py +192 -0
  21. flock/webapp/app/api/execution.py +79 -2
  22. flock/webapp/app/chat.py +83 -3
  23. flock/webapp/app/services/sharing_models.py +38 -0
  24. flock/webapp/app/services/sharing_store.py +60 -1
  25. flock/webapp/static/css/chat.css +2 -0
  26. flock/webapp/templates/partials/_chat_messages.html +50 -4
  27. flock/webapp/templates/partials/_results_display.html +39 -0
  28. {flock_core-0.4.0b50.dist-info → flock_core-0.4.1.dist-info}/METADATA +6 -4
  29. {flock_core-0.4.0b50.dist-info → flock_core-0.4.1.dist-info}/RECORD +33 -22
  30. flock/modules/mem0graph/mem0_graph_module.py +0 -63
  31. /flock/modules/{mem0graph → mem0_async}/__init__.py +0 -0
  32. {flock_core-0.4.0b50.dist-info → flock_core-0.4.1.dist-info}/WHEEL +0 -0
  33. {flock_core-0.4.0b50.dist-info → flock_core-0.4.1.dist-info}/entry_points.txt +0 -0
  34. {flock_core-0.4.0b50.dist-info → flock_core-0.4.1.dist-info}/licenses/LICENSE +0 -0
@@ -1,5 +1,8 @@
1
1
  from typing import Any
2
2
 
3
+ # from mem0.client.main import MemoryClient
4
+ # from mem0.memory.main import Memory
5
+ from mem0 import Memory, MemoryClient
3
6
  from pydantic import Field
4
7
 
5
8
  from flock.core.context.context import FlockContext
@@ -11,35 +14,46 @@ from flock.core.logging.logging import get_logger
11
14
  logger = get_logger("module.mem0")
12
15
 
13
16
 
14
- class Mem0ModuleConfig(FlockModuleConfig):
15
- qdrant_host: str = Field(default="localhost", description="Qdrant hostname")
16
- qdrant_port: int = Field(default=6333, description="Qdrant port")
17
- collection: str = Field(default="flock_memories", description="Vector collection")
18
- embedder_provider: str = Field(default="openai", description="'openai' or 'local'")
19
- embedder_model: str = Field(default="text-embedding-ada-002",
20
- description="Model name for embeddings")
21
- # Optional: allow separate LLM for reflection/summarisation
22
- llm_provider: str | None = Field(default=None)
23
- llm_model: str | None = Field(default=None)
24
- top_k: int = Field(default=5, description="Number of memories to retrieve")
17
+ config = {
18
+ "vector_store": {
19
+ "provider": "chroma",
20
+ "config": {
21
+ "collection_name": "flock_memory",
22
+ "path": ".flock/memory",
23
+ }
24
+ }
25
+ }
26
+
25
27
 
28
+ class Mem0ModuleConfig(FlockModuleConfig):
29
+ top_k: int = Field(default=10, description="Number of memories to retrieve")
30
+ user_id: str = Field(default="flock", description="User ID the memories will be associated with")
31
+ agent_id: str = Field(default="flock", description="Agent ID the memories will be associated with")
32
+ memory_input_key: str | None = Field(default=None, description="Input key to use for memory, if none the description of the agent will be used")
33
+ api_key: str | None = Field(default=None, description="API key for mem0 Platform")
34
+ config: dict[str, Any] = Field(default=config, description="Configuration for mem0")
26
35
 
27
36
 
28
37
  @flock_component(config_class=Mem0ModuleConfig)
29
38
  class Mem0Module(FlockModule):
30
- """Module that adds Zep capabilities to a Flock agent."""
31
39
 
32
40
  name: str = "mem0"
33
41
  config: Mem0ModuleConfig = Mem0ModuleConfig()
34
- session_id: str | None = None
35
- user_id: str | None = None
42
+
36
43
 
37
44
  def __init__(self, name, config: Mem0ModuleConfig) -> None:
45
+ global memory
38
46
  """Initialize Mem0 module."""
39
47
  super().__init__(name=name, config=config)
40
48
  logger.debug("Initializing Mem0 module")
41
49
 
42
50
 
51
+
52
+
53
+ def dict_to_str_repr(self,d: dict) -> str:
54
+ return repr(d)
55
+
56
+
43
57
  async def on_post_evaluate(
44
58
  self,
45
59
  agent: FlockAgent,
@@ -47,6 +61,26 @@ class Mem0Module(FlockModule):
47
61
  context: FlockContext | None = None,
48
62
  result: dict[str, Any] | None = None,
49
63
  ) -> dict[str, Any]:
64
+ if self.config.api_key:
65
+ memory = MemoryClient(api_key=self.config.api_key)
66
+ else:
67
+ memory = Memory.from_config(config_dict=self.config.config)
68
+
69
+ agent_id = self.config.agent_id if self.config.agent_id else agent.name
70
+
71
+ # get the result without the inputs
72
+ filtered_result = {k: v for k, v in result.items() if k not in inputs}
73
+ # get the inputs without memory
74
+ filtered_inputs = {k: v for k, v in inputs.items() if k not in [self.config.memory_input_key]}
75
+
76
+ # add memories about the user inputs
77
+ added_user_memory = memory.add(self.dict_to_str_repr(filtered_inputs), user_id=self.config.user_id)
78
+ logger.info(f"Added caller memory: {added_user_memory}")
79
+
80
+ # add memories about the agent result
81
+ added_agent_memory = memory.add(self.dict_to_str_repr(filtered_result), agent_id=agent_id)
82
+ logger.info(f"Added agent memory: {added_agent_memory}")
83
+
50
84
 
51
85
  return result
52
86
 
@@ -56,8 +90,37 @@ class Mem0Module(FlockModule):
56
90
  inputs: dict[str, Any],
57
91
  context: FlockContext | None = None,
58
92
  ) -> dict[str, Any]:
59
-
60
-
93
+ if self.config.api_key:
94
+ memory = MemoryClient(api_key=self.config.api_key)
95
+ else:
96
+ memory = Memory.from_config(config_dict=self.config.config)
97
+
98
+ message = self.dict_to_str_repr(inputs)
99
+ agent_id = self.config.agent_id if self.config.agent_id else agent.name
100
+
101
+ relevant_agent_memories = memory.search(query=message, agent_id=agent_id, limit=self.config.top_k)
102
+ logger.info(f"Relevant agent memories: {relevant_agent_memories}")
103
+
104
+ relevant_user_memories = memory.search(query=message, user_id=self.config.user_id, limit=self.config.top_k)
105
+ logger.info(f"Relevant user memories: {relevant_user_memories}")
106
+
107
+ if relevant_agent_memories or relevant_user_memories:
108
+ memories_str = ''
109
+ if "results" in relevant_agent_memories:
110
+ memories_str = "\n".join(f"- {entry['memory']}" for entry in relevant_agent_memories["results"])
111
+ else:
112
+ memories_str = "\n".join(f"- {entry}" for entry in relevant_agent_memories)
113
+
114
+ if "results" in relevant_user_memories:
115
+ memories_str = memories_str + "\n" + "\n".join(f"- {entry['memory']}" for entry in relevant_user_memories["results"])
116
+ else:
117
+ memories_str = memories_str + "\n" + "\n".join(f"- {entry}" for entry in relevant_user_memories)
118
+
119
+ if memories_str:
120
+ if self.config.memory_input_key:
121
+ inputs[self.config.memory_input_key] = memories_str
122
+ else:
123
+ agent.description = agent.description + "\n\n Memories:" + memories_str
61
124
 
62
125
 
63
126
  return inputs
@@ -0,0 +1,126 @@
1
+ from typing import Any
2
+
3
+ # from mem0.client.main import AsyncMemoryClient, MemoryClient
4
+ # from mem0.memory.main import AsyncMemory
5
+ from mem0 import AsyncMemory, AsyncMemoryClient
6
+ from pydantic import Field
7
+
8
+ from flock.core.context.context import FlockContext
9
+ from flock.core.flock_agent import FlockAgent
10
+ from flock.core.flock_module import FlockModule, FlockModuleConfig
11
+ from flock.core.flock_registry import flock_component
12
+ from flock.core.logging.logging import get_logger
13
+
14
+ logger = get_logger("module.mem0")
15
+
16
+
17
+ config = {
18
+ "vector_store": {
19
+ "provider": "chroma",
20
+ "config": {
21
+ "collection_name": "flock_memory",
22
+ "path": ".flock/memory",
23
+ }
24
+ }
25
+ }
26
+
27
+
28
+ class AsyncMem0ModuleConfig(FlockModuleConfig):
29
+ top_k: int = Field(default=10, description="Number of memories to retrieve")
30
+ user_id: str = Field(default="flock", description="User ID the memories will be associated with")
31
+ agent_id: str = Field(default="flock", description="Agent ID the memories will be associated with")
32
+ memory_input_key: str | None = Field(default=None, description="Input key to use for memory, if none the description of the agent will be used")
33
+ api_key: str | None = Field(default=None, description="API key for mem0 Platform")
34
+ config: dict[str, Any] = Field(default=config, description="Configuration for mem0")
35
+
36
+
37
+ @flock_component(config_class=AsyncMem0ModuleConfig)
38
+ class AsyncMem0Module(FlockModule):
39
+
40
+ name: str = "mem0"
41
+ config: AsyncMem0ModuleConfig = AsyncMem0ModuleConfig()
42
+
43
+
44
+ def __init__(self, name, config: AsyncMem0ModuleConfig) -> None:
45
+ global memory
46
+ """Initialize Mem0 module."""
47
+ super().__init__(name=name, config=config)
48
+ logger.debug("Initializing Mem0 module")
49
+
50
+
51
+
52
+
53
+ def dict_to_str_repr(self,d: dict) -> str:
54
+ return repr(d)
55
+
56
+
57
+ async def on_post_evaluate(
58
+ self,
59
+ agent: FlockAgent,
60
+ inputs: dict[str, Any],
61
+ context: FlockContext | None = None,
62
+ result: dict[str, Any] | None = None,
63
+ ) -> dict[str, Any]:
64
+ if self.config.api_key:
65
+ memory = AsyncMemoryClient(api_key=self.config.api_key)
66
+ else:
67
+ memory = await AsyncMemory.from_config(config_dict=self.config.config)
68
+
69
+ agent_id = self.config.agent_id if self.config.agent_id else agent.name
70
+
71
+ # get the result without the inputs
72
+ filtered_result = {k: v for k, v in result.items() if k not in inputs}
73
+ # get the inputs without memory
74
+ filtered_inputs = {k: v for k, v in inputs.items() if k not in [self.config.memory_input_key]}
75
+
76
+ # add memories about the user inputs
77
+ added_user_memory = await memory.add(self.dict_to_str_repr(filtered_inputs), user_id=self.config.user_id)
78
+ logger.info(f"Added caller memory: {added_user_memory}")
79
+
80
+ # add memories about the agent result
81
+ added_agent_memory = await memory.add(self.dict_to_str_repr(filtered_result), agent_id=agent_id)
82
+ logger.info(f"Added agent memory: {added_agent_memory}")
83
+
84
+
85
+ return result
86
+
87
+ async def on_pre_evaluate(
88
+ self,
89
+ agent: FlockAgent,
90
+ inputs: dict[str, Any],
91
+ context: FlockContext | None = None,
92
+ ) -> dict[str, Any]:
93
+ if self.config.api_key:
94
+ memory = AsyncMemoryClient(api_key=self.config.api_key)
95
+ else:
96
+ memory = await AsyncMemory.from_config(config_dict=self.config.config)
97
+
98
+ message = self.dict_to_str_repr(inputs)
99
+ agent_id = self.config.agent_id if self.config.agent_id else agent.name
100
+
101
+ relevant_agent_memories = await memory.search(query=message, agent_id=agent_id, limit=self.config.top_k)
102
+ logger.info(f"Relevant agent memories: {relevant_agent_memories}")
103
+
104
+ relevant_user_memories = await memory.search(query=message, user_id=self.config.user_id, limit=self.config.top_k)
105
+ logger.info(f"Relevant user memories: {relevant_user_memories}")
106
+
107
+ if relevant_agent_memories or relevant_user_memories:
108
+ memories_str = ''
109
+ if "results" in relevant_agent_memories:
110
+ memories_str = "\n".join(f"- {entry['memory']}" for entry in relevant_agent_memories["results"])
111
+ else:
112
+ memories_str = "\n".join(f"- {entry}" for entry in relevant_agent_memories)
113
+
114
+ if "results" in relevant_user_memories:
115
+ memories_str = memories_str + "\n" + "\n".join(f"- {entry['memory']}" for entry in relevant_user_memories["results"])
116
+ else:
117
+ memories_str = memories_str + "\n" + "\n".join(f"- {entry}" for entry in relevant_user_memories)
118
+
119
+ if memories_str:
120
+ if self.config.memory_input_key:
121
+ inputs[self.config.memory_input_key] = memories_str
122
+ else:
123
+ agent.description = agent.description + "\n\n Memories:" + memories_str
124
+
125
+
126
+ return inputs
@@ -4,7 +4,6 @@ from datetime import datetime
4
4
  from typing import Any, Literal
5
5
 
6
6
  from pydantic import Field
7
- from tqdm import tqdm
8
7
 
9
8
  from flock.core.context.context import FlockContext
10
9
 
@@ -27,7 +26,7 @@ class MemoryModuleConfig(FlockModuleConfig):
27
26
  """
28
27
 
29
28
  folder_path: str = Field(
30
- default="concept_memory/",
29
+ default=".flock/memory/",
31
30
  description="Directory where memory file and concept graph will be saved",
32
31
  )
33
32
  concept_graph_file: str = Field(
@@ -50,14 +49,19 @@ class MemoryModuleConfig(FlockModuleConfig):
50
49
  default=True, description="Whether to save memory after each update"
51
50
  )
52
51
  splitting_mode: Literal["summary", "semantic", "characters", "none"] = (
53
- Field(default="none", description="Mode to split memory content")
52
+ Field(default="characters", description="Mode to split memory content")
54
53
  )
55
54
  enable_read_only_mode: bool = Field(
56
55
  default=False, description="Whether to enable read only mode"
57
56
  )
57
+ enable_write_only_mode: bool = Field(
58
+ default=False, description="Whether to enable write only mode"
59
+ )
58
60
  number_of_concepts_to_extract: int = Field(
59
61
  default=3, description="Number of concepts to extract from the memory"
60
62
  )
63
+ memory_input_key: str | None = Field(default=None, description="Input key to use for memory, if none the description of the agent will be used")
64
+
61
65
 
62
66
 
63
67
  @flock_component(config_class=MemoryModuleConfig)
@@ -111,6 +115,9 @@ class MemoryModule(FlockModule):
111
115
  if not self.memory_store:
112
116
  return inputs
113
117
 
118
+ if self.config.enable_write_only_mode:
119
+ return inputs
120
+
114
121
  inputs = await self.search_memory(agent, inputs)
115
122
 
116
123
  if "context" in inputs:
@@ -169,10 +176,11 @@ class MemoryModule(FlockModule):
169
176
 
170
177
  async def search_memory(
171
178
  self, agent: FlockAgent, query: dict[str, Any]
172
- ) -> list[str]:
179
+ ) -> dict[str, Any]:
173
180
  """Search memory for the query."""
174
181
  if not self.memory_store:
175
- return []
182
+ # No memory store loaded – just return the untouched input
183
+ return query
176
184
 
177
185
  try:
178
186
  input_text = json.dumps(query)
@@ -237,6 +245,9 @@ class MemoryModule(FlockModule):
237
245
  if not self.memory_store:
238
246
  return result
239
247
 
248
+ if self.config.enable_read_only_mode:
249
+ return result
250
+
240
251
  try:
241
252
  chunks = await self._get_chunks(agent, inputs, result)
242
253
  await self._store_chunks(agent, chunks)
@@ -313,7 +324,7 @@ class MemoryModule(FlockModule):
313
324
  agent: FlockAgent,
314
325
  inputs: dict[str, Any],
315
326
  result: dict[str, Any],
316
- ) -> str | list[dict[str, str]]:
327
+ ) -> list[str]:
317
328
  """Extract information chunks using semantic mode."""
318
329
  split_signature = agent.create_dspy_signature_class(
319
330
  f"{self.name}_splitter",
@@ -327,7 +338,15 @@ class MemoryModule(FlockModule):
327
338
  splitter = agent._select_task(split_signature, "Completion")
328
339
  full_text = json.dumps(inputs) + (json.dumps(result) if result else "")
329
340
  split_result = splitter(content=full_text)
330
- return split_result.chunks
341
+ # Flatten list[dict] into list[str] of "title: content" strings to
342
+ # keep downstream storage logic simple and type-safe.
343
+ flattened: list[str] = []
344
+ for chunk in split_result.chunks:
345
+ if isinstance(chunk, dict):
346
+ flattened.extend([f"{k}: {v}" for k, v in chunk.items()])
347
+ else:
348
+ flattened.append(str(chunk))
349
+ return flattened
331
350
 
332
351
  async def _character_splitter_mode(
333
352
  self,
@@ -394,7 +413,8 @@ class MemoryModule(FlockModule):
394
413
  if isinstance(chunks, str):
395
414
  await self._store_chunk(agent, chunks)
396
415
  elif isinstance(chunks, list):
397
- for chunk in tqdm(chunks, desc="Storing chunks in memory"):
416
+ # Avoid tqdm in async context simple for-loop is safer.
417
+ for chunk in chunks:
398
418
  await self._store_chunk(agent, chunk)
399
419
 
400
420
  def save_memory(self) -> None:
@@ -48,7 +48,7 @@ class MetricsModuleConfig(FlockModuleConfig):
48
48
  default="json", description="Where to store metrics"
49
49
  )
50
50
  metrics_dir: str = Field(
51
- default="metrics/", description="Directory for metrics storage"
51
+ default=".flock/metrics/", description="Directory for metrics storage"
52
52
  )
53
53
 
54
54
  # Aggregation settings
@@ -77,6 +77,9 @@ class MetricsModuleConfig(FlockModuleConfig):
77
77
  class MetricsModule(FlockModule):
78
78
  """Module for collecting and analyzing agent performance metrics."""
79
79
 
80
+ # --- Singleton holder for convenient static access ---
81
+ _INSTANCE: "MetricsModule | None" = None
82
+
80
83
  name: str = "performance_metrics"
81
84
  config: MetricsModuleConfig = Field(
82
85
  default_factory=MetricsModuleConfig,
@@ -85,6 +88,8 @@ class MetricsModule(FlockModule):
85
88
 
86
89
  def __init__(self, name, config):
87
90
  super().__init__(name=name, config=config)
91
+ # Register singleton for static helpers
92
+ MetricsModule._INSTANCE = self
88
93
  self._metrics = defaultdict(list)
89
94
  self._start_time: float | None = None
90
95
  self._start_memory: int | None = None
@@ -495,3 +500,21 @@ class MetricsModule(FlockModule):
495
500
  1,
496
501
  {"agent": agent.name, "error_type": type(error).__name__},
497
502
  )
503
+
504
+ # --------------------------------------------------
505
+ # Public helper for external modules
506
+ # --------------------------------------------------
507
+ @classmethod
508
+ def record(cls, name: str, value: int | float | str, tags: dict[str, str] | None = None):
509
+ """Record a metric from anywhere in the codebase.
510
+
511
+ Example:
512
+ MetricsModule.record("custom_latency", 123, {"stage": "inference"})
513
+ The call will forward to the *first* instantiated MetricsModule. If no
514
+ instance exists in the current run the call is a no-op so that importing
515
+ this helper never crashes test-code.
516
+ """
517
+ instance = cls._INSTANCE
518
+ if instance is None:
519
+ return # silently ignore if module isn't active
520
+ instance._record_metric(name, value, tags or {})
@@ -0,0 +1 @@
1
+ # Package for modules
@@ -0,0 +1,192 @@
1
+ import uuid
2
+ from typing import Any
3
+
4
+ from pydantic import Field
5
+ from zep_python.client import Zep
6
+ from zep_python.types import Message as ZepMessage, SessionSearchResult
7
+
8
+ from flock.core.context.context import FlockContext
9
+ from flock.core.flock_agent import FlockAgent
10
+ from flock.core.flock_module import FlockModule, FlockModuleConfig
11
+ from flock.core.flock_registry import flock_component
12
+ from flock.core.logging.logging import get_logger
13
+
14
+ logger = get_logger("module.zep")
15
+
16
+
17
+ class ZepModuleConfig(FlockModuleConfig):
18
+ """Configuration for the Zep module."""
19
+
20
+ zep_url: str = "http://localhost:8000"
21
+ zep_api_key: str = "apikey"
22
+ min_fact_rating: float = Field(
23
+ default=0.7, description="Minimum rating for facts to be considered"
24
+ )
25
+ enable_read: bool = True
26
+ enable_write: bool = False
27
+ top_k: int = Field(default=10, description="Number of memories to retrieve")
28
+ user_id: str = Field(default="flock", description="User ID the memories will be associated with")
29
+ agent_id: str = Field(default="flock", description="Agent ID the memories will be associated with")
30
+ memory_input_key: str | None = Field(default=None, description="Input key to use for memory, if none the description of the agent will be used")
31
+
32
+
33
+
34
+ @flock_component(config_class=ZepModuleConfig)
35
+ class ZepModule(FlockModule):
36
+ """Module that adds Zep capabilities to a Flock agent."""
37
+
38
+ name: str = "zep"
39
+ config: ZepModuleConfig = ZepModuleConfig()
40
+ session_id: str | None = None
41
+ user_id: str | None = None
42
+
43
+ def __init__(self, name, config: ZepModuleConfig) -> None:
44
+ """Initialize Zep module."""
45
+ super().__init__(name=name, config=config)
46
+ logger.debug("Initializing Zep module")
47
+ zep_client = Zep(
48
+ base_url=self.config.zep_url, api_key=self.config.zep_api_key
49
+ )
50
+ self.user_id = self.name
51
+ self._setup_user(zep_client)
52
+ self.session_id = str(uuid.uuid4())
53
+ self._setup_session(zep_client)
54
+
55
+ def _setup_user(self, zep_client: Zep) -> None:
56
+ """Set up user in Zep."""
57
+ if not zep_client or not self.user_id:
58
+ raise ValueError("Zep service or user_id not initialized")
59
+
60
+ try:
61
+ user = zep_client.user.get(user_id=self.user_id)
62
+ if not user:
63
+ zep_client.user.add(user_id=self.user_id)
64
+ except Exception:
65
+ zep_client.user.add(user_id=self.user_id)
66
+
67
+ def _setup_session(self, zep_client: Zep) -> None:
68
+ """Set up new session."""
69
+ if not zep_client or not self.user_id or not self.session_id:
70
+ raise ValueError(
71
+ "Zep service, user_id, or session_id not initialized"
72
+ )
73
+
74
+ zep_client.memory.add_session(
75
+ user_id=self.user_id,
76
+ session_id=self.session_id,
77
+ )
78
+
79
+ def get_client(self) -> Zep:
80
+ """Get Zep client."""
81
+ return Zep(
82
+ base_url=self.config.zep_url, api_key=self.config.zep_api_key
83
+ )
84
+
85
+ def get_memory(self, zep_client: Zep) -> str | None:
86
+ """Get memory for the current session."""
87
+ if not zep_client or not self.session_id:
88
+ logger.error("Zep service or session_id not initialized")
89
+ return None
90
+
91
+ try:
92
+ memory = zep_client.memory.get(
93
+ self.session_id, min_rating=self.config.min_fact_rating
94
+ )
95
+ if memory:
96
+ return f"{memory.relevant_facts}"
97
+ except Exception as e:
98
+ logger.error(f"Error fetching memory: {e}")
99
+ return None
100
+
101
+ return None
102
+
103
+ def split_text(
104
+ self, text: str | None, max_length: int = 1000
105
+ ) -> list[ZepMessage]:
106
+ """Split text into smaller chunks."""
107
+ result: list[ZepMessage] = []
108
+ if not text:
109
+ return result
110
+ if len(text) <= max_length:
111
+ return [ZepMessage(role="user", content=text, role_type="user")]
112
+ for i in range(0, len(text), max_length):
113
+ result.append(
114
+ ZepMessage(
115
+ role="user",
116
+ content=text[i : i + max_length],
117
+ role_type="user",
118
+ )
119
+ )
120
+ return result
121
+
122
+ def add_to_memory(self, text: str, zep_client: Zep) -> None:
123
+ """Add text to memory."""
124
+ if not zep_client or not self.session_id:
125
+ logger.error("Zep service or session_id not initialized")
126
+ return
127
+
128
+ messages = self.split_text(text)
129
+ zep_client.memory.add(session_id=self.session_id, messages=messages)
130
+
131
+ def search_memory(
132
+ self, query: str, zep_client: Zep
133
+ ) -> list[SessionSearchResult]:
134
+ """Search memory for a query."""
135
+ if not zep_client or not self.user_id:
136
+ logger.error("Zep service or user_id not initialized")
137
+ return []
138
+
139
+ response = zep_client.memory.search_sessions(
140
+ text=query,
141
+ user_id=self.user_id,
142
+ search_scope="facts",
143
+ min_fact_rating=self.config.min_fact_rating,
144
+ )
145
+ if not response.results:
146
+ return []
147
+ return response.results
148
+
149
+ async def on_post_evaluate(
150
+ self,
151
+ agent: FlockAgent,
152
+ inputs: dict[str, Any],
153
+ context: FlockContext | None = None,
154
+ result: dict[str, Any] | None = None,
155
+ ) -> dict[str, Any]:
156
+ """Format and display the output."""
157
+ if not self.config.enable_write:
158
+ return result
159
+ logger.debug("Saving data to memory")
160
+ zep_client = Zep(
161
+ base_url=self.config.zep_url, api_key=self.config.zep_api_key
162
+ )
163
+ self.add_to_memory(str(result), zep_client)
164
+ return result
165
+
166
+ async def on_pre_evaluate(
167
+ self,
168
+ agent: FlockAgent,
169
+ inputs: dict[str, Any],
170
+ context: FlockContext | None = None,
171
+ ) -> dict[str, Any]:
172
+ """Format and display the output."""
173
+ if not self.config.enable_read:
174
+ return inputs
175
+
176
+ zep_client = Zep(
177
+ base_url=self.config.zep_url, api_key=self.config.zep_api_key
178
+ )
179
+
180
+ logger.debug("Searching memory")
181
+ facts = self.search_memory(str(inputs), zep_client)
182
+
183
+ # Add memory to inputs
184
+ facts_str = ""
185
+ if facts:
186
+ for fact in facts:
187
+ facts_str += fact.fact.fact + "\n"
188
+ logger.debug("Found facts in memory: {}", facts_str)
189
+ agent.input = agent.input + ", memory"
190
+ inputs["memory"] = facts_str
191
+
192
+ return inputs