agently 4.0.6.11__tar.gz → 4.0.7.1__tar.gz

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.
Files changed (77) hide show
  1. {agently-4.0.6.11 → agently-4.0.7.1}/PKG-INFO +1 -1
  2. {agently-4.0.6.11 → agently-4.0.7.1}/agently/_default_settings.yaml +2 -1
  3. {agently-4.0.6.11 → agently-4.0.7.1}/agently/base.py +17 -1
  4. {agently-4.0.6.11 → agently-4.0.7.1}/agently/builtins/plugins/PromptGenerator/AgentlyPromptGenerator.py +21 -5
  5. {agently-4.0.6.11 → agently-4.0.7.1}/agently/builtins/tools/Search.py +1 -1
  6. {agently-4.0.6.11 → agently-4.0.7.1}/agently/core/ModelRequest.py +9 -1
  7. {agently-4.0.6.11 → agently-4.0.7.1}/agently/core/TriggerFlow/BluePrint.py +2 -0
  8. {agently-4.0.6.11 → agently-4.0.7.1}/agently/core/TriggerFlow/Execution.py +28 -11
  9. {agently-4.0.6.11 → agently-4.0.7.1}/agently/core/TriggerFlow/TriggerFlow.py +22 -7
  10. {agently-4.0.6.11 → agently-4.0.7.1}/agently/core/TriggerFlow/process/BaseProcess.py +16 -2
  11. {agently-4.0.6.11 → agently-4.0.7.1}/agently/core/TriggerFlow/process/ForEachProcess.py +29 -23
  12. {agently-4.0.6.11 → agently-4.0.7.1}/agently/utils/DataFormatter.py +8 -2
  13. {agently-4.0.6.11 → agently-4.0.7.1}/agently/utils/DataLocator.py +108 -31
  14. {agently-4.0.6.11 → agently-4.0.7.1}/pyproject.toml +1 -1
  15. {agently-4.0.6.11 → agently-4.0.7.1}/LICENSE +0 -0
  16. {agently-4.0.6.11 → agently-4.0.7.1}/README.md +0 -0
  17. {agently-4.0.6.11 → agently-4.0.7.1}/agently/__init__.py +0 -0
  18. {agently-4.0.6.11 → agently-4.0.7.1}/agently/_default_init.py +0 -0
  19. {agently-4.0.6.11 → agently-4.0.7.1}/agently/builtins/agent_extensions/AutoFuncExtension.py +0 -0
  20. {agently-4.0.6.11 → agently-4.0.7.1}/agently/builtins/agent_extensions/ChatSessionExtension.py +0 -0
  21. {agently-4.0.6.11 → agently-4.0.7.1}/agently/builtins/agent_extensions/ConfigurePromptExtension.py +0 -0
  22. {agently-4.0.6.11 → agently-4.0.7.1}/agently/builtins/agent_extensions/KeyWaiterExtension.py +0 -0
  23. {agently-4.0.6.11 → agently-4.0.7.1}/agently/builtins/agent_extensions/ToolExtension.py +0 -0
  24. {agently-4.0.6.11 → agently-4.0.7.1}/agently/builtins/agent_extensions/__init__.py +0 -0
  25. {agently-4.0.6.11 → agently-4.0.7.1}/agently/builtins/hookers/ConsoleHooker.py +0 -0
  26. {agently-4.0.6.11 → agently-4.0.7.1}/agently/builtins/hookers/PureLoggerHooker.py +0 -0
  27. {agently-4.0.6.11 → agently-4.0.7.1}/agently/builtins/hookers/SystemMessageHooker.py +0 -0
  28. {agently-4.0.6.11 → agently-4.0.7.1}/agently/builtins/plugins/ModelRequester/OpenAICompatible.py +0 -0
  29. {agently-4.0.6.11 → agently-4.0.7.1}/agently/builtins/plugins/ResponseParser/AgentlyResponseParser.py +0 -0
  30. {agently-4.0.6.11 → agently-4.0.7.1}/agently/builtins/plugins/ToolManager/AgentlyToolManager.py +0 -0
  31. {agently-4.0.6.11 → agently-4.0.7.1}/agently/builtins/plugins/__init__.py +0 -0
  32. {agently-4.0.6.11 → agently-4.0.7.1}/agently/builtins/tools/Browse.py +0 -0
  33. {agently-4.0.6.11 → agently-4.0.7.1}/agently/builtins/tools/__init__.py +0 -0
  34. {agently-4.0.6.11 → agently-4.0.7.1}/agently/core/Agent.py +0 -0
  35. {agently-4.0.6.11 → agently-4.0.7.1}/agently/core/EventCenter.py +0 -0
  36. {agently-4.0.6.11 → agently-4.0.7.1}/agently/core/ExtensionHandlers.py +0 -0
  37. {agently-4.0.6.11 → agently-4.0.7.1}/agently/core/PluginManager.py +0 -0
  38. {agently-4.0.6.11 → agently-4.0.7.1}/agently/core/Prompt.py +0 -0
  39. {agently-4.0.6.11 → agently-4.0.7.1}/agently/core/Tool.py +0 -0
  40. {agently-4.0.6.11 → agently-4.0.7.1}/agently/core/TriggerFlow/Chunk.py +0 -0
  41. {agently-4.0.6.11 → agently-4.0.7.1}/agently/core/TriggerFlow/Process.py +0 -0
  42. {agently-4.0.6.11 → agently-4.0.7.1}/agently/core/TriggerFlow/__init__.py +0 -0
  43. {agently-4.0.6.11 → agently-4.0.7.1}/agently/core/TriggerFlow/process/MatchCaseProcess.py +0 -0
  44. {agently-4.0.6.11 → agently-4.0.7.1}/agently/core/TriggerFlow/process/__init__.py +0 -0
  45. {agently-4.0.6.11 → agently-4.0.7.1}/agently/core/__init__.py +0 -0
  46. {agently-4.0.6.11 → agently-4.0.7.1}/agently/integrations/chromadb.py +0 -0
  47. {agently-4.0.6.11 → agently-4.0.7.1}/agently/types/__init__.py +0 -0
  48. {agently-4.0.6.11 → agently-4.0.7.1}/agently/types/data/__init__.py +0 -0
  49. {agently-4.0.6.11 → agently-4.0.7.1}/agently/types/data/event.py +0 -0
  50. {agently-4.0.6.11 → agently-4.0.7.1}/agently/types/data/prompt.py +0 -0
  51. {agently-4.0.6.11 → agently-4.0.7.1}/agently/types/data/request.py +0 -0
  52. {agently-4.0.6.11 → agently-4.0.7.1}/agently/types/data/response.py +0 -0
  53. {agently-4.0.6.11 → agently-4.0.7.1}/agently/types/data/serializable.py +0 -0
  54. {agently-4.0.6.11 → agently-4.0.7.1}/agently/types/data/tool.py +0 -0
  55. {agently-4.0.6.11 → agently-4.0.7.1}/agently/types/plugins/EventHooker.py +0 -0
  56. {agently-4.0.6.11 → agently-4.0.7.1}/agently/types/plugins/ModelRequester.py +0 -0
  57. {agently-4.0.6.11 → agently-4.0.7.1}/agently/types/plugins/PromptGenerator.py +0 -0
  58. {agently-4.0.6.11 → agently-4.0.7.1}/agently/types/plugins/ResponseParser.py +0 -0
  59. {agently-4.0.6.11 → agently-4.0.7.1}/agently/types/plugins/ToolManager.py +0 -0
  60. {agently-4.0.6.11 → agently-4.0.7.1}/agently/types/plugins/__init__.py +0 -0
  61. {agently-4.0.6.11 → agently-4.0.7.1}/agently/types/plugins/base.py +0 -0
  62. {agently-4.0.6.11 → agently-4.0.7.1}/agently/types/trigger_flow/__init__.py +0 -0
  63. {agently-4.0.6.11 → agently-4.0.7.1}/agently/types/trigger_flow/trigger_flow.py +0 -0
  64. {agently-4.0.6.11 → agently-4.0.7.1}/agently/utils/DataPathBuilder.py +0 -0
  65. {agently-4.0.6.11 → agently-4.0.7.1}/agently/utils/FunctionShifter.py +0 -0
  66. {agently-4.0.6.11 → agently-4.0.7.1}/agently/utils/GeneratorConsumer.py +0 -0
  67. {agently-4.0.6.11 → agently-4.0.7.1}/agently/utils/LazyImport.py +0 -0
  68. {agently-4.0.6.11 → agently-4.0.7.1}/agently/utils/Logger.py +0 -0
  69. {agently-4.0.6.11 → agently-4.0.7.1}/agently/utils/Messenger.py +0 -0
  70. {agently-4.0.6.11 → agently-4.0.7.1}/agently/utils/PythonSandbox.py +0 -0
  71. {agently-4.0.6.11 → agently-4.0.7.1}/agently/utils/RuntimeData.py +0 -0
  72. {agently-4.0.6.11 → agently-4.0.7.1}/agently/utils/SerializableRuntimeData.py +0 -0
  73. {agently-4.0.6.11 → agently-4.0.7.1}/agently/utils/Settings.py +0 -0
  74. {agently-4.0.6.11 → agently-4.0.7.1}/agently/utils/Storage.py +0 -0
  75. {agently-4.0.6.11 → agently-4.0.7.1}/agently/utils/StreamingJSONCompleter.py +0 -0
  76. {agently-4.0.6.11 → agently-4.0.7.1}/agently/utils/StreamingJSONParser.py +0 -0
  77. {agently-4.0.6.11 → agently-4.0.7.1}/agently/utils/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agently
