agently 4.0.6.10__py3-none-any.whl → 4.0.7__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.
@@ -51,6 +51,7 @@ class TriggerFlow:
51
51
  self._skip_exceptions = skip_exceptions
52
52
  self._executions: dict[str, "TriggerFlowExecution"] = {}
53
53
  self._start_process = TriggerFlowProcess(
54
+ flow_chunk=self.chunk,
54
55
  trigger_event="START",
55
56
  blue_print=self._blue_print,
56
57
  block_data=TriggerFlowBlockData(
@@ -60,6 +61,8 @@ class TriggerFlow:
60
61
 
61
62
  self.chunks = self._blue_print.chunks
62
63
 
64
+ self.set_settings = self.settings.set_settings
65
+
63
66
  self.get_flow_data = self._flow_data.get
64
67
  self.set_flow_data = FunctionShifter.syncify(self.async_set_flow_data)
65
68
  self.append_flow_data = FunctionShifter.syncify(self.async_append_flow_data)
@@ -74,10 +77,6 @@ class TriggerFlow:
74
77
  self.start_execution = FunctionShifter.syncify(self.async_start_execution)
75
78
  self.start = FunctionShifter.syncify(self.async_start)
76
79
 
77
- def set_settings(self, key: str, value: "SerializableValue"):
78
- self.settings.set_settings(key, value)
79
- return self
80
-
81
80
  @overload
82
81
  def chunk(self, handler_or_name: "TriggerFlowHandler") -> TriggerFlowChunk: ...
83
82
 
@@ -100,13 +99,19 @@ class TriggerFlow:
100
99
  self._blue_print.chunks[handler_or_name.__name__] = chunk
101
100
  return chunk
102
101
 
103
- def create_execution(self, *, skip_exceptions: bool | None = None):
102
+ def create_execution(
103
+ self,
104
+ *,
105
+ skip_exceptions: bool | None = None,
106
+ concurrency: int | None = None,
107
+ ):
104
108
  execution_id = uuid.uuid4().hex
105
109
  skip_exceptions = skip_exceptions if skip_exceptions is not None else self._skip_exceptions
106
110
  execution = self._blue_print.create_execution(
107
111
  self,
108
112
  execution_id=execution_id,
109
113
  skip_exceptions=skip_exceptions,
114
+ concurrency=concurrency,
110
115
  )
111
116
  self._executions[execution_id] = execution
112
117
  return execution
@@ -119,8 +124,14 @@ class TriggerFlow:
119
124
  if execution.id in self._executions:
120
125
  del self._executions[execution.id]
121
126
 
122
- async def async_start_execution(self, initial_value: Any, *, wait_for_result: bool = False):
123
- execution = self.create_execution()
127
+ async def async_start_execution(
128
+ self,
129
+ initial_value: Any,
130
+ *,
131
+ wait_for_result: bool = False,
132
+ concurrency: int | None = None,
133
+ ):
134
+ execution = self.create_execution(concurrency=concurrency)
124
135
  await execution.async_start(initial_value, wait_for_result=wait_for_result)
125
136
  return execution
126
137
 
@@ -193,8 +204,9 @@ class TriggerFlow:
193
204
  *,
194
205
  wait_for_result: bool = True,
195
206
  timeout: int | None = 10,
207
+ concurrency: int | None = None,
196
208
  ):
197
- execution = await self.async_start_execution(initial_value)
209
+ execution = await self.async_start_execution(initial_value, concurrency=concurrency)
198
210
  if wait_for_result:
199
211
  return await execution.async_get_result(timeout=timeout)
200
212
 
@@ -203,8 +215,9 @@ class TriggerFlow:
203
215
  initial_value: Any = None,
204
216
  *,
205
217
  timeout: int | None = 10,
218
+ concurrency: int | None = None,
206
219
  ):
207
- execution = self.create_execution()
220
+ execution = self.create_execution(concurrency=concurrency)
208
221
  return execution.get_async_runtime_stream(
209
222
  initial_value,
210
223
  timeout=timeout,
@@ -215,8 +228,9 @@ class TriggerFlow:
215
228
  initial_value: Any = None,
216
229
  *,
217
230
  timeout: int | None = 10,
231
+ concurrency: int | None = None,
218
232
  ):
