flock-core 0.3.10__py3-none-any.whl → 0.3.13__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 +15 -0
- flock/core/flock_agent.py +2 -1
- flock/core/flock_api.py +0 -1
- flock/core/logging/logging.py +172 -17
- flock/core/tools/basic_tools.py +8 -0
- flock/core/tools/llm_tools.py +788 -0
- flock/core/tools/markdown_tools.py +195 -0
- flock/evaluators/memory/memory_evaluator.py +88 -0
- flock/modules/memory/memory_module.py +257 -95
- flock/modules/memory/memory_parser.py +1 -1
- {flock_core-0.3.10.dist-info → flock_core-0.3.13.dist-info}/METADATA +3 -2
- {flock_core-0.3.10.dist-info → flock_core-0.3.13.dist-info}/RECORD +15 -12
- {flock_core-0.3.10.dist-info → flock_core-0.3.13.dist-info}/WHEEL +0 -0
- {flock_core-0.3.10.dist-info → flock_core-0.3.13.dist-info}/entry_points.txt +0 -0
- {flock_core-0.3.10.dist-info → flock_core-0.3.13.dist-info}/licenses/LICENSE +0 -0
flock/core/flock.py
CHANGED
|
@@ -295,6 +295,21 @@ class Flock:
|
|
|
295
295
|
data = self.model_dump()
|
|
296
296
|
return convert_callable(data)
|
|
297
297
|
|
|
298
|
+
def start_api(self, host: str = "0.0.0.0", port: int = 8344) -> None:
|
|
299
|
+
"""Start a REST API server for this Flock instance.
|
|
300
|
+
|
|
301
|
+
This method creates a FlockAPI instance for the current Flock and starts the API server.
|
|
302
|
+
It provides an easier alternative to manually creating and starting the API.
|
|
303
|
+
|
|
304
|
+
Args:
|
|
305
|
+
host (str): The host to bind the server to. Defaults to "0.0.0.0".
|
|
306
|
+
port (int): The port to bind the server to. Defaults to 8344.
|
|
307
|
+
"""
|
|
308
|
+
from flock.core.flock_api import FlockAPI
|
|
309
|
+
|
|
310
|
+
api = FlockAPI(self)
|
|
311
|
+
api.start(host=host, port=port)
|
|
312
|
+
|
|
298
313
|
@classmethod
|
|
299
314
|
def from_dict(cls: type[T], data: dict[str, Any]) -> T:
|
|
300
315
|
"""Deserialize a FlockAgent instance from a dictionary.
|
flock/core/flock_agent.py
CHANGED
|
@@ -15,6 +15,7 @@ from flock.core.flock_evaluator import FlockEvaluator
|
|
|
15
15
|
from flock.core.flock_module import FlockModule
|
|
16
16
|
from flock.core.flock_router import FlockRouter
|
|
17
17
|
from flock.core.logging.logging import get_logger
|
|
18
|
+
from flock.core.mixin.dspy_integration import DSPyIntegrationMixin
|
|
18
19
|
|
|
19
20
|
logger = get_logger("agent")
|
|
20
21
|
tracer = trace.get_tracer(__name__)
|
|
@@ -23,7 +24,7 @@ tracer = trace.get_tracer(__name__)
|
|
|
23
24
|
T = TypeVar("T", bound="FlockAgent")
|
|
24
25
|
|
|
25
26
|
|
|
26
|
-
class FlockAgent(BaseModel, ABC):
|
|
27
|
+
class FlockAgent(BaseModel, ABC, DSPyIntegrationMixin):
|
|
27
28
|
name: str = Field(..., description="Unique identifier for the agent.")
|
|
28
29
|
model: str | None = Field(
|
|
29
30
|
None, description="The model to use (e.g., 'openai/gpt-4o')."
|
flock/core/flock_api.py
CHANGED
flock/core/logging/logging.py
CHANGED
|
@@ -5,7 +5,7 @@ Key points:
|
|
|
5
5
|
- We always have Temporal imported, so we cannot decide based on import.
|
|
6
6
|
- Instead, we dynamically check if we're in a workflow context by trying
|
|
7
7
|
to call `workflow.info()`.
|
|
8
|
-
- In a workflow, we use Temporal
|
|
8
|
+
- In a workflow, we use Temporal's built-in logger and skip debug/info/warning
|
|
9
9
|
logs during replay.
|
|
10
10
|
- Outside workflows, we use Loguru with rich formatting.
|
|
11
11
|
"""
|
|
@@ -79,21 +79,24 @@ def color_for_category(category: str) -> str:
|
|
|
79
79
|
|
|
80
80
|
|
|
81
81
|
def custom_format(record):
|
|
82
|
-
"""A
|
|
83
|
-
|
|
84
|
-
- Prints the time in green
|
|
85
|
-
- Prints the level with Loguru's <level> tag
|
|
86
|
-
- Prints the trace_id in cyan
|
|
87
|
-
- Looks up the category in the record's extras and applies a color
|
|
88
|
-
- Finally prints the message
|
|
89
|
-
"""
|
|
82
|
+
"""A formatter that applies truncation to the entire formatted message."""
|
|
90
83
|
t = record["time"].strftime("%Y-%m-%d %H:%M:%S")
|
|
91
84
|
level_name = record["level"].name
|
|
92
85
|
category = record["extra"].get("category", "unknown")
|
|
93
86
|
trace_id = record["extra"].get("trace_id", "no-trace")
|
|
94
87
|
color = color_for_category(category)
|
|
88
|
+
|
|
89
|
+
# Get the formatted message (already includes args)
|
|
95
90
|
message = record["message"]
|
|
96
91
|
|
|
92
|
+
# Apply truncation to the full formatted message
|
|
93
|
+
if len(message) > MAX_LENGTH:
|
|
94
|
+
truncated_chars = len(message) - MAX_LENGTH
|
|
95
|
+
message = (
|
|
96
|
+
message[:MAX_LENGTH]
|
|
97
|
+
+ f"<yellow>...+({truncated_chars} chars)</yellow>"
|
|
98
|
+
)
|
|
99
|
+
|
|
97
100
|
return (
|
|
98
101
|
f"<green>{t}</green> | <level>{level_name: <8}</level> | "
|
|
99
102
|
f"<cyan>[trace_id: {trace_id}]</cyan> | "
|
|
@@ -103,31 +106,54 @@ def custom_format(record):
|
|
|
103
106
|
|
|
104
107
|
class ImmediateFlushSink:
|
|
105
108
|
"""A custom Loguru sink that writes to a stream and flushes immediately after each message.
|
|
109
|
+
|
|
106
110
|
This ensures that logs appear in real time.
|
|
107
111
|
"""
|
|
108
112
|
|
|
109
113
|
def __init__(self, stream=None):
|
|
114
|
+
"""Initialize the ImmediateFlushSink.
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
stream (Stream, optional): The stream to write to. Defaults to sys.stderr.
|
|
118
|
+
"""
|
|
110
119
|
self._stream = stream if stream else sys.stderr
|
|
111
120
|
|
|
112
121
|
def write(self, message):
|
|
122
|
+
"""Write a message to the stream and flush immediately.
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
message (str): The message to write.
|
|
126
|
+
"""
|
|
113
127
|
self._stream.write(message)
|
|
114
128
|
self._stream.flush()
|
|
115
129
|
|
|
116
130
|
def flush(self):
|
|
131
|
+
"""Flush the stream."""
|
|
117
132
|
self._stream.flush()
|
|
118
133
|
|
|
119
134
|
|
|
120
135
|
class PrintAndFlushSink:
|
|
121
|
-
"""A Loguru sink
|
|
136
|
+
"""A Loguru sink.
|
|
137
|
+
|
|
138
|
+
forcibly prints each log record and flushes immediately,
|
|
122
139
|
mimicking print(..., flush=True).
|
|
123
140
|
"""
|
|
124
141
|
|
|
125
142
|
def write(self, message: str):
|
|
143
|
+
"""Write a message to the stream and flush immediately.
|
|
144
|
+
|
|
145
|
+
Args:
|
|
146
|
+
message (str): The message to write.
|
|
147
|
+
"""
|
|
126
148
|
# message already ends with a newline
|
|
127
149
|
print(message, end="", flush=True)
|
|
128
150
|
|
|
129
151
|
def flush(self):
|
|
130
|
-
|
|
152
|
+
"""Flush the stream.
|
|
153
|
+
|
|
154
|
+
Already flushed on every write call.
|
|
155
|
+
"""
|
|
156
|
+
pass
|
|
131
157
|
|
|
132
158
|
|
|
133
159
|
# Configure Loguru for non-workflow (local/worker) contexts.
|
|
@@ -169,6 +195,10 @@ class DummyLogger:
|
|
|
169
195
|
dummy_logger = DummyLogger()
|
|
170
196
|
|
|
171
197
|
|
|
198
|
+
# Maximum length for log messages before truncation
|
|
199
|
+
MAX_LENGTH = 500
|
|
200
|
+
|
|
201
|
+
|
|
172
202
|
class FlockLogger:
|
|
173
203
|
"""A unified logger that selects the appropriate logging mechanism based on context.
|
|
174
204
|
|
|
@@ -178,6 +208,12 @@ class FlockLogger:
|
|
|
178
208
|
"""
|
|
179
209
|
|
|
180
210
|
def __init__(self, name: str, enable_logging: bool = False):
|
|
211
|
+
"""Initialize the FlockLogger.
|
|
212
|
+
|
|
213
|
+
Args:
|
|
214
|
+
name (str): The name of the logger.
|
|
215
|
+
enable_logging (bool, optional): Whether to enable logging. Defaults to False.
|
|
216
|
+
"""
|
|
181
217
|
self.name = name
|
|
182
218
|
self.enable_logging = enable_logging
|
|
183
219
|
|
|
@@ -194,28 +230,122 @@ class FlockLogger:
|
|
|
194
230
|
trace_id=get_current_trace_id(),
|
|
195
231
|
)
|
|
196
232
|
|
|
197
|
-
def
|
|
233
|
+
def _truncate_message(self, message: str, max_length: int) -> str:
|
|
234
|
+
"""Truncate a message if it exceeds max_length and add truncation indicator."""
|
|
235
|
+
if len(message) > max_length:
|
|
236
|
+
truncated_chars = len(message) - max_length
|
|
237
|
+
return (
|
|
238
|
+
message[:max_length]
|
|
239
|
+
+ f"...<yellow>+({truncated_chars} chars)</yellow>"
|
|
240
|
+
)
|
|
241
|
+
return message
|
|
242
|
+
|
|
243
|
+
def debug(
|
|
244
|
+
self,
|
|
245
|
+
message: str,
|
|
246
|
+
*args,
|
|
247
|
+
flush: bool = False,
|
|
248
|
+
max_length: int = MAX_LENGTH,
|
|
249
|
+
**kwargs,
|
|
250
|
+
) -> None:
|
|
251
|
+
"""Debug a message.
|
|
252
|
+
|
|
253
|
+
Args:
|
|
254
|
+
message (str): The message to debug.
|
|
255
|
+
flush (bool, optional): Whether to flush the message. Defaults to False.
|
|
256
|
+
max_length (int, optional): The maximum length of the message. Defaults to MAX_LENGTH.
|
|
257
|
+
"""
|
|
258
|
+
message = self._truncate_message(message, max_length)
|
|
198
259
|
self._get_logger().debug(message, *args, **kwargs)
|
|
199
260
|
|
|
200
|
-
def info(
|
|
261
|
+
def info(
|
|
262
|
+
self,
|
|
263
|
+
message: str,
|
|
264
|
+
*args,
|
|
265
|
+
flush: bool = False,
|
|
266
|
+
max_length: int = MAX_LENGTH,
|
|
267
|
+
**kwargs,
|
|
268
|
+
) -> None:
|
|
269
|
+
"""Info a message.
|
|
270
|
+
|
|
271
|
+
Args:
|
|
272
|
+
message (str): The message to info.
|
|
273
|
+
flush (bool, optional): Whether to flush the message. Defaults to False.
|
|
274
|
+
max_length (int, optional): The maximum length of the message. Defaults to MAX_LENGTH.
|
|
275
|
+
"""
|
|
276
|
+
message = self._truncate_message(message, max_length)
|
|
201
277
|
self._get_logger().info(message, *args, **kwargs)
|
|
202
278
|
|
|
203
279
|
def warning(
|
|
204
|
-
self,
|
|
280
|
+
self,
|
|
281
|
+
message: str,
|
|
282
|
+
*args,
|
|
283
|
+
flush: bool = False,
|
|
284
|
+
max_length: int = MAX_LENGTH,
|
|
285
|
+
**kwargs,
|
|
205
286
|
) -> None:
|
|
287
|
+
"""Warning a message.
|
|
288
|
+
|
|
289
|
+
Args:
|
|
290
|
+
message (str): The message to warning.
|
|
291
|
+
flush (bool, optional): Whether to flush the message. Defaults to False.
|
|
292
|
+
max_length (int, optional): The maximum length of the message. Defaults to MAX_LENGTH.
|
|
293
|
+
"""
|
|
294
|
+
message = self._truncate_message(message, max_length)
|
|
206
295
|
self._get_logger().warning(message, *args, **kwargs)
|
|
207
296
|
|
|
208
|
-
def error(
|
|
297
|
+
def error(
|
|
298
|
+
self,
|
|
299
|
+
message: str,
|
|
300
|
+
*args,
|
|
301
|
+
flush: bool = False,
|
|
302
|
+
max_length: int = MAX_LENGTH,
|
|
303
|
+
**kwargs,
|
|
304
|
+
) -> None:
|
|
305
|
+
"""Error a message.
|
|
306
|
+
|
|
307
|
+
Args:
|
|
308
|
+
message (str): The message to error.
|
|
309
|
+
flush (bool, optional): Whether to flush the message. Defaults to False.
|
|
310
|
+
max_length (int, optional): The maximum length of the message. Defaults to MAX_LENGTH.
|
|
311
|
+
"""
|
|
312
|
+
message = self._truncate_message(message, max_length)
|
|
209
313
|
self._get_logger().error(message, *args, **kwargs)
|
|
210
314
|
|
|
211
315
|
def exception(
|
|
212
|
-
self,
|
|
316
|
+
self,
|
|
317
|
+
message: str,
|
|
318
|
+
*args,
|
|
319
|
+
flush: bool = False,
|
|
320
|
+
max_length: int = MAX_LENGTH,
|
|
321
|
+
**kwargs,
|
|
213
322
|
) -> None:
|
|
323
|
+
"""Exception a message.
|
|
324
|
+
|
|
325
|
+
Args:
|
|
326
|
+
message (str): The message to exception.
|
|
327
|
+
flush (bool, optional): Whether to flush the message. Defaults to False.
|
|
328
|
+
max_length (int, optional): The maximum length of the message. Defaults to MAX_LENGTH.
|
|
329
|
+
"""
|
|
330
|
+
message = self._truncate_message(message, max_length)
|
|
214
331
|
self._get_logger().exception(message, *args, **kwargs)
|
|
215
332
|
|
|
216
333
|
def success(
|
|
217
|
-
self,
|
|
334
|
+
self,
|
|
335
|
+
message: str,
|
|
336
|
+
*args,
|
|
337
|
+
flush: bool = False,
|
|
338
|
+
max_length: int = MAX_LENGTH,
|
|
339
|
+
**kwargs,
|
|
218
340
|
) -> None:
|
|
341
|
+
"""Success a message.
|
|
342
|
+
|
|
343
|
+
Args:
|
|
344
|
+
message (str): The message to success.
|
|
345
|
+
flush (bool, optional): Whether to flush the message. Defaults to False.
|
|
346
|
+
max_length (int, optional): The maximum length of the message. Defaults to MAX_LENGTH.
|
|
347
|
+
"""
|
|
348
|
+
message = self._truncate_message(message, max_length)
|
|
219
349
|
self._get_logger().success(message, *args, **kwargs)
|
|
220
350
|
|
|
221
351
|
|
|
@@ -224,6 +354,7 @@ _LOGGER_CACHE: dict[str, FlockLogger] = {}
|
|
|
224
354
|
|
|
225
355
|
def get_logger(name: str = "flock", enable_logging: bool = True) -> FlockLogger:
|
|
226
356
|
"""Return a cached FlockLogger instance for the given name.
|
|
357
|
+
|
|
227
358
|
If the logger doesn't exist, create it.
|
|
228
359
|
If it does exist, update 'enable_logging' if a new value is passed.
|
|
229
360
|
"""
|
|
@@ -242,3 +373,27 @@ def get_module_loggers() -> list[FlockLogger]:
|
|
|
242
373
|
result.append(_LOGGER_CACHE[kvp])
|
|
243
374
|
|
|
244
375
|
return result
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
def truncate_for_logging(obj, max_item_length=100, max_items=10):
|
|
379
|
+
"""Truncate large data structures for logging purposes."""
|
|
380
|
+
if isinstance(obj, str) and len(obj) > max_item_length:
|
|
381
|
+
return (
|
|
382
|
+
obj[:max_item_length]
|
|
383
|
+
+ f"... ({len(obj) - max_item_length} more chars)"
|
|
384
|
+
)
|
|
385
|
+
elif isinstance(obj, dict):
|
|
386
|
+
if len(obj) > max_items:
|
|
387
|
+
return {
|
|
388
|
+
k: truncate_for_logging(v)
|
|
389
|
+
for i, (k, v) in enumerate(obj.items())
|
|
390
|
+
if i < max_items
|
|
391
|
+
}
|
|
392
|
+
return {k: truncate_for_logging(v) for k, v in obj.items()}
|
|
393
|
+
elif isinstance(obj, list):
|
|
394
|
+
if len(obj) > max_items:
|
|
395
|
+
return [truncate_for_logging(item) for item in obj[:max_items]] + [
|
|
396
|
+
f"... ({len(obj) - max_items} more items)"
|
|
397
|
+
]
|
|
398
|
+
return [truncate_for_logging(item) for item in obj]
|
|
399
|
+
return obj
|
flock/core/tools/basic_tools.py
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import importlib
|
|
4
4
|
import os
|
|
5
|
+
import re
|
|
5
6
|
from typing import Literal
|
|
6
7
|
|
|
7
8
|
from flock.core.interpreter.python_interpreter import PythonInterpreter
|
|
@@ -42,6 +43,13 @@ def web_search_duckduckgo(
|
|
|
42
43
|
raise
|
|
43
44
|
|
|
44
45
|
|
|
46
|
+
def extract_links_from_markdown(markdown: str, url: str) -> list:
|
|
47
|
+
# Regular expression to find all markdown links
|
|
48
|
+
link_pattern = re.compile(r"\[([^\]]+)\]\(([^)]+)\)")
|
|
49
|
+
links = link_pattern.findall(markdown)
|
|
50
|
+
return [url + link[1] for link in links]
|
|
51
|
+
|
|
52
|
+
|
|
45
53
|
@traced_and_logged
|
|
46
54
|
def get_web_content_as_markdown(url: str):
|
|
47
55
|
if (
|