3
- Version: 4.0.6.11
3
+ Version: 4.0.7.1
4
4
  Summary:
5
5
  License: Apache-2.0
6
6
  License-File: LICENSE
@@ -43,6 +43,7 @@ runtime:
43
43
  show_model_logs: False
44
44
  show_tool_logs: False
45
45
  show_trigger_flow_logs: False
46
+ httpx_log_level: "WARNING"
46
47
  plugins:
47
48
  ToolManager:
48
- activate: AgentlyToolManager
49
+ activate: AgentlyToolManager
@@ -12,6 +12,7 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
+ import logging
15
16
  from typing import Any, Literal, Type, TYPE_CHECKING, TypeVar, Generic, cast
16
17
 
17
18
  from agently.utils import Settings, create_logger, FunctionShifter, DataFormatter
@@ -37,6 +38,10 @@ _hook_default_event_handlers(event_center)
37
38
  async_system_message = event_center.async_system_message
38
39
  system_message = event_center.system_message
39
40
  logger = create_logger()
41
+ httpx_level_name = settings.get("runtime.httpx_log_level", "WARNING")
42
+ httpx_level = getattr(logging, str(httpx_level_name).upper(), logging.WARNING)
43
+ logging.getLogger("httpx").setLevel(httpx_level)
44
+ logging.getLogger("httpcore").setLevel(httpx_level)
40
45
  tool = Tool(plugin_manager, settings)