219
- execution = self.create_execution()
233
+ execution = self.create_execution(concurrency=concurrency)
220
234
  return execution.get_runtime_stream(
221
235
  initial_value,
222
236
  timeout=timeout,
@@ -14,10 +14,10 @@
14
14
 
15
15
 
16
16
  import uuid
17
- from asyncio import Event
17
+ from asyncio import Event, Semaphore
18
18
  from threading import Lock
19
19
 
20
- from typing import Any, Literal, TYPE_CHECKING, overload
20
+ from typing import Callable, Any, Literal, TYPE_CHECKING, overload, cast
21
21
  from typing_extensions import Self
22
22
 
23
23
 
@@ -31,15 +31,18 @@ from agently.types.trigger_flow import TriggerFlowBlockData
31
31
 
32
32
 
33
33
  class TriggerFlowBaseProcess:
34
+
34
35
  def __init__(
35
36
  self,
36
37
  *,
38
+ flow_chunk,
37
39
  trigger_event: str,
38
40
  blue_print: "TriggerFlowBluePrint",
39
41
  block_data: "TriggerFlowBlockData",
40
42
  trigger_type: Literal["event", "runtime_data", "flow_data"] = "event",
41
43
  **options,
42
44
  ):
45
+ self._flow_chunk = flow_chunk
43
46
  self.trigger_event = trigger_event
44
47
  self.trigger_type: Literal["event", "runtime_data", "flow_data"] = trigger_type
45
48
  self._blue_print = blue_print
@@ -55,6 +58,7 @@ class TriggerFlowBaseProcess:
55
58
  **options,
56
59
  ):
