hexdag 0.5.0.dev1__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.
Files changed (261) hide show
  1. hexdag/__init__.py +116 -0
  2. hexdag/__main__.py +30 -0
  3. hexdag/adapters/executors/__init__.py +5 -0
  4. hexdag/adapters/executors/local_executor.py +316 -0
  5. hexdag/builtin/__init__.py +6 -0
  6. hexdag/builtin/adapters/__init__.py +51 -0
  7. hexdag/builtin/adapters/anthropic/__init__.py +5 -0
  8. hexdag/builtin/adapters/anthropic/anthropic_adapter.py +151 -0
  9. hexdag/builtin/adapters/database/__init__.py +6 -0
  10. hexdag/builtin/adapters/database/csv/csv_adapter.py +249 -0
  11. hexdag/builtin/adapters/database/pgvector/__init__.py +5 -0
  12. hexdag/builtin/adapters/database/pgvector/pgvector_adapter.py +478 -0
  13. hexdag/builtin/adapters/database/sqlalchemy/sqlalchemy_adapter.py +252 -0
  14. hexdag/builtin/adapters/database/sqlite/__init__.py +5 -0
  15. hexdag/builtin/adapters/database/sqlite/sqlite_adapter.py +410 -0
  16. hexdag/builtin/adapters/local/README.md +59 -0
  17. hexdag/builtin/adapters/local/__init__.py +7 -0
  18. hexdag/builtin/adapters/local/local_observer_manager.py +696 -0
  19. hexdag/builtin/adapters/memory/__init__.py +47 -0
  20. hexdag/builtin/adapters/memory/file_memory_adapter.py +297 -0
  21. hexdag/builtin/adapters/memory/in_memory_memory.py +216 -0
  22. hexdag/builtin/adapters/memory/schemas.py +57 -0
  23. hexdag/builtin/adapters/memory/session_memory.py +178 -0
  24. hexdag/builtin/adapters/memory/sqlite_memory_adapter.py +215 -0
  25. hexdag/builtin/adapters/memory/state_memory.py +280 -0
  26. hexdag/builtin/adapters/mock/README.md +89 -0
  27. hexdag/builtin/adapters/mock/__init__.py +15 -0
  28. hexdag/builtin/adapters/mock/hexdag.toml +50 -0
  29. hexdag/builtin/adapters/mock/mock_database.py +225 -0
  30. hexdag/builtin/adapters/mock/mock_embedding.py +223 -0
  31. hexdag/builtin/adapters/mock/mock_llm.py +177 -0
  32. hexdag/builtin/adapters/mock/mock_tool_adapter.py +192 -0
  33. hexdag/builtin/adapters/mock/mock_tool_router.py +232 -0
  34. hexdag/builtin/adapters/openai/__init__.py +5 -0
  35. hexdag/builtin/adapters/openai/openai_adapter.py +634 -0
  36. hexdag/builtin/adapters/secret/__init__.py +7 -0
  37. hexdag/builtin/adapters/secret/local_secret_adapter.py +248 -0
  38. hexdag/builtin/adapters/unified_tool_router.py +280 -0
  39. hexdag/builtin/macros/__init__.py +17 -0
  40. hexdag/builtin/macros/conversation_agent.py +390 -0
  41. hexdag/builtin/macros/llm_macro.py +151 -0
  42. hexdag/builtin/macros/reasoning_agent.py +423 -0
  43. hexdag/builtin/macros/tool_macro.py +380 -0
  44. hexdag/builtin/nodes/__init__.py +38 -0
  45. hexdag/builtin/nodes/_discovery.py +123 -0
  46. hexdag/builtin/nodes/agent_node.py +696 -0
  47. hexdag/builtin/nodes/base_node_factory.py +242 -0
  48. hexdag/builtin/nodes/composite_node.py +926 -0
  49. hexdag/builtin/nodes/data_node.py +201 -0
  50. hexdag/builtin/nodes/expression_node.py +487 -0
  51. hexdag/builtin/nodes/function_node.py +454 -0
  52. hexdag/builtin/nodes/llm_node.py +491 -0
  53. hexdag/builtin/nodes/loop_node.py +920 -0
  54. hexdag/builtin/nodes/mapped_input.py +518 -0
  55. hexdag/builtin/nodes/port_call_node.py +269 -0
  56. hexdag/builtin/nodes/tool_call_node.py +195 -0
  57. hexdag/builtin/nodes/tool_utils.py +390 -0
  58. hexdag/builtin/prompts/__init__.py +68 -0
  59. hexdag/builtin/prompts/base.py +422 -0
  60. hexdag/builtin/prompts/chat_prompts.py +303 -0
  61. hexdag/builtin/prompts/error_correction_prompts.py +320 -0
  62. hexdag/builtin/prompts/tool_prompts.py +160 -0
  63. hexdag/builtin/tools/builtin_tools.py +84 -0
  64. hexdag/builtin/tools/database_tools.py +164 -0
  65. hexdag/cli/__init__.py +17 -0
  66. hexdag/cli/__main__.py +7 -0
  67. hexdag/cli/commands/__init__.py +27 -0
  68. hexdag/cli/commands/build_cmd.py +812 -0
  69. hexdag/cli/commands/create_cmd.py +208 -0
  70. hexdag/cli/commands/docs_cmd.py +293 -0
  71. hexdag/cli/commands/generate_types_cmd.py +252 -0
  72. hexdag/cli/commands/init_cmd.py +188 -0
  73. hexdag/cli/commands/pipeline_cmd.py +494 -0
  74. hexdag/cli/commands/plugin_dev_cmd.py +529 -0
  75. hexdag/cli/commands/plugins_cmd.py +441 -0
  76. hexdag/cli/commands/studio_cmd.py +101 -0
  77. hexdag/cli/commands/validate_cmd.py +221 -0
  78. hexdag/cli/main.py +84 -0
  79. hexdag/core/__init__.py +83 -0
  80. hexdag/core/config/__init__.py +20 -0
  81. hexdag/core/config/loader.py +479 -0
  82. hexdag/core/config/models.py +150 -0
  83. hexdag/core/configurable.py +294 -0
  84. hexdag/core/context/__init__.py +37 -0
  85. hexdag/core/context/execution_context.py +378 -0
  86. hexdag/core/docs/__init__.py +26 -0
  87. hexdag/core/docs/extractors.py +678 -0
  88. hexdag/core/docs/generators.py +890 -0
  89. hexdag/core/docs/models.py +120 -0
  90. hexdag/core/domain/__init__.py +10 -0
  91. hexdag/core/domain/dag.py +1225 -0
  92. hexdag/core/exceptions.py +234 -0
  93. hexdag/core/expression_parser.py +569 -0
  94. hexdag/core/logging.py +449 -0
  95. hexdag/core/models/__init__.py +17 -0
  96. hexdag/core/models/base.py +138 -0
  97. hexdag/core/orchestration/__init__.py +46 -0
  98. hexdag/core/orchestration/body_executor.py +481 -0
  99. hexdag/core/orchestration/components/__init__.py +97 -0
  100. hexdag/core/orchestration/components/adapter_lifecycle_manager.py +113 -0
  101. hexdag/core/orchestration/components/checkpoint_manager.py +134 -0
  102. hexdag/core/orchestration/components/execution_coordinator.py +360 -0
  103. hexdag/core/orchestration/components/health_check_manager.py +176 -0
  104. hexdag/core/orchestration/components/input_mapper.py +143 -0
  105. hexdag/core/orchestration/components/lifecycle_manager.py +583 -0
  106. hexdag/core/orchestration/components/node_executor.py +377 -0
  107. hexdag/core/orchestration/components/secret_manager.py +202 -0
  108. hexdag/core/orchestration/components/wave_executor.py +158 -0
  109. hexdag/core/orchestration/constants.py +17 -0
  110. hexdag/core/orchestration/events/README.md +312 -0
  111. hexdag/core/orchestration/events/__init__.py +104 -0
  112. hexdag/core/orchestration/events/batching.py +330 -0
  113. hexdag/core/orchestration/events/decorators.py +139 -0
  114. hexdag/core/orchestration/events/events.py +573 -0
  115. hexdag/core/orchestration/events/observers/__init__.py +30 -0
  116. hexdag/core/orchestration/events/observers/core_observers.py +690 -0
  117. hexdag/core/orchestration/events/observers/models.py +111 -0
  118. hexdag/core/orchestration/events/taxonomy.py +269 -0
  119. hexdag/core/orchestration/hook_context.py +237 -0
  120. hexdag/core/orchestration/hooks.py +437 -0
  121. hexdag/core/orchestration/models.py +418 -0
  122. hexdag/core/orchestration/orchestrator.py +910 -0
  123. hexdag/core/orchestration/orchestrator_factory.py +275 -0
  124. hexdag/core/orchestration/port_wrappers.py +327 -0
  125. hexdag/core/orchestration/prompt/__init__.py +32 -0
  126. hexdag/core/orchestration/prompt/template.py +332 -0
  127. hexdag/core/pipeline_builder/__init__.py +21 -0
  128. hexdag/core/pipeline_builder/component_instantiator.py +386 -0
  129. hexdag/core/pipeline_builder/include_tag.py +265 -0
  130. hexdag/core/pipeline_builder/pipeline_config.py +133 -0
  131. hexdag/core/pipeline_builder/py_tag.py +223 -0
  132. hexdag/core/pipeline_builder/tag_discovery.py +268 -0
  133. hexdag/core/pipeline_builder/yaml_builder.py +1196 -0
  134. hexdag/core/pipeline_builder/yaml_validator.py +569 -0
  135. hexdag/core/ports/__init__.py +65 -0
  136. hexdag/core/ports/api_call.py +133 -0
  137. hexdag/core/ports/database.py +489 -0
  138. hexdag/core/ports/embedding.py +215 -0
  139. hexdag/core/ports/executor.py +237 -0
  140. hexdag/core/ports/file_storage.py +117 -0
  141. hexdag/core/ports/healthcheck.py +87 -0
  142. hexdag/core/ports/llm.py +551 -0
  143. hexdag/core/ports/memory.py +70 -0
  144. hexdag/core/ports/observer_manager.py +130 -0
  145. hexdag/core/ports/secret.py +145 -0
  146. hexdag/core/ports/tool_router.py +94 -0
  147. hexdag/core/ports_builder.py +623 -0
  148. hexdag/core/protocols.py +273 -0
  149. hexdag/core/resolver.py +304 -0
  150. hexdag/core/schema/__init__.py +9 -0
  151. hexdag/core/schema/generator.py +742 -0
  152. hexdag/core/secrets.py +242 -0
  153. hexdag/core/types.py +413 -0
  154. hexdag/core/utils/async_warnings.py +206 -0
  155. hexdag/core/utils/schema_conversion.py +78 -0
  156. hexdag/core/utils/sql_validation.py +86 -0
  157. hexdag/core/validation/secure_json.py +148 -0
  158. hexdag/core/yaml_macro.py +517 -0
  159. hexdag/mcp_server.py +3120 -0
  160. hexdag/studio/__init__.py +10 -0
  161. hexdag/studio/build_ui.py +92 -0
  162. hexdag/studio/server/__init__.py +1 -0
  163. hexdag/studio/server/main.py +100 -0
  164. hexdag/studio/server/routes/__init__.py +9 -0
  165. hexdag/studio/server/routes/execute.py +208 -0
  166. hexdag/studio/server/routes/export.py +558 -0
  167. hexdag/studio/server/routes/files.py +207 -0
  168. hexdag/studio/server/routes/plugins.py +419 -0
  169. hexdag/studio/server/routes/validate.py +220 -0
  170. hexdag/studio/ui/index.html +13 -0
  171. hexdag/studio/ui/package-lock.json +2992 -0
  172. hexdag/studio/ui/package.json +31 -0
  173. hexdag/studio/ui/postcss.config.js +6 -0
  174. hexdag/studio/ui/public/hexdag.svg +5 -0
  175. hexdag/studio/ui/src/App.tsx +251 -0
  176. hexdag/studio/ui/src/components/Canvas.tsx +408 -0
  177. hexdag/studio/ui/src/components/ContextMenu.tsx +187 -0
  178. hexdag/studio/ui/src/components/FileBrowser.tsx +123 -0
  179. hexdag/studio/ui/src/components/Header.tsx +181 -0
  180. hexdag/studio/ui/src/components/HexdagNode.tsx +193 -0
  181. hexdag/studio/ui/src/components/NodeInspector.tsx +512 -0
  182. hexdag/studio/ui/src/components/NodePalette.tsx +262 -0
  183. hexdag/studio/ui/src/components/NodePortsSection.tsx +403 -0
  184. hexdag/studio/ui/src/components/PluginManager.tsx +347 -0
  185. hexdag/studio/ui/src/components/PortsEditor.tsx +481 -0
  186. hexdag/studio/ui/src/components/PythonEditor.tsx +195 -0
  187. hexdag/studio/ui/src/components/ValidationPanel.tsx +105 -0
  188. hexdag/studio/ui/src/components/YamlEditor.tsx +196 -0
  189. hexdag/studio/ui/src/components/index.ts +8 -0
  190. hexdag/studio/ui/src/index.css +92 -0
  191. hexdag/studio/ui/src/main.tsx +10 -0
  192. hexdag/studio/ui/src/types/index.ts +123 -0
  193. hexdag/studio/ui/src/vite-env.d.ts +1 -0
  194. hexdag/studio/ui/tailwind.config.js +29 -0
  195. hexdag/studio/ui/tsconfig.json +37 -0
  196. hexdag/studio/ui/tsconfig.node.json +13 -0
  197. hexdag/studio/ui/vite.config.ts +35 -0
  198. hexdag/visualization/__init__.py +69 -0
  199. hexdag/visualization/dag_visualizer.py +1020 -0
  200. hexdag-0.5.0.dev1.dist-info/METADATA +369 -0
  201. hexdag-0.5.0.dev1.dist-info/RECORD +261 -0
  202. hexdag-0.5.0.dev1.dist-info/WHEEL +4 -0
  203. hexdag-0.5.0.dev1.dist-info/entry_points.txt +4 -0
  204. hexdag-0.5.0.dev1.dist-info/licenses/LICENSE +190 -0
  205. hexdag_plugins/.gitignore +43 -0
  206. hexdag_plugins/README.md +73 -0
  207. hexdag_plugins/__init__.py +1 -0
  208. hexdag_plugins/azure/LICENSE +21 -0
  209. hexdag_plugins/azure/README.md +414 -0
  210. hexdag_plugins/azure/__init__.py +21 -0
  211. hexdag_plugins/azure/azure_blob_adapter.py +450 -0
  212. hexdag_plugins/azure/azure_cosmos_adapter.py +383 -0
  213. hexdag_plugins/azure/azure_keyvault_adapter.py +314 -0
  214. hexdag_plugins/azure/azure_openai_adapter.py +415 -0
  215. hexdag_plugins/azure/pyproject.toml +107 -0
  216. hexdag_plugins/azure/tests/__init__.py +1 -0
  217. hexdag_plugins/azure/tests/test_azure_blob_adapter.py +350 -0
  218. hexdag_plugins/azure/tests/test_azure_cosmos_adapter.py +323 -0
  219. hexdag_plugins/azure/tests/test_azure_keyvault_adapter.py +330 -0
  220. hexdag_plugins/azure/tests/test_azure_openai_adapter.py +329 -0
  221. hexdag_plugins/hexdag_etl/README.md +168 -0
  222. hexdag_plugins/hexdag_etl/__init__.py +53 -0
  223. hexdag_plugins/hexdag_etl/examples/01_simple_pandas_transform.py +270 -0
  224. hexdag_plugins/hexdag_etl/examples/02_simple_pandas_only.py +149 -0
  225. hexdag_plugins/hexdag_etl/examples/03_file_io_pipeline.py +109 -0
  226. hexdag_plugins/hexdag_etl/examples/test_pandas_transform.py +84 -0
  227. hexdag_plugins/hexdag_etl/hexdag.toml +25 -0
  228. hexdag_plugins/hexdag_etl/hexdag_etl/__init__.py +48 -0
  229. hexdag_plugins/hexdag_etl/hexdag_etl/nodes/__init__.py +13 -0
  230. hexdag_plugins/hexdag_etl/hexdag_etl/nodes/api_extract.py +230 -0
  231. hexdag_plugins/hexdag_etl/hexdag_etl/nodes/base_node_factory.py +181 -0
  232. hexdag_plugins/hexdag_etl/hexdag_etl/nodes/file_io.py +415 -0
  233. hexdag_plugins/hexdag_etl/hexdag_etl/nodes/outlook.py +492 -0
  234. hexdag_plugins/hexdag_etl/hexdag_etl/nodes/pandas_transform.py +563 -0
  235. hexdag_plugins/hexdag_etl/hexdag_etl/nodes/sql_extract_load.py +112 -0
  236. hexdag_plugins/hexdag_etl/pyproject.toml +82 -0
  237. hexdag_plugins/hexdag_etl/test_transform.py +54 -0
  238. hexdag_plugins/hexdag_etl/tests/test_plugin_integration.py +62 -0
  239. hexdag_plugins/mysql_adapter/LICENSE +21 -0
  240. hexdag_plugins/mysql_adapter/README.md +224 -0
  241. hexdag_plugins/mysql_adapter/__init__.py +6 -0
  242. hexdag_plugins/mysql_adapter/mysql_adapter.py +408 -0
  243. hexdag_plugins/mysql_adapter/pyproject.toml +93 -0
  244. hexdag_plugins/mysql_adapter/tests/test_mysql_adapter.py +259 -0
  245. hexdag_plugins/storage/README.md +184 -0
  246. hexdag_plugins/storage/__init__.py +19 -0
  247. hexdag_plugins/storage/file/__init__.py +5 -0
  248. hexdag_plugins/storage/file/local.py +325 -0
  249. hexdag_plugins/storage/ports/__init__.py +5 -0
  250. hexdag_plugins/storage/ports/vector_store.py +236 -0
  251. hexdag_plugins/storage/sql/__init__.py +7 -0
  252. hexdag_plugins/storage/sql/base.py +187 -0
  253. hexdag_plugins/storage/sql/mysql.py +27 -0
  254. hexdag_plugins/storage/sql/postgresql.py +27 -0
  255. hexdag_plugins/storage/tests/__init__.py +1 -0
  256. hexdag_plugins/storage/tests/test_local_file_storage.py +161 -0
  257. hexdag_plugins/storage/tests/test_sql_adapters.py +212 -0
  258. hexdag_plugins/storage/vector/__init__.py +7 -0
  259. hexdag_plugins/storage/vector/chromadb.py +223 -0
  260. hexdag_plugins/storage/vector/in_memory.py +285 -0
  261. hexdag_plugins/storage/vector/pgvector.py +502 -0