41
46
  _agently_messenger = event_center.create_messenger("Agently")
42
47
 
@@ -70,11 +75,13 @@ settings.update_mappings(
70
75
  "runtime.show_model_logs": True,
71
76
  "runtime.show_tool_logs": True,
72
77
  "runtime.show_trigger_flow_logs": True,
78
+ "runtime.httpx_log_level": "INFO",
73
79
  },
74
80
  False: {
75
81
  "runtime.show_model_logs": False,
76
82
  "runtime.show_tool_logs": False,
77
83
  "runtime.show_trigger_flow_logs": False,
84
+ "runtime.httpx_log_level": "WARNING",
78
85
  },
79
86
  }
80
87
  }
@@ -117,7 +124,16 @@ class AgentlyMain(Generic[A]):
117
124
  self.tool = tool
118
125
  self.AgentType = AgentType
119
126
 
120
- self.set_settings = self.settings.set_settings
127
+ def set_settings(key: str, value: "SerializableValue", *, auto_load_env: bool = False):
128
+ self.settings.set_settings(key, value, auto_load_env=auto_load_env)
129
+ if key in ("runtime.httpx_log_level", "debug"):
130
+ level_name = self.settings.get("runtime.httpx_log_level", "WARNING")
131
+ level = getattr(logging, str(level_name).upper(), logging.WARNING)
132
+ logging.getLogger("httpx").setLevel(level)
133
+ logging.getLogger("httpcore").setLevel(level)
134
+ return self
135
+
136
+ self.set_settings = set_settings
121
137
 
122
138
  def set_debug_console(self, debug_console_status: Literal["ON", "OFF"]):
123
139
  match debug_console_status:
