flock-core 0.3.11__py3-none-any.whl → 0.3.14__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
@@ -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
@@ -150,7 +150,6 @@ class FlockAPI:
150
150
  "description": agent.description,
151
151
  "input_schema": agent.input,
152
152
  "output_schema": agent.output,
153
- "hand_off": agent.hand_off,
154
153
  }
155
154
  for agent in self.flock.agents.values()
156
155
  ]
@@ -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 Temporals built-in logger and skip debug/info/warning
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 function-based formatter for Loguru that.
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 that forcibly prints each log record and flushes immediately,
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
- pass # Already flushed on every write call.
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 debug(self, message: str, *args, flush: bool = False, **kwargs) -> None:
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(self, message: str, *args, flush: bool = False, **kwargs) -> None:
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, message: str, *args, flush: bool = False, **kwargs
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(self, message: str, *args, flush: bool = False, **kwargs) -> None:
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, message: str, *args, flush: bool = False, **kwargs
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, message: str, *args, flush: bool = False, **kwargs
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
@@ -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 (