57
60
  return type(self)(
61
+ flow_chunk=self._flow_chunk,
58
62
  trigger_event=trigger_event,
59
63
  trigger_type=trigger_type,
60
64
  blue_print=blue_print,
@@ -112,8 +116,12 @@ class TriggerFlowBaseProcess:
112
116
  if isinstance(trigger_or_triggers, TriggerFlowChunk):
113
117
  trigger_or_triggers = trigger_or_triggers.trigger
114
118
  if isinstance(trigger_or_triggers, str):
119
+ if trigger_or_triggers in self._blue_print.chunks:
120
+ trigger = self._blue_print.chunks[trigger_or_triggers].trigger
121
+ else:
122
+ trigger = trigger_or_triggers
115
123
  return self._new(
116
- trigger_event=trigger_or_triggers,
124
+ trigger_event=trigger,
117
125
  trigger_type="event",
118
126
  blue_print=self._blue_print,
119
127
  block_data=TriggerFlowBlockData(
@@ -225,9 +233,10 @@ class TriggerFlowBaseProcess:
225
233
  elif isinstance(chunk, tuple):
226
234
  chunk_name = chunk[0]
227
235
  chunk_func = chunk[1]
228
- chunk = TriggerFlowChunk(chunk_func, name=chunk_name)
236
+ chunk = self._flow_chunk(chunk_name)(chunk_func)
229
237
  else:
230
- chunk = TriggerFlowChunk(chunk, name=name) if callable(chunk) else chunk
238
+ chunk = self._flow_chunk(name or chunk.__name__)(chunk) if callable(chunk) else chunk
239
+ assert isinstance(chunk, TriggerFlowChunk)
231
240
  self._blue_print.add_handler(
232
241
  self.trigger_type,
233
242
  self.trigger_event,
@@ -257,11 +266,13 @@ class TriggerFlowBaseProcess:
257
266
  self,
258
267
  *chunks: "TriggerFlowChunk | TriggerFlowHandler | tuple[str, TriggerFlowHandler]",
259
268
  side_branch: bool = False,
269
+ concurrency: int | None = None,
260
270
  ):
261
271
  batch_trigger = f"Batch-{ uuid.uuid4().hex }"
262
272
  results = {}
263
273
  triggers_to_wait = {}
264
274
  trigger_to_chunk_name = {}
275
+ semaphore = Semaphore(concurrency) if concurrency and concurrency > 0 else None
265
276
 
266
277
  async def wait_all_chunks(data: "TriggerFlowEventData"):
267
278
  if data.event in triggers_to_wait:
@@ -280,16 +291,24 @@ class TriggerFlowBaseProcess:
280
291
  if isinstance(chunk, tuple):
281
292
  chunk_name = chunk[0]
282
293
  chunk_func = chunk[1]
283
- chunk = TriggerFlowChunk(chunk_func, name=chunk_name)
294
+ chunk = self._flow_chunk(chunk_name)(chunk_func)
284
295
  else:
285
- chunk = TriggerFlowChunk(chunk) if callable(chunk) else chunk
296
+ chunk = self._flow_chunk(chunk.__name__)(chunk) if callable(chunk) else chunk
286
297
  triggers_to_wait[chunk.trigger] = False
287
298
  trigger_to_chunk_name[chunk.trigger] = chunk.name
288
299
  results[chunk.name] = None
300
+
301
+ if semaphore is None:
302
+ handler = chunk.async_call
303
+ else:
304
+ async def handler(data: "TriggerFlowEventData", _chunk=chunk):
305
+ async with semaphore:
306
+ return await _chunk.async_call(data)
307
+
289
308
  self._blue_print.add_handler(
290
309
  self.trigger_type,
291
310
  self.trigger_event,
292
- chunk.async_call,
311
+ handler,
293
312
  )
294
313
  self._blue_print.add_event_handler(chunk.trigger, wait_all_chunks)
295
314
 
@@ -22,7 +22,7 @@ from agently.utils import RuntimeDataNamespace
22
22
 
23
23
 
24
24
  class TriggerFlowForEachProcess(TriggerFlowBaseProcess):
25
- def for_each(self):
25
+ def for_each(self, *, concurrency: int | None = None):
26
26
  for_each_id = uuid.uuid4().hex
27
27
  for_each_block_data = TriggerFlowBlockData(
28
28
  outer_block=self._block_data,
@@ -31,6 +31,7 @@ class TriggerFlowForEachProcess(TriggerFlowBaseProcess):
31
31
  },
32
32
  )
33
33
  send_item_trigger = f"ForEach-{ for_each_id }-Send"
34
+ semaphore = asyncio.Semaphore(concurrency) if concurrency and concurrency > 0 else None
34
35
 
35
36
  async def send_items(data: "TriggerFlowEventData"):
36
37
  data.layer_in()
@@ -38,33 +39,38 @@ class TriggerFlowForEachProcess(TriggerFlowBaseProcess):
38
39
  assert for_each_instance_id is not None
39
40
 
40
41
  send_tasks = []
41
- if not isinstance(data.value, str) and isinstance(data.value, Sequence):
42
- items = list(data.value)
43
- for item in items:
44
- data.layer_in()
45
- item_id = data.layer_mark
46
- assert item_id is not None
47
- data._system_runtime_data.set(f"for_each_results.{ for_each_instance_id }.{ item_id }", EMPTY)
48
- send_tasks.append(
49
- data.async_emit(
50
- send_item_trigger,
51
- item,
52
- data._layer_marks.copy(),
53
- )
54
- )
55
- data.layer_out()
56
- await asyncio.gather(*send_tasks)
57
- else:
42
+ def prepare_item(item):
58
43
  data.layer_in()
59
44
  item_id = data.layer_mark
60
45
  assert item_id is not None
46
+ layer_marks = data._layer_marks.copy()
61
47
  data._system_runtime_data.set(f"for_each_results.{ for_each_instance_id }.{ item_id }", EMPTY)
62
- await data.async_emit(
63
- send_item_trigger,
64
- data.value,
65
- data._layer_marks.copy(),
66
- )
67
48
  data.layer_out()
49
+ return item_id, layer_marks, item
50
+
51
+ async def emit_item(item, layer_marks):
52
+ if semaphore is None:
53
+ await data.async_emit(
54
+ send_item_trigger,
55
+ item,
56
+ layer_marks,
57
+ )
58
+ else:
59
+ async with semaphore:
60
+ await data.async_emit(
61
+ send_item_trigger,
62
+ item,
63
+ layer_marks,
64
+ )
65
+ if not isinstance(data.value, str) and isinstance(data.value, Sequence):
66
+ items = list(data.value)
67
+ for item in items:
68
+ _, layer_marks, item_value = prepare_item(item)
69
+ send_tasks.append(emit_item(item_value, layer_marks))
70
+ await asyncio.gather(*send_tasks)
71
+ else:
72
+ _, layer_marks, item_value = prepare_item(data.value)
73
+ await emit_item(item_value, layer_marks)
68
74
 
69
75
  self.to(send_items)
70
76
 
@@ -1,3 +1,18 @@
1
+ # Copyright 2023-2025 AgentEra(Agently.Tech)
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+
1
16
  from agently.utils import LazyImport
2
17
 
3
18
  LazyImport.import_package("chromadb")
@@ -24,7 +24,16 @@ if TYPE_CHECKING:
24
24
  from agently.types.data.serializable import SerializableValue
25
25
 
26
26
  AgentlyModelResponseEvent = Literal[
27
- "error", "original_delta", "delta", "tool_calls", "original_done", "done", "meta", "extra"
27
+ "error",
28
+ "original_delta",
29
+ "reasoning_delta",
30
+ "delta",
31
+ "tool_calls",
32
+ "original_done",
33
+ "reasoning_done",
34
+ "done",
35
+ "meta",
36
+ "extra",
28
37
  ]
29
38
 
30
39
  AgentlyModelResponseMessage: TypeAlias = tuple[AgentlyModelResponseEvent, Any]
@@ -77,29 +77,39 @@ class ResponseParser(AgentlyPlugin, Protocol):
77
77
  def get_async_generator(
78
78
  self,
79
79
  type: Literal["instant", "streaming_parse"],
80
+ *,
81
+ specific: list[str] | str | None = None,
80
82
  ) -> AsyncGenerator["StreamingData", None]: ...
81
83
 
82
84
  @overload
83
85
  def get_async_generator(
84
86
  self,
85
87
  type: Literal["all"],
88
+ *,
89
+ specific: list[str] | str | None = None,
86
90
  ) -> AsyncGenerator[tuple[str, Any], None]: ...
87
91
 
88
92
  @overload
89
93
  def get_async_generator(
90
94
  self,
91
- type: Literal["delta", "typed_delta", "original"],
95
+ type: Literal["delta", "specific", "original"],
96
+ *,
97
+ specific: list[str] | str | None = None,
92
98
  ) -> AsyncGenerator[str, None]: ...
93
99
 
94
100
  @overload
95
101
  def get_async_generator(
96
102
  self,
97
- type: Literal["all", "original", "delta", "typed_delta", "instant", "streaming_parse"] | None = "delta",
103
+ type: Literal["all", "original", "delta", "specific", "instant", "streaming_parse"] | None = "delta",
104
+ *,
105
+ specific: list[str] | str | None = None,
98
106
  ) -> AsyncGenerator: ...
99
107
 
100
108
  def get_async_generator(
101
109
  self,
102
- type: Literal["all", "original", "delta", "typed_delta", "instant", "streaming_parse"] | None = "delta",
110
+ type: Literal["all", "original", "delta", "specific", "instant", "streaming_parse"] | None = "delta",
111
+ *,
112
+ specific: list[str] | str | None = None,
103
113
  ) -> AsyncGenerator:
104
114
  """
105
115
  'instant' is Agently v3 compatible for 'streaming_parse'
@@ -110,29 +120,39 @@ class ResponseParser(AgentlyPlugin, Protocol):
110
120
  def get_generator(
111
121
  self,
112
122
  type: Literal["instant", "streaming_parse"],
123
+ *,
124
+ specific: list[str] | str | None = None,
113
125
  ) -> Generator["StreamingData", None, None]: ...
114
126
 
115
127
  @overload
116
128
  def get_generator(
117
129
  self,
118
130
  type: Literal["all"],
131
+ *,
132
+ specific: list[str] | str | None = None,
119
133
  ) -> Generator[tuple[str, Any], None, None]: ...
120
134
 
121
135
  @overload
122
136
  def get_generator(
123
137
  self,
124
- type: Literal["delta", "typed_delta", "original"],
138
+ type: Literal["delta", "specific", "original"],
139
+ *,
140
+ specific: list[str] | str | None = None,
125
141
  ) -> Generator[str, None, None]: ...
126
142
 
127
143
  @overload
128
144
  def get_generator(
129
145
  self,
130
- type: Literal["all", "original", "delta", "typed_delta", "instant", "streaming_parse"] | None = "delta",
146
+ type: Literal["all", "original", "delta", "specific", "instant", "streaming_parse"] | None = "delta",
147
+ *,
148
+ specific: list[str] | str | None = None,
131
149
  ) -> Generator: ...
132
150
 
133
151
  def get_generator(
134
152
  self,
135
- type: Literal["all", "original", "delta", "typed_delta", "instant", "streaming_parse"] | None = "delta",
153
+ type: Literal["all", "original", "delta", "specific", "instant", "streaming_parse"] | None = "delta",
154
+ *,
155
+ specific: list[str] | str | None = None,
136
156
  ) -> Generator:
137
157
  """
138
158
  'instant' is Agently v3 compatible for 'streaming_parse'
@@ -12,12 +12,14 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
+ import re
15
16
  import datetime
16
17
  import warnings
17
18
  from typing import (
18
19
  Any,
19
20
  Literal,
20
21
  Mapping,
22
+ Sequence,
21
23
  Union,
22
24
  get_origin,
23
25
  get_args,
@@ -29,9 +31,12 @@ from pydantic import BaseModel
29
31
 
30
32
  if TYPE_CHECKING:
31
33
  from agently.types.data import SerializableValue, KwargsType
34
+ from re import Pattern
32
35
 
33
36
  T = TypeVar("T")
34
37
 
38
+ DEFAULT_PLACEHOLDER_PATTERN = re.compile(r"\$\{\s*([^}]+?)\s*\}")
39
+
35
40
 
36
41
  class DataFormatter:
37
42
  @staticmethod
@@ -246,3 +251,75 @@ class DataFormatter:
246
251
  return kwargs_format or None
247
252
 
248
253
  return None
254
+
255
+ @staticmethod
256
+ def substitute_placeholder(
257
+ obj: T,
258
+ variable_mappings: dict[str, Any],
259
+ *,
260
+ placeholder_pattern: "Pattern | None" = None,
261
+ ) -> T | Any:
262
+ if placeholder_pattern is None:
263
+ placeholder_pattern = DEFAULT_PLACEHOLDER_PATTERN
264
+
265
+ if not isinstance(variable_mappings, dict):
266
+ raise TypeError(f"Variable mappings require a dictionary but got: { variable_mappings }")
267
+
268
+ if isinstance(obj, str):
269
+ full_match = placeholder_pattern.fullmatch(obj)
270
+ if full_match:
271
+ key = full_match.group(1).strip()
272
+ return variable_mappings.get(key, obj)
273
+ else:
274
+
275
+ def replacer(match):
276
+ key = match.group(1).strip()
277
+ return str(variable_mappings.get(key, match.group(0)))
278
+
279
+ return placeholder_pattern.sub(replacer, obj)
280
+
281
+ if isinstance(obj, Mapping):
282
+ return {
283
+ DataFormatter.substitute_placeholder(
284
+ key,
285
+ variable_mappings,
286
+ placeholder_pattern=placeholder_pattern,
287
+ ): DataFormatter.substitute_placeholder(
288
+ value,
289
+ variable_mappings,
290
+ placeholder_pattern=placeholder_pattern,
291
+ )
292
+ for key, value in obj.items()
293
+ }
294
+
295
+ if isinstance(obj, Sequence) and not isinstance(obj, (str, bytes, bytearray)):
296
+ if isinstance(obj, tuple):
297
+ return tuple(
298
+ DataFormatter.substitute_placeholder(
299
+ value,
300
+ variable_mappings,
301
+ placeholder_pattern=placeholder_pattern,
302
+ )
303
+ for value in obj
304
+ )
305
+ else:
306
+ return [
307
+ DataFormatter.substitute_placeholder(
308
+ value,
309
+ variable_mappings,
310
+ placeholder_pattern=placeholder_pattern,
311
+ )
312
+ for value in obj
313
+ ]
314
+
315
+ if isinstance(obj, set):
316
+ return {
317
+ DataFormatter.substitute_placeholder(
318
+ value,
319
+ variable_mappings,
320
+ placeholder_pattern=placeholder_pattern,
321
+ )
322
+ for value in obj
323
+ }
324
+
325
+ return obj
@@ -0,0 +1,101 @@
1
+ # Copyright 2023-2025 AgentEra(Agently.Tech)
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+
16
+ from types import MappingProxyType
17
+ from typing import Any
18
+
19
+ SAFE_BUILTINS = {
20
+ "abs": abs,
21
+ "min": min,
22
+ "max": max,
23
+ "sum": sum,
24
+ "len": len,
25
+ "range": range,
26
+ "enumerate": enumerate,
27
+ "list": list,
28
+ "dict": dict,
29
+ "set": set,
30
+ "tuple": tuple,
31
+ "print": print,
32
+ "sorted": sorted,
33
+ "str": str,
34
+ "int": int,
35
+ "float": float,
36
+ "list": list,
37
+ "dict": dict,
38
+ "bool": bool,
39
+ }
40
+
41
+ SAFE_TYPES = [int, float, str, list, dict, set, tuple, bool, type(None)]
42
+
43
+
44
+ class PythonSandbox:
45
+ def __init__(
46
+ self,
47
+ preset_objects: dict[str, object] | None = None,
48
+ base_vars: dict[str, Any] | None = None,
49
+ allowed_return_types: list[type] = SAFE_TYPES,
50
+ ):
51
+ self.preset_objects = preset_objects or {}
52
+ self.base_vars = base_vars or {}
53
+ self.allowed_return_types = allowed_return_types
54
+
55
+ self.safe_globals = MappingProxyType(
56
+ {
57
+ "__builtins__": SAFE_BUILTINS,
58
+ **self.preset_objects,
59
+ **self.base_vars,
60
+ }
61
+ )
62
+
63
+ def _check_safe_value(self, value):
64
+ if isinstance(value, tuple(self.allowed_return_types)):
65
+ return value
66
+ raise ValueError(f"Type of return '{ type(value) }' can not be used in Python Sandbox.")
67
+
68
+ def _wrap_obj(self, obj):
69
+ sandbox = self
70
+
71
+ class ObjectWrapper:
72
+ def __init__(self, obj):
73
+ self._obj = obj
74
+
75
+ def __getattr__(self, name):
76
+ if name.startswith("_"):
77
+ raise AttributeError(f"Can not access private attribute '{name}'.")
78
+ attr = getattr(self._obj, name)
79
+ if callable(attr):
80
+
81
+ def method(*args, **kwargs):
82
+ result = attr(*args, **kwargs)
83
+ return sandbox._check_safe_value(result)
84
+
85
+ return method
86
+ return sandbox._check_safe_value(attr)
87
+
88
+ self.allowed_return_types.append(ObjectWrapper)
89
+
90
+ return ObjectWrapper(obj)
91
+
92
+ def run(self, code: str):
93
+ safe_objects = {k: self._wrap_obj(v) for k, v in self.preset_objects.items()}
94
+ globals_dict = dict(self.safe_globals)
95
+ globals_dict.update(safe_objects)
96
+
97
+ local_vars = {}
98
+ exec(code, globals_dict, local_vars)
99
+ for k, v in local_vars.items():
100
+ self._check_safe_value(v)
101
+ return local_vars
agently/utils/Settings.py CHANGED
@@ -12,11 +12,14 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
+ import re
15
16
  import json
16
17
  import yaml
17
18
  import toml
18
19
  from typing import TYPE_CHECKING, Literal, cast
19
- from agently.utils import SerializableRuntimeData, SerializableRuntimeDataNamespace
20
+ from .SerializableRuntimeData import SerializableRuntimeData, SerializableRuntimeDataNamespace
21
+ from .LazyImport import LazyImport
22
+ from .DataFormatter import DataFormatter
20
23
 
21
24
  if TYPE_CHECKING:
22
25
  from agently.types.data import SerializableData, SerializableValue
@@ -114,7 +117,21 @@ class Settings(SerializableRuntimeData):
114
117
  else:
115
118
  raise TypeError(f"[Agently Settings] Cannot load parsed data, expect dictionary type, got: { type(data) }")
116
119
 
117
- def set_settings(self, key: str, value: "SerializableValue"):
120
+ def set_settings(self, key: str, value: "SerializableValue", *, auto_load_env: bool = False):
121
+ if auto_load_env:
122
+ import os
123
+
124
+ LazyImport.import_package("dotenv")
125
+ from dotenv import load_dotenv, find_dotenv
126
+
127
+ load_dotenv(find_dotenv())
128
+
129
+ environ = dict(os.environ)
130
+ value = DataFormatter.substitute_placeholder(
131
+ value,
132
+ environ,
133
+ placeholder_pattern=re.compile(r"\$\{\s*ENV\.([^}]+?)\s*\}"),
134
+ )
118
135
  if key in self._path_mappings:
119
136
  self.update({str(self._path_mappings[key]): value})
120
137
  return self
agently/utils/__init__.py CHANGED
@@ -27,3 +27,4 @@ from .DataLocator import DataLocator
27
27
  from .GeneratorConsumer import GeneratorConsumer
28
28
  from .StreamingJSONCompleter import StreamingJSONCompleter
29
29
  from .StreamingJSONParser import StreamingJSONParser
30
+ from .PythonSandbox import PythonSandbox
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agently
3
- Version: 4.0.6.10
3
+ Version: 4.0.7
4
4
  Summary:
5
5
  License: Apache-2.0
6
6
  License-File: LICENSE