@@ -589,13 +589,29 @@ class AgentlyPromptGenerator(PromptGenerator):
589
589
  fields = {}
590
590
  validators = {}
591
591
 
592
- def ensure_list_and_cast(v: Any, target_type: type):
592
+ def ensure_list_and_cast(v: Any, target_type: Any):
593
593
  if not isinstance(v, list):
594
594
  v = [v]
595
- return [
596
- (target_type(item) if target_type is not Any and not isinstance(item, target_type) else item)
597
- for item in v
598
- ]
595
+ casted = []
596
+ for item in v:
597
+ if target_type is Any or not isinstance(target_type, type):
598
+ casted.append(item)
599
+ continue
600
+ target_type = cast(type, target_type)
601
+ if isinstance(item, target_type):
602
+ casted.append(item)
603
+ continue
604
+ if isinstance(item, Mapping):
605
+ model_validate = getattr(target_type, "model_validate", None)
606
+ if callable(model_validate):
607
+ casted.append(model_validate(item))
608
+ continue
609
+ parse_obj = getattr(target_type, "parse_obj", None)
610
+ if callable(parse_obj):
611
+ casted.append(parse_obj(item))
612
+ continue
613
+ casted.append(target_type(item))
614
+ return casted
599
615
 
600
616
  if isinstance(schema, Mapping):
601
617
  for field_name, field_type_schema in schema.items():
@@ -103,7 +103,7 @@ class Search:
103
103
  ] = "us-en",
104
104
  options: dict[str, Any] | None = None,
105
105
  ):
106
- LazyImport.import_package("ddgs")
106
+ LazyImport.import_package("ddgs", version_constraint=">=9.10.0")
107
107
  from ddgs import DDGS
108
108
 
109
109
  self.proxy = proxy
@@ -153,6 +153,11 @@ class ModelResponseResult:
153
153
  return await self._response_parser.async_get_data(type=type)
154
154
  return await self._response_parser.async_get_data(type=type)
155
155
 
156
+ @overload
157
+ async def async_get_data_object(
158
+ self,
159
+ ) -> "BaseModel | None": ...
160
+
156
161
  @overload
157
162
  async def async_get_data_object(
158
163
  self,
@@ -432,6 +437,9 @@ class ModelRequest:
432
437
  self.get_data = FunctionShifter.syncify(self.async_get_data)
433
438
  self.get_data_object = FunctionShifter.syncify(self.async_get_data_object)
434
439
 
440
+ self.start = self.get_data
441
+ self.async_start = self.async_get_data
442
+
435
443
  def set_prompt(
436
444
  self,
437
445
  key: "PromptStandardSlot | str",
@@ -464,7 +472,7 @@ class ModelRequest:
464
472
  prompt: Any,
465
473
  mappings: dict[str, Any] | None = None,
466
474
  ):
467
- self.prompt.set("system", ["YOU MUST REACT AND RESPOND AS {system.role}!"])
475
+ self.prompt.set("system", ["YOU MUST REACT AND RESPOND AS {system.your_role}!"])
468
476
  self.prompt.set("system.your_role", prompt, mappings)
469
477
  return self
470
478
 
@@ -132,6 +132,7 @@ class TriggerFlowBluePrint:
132
132
  *,
133
133
  execution_id: str | None = None,
134
134
  skip_exceptions: bool = False,
135
+ concurrency: int | None = None,
135
136
  ):
136
137
  handlers_snapshot: TriggerFlowAllHandlers = {
137
138
  "event": {k: v.copy() for k, v in self._handlers["event"].items()},
@@ -143,6 +144,7 @@ class TriggerFlowBluePrint:
143
144
  trigger_flow=trigger_flow,
144
145
  id=execution_id,
145
146
  skip_exceptions=skip_exceptions,
147
+ concurrency=concurrency,
146
148
  )
147
149
 
148
150
  def copy(self, *, name: str | None = None):
@@ -16,6 +16,7 @@
16
16
  import uuid
17
17
  import asyncio
18
18
  import warnings
19
+ from contextvars import ContextVar
19
20
 
20
21
  from typing import Any, Literal, TYPE_CHECKING
21
22
 
@@ -37,6 +38,7 @@ class TriggerFlowExecution:
37
38
  trigger_flow: "TriggerFlow",
38
39
  id: str | None = None,
