flock-core 0.2.16__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 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(start_agent, input, context, run_id, box_result)
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
@@ -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=3EQqrAej3Tb1vLYsILkXBcV9Feb2b1ABUviM3Xp9tMs,17151
12
- flock/core/flock_agent.py,sha256=FPi6hz8pOvHpsk5YzzKScnOaQzGo1W3lIOwzfHzrjyE,30572
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=kCmGXLD8yzhfzENJUQOsqX3Sdo2PuN8JIZvBBWO22JI,20834
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.16.dist-info/METADATA,sha256=GYQ8kQbOOthWVA3ld9RF2a06BFuA8iSk0q1h6E2h5ao,12057
383
- flock_core-0.2.16.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
384
- flock_core-0.2.16.dist-info/entry_points.txt,sha256=rWaS5KSpkTmWySURGFZk6PhbJ87TmvcFQDi2uzjlagQ,37
385
- flock_core-0.2.16.dist-info/licenses/LICENSE,sha256=iYEqWy0wjULzM9GAERaybP4LBiPeu7Z1NEliLUdJKSc,1072
386
- flock_core-0.2.16.dist-info/RECORD,,
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.16
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
-