flock-core 0.1.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/__init__.py +4 -0
- flock/agents/__init__.py +3 -0
- flock/agents/batch_agent.py +175 -0
- flock/agents/declarative_agent.py +166 -0
- flock/agents/loop_agent.py +178 -0
- flock/agents/trigger_agent.py +191 -0
- flock/agents/user_agent.py +230 -0
- flock/app/components/__init__.py +14 -0
- flock/app/components/charts/agent_workflow.py +14 -0
- flock/app/components/charts/core_architecture.py +14 -0
- flock/app/components/charts/tool_system.py +14 -0
- flock/app/components/history_grid.py +168 -0
- flock/app/components/history_grid_alt.py +189 -0
- flock/app/components/sidebar.py +19 -0
- flock/app/components/theme.py +9 -0
- flock/app/components/util.py +18 -0
- flock/app/hive_app.py +118 -0
- flock/app/html/d3.html +179 -0
- flock/app/modules/__init__.py +12 -0
- flock/app/modules/about.py +17 -0
- flock/app/modules/agent_detail.py +70 -0
- flock/app/modules/agent_list.py +59 -0
- flock/app/modules/playground.py +322 -0
- flock/app/modules/settings.py +96 -0
- flock/core/__init__.py +7 -0
- flock/core/agent.py +150 -0
- flock/core/agent_registry.py +162 -0
- flock/core/config/declarative_agent_config.py +0 -0
- flock/core/context.py +279 -0
- flock/core/context_vars.py +6 -0
- flock/core/flock.py +208 -0
- flock/core/handoff/handoff_base.py +12 -0
- flock/core/logging/__init__.py +18 -0
- flock/core/logging/error_handler.py +84 -0
- flock/core/logging/formatters.py +122 -0
- flock/core/logging/handlers.py +117 -0
- flock/core/logging/logger.py +107 -0
- flock/core/serializable.py +206 -0
- flock/core/tools/basic_tools.py +98 -0
- flock/workflow/activities.py +115 -0
- flock/workflow/agent_activities.py +26 -0
- flock/workflow/temporal_setup.py +37 -0
- flock/workflow/workflow.py +53 -0
- flock_core-0.1.1.dist-info/METADATA +449 -0
- flock_core-0.1.1.dist-info/RECORD +48 -0
- flock_core-0.1.1.dist-info/WHEEL +4 -0
- flock_core-0.1.1.dist-info/entry_points.txt +2 -0
- flock_core-0.1.1.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
"""Module for serializable objects in the system."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from abc import ABC, abstractmethod
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any, TypeVar
|
|
7
|
+
|
|
8
|
+
import cloudpickle
|
|
9
|
+
import msgpack
|
|
10
|
+
|
|
11
|
+
from flock.core.logging import flock_logger, performance_handler
|
|
12
|
+
|
|
13
|
+
T = TypeVar("T", bound="Serializable")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class Serializable(ABC):
|
|
17
|
+
"""Base class for all serializable objects in the system.
|
|
18
|
+
|
|
19
|
+
Provides methods for serializing/deserializing objects to various formats
|
|
20
|
+
with comprehensive logging and performance tracking.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
@abstractmethod
|
|
24
|
+
def to_dict(self) -> dict[str, Any]:
|
|
25
|
+
"""Convert instance to dictionary representation."""
|
|
26
|
+
pass
|
|
27
|
+
|
|
28
|
+
@classmethod
|
|
29
|
+
@abstractmethod
|
|
30
|
+
def from_dict(cls: type[T], data: dict[str, Any]) -> T:
|
|
31
|
+
"""Create instance from dictionary representation."""
|
|
32
|
+
pass
|
|
33
|
+
|
|
34
|
+
def to_json(self) -> str:
|
|
35
|
+
"""Serialize to JSON string."""
|
|
36
|
+
try:
|
|
37
|
+
with performance_handler.track_time("json_serialization"):
|
|
38
|
+
flock_logger.debug(
|
|
39
|
+
"Serializing to JSON",
|
|
40
|
+
class_name=self.__class__.__name__,
|
|
41
|
+
)
|
|
42
|
+
json_str = json.dumps(self.to_dict())
|
|
43
|
+
flock_logger.debug(
|
|
44
|
+
"JSON serialization complete",
|
|
45
|
+
size_bytes=len(json_str),
|
|
46
|
+
)
|
|
47
|
+
return json_str
|
|
48
|
+
except Exception as e:
|
|
49
|
+
flock_logger.error(
|
|
50
|
+
"JSON serialization failed",
|
|
51
|
+
class_name=self.__class__.__name__,
|
|
52
|
+
error=str(e),
|
|
53
|
+
)
|
|
54
|
+
raise
|
|
55
|
+
|
|
56
|
+
@classmethod
|
|
57
|
+
def from_json(cls: type[T], json_str: str) -> T:
|
|
58
|
+
"""Create instance from JSON string."""
|
|
59
|
+
try:
|
|
60
|
+
with performance_handler.track_time("json_deserialization"):
|
|
61
|
+
flock_logger.debug(
|
|
62
|
+
"Deserializing from JSON",
|
|
63
|
+
class_name=cls.__name__,
|
|
64
|
+
size_bytes=len(json_str),
|
|
65
|
+
)
|
|
66
|
+
instance = cls.from_dict(json.loads(json_str))
|
|
67
|
+
flock_logger.debug("JSON deserialization complete")
|
|
68
|
+
return instance
|
|
69
|
+
except Exception as e:
|
|
70
|
+
flock_logger.error(
|
|
71
|
+
"JSON deserialization failed",
|
|
72
|
+
class_name=cls.__name__,
|
|
73
|
+
error=str(e),
|
|
74
|
+
)
|
|
75
|
+
raise
|
|
76
|
+
|
|
77
|
+
def to_msgpack(self, path: Path | None = None) -> bytes:
|
|
78
|
+
"""Serialize to msgpack bytes."""
|
|
79
|
+
try:
|
|
80
|
+
with performance_handler.track_time("msgpack_serialization"):
|
|
81
|
+
flock_logger.debug(
|
|
82
|
+
"Serializing to msgpack",
|
|
83
|
+
class_name=self.__class__.__name__,
|
|
84
|
+
)
|
|
85
|
+
msgpack_bytes = msgpack.packb(self.to_dict())
|
|
86
|
+
|
|
87
|
+
if path:
|
|
88
|
+
flock_logger.debug(f"Writing msgpack to file: {path}")
|
|
89
|
+
path.write_bytes(msgpack_bytes)
|
|
90
|
+
|
|
91
|
+
flock_logger.debug(
|
|
92
|
+
"Msgpack serialization complete",
|
|
93
|
+
size_bytes=len(msgpack_bytes),
|
|
94
|
+
file_path=str(path) if path else None,
|
|
95
|
+
)
|
|
96
|
+
return msgpack_bytes
|
|
97
|
+
except Exception as e:
|
|
98
|
+
flock_logger.error(
|
|
99
|
+
"Msgpack serialization failed",
|
|
100
|
+
class_name=self.__class__.__name__,
|
|
101
|
+
file_path=str(path) if path else None,
|
|
102
|
+
error=str(e),
|
|
103
|
+
)
|
|
104
|
+
raise
|
|
105
|
+
|
|
106
|
+
@classmethod
|
|
107
|
+
def from_msgpack(cls: type[T], msgpack_bytes: bytes) -> T:
|
|
108
|
+
"""Create instance from msgpack bytes."""
|
|
109
|
+
try:
|
|
110
|
+
with performance_handler.track_time("msgpack_deserialization"):
|
|
111
|
+
flock_logger.debug(
|
|
112
|
+
"Deserializing from msgpack",
|
|
113
|
+
class_name=cls.__name__,
|
|
114
|
+
size_bytes=len(msgpack_bytes),
|
|
115
|
+
)
|
|
116
|
+
instance = cls.from_dict(msgpack.unpackb(msgpack_bytes))
|
|
117
|
+
flock_logger.debug("Msgpack deserialization complete")
|
|
118
|
+
return instance
|
|
119
|
+
except Exception as e:
|
|
120
|
+
flock_logger.error(
|
|
121
|
+
"Msgpack deserialization failed",
|
|
122
|
+
class_name=cls.__name__,
|
|
123
|
+
error=str(e),
|
|
124
|
+
)
|
|
125
|
+
raise
|
|
126
|
+
|
|
127
|
+
@classmethod
|
|
128
|
+
def from_msgpack_file(cls: type[T], path: Path) -> T:
|
|
129
|
+
"""Create instance from msgpack file."""
|
|
130
|
+
try:
|
|
131
|
+
with performance_handler.track_time("msgpack_file_read"):
|
|
132
|
+
flock_logger.debug(
|
|
133
|
+
f"Reading msgpack from file: {path}",
|
|
134
|
+
class_name=cls.__name__,
|
|
135
|
+
)
|
|
136
|
+
return cls.from_msgpack(path.read_bytes())
|
|
137
|
+
except Exception as e:
|
|
138
|
+
flock_logger.error(
|
|
139
|
+
"Msgpack file read failed",
|
|
140
|
+
class_name=cls.__name__,
|
|
141
|
+
file_path=str(path),
|
|
142
|
+
error=str(e),
|
|
143
|
+
)
|
|
144
|
+
raise
|
|
145
|
+
|
|
146
|
+
def to_pickle(self) -> bytes:
|
|
147
|
+
"""Serialize to pickle bytes."""
|
|
148
|
+
try:
|
|
149
|
+
with performance_handler.track_time("pickle_serialization"):
|
|
150
|
+
flock_logger.debug(
|
|
151
|
+
"Serializing to pickle",
|
|
152
|
+
class_name=self.__class__.__name__,
|
|
153
|
+
)
|
|
154
|
+
pickle_bytes = cloudpickle.dumps(self)
|
|
155
|
+
flock_logger.debug(
|
|
156
|
+
"Pickle serialization complete",
|
|
157
|
+
size_bytes=len(pickle_bytes),
|
|
158
|
+
)
|
|
159
|
+
return pickle_bytes
|
|
160
|
+
except Exception as e:
|
|
161
|
+
flock_logger.error(
|
|
162
|
+
"Pickle serialization failed",
|
|
163
|
+
class_name=self.__class__.__name__,
|
|
164
|
+
error=str(e),
|
|
165
|
+
)
|
|
166
|
+
raise
|
|
167
|
+
|
|
168
|
+
@classmethod
|
|
169
|
+
def from_pickle(cls, pickle_bytes: bytes) -> T:
|
|
170
|
+
"""Create instance from pickle bytes."""
|
|
171
|
+
try:
|
|
172
|
+
with performance_handler.track_time("pickle_deserialization"):
|
|
173
|
+
flock_logger.debug(
|
|
174
|
+
"Deserializing from pickle",
|
|
175
|
+
class_name=cls.__name__,
|
|
176
|
+
size_bytes=len(pickle_bytes),
|
|
177
|
+
)
|
|
178
|
+
instance = cloudpickle.loads(pickle_bytes)
|
|
179
|
+
flock_logger.debug("Pickle deserialization complete")
|
|
180
|
+
return instance
|
|
181
|
+
except Exception as e:
|
|
182
|
+
flock_logger.error(
|
|
183
|
+
"Pickle deserialization failed",
|
|
184
|
+
class_name=cls.__name__,
|
|
185
|
+
error=str(e),
|
|
186
|
+
)
|
|
187
|
+
raise
|
|
188
|
+
|
|
189
|
+
@classmethod
|
|
190
|
+
def from_pickle_file(cls: type[T], path: Path) -> T:
|
|
191
|
+
"""Create instance from pickle file."""
|
|
192
|
+
try:
|
|
193
|
+
with performance_handler.track_time("pickle_file_read"):
|
|
194
|
+
flock_logger.debug(
|
|
195
|
+
f"Reading pickle from file: {path}",
|
|
196
|
+
class_name=cls.__name__,
|
|
197
|
+
)
|
|
198
|
+
return cls.from_pickle(path.read_bytes())
|
|
199
|
+
except Exception as e:
|
|
200
|
+
flock_logger.error(
|
|
201
|
+
"Pickle file read failed",
|
|
202
|
+
class_name=cls.__name__,
|
|
203
|
+
file_path=str(path),
|
|
204
|
+
error=str(e),
|
|
205
|
+
)
|
|
206
|
+
raise
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
import httpx
|
|
4
|
+
from markdownify import markdownify as md
|
|
5
|
+
from tavily import TavilyClient
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def web_search_tavily(query: str):
|
|
9
|
+
client = TavilyClient(api_key=os.getenv("TAVILY_API_KEY"))
|
|
10
|
+
try:
|
|
11
|
+
response = client.search(query, include_answer=True) # type: ignore
|
|
12
|
+
return response
|
|
13
|
+
except Exception:
|
|
14
|
+
raise
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def get_web_content_as_markdown(url: str):
|
|
18
|
+
try:
|
|
19
|
+
response = httpx.get(url)
|
|
20
|
+
response.raise_for_status()
|
|
21
|
+
markdown = md(response.text)
|
|
22
|
+
return markdown
|
|
23
|
+
except Exception:
|
|
24
|
+
raise
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def evaluate_math(expression: str) -> float:
|
|
28
|
+
import dspy
|
|
29
|
+
|
|
30
|
+
try:
|
|
31
|
+
result = dspy.PythonInterpreter({}).execute(expression)
|
|
32
|
+
return result
|
|
33
|
+
except Exception:
|
|
34
|
+
raise
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def code_eval(python_code: str) -> float:
|
|
38
|
+
import dspy
|
|
39
|
+
|
|
40
|
+
try:
|
|
41
|
+
result = dspy.PythonInterpreter({}).execute(python_code)
|
|
42
|
+
return result
|
|
43
|
+
except Exception:
|
|
44
|
+
raise
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def get_current_time() -> str:
|
|
48
|
+
import datetime
|
|
49
|
+
|
|
50
|
+
time = datetime.datetime.now().isoformat()
|
|
51
|
+
return time
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def count_words(text: str) -> int:
|
|
55
|
+
count = len(text.split())
|
|
56
|
+
return count
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def extract_urls(text: str) -> list[str]:
|
|
60
|
+
import re
|
|
61
|
+
|
|
62
|
+
url_pattern = r"https?://(?:[-\w.]|(?:%[\da-fA-F]{2}))+"
|
|
63
|
+
urls = re.findall(url_pattern, text)
|
|
64
|
+
return urls
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def extract_numbers(text: str) -> list[float]:
|
|
68
|
+
import re
|
|
69
|
+
|
|
70
|
+
numbers = [float(x) for x in re.findall(r"-?\d*\.?\d+", text)]
|
|
71
|
+
return numbers
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def json_parse_safe(text: str) -> dict:
|
|
75
|
+
import json
|
|
76
|
+
|
|
77
|
+
try:
|
|
78
|
+
result = json.loads(text)
|
|
79
|
+
return result
|
|
80
|
+
except Exception:
|
|
81
|
+
return {}
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def save_to_file(content: str, filename: str):
|
|
85
|
+
try:
|
|
86
|
+
with open(filename, "w") as f:
|
|
87
|
+
f.write(content)
|
|
88
|
+
except Exception:
|
|
89
|
+
raise
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def read_from_file(filename: str) -> str:
|
|
93
|
+
try:
|
|
94
|
+
with open(filename) as f:
|
|
95
|
+
content = f.read()
|
|
96
|
+
return content
|
|
97
|
+
except Exception:
|
|
98
|
+
raise
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"""Defines Temporal activities for running a chain of agents."""
|
|
2
|
+
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
|
|
5
|
+
from temporalio import activity
|
|
6
|
+
|
|
7
|
+
from flock.core.agent import Agent
|
|
8
|
+
from flock.core.agent_registry import Registry
|
|
9
|
+
from flock.core.context import FlockContext
|
|
10
|
+
from flock.core.context_vars import FLOCK_CURRENT_AGENT, FLOCK_INITIAL_INPUT
|
|
11
|
+
from flock.core.handoff.handoff_base import HandoffBase
|
|
12
|
+
from flock.core.logging import flock_logger
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@activity.defn
|
|
16
|
+
async def run_agent(context: FlockContext) -> dict:
|
|
17
|
+
"""Runs a chain of agents using the provided context.
|
|
18
|
+
|
|
19
|
+
The context contains:
|
|
20
|
+
- A state (e.g., "init_input", "current_agent", etc.),
|
|
21
|
+
- A history of agent runs.
|
|
22
|
+
|
|
23
|
+
Each agent uses the current value of the variable specified in its `input` attribute.
|
|
24
|
+
After each run, its output is merged into the context state.
|
|
25
|
+
The default handoff behavior is to fetch the next agent's input automatically from the context.
|
|
26
|
+
"""
|
|
27
|
+
registry = Registry()
|
|
28
|
+
current_agent_name = context.get_variable(FLOCK_CURRENT_AGENT)
|
|
29
|
+
flock_logger.info(f"Starting agent chain with initial agent", agent=current_agent_name)
|
|
30
|
+
|
|
31
|
+
agent = registry.get_agent(current_agent_name)
|
|
32
|
+
if not agent:
|
|
33
|
+
flock_logger.error(f"Agent not found", agent=current_agent_name)
|
|
34
|
+
return {"error": f"Agent '{current_agent_name}' not found."}
|
|
35
|
+
|
|
36
|
+
while agent:
|
|
37
|
+
# Determine the input for this agent.
|
|
38
|
+
# (Preferably, the agent's input key is used; otherwise "init_input" is assumed.)
|
|
39
|
+
agent_input = (
|
|
40
|
+
context.get_variable(agent.input)
|
|
41
|
+
if getattr(agent, "input", None)
|
|
42
|
+
else context.get_variable(FLOCK_INITIAL_INPUT)
|
|
43
|
+
)
|
|
44
|
+
flock_logger.info(
|
|
45
|
+
"Determined agent input", agent=agent.name, input_source=getattr(agent, "input", "FLOCK_INITIAL_INPUT")
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
# Prepare a deep copy of the state for the agent's run.
|
|
49
|
+
local_context = FlockContext.from_dict(context.to_dict())
|
|
50
|
+
local_context.set_variable(
|
|
51
|
+
f"{agent.name}.{agent.input}", agent_input
|
|
52
|
+
) # ensure the agent's expected input is set
|
|
53
|
+
|
|
54
|
+
# Execute the agent.
|
|
55
|
+
flock_logger.info("Executing agent", agent=agent.name)
|
|
56
|
+
result = await agent.run(local_context)
|
|
57
|
+
flock_logger.info("Agent execution completed", agent=agent.name)
|
|
58
|
+
|
|
59
|
+
# If no handoff is defined, return the result.
|
|
60
|
+
if not agent.hand_off:
|
|
61
|
+
# Record the agent's execution.
|
|
62
|
+
context.record(agent.name, result, timestamp=datetime.now(), hand_off=None)
|
|
63
|
+
flock_logger.info("No handoff defined, completing chain", agent=agent.name)
|
|
64
|
+
return result
|
|
65
|
+
|
|
66
|
+
# Determine the next agent.
|
|
67
|
+
if callable(agent.hand_off):
|
|
68
|
+
# The handoff function may override the default behavior:
|
|
69
|
+
# it can explicitly return a dict with "next_agent" and optionally "input"
|
|
70
|
+
flock_logger.info("Executing handoff function", agent=agent.name)
|
|
71
|
+
handoff_data: HandoffBase = agent.hand_off(context, result)
|
|
72
|
+
if isinstance(handoff_data.next_agent, Agent):
|
|
73
|
+
next_agent_name = handoff_data.next_agent.name
|
|
74
|
+
else:
|
|
75
|
+
next_agent_name = handoff_data.next_agent
|
|
76
|
+
|
|
77
|
+
# Use the provided new input if present, otherwise let the context update be automatic.
|
|
78
|
+
if handoff_data.input:
|
|
79
|
+
context.state["init_input"] = handoff_data["input"]
|
|
80
|
+
flock_logger.debug("Using handoff-provided input", agent=agent.name)
|
|
81
|
+
if handoff_data.context_params:
|
|
82
|
+
context.state.update(handoff_data.context_params)
|
|
83
|
+
flock_logger.debug("Updated context with handoff params", agent=agent.name)
|
|
84
|
+
elif isinstance(agent.hand_off, str | Agent):
|
|
85
|
+
next_agent_name = agent.hand_off if isinstance(agent.hand_off, str) else agent.hand_off.name
|
|
86
|
+
|
|
87
|
+
else:
|
|
88
|
+
return {"error": "Unsupported hand_off type."}
|
|
89
|
+
context.record(agent.name, result, timestamp=datetime.now(), hand_off=next_agent_name)
|
|
90
|
+
# Update the current agent and prepare the next input automatically.
|
|
91
|
+
next_agent = registry.get_agent(next_agent_name)
|
|
92
|
+
if not next_agent:
|
|
93
|
+
flock_logger.error("Next agent not found", agent=next_agent_name)
|
|
94
|
+
return {"error": f"Next agent '{next_agent_name}' not found."}
|
|
95
|
+
|
|
96
|
+
context.state["current_agent"] = next_agent.name
|
|
97
|
+
# By default, the next input is determined from the current state using next_input_for().
|
|
98
|
+
context.state["init_input"] = context.next_input_for(next_agent)
|
|
99
|
+
flock_logger.info("Handing off to next agent", current=agent.name, next=next_agent.name)
|
|
100
|
+
agent = next_agent
|
|
101
|
+
|
|
102
|
+
# If the loop ever terminates without a final result, return the latest state value.
|
|
103
|
+
flock_logger.info("Chain completed, returning final state")
|
|
104
|
+
return context.get_variable("init_input")
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
@activity.defn
|
|
108
|
+
async def get_next_agent(name: str) -> Agent | None:
|
|
109
|
+
"""Retrieves the agent with the given name from the registry."""
|
|
110
|
+
flock_logger.debug("Looking up next agent", agent=name)
|
|
111
|
+
registry = Registry()
|
|
112
|
+
agent = registry.get_agent(name)
|
|
113
|
+
if not agent:
|
|
114
|
+
flock_logger.warning("Next agent not found", agent=name)
|
|
115
|
+
return agent
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from temporalio import activity
|
|
2
|
+
|
|
3
|
+
from flock.agents.declarative_agent import DeclarativeAgent
|
|
4
|
+
from flock.core.context import FlockContext
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@activity.defn
|
|
8
|
+
async def run_declarative_agent_activity(params: dict) -> dict:
|
|
9
|
+
"""Temporal activity to run a declarative (or batch) agent.
|
|
10
|
+
|
|
11
|
+
Expects a dictionary with:
|
|
12
|
+
- "agent_data": a dict representation of the agent (as produced by .dict()),
|
|
13
|
+
- "context_data": a dict containing the FlockContext state and optionally other fields.
|
|
14
|
+
|
|
15
|
+
The activity reconstructs the agent and a FlockContext, then calls the agent’s _evaluate() method.
|
|
16
|
+
"""
|
|
17
|
+
agent_data = params.get("agent_data")
|
|
18
|
+
context_data = params.get("context_data", {})
|
|
19
|
+
# Reconstruct the agent from its serialized representation.
|
|
20
|
+
agent = DeclarativeAgent.parse_obj(agent_data)
|
|
21
|
+
# Reconstruct the FlockContext from the state.
|
|
22
|
+
state = context_data.get("state", {})
|
|
23
|
+
# (For simplicity, we ignore history and agent_definitions here. You can extend this if needed.)
|
|
24
|
+
context = FlockContext(state=state)
|
|
25
|
+
result = await agent._evaluate(context)
|
|
26
|
+
return result
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import uuid
|
|
3
|
+
|
|
4
|
+
from temporalio.client import Client
|
|
5
|
+
from temporalio.worker import Worker
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
async def create_temporal_client() -> Client:
|
|
9
|
+
client = await Client.connect("localhost:7233")
|
|
10
|
+
return client
|
|
11
|
+
|
|
12
|
+
async def setup_worker(workflow,activity) -> Client:
|
|
13
|
+
worker_client = await create_temporal_client()
|
|
14
|
+
worker = Worker(worker_client, task_queue="flock-queue", workflows=[workflow], activities=[activity])
|
|
15
|
+
asyncio.create_task(worker.run())
|
|
16
|
+
await asyncio.sleep(1)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
async def run_worker(client: Client, task_queue: str, workflows, activities):
|
|
20
|
+
worker = Worker(client, task_queue=task_queue, workflows=workflows, activities=activities)
|
|
21
|
+
await worker.run()
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
async def run_activity(client: Client, name: str, func, param):
|
|
25
|
+
run_id = f"{name}_{uuid.uuid4().hex[:4]}"
|
|
26
|
+
|
|
27
|
+
try:
|
|
28
|
+
result = await client.execute_activity(
|
|
29
|
+
func,
|
|
30
|
+
param,
|
|
31
|
+
id=run_id,
|
|
32
|
+
task_queue="flock-queue",
|
|
33
|
+
start_to_close_timeout=300, # e.g., 5 minutes
|
|
34
|
+
)
|
|
35
|
+
return result
|
|
36
|
+
except Exception:
|
|
37
|
+
raise
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
from datetime import timedelta
|
|
2
|
+
|
|
3
|
+
from temporalio import workflow
|
|
4
|
+
|
|
5
|
+
from flock.core.context import FlockContext
|
|
6
|
+
from flock.core.logging import flock_logger
|
|
7
|
+
from flock.workflow.activities import run_agent
|
|
8
|
+
|
|
9
|
+
# Import activity, passing it through the sandbox without reloading the module
|
|
10
|
+
with workflow.unsafe.imports_passed_through():
|
|
11
|
+
from flock.workflow.activities import run_agent
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@workflow.defn
|
|
15
|
+
class FlockWorkflow:
|
|
16
|
+
def __init__(self) -> None:
|
|
17
|
+
self.context = None
|
|
18
|
+
|
|
19
|
+
@workflow.run
|
|
20
|
+
async def run(self, context_dict: dict) -> dict:
|
|
21
|
+
self.context = FlockContext.from_dict(context_dict)
|
|
22
|
+
self.context.workflow_id = workflow.info().workflow_id
|
|
23
|
+
self.context.workflow_timestamp = workflow.info().start_time.strftime("%Y-%m-%d %H:%M:%S")
|
|
24
|
+
|
|
25
|
+
try:
|
|
26
|
+
flock_logger.set_context(workflow_id=self.context.workflow_id)
|
|
27
|
+
flock_logger.workflow_event(f"Starting workflow execution at {self.context.workflow_timestamp}")
|
|
28
|
+
|
|
29
|
+
result = await workflow.execute_activity(
|
|
30
|
+
run_agent, self.context, start_to_close_timeout=timedelta(minutes=5)
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
self.context.set_variable(
|
|
34
|
+
"flock.result",
|
|
35
|
+
{
|
|
36
|
+
"result": result,
|
|
37
|
+
"success": True,
|
|
38
|
+
},
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
flock_logger.workflow_event("Workflow completed successfully")
|
|
42
|
+
return result
|
|
43
|
+
|
|
44
|
+
except Exception as e:
|
|
45
|
+
flock_logger.error(f"Workflow execution failed: {e}")
|
|
46
|
+
self.context.set_variable(
|
|
47
|
+
"flock.result",
|
|
48
|
+
{
|
|
49
|
+
"result": f"Failed: {e}",
|
|
50
|
+
"success": False,
|
|
51
|
+
},
|
|
52
|
+
)
|
|
53
|
+
return self.context
|