@@ -0,0 +1,242 @@
1
+ """Simplified BaseNodeFactory for creating nodes with Pydantic models."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from abc import ABC, abstractmethod
6
+ from typing import Any, cast
7
+
8
+ from pydantic import BaseModel, create_model
9
+
10
+ from hexdag.core.domain.dag import NodeSpec
11
+ from hexdag.core.orchestration.prompt.template import PromptTemplate
12
+ from hexdag.core.protocols import is_schema_type
13
+
14
+
15
+ class BaseNodeFactory(ABC):
16
+ """Minimal base class for node factories with Pydantic models."""
17
+
18
+ # Note: Event emission has been removed as it's now handled by the orchestrator
19
+ # The new event system uses ObserverManager at the orchestrator level
20
+
21
+ def create_pydantic_model(
22
+ self, name: str, schema: dict[str, Any] | type[BaseModel] | type[Any] | None
23
+ ) -> type[BaseModel] | None:
24
+ """Create a Pydantic model from a schema.
25
+
26
+ Raises
27
+ ------
28
+ ValueError
29
+ If schema type is not supported
30
+ """
31
+ if schema is None:
32
+ return None
33
+
34
+ if is_schema_type(schema):
35
+ return schema # type: ignore[return-value] # is_schema_type checks for BaseModel subclass
36
+
37
+ if isinstance(schema, dict):
38
+ # String type names mapping (for when field_type is a string)
39
+ type_map = {
40
+ "str": str,
41
+ "int": int,
42
+ "float": float,
43
+ "bool": bool,
44
+ "list": list,
45
+ "dict": dict,
46
+ "Any": Any,
47
+ }
48
+
49
+ field_definitions: dict[str, Any] = {}
50
+ for field_name, field_type in schema.items():
51
+ # Dispatch based on field_type's type using match pattern
52
+ match field_type:
53
+ case str():
54
+ # String type names - convert to actual types
55
+ actual_type = type_map.get(field_type, Any)
56
+ field_definitions[field_name] = (actual_type, ...)
57
+ case type():
58
+ # Already a type
59
+ field_definitions[field_name] = (field_type, ...)
60
+ case tuple():
61
+ # Already in the correct format (type, default)
62
+ field_definitions[field_name] = field_type
63
+ case _:
64
+ # Unknown type specification - use Any
65
+ field_definitions[field_name] = (Any, ...)
66
+
67
+ return create_model(name, **field_definitions)
68
+
69
+ # At this point, schema should be a type
70
+ try:
71
+ return cast("type[Any] | None", create_model(name, value=(schema, ...)))
72
+ except (TypeError, AttributeError) as e:
73
+ # If we get here, schema is an unexpected type
74
+ raise ValueError(
75
+ f"Schema must be a dict, type, or Pydantic model, got {type(schema).__name__}"
76
+ ) from e
77
+
78
+ @staticmethod
79
+ def infer_input_schema_from_template(
80
+ template: str | PromptTemplate,
81
+ special_params: set[str] | None = None,
82
+ ) -> dict[str, Any]:
83
+ """Infer input schema from template variables with configurable filtering.
84
+
85
+ This method extracts variable names from a prompt template and creates
86
+ a schema dictionary mapping those variables to string types. It supports
87
+ filtering out special parameters that are not user inputs.
88
+
89
+ Parameters
90
+ ----------
91
+ template : str | PromptTemplate
92
+ The prompt template to analyze. Can be a string or PromptTemplate instance.
93
+ special_params : set[str] | None, optional
94
+ Set of parameter names to exclude from the schema (e.g., "context_history").
95
+ If None, no filtering is applied.
96
+
97
+ Returns
98
+ -------
99
+ dict[str, Any]
100
+ Schema dictionary mapping variable names to str type.
101
+ Returns {"input": str} if no variables found.
102
+
103
+ Examples
104
+ --------
105
+ >>> BaseNodeFactory.infer_input_schema_from_template("Hello {{name}}")
106
+ {'name': <class 'str'>}
107
+
108
+ >>> BaseNodeFactory.infer_input_schema_from_template(
109
+ ... "Process {{user}} with {{context_history}}",
110
+ ... special_params={"context_history"}
111
+ ... )
112
+ {'user': <class 'str'>}
113
+
114
+ >>> BaseNodeFactory.infer_input_schema_from_template("No variables")
115
+ {'input': <class 'str'>}
116
+ """
117
+
118
+ if isinstance(template, str):
119
+ template = PromptTemplate(template)
120
+
121
+ variables = getattr(template, "input_vars", [])
122
+
123
+ if special_params:
124
+ variables = [v for v in variables if v not in special_params]
125
+
126
+ if not variables:
127
+ return {"input": str}
128
+
129
+ schema: dict[str, Any] = {}
130
+ for var in variables:
131
+ base_var = var.split(".")[0]
132
+ # Double-check against special params for nested variables
133
+ if not special_params or base_var not in special_params:
134
+ schema[base_var] = str
135
+
136
+ return schema
137
+
138
+ def _copy_required_ports_to_wrapper(self, wrapper_fn: Any) -> None:
139
+ """Copy required_ports metadata from factory class to wrapper function.
140
+
141
+ This ensures port requirements are preserved when creating node functions.
142
+ """
143
+ if hasattr(self.__class__, "_hexdag_required_ports"):
144
+ # _hexdag_required_ports is added dynamically by @node decorator
145
+ wrapper_fn._hexdag_required_ports = self.__class__._hexdag_required_ports # pyright: ignore[reportAttributeAccessIssue] # noqa: B010
146
+
147
+ @staticmethod
148
+ def extract_framework_params(kwargs: dict[str, Any]) -> dict[str, Any]:
149
+ """Extract framework-level parameters from kwargs.
150
+
151
+ This method provides a single place to extract NodeSpec framework params
152
+ (timeout, max_retries, retry config, when) from kwargs. All node factories
153
+ should use this to ensure consistent handling.
154
+
155
+ Parameters
156
+ ----------
157
+ kwargs : dict[str, Any]
158
+ Keyword arguments to extract from (modified in place)
159
+
160
+ Returns
161
+ -------
162
+ dict[str, Any]
163
+ Dictionary with keys: timeout, max_retries, retry_delay, retry_backoff,
164
+ retry_max_delay, when. Values are None if not present in kwargs.
165
+
166
+ Examples
167
+ --------
168
+ >>> kwargs = {
169
+ ... "when": "status == 'active'", "timeout": 30,
170
+ ... "max_retries": 3, "other": "value"
171
+ ... }
172
+ >>> framework = BaseNodeFactory.extract_framework_params(kwargs)
173
+ >>> framework["timeout"]
174
+ 30
175
+ >>> framework["max_retries"]
176
+ 3
177
+ >>> kwargs
178
+ {'other': 'value'}
179
+
180
+ With retry backoff configuration::
181
+
182
+ >>> kwargs = {"max_retries": 3, "retry_delay": 1.0, "retry_backoff": 2.0}
183
+ >>> framework = BaseNodeFactory.extract_framework_params(kwargs)
184
+ >>> framework["retry_delay"]
185
+ 1.0
186
+ """
187
+ return {
188
+ "timeout": kwargs.pop("timeout", None),
189
+ "max_retries": kwargs.pop("max_retries", None),
190
+ "retry_delay": kwargs.pop("retry_delay", None),
191
+ "retry_backoff": kwargs.pop("retry_backoff", None),
192
+ "retry_max_delay": kwargs.pop("retry_max_delay", None),
193
+ "when": kwargs.pop("when", None),
194
+ }
195
+
196
+ def create_node_with_mapping(
197
+ self,
198
+ name: str,
199
+ wrapped_fn: Any,
200
+ input_schema: dict[str, Any] | None,
201
+ output_schema: dict[str, Any] | type[BaseModel] | None,
202
+ deps: list[str] | None = None,
203
+ **kwargs: Any,
204
+ ) -> NodeSpec:
205
+ """Universal NodeSpec creation."""
206
+ # Copy required_ports metadata to wrapper
207
+ self._copy_required_ports_to_wrapper(wrapped_fn)
208
+
209
+ # Extract framework-level parameters from kwargs
210
+ framework = self.extract_framework_params(kwargs)
211
+
212
+ input_model = self.create_pydantic_model(f"{name}Input", input_schema)
213
+ output_model = self.create_pydantic_model(f"{name}Output", output_schema)
214
+
215
+ return NodeSpec(
216
+ name=name,
217
+ fn=wrapped_fn,
218
+ in_model=input_model,
219
+ out_model=output_model,
220
+ deps=frozenset(deps or []),
221
+ params=kwargs,
222
+ timeout=framework["timeout"],
223
+ max_retries=framework["max_retries"],
224
+ retry_delay=framework["retry_delay"],
225
+ retry_backoff=framework["retry_backoff"],
226
+ retry_max_delay=framework["retry_max_delay"],
227
+ when=framework["when"],
228
+ )
229
+
230
+ @abstractmethod
231
+ def __call__(self, name: str, *args: Any, **kwargs: Any) -> NodeSpec: # noqa: ARG002
232
+ """Create a NodeSpec.
233
+
234
+ Must be implemented by subclasses.
235
+
236
+ Args:
237
+ name: Name of the node
238
+ *args: Additional positional arguments (unused, for subclass flexibility)
239
+ **kwargs: Additional keyword arguments
240
+ """
241
+ _ = args # Marked as intentionally unused for subclass API flexibility
242
+ raise NotImplementedError