flock-core 0.2.15__py3-none-any.whl → 0.2.17__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/core/flock.py +9 -1
- flock/core/flock_agent.py +6 -2
- flock/core/logging/formatters/themed_formatter.py +7 -0
- flock/core/util/hydrator.py +306 -0
- flock_core-0.2.17.dist-info/METADATA +441 -0
- {flock_core-0.2.15.dist-info → flock_core-0.2.17.dist-info}/RECORD +9 -8
- flock_core-0.2.15.dist-info/METADATA +0 -332
- {flock_core-0.2.15.dist-info → flock_core-0.2.17.dist-info}/WHEEL +0 -0
- {flock_core-0.2.15.dist-info → flock_core-0.2.17.dist-info}/entry_points.txt +0 -0
- {flock_core-0.2.15.dist-info → flock_core-0.2.17.dist-info}/licenses/LICENSE +0 -0
flock/core/flock.py
CHANGED
|
@@ -146,10 +146,13 @@ class Flock:
|
|
|
146
146
|
context: FlockContext = None,
|
|
147
147
|
run_id: str = "",
|
|
148
148
|
box_result: bool = True,
|
|
149
|
+
agents: list[FlockAgent] = [],
|
|
149
150
|
) -> dict:
|
|
150
151
|
"""Entry point for running an agent system synchronously."""
|
|
151
152
|
return asyncio.run(
|
|
152
|
-
self.run_async(
|
|
153
|
+
self.run_async(
|
|
154
|
+
start_agent, input, context, run_id, box_result, agents
|
|
155
|
+
)
|
|
153
156
|
)
|
|
154
157
|
|
|
155
158
|
def save_to_file(
|
|
@@ -310,6 +313,7 @@ class Flock:
|
|
|
310
313
|
context: FlockContext = None,
|
|
311
314
|
run_id: str = "",
|
|
312
315
|
box_result: bool = True,
|
|
316
|
+
agents: list[FlockAgent] = [],
|
|
313
317
|
) -> dict:
|
|
314
318
|
"""Entry point for running an agent system asynchronously.
|
|
315
319
|
|
|
@@ -326,6 +330,7 @@ class Flock:
|
|
|
326
330
|
context (FlockContext, optional): A FlockContext instance to use. If not provided, a default context is used.
|
|
327
331
|
run_id (str, optional): A unique identifier for this run. If empty, one is generated automatically.
|
|
328
332
|
box_result (bool, optional): If True, wraps the output in a Box for nicer formatting. Defaults to True.
|
|
333
|
+
agents (list, optional): additional way to add agents to flock instead of add_agent
|
|
329
334
|
|
|
330
335
|
Returns:
|
|
331
336
|
dict: A dictionary containing the result of the agent workflow execution.
|
|
@@ -341,6 +346,9 @@ class Flock:
|
|
|
341
346
|
if hasattr(start_agent, "name")
|
|
342
347
|
else start_agent,
|
|
343
348
|
)
|
|
349
|
+
for agent in agents:
|
|
350
|
+
self.add_agent(agent)
|
|
351
|
+
|
|
344
352
|
if start_agent:
|
|
345
353
|
self.start_agent = start_agent
|
|
346
354
|
if input:
|
flock/core/flock_agent.py
CHANGED
|
@@ -69,6 +69,9 @@ class FlockAgentOutputConfig:
|
|
|
69
69
|
wait_for_input: bool = field(
|
|
70
70
|
default=False, metadata={"description": "Wait for input."}
|
|
71
71
|
)
|
|
72
|
+
write_to_file: bool = field(
|
|
73
|
+
default=False, metadata={"description": "Write to file."}
|
|
74
|
+
)
|
|
72
75
|
|
|
73
76
|
|
|
74
77
|
@dataclass
|
|
@@ -159,8 +162,8 @@ class FlockAgent(BaseModel, ABC, PromptParserMixin, DSPyIntegrationMixin):
|
|
|
159
162
|
"""
|
|
160
163
|
|
|
161
164
|
name: str = Field(..., description="Unique identifier for the agent.")
|
|
162
|
-
model: str = Field(
|
|
163
|
-
|
|
165
|
+
model: str | None = Field(
|
|
166
|
+
None, description="The model to use (e.g., 'openai/gpt-4o')."
|
|
164
167
|
)
|
|
165
168
|
description: str | Callable[..., str] = Field(
|
|
166
169
|
"", description="A human-readable description of the agent."
|
|
@@ -465,6 +468,7 @@ class FlockAgent(BaseModel, ABC, PromptParserMixin, DSPyIntegrationMixin):
|
|
|
465
468
|
self.output_config.max_length,
|
|
466
469
|
self.output_config.render_table,
|
|
467
470
|
self.output_config.wait_for_input,
|
|
471
|
+
self.output_config.write_to_file,
|
|
468
472
|
).display_result(result, self.name)
|
|
469
473
|
|
|
470
474
|
async def run_temporal(self, inputs: dict[str, Any]) -> dict[str, Any]:
|
|
@@ -416,6 +416,7 @@ class ThemedAgentResultFormatter:
|
|
|
416
416
|
max_length: int = -1,
|
|
417
417
|
render_table: bool = True,
|
|
418
418
|
wait_for_input: bool = False,
|
|
419
|
+
write_to_file: bool = False,
|
|
419
420
|
):
|
|
420
421
|
"""Initialize the formatter with a theme and optional max length."""
|
|
421
422
|
self.theme = theme
|
|
@@ -423,6 +424,7 @@ class ThemedAgentResultFormatter:
|
|
|
423
424
|
self.max_length = max_length
|
|
424
425
|
self.render_table = render_table
|
|
425
426
|
self.wait_for_input = wait_for_input
|
|
427
|
+
self.write_to_file = write_to_file
|
|
426
428
|
|
|
427
429
|
def format_result(
|
|
428
430
|
self,
|
|
@@ -482,6 +484,11 @@ class ThemedAgentResultFormatter:
|
|
|
482
484
|
|
|
483
485
|
s = pformat(result, highlight=False)
|
|
484
486
|
|
|
487
|
+
if self.write_to_file:
|
|
488
|
+
output_file = pathlib.Path(f"{agent_name}_result.txt")
|
|
489
|
+
with open(output_file, "w") as f:
|
|
490
|
+
f.write(s)
|
|
491
|
+
|
|
485
492
|
if self.render_table:
|
|
486
493
|
return Panel(
|
|
487
494
|
table,
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import json
|
|
3
|
+
from typing import get_origin, get_type_hints
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
# -----------------------------------------------------------
|
|
7
|
+
# Dummy FlockAgent for demonstration:
|
|
8
|
+
# -----------------------------------------------------------
|
|
9
|
+
class FlockAgent:
|
|
10
|
+
def __init__(self, name, input, output, model, description):
|
|
11
|
+
self.name = name
|
|
12
|
+
self.input = input
|
|
13
|
+
self.output = output
|
|
14
|
+
self.model = model
|
|
15
|
+
self.description = description
|
|
16
|
+
|
|
17
|
+
async def evaluate(self, data: dict) -> dict:
|
|
18
|
+
"""Pretend LLM call.
|
|
19
|
+
We'll parse self.output to see which keys we want,
|
|
20
|
+
then generate some placeholders for those keys.
|
|
21
|
+
"""
|
|
22
|
+
print(
|
|
23
|
+
f"[FlockAgent] Evaluate called for agent {self.name} with data: {data}"
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
# Very naive parse of output string: "title: str | desc, budget: int | desc, ..."
|
|
27
|
+
fields = []
|
|
28
|
+
for out_part in self.output.split(","):
|
|
29
|
+
out_part = out_part.strip()
|
|
30
|
+
# out_part might look like: "title: str | property of MyBlogPost"
|
|
31
|
+
if not out_part:
|
|
32
|
+
continue
|
|
33
|
+
field_name = out_part.split(":")[0].strip()
|
|
34
|
+
fields.append(field_name)
|
|
35
|
+
|
|
36
|
+
# We'll pretend the LLM returns either an integer for int fields or a string for others:
|
|
37
|
+
response = {}
|
|
38
|
+
for f in fields:
|
|
39
|
+
if " int" in self.output: # naive
|
|
40
|
+
response[f] = 42
|
|
41
|
+
else:
|
|
42
|
+
response[f] = f"Generated data for {f}"
|
|
43
|
+
return response
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
# -----------------------------------------------------------
|
|
47
|
+
# Optional: a decorator that marks a class as "flockclass"
|
|
48
|
+
# -----------------------------------------------------------
|
|
49
|
+
def flockclass(model: str):
|
|
50
|
+
def decorator(cls):
|
|
51
|
+
cls.__is_flockclass__ = True
|
|
52
|
+
cls.__flock_model__ = model
|
|
53
|
+
return cls
|
|
54
|
+
|
|
55
|
+
return decorator
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
# -----------------------------------------------------------
|
|
59
|
+
# Utility sets
|
|
60
|
+
# -----------------------------------------------------------
|
|
61
|
+
BASIC_TYPES = {str, int, float, bool}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
# -----------------------------------------------------------
|
|
65
|
+
# The main hydrator that can handle:
|
|
66
|
+
# - basic types (do nothing)
|
|
67
|
+
# - user-defined classes (auto-fill missing fields + recurse)
|
|
68
|
+
# - lists (ask LLM how many items to create + fill them)
|
|
69
|
+
# - dicts (ask LLM how many key->value pairs to create + fill them)
|
|
70
|
+
# -----------------------------------------------------------
|
|
71
|
+
def hydrate_object(obj, model="gpt-4", class_name=None):
|
|
72
|
+
"""Recursively hydrates the object in-place,
|
|
73
|
+
calling an LLM for missing fields or structure.
|
|
74
|
+
"""
|
|
75
|
+
# 1) If None or basic, do nothing
|
|
76
|
+
if obj is None or isinstance(obj, (str, int, float, bool)):
|
|
77
|
+
return
|
|
78
|
+
|
|
79
|
+
# 2) If list, check if it is empty => ask the LLM how many items we need
|
|
80
|
+
if isinstance(obj, list):
|
|
81
|
+
if len(obj) == 0:
|
|
82
|
+
# We'll do a single LLM call to decide how many items to put in:
|
|
83
|
+
# In real usage, you'd put a more robust prompt.
|
|
84
|
+
list_agent = FlockAgent(
|
|
85
|
+
name=f"{class_name or 'list'}Generator",
|
|
86
|
+
input="Generate number of items for this list",
|
|
87
|
+
output="count: int | number of items to create",
|
|
88
|
+
model=model,
|
|
89
|
+
description="Agent that decides how many items to create in a list.",
|
|
90
|
+
)
|
|
91
|
+
result = asyncio.run(list_agent.evaluate({}))
|
|
92
|
+
num_items = result.get("count", 0)
|
|
93
|
+
# We'll assume the list should hold some type T.
|
|
94
|
+
# But in Python, we rarely store that info in the runtime.
|
|
95
|
+
# For demonstration, let's just store dummy strings or we can guess "object".
|
|
96
|
+
for i in range(num_items):
|
|
97
|
+
# For demonstration, create a simple string or dict
|
|
98
|
+
# If you want a typed approach, you'll need additional metadata or pass in generics
|
|
99
|
+
item = f"Generated item {i + 1}"
|
|
100
|
+
obj.append(item)
|
|
101
|
+
|
|
102
|
+
# Now recursively fill each item
|
|
103
|
+
for i in range(len(obj)):
|
|
104
|
+
hydrate_object(
|
|
105
|
+
obj[i],
|
|
106
|
+
model=model,
|
|
107
|
+
class_name=f"{class_name or 'list'}[item={i}]",
|
|
108
|
+
)
|
|
109
|
+
return
|
|
110
|
+
|
|
111
|
+
# 3) If dict, check if it is empty => ask LLM for which keys to create
|
|
112
|
+
if isinstance(obj, dict):
|
|
113
|
+
if len(obj) == 0:
|
|
114
|
+
# We'll do a single LLM call that returns a list of keys
|
|
115
|
+
dict_agent = FlockAgent(
|
|
116
|
+
name=f"{class_name or 'dict'}Generator",
|
|
117
|
+
input="Generate keys for this dict",
|
|
118
|
+
output="keys: str | comma-separated list of keys to create",
|
|
119
|
+
model=model,
|
|
120
|
+
description="Agent that decides which keys to create in a dict.",
|
|
121
|
+
)
|
|
122
|
+
result = asyncio.run(dict_agent.evaluate({}))
|
|
123
|
+
keys_str = result.get("keys", "")
|
|
124
|
+
keys = [k.strip() for k in keys_str.split(",") if k.strip()]
|
|
125
|
+
|
|
126
|
+
# For demonstration, let's assume the dict holds sub-objects that we can fill further
|
|
127
|
+
# We'll create a plain dict or plain string for each key
|
|
128
|
+
for k in keys:
|
|
129
|
+
obj[k] = f"Placeholder for {k}"
|
|
130
|
+
|
|
131
|
+
# Now recursively fill each value
|
|
132
|
+
for key, val in obj.items():
|
|
133
|
+
hydrate_object(
|
|
134
|
+
val,
|
|
135
|
+
model=model,
|
|
136
|
+
class_name=f"{class_name or 'dict'}[key={key}]",
|
|
137
|
+
)
|
|
138
|
+
return
|
|
139
|
+
|
|
140
|
+
# 4) If it's a user-defined class with annotations, fill missing fields
|
|
141
|
+
cls = type(obj)
|
|
142
|
+
if hasattr(cls, "__annotations__"):
|
|
143
|
+
# If there's a model stored on the class, we can use that. Else fallback to the default
|
|
144
|
+
used_model = getattr(cls, "__flock_model__", model)
|
|
145
|
+
|
|
146
|
+
# Figure out which fields are missing or None
|
|
147
|
+
type_hints = get_type_hints(cls)
|
|
148
|
+
missing_basic_fields = []
|
|
149
|
+
complex_fields = []
|
|
150
|
+
for field_name, field_type in type_hints.items():
|
|
151
|
+
value = getattr(obj, field_name, None)
|
|
152
|
+
if value is None:
|
|
153
|
+
# It's missing. See if it's a basic type or complex
|
|
154
|
+
if _is_basic_type(field_type):
|
|
155
|
+
missing_basic_fields.append(field_name)
|
|
156
|
+
else:
|
|
157
|
+
complex_fields.append(field_name)
|
|
158
|
+
else:
|
|
159
|
+
# Already has some value, but if it's a complex type, we should recurse
|
|
160
|
+
if not _is_basic_type(field_type):
|
|
161
|
+
complex_fields.append(field_name)
|
|
162
|
+
|
|
163
|
+
# If we have missing basic fields, do a single LLM call to fill them
|
|
164
|
+
if missing_basic_fields:
|
|
165
|
+
input_str = (
|
|
166
|
+
f"Existing data: {json.dumps(obj.__dict__, default=str)}"
|
|
167
|
+
)
|
|
168
|
+
output_fields_str = []
|
|
169
|
+
for bf in missing_basic_fields:
|
|
170
|
+
bf_type = type_hints[bf]
|
|
171
|
+
bf_type_name = (
|
|
172
|
+
bf_type.__name__
|
|
173
|
+
if hasattr(bf_type, "__name__")
|
|
174
|
+
else str(bf_type)
|
|
175
|
+
)
|
|
176
|
+
desc = f"property of a class named {cls.__name__}"
|
|
177
|
+
output_fields_str.append(f"{bf}: {bf_type_name} | {desc}")
|
|
178
|
+
|
|
179
|
+
agent = FlockAgent(
|
|
180
|
+
name=cls.__name__,
|
|
181
|
+
input=input_str,
|
|
182
|
+
output=", ".join(output_fields_str),
|
|
183
|
+
model=used_model,
|
|
184
|
+
description=f"Agent for {cls.__name__}",
|
|
185
|
+
)
|
|
186
|
+
result = asyncio.run(agent.evaluate(obj.__dict__))
|
|
187
|
+
for bf in missing_basic_fields:
|
|
188
|
+
if bf in result:
|
|
189
|
+
setattr(obj, bf, result[bf])
|
|
190
|
+
|
|
191
|
+
# For each "complex" field, instantiate if None + recurse
|
|
192
|
+
for cf in complex_fields:
|
|
193
|
+
cf_value = getattr(obj, cf, None)
|
|
194
|
+
cf_type = type_hints[cf]
|
|
195
|
+
|
|
196
|
+
if cf_value is None:
|
|
197
|
+
# We need to create something of the appropriate type
|
|
198
|
+
new_val = _instantiate_type(cf_type)
|
|
199
|
+
setattr(obj, cf, new_val)
|
|
200
|
+
hydrate_object(
|
|
201
|
+
new_val, model=used_model, class_name=cf_type.__name__
|
|
202
|
+
)
|
|
203
|
+
else:
|
|
204
|
+
# Recurse into it
|
|
205
|
+
hydrate_object(
|
|
206
|
+
cf_value, model=used_model, class_name=cf_type.__name__
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
else:
|
|
210
|
+
# It's some Python object with no annotations -> do nothing
|
|
211
|
+
pass
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
# -----------------------------------------------------------
|
|
215
|
+
# Helper: is a type "basic"?
|
|
216
|
+
# -----------------------------------------------------------
|
|
217
|
+
def _is_basic_type(t):
|
|
218
|
+
if t in BASIC_TYPES:
|
|
219
|
+
return True
|
|
220
|
+
# You may want to check for Optionals or Unions
|
|
221
|
+
# e.g., if get_origin(t) == Union, parse that, etc.
|
|
222
|
+
return False
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
# -----------------------------------------------------------
|
|
226
|
+
# Helper: instantiate a type (list, dict, or user-defined)
|
|
227
|
+
# -----------------------------------------------------------
|
|
228
|
+
def _instantiate_type(t):
|
|
229
|
+
origin = get_origin(t)
|
|
230
|
+
if origin is list:
|
|
231
|
+
return []
|
|
232
|
+
if origin is dict:
|
|
233
|
+
return {}
|
|
234
|
+
|
|
235
|
+
# If it's a built-in basic type, return None (we fill it from LLM).
|
|
236
|
+
if t in BASIC_TYPES:
|
|
237
|
+
return None
|
|
238
|
+
|
|
239
|
+
# If it's a user-defined class
|
|
240
|
+
if isinstance(t, type):
|
|
241
|
+
try:
|
|
242
|
+
# Attempt parameterless init
|
|
243
|
+
return t()
|
|
244
|
+
except:
|
|
245
|
+
# Or try __new__
|
|
246
|
+
try:
|
|
247
|
+
return t.__new__(t)
|
|
248
|
+
except:
|
|
249
|
+
return None
|
|
250
|
+
return None
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
# -----------------------------------------------------------
|
|
254
|
+
# Example classes
|
|
255
|
+
# -----------------------------------------------------------
|
|
256
|
+
@flockclass("gpt-4")
|
|
257
|
+
class LongContent:
|
|
258
|
+
title: str
|
|
259
|
+
content: str
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
@flockclass("gpt-4")
|
|
263
|
+
class MyBlogPost:
|
|
264
|
+
title: str
|
|
265
|
+
headers: str
|
|
266
|
+
# We'll have a dict of key->LongContent
|
|
267
|
+
content: dict[str, LongContent]
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
@flockclass("gpt-4")
|
|
271
|
+
class MyProjectPlan:
|
|
272
|
+
project_idea: str
|
|
273
|
+
budget: int
|
|
274
|
+
title: str
|
|
275
|
+
content: MyBlogPost
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
# -----------------------------------------------------------
|
|
279
|
+
# Demo
|
|
280
|
+
# -----------------------------------------------------------
|
|
281
|
+
if __name__ == "__main__":
|
|
282
|
+
plan = MyProjectPlan()
|
|
283
|
+
plan.project_idea = "a declarative agent framework"
|
|
284
|
+
plan.budget = 100000
|
|
285
|
+
|
|
286
|
+
# content is None by default, so the hydrator will create MyBlogPost
|
|
287
|
+
# and fill it in. MyBlogPost.content is a dict[str, LongContent],
|
|
288
|
+
# also None -> becomes an empty dict -> we let the LLM decide the keys.
|
|
289
|
+
|
|
290
|
+
hydrate_object(plan, model="gpt-4", class_name="MyProjectPlan")
|
|
291
|
+
|
|
292
|
+
print("\n--- MyProjectPlan hydrated ---")
|
|
293
|
+
for k, v in plan.__dict__.items():
|
|
294
|
+
print(f"{k} = {v}")
|
|
295
|
+
if plan.content:
|
|
296
|
+
print("\n--- MyBlogPost hydrated ---")
|
|
297
|
+
for k, v in plan.content.__dict__.items():
|
|
298
|
+
print(f" {k} = {v}")
|
|
299
|
+
if k == "content" and isinstance(v, dict):
|
|
300
|
+
print(" (keys) =", list(v.keys()))
|
|
301
|
+
for sub_k, sub_val in v.items():
|
|
302
|
+
print(f" {sub_k} -> {sub_val}")
|
|
303
|
+
if isinstance(sub_val, LongContent):
|
|
304
|
+
print(
|
|
305
|
+
f" -> LongContent fields: {sub_val.__dict__}"
|
|
306
|
+
)
|
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: flock-core
|
|
3
|
+
Version: 0.2.17
|
|
4
|
+
Summary: Declarative LLM Orchestration at Scale
|
|
5
|
+
Author-email: Andre Ratzenberger <andre.ratzenberger@whiteduck.de>
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
8
|
+
Classifier: Operating System :: OS Independent
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Requires-Python: >=3.10
|
|
11
|
+
Requires-Dist: cloudpickle>=3.1.1
|
|
12
|
+
Requires-Dist: devtools>=0.12.2
|
|
13
|
+
Requires-Dist: dspy==2.5.42
|
|
14
|
+
Requires-Dist: duckduckgo-search>=7.3.2
|
|
15
|
+
Requires-Dist: httpx>=0.28.1
|
|
16
|
+
Requires-Dist: loguru>=0.7.3
|
|
17
|
+
Requires-Dist: msgpack>=1.1.0
|
|
18
|
+
Requires-Dist: opentelemetry-api>=1.30.0
|
|
19
|
+
Requires-Dist: opentelemetry-exporter-jaeger-proto-grpc>=1.21.0
|
|
20
|
+
Requires-Dist: opentelemetry-exporter-jaeger>=1.21.0
|
|
21
|
+
Requires-Dist: opentelemetry-exporter-otlp>=1.30.0
|
|
22
|
+
Requires-Dist: opentelemetry-instrumentation-logging>=0.51b0
|
|
23
|
+
Requires-Dist: opentelemetry-sdk>=1.30.0
|
|
24
|
+
Requires-Dist: pydantic>=2.10.5
|
|
25
|
+
Requires-Dist: python-box>=7.3.2
|
|
26
|
+
Requires-Dist: python-decouple>=3.8
|
|
27
|
+
Requires-Dist: questionary>=2.1.0
|
|
28
|
+
Requires-Dist: rich>=13.9.4
|
|
29
|
+
Requires-Dist: temporalio>=1.9.0
|
|
30
|
+
Requires-Dist: toml>=0.10.2
|
|
31
|
+
Provides-Extra: all-tools
|
|
32
|
+
Requires-Dist: docling>=2.18.0; extra == 'all-tools'
|
|
33
|
+
Requires-Dist: markdownify>=0.14.1; extra == 'all-tools'
|
|
34
|
+
Requires-Dist: tavily-python>=0.5.0; extra == 'all-tools'
|
|
35
|
+
Provides-Extra: tools
|
|
36
|
+
Requires-Dist: markdownify>=0.14.1; extra == 'tools'
|
|
37
|
+
Requires-Dist: tavily-python>=0.5.0; extra == 'tools'
|
|
38
|
+
Description-Content-Type: text/markdown
|
|
39
|
+
|
|
40
|
+
<p align="center">
|
|
41
|
+
<img src="docs/img/flock.png" width="600"><br>
|
|
42
|
+
<img alt="Dynamic TOML Badge" src="https://img.shields.io/badge/dynamic/toml?url=https%3A%2F%2Fraw.githubusercontent.com%2Fwhiteducksoftware%2Fflock%2Frefs%2Fheads%2Fmaster%2Fpyproject.toml&query=%24.project.version&style=for-the-badge&logo=pypi&label=pip%20version">
|
|
43
|
+
<a href="https://www.linkedin.com/company/whiteduck" target="_blank"><img alt="LinkedIn" src="https://img.shields.io/badge/linkedin-%230077B5.svg?style=for-the-badge&logo=linkedin&logoColor=white&label=whiteduck"></a>
|
|
44
|
+
<a href="https://bsky.app/profile/whiteduck-gmbh.bsky.social" target="_blank"><img alt="Bluesky" src="https://img.shields.io/badge/bluesky-Follow-blue?style=for-the-badge&logo=bluesky&logoColor=%23fff&color=%23333&labelColor=%230285FF&label=whiteduck-gmbh"></a>
|
|
45
|
+
|
|
46
|
+
## Overview
|
|
47
|
+
|
|
48
|
+
Flock is a framework for orchestrating LLM-powered agents. It leverages a **declarative approach** where you simply specify what each agent needs as input and what it produces as output—without having to write lengthy, brittle prompts. Under the hood, Flock transforms these declarations into robust workflows, using cutting-edge components such as Temporal and DSPy to handle fault tolerance, state management, and error recovery.
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
| Traditional Agent Frameworks 🙃 | Flock 🐤🐧🐓🦆 |
|
|
53
|
+
|------------------------------------------|--------------------------------------------------------------|
|
|
54
|
+
| 🤖 **Complex Prompt Engineering** | 📝 **Declarative Agent Definitions** |
|
|
55
|
+
| • Lengthy, brittle prompts | • Clear, concise input/output declarations |
|
|
56
|
+
| • Hard-to-tune and adapt | • No need for manual prompt engineering |
|
|
57
|
+
| | |
|
|
58
|
+
| 💥 **Fragile Execution** | ⚡ **Robust & Scalable** |
|
|
59
|
+
| • Single failure can break the system | • Fault-tolerant with built-in retries and error handling |
|
|
60
|
+
| • Difficult to monitor and recover | • Automatic recovery via Temporal workflow integration |
|
|
61
|
+
| | |
|
|
62
|
+
| 🏗️ **Rigid Workflows** | 🔄 **Flexible Orchestration** |
|
|
63
|
+
| • Limited adaptability | • Dynamic agent chaining and hand-offs |
|
|
64
|
+
| • Hard to scale and parallelize | • Modular, concurrent, and batch processing |
|
|
65
|
+
| | |
|
|
66
|
+
|
|
67
|
+
## Video Demonstration
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
https://github.com/user-attachments/assets/bdab4786-d532-459f-806a-024727164dcc
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
## Key Innovations
|
|
76
|
+
|
|
77
|
+
- **Declarative Agent System:**
|
|
78
|
+
When you order a pizza at your favorite place, you just tell them what pizza you want, not the 30 steps to get there!
|
|
79
|
+
And thanks to an invention called LLMs, we now have the technology to come up with those 30 steps.
|
|
80
|
+
|
|
81
|
+
Flock takes advantage of that.
|
|
82
|
+
|
|
83
|
+
Define agents by declaring their input/output interfaces (with type hints and human-readable descriptions) using a concise syntax.
|
|
84
|
+
The framework automatically extracts type and description details, builds precise prompts, and configures the underlying LLM.
|
|
85
|
+
|
|
86
|
+
Testing becomes quite simple as well. You got the pizza you ordered? Passed ✅
|
|
87
|
+
|
|
88
|
+
- **Type Safety and Clear Contracts:**
|
|
89
|
+
Agents are implemented as Pydantic models. This provides automatic JSON serialization/deserialization, strong typing, and an explicit contract for inputs and outputs. Testing, validation, and integration become straightforward.
|
|
90
|
+
|
|
91
|
+
- **Unparalleled Flexibility:**
|
|
92
|
+
Each agent (via the new `FlockAgent` base class) supports lifecycle hooks such as `initialize()`, `terminate()`, `evaluate()`, and `on_error()`. This ensures that agents can perform setup, cleanup, and robust error handling—all without cluttering the main business logic. Everything is overridable or lets you provide your own callables per callback. We mean `everything` quite literally. Except for the agent name, literally every property of an agent can be set to a callable, leading to highly dynamic and capable agents.
|
|
93
|
+
|
|
94
|
+
- **Fault Tolerance & Temporal Integration:**
|
|
95
|
+
Flock is built with production readiness in mind. By integrating with Temporal, your agent workflows enjoy automatic retries, durable state management, and resilience against failures. This means that a single agent crash won't bring down your entire system.
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
<p align="center">
|
|
99
|
+
<img src="docs/img/flock_cli.png" width="200"><br>
|
|
100
|
+
|
|
101
|
+
## Examples
|
|
102
|
+
|
|
103
|
+
Let's showcase easy to understand examples to give you an idea what flock offers!
|
|
104
|
+
All examples and/or similar examples can be found in the examples folder!
|
|
105
|
+
|
|
106
|
+
### Hello Flock!
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
Let's start the most simple way possible 🚀
|
|
110
|
+
|
|
111
|
+
```python
|
|
112
|
+
|
|
113
|
+
from flock.core import Flock, FlockAgent
|
|
114
|
+
|
|
115
|
+
MODEL = "openai/gpt-4o"
|
|
116
|
+
|
|
117
|
+
flock = Flock(model=MODEL, local_debug=True)
|
|
118
|
+
|
|
119
|
+
bloggy = FlockAgent(
|
|
120
|
+
name="bloggy",
|
|
121
|
+
input="blog_idea",
|
|
122
|
+
output="funny_blog_title, blog_headers"
|
|
123
|
+
)
|
|
124
|
+
flock.add_agent(bloggy)
|
|
125
|
+
|
|
126
|
+
result = flock.run(
|
|
127
|
+
start_agent=bloggy,
|
|
128
|
+
input={"blog_idea": "A blog about cats"}
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
With almost no boilerplate needed, getting your first agent to run is as easy as cake!
|
|
134
|
+
|
|
135
|
+
`bloggy` takes in a `blog_idea` to produce a `funny_blog_title` and `blog_headers`. That is all!
|
|
136
|
+
|
|
137
|
+
Flock does take care of the rest, which frees you from needing to write paragraphs of text.
|
|
138
|
+
You might think abstracting prompting like this means less control - but nope! Quite the contrary, it'll increase your control over it!
|
|
139
|
+
|
|
140
|
+
When we let `bloggy` loose in the flock:
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
```python
|
|
144
|
+
{
|
|
145
|
+
'funny_blog_title': '"Whisker Wonders: The Purr-fect Guide to Cat-tastrophes and Feline Follies"',
|
|
146
|
+
'blog_headers': (
|
|
147
|
+
'1. "The Cat\'s Meow: Understanding Your Feline\'s Language"\n'
|
|
148
|
+
'2. "Paws and Reflect: The Secret Life of Cats"\n'
|
|
149
|
+
'3. "Fur Real: Debunking Myths About Our Furry Friends"\n'
|
|
150
|
+
'4. "Claw-some Adventures: How to Entertain Your Indoor Cat"\n'
|
|
151
|
+
'5. "Cat-astrophic Cuteness: Why We Can\'t Resist Those Whiskers"\n'
|
|
152
|
+
'6. "Tail Tales: The History of Cats and Their Human Companions"\n'
|
|
153
|
+
'7. "Purr-sonality Plus: What Your Cat\'s Behavior Says About Them"\n'
|
|
154
|
+
'8. "Kitty Conundrums: Solving Common Cat Problems with Humor"'
|
|
155
|
+
),
|
|
156
|
+
'blog_idea': 'A blog about cats',
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Look at that! A real Python object with fields exactly as we defined them in the agent.
|
|
161
|
+
No need to mess around with parsing or post-processing! 🎉
|
|
162
|
+
|
|
163
|
+
### It's not my type
|
|
164
|
+
|
|
165
|
+
You probably noticed that your headers aren't a real Python list, but you need one for your downstream task. Flock got you! Just sprinkle some type hints in your agent definition! ✨
|
|
166
|
+
|
|
167
|
+
```python
|
|
168
|
+
|
|
169
|
+
from flock.core import Flock, FlockAgent
|
|
170
|
+
|
|
171
|
+
MODEL = "openai/gpt-4o"
|
|
172
|
+
|
|
173
|
+
flock = Flock(model=MODEL, local_debug=True)
|
|
174
|
+
|
|
175
|
+
bloggy = FlockAgent(
|
|
176
|
+
name="bloggy",
|
|
177
|
+
input="blog_idea",
|
|
178
|
+
output="funny_blog_title, blog_headers: list[str]"
|
|
179
|
+
)
|
|
180
|
+
flock.add_agent(bloggy)
|
|
181
|
+
|
|
182
|
+
result = flock.run(
|
|
183
|
+
start_agent=bloggy,
|
|
184
|
+
input={"blog_idea": "A blog about cats"}
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
Et voila! Now you get:
|
|
190
|
+
|
|
191
|
+
```python
|
|
192
|
+
{
|
|
193
|
+
'funny_blog_title': '"Whisker Me This: The Purr-fect Guide to Cat-tastic Adventures"',
|
|
194
|
+
'blog_headers': [
|
|
195
|
+
"The Cat's Out of the Bag: Understanding Feline Behavior",
|
|
196
|
+
'Paws and Reflect: The Secret Life of Cats',
|
|
197
|
+
'Feline Fine: Health Tips for Your Kitty',
|
|
198
|
+
"Cat-astrophic Cuteness: Why We Can't Resist Them",
|
|
199
|
+
'Meow-sic to Your Ears: Communicating with Your Cat',
|
|
200
|
+
'Claw-some Toys and Games: Keeping Your Cat Entertained',
|
|
201
|
+
'The Tail End: Myths and Facts About Cats',
|
|
202
|
+
],
|
|
203
|
+
'blog_idea': 'A blog about cats',
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### Being pydantic
|
|
209
|
+
|
|
210
|
+
That's not enough for you, since you already got your data classes defined and don't want to redefine them again for some agents?
|
|
211
|
+
|
|
212
|
+
Also got some hard constraints, like the title needs to be in ALL CAPS? 🔥
|
|
213
|
+
|
|
214
|
+
Check this out:
|
|
215
|
+
|
|
216
|
+
```python
|
|
217
|
+
from pydantic import BaseModel, Field
|
|
218
|
+
|
|
219
|
+
class BlogSection(BaseModel):
|
|
220
|
+
header: str
|
|
221
|
+
content: str
|
|
222
|
+
|
|
223
|
+
class MyBlog(BaseModel):
|
|
224
|
+
funny_blog_title: str = Field(description="The funny blog title in all caps")
|
|
225
|
+
blog_sections: list[BlogSection]
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
Since flock is bein' pedantic about pydantic, you can just use your pydantic models like you would use type hints:
|
|
229
|
+
|
|
230
|
+
```python
|
|
231
|
+
bloggy = FlockAgent(
|
|
232
|
+
name="bloggy",
|
|
233
|
+
input="blog_idea",
|
|
234
|
+
output="blog: MyBlog",
|
|
235
|
+
)
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
And BAM! Your finished data model filled up to the brim with data! 🎊
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
```python
|
|
242
|
+
{
|
|
243
|
+
'blog': MyBlog(
|
|
244
|
+
funny_blog_title='THE PURR-FECT LIFE: CATS AND THEIR QUIRKY ANTICS',
|
|
245
|
+
blog_sections=[
|
|
246
|
+
BlogSection(
|
|
247
|
+
header='Introduction to the Feline World',
|
|
248
|
+
content=(
|
|
249
|
+
'Cats have been our companions for thousands of years, yet they remain as mysterious and intriguin'
|
|
250
|
+
'g as ever. From their graceful movements to their independent nature, cats have a unique charm th'
|
|
251
|
+
"at captivates us. In this blog, we'll explore the fascinating world of cats and their quirky anti"
|
|
252
|
+
'cs that make them the purr-fect pets.'
|
|
253
|
+
),
|
|
254
|
+
),
|
|
255
|
+
BlogSection(
|
|
256
|
+
header='The Mysterious Ways of Cats',
|
|
257
|
+
content=(
|
|
258
|
+
'Ever wonder why your cat suddenly sprints across the room at 3 AM or stares at a blank wall for h'
|
|
259
|
+
'ours? Cats are known for their mysterious behaviors that often leave us scratching our heads. The'
|
|
260
|
+
'se antics are not just random; they are deeply rooted in their instincts and natural behaviors. L'
|
|
261
|
+
"et's dive into some of the most common and puzzling cat behaviors."
|
|
262
|
+
),
|
|
263
|
+
),
|
|
264
|
+
BlogSection(
|
|
265
|
+
header="The Art of Napping: A Cat's Guide",
|
|
266
|
+
content=(
|
|
267
|
+
"Cats are the ultimate nappers, spending up to 16 hours a day snoozing. But there's more to a catn"
|
|
268
|
+
'ap than meets the eye. Cats have perfected the art of napping, and each nap serves a purpose, whe'
|
|
269
|
+
"ther it's a quick power nap or a deep sleep. Learn how cats choose their napping spots and the sc"
|
|
270
|
+
'ience behind their sleep patterns.'
|
|
271
|
+
),
|
|
272
|
+
),
|
|
273
|
+
BlogSection(
|
|
274
|
+
header='The Great Cat Conspiracy: Do They Really Rule the World?',
|
|
275
|
+
content=(
|
|
276
|
+
"It's a well-known fact among cat owners that cats secretly rule the world. With their ability to "
|
|
277
|
+
"manipulate humans into providing endless treats and belly rubs, it's no wonder they have us wrapp"
|
|
278
|
+
"ed around their little paws. Explore the humorous side of cat ownership and the 'conspiracy' theo"
|
|
279
|
+
'ries that suggest cats are the true overlords of our homes.'
|
|
280
|
+
),
|
|
281
|
+
),
|
|
282
|
+
BlogSection(
|
|
283
|
+
header='Conclusion: Why We Love Cats',
|
|
284
|
+
content=(
|
|
285
|
+
'Despite their quirks and sometimes aloof nature, cats have a special place in our hearts. Their c'
|
|
286
|
+
"ompanionship, playful antics, and soothing purrs bring joy and comfort to our lives. Whether you'"
|
|
287
|
+
"re a lifelong cat lover or a new cat parent, there's no denying the unique bond we share with our"
|
|
288
|
+
" feline friends. So, here's to the purr-fect life with cats!"
|
|
289
|
+
),
|
|
290
|
+
),
|
|
291
|
+
],
|
|
292
|
+
),
|
|
293
|
+
'blog_idea': 'A blog about cats',
|
|
294
|
+
}
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
So far we've barely scratched the surface of what flock has to offer, and we're currently hard at work building up the documentation for all the other super cool features Flock has up its sleeve! Stay tuned! 🚀
|
|
298
|
+
|
|
299
|
+
## Temporal Workflow Integration
|
|
300
|
+
|
|
301
|
+
Flock supports execution on Temporal, ensuring robust, fault-tolerant workflows:
|
|
302
|
+
|
|
303
|
+
- **Durability:** Persistent state management even in the case of failures.
|
|
304
|
+
- **Retries & Error Handling:** Automatic recovery via Temporal's built-in mechanisms.
|
|
305
|
+
- **Scalability:** Seamless orchestration of distributed agent workflows.
|
|
306
|
+
|
|
307
|
+
Documentation in progress!
|
|
308
|
+
|
|
309
|
+
## Architecture
|
|
310
|
+
|
|
311
|
+
Documentation in progress!
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
## Requirements
|
|
315
|
+
|
|
316
|
+
- Python 3.10+
|
|
317
|
+
- (Optional) Temporal server running locally for production-grade workflow features
|
|
318
|
+
- API keys for integrated services
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
recommended services
|
|
322
|
+
```bash
|
|
323
|
+
export OPENAI_API_KEY=sk-proj-
|
|
324
|
+
export TAVILY_API_KEY=tvly-
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
or in `.env`
|
|
328
|
+
|
|
329
|
+
For LLM interaction LiteLLM is getting used. Please refer to its documentation on how to easily use other models and/or provider.
|
|
330
|
+
|
|
331
|
+
https://docs.litellm.ai/docs/providers
|
|
332
|
+
|
|
333
|
+
## Installation
|
|
334
|
+
|
|
335
|
+
```bash
|
|
336
|
+
pip install flock-core
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
if you want to use the integrated tools
|
|
340
|
+
|
|
341
|
+
```bash
|
|
342
|
+
pip install flock-core[tools]
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
and for the docling tools
|
|
346
|
+
|
|
347
|
+
```bash
|
|
348
|
+
pip install flock-core[all-tools]
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
## Development
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
1. **Clone the Repository:**
|
|
355
|
+
|
|
356
|
+
```bash
|
|
357
|
+
git clone https://github.com/whiteducksoftware/flock
|
|
358
|
+
cd flock
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
2. **Create a Virtual Environment and sync all packages:**
|
|
362
|
+
|
|
363
|
+
```bash
|
|
364
|
+
uv sync --all-groups --all-extras
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
3. **Install local version of flock:**
|
|
368
|
+
|
|
369
|
+
```bash
|
|
370
|
+
uv build && uv pip install -e .
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
4. **Install Jaeger for telemetry**
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
docker run -d --name jaeger \
|
|
377
|
+
-e COLLECTOR_ZIPKIN_HTTP_PORT=9411 \
|
|
378
|
+
-p 5775:5775/udp \
|
|
379
|
+
-p 6831:6831/udp \
|
|
380
|
+
-p 6832:6832/udp \
|
|
381
|
+
-p 5778:5778 \
|
|
382
|
+
-p 16686:16686 \
|
|
383
|
+
-p 14268:14268 \
|
|
384
|
+
-p 14250:14250 \
|
|
385
|
+
-p 9411:9411 \
|
|
386
|
+
jaegertracing/all-in-one:1.41
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
5. **Create your .env**
|
|
392
|
+
|
|
393
|
+
Use `.env_template` as a template for you custom config variables
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
## Contributing
|
|
397
|
+
|
|
398
|
+
Contributions are welcome! Please submit Pull Requests and open issues on GitHub.
|
|
399
|
+
|
|
400
|
+
## License
|
|
401
|
+
|
|
402
|
+
This project is licensed under the terms of the LICENSE file included in the repository.
|
|
403
|
+
|
|
404
|
+
## Acknowledgments
|
|
405
|
+
|
|
406
|
+
- Built with [DSPy](https://github.com/stanfordnlp/dspy)
|
|
407
|
+
- Uses [Temporal](https://temporal.io/) for workflow management
|
|
408
|
+
- Integrates with [Tavily](https://tavily.com/) for web search capabilities
|
|
409
|
+
|
|
410
|
+
## Evolution & Future Direction
|
|
411
|
+
|
|
412
|
+
Flock was created to overcome the limitations of traditional agent frameworks. Key design goals include:
|
|
413
|
+
|
|
414
|
+
### Declarative Over Prompt Engineering
|
|
415
|
+
|
|
416
|
+
- **Simplify Agent Definitions:**
|
|
417
|
+
Focus on clear input/output contracts rather than long, complex prompts.
|
|
418
|
+
- **Model Agnostic:**
|
|
419
|
+
Change LLM backends without altering agent logic.
|
|
420
|
+
- **Improved Testability:**
|
|
421
|
+
Clear, structured interfaces facilitate unit testing and validation.
|
|
422
|
+
|
|
423
|
+
### Robust, Production-Grade Orchestration
|
|
424
|
+
|
|
425
|
+
- **Fault Tolerance:**
|
|
426
|
+
Leveraging Temporal for automatic retries, durable state, and robust error handling.
|
|
427
|
+
- **Scalability:**
|
|
428
|
+
Support for concurrent, batch, and distributed workflows.
|
|
429
|
+
- **Observability:**
|
|
430
|
+
Built-in logging and monitoring for real-time insights into workflow execution.
|
|
431
|
+
|
|
432
|
+
### Future Enhancements
|
|
433
|
+
|
|
434
|
+
- Expanded type system for richer agent interactions
|
|
435
|
+
- Enhanced tool ecosystem and custom integrations
|
|
436
|
+
- Advanced monitoring, debugging, and performance metrics
|
|
437
|
+
- Extended testing frameworks and validation tools
|
|
438
|
+
|
|
439
|
+
Join us in building the next generation of reliable, production-ready AI agent systems!
|
|
440
|
+
Become part of the FLOCK!
|
|
441
|
+
|
|
@@ -8,8 +8,8 @@ flock/cli/load_examples.py,sha256=DkeLUlrb7rGx3nZ04aADU9HXXu5mZTf_DBwT0xhzIv4,7
|
|
|
8
8
|
flock/cli/load_flock.py,sha256=3JdECvt5X7uyOG2vZS3-Zk5C5SI_84_QZjcsB3oJmfA,932
|
|
9
9
|
flock/cli/settings.py,sha256=DkeLUlrb7rGx3nZ04aADU9HXXu5mZTf_DBwT0xhzIv4,7
|
|
10
10
|
flock/core/__init__.py,sha256=0Xq_txurlxxjKGXjRn6GNJusGTiBcd7zw2eF0L7JyuU,183
|
|
11
|
-
flock/core/flock.py,sha256=
|
|
12
|
-
flock/core/flock_agent.py,sha256=
|
|
11
|
+
flock/core/flock.py,sha256=RoLatRW91dSJjFhq6T5t03y_zsIYPxcatn6IHpRUcVc,17435
|
|
12
|
+
flock/core/flock_agent.py,sha256=WWhYMnLGvaeJ47gVX5s3v6sJx1VsGkcU6NDrA3ELbVY,30723
|
|
13
13
|
flock/core/context/context.py,sha256=jH06w4C_O5CEL-YxjX_x_dmgLe9Rcllnn1Ebs0dvwaE,6171
|
|
14
14
|
flock/core/context/context_manager.py,sha256=qMySVny_dbTNLh21RHK_YT0mNKIOrqJDZpi9ZVdBsxU,1103
|
|
15
15
|
flock/core/context/context_vars.py,sha256=0Hn6fM2iNc0_jIIU0B7KX-K2o8qXqtZ5EYtwujETQ7U,272
|
|
@@ -21,7 +21,7 @@ flock/core/logging/telemetry.py,sha256=3E9Tyj6AUR6A5RlIufcdCdWm5BAA7tbOsCa7lHoUQ
|
|
|
21
21
|
flock/core/logging/trace_and_logged.py,sha256=5vNrK1kxuPMoPJ0-QjQg-EDJL1oiEzvU6UNi6X8FiMs,2117
|
|
22
22
|
flock/core/logging/formatters/enum_builder.py,sha256=LgEYXUv84wK5vwHflZ5h8HBGgvLH3sByvUQe8tZiyY0,981
|
|
23
23
|
flock/core/logging/formatters/theme_builder.py,sha256=Wnaal3HuUDA4HFg9tdql1BxYwK83ACOZBBQy-DXnxcA,17342
|
|
24
|
-
flock/core/logging/formatters/themed_formatter.py,sha256=
|
|
24
|
+
flock/core/logging/formatters/themed_formatter.py,sha256=6WO9RPr6Su05rJEX9bRXd8peE-QzGCQeO5veIjQH-Vc,21086
|
|
25
25
|
flock/core/logging/formatters/themes.py,sha256=vZqDyPlxZ1RGwzp8QCm0-Y0aXBZ62gTllulK6nbi_L4,10675
|
|
26
26
|
flock/core/logging/span_middleware/baggage_span_processor.py,sha256=gJfRl8FeB6jdtghTaRHCrOaTo4fhPMRKgjqtZj-8T48,1118
|
|
27
27
|
flock/core/logging/telemetry_exporter/base_exporter.py,sha256=rQJJzS6q9n2aojoSqwCnl7ZtHrh5LZZ-gkxUuI5WfrQ,1124
|
|
@@ -33,6 +33,7 @@ flock/core/registry/agent_registry.py,sha256=YkYIyvFNjm7gYKRAiQQdOvVYMtsYH5cijfr
|
|
|
33
33
|
flock/core/tools/basic_tools.py,sha256=OwWaFu4NoVrc3Uijj56RY9XDDaP_mOnEa5B3wSWwwLE,4756
|
|
34
34
|
flock/core/tools/dev_tools/github.py,sha256=a2OTPXS7kWOVA4zrZHynQDcsmEi4Pac5MfSjQOLePzA,5308
|
|
35
35
|
flock/core/util/cli_helper.py,sha256=nlSnPrc1pnYEFQXcL_wCqPI6g1Jr7AzNtmGoOPs0zKw,936
|
|
36
|
+
flock/core/util/hydrator.py,sha256=6qNwOwCZB7r6y25BZ--0PGofrAlfMaXbDKFQeP5NLts,11196
|
|
36
37
|
flock/core/util/input_resolver.py,sha256=g9vDPdY4OH-G7qjas5ksGEHueokHGFPMoLOvC-ngeLo,5984
|
|
37
38
|
flock/core/util/serializable.py,sha256=SymJ0YrjBx48mOBItYSqoRpKuzIc4vKWRS6ScTzre7s,2573
|
|
38
39
|
flock/interpreter/python_interpreter.py,sha256=pq2e7KJfAYtBCP2hhbtFNeg18QdMFF66esoYn3MHfA4,26177
|
|
@@ -379,8 +380,8 @@ flock/workflow/activities.py,sha256=2zcYyDoCuYs9oQbnhLjCzBUdEi7d5IEIemKJ7TV_B8w,
|
|
|
379
380
|
flock/workflow/agent_activities.py,sha256=NhBZscflEf2IMfSRa_pBM_TRP7uVEF_O0ROvWZ33eDc,963
|
|
380
381
|
flock/workflow/temporal_setup.py,sha256=VWBgmBgfTBjwM5ruS_dVpA5AVxx6EZ7oFPGw4j3m0l0,1091
|
|
381
382
|
flock/workflow/workflow.py,sha256=I9MryXW_bqYVTHx-nl2epbTqeRy27CAWHHA7ZZA0nAk,1696
|
|
382
|
-
flock_core-0.2.
|
|
383
|
-
flock_core-0.2.
|
|
384
|
-
flock_core-0.2.
|
|
385
|
-
flock_core-0.2.
|
|
386
|
-
flock_core-0.2.
|
|
383
|
+
flock_core-0.2.17.dist-info/METADATA,sha256=uluO8ie9LjTnlmLXTdaNTvanGOE-tsIcGdYB-tPKJjk,16930
|
|
384
|
+
flock_core-0.2.17.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
385
|
+
flock_core-0.2.17.dist-info/entry_points.txt,sha256=rWaS5KSpkTmWySURGFZk6PhbJ87TmvcFQDi2uzjlagQ,37
|
|
386
|
+
flock_core-0.2.17.dist-info/licenses/LICENSE,sha256=iYEqWy0wjULzM9GAERaybP4LBiPeu7Z1NEliLUdJKSc,1072
|
|
387
|
+
flock_core-0.2.17.dist-info/RECORD,,
|
|
@@ -1,332 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: flock-core
|
|
3
|
-
Version: 0.2.15
|
|
4
|
-
Summary: Declarative LLM Orchestration at Scale
|
|
5
|
-
Author-email: Andre Ratzenberger <andre.ratzenberger@whiteduck.de>
|
|
6
|
-
License-File: LICENSE
|
|
7
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
8
|
-
Classifier: Operating System :: OS Independent
|
|
9
|
-
Classifier: Programming Language :: Python :: 3
|
|
10
|
-
Requires-Python: >=3.10
|
|
11
|
-
Requires-Dist: cloudpickle>=3.1.1
|
|
12
|
-
Requires-Dist: devtools>=0.12.2
|
|
13
|
-
Requires-Dist: dspy==2.5.42
|
|
14
|
-
Requires-Dist: duckduckgo-search>=7.3.2
|
|
15
|
-
Requires-Dist: httpx>=0.28.1
|
|
16
|
-
Requires-Dist: loguru>=0.7.3
|
|
17
|
-
Requires-Dist: msgpack>=1.1.0
|
|
18
|
-
Requires-Dist: opentelemetry-api>=1.30.0
|
|
19
|
-
Requires-Dist: opentelemetry-exporter-jaeger-proto-grpc>=1.21.0
|
|
20
|
-
Requires-Dist: opentelemetry-exporter-jaeger>=1.21.0
|
|
21
|
-
Requires-Dist: opentelemetry-exporter-otlp>=1.30.0
|
|
22
|
-
Requires-Dist: opentelemetry-instrumentation-logging>=0.51b0
|
|
23
|
-
Requires-Dist: opentelemetry-sdk>=1.30.0
|
|
24
|
-
Requires-Dist: pydantic>=2.10.5
|
|
25
|
-
Requires-Dist: python-box>=7.3.2
|
|
26
|
-
Requires-Dist: python-decouple>=3.8
|
|
27
|
-
Requires-Dist: questionary>=2.1.0
|
|
28
|
-
Requires-Dist: rich>=13.9.4
|
|
29
|
-
Requires-Dist: temporalio>=1.9.0
|
|
30
|
-
Requires-Dist: toml>=0.10.2
|
|
31
|
-
Provides-Extra: all-tools
|
|
32
|
-
Requires-Dist: docling>=2.18.0; extra == 'all-tools'
|
|
33
|
-
Requires-Dist: markdownify>=0.14.1; extra == 'all-tools'
|
|
34
|
-
Requires-Dist: tavily-python>=0.5.0; extra == 'all-tools'
|
|
35
|
-
Provides-Extra: tools
|
|
36
|
-
Requires-Dist: markdownify>=0.14.1; extra == 'tools'
|
|
37
|
-
Requires-Dist: tavily-python>=0.5.0; extra == 'tools'
|
|
38
|
-
Description-Content-Type: text/markdown
|
|
39
|
-
|
|
40
|
-
<p align="center">
|
|
41
|
-
<img src="docs/img/flock.png" width="600"><br>
|
|
42
|
-
<img alt="Dynamic TOML Badge" src="https://img.shields.io/badge/dynamic/toml?url=https%3A%2F%2Fraw.githubusercontent.com%2Fwhiteducksoftware%2Fflock%2Frefs%2Fheads%2Fmaster%2Fpyproject.toml&query=%24.project.version&style=for-the-badge&logo=pypi&label=pip%20version">
|
|
43
|
-
<a href="https://www.linkedin.com/company/whiteduck" target="_blank"><img alt="LinkedIn" src="https://img.shields.io/badge/linkedin-%230077B5.svg?style=for-the-badge&logo=linkedin&logoColor=white&label=whiteduck"></a>
|
|
44
|
-
<a href="https://bsky.app/profile/whiteduck-gmbh.bsky.social" target="_blank"><img alt="Bluesky" src="https://img.shields.io/badge/bluesky-Follow-blue?style=for-the-badge&logo=bluesky&logoColor=%23fff&color=%23333&labelColor=%230285FF&label=whiteduck-gmbh"></a>
|
|
45
|
-
|
|
46
|
-
## Overview
|
|
47
|
-
|
|
48
|
-
Flock is a framework for orchestrating LLM-powered agents. It leverages a **declarative approach** where you simply specify what each agent needs as input and what it produces as output—without having to write lengthy, brittle prompts. Under the hood, Flock transforms these declarations into robust workflows, using cutting-edge components such as Temporal and DSPy to handle fault tolerance, state management, and error recovery.
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
| Traditional Agent Frameworks 🙃 | Flock 🐤🐧🐓🦆 |
|
|
53
|
-
|------------------------------------------|--------------------------------------------------------------|
|
|
54
|
-
| 🤖 **Complex Prompt Engineering** | 📝 **Declarative Agent Definitions** |
|
|
55
|
-
| • Lengthy, brittle prompts | • Clear, concise input/output declarations |
|
|
56
|
-
| • Hard-to-tune and adapt | • No need for manual prompt engineering |
|
|
57
|
-
| | |
|
|
58
|
-
| 💥 **Fragile Execution** | ⚡ **Robust & Scalable** |
|
|
59
|
-
| • Single failure can break the system | • Fault-tolerant with built-in retries and error handling |
|
|
60
|
-
| • Difficult to monitor and recover | • Automatic recovery via Temporal workflow integration |
|
|
61
|
-
| | |
|
|
62
|
-
| 🏗️ **Rigid Workflows** | 🔄 **Flexible Orchestration** |
|
|
63
|
-
| • Limited adaptability | • Dynamic agent chaining and hand-offs |
|
|
64
|
-
| • Hard to scale and parallelize | • Modular, concurrent, and batch processing |
|
|
65
|
-
| | |
|
|
66
|
-
|
|
67
|
-
## Video Demonstration
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
https://github.com/user-attachments/assets/bdab4786-d532-459f-806a-024727164dcc
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
## Key Innovations
|
|
76
|
-
|
|
77
|
-
- **Declarative Agent System:**
|
|
78
|
-
Define agents by declaring their input/output interfaces (with type hints and human-readable descriptions) using a concise syntax.
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
<img src="docs/img/examples/01_01.png" width="300"><br>
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
Example syntax:
|
|
85
|
-
```python
|
|
86
|
-
input = "query: str|The search query, context: dict|The full conversation context"
|
|
87
|
-
output = "idea: str|The generated software project idea"
|
|
88
|
-
```
|
|
89
|
-
The framework automatically extracts type and description details, builds precise prompts, and configures the underlying LLM.
|
|
90
|
-
|
|
91
|
-
- **Lifecycle Hooks:**
|
|
92
|
-
Each agent (via the new `FlockAgent` base class) supports lifecycle hooks such as `initialize()`, `terminate()`, and `on_error()`. This ensures that agents can perform setup, cleanup, and robust error handling—all without cluttering the main business logic.
|
|
93
|
-
|
|
94
|
-
- **Fault Tolerance & Temporal Integration:**
|
|
95
|
-
Flock is built with production readiness in mind. By integrating with Temporal, your agent workflows enjoy automatic retries, durable state management, and resilience against failures. This means that a single agent crash won't bring down your entire system.
|
|
96
|
-
|
|
97
|
-
- **Type Safety and Clear Contracts:**
|
|
98
|
-
Agents are implemented as Pydantic models. This provides automatic JSON serialization/deserialization, strong typing, and an explicit contract for inputs and outputs. Testing, validation, and integration become straightforward.
|
|
99
|
-
|
|
100
|
-
- **DSPy Integration:**
|
|
101
|
-
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.
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
<p align="center">
|
|
105
|
-
<img src="docs/img/flock_cli.png" width="200"><br>
|
|
106
|
-
|
|
107
|
-
## Quick Start
|
|
108
|
-
|
|
109
|
-
Below is a simple example of how to create and run an agent with Flock:
|
|
110
|
-
|
|
111
|
-
```python
|
|
112
|
-
import asyncio
|
|
113
|
-
from flock.core.flock import Flock
|
|
114
|
-
from flock.core.agents.flock_agent import FlockAgent
|
|
115
|
-
from flock.core.tools import basic_tools
|
|
116
|
-
|
|
117
|
-
async def main():
|
|
118
|
-
# Initialize Flock
|
|
119
|
-
flock = Flock(model="openai/gpt-4o")
|
|
120
|
-
|
|
121
|
-
# Create an agent with clear input/output declarations and optional tools.
|
|
122
|
-
idea_agent = FlockAgent(
|
|
123
|
-
name="idea_agent",
|
|
124
|
-
input="query: str|The search query, context: dict|Additional context",
|
|
125
|
-
output="a_fun_software_project_idea: str|The generated software project idea",
|
|
126
|
-
tools=[basic_tools.web_search_tavily],
|
|
127
|
-
)
|
|
128
|
-
flock.add_agent(idea_agent)
|
|
129
|
-
|
|
130
|
-
# Run the agent locally (with built-in debugging/logging)
|
|
131
|
-
result = await flock.run_async(
|
|
132
|
-
start_agent=idea_agent,
|
|
133
|
-
input="build a revolutionary app",
|
|
134
|
-
local_debug=True
|
|
135
|
-
)
|
|
136
|
-
print(result)
|
|
137
|
-
|
|
138
|
-
if __name__ == "__main__":
|
|
139
|
-
asyncio.run(main())
|
|
140
|
-
```
|
|
141
|
-
|
|
142
|
-
## Advanced Usage
|
|
143
|
-
|
|
144
|
-
### Agents with Lifecycle Hooks
|
|
145
|
-
|
|
146
|
-
Customize behavior by overriding lifecycle methods:
|
|
147
|
-
|
|
148
|
-
- **initialize(inputs):** Set up resources, validate inputs, or log pre-run state.
|
|
149
|
-
- **terminate(inputs, result):** Clean up resources, log output, or perform post-run actions.
|
|
150
|
-
- **on_error(error, inputs):** Handle exceptions gracefully, log detailed error information, and trigger recovery logic.
|
|
151
|
-
|
|
152
|
-
### Agents with Tools
|
|
153
|
-
|
|
154
|
-
Agents can seamlessly integrate external tools for enhanced functionality:
|
|
155
|
-
|
|
156
|
-
```python
|
|
157
|
-
from flock.core.tools import basic_tools
|
|
158
|
-
|
|
159
|
-
research_agent = FlockAgent(
|
|
160
|
-
name="research_agent",
|
|
161
|
-
input="research_topic: str|Topic to investigate",
|
|
162
|
-
output="research_result: str|The outcome of the research",
|
|
163
|
-
tools=[basic_tools.web_search_tavily],
|
|
164
|
-
)
|
|
165
|
-
```
|
|
166
|
-
|
|
167
|
-
### Agent Chaining
|
|
168
|
-
|
|
169
|
-
Chain agents together to create complex workflows:
|
|
170
|
-
|
|
171
|
-
```python
|
|
172
|
-
# Define the first agent in the chain.
|
|
173
|
-
project_plan_agent = FlockAgent(
|
|
174
|
-
name="project_plan_agent",
|
|
175
|
-
input="project_idea: str|Initial project idea",
|
|
176
|
-
output="plan_headings: list[str]|Headings for the project plan",
|
|
177
|
-
tools=[basic_tools.web_search_tavily, basic_tools.code_eval],
|
|
178
|
-
)
|
|
179
|
-
|
|
180
|
-
# Define a second agent that builds on the output of the first.
|
|
181
|
-
content_agent = FlockAgent(
|
|
182
|
-
name="content_agent",
|
|
183
|
-
input="context: dict|Global context, project_plan_agent.plan_headings: list[str]|Plan headings",
|
|
184
|
-
output="project_plan_content: str|Detailed content for the plan",
|
|
185
|
-
)
|
|
186
|
-
|
|
187
|
-
# Set up hand-off from the first agent to the second.
|
|
188
|
-
project_plan_agent.hand_off = content_agent
|
|
189
|
-
```
|
|
190
|
-
|
|
191
|
-
### Temporal Workflow Integration
|
|
192
|
-
|
|
193
|
-
Flock supports execution on Temporal, ensuring robust, fault-tolerant workflows:
|
|
194
|
-
|
|
195
|
-
- **Durability:** Persistent state management even in the case of failures.
|
|
196
|
-
- **Retries & Error Handling:** Automatic recovery via Temporal's built-in mechanisms.
|
|
197
|
-
- **Scalability:** Seamless orchestration of distributed agent workflows.
|
|
198
|
-
|
|
199
|
-
## Architecture
|
|
200
|
-
|
|
201
|
-
TODO: Insert charts
|
|
202
|
-
|
|
203
|
-
## Requirements
|
|
204
|
-
|
|
205
|
-
- Python 3.12+
|
|
206
|
-
- (Optional) Temporal server running locally for production-grade workflow features
|
|
207
|
-
- API keys for integrated services
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
recommended services
|
|
211
|
-
```bash
|
|
212
|
-
export OPENAI_API_KEY=sk-proj-
|
|
213
|
-
export TAVILY_API_KEY=tvly-
|
|
214
|
-
```
|
|
215
|
-
|
|
216
|
-
or in `.env`
|
|
217
|
-
|
|
218
|
-
For LLM interaction LiteLLM is getting used. Please refer to its documentation on how to easily use other models and/or provider.
|
|
219
|
-
|
|
220
|
-
https://docs.litellm.ai/docs/providers
|
|
221
|
-
|
|
222
|
-
## Installation
|
|
223
|
-
|
|
224
|
-
```bash
|
|
225
|
-
pip install flock-core
|
|
226
|
-
```
|
|
227
|
-
|
|
228
|
-
if you want to use the integrated tools
|
|
229
|
-
|
|
230
|
-
```bash
|
|
231
|
-
pip install flock-core[tools]
|
|
232
|
-
```
|
|
233
|
-
|
|
234
|
-
and for the docling tools
|
|
235
|
-
|
|
236
|
-
```bash
|
|
237
|
-
pip install flock-core[all-tools]
|
|
238
|
-
```
|
|
239
|
-
|
|
240
|
-
## Development
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
1. **Clone the Repository:**
|
|
244
|
-
|
|
245
|
-
```bash
|
|
246
|
-
git clone https://github.com/yourusername/flock.git
|
|
247
|
-
cd flock
|
|
248
|
-
```
|
|
249
|
-
|
|
250
|
-
2. **Create a Virtual Environment and sync all packages:**
|
|
251
|
-
|
|
252
|
-
```bash
|
|
253
|
-
uv sync --all-groups --all-extras
|
|
254
|
-
```
|
|
255
|
-
|
|
256
|
-
3. **Install local version of flock:**
|
|
257
|
-
|
|
258
|
-
```bash
|
|
259
|
-
uv build && uv pip install -e .
|
|
260
|
-
```
|
|
261
|
-
|
|
262
|
-
Install Jaeger for telemetry
|
|
263
|
-
```
|
|
264
|
-
|
|
265
|
-
docker run -d --name jaeger \
|
|
266
|
-
-e COLLECTOR_ZIPKIN_HTTP_PORT=9411 \
|
|
267
|
-
-p 5775:5775/udp \
|
|
268
|
-
-p 6831:6831/udp \
|
|
269
|
-
-p 6832:6832/udp \
|
|
270
|
-
-p 5778:5778 \
|
|
271
|
-
-p 16686:16686 \
|
|
272
|
-
-p 14268:14268 \
|
|
273
|
-
-p 14250:14250 \
|
|
274
|
-
-p 9411:9411 \
|
|
275
|
-
jaegertracing/all-in-one:1.41
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
```
|
|
279
|
-
|
|
280
|
-
or zipkin
|
|
281
|
-
|
|
282
|
-
```
|
|
283
|
-
docker run -d -p 9411:9411 openzipkin/zipkin
|
|
284
|
-
|
|
285
|
-
```
|
|
286
|
-
|
|
287
|
-
## Contributing
|
|
288
|
-
|
|
289
|
-
Contributions are welcome! Please submit Pull Requests and open issues on GitHub.
|
|
290
|
-
|
|
291
|
-
## License
|
|
292
|
-
|
|
293
|
-
This project is licensed under the terms of the LICENSE file included in the repository.
|
|
294
|
-
|
|
295
|
-
## Acknowledgments
|
|
296
|
-
|
|
297
|
-
- Built with [DSPy](https://github.com/stanfordnlp/dspy)
|
|
298
|
-
- Uses [Temporal](https://temporal.io/) for workflow management
|
|
299
|
-
- Integrates with [Tavily](https://tavily.com/) for web search capabilities
|
|
300
|
-
- Web interface built with FastHTML and MonsterUI
|
|
301
|
-
|
|
302
|
-
## Evolution & Future Direction
|
|
303
|
-
|
|
304
|
-
Flock was created to overcome the limitations of traditional agent frameworks. Key design goals include:
|
|
305
|
-
|
|
306
|
-
### Declarative Over Prompt Engineering
|
|
307
|
-
|
|
308
|
-
- **Simplify Agent Definitions:**
|
|
309
|
-
Focus on clear input/output contracts rather than long, complex prompts.
|
|
310
|
-
- **Model Agnostic:**
|
|
311
|
-
Change LLM backends without altering agent logic.
|
|
312
|
-
- **Improved Testability:**
|
|
313
|
-
Clear, structured interfaces facilitate unit testing and validation.
|
|
314
|
-
|
|
315
|
-
### Robust, Production-Grade Orchestration
|
|
316
|
-
|
|
317
|
-
- **Fault Tolerance:**
|
|
318
|
-
Leveraging Temporal for automatic retries, durable state, and robust error handling.
|
|
319
|
-
- **Scalability:**
|
|
320
|
-
Support for concurrent, batch, and distributed workflows.
|
|
321
|
-
- **Observability:**
|
|
322
|
-
Built-in logging and monitoring for real-time insights into workflow execution.
|
|
323
|
-
|
|
324
|
-
### Future Enhancements
|
|
325
|
-
|
|
326
|
-
- Expanded type system for richer agent interactions
|
|
327
|
-
- Enhanced tool ecosystem and custom integrations
|
|
328
|
-
- Advanced monitoring, debugging, and performance metrics
|
|
329
|
-
- Extended testing frameworks and validation tools
|
|
330
|
-
|
|
331
|
-
Join us in building the next generation of reliable, production-ready AI agent systems!
|
|
332
|
-
|
|
File without changes
|
|
File without changes
|
|
File without changes
|