flock-core 0.2.1__py3-none-any.whl → 0.2.3__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/config.py +29 -0
- flock/core/__init__.py +5 -0
- flock/core/context/context.py +104 -133
- flock/core/execution/temporal_executor.py +22 -6
- flock/core/flock.py +94 -62
- flock/core/flock_agent.py +150 -68
- flock/core/logging/logging.py +22 -4
- flock/core/logging/telemetry.py +109 -0
- flock/core/logging/telemetry_exporter/file_span.py +37 -0
- flock/core/logging/telemetry_exporter/sqllite_span.py +68 -0
- flock/core/logging/trace_and_logged.py +55 -0
- flock/core/mixin/dspy_integration.py +40 -14
- flock/core/registry/agent_registry.py +89 -74
- flock/core/tools/basic_tools.py +27 -4
- flock/core/tools/dev_tools/github.py +37 -8
- flock/core/util/cli_helper.py +7 -3
- flock/workflow/activities.py +148 -90
- {flock_core-0.2.1.dist-info → flock_core-0.2.3.dist-info}/METADATA +36 -2
- {flock_core-0.2.1.dist-info → flock_core-0.2.3.dist-info}/RECORD +22 -22
- flock/agents/__init__.py +0 -0
- flock/agents/batch_agent.py +0 -140
- flock/agents/loop_agent.py +0 -117
- flock/agents/trigger_agent.py +0 -113
- flock/agents/user_agent.py +0 -145
- {flock_core-0.2.1.dist-info → flock_core-0.2.3.dist-info}/WHEEL +0 -0
- {flock_core-0.2.1.dist-info → flock_core-0.2.3.dist-info}/licenses/LICENSE +0 -0
flock/workflow/activities.py
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
"""Defines Temporal activities for running a chain of agents."""
|
|
1
|
+
"""Defines Temporal activities for running a chain of agents with logging and tracing."""
|
|
2
2
|
|
|
3
3
|
from datetime import datetime
|
|
4
4
|
|
|
5
|
+
from opentelemetry import trace
|
|
5
6
|
from temporalio import activity
|
|
6
7
|
|
|
7
8
|
from flock.core.context.context import FlockContext
|
|
8
9
|
from flock.core.context.context_vars import FLOCK_CURRENT_AGENT
|
|
9
|
-
from flock.core.flock_agent import FlockAgent,
|
|
10
|
+
from flock.core.flock_agent import FlockAgent, HandOff
|
|
10
11
|
from flock.core.logging.formatters.base_formatter import FormatterOptions
|
|
11
12
|
from flock.core.logging.formatters.formatter_factory import FormatterFactory
|
|
12
13
|
from flock.core.logging.logging import get_logger
|
|
@@ -14,6 +15,7 @@ from flock.core.registry.agent_registry import Registry
|
|
|
14
15
|
from flock.core.util.input_resolver import resolve_inputs
|
|
15
16
|
|
|
16
17
|
logger = get_logger("activities")
|
|
18
|
+
tracer = trace.get_tracer(__name__)
|
|
17
19
|
|
|
18
20
|
|
|
19
21
|
@activity.defn
|
|
@@ -22,96 +24,152 @@ async def run_agent(
|
|
|
22
24
|
) -> dict:
|
|
23
25
|
"""Runs a chain of agents using the provided context.
|
|
24
26
|
|
|
25
|
-
The context contains
|
|
26
|
-
|
|
27
|
-
- A history of agent runs.
|
|
28
|
-
|
|
29
|
-
Each agent uses the current value of the variable specified in its `input` attribute.
|
|
30
|
-
After each run, its output is merged into the context state.
|
|
31
|
-
The default handoff behavior is to fetch the next agent's input automatically from the context.
|
|
27
|
+
The context contains state, history, and agent definitions.
|
|
28
|
+
After each agent run, its output is merged into the context.
|
|
32
29
|
"""
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
# Execute the agent
|
|
51
|
-
logger.info("Executing agent")
|
|
52
|
-
result = await agent.run(agent_inputs)
|
|
53
|
-
logger.debug("Agent execution completed")
|
|
54
|
-
|
|
55
|
-
if output_formatter:
|
|
56
|
-
formatter = FormatterFactory.create_formatter(output_formatter)
|
|
57
|
-
formatter.display(
|
|
58
|
-
result, agent.name, output_formatter.wait_for_input
|
|
30
|
+
# Start a top-level span for the entire run_agent activity.
|
|
31
|
+
with tracer.start_as_current_span("run_agent") as span:
|
|
32
|
+
registry = Registry()
|
|
33
|
+
previous_agent_name = ""
|
|
34
|
+
if isinstance(context, dict):
|
|
35
|
+
context = FlockContext.from_dict(context)
|
|
36
|
+
current_agent_name = context.get_variable(FLOCK_CURRENT_AGENT)
|
|
37
|
+
span.set_attribute("initial.agent", current_agent_name)
|
|
38
|
+
logger.info("Starting agent chain", initial_agent=current_agent_name)
|
|
39
|
+
|
|
40
|
+
agent = registry.get_agent(current_agent_name)
|
|
41
|
+
agent.resolve_callables(context=context)
|
|
42
|
+
if not agent:
|
|
43
|
+
logger.error("Agent not found", agent=current_agent_name)
|
|
44
|
+
span.record_exception(
|
|
45
|
+
Exception(f"Agent '{current_agent_name}' not found")
|
|
59
46
|
)
|
|
47
|
+
return {"error": f"Agent '{current_agent_name}' not found."}
|
|
60
48
|
|
|
61
|
-
#
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
timestamp=datetime.now(),
|
|
67
|
-
hand_off=None,
|
|
68
|
-
called_from=previous_agent_name,
|
|
69
|
-
)
|
|
70
|
-
logger.info("No handoff defined, completing chain")
|
|
71
|
-
return result
|
|
72
|
-
|
|
73
|
-
# Determine the next agent
|
|
74
|
-
handoff_data = HandoffBase()
|
|
75
|
-
if callable(agent.hand_off):
|
|
76
|
-
logger.debug("Executing handoff function")
|
|
77
|
-
handoff_data = agent.hand_off(context, result)
|
|
78
|
-
if isinstance(handoff_data.next_agent, FlockAgent):
|
|
79
|
-
handoff_data.next_agent = handoff_data.next_agent.name
|
|
80
|
-
|
|
81
|
-
elif isinstance(agent.hand_off, str | FlockAgent):
|
|
82
|
-
handoff_data.next_agent = (
|
|
83
|
-
agent.hand_off
|
|
84
|
-
if isinstance(agent.hand_off, str)
|
|
85
|
-
else agent.hand_off.name
|
|
86
|
-
)
|
|
87
|
-
else:
|
|
88
|
-
logger.error("Unsupported hand_off type")
|
|
89
|
-
return {"error": "Unsupported hand_off type."}
|
|
90
|
-
|
|
91
|
-
context.record(
|
|
92
|
-
agent.name,
|
|
93
|
-
result,
|
|
94
|
-
timestamp=datetime.now().isoformat(),
|
|
95
|
-
hand_off=handoff_data,
|
|
96
|
-
called_from=previous_agent_name,
|
|
97
|
-
)
|
|
98
|
-
previous_agent_name = agent.name
|
|
99
|
-
|
|
100
|
-
# Update the current agent and prepare the next input automatically
|
|
101
|
-
try:
|
|
102
|
-
agent = registry.get_agent(handoff_data.next_agent)
|
|
103
|
-
if not agent:
|
|
104
|
-
logger.error(
|
|
105
|
-
"Next agent not found", agent=handoff_data.next_agent
|
|
106
|
-
)
|
|
107
|
-
return {
|
|
108
|
-
"error": f"Next agent '{handoff_data.next_agent}' not found."
|
|
109
|
-
}
|
|
49
|
+
# Loop over agents in the chain.
|
|
50
|
+
while agent:
|
|
51
|
+
# Create a nested span for this iteration.
|
|
52
|
+
with tracer.start_as_current_span("agent_iteration") as iter_span:
|
|
53
|
+
iter_span.set_attribute("agent.name", agent.name)
|
|
110
54
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
55
|
+
# Resolve inputs for the agent.
|
|
56
|
+
agent_inputs = resolve_inputs(
|
|
57
|
+
agent.input, context, previous_agent_name
|
|
58
|
+
)
|
|
59
|
+
iter_span.add_event(
|
|
60
|
+
"resolved inputs", attributes={"inputs": str(agent_inputs)}
|
|
61
|
+
)
|
|
116
62
|
|
|
117
|
-
|
|
63
|
+
# Execute the agent with its own span.
|
|
64
|
+
with tracer.start_as_current_span("execute_agent") as exec_span:
|
|
65
|
+
logger.info("Executing agent", agent=agent.name)
|
|
66
|
+
try:
|
|
67
|
+
result = await agent.run(agent_inputs)
|
|
68
|
+
exec_span.set_attribute("result", str(result))
|
|
69
|
+
logger.debug(
|
|
70
|
+
"Agent execution completed", agent=agent.name
|
|
71
|
+
)
|
|
72
|
+
except Exception as e:
|
|
73
|
+
logger.error(
|
|
74
|
+
"Agent execution failed",
|
|
75
|
+
agent=agent.name,
|
|
76
|
+
error=str(e),
|
|
77
|
+
)
|
|
78
|
+
exec_span.record_exception(e)
|
|
79
|
+
raise
|
|
80
|
+
|
|
81
|
+
# Optionally display formatted output.
|
|
82
|
+
display_output = True
|
|
83
|
+
if agent.config:
|
|
84
|
+
display_output = not agent.config.disable_output
|
|
85
|
+
|
|
86
|
+
if output_formatter and display_output:
|
|
87
|
+
formatter = FormatterFactory.create_formatter(
|
|
88
|
+
output_formatter
|
|
89
|
+
)
|
|
90
|
+
formatter.display(
|
|
91
|
+
result, agent.name, output_formatter.wait_for_input
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
# If there is no handoff, record the result and finish.
|
|
95
|
+
if not agent.hand_off:
|
|
96
|
+
context.record(
|
|
97
|
+
agent.name,
|
|
98
|
+
result,
|
|
99
|
+
timestamp=datetime.now().isoformat(),
|
|
100
|
+
hand_off=None,
|
|
101
|
+
called_from=previous_agent_name,
|
|
102
|
+
)
|
|
103
|
+
logger.info(
|
|
104
|
+
"No handoff defined, completing chain", agent=agent.name
|
|
105
|
+
)
|
|
106
|
+
iter_span.add_event("chain completed")
|
|
107
|
+
return result
|
|
108
|
+
|
|
109
|
+
# Determine the next agent.
|
|
110
|
+
handoff_data = HandOff()
|
|
111
|
+
if callable(agent.hand_off):
|
|
112
|
+
logger.debug("Executing handoff function", agent=agent.name)
|
|
113
|
+
try:
|
|
114
|
+
handoff_data = agent.hand_off(context, result)
|
|
115
|
+
if isinstance(handoff_data.next_agent, FlockAgent):
|
|
116
|
+
handoff_data.next_agent = (
|
|
117
|
+
handoff_data.next_agent.name
|
|
118
|
+
)
|
|
119
|
+
except Exception as e:
|
|
120
|
+
logger.error(
|
|
121
|
+
"Handoff function error",
|
|
122
|
+
agent=agent.name,
|
|
123
|
+
error=str(e),
|
|
124
|
+
)
|
|
125
|
+
iter_span.record_exception(e)
|
|
126
|
+
return {"error": f"Handoff function error: {e}"}
|
|
127
|
+
elif isinstance(agent.hand_off, str | FlockAgent):
|
|
128
|
+
handoff_data.next_agent = (
|
|
129
|
+
agent.hand_off
|
|
130
|
+
if isinstance(agent.hand_off, str)
|
|
131
|
+
else agent.hand_off.name
|
|
132
|
+
)
|
|
133
|
+
else:
|
|
134
|
+
logger.error("Unsupported hand_off type", agent=agent.name)
|
|
135
|
+
iter_span.add_event("unsupported hand_off type")
|
|
136
|
+
return {"error": "Unsupported hand_off type."}
|
|
137
|
+
|
|
138
|
+
# Record the agent run in the context.
|
|
139
|
+
context.record(
|
|
140
|
+
agent.name,
|
|
141
|
+
result,
|
|
142
|
+
timestamp=datetime.now().isoformat(),
|
|
143
|
+
hand_off=handoff_data,
|
|
144
|
+
called_from=previous_agent_name,
|
|
145
|
+
)
|
|
146
|
+
previous_agent_name = agent.name
|
|
147
|
+
|
|
148
|
+
# Prepare the next agent.
|
|
149
|
+
try:
|
|
150
|
+
agent = registry.get_agent(handoff_data.next_agent)
|
|
151
|
+
agent.resolve_callables(context=context)
|
|
152
|
+
if not agent:
|
|
153
|
+
logger.error(
|
|
154
|
+
"Next agent not found",
|
|
155
|
+
agent=handoff_data.next_agent,
|
|
156
|
+
)
|
|
157
|
+
iter_span.record_exception(
|
|
158
|
+
Exception(
|
|
159
|
+
f"Next agent '{handoff_data.next_agent}' not found"
|
|
160
|
+
)
|
|
161
|
+
)
|
|
162
|
+
return {
|
|
163
|
+
"error": f"Next agent '{handoff_data.next_agent}' not found."
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
context.set_variable(FLOCK_CURRENT_AGENT, agent.name)
|
|
167
|
+
logger.info("Handing off to next agent", next=agent.name)
|
|
168
|
+
iter_span.set_attribute("next.agent", agent.name)
|
|
169
|
+
except Exception as e:
|
|
170
|
+
logger.error("Error during handoff", error=str(e))
|
|
171
|
+
iter_span.record_exception(e)
|
|
172
|
+
return {"error": f"Error during handoff: {e}"}
|
|
173
|
+
|
|
174
|
+
# If the loop exits unexpectedly, return the initial input.
|
|
175
|
+
return context.get_variable("init_input")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: flock-core
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.3
|
|
4
4
|
Summary: Declarative LLM Orchestration at Scale
|
|
5
5
|
Author-email: Andre Ratzenberger <andre.ratzenberger@whiteduck.de>
|
|
6
6
|
License-File: LICENSE
|
|
@@ -14,6 +14,12 @@ Requires-Dist: dspy==2.5.42
|
|
|
14
14
|
Requires-Dist: httpx>=0.28.1
|
|
15
15
|
Requires-Dist: loguru>=0.7.3
|
|
16
16
|
Requires-Dist: msgpack>=1.1.0
|
|
17
|
+
Requires-Dist: opentelemetry-api>=1.30.0
|
|
18
|
+
Requires-Dist: opentelemetry-exporter-jaeger-proto-grpc>=1.21.0
|
|
19
|
+
Requires-Dist: opentelemetry-exporter-jaeger>=1.21.0
|
|
20
|
+
Requires-Dist: opentelemetry-exporter-otlp>=1.30.0
|
|
21
|
+
Requires-Dist: opentelemetry-instrumentation-logging>=0.51b0
|
|
22
|
+
Requires-Dist: opentelemetry-sdk>=1.30.0
|
|
17
23
|
Requires-Dist: pydantic>=2.10.5
|
|
18
24
|
Requires-Dist: python-box>=7.3.2
|
|
19
25
|
Requires-Dist: rich>=13.9.4
|
|
@@ -30,7 +36,7 @@ Description-Content-Type: text/markdown
|
|
|
30
36
|
|
|
31
37
|
<p align="center">
|
|
32
38
|
<img src="docs/img/flock.png" width="600"><br>
|
|
33
|
-
<h1 align="center">Flock
|
|
39
|
+
<h1 align="center">Flock<br></h1><br>
|
|
34
40
|
|
|
35
41
|
|
|
36
42
|
## Overview
|
|
@@ -83,6 +89,10 @@ Flock is a framework for orchestrating LLM-powered agents. It leverages a **decl
|
|
|
83
89
|
- **DSPy Integration:**
|
|
84
90
|
Flock leverages DSPy for managing LLM interactions. The framework constructs clean signature strings and updates field metadata so that DSPy can include detailed instructions and context for each agent call.
|
|
85
91
|
|
|
92
|
+
|
|
93
|
+
<p align="center">
|
|
94
|
+
<img src="docs/img/flock_cli.png" width="200"><br>
|
|
95
|
+
|
|
86
96
|
## Quick Start
|
|
87
97
|
|
|
88
98
|
Below is a simple example of how to create and run an agent with Flock:
|
|
@@ -238,6 +248,30 @@ pip install flock-core[all-tools]
|
|
|
238
248
|
uv build && uv pip install -e .
|
|
239
249
|
```
|
|
240
250
|
|
|
251
|
+
Install Jaeger for telemetry
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
docker run -d --name jaeger \
|
|
255
|
+
-e COLLECTOR_ZIPKIN_HTTP_PORT=9411 \
|
|
256
|
+
-p 5775:5775/udp \
|
|
257
|
+
-p 6831:6831/udp \
|
|
258
|
+
-p 6832:6832/udp \
|
|
259
|
+
-p 5778:5778 \
|
|
260
|
+
-p 16686:16686 \
|
|
261
|
+
-p 14268:14268 \
|
|
262
|
+
-p 14250:14250 \
|
|
263
|
+
-p 9411:9411 \
|
|
264
|
+
jaegertracing/all-in-one:1.41
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
or zipkin
|
|
270
|
+
|
|
271
|
+
```
|
|
272
|
+
docker run -d -p 9411:9411 openzipkin/zipkin
|
|
273
|
+
|
|
274
|
+
```
|
|
241
275
|
|
|
242
276
|
## Contributing
|
|
243
277
|
|
|
@@ -1,31 +1,31 @@
|
|
|
1
|
-
flock/__init__.py,sha256=
|
|
2
|
-
flock/
|
|
3
|
-
flock/
|
|
4
|
-
flock/
|
|
5
|
-
flock/
|
|
6
|
-
flock/
|
|
7
|
-
flock/core/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
8
|
-
flock/core/flock.py,sha256=F7hZWycq6IXPw1VNqhT1w6Wil3O4vQIkDZTDBmmILaw,8027
|
|
9
|
-
flock/core/flock_agent.py,sha256=P6GCE5-d8DvGARwN5u2HFDsDOtTQB6aEASozJc09w3g,23662
|
|
10
|
-
flock/core/context/context.py,sha256=yNsYHanXbo8EijBp4f3PiY0YCFcN9dSbmnLKhx3Q8Nw,7340
|
|
1
|
+
flock/__init__.py,sha256=uJxXAxt0def69cccAAdLjBxQOFXVRO72RE82hJnODSw,108
|
|
2
|
+
flock/config.py,sha256=hul_Vmgl8w5a_4YNfg3Mvll3iVEkjiR5At9D0WsVesw,1006
|
|
3
|
+
flock/core/__init__.py,sha256=0Xq_txurlxxjKGXjRn6GNJusGTiBcd7zw2eF0L7JyuU,183
|
|
4
|
+
flock/core/flock.py,sha256=Iw7Frmz2aZheApxi2KSjsX7gA8ZcemzXhfKXeQdtY0w,9438
|
|
5
|
+
flock/core/flock_agent.py,sha256=59qQ7ohOy2lc1KjI6SV7IcrqYL86ofAhq32pZGgk6eA,27761
|
|
6
|
+
flock/core/context/context.py,sha256=jH06w4C_O5CEL-YxjX_x_dmgLe9Rcllnn1Ebs0dvwaE,6171
|
|
11
7
|
flock/core/context/context_manager.py,sha256=qMySVny_dbTNLh21RHK_YT0mNKIOrqJDZpi9ZVdBsxU,1103
|
|
12
8
|
flock/core/context/context_vars.py,sha256=0Hn6fM2iNc0_jIIU0B7KX-K2o8qXqtZ5EYtwujETQ7U,272
|
|
13
9
|
flock/core/execution/local_executor.py,sha256=O_dgQ_HJPCp97ghdEoDSNDIiaYkogrUS0G2FfK04RRc,973
|
|
14
|
-
flock/core/execution/temporal_executor.py,sha256=
|
|
10
|
+
flock/core/execution/temporal_executor.py,sha256=ai6ikr9rEiN2Kc-208OalxtfqL_FTt_UaH6a--oEkJM,2010
|
|
15
11
|
flock/core/logging/__init__.py,sha256=Q8hp9-1ilPIUIV0jLgJ3_cP7COrea32cVwL7dicPnlM,82
|
|
16
|
-
flock/core/logging/logging.py,sha256=
|
|
12
|
+
flock/core/logging/logging.py,sha256=F-FDz9etBXmAIT-fjx3pUfvNXsckgR6ONCMaFZIc4Kw,4619
|
|
13
|
+
flock/core/logging/telemetry.py,sha256=T2CRSiqOWvOsXe-WRsObkkOkrrd6z-BwEYLaBUU2AAM,4017
|
|
14
|
+
flock/core/logging/trace_and_logged.py,sha256=h4YH8s0KjK4tiBdrEZdCLd4fDzMB5-NKwqzrtkWhQw4,1999
|
|
17
15
|
flock/core/logging/formatters/base_formatter.py,sha256=CyG-X2NWq8sqEhFEO2aG7Mey5tVkIzoWiihW301_VIo,1023
|
|
18
16
|
flock/core/logging/formatters/formatter_factory.py,sha256=hmH-NpCESHkioX0GBQ5CuQR4axyIXnSRWwAZCHylx6Q,1283
|
|
19
17
|
flock/core/logging/formatters/pprint_formatter.py,sha256=2CZoMbfzJB9Xp9WaItxnXY3hfANMjDTXowk3ni6N-h4,565
|
|
20
18
|
flock/core/logging/formatters/rich_formatters.py,sha256=h1FD0_cIdQBQ8P2x05XhgD1cmmP80IBNVT5jb3cAV9M,4776
|
|
21
19
|
flock/core/logging/formatters/theme_builder.py,sha256=1RUEwPIDfCjwTapbK1liasA5SdukOn7YwbZ4H4j1WkI,17364
|
|
22
20
|
flock/core/logging/formatters/themed_formatter.py,sha256=CbxmqUC7zkLzyIxngk-3dcpQ6vxPR6zaDNA2TAMitCI,16714
|
|
23
|
-
flock/core/
|
|
21
|
+
flock/core/logging/telemetry_exporter/file_span.py,sha256=e4hr4D7tC9j4KT7JZBuZU0YxQCdHKADpXNeUNEwggN4,1294
|
|
22
|
+
flock/core/logging/telemetry_exporter/sqllite_span.py,sha256=9bqxHt1mDQGyhKxA9dON5xDi_6FMOfBSdrU_zWV0xv4,2107
|
|
23
|
+
flock/core/mixin/dspy_integration.py,sha256=oT5YfXxPhHkMCuwhXoppBAYBGePUAKse7KebGSM-bq0,6880
|
|
24
24
|
flock/core/mixin/prompt_parser.py,sha256=eOqI-FK3y17gVqpc_y5GF-WmK1Jv8mFlkZxTcgweoxI,5121
|
|
25
|
-
flock/core/registry/agent_registry.py,sha256=
|
|
26
|
-
flock/core/tools/basic_tools.py,sha256=
|
|
27
|
-
flock/core/tools/dev_tools/github.py,sha256=
|
|
28
|
-
flock/core/util/cli_helper.py,sha256=
|
|
25
|
+
flock/core/registry/agent_registry.py,sha256=QHdr3Cb-32PEdz8jFCIZSH9OlfpRwAJMtSRpHCWJDq4,4889
|
|
26
|
+
flock/core/tools/basic_tools.py,sha256=nRc1bIz96z-WUTe_yYf9V6EfCPEncl_XnrpGdC7dEmo,8721
|
|
27
|
+
flock/core/tools/dev_tools/github.py,sha256=6ya2_eN-qITV3b_pYP24jQC3X4oZbRY5GKh1AF-9Zic,6836
|
|
28
|
+
flock/core/util/cli_helper.py,sha256=hXBXhTQizZILXiVeBFR6QdAsQm0Qj29ANHJRgiekwyw,859
|
|
29
29
|
flock/core/util/input_resolver.py,sha256=OesGqX2Dld8myL9Qz04mmxLqoYqOSQC632pj1EMk9Yk,5456
|
|
30
30
|
flock/core/util/serializable.py,sha256=SymJ0YrjBx48mOBItYSqoRpKuzIc4vKWRS6ScTzre7s,2573
|
|
31
31
|
flock/themes/3024-day.toml,sha256=uOVHqEzSyHx0WlUk3D0lne4RBsNBAPCTy3C58yU7kEY,667
|
|
@@ -365,11 +365,11 @@ flock/themes/zenburned.toml,sha256=UEmquBbcAO3Zj652XKUwCsNoC2iQSlIh-q5c6DH-7Kc,1
|
|
|
365
365
|
flock/themes/zenwritten-dark.toml,sha256=To5l6520_3UqAGiEumpzGWsHhXxqu9ThrMildXKgIO0,1669
|
|
366
366
|
flock/themes/zenwritten-light.toml,sha256=G1iEheCPfBNsMTGaVpEVpDzYBHA_T-MV27rolUYolmE,1666
|
|
367
367
|
flock/workflow/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
368
|
-
flock/workflow/activities.py,sha256=
|
|
368
|
+
flock/workflow/activities.py,sha256=YEg-Gr8kzVsxWsmsZguIVhX2XwMRvhZ2OlnsJoG5g_A,7646
|
|
369
369
|
flock/workflow/agent_activities.py,sha256=NhBZscflEf2IMfSRa_pBM_TRP7uVEF_O0ROvWZ33eDc,963
|
|
370
370
|
flock/workflow/temporal_setup.py,sha256=VWBgmBgfTBjwM5ruS_dVpA5AVxx6EZ7oFPGw4j3m0l0,1091
|
|
371
371
|
flock/workflow/workflow.py,sha256=I9MryXW_bqYVTHx-nl2epbTqeRy27CAWHHA7ZZA0nAk,1696
|
|
372
|
-
flock_core-0.2.
|
|
373
|
-
flock_core-0.2.
|
|
374
|
-
flock_core-0.2.
|
|
375
|
-
flock_core-0.2.
|
|
372
|
+
flock_core-0.2.3.dist-info/METADATA,sha256=cgqdCwNfYyRzXuhttnph9L7L87YOolVUDh6PYRHCbJ4,11129
|
|
373
|
+
flock_core-0.2.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
374
|
+
flock_core-0.2.3.dist-info/licenses/LICENSE,sha256=iYEqWy0wjULzM9GAERaybP4LBiPeu7Z1NEliLUdJKSc,1072
|
|
375
|
+
flock_core-0.2.3.dist-info/RECORD,,
|
flock/agents/__init__.py
DELETED
|
File without changes
|
flock/agents/batch_agent.py
DELETED
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
import asyncio
|
|
2
|
-
import uuid
|
|
3
|
-
from typing import Any
|
|
4
|
-
|
|
5
|
-
from pydantic import Field
|
|
6
|
-
|
|
7
|
-
from flock.core.context.context import FlockContext
|
|
8
|
-
from flock.core.flock_agent import FlockAgent
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class BatchAgent(FlockAgent):
|
|
12
|
-
"""A DeclarativeAgent that processes an iterable input in batches.
|
|
13
|
-
|
|
14
|
-
Additional Attributes:
|
|
15
|
-
iter_input: The key in the FlockContext that holds the iterable (a list).
|
|
16
|
-
batch_site: The number of items per batch.
|
|
17
|
-
|
|
18
|
-
For each batch, the agent's input dictionary is built from the FlockContext with the
|
|
19
|
-
value for the iter_input key overridden by the current batch. The outputs across batches
|
|
20
|
-
are then aggregated.
|
|
21
|
-
"""
|
|
22
|
-
|
|
23
|
-
iter_input: str = Field(
|
|
24
|
-
default="",
|
|
25
|
-
description="Key of the iterable input (must be a list in the FlockContext)",
|
|
26
|
-
)
|
|
27
|
-
batch_size: int = Field(
|
|
28
|
-
default=1, description="Batch size (number of items per batch)"
|
|
29
|
-
)
|
|
30
|
-
|
|
31
|
-
async def run(self, context: FlockContext) -> dict:
|
|
32
|
-
"""Run the BatchAgent locally by partitioning the iterable and aggregating the results."""
|
|
33
|
-
try:
|
|
34
|
-
iterable = context.get_variable(self.iter_input)
|
|
35
|
-
if not isinstance(iterable, list):
|
|
36
|
-
error_msg = (
|
|
37
|
-
f"Expected a list for key '{self.iter_input}' in context."
|
|
38
|
-
)
|
|
39
|
-
return {"error": error_msg}
|
|
40
|
-
|
|
41
|
-
# Partition the iterable into batches
|
|
42
|
-
batches: list[list[Any]] = [
|
|
43
|
-
iterable[i : i + self.batch_size]
|
|
44
|
-
for i in range(0, len(iterable), self.batch_size)
|
|
45
|
-
]
|
|
46
|
-
|
|
47
|
-
# Process batches
|
|
48
|
-
tasks = []
|
|
49
|
-
for batch in batches:
|
|
50
|
-
tasks.append(
|
|
51
|
-
self.evaluate(
|
|
52
|
-
context, input_overrides={self.iter_input: batch}
|
|
53
|
-
)
|
|
54
|
-
)
|
|
55
|
-
|
|
56
|
-
batch_results = await asyncio.gather(*tasks)
|
|
57
|
-
|
|
58
|
-
# Aggregate the outputs
|
|
59
|
-
output_keys = self._parse_keys(self.output)
|
|
60
|
-
aggregated = {key: [] for key in output_keys}
|
|
61
|
-
for res in batch_results:
|
|
62
|
-
for key in output_keys:
|
|
63
|
-
aggregated[key].append(res.get(key))
|
|
64
|
-
aggregated["batch_results"] = batch_results
|
|
65
|
-
return aggregated
|
|
66
|
-
|
|
67
|
-
except Exception:
|
|
68
|
-
raise
|
|
69
|
-
|
|
70
|
-
async def run_temporal(self, context: FlockContext) -> dict:
|
|
71
|
-
"""Run the BatchAgent via Temporal.
|
|
72
|
-
|
|
73
|
-
For each batch, the agent's evaluation is performed as a separate Temporal activity.
|
|
74
|
-
The results are then aggregated.
|
|
75
|
-
"""
|
|
76
|
-
try:
|
|
77
|
-
from temporalio.client import Client
|
|
78
|
-
|
|
79
|
-
from flock.workflow.agent_activities import (
|
|
80
|
-
run_declarative_agent_activity,
|
|
81
|
-
)
|
|
82
|
-
from flock.workflow.temporal_setup import run_activity
|
|
83
|
-
|
|
84
|
-
# Connect to Temporal
|
|
85
|
-
client = await Client.connect("localhost:7233", namespace="default")
|
|
86
|
-
|
|
87
|
-
# Validate and prepare input
|
|
88
|
-
iterable = context.get_variable(self.iter_input)
|
|
89
|
-
if not isinstance(iterable, list):
|
|
90
|
-
error_msg = (
|
|
91
|
-
f"Expected a list for key '{self.iter_input}' in context."
|
|
92
|
-
)
|
|
93
|
-
return {"error": error_msg}
|
|
94
|
-
|
|
95
|
-
# Partition into batches
|
|
96
|
-
batches: list[list[Any]] = [
|
|
97
|
-
iterable[i : i + self.batch_size]
|
|
98
|
-
for i in range(0, len(iterable), self.batch_size)
|
|
99
|
-
]
|
|
100
|
-
|
|
101
|
-
# Process batches
|
|
102
|
-
tasks = []
|
|
103
|
-
for batch in batches:
|
|
104
|
-
# Prepare context for this batch
|
|
105
|
-
new_state = context.state.copy()
|
|
106
|
-
new_state[self.iter_input] = batch
|
|
107
|
-
context_data = {
|
|
108
|
-
"state": new_state,
|
|
109
|
-
"history": [], # you might choose to pass along history if needed
|
|
110
|
-
"agent_definitions": [],
|
|
111
|
-
}
|
|
112
|
-
agent_data = self.dict()
|
|
113
|
-
task_id = f"{self.name}_{uuid.uuid4().hex[:4]}"
|
|
114
|
-
|
|
115
|
-
# Create temporal activity task
|
|
116
|
-
tasks.append(
|
|
117
|
-
run_activity(
|
|
118
|
-
client,
|
|
119
|
-
task_id,
|
|
120
|
-
run_declarative_agent_activity,
|
|
121
|
-
{
|
|
122
|
-
"agent_data": agent_data,
|
|
123
|
-
"context_data": context_data,
|
|
124
|
-
},
|
|
125
|
-
)
|
|
126
|
-
)
|
|
127
|
-
|
|
128
|
-
batch_results = await asyncio.gather(*tasks)
|
|
129
|
-
|
|
130
|
-
# Aggregate the outputs
|
|
131
|
-
output_keys = self._parse_keys(self.output)
|
|
132
|
-
aggregated = {key: [] for key in output_keys}
|
|
133
|
-
for res in batch_results:
|
|
134
|
-
for key in output_keys:
|
|
135
|
-
aggregated[key].append(res.get(key))
|
|
136
|
-
aggregated["batch_results"] = batch_results
|
|
137
|
-
return aggregated
|
|
138
|
-
|
|
139
|
-
except Exception:
|
|
140
|
-
raise
|
flock/agents/loop_agent.py
DELETED
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
from collections.abc import Callable
|
|
2
|
-
from typing import Any
|
|
3
|
-
|
|
4
|
-
from pydantic import Field
|
|
5
|
-
|
|
6
|
-
from flock.core.context.context import FlockContext
|
|
7
|
-
from flock.core.flock_agent import FlockAgent
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
class LoopAgent(FlockAgent):
|
|
11
|
-
"""An agent that executes its logic in a loop until a termination condition is met.
|
|
12
|
-
|
|
13
|
-
Attributes:
|
|
14
|
-
input: Input domain for the agent
|
|
15
|
-
output: Output types for the agent
|
|
16
|
-
tools: Tools the agent is allowed to use
|
|
17
|
-
max_iterations: Maximum number of iterations before forced termination
|
|
18
|
-
termination_condition: Optional callable that determines when to stop the loop
|
|
19
|
-
"""
|
|
20
|
-
|
|
21
|
-
input: str = Field(default="", description="Input domain for the agent")
|
|
22
|
-
output: str = Field(default="", description="Output types for the agent")
|
|
23
|
-
tools: list[Callable] | None = Field(default=None, description="Tools the agent is allowed to use")
|
|
24
|
-
max_iterations: int = Field(default=10, description="Maximum number of iterations")
|
|
25
|
-
termination_condition: Callable[[dict[str, Any]], bool] | None = Field(
|
|
26
|
-
default=None, description="Optional function to determine loop termination"
|
|
27
|
-
)
|
|
28
|
-
|
|
29
|
-
async def _process_iteration(self, context: FlockContext, iteration: int) -> dict[str, Any]:
|
|
30
|
-
"""Process a single iteration of the loop."""
|
|
31
|
-
try:
|
|
32
|
-
# Here you would implement the actual iteration logic
|
|
33
|
-
# For now, we'll just return a simple result
|
|
34
|
-
return {"iteration": iteration, "status": "completed"}
|
|
35
|
-
except Exception:
|
|
36
|
-
raise
|
|
37
|
-
|
|
38
|
-
def _should_continue(self, result: dict[str, Any], iteration: int) -> bool:
|
|
39
|
-
"""Determine if the loop should continue."""
|
|
40
|
-
if iteration >= self.max_iterations:
|
|
41
|
-
return False
|
|
42
|
-
|
|
43
|
-
if self.termination_condition:
|
|
44
|
-
should_terminate = self.termination_condition(result)
|
|
45
|
-
return not should_terminate
|
|
46
|
-
|
|
47
|
-
return True
|
|
48
|
-
|
|
49
|
-
async def run(self, context: FlockContext) -> dict[str, Any]:
|
|
50
|
-
"""Run the agent in a loop until the termination condition is met or max iterations reached."""
|
|
51
|
-
try:
|
|
52
|
-
results = []
|
|
53
|
-
iteration = 0
|
|
54
|
-
|
|
55
|
-
while True:
|
|
56
|
-
result = await self._process_iteration(context, iteration)
|
|
57
|
-
results.append(result)
|
|
58
|
-
|
|
59
|
-
# Check termination conditions
|
|
60
|
-
if not self._should_continue(result, iteration):
|
|
61
|
-
break
|
|
62
|
-
|
|
63
|
-
iteration += 1
|
|
64
|
-
|
|
65
|
-
return {
|
|
66
|
-
"iterations": iteration + 1,
|
|
67
|
-
"results": results,
|
|
68
|
-
"final_result": results[-1] if results else None,
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
except Exception:
|
|
72
|
-
raise
|
|
73
|
-
|
|
74
|
-
async def run_temporal(self, context: FlockContext) -> dict[str, Any]:
|
|
75
|
-
"""Run the loop agent via Temporal."""
|
|
76
|
-
try:
|
|
77
|
-
from temporalio.client import Client
|
|
78
|
-
|
|
79
|
-
from flock.workflow.agent_activities import run_agent_activity
|
|
80
|
-
from flock.workflow.temporal_setup import run_activity
|
|
81
|
-
|
|
82
|
-
client = await Client.connect("localhost:7233", namespace="default")
|
|
83
|
-
|
|
84
|
-
results = []
|
|
85
|
-
iteration = 0
|
|
86
|
-
|
|
87
|
-
while True:
|
|
88
|
-
# Process iteration as a temporal activity
|
|
89
|
-
context_data = {
|
|
90
|
-
"state": context.state,
|
|
91
|
-
"history": [record.__dict__ for record in context.history],
|
|
92
|
-
"agent_definitions": [definition.__dict__ for definition in context.agent_definitions],
|
|
93
|
-
}
|
|
94
|
-
agent_data = self.dict()
|
|
95
|
-
|
|
96
|
-
result = await run_activity(
|
|
97
|
-
client,
|
|
98
|
-
f"{self.name}_iteration_{iteration}",
|
|
99
|
-
run_agent_activity,
|
|
100
|
-
{"agent_data": agent_data, "context_data": context_data},
|
|
101
|
-
)
|
|
102
|
-
results.append(result)
|
|
103
|
-
|
|
104
|
-
# Check termination conditions
|
|
105
|
-
if not self._should_continue(result, iteration):
|
|
106
|
-
break
|
|
107
|
-
|
|
108
|
-
iteration += 1
|
|
109
|
-
|
|
110
|
-
return {
|
|
111
|
-
"iterations": iteration + 1,
|
|
112
|
-
"results": results,
|
|
113
|
-
"final_result": results[-1] if results else None,
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
except Exception:
|
|
117
|
-
raise
|