39
40
  skip_exceptions: bool = False,
41
+ concurrency: int | None = None,
40
42
  ):
41
43
  # Basic Attributions
42
44
  self.id = id if id is not None else uuid.uuid4().hex
@@ -45,6 +47,11 @@ class TriggerFlowExecution:
45
47
  self._runtime_data = RuntimeData()
46
48
  self._system_runtime_data = RuntimeData()
47
49
  self._skip_exceptions = skip_exceptions
50
+ self._concurrency_semaphore = asyncio.Semaphore(concurrency) if concurrency and concurrency > 0 else None
51
+ self._concurrency_depth = ContextVar(
52
+ f"trigger_flow_execution_concurrency_depth_{ self.id }",
53
+ default=0,
54
+ )
48
55
 
49
56
  # Settings
50
57
  self.settings = Settings(
@@ -126,19 +133,29 @@ class TriggerFlowExecution:
126
133
  },
127
134
  self.settings,
128
135
  )
129
- tasks.append(
130
- asyncio.ensure_future(
131
- FunctionShifter.asyncify(handler)(
132
- TriggerFlowEventData(
133
- trigger_event=trigger_event,
134
- trigger_type=trigger_type,
135
- value=value,
136
- execution=self,
137
- _layer_marks=_layer_marks,
138
- )
139
- )
136
+ async def run_handler(handler_func):
137
+ if self._concurrency_semaphore is None:
138
+ return await handler_func
139
+ depth = self._concurrency_depth.get()
140
+ token = self._concurrency_depth.set(depth + 1)
141
+ try:
142
+ if depth > 0:
143
+ return await handler_func
144
+ async with self._concurrency_semaphore:
145
+ return await handler_func
146
+ finally:
147
+ self._concurrency_depth.reset(token)
148
+
149
+ handler_task = FunctionShifter.asyncify(handler)(
150
+ TriggerFlowEventData(
151
+ trigger_event=trigger_event,
152
+ trigger_type=trigger_type,
153
+ value=value,
154
+ execution=self,
155
+ _layer_marks=_layer_marks,
140
156
  )
141
157
  )
158
+ tasks.append(asyncio.ensure_future(run_handler(handler_task)))
142
159
 
143
160
  if tasks:
144
161
  await asyncio.gather(*tasks, return_exceptions=self._skip_exceptions)
@@ -99,13 +99,19 @@ class TriggerFlow:
99
99
  self._blue_print.chunks[handler_or_name.__name__] = chunk
100
100
  return chunk
101
101
 
102
- 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
+ ):
103
108
  execution_id = uuid.uuid4().hex
104
109
  skip_exceptions = skip_exceptions if skip_exceptions is not None else self._skip_exceptions
105
110
  execution = self._blue_print.create_execution(
106
111
  self,
107
112
  execution_id=execution_id,
108
113
  skip_exceptions=skip_exceptions,
114
+ concurrency=concurrency,
109
115
  )
110
116
  self._executions[execution_id] = execution
111
117
  return execution
@@ -118,8 +124,14 @@ class TriggerFlow:
118
124
  if execution.id in self._executions:
119
125
  del self._executions[execution.id]
120
126
 
121
- async def async_start_execution(self, initial_value: Any, *, wait_for_result: bool = False):
122
- 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)
123
135
  await execution.async_start(initial_value, wait_for_result=wait_for_result)
124
136
  return execution
125
137
 
@@ -192,8 +204,9 @@ class TriggerFlow:
192
204
  *,
193
205
  wait_for_result: bool = True,
194
206
  timeout: int | None = 10,
207
+ concurrency: int | None = None,
195
208
  ):
196
- execution = await self.async_start_execution(initial_value)
209
+ execution = await self.async_start_execution(initial_value, concurrency=concurrency)
197
210
  if wait_for_result:
198
211
  return await execution.async_get_result(timeout=timeout)
199
212
 
@@ -202,8 +215,9 @@ class TriggerFlow:
202
215
  initial_value: Any = None,
203
216
  *,
204
217
  timeout: int | None = 10,
218
+ concurrency: int | None = None,
205
219
  ):
