flowcept 0.9.18__py3-none-any.whl → 0.9.19__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.
- flowcept/agents/agent_client.py +10 -4
- flowcept/agents/agents_utils.py +12 -19
- flowcept/agents/flowcept_agent.py +116 -15
- flowcept/agents/flowcept_ctx_manager.py +31 -24
- flowcept/commons/daos/keyvalue_dao.py +12 -3
- flowcept/commons/daos/mq_dao/mq_dao_base.py +37 -20
- flowcept/configs.py +13 -2
- flowcept/flowceptor/consumers/agent/base_agent_context_manager.py +2 -2
- flowcept/flowceptor/consumers/base_consumer.py +22 -4
- flowcept/flowceptor/consumers/document_inserter.py +22 -1
- flowcept/version.py +1 -1
- {flowcept-0.9.18.dist-info → flowcept-0.9.19.dist-info}/METADATA +1 -1
- {flowcept-0.9.18.dist-info → flowcept-0.9.19.dist-info}/RECORD +17 -17
- resources/sample_settings.yaml +1 -1
- {flowcept-0.9.18.dist-info → flowcept-0.9.19.dist-info}/WHEEL +0 -0
- {flowcept-0.9.18.dist-info → flowcept-0.9.19.dist-info}/entry_points.txt +0 -0
- {flowcept-0.9.18.dist-info → flowcept-0.9.19.dist-info}/licenses/LICENSE +0 -0
flowcept/agents/agent_client.py
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
+
import json
|
|
3
|
+
import re
|
|
2
4
|
from typing import Dict, List, Callable
|
|
3
5
|
|
|
4
6
|
from flowcept.configs import AGENT_HOST, AGENT_PORT
|
|
@@ -48,10 +50,14 @@ def run_tool(
|
|
|
48
50
|
result: List[TextContent] = await session.call_tool(tool_name, arguments=kwargs)
|
|
49
51
|
actual_result = []
|
|
50
52
|
for r in result.content:
|
|
51
|
-
if isinstance(r, str)
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
actual_result.append(
|
|
53
|
+
text = r if isinstance(r, str) else r.text
|
|
54
|
+
try:
|
|
55
|
+
json.loads(text)
|
|
56
|
+
actual_result.append(text)
|
|
57
|
+
except Exception:
|
|
58
|
+
match = re.search(r"Error code:\\s*(\\d+)", text)
|
|
59
|
+
code = int(match.group(1)) if match else 400
|
|
60
|
+
actual_result.append(json.dumps({"code": code, "result": text, "tool_name": tool_name}))
|
|
55
61
|
|
|
56
62
|
return actual_result
|
|
57
63
|
|
flowcept/agents/agents_utils.py
CHANGED
|
@@ -139,8 +139,8 @@ def build_llm_model(
|
|
|
139
139
|
if _service_provider == "sambanova":
|
|
140
140
|
from langchain_community.llms.sambanova import SambaStudio
|
|
141
141
|
|
|
142
|
-
os.environ["SAMBASTUDIO_URL"] = AGENT.get("llm_server_url")
|
|
143
|
-
os.environ["SAMBASTUDIO_API_KEY"] = AGENT.get("api_key")
|
|
142
|
+
os.environ["SAMBASTUDIO_URL"] = os.environ.get("SAMBASTUDIO_URL", AGENT.get("llm_server_url"))
|
|
143
|
+
os.environ["SAMBASTUDIO_API_KEY"] = os.environ.get("SAMBASTUDIO_API_KEY", AGENT.get("api_key"))
|
|
144
144
|
|
|
145
145
|
llm = SambaStudio(model_kwargs=_model_kwargs)
|
|
146
146
|
elif _service_provider == "azure":
|
|
@@ -155,7 +155,16 @@ def build_llm_model(
|
|
|
155
155
|
from langchain_openai import ChatOpenAI
|
|
156
156
|
|
|
157
157
|
api_key = os.environ.get("OPENAI_API_KEY", AGENT.get("api_key", None))
|
|
158
|
-
|
|
158
|
+
base_url = os.environ.get("OPENAI_BASE_URL", AGENT.get("llm_server_url") or None)
|
|
159
|
+
org = os.environ.get("OPENAI_ORG_ID", AGENT.get("organization", None))
|
|
160
|
+
|
|
161
|
+
init_kwargs = {"api_key": api_key}
|
|
162
|
+
if base_url:
|
|
163
|
+
init_kwargs["base_url"] = base_url
|
|
164
|
+
if org:
|
|
165
|
+
init_kwargs["organization"] = org
|
|
166
|
+
|
|
167
|
+
llm = ChatOpenAI(**init_kwargs, **_model_kwargs)
|
|
159
168
|
elif _service_provider == "google":
|
|
160
169
|
if "claude" in _model_kwargs["model"]:
|
|
161
170
|
api_key = os.environ.get("GOOGLE_API_KEY", AGENT.get("api_key", None))
|
|
@@ -168,22 +177,6 @@ def build_llm_model(
|
|
|
168
177
|
from flowcept.agents.llms.gemini25 import Gemini25LLM
|
|
169
178
|
|
|
170
179
|
llm = Gemini25LLM(**_model_kwargs)
|
|
171
|
-
elif _service_provider == "openai":
|
|
172
|
-
from langchain_openai import ChatOpenAI
|
|
173
|
-
|
|
174
|
-
api_key = os.environ.get("OPENAI_API_KEY", AGENT.get("api_key"))
|
|
175
|
-
base_url = os.environ.get("OPENAI_BASE_URL", AGENT.get("llm_server_url") or None) # optional
|
|
176
|
-
org = os.environ.get("OPENAI_ORG_ID", AGENT.get("organization", None)) # optional
|
|
177
|
-
|
|
178
|
-
init_kwargs = {"api_key": api_key}
|
|
179
|
-
if base_url:
|
|
180
|
-
init_kwargs["base_url"] = base_url
|
|
181
|
-
if org:
|
|
182
|
-
init_kwargs["organization"] = org
|
|
183
|
-
|
|
184
|
-
# IMPORTANT: use the merged kwargs so `model` and temps flow through
|
|
185
|
-
llm = ChatOpenAI(**init_kwargs, **_model_kwargs)
|
|
186
|
-
|
|
187
180
|
else:
|
|
188
181
|
raise Exception("Currently supported providers are sambanova, openai, azure, and google.")
|
|
189
182
|
if track_tools:
|
|
@@ -1,32 +1,133 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
1
3
|
from threading import Thread
|
|
2
|
-
from time import sleep
|
|
3
4
|
|
|
4
5
|
from flowcept.agents import check_liveness
|
|
6
|
+
from flowcept.agents.agents_utils import ToolResult
|
|
7
|
+
from flowcept.agents.tools.general_tools import prompt_handler
|
|
5
8
|
from flowcept.agents.agent_client import run_tool
|
|
6
|
-
from flowcept.agents.flowcept_ctx_manager import mcp_flowcept
|
|
7
|
-
from flowcept.
|
|
8
|
-
from flowcept.
|
|
9
|
+
from flowcept.agents.flowcept_ctx_manager import mcp_flowcept, ctx_manager
|
|
10
|
+
from flowcept.commons.flowcept_logger import FlowceptLogger
|
|
11
|
+
from flowcept.configs import AGENT_HOST, AGENT_PORT, DUMP_BUFFER_PATH, MQ_ENABLED
|
|
12
|
+
from flowcept.flowceptor.consumers.agent.base_agent_context_manager import BaseAgentContextManager
|
|
13
|
+
from uuid import uuid4
|
|
9
14
|
|
|
10
15
|
import uvicorn
|
|
11
16
|
|
|
12
17
|
|
|
13
|
-
|
|
18
|
+
class FlowceptAgent:
|
|
14
19
|
"""
|
|
15
|
-
|
|
20
|
+
Flowcept agent server wrapper with optional offline buffer loading.
|
|
16
21
|
"""
|
|
17
|
-
f = Flowcept(start_persistence=False, save_workflow=False, check_safe_stops=False).start()
|
|
18
|
-
f.logger.info(f"This section's workflow_id={Flowcept.current_workflow_id}")
|
|
19
22
|
|
|
20
|
-
def
|
|
21
|
-
|
|
23
|
+
def __init__(self, buffer_path: str | None = None):
|
|
24
|
+
"""
|
|
25
|
+
Initialize a FlowceptAgent.
|
|
26
|
+
|
|
27
|
+
Parameters
|
|
28
|
+
----------
|
|
29
|
+
buffer_path : str or None
|
|
30
|
+
Optional path to a JSONL buffer file. When MQ is disabled, the agent
|
|
31
|
+
loads this file once at startup.
|
|
32
|
+
"""
|
|
33
|
+
self.buffer_path = buffer_path
|
|
34
|
+
self.logger = FlowceptLogger()
|
|
35
|
+
self._server_thread: Thread | None = None
|
|
36
|
+
self._server = None
|
|
37
|
+
|
|
38
|
+
def _load_buffer_once(self) -> int:
|
|
39
|
+
"""
|
|
40
|
+
Load messages from a JSONL buffer file into the agent context.
|
|
41
|
+
|
|
42
|
+
Returns
|
|
43
|
+
-------
|
|
44
|
+
int
|
|
45
|
+
Number of messages loaded.
|
|
46
|
+
"""
|
|
47
|
+
path = self.buffer_path or DUMP_BUFFER_PATH
|
|
48
|
+
if not os.path.exists(path):
|
|
49
|
+
raise FileNotFoundError(f"Buffer file not found: {path}")
|
|
50
|
+
|
|
51
|
+
count = 0
|
|
52
|
+
self.logger.info(f"Loading agent buffer from {path}")
|
|
53
|
+
if ctx_manager.agent_id is None:
|
|
54
|
+
agent_id = str(uuid4())
|
|
55
|
+
BaseAgentContextManager.agent_id = agent_id
|
|
56
|
+
ctx_manager.agent_id = agent_id
|
|
57
|
+
with open(path, "r") as handle:
|
|
58
|
+
for line in handle:
|
|
59
|
+
line = line.strip()
|
|
60
|
+
if not line:
|
|
61
|
+
continue
|
|
62
|
+
msg_obj = json.loads(line)
|
|
63
|
+
ctx_manager.message_handler(msg_obj)
|
|
64
|
+
count += 1
|
|
65
|
+
self.logger.info(f"Loaded {count} messages from buffer.")
|
|
66
|
+
return count
|
|
67
|
+
|
|
68
|
+
def _run_server(self):
|
|
69
|
+
"""Run the MCP server (blocking call)."""
|
|
70
|
+
config = uvicorn.Config(mcp_flowcept.streamable_http_app, host=AGENT_HOST, port=AGENT_PORT, lifespan="on")
|
|
71
|
+
self._server = uvicorn.Server(config)
|
|
72
|
+
self._server.run()
|
|
73
|
+
|
|
74
|
+
def start(self):
|
|
75
|
+
"""
|
|
76
|
+
Start the agent server in a background thread.
|
|
77
|
+
|
|
78
|
+
Returns
|
|
79
|
+
-------
|
|
80
|
+
FlowceptAgent
|
|
81
|
+
The current instance.
|
|
82
|
+
"""
|
|
83
|
+
if not MQ_ENABLED:
|
|
84
|
+
self._load_buffer_once()
|
|
85
|
+
|
|
86
|
+
self._server_thread = Thread(target=self._run_server, daemon=False)
|
|
87
|
+
self._server_thread.start()
|
|
88
|
+
self.logger.info(f"Flowcept agent server started on {AGENT_HOST}:{AGENT_PORT}")
|
|
89
|
+
return self
|
|
22
90
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
91
|
+
def stop(self):
|
|
92
|
+
"""Stop the agent server and wait briefly for shutdown."""
|
|
93
|
+
if self._server is not None:
|
|
94
|
+
self._server.should_exit = True
|
|
95
|
+
if self._server_thread is not None:
|
|
96
|
+
self._server_thread.join(timeout=5)
|
|
97
|
+
|
|
98
|
+
def wait(self):
|
|
99
|
+
"""Block until the server thread exits."""
|
|
100
|
+
if self._server_thread is not None:
|
|
101
|
+
self._server_thread.join()
|
|
102
|
+
|
|
103
|
+
def query(self, message: str) -> ToolResult:
|
|
104
|
+
"""
|
|
105
|
+
Send a prompt to the agent's main router tool and return the response.
|
|
106
|
+
"""
|
|
107
|
+
try:
|
|
108
|
+
resp = run_tool(tool_name=prompt_handler, kwargs={"message": message})[0]
|
|
109
|
+
except Exception as e:
|
|
110
|
+
return ToolResult(code=400, result=f"Error executing tool prompt_handler: {e}", tool_name="prompt_handler")
|
|
111
|
+
|
|
112
|
+
try:
|
|
113
|
+
return ToolResult(**json.loads(resp))
|
|
114
|
+
except Exception as e:
|
|
115
|
+
return ToolResult(
|
|
116
|
+
code=499,
|
|
117
|
+
result=f"Could not parse tool response as JSON: {resp}",
|
|
118
|
+
extra=str(e),
|
|
119
|
+
tool_name="prompt_handler",
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def main():
|
|
124
|
+
"""
|
|
125
|
+
Start the MCP server.
|
|
126
|
+
"""
|
|
127
|
+
agent = FlowceptAgent().start()
|
|
26
128
|
# Wake up tool call
|
|
27
129
|
print(run_tool(check_liveness, host=AGENT_HOST, port=AGENT_PORT)[0])
|
|
28
|
-
|
|
29
|
-
server_thread.join()
|
|
130
|
+
agent.wait()
|
|
30
131
|
|
|
31
132
|
|
|
32
133
|
if __name__ == "__main__":
|
|
@@ -103,7 +103,7 @@ class FlowceptAgentContextManager(BaseAgentContextManager):
|
|
|
103
103
|
self.schema_tracker = DynamicSchemaTracker(**self.tracker_config)
|
|
104
104
|
self.msgs_counter = 0
|
|
105
105
|
self.context_chunk_size = 1 # Should be in the settings
|
|
106
|
-
super().__init__()
|
|
106
|
+
super().__init__(allow_mq_disabled=True)
|
|
107
107
|
|
|
108
108
|
def message_handler(self, msg_obj: Dict):
|
|
109
109
|
"""
|
|
@@ -133,12 +133,15 @@ class FlowceptAgentContextManager(BaseAgentContextManager):
|
|
|
133
133
|
if task_msg.activity_id == "reset_user_context":
|
|
134
134
|
self.context.reset_context()
|
|
135
135
|
self.msgs_counter = 0
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
136
|
+
if self._mq_dao is None:
|
|
137
|
+
self.logger.warning("MQ is disabled; skipping reset_user_context response message.")
|
|
138
|
+
else:
|
|
139
|
+
FlowceptTask(
|
|
140
|
+
agent_id=self.agent_id,
|
|
141
|
+
generated={"msg": "Provenance Agent reset context."},
|
|
142
|
+
subtype="agent_task",
|
|
143
|
+
activity_id="reset_user_context",
|
|
144
|
+
).send()
|
|
142
145
|
return True
|
|
143
146
|
elif task_msg.activity_id == "provenance_query":
|
|
144
147
|
self.logger.info("Received a prov query message!")
|
|
@@ -161,14 +164,17 @@ class FlowceptAgentContextManager(BaseAgentContextManager):
|
|
|
161
164
|
status = Status.ERROR
|
|
162
165
|
error = f"Could not convert the following into a ToolResult:\n{resp}\nException: {e}"
|
|
163
166
|
generated = {"text": str(resp)}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
167
|
+
if self._mq_dao is None:
|
|
168
|
+
self.logger.warning("MQ is disabled; skipping provenance_query response message.")
|
|
169
|
+
else:
|
|
170
|
+
FlowceptTask(
|
|
171
|
+
agent_id=self.agent_id,
|
|
172
|
+
generated=generated,
|
|
173
|
+
stderr=error,
|
|
174
|
+
status=status,
|
|
175
|
+
subtype="agent_task",
|
|
176
|
+
activity_id="provenance_query_response",
|
|
177
|
+
).send()
|
|
172
178
|
|
|
173
179
|
return True
|
|
174
180
|
|
|
@@ -200,12 +206,10 @@ class FlowceptAgentContextManager(BaseAgentContextManager):
|
|
|
200
206
|
]
|
|
201
207
|
)
|
|
202
208
|
except Exception as e:
|
|
203
|
-
self.
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
}"
|
|
208
|
-
)
|
|
209
|
+
task_slice = self.context.task_summaries[
|
|
210
|
+
self.msgs_counter - self.context_chunk_size : self.msgs_counter
|
|
211
|
+
]
|
|
212
|
+
self.logger.error(f"Could not add these tasks to buffer!\n{task_slice}")
|
|
209
213
|
self.logger.exception(e)
|
|
210
214
|
|
|
211
215
|
# self.monitor_chunk()
|
|
@@ -232,9 +236,12 @@ class FlowceptAgentContextManager(BaseAgentContextManager):
|
|
|
232
236
|
if len(result):
|
|
233
237
|
content = result[0].text
|
|
234
238
|
if content != "Error executing tool":
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
239
|
+
if self._mq_dao is None:
|
|
240
|
+
self.logger.warning("MQ is disabled; skipping monitor message.")
|
|
241
|
+
else:
|
|
242
|
+
msg = {"type": "flowcept_agent", "info": "monitor", "content": content}
|
|
243
|
+
self._mq_dao.send_message(msg)
|
|
244
|
+
self.logger.debug(str(content))
|
|
238
245
|
else:
|
|
239
246
|
self.logger.error(content)
|
|
240
247
|
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
"""Key value module."""
|
|
2
2
|
|
|
3
|
-
from flowcept.commons.daos.redis_conn import RedisConn
|
|
4
|
-
|
|
5
3
|
from flowcept.commons.flowcept_logger import FlowceptLogger
|
|
6
4
|
from flowcept.configs import (
|
|
7
5
|
KVDB_HOST,
|
|
@@ -26,12 +24,23 @@ class KeyValueDAO:
|
|
|
26
24
|
|
|
27
25
|
def __init__(self):
|
|
28
26
|
if not hasattr(self, "_initialized"):
|
|
29
|
-
self._initialized = True
|
|
30
27
|
self.logger = FlowceptLogger()
|
|
28
|
+
from flowcept.commons.daos.redis_conn import RedisConn
|
|
29
|
+
|
|
31
30
|
self.redis_conn = RedisConn.build_redis_conn_pool(
|
|
32
31
|
host=KVDB_HOST, port=KVDB_PORT, password=KVDB_PASSWORD, uri=KVDB_URI
|
|
33
32
|
)
|
|
34
33
|
|
|
34
|
+
self._initialized = True
|
|
35
|
+
|
|
36
|
+
@staticmethod
|
|
37
|
+
def get_set_name(set_id: str, exec_bundle_id=None) -> str:
|
|
38
|
+
"""Return a consistent set name for KVDB sets."""
|
|
39
|
+
set_name = set_id
|
|
40
|
+
if exec_bundle_id is not None:
|
|
41
|
+
set_name += "_" + str(exec_bundle_id)
|
|
42
|
+
return set_name
|
|
43
|
+
|
|
35
44
|
def delete_set(self, set_name: str):
|
|
36
45
|
"""Delete it."""
|
|
37
46
|
self.redis_conn.delete(set_name)
|
|
@@ -7,6 +7,7 @@ import msgpack
|
|
|
7
7
|
from time import time
|
|
8
8
|
import flowcept.commons
|
|
9
9
|
from flowcept.commons.autoflush_buffer import AutoflushBuffer
|
|
10
|
+
from flowcept.commons.daos.keyvalue_dao import KeyValueDAO
|
|
10
11
|
from flowcept.commons.utils import chunked
|
|
11
12
|
from flowcept.commons.flowcept_logger import FlowceptLogger
|
|
12
13
|
from flowcept.configs import (
|
|
@@ -29,6 +30,8 @@ class MQDao(object):
|
|
|
29
30
|
|
|
30
31
|
ENCODER = GenericJSONEncoder if JSON_SERIALIZER == "complex" else None
|
|
31
32
|
# TODO we don't have a unit test to cover complex dict!
|
|
33
|
+
MQ_THREAD_SET_ID = "started_mq_thread_execution"
|
|
34
|
+
MQ_FLUSH_COMPLETE_SET_ID = "pending_mq_flush_complete"
|
|
32
35
|
|
|
33
36
|
@staticmethod
|
|
34
37
|
def build(*args, **kwargs) -> "MQDao":
|
|
@@ -51,20 +54,6 @@ class MQDao(object):
|
|
|
51
54
|
else:
|
|
52
55
|
raise NotImplementedError
|
|
53
56
|
|
|
54
|
-
@staticmethod
|
|
55
|
-
def _get_set_name(exec_bundle_id=None):
|
|
56
|
-
"""Get the set name.
|
|
57
|
-
|
|
58
|
-
:param exec_bundle_id: A way to group one or many interceptors, and
|
|
59
|
-
treat each group as a bundle to control when their time_based
|
|
60
|
-
threads started and ended.
|
|
61
|
-
:return:
|
|
62
|
-
"""
|
|
63
|
-
set_id = "started_mq_thread_execution"
|
|
64
|
-
if exec_bundle_id is not None:
|
|
65
|
-
set_id += "_" + str(exec_bundle_id)
|
|
66
|
-
return set_id
|
|
67
|
-
|
|
68
57
|
def __init__(self, adapter_settings=None):
|
|
69
58
|
self.logger = FlowceptLogger()
|
|
70
59
|
self.started = False
|
|
@@ -103,22 +92,36 @@ class MQDao(object):
|
|
|
103
92
|
|
|
104
93
|
def register_time_based_thread_init(self, interceptor_instance_id: str, exec_bundle_id=None):
|
|
105
94
|
"""Register the time."""
|
|
106
|
-
set_name = MQDao.
|
|
95
|
+
set_name = KeyValueDAO.get_set_name(MQDao.MQ_THREAD_SET_ID, exec_bundle_id)
|
|
107
96
|
# self.logger.info(
|
|
108
97
|
# f"Register start of time_based MQ flush thread {set_name}.{interceptor_instance_id}"
|
|
109
98
|
# )
|
|
110
99
|
self._keyvalue_dao.add_key_into_set(set_name, interceptor_instance_id)
|
|
100
|
+
flush_set_name = KeyValueDAO.get_set_name(MQDao.MQ_FLUSH_COMPLETE_SET_ID, exec_bundle_id)
|
|
101
|
+
self._keyvalue_dao.add_key_into_set(flush_set_name, interceptor_instance_id)
|
|
111
102
|
|
|
112
103
|
def register_time_based_thread_end(self, interceptor_instance_id: str, exec_bundle_id=None):
|
|
113
104
|
"""Register time."""
|
|
114
|
-
set_name = MQDao.
|
|
105
|
+
set_name = KeyValueDAO.get_set_name(MQDao.MQ_THREAD_SET_ID, exec_bundle_id)
|
|
115
106
|
self.logger.info(f"Registering end of time_based MQ flush thread {set_name}.{interceptor_instance_id}")
|
|
116
107
|
self._keyvalue_dao.remove_key_from_set(set_name, interceptor_instance_id)
|
|
117
108
|
self.logger.info(f"Done registering time_based MQ flush thread {set_name}.{interceptor_instance_id}")
|
|
118
109
|
|
|
119
110
|
def all_time_based_threads_ended(self, exec_bundle_id=None):
|
|
120
111
|
"""Get all time."""
|
|
121
|
-
set_name = MQDao.
|
|
112
|
+
set_name = KeyValueDAO.get_set_name(MQDao.MQ_THREAD_SET_ID, exec_bundle_id)
|
|
113
|
+
return self._keyvalue_dao.set_is_empty(set_name)
|
|
114
|
+
|
|
115
|
+
def register_flush_complete(self, interceptor_instance_id: str, exec_bundle_id=None):
|
|
116
|
+
"""Register a flush-complete signal for an interceptor."""
|
|
117
|
+
set_name = KeyValueDAO.get_set_name(MQDao.MQ_FLUSH_COMPLETE_SET_ID, exec_bundle_id)
|
|
118
|
+
self.logger.info(f"Registering flush completion {set_name}.{interceptor_instance_id}")
|
|
119
|
+
self._keyvalue_dao.remove_key_from_set(set_name, interceptor_instance_id)
|
|
120
|
+
self.logger.info(f"Done registering flush completion {set_name}.{interceptor_instance_id}")
|
|
121
|
+
|
|
122
|
+
def all_flush_complete_received(self, exec_bundle_id=None):
|
|
123
|
+
"""Return True when all interceptors in the bundle reported flush completion."""
|
|
124
|
+
set_name = KeyValueDAO.get_set_name(MQDao.MQ_FLUSH_COMPLETE_SET_ID, exec_bundle_id)
|
|
122
125
|
return self._keyvalue_dao.set_is_empty(set_name)
|
|
123
126
|
|
|
124
127
|
def set_campaign_id(self, campaign_id=None):
|
|
@@ -172,11 +175,14 @@ class MQDao(object):
|
|
|
172
175
|
if self._time_based_flushing_started:
|
|
173
176
|
self.buffer.stop()
|
|
174
177
|
self._time_based_flushing_started = False
|
|
178
|
+
self.logger.debug("MQ time-based flushed for the last time!")
|
|
175
179
|
else:
|
|
176
180
|
self.logger.error("MQ time-based flushing is not started")
|
|
177
181
|
else:
|
|
178
182
|
self.buffer = list()
|
|
179
183
|
|
|
184
|
+
self.logger.debug("Buffer closed.")
|
|
185
|
+
|
|
180
186
|
def _stop_timed(self, interceptor_instance_id: str, check_safe_stops: bool = True, bundle_exec_id: int = None):
|
|
181
187
|
t1 = time()
|
|
182
188
|
self._stop(interceptor_instance_id, check_safe_stops, bundle_exec_id)
|
|
@@ -190,10 +196,12 @@ class MQDao(object):
|
|
|
190
196
|
|
|
191
197
|
def _stop(self, interceptor_instance_id: str = None, check_safe_stops: bool = True, bundle_exec_id: int = None):
|
|
192
198
|
"""Stop MQ publisher."""
|
|
193
|
-
self.logger.debug(f"MQ pub received stop sign: bundle={bundle_exec_id}, interceptor={interceptor_instance_id}")
|
|
194
199
|
self._close_buffer()
|
|
195
|
-
|
|
196
|
-
|
|
200
|
+
if check_safe_stops and MQ_ENABLED:
|
|
201
|
+
self.logger.debug(
|
|
202
|
+
f"Sending flush-complete msg. Bundle: {bundle_exec_id}; interceptor id: {interceptor_instance_id}"
|
|
203
|
+
)
|
|
204
|
+
self._send_mq_dao_flush_complete(interceptor_instance_id, bundle_exec_id)
|
|
197
205
|
self.logger.debug(f"Sending stop msg. Bundle: {bundle_exec_id}; interceptor id: {interceptor_instance_id}")
|
|
198
206
|
self._send_mq_dao_time_thread_stop(interceptor_instance_id, bundle_exec_id)
|
|
199
207
|
self.started = False
|
|
@@ -210,6 +218,15 @@ class MQDao(object):
|
|
|
210
218
|
# self.logger.info("Control msg sent: " + str(msg))
|
|
211
219
|
self.send_message(msg)
|
|
212
220
|
|
|
221
|
+
def _send_mq_dao_flush_complete(self, interceptor_instance_id, exec_bundle_id=None):
|
|
222
|
+
msg = {
|
|
223
|
+
"type": "flowcept_control",
|
|
224
|
+
"info": "mq_flush_complete",
|
|
225
|
+
"interceptor_instance_id": interceptor_instance_id,
|
|
226
|
+
"exec_bundle_id": exec_bundle_id,
|
|
227
|
+
}
|
|
228
|
+
self.send_message(msg)
|
|
229
|
+
|
|
213
230
|
def send_document_inserter_stop(self, exec_bundle_id=None):
|
|
214
231
|
"""Send the document."""
|
|
215
232
|
# These control_messages are handled by the document inserter
|
flowcept/configs.py
CHANGED
|
@@ -9,7 +9,7 @@ from flowcept.version import __version__
|
|
|
9
9
|
PROJECT_NAME = "flowcept"
|
|
10
10
|
|
|
11
11
|
DEFAULT_SETTINGS = {
|
|
12
|
-
"
|
|
12
|
+
"flowcept_version": __version__,
|
|
13
13
|
"log": {"log_file_level": "disable", "log_stream_level": "disable"},
|
|
14
14
|
"project": {"dump_buffer": {"enabled": True}},
|
|
15
15
|
"telemetry_capture": {},
|
|
@@ -81,7 +81,7 @@ FLOWCEPT_USER = settings["experiment"].get("user", "blank_user")
|
|
|
81
81
|
|
|
82
82
|
MQ_INSTANCES = settings["mq"].get("instances", None)
|
|
83
83
|
MQ_SETTINGS = settings["mq"]
|
|
84
|
-
MQ_ENABLED = os.getenv("MQ_ENABLED", settings["mq"].get("enabled", True))
|
|
84
|
+
MQ_ENABLED = os.getenv("MQ_ENABLED", str(settings["mq"].get("enabled", True))).strip().lower() in _TRUE_VALUES
|
|
85
85
|
MQ_TYPE = os.getenv("MQ_TYPE", settings["mq"].get("type", "redis"))
|
|
86
86
|
MQ_CHANNEL = os.getenv("MQ_CHANNEL", settings["mq"].get("channel", "interception"))
|
|
87
87
|
MQ_PASSWORD = settings["mq"].get("password", None)
|
|
@@ -103,6 +103,11 @@ KVDB_PORT = int(os.getenv("KVDB_PORT", settings["kv_db"].get("port", "6379")))
|
|
|
103
103
|
KVDB_URI = os.getenv("KVDB_URI", settings["kv_db"].get("uri", None))
|
|
104
104
|
KVDB_ENABLED = settings["kv_db"].get("enabled", False)
|
|
105
105
|
|
|
106
|
+
if MQ_ENABLED and not KVDB_ENABLED:
|
|
107
|
+
raise ValueError(
|
|
108
|
+
"Invalid configuration: MQ is enabled but kv_db is disabled. "
|
|
109
|
+
"Enable kv_db.enabled (and KVDB) when MQ is enabled."
|
|
110
|
+
)
|
|
106
111
|
|
|
107
112
|
DATABASES = settings.get("databases", {})
|
|
108
113
|
|
|
@@ -160,6 +165,12 @@ JSON_SERIALIZER = settings["project"].get("json_serializer", "default")
|
|
|
160
165
|
REPLACE_NON_JSON_SERIALIZABLE = settings["project"].get("replace_non_json_serializable", True)
|
|
161
166
|
ENRICH_MESSAGES = settings["project"].get("enrich_messages", True)
|
|
162
167
|
|
|
168
|
+
if DB_FLUSH_MODE == "online" and not MQ_ENABLED:
|
|
169
|
+
raise ValueError(
|
|
170
|
+
"Invalid configuration: project.db_flush_mode is 'online' but MQ is disabled. "
|
|
171
|
+
"Enable mq.enabled (or MQ_ENABLED=true) or set project.db_flush_mode to 'offline'."
|
|
172
|
+
)
|
|
173
|
+
|
|
163
174
|
# Default: enable dump buffer only when running in offline flush mode.
|
|
164
175
|
_DEFAULT_DUMP_BUFFER_ENABLED = DB_FLUSH_MODE == "offline"
|
|
165
176
|
DUMP_BUFFER_ENABLED = (
|
|
@@ -45,12 +45,12 @@ class BaseAgentContextManager(BaseConsumer):
|
|
|
45
45
|
|
|
46
46
|
agent_id = None
|
|
47
47
|
|
|
48
|
-
def __init__(self):
|
|
48
|
+
def __init__(self, allow_mq_disabled: bool = False):
|
|
49
49
|
"""
|
|
50
50
|
Initializes the agent and resets its context state.
|
|
51
51
|
"""
|
|
52
52
|
self._started = False
|
|
53
|
-
super().__init__()
|
|
53
|
+
super().__init__(allow_mq_disabled=allow_mq_disabled)
|
|
54
54
|
# self.context = BaseAppContext(tasks=[])
|
|
55
55
|
self.agent_id = BaseAgentContextManager.agent_id
|
|
56
56
|
|
|
@@ -13,18 +13,28 @@ class BaseConsumer(object):
|
|
|
13
13
|
|
|
14
14
|
This class provides a standard interface and shared logic for subscribing to
|
|
15
15
|
message queues and dispatching messages to a handler.
|
|
16
|
+
|
|
17
|
+
Note
|
|
18
|
+
----
|
|
19
|
+
The MQ-disabled path is only intended for agent consumers that can operate
|
|
20
|
+
from an offline buffer file. General consumers that require MQ should keep
|
|
21
|
+
the default behavior (raise when MQ_ENABLED is False).
|
|
16
22
|
"""
|
|
17
23
|
|
|
18
|
-
def __init__(self):
|
|
24
|
+
def __init__(self, allow_mq_disabled: bool = False):
|
|
19
25
|
"""Initialize the message queue DAO and logger."""
|
|
26
|
+
self.logger = FlowceptLogger()
|
|
27
|
+
self._main_thread: Optional[Thread] = None
|
|
28
|
+
|
|
20
29
|
if not MQ_ENABLED:
|
|
30
|
+
if allow_mq_disabled:
|
|
31
|
+
self._mq_dao = None
|
|
32
|
+
self.logger.warning("MQ is disabled; starting consumer without a message queue.")
|
|
33
|
+
return
|
|
21
34
|
raise Exception("MQ is disabled in the settings. You cannot consume messages.")
|
|
22
35
|
|
|
23
36
|
self._mq_dao = MQDao.build()
|
|
24
37
|
|
|
25
|
-
self.logger = FlowceptLogger()
|
|
26
|
-
self._main_thread: Optional[Thread] = None
|
|
27
|
-
|
|
28
38
|
@abstractmethod
|
|
29
39
|
def message_handler(self, msg_obj: Dict) -> bool:
|
|
30
40
|
"""
|
|
@@ -62,6 +72,9 @@ class BaseConsumer(object):
|
|
|
62
72
|
BaseConsumer
|
|
63
73
|
The current instance (to allow chaining).
|
|
64
74
|
"""
|
|
75
|
+
if self._mq_dao is None:
|
|
76
|
+
self.logger.warning("MQ is disabled; skipping message consumption start.")
|
|
77
|
+
return self
|
|
65
78
|
if target is None:
|
|
66
79
|
target = self.default_thread_target
|
|
67
80
|
self._mq_dao.subscribe()
|
|
@@ -85,6 +98,9 @@ class BaseConsumer(object):
|
|
|
85
98
|
--------
|
|
86
99
|
start : Starts the consumer and optionally spawns a background thread to run this method.
|
|
87
100
|
"""
|
|
101
|
+
if self._mq_dao is None:
|
|
102
|
+
self.logger.warning("MQ is disabled; no message listener will run.")
|
|
103
|
+
return
|
|
88
104
|
self.logger.debug("Going to wait for new messages!")
|
|
89
105
|
self._mq_dao.message_listener(self.message_handler)
|
|
90
106
|
self.logger.debug("Broke main message listening loop!")
|
|
@@ -96,4 +112,6 @@ class BaseConsumer(object):
|
|
|
96
112
|
"""
|
|
97
113
|
Stop consuming messages by unsubscribing from the message queue.
|
|
98
114
|
"""
|
|
115
|
+
if self._mq_dao is None:
|
|
116
|
+
return
|
|
99
117
|
self._mq_dao.unsubscribe()
|
|
@@ -197,6 +197,24 @@ class DocumentInserter(BaseConsumer):
|
|
|
197
197
|
f"{'' if exec_bundle_id is None else exec_bundle_id}_{interceptor_instance_id}!"
|
|
198
198
|
)
|
|
199
199
|
return "continue"
|
|
200
|
+
elif message["info"] == "mq_flush_complete":
|
|
201
|
+
exec_bundle_id = message.get("exec_bundle_id", None)
|
|
202
|
+
interceptor_instance_id = message.get("interceptor_instance_id")
|
|
203
|
+
self.logger.info(
|
|
204
|
+
f"DocInserter id {id(self)}. Received mq_flush_complete message "
|
|
205
|
+
f"from the interceptor {'' if exec_bundle_id is None else exec_bundle_id}_{interceptor_instance_id}!"
|
|
206
|
+
)
|
|
207
|
+
if self.check_safe_stops:
|
|
208
|
+
self.logger.info(
|
|
209
|
+
f"Begin register_flush_complete "
|
|
210
|
+
f"{'' if exec_bundle_id is None else exec_bundle_id}_{interceptor_instance_id}!"
|
|
211
|
+
)
|
|
212
|
+
self._mq_dao.register_flush_complete(interceptor_instance_id, exec_bundle_id)
|
|
213
|
+
self.logger.info(
|
|
214
|
+
f"Done register_flush_complete "
|
|
215
|
+
f"{'' if exec_bundle_id is None else exec_bundle_id}_{interceptor_instance_id}!"
|
|
216
|
+
)
|
|
217
|
+
return "continue"
|
|
200
218
|
elif message["info"] == "stop_document_inserter":
|
|
201
219
|
exec_bundle_id = message.get("exec_bundle_id", None)
|
|
202
220
|
if self._bundle_exec_id == exec_bundle_id:
|
|
@@ -297,7 +315,10 @@ class DocumentInserter(BaseConsumer):
|
|
|
297
315
|
return self
|
|
298
316
|
if self.check_safe_stops:
|
|
299
317
|
trial = 0
|
|
300
|
-
while not
|
|
318
|
+
while not (
|
|
319
|
+
self._mq_dao.all_time_based_threads_ended(bundle_exec_id)
|
|
320
|
+
and self._mq_dao.all_flush_complete_received(bundle_exec_id)
|
|
321
|
+
):
|
|
301
322
|
self.logger.debug(
|
|
302
323
|
f"# time_based_threads for bundle_exec_id {bundle_exec_id} is"
|
|
303
324
|
f"{self._mq_dao._keyvalue_dao.set_count(bundle_exec_id)}"
|
flowcept/version.py
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
flowcept/__init__.py,sha256=tvVZKyymdqv3qOsgpAyDppBlUiBc0ag4QF21IcS-mVk,2449
|
|
2
2
|
flowcept/cli.py,sha256=d3hogRpuMwQFIqw_fsYD4y074o3AFslBjlkRvTthOVM,25702
|
|
3
|
-
flowcept/configs.py,sha256=
|
|
4
|
-
flowcept/version.py,sha256=
|
|
3
|
+
flowcept/configs.py,sha256=hqHdaXbVaXF0IPrUjqPPV_ey5ol31Gx2Gmjh43eQuik,9512
|
|
4
|
+
flowcept/version.py,sha256=cmAij7Yt87_OWquErsjUo2KO0u1npqmoClVqCUwAw90,307
|
|
5
5
|
flowcept/agents/__init__.py,sha256=8eeD2CiKBtHiDsWdrHK_UreIkKlTq4dUbhHDyzw372o,175
|
|
6
|
-
flowcept/agents/agent_client.py,sha256=
|
|
7
|
-
flowcept/agents/agents_utils.py,sha256=
|
|
6
|
+
flowcept/agents/agent_client.py,sha256=sfXZfF48pOaznRVgzPbTqkEgFnSrqn7SkgRsNE82-cg,2353
|
|
7
|
+
flowcept/agents/agents_utils.py,sha256=oc_ExBIKYGsPPS-FkZEjuL6RAx75IlYE_ykbIzxHYGg,8375
|
|
8
8
|
flowcept/agents/dynamic_schema_tracker.py,sha256=TsmXRRkyUkqB-0bEgmeqSms8xj1tMMJeYvjoaO2mtwI,6829
|
|
9
|
-
flowcept/agents/flowcept_agent.py,sha256
|
|
10
|
-
flowcept/agents/flowcept_ctx_manager.py,sha256=
|
|
9
|
+
flowcept/agents/flowcept_agent.py,sha256=iF9ZucrKU7uz35_W1UdXwXybQPxLpr_MkwSZLU0vAbo,4330
|
|
10
|
+
flowcept/agents/flowcept_ctx_manager.py,sha256=WIA2K5WJ71z-wwvyaHmHir8eIlJUVvuE6UqNrsNLZAk,10275
|
|
11
11
|
flowcept/agents/gui/__init__.py,sha256=Qw9YKbAzgZqBjMQGnF7XWmfUo0fivtkDISQRK3LA3gU,113
|
|
12
12
|
flowcept/agents/gui/agent_gui.py,sha256=VpwhQamzFKBfrmibxOIc-8wXtZnd2Cq7tbKahZZOp7c,2995
|
|
13
13
|
flowcept/agents/gui/audio_utils.py,sha256=piA_dc36io1sYqLF6QArS4AMl-cfDa001jGhYz5LkB4,4279
|
|
@@ -36,14 +36,14 @@ flowcept/commons/task_data_preprocess.py,sha256=-ceLexv2ZfZOAYF43DPagGwQPgt_L_lN
|
|
|
36
36
|
flowcept/commons/utils.py,sha256=okCShkcuWhzznBtADDDusTdfPXO0W041b2f4Aog-7SE,9831
|
|
37
37
|
flowcept/commons/vocabulary.py,sha256=0psC4NulNFn88mjTcoT_aT4QxX8ljMFgTOF3FxzM40A,1118
|
|
38
38
|
flowcept/commons/daos/__init__.py,sha256=RO51svfHOg9naN676zuQwbj_RQ6IFHu-RALeefvtwwk,23
|
|
39
|
-
flowcept/commons/daos/keyvalue_dao.py,sha256=
|
|
39
|
+
flowcept/commons/daos/keyvalue_dao.py,sha256=_tLnq2Se9deemYmQ9QjGYB_J16ZdAfa2pO3zzd3cQHU,3891
|
|
40
40
|
flowcept/commons/daos/redis_conn.py,sha256=gFyW-5yf6B8ExEYopCmbap8ki-iEwuIw-KH9f6o7UGQ,1495
|
|
41
41
|
flowcept/commons/daos/docdb_dao/__init__.py,sha256=qRvXREeUJ4mkhxdC9bzpOsVX6M2FB5hDyLFxhMxTGhs,30
|
|
42
42
|
flowcept/commons/daos/docdb_dao/docdb_dao_base.py,sha256=YbfSVJPwZGK2GBYkeapRC83HkmP0c6Msv5TriD88RcI,11812
|
|
43
43
|
flowcept/commons/daos/docdb_dao/lmdb_dao.py,sha256=5FV11hjIpFUZTP4HJwPTswd4TT27EJ3it82TpY5Z2RI,12187
|
|
44
44
|
flowcept/commons/daos/docdb_dao/mongodb_dao.py,sha256=5x0un15uCDTcnuITOyOhvF9mKj_bUmF2du0AHQfjN9k,40055
|
|
45
45
|
flowcept/commons/daos/mq_dao/__init__.py,sha256=Xxm4FmbBUZDQ7XIAmSFbeKE_AdHsbgFmSuftvMWSykQ,21
|
|
46
|
-
flowcept/commons/daos/mq_dao/mq_dao_base.py,sha256=
|
|
46
|
+
flowcept/commons/daos/mq_dao/mq_dao_base.py,sha256=BAegPRL9yozNFJnlkpyc2pW06ZiLXHzGITKFhY2ID7A,10777
|
|
47
47
|
flowcept/commons/daos/mq_dao/mq_dao_kafka.py,sha256=mWoY9RvViHegzXXynuegU_jg-S55YSV5lfgNqaPuMlg,5085
|
|
48
48
|
flowcept/commons/daos/mq_dao/mq_dao_mofka.py,sha256=tRdMGYDzdeIJxad-B4-DE6u8Wzs61eTzOW4ojZrnTxs,4057
|
|
49
49
|
flowcept/commons/daos/mq_dao/mq_dao_redis.py,sha256=be1ejbQofeJhf2qw7AWT9JF8z-a3kk-DbSCMU9wAwuI,6773
|
|
@@ -82,11 +82,11 @@ flowcept/flowceptor/adapters/tensorboard/__init__.py,sha256=LrcR4WCIlBwwHIUSteQ8
|
|
|
82
82
|
flowcept/flowceptor/adapters/tensorboard/tensorboard_dataclasses.py,sha256=lSfDd6TucVNzGxbm69BYyCVgMr2p9iUEQjnsS4jIfeI,554
|
|
83
83
|
flowcept/flowceptor/adapters/tensorboard/tensorboard_interceptor.py,sha256=GcRiY93MjuEmdhh37PAyj4ZtwBovA7FPiEM2TjVxsPw,5123
|
|
84
84
|
flowcept/flowceptor/consumers/__init__.py,sha256=foxtVEb2ZEe9g1slfYIKM4tIFv-He1l7XS--SYs7nlQ,28
|
|
85
|
-
flowcept/flowceptor/consumers/base_consumer.py,sha256=
|
|
85
|
+
flowcept/flowceptor/consumers/base_consumer.py,sha256=B_oo-FI222PsHErlTiW0IALMPqu2d3cSSRA8nMW5hDk,4113
|
|
86
86
|
flowcept/flowceptor/consumers/consumer_utils.py,sha256=E6R07zIKNXJTCxvL-OCrCKNYRpqtwRiXiZx0D2BKidk,5893
|
|
87
|
-
flowcept/flowceptor/consumers/document_inserter.py,sha256=
|
|
87
|
+
flowcept/flowceptor/consumers/document_inserter.py,sha256=f9EnQfBbMx27UKzCIymNvsyTxuv8mP4ftY4njxvyjo0,14515
|
|
88
88
|
flowcept/flowceptor/consumers/agent/__init__.py,sha256=R1uvjBPeTLw9SpYgyUc6Qmo16pE84PFHcELTTFvyTWU,56
|
|
89
|
-
flowcept/flowceptor/consumers/agent/base_agent_context_manager.py,sha256=
|
|
89
|
+
flowcept/flowceptor/consumers/agent/base_agent_context_manager.py,sha256=Hi2fBgyWLks3mamWNoT0dKT5vDspYIURq4eflq-JKeU,4184
|
|
90
90
|
flowcept/instrumentation/__init__.py,sha256=M5bTmg80E4QyN91gUX3qfw_nbtJSXwGWcKxdZP3vJz0,34
|
|
91
91
|
flowcept/instrumentation/flowcept_agent_task.py,sha256=XN9JU4LODca0SgojUm4F5iU_V8tuWkOt1fAKcoOAG34,10757
|
|
92
92
|
flowcept/instrumentation/flowcept_decorator.py,sha256=X4Lp_FSsoL08K8ZhRM4mC0OjKupbQtbMQR8zxy3ezDY,1350
|
|
@@ -94,9 +94,9 @@ flowcept/instrumentation/flowcept_loop.py,sha256=nF7Sov-DCDapyYvS8zx-1ZFrnjc3CPg
|
|
|
94
94
|
flowcept/instrumentation/flowcept_task.py,sha256=_G1e5SOMQ1y8RhO_rVexe7icQbgFF4TpaHlpW_MxERc,11135
|
|
95
95
|
flowcept/instrumentation/flowcept_torch.py,sha256=kkZQRYq6cDBpdBU6J39_4oKRVkhyF3ODlz8ydV5WGKw,23455
|
|
96
96
|
flowcept/instrumentation/task_capture.py,sha256=p_Cj9_cHVMhgzqDXhKqdpk01-88VAAVhbgk5IDa-7sk,8576
|
|
97
|
-
resources/sample_settings.yaml,sha256=
|
|
98
|
-
flowcept-0.9.
|
|
99
|
-
flowcept-0.9.
|
|
100
|
-
flowcept-0.9.
|
|
101
|
-
flowcept-0.9.
|
|
102
|
-
flowcept-0.9.
|
|
97
|
+
resources/sample_settings.yaml,sha256=5k1e81rNmBs7MEW-FyX1DPnrZcQQUrF7imAPKr4Mtys,6895
|
|
98
|
+
flowcept-0.9.19.dist-info/METADATA,sha256=bcyJrlLE1SGz8M96BA6SpVIZkjsCxrgYV6NlYOpxVvM,33386
|
|
99
|
+
flowcept-0.9.19.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
100
|
+
flowcept-0.9.19.dist-info/entry_points.txt,sha256=i8q67WE0201rVxYI2lyBtS52shvgl93x2Szp4q8zMlw,47
|
|
101
|
+
flowcept-0.9.19.dist-info/licenses/LICENSE,sha256=r5-2P6tFTuRGWT5TiX32s1y0tnp4cIqBEC1QjTaXe2k,1086
|
|
102
|
+
flowcept-0.9.19.dist-info/RECORD,,
|
resources/sample_settings.yaml
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
flowcept_version: 0.9.
|
|
1
|
+
flowcept_version: 0.9.19 # Version of the Flowcept package. This setting file is compatible with this version.
|
|
2
2
|
|
|
3
3
|
project:
|
|
4
4
|
debug: true # Toggle debug mode. This will add a property `debug: true` to all saved data, making it easier to retrieve/delete them later.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|