flock-core 0.4.0b49__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.
- flock/adapter/__init__.py +14 -0
- flock/adapter/azure_adapter.py +68 -0
- flock/adapter/chroma_adapter.py +73 -0
- flock/adapter/faiss_adapter.py +97 -0
- flock/adapter/pinecone_adapter.py +51 -0
- flock/adapter/vector_base.py +47 -0
- flock/config.py +1 -1
- flock/core/context/context.py +20 -0
- flock/core/flock.py +71 -91
- flock/core/flock_agent.py +58 -3
- flock/core/flock_module.py +5 -0
- flock/di.py +41 -0
- flock/modules/enterprise_memory/README.md +99 -0
- flock/modules/enterprise_memory/enterprise_memory_module.py +526 -0
- flock/modules/mem0/mem0_module.py +79 -16
- flock/modules/mem0_async/async_mem0_module.py +126 -0
- flock/modules/memory/memory_module.py +28 -8
- flock/modules/performance/metrics_module.py +24 -1
- flock/modules/zep/__init__.py +1 -0
- flock/modules/zep/zep_module.py +192 -0
- flock/webapp/app/api/execution.py +108 -73
- flock/webapp/app/chat.py +96 -12
- flock/webapp/app/config.py +1 -1
- flock/webapp/app/main.py +14 -12
- flock/webapp/app/services/sharing_models.py +38 -0
- flock/webapp/app/services/sharing_store.py +60 -1
- flock/webapp/static/css/chat.css +2 -0
- flock/webapp/templates/base.html +91 -1
- flock/webapp/templates/chat.html +64 -1
- flock/webapp/templates/partials/_agent_detail_form.html +3 -3
- flock/webapp/templates/partials/_chat_messages.html +50 -4
- flock/webapp/templates/partials/_flock_properties_form.html +2 -2
- flock/webapp/templates/partials/_results_display.html +54 -11
- flock/webapp/templates/partials/_structured_data_view.html +2 -2
- flock/webapp/templates/shared_run_page.html +27 -0
- {flock_core-0.4.0b49.dist-info → flock_core-0.4.1.dist-info}/METADATA +6 -4
- {flock_core-0.4.0b49.dist-info → flock_core-0.4.1.dist-info}/RECORD +41 -30
- flock/modules/mem0graph/mem0_graph_module.py +0 -63
- /flock/modules/{mem0graph → mem0_async}/__init__.py +0 -0
- {flock_core-0.4.0b49.dist-info → flock_core-0.4.1.dist-info}/WHEEL +0 -0
- {flock_core-0.4.0b49.dist-info → flock_core-0.4.1.dist-info}/entry_points.txt +0 -0
- {flock_core-0.4.0b49.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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
|
|
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="
|
|
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="
|
|
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
|
-
) ->
|
|
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
|
-
) ->
|
|
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
|
-
|
|
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
|
-
|
|
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
|