206
- execution = self.create_execution()
220
+ execution = self.create_execution(concurrency=concurrency)
207
221
  return execution.get_async_runtime_stream(
208
222
  initial_value,
209
223
  timeout=timeout,
@@ -213,9 +227,10 @@ class TriggerFlow:
213
227
  self,
214
228
  initial_value: Any = None,
215
229
  *,
216
- timeout: int | None = 10,
230
+ timeout: float | None = 10.0,
231
+ concurrency: int | None = None,
217
232
  ):
218
- execution = self.create_execution()
233
+ execution = self.create_execution(concurrency=concurrency)
219
234
  return execution.get_runtime_stream(
220
235
  initial_value,
221
236
  timeout=timeout,
@@ -14,7 +14,7 @@
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
20
  from typing import Callable, Any, Literal, TYPE_CHECKING, overload, cast
@@ -266,11 +266,13 @@ class TriggerFlowBaseProcess:
266
266
  self,
267
267
  *chunks: "TriggerFlowChunk | TriggerFlowHandler | tuple[str, TriggerFlowHandler]",
268
268
  side_branch: bool = False,
269
+ concurrency: int | None = None,
269
270
  ):
270
271
  batch_trigger = f"Batch-{ uuid.uuid4().hex }"
271
272
  results = {}
272
273
  triggers_to_wait = {}
273
274
  trigger_to_chunk_name = {}
275
+ semaphore = Semaphore(concurrency) if concurrency and concurrency > 0 else None
274
276
 
275
277
  async def wait_all_chunks(data: "TriggerFlowEventData"):
276
278
  if data.event in triggers_to_wait:
@@ -292,13 +294,25 @@ class TriggerFlowBaseProcess:
292
294
  chunk = self._flow_chunk(chunk_name)(chunk_func)
293
295
  else:
294
296
  chunk = self._flow_chunk(chunk.__name__)(chunk) if callable(chunk) else chunk
297
+ typed_chunk = cast(TriggerFlowChunk, chunk)
295
298
  triggers_to_wait[chunk.trigger] = False
296
299
  trigger_to_chunk_name[chunk.trigger] = chunk.name
297
300
  results[chunk.name] = None
301
+
302
+ if semaphore is None:
303
+ handler = typed_chunk.async_call
304
+ else:
305
+ def make_handler(bound_chunk: TriggerFlowChunk):
306
+ async def handler(data: "TriggerFlowEventData"):
307
+ async with semaphore:
308
+ return await bound_chunk.async_call(data)
309
+ return handler
310
+ handler = make_handler(typed_chunk)
311
+
298
312
  self._blue_print.add_handler(
299
313
  self.trigger_type,
300
314
  self.trigger_event,
301
- chunk.async_call,
315
+ handler,
302
316
  )
303
317
  self._blue_print.add_event_handler(chunk.trigger, wait_all_chunks)
304
318
 
@@ -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
 
@@ -56,15 +56,21 @@ class DataFormatter:
56
56
  if issubclass(value, BaseModel):
57
57
  extracted_value = {}
58
58
  for name, field in value.model_fields.items():
59
+ annotation = field.annotation
60
+ if hasattr(field, "rebuild_annotation"):
61
+ try:
62
+ annotation = field.rebuild_annotation()
63
+ except Exception:
64
+ annotation = field.annotation
59
65
  extracted_value.update(
60
66
  {
61
67
  name: (
62
68
  (
63
- DataFormatter.sanitize(field.annotation, remain_type=remain_type),
69
+ DataFormatter.sanitize(annotation, remain_type=remain_type),
64
70
  field.description,
65
71
  )
66
72
  if field.description
67
- else (DataFormatter.sanitize(field.annotation, remain_type=remain_type),)
73
+ else (DataFormatter.sanitize(annotation, remain_type=remain_type),)
68
74
  )
69
75
  }
70
76
  )
@@ -21,6 +21,101 @@ if TYPE_CHECKING:
21
21
 
22
22
 
23
23
  class DataLocator:
24
+ @staticmethod
25
+ def _locate_path_parts(
26
+ result: Any,
27
+ path_parts: list[str],
28
+ *,
29
+ style: Literal["dot", "slash"],
30
+ default: Any,
31
+ ):
32
+ if not path_parts:
33
+ return result
34
+ path_part = path_parts[0]
35
+ remaining = path_parts[1:]
36
+ if style == "dot":
37
+ if "[" in path_part:
38
+ path_key_and_index = path_part.split("[")
39
+ path_key = path_key_and_index[0]
40
+ path_index = path_key_and_index[1][:-1]
41
+ if isinstance(result, Mapping):
42
+ result = result.get(path_key, default)
43
+ else:
44
+ return default
45
+ if path_index in ("*", ""):
46
+ if not isinstance(result, str) and isinstance(result, Sequence):
47
+ values = []
48
+ for item in result:
49
+ value = DataLocator._locate_path_parts(
50
+ item,
51
+ remaining,
52
+ style=style,
53
+ default=default,
54
+ )
55
+ if value is default:
56
+ return default
57
+ values.append(value)
58
+ return values
59
+ return default
60
+ try:
61
+ index = int(path_index)
62
+ except Exception:
63
+ return default
64
+ if not isinstance(result, str) and isinstance(result, Sequence):
65
+ try:
66
+ return DataLocator._locate_path_parts(
67
+ result[index],
68
+ remaining,
69
+ style=style,
70
+ default=default,
71
+ )
72
+ except Exception:
73
+ return default
74
+ return default
75
+ else:
76
+ if isinstance(result, Mapping):
77
+ return DataLocator._locate_path_parts(
78
+ result.get(path_part, default),
79
+ remaining,
80
+ style=style,
81
+ default=default,
82
+ )
83
+ return default
84
+ else:
85
+ if path_part == "*":
86
+ if not isinstance(result, str) and isinstance(result, Sequence):
87
+ values = []
88
+ for item in result:
89
+ value = DataLocator._locate_path_parts(
90
+ item,
91
+ remaining,
92
+ style=style,
93
+ default=default,
94
+ )
95
+ if value is default:
96
+ return default
97
+ values.append(value)
98
+ return values
99
+ return default
100
+ if isinstance(result, Mapping):
101
+ return DataLocator._locate_path_parts(
102
+ result.get(path_part, default),
103
+ remaining,
104
+ style=style,
105
+ default=default,
106
+ )
107
+ if not isinstance(result, str) and isinstance(result, Sequence):
108
+ try:
109
+ return DataLocator._locate_path_parts(
110
+ result[int(path_part)],
111
+ remaining,
112
+ style=style,
113
+ default=default,
114
+ )
115
+ except Exception:
116
+ return default
117
+ return default
118
+
24
119
  @staticmethod
25
120
  def locate_path_in_dict(
26
121
  original_dict: dict,
@@ -34,42 +129,24 @@ class DataLocator:
34
129
  match style:
35
130
  case "dot":
36
131
  try:
37
- result = original_dict
38
132
  path_parts = path.split(".")
39
- for path_part in path_parts:
40
- if "[" in path_part:
41
- path_key_and_index = path_part.split("[")
42
- path_key = path_key_and_index[0]
43
- path_index = int(path_key_and_index[1][:-1])
44
- if isinstance(result, Mapping):
45
- result = result[path_key]
46
- else:
47
- return default
48
- if not isinstance(result, str) and isinstance(result, Sequence):
49
- result = result[path_index]
50
- else:
51
- return default
52
- else:
53
- if isinstance(result, Mapping):
54
- result = result[path_part]
55
- else:
56
- return default
57
- return result
133
+ return DataLocator._locate_path_parts(
134
+ original_dict,
135
+ path_parts,
136
+ style="dot",
137
+ default=default,
138
+ )
58
139
  except Exception:
59
140
  return default
60
141
  case "slash":
61
- result = original_dict
62
- path_parts = path.split("/")
63
142
  try:
64
- for path_part in path_parts:
65
- if path_part:
66
- if isinstance(result, Mapping):
67
- result = result[path_part]
68
- elif not isinstance(result, str) and isinstance(result, Sequence):
69
- result = result[int(path_part)]
70
- else:
71
- return default
72
- return result
143
+ path_parts = [part for part in path.split("/") if part]
144
+ return DataLocator._locate_path_parts(
145
+ original_dict,
146
+ path_parts,
147
+ style="slash",
148
+ default=default,
149
+ )
73
150
  except Exception:
74
151
  return default
75
152
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "agently"
3
- version = "4.0.6.11"
3
+ version = "4.0.7.1"
4
4
  description = ""
5
5
  authors = [
6
6
  {name = "Agently Team",email = "developer@agently.tech"},
File without changes
File without changes