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.
- hexdag/__init__.py +116 -0
- hexdag/__main__.py +30 -0
- hexdag/adapters/executors/__init__.py +5 -0
- hexdag/adapters/executors/local_executor.py +316 -0
- hexdag/builtin/__init__.py +6 -0
- hexdag/builtin/adapters/__init__.py +51 -0
- hexdag/builtin/adapters/anthropic/__init__.py +5 -0
- hexdag/builtin/adapters/anthropic/anthropic_adapter.py +151 -0
- hexdag/builtin/adapters/database/__init__.py +6 -0
- hexdag/builtin/adapters/database/csv/csv_adapter.py +249 -0
- hexdag/builtin/adapters/database/pgvector/__init__.py +5 -0
- hexdag/builtin/adapters/database/pgvector/pgvector_adapter.py +478 -0
- hexdag/builtin/adapters/database/sqlalchemy/sqlalchemy_adapter.py +252 -0
- hexdag/builtin/adapters/database/sqlite/__init__.py +5 -0
- hexdag/builtin/adapters/database/sqlite/sqlite_adapter.py +410 -0
- hexdag/builtin/adapters/local/README.md +59 -0
- hexdag/builtin/adapters/local/__init__.py +7 -0
- hexdag/builtin/adapters/local/local_observer_manager.py +696 -0
- hexdag/builtin/adapters/memory/__init__.py +47 -0
- hexdag/builtin/adapters/memory/file_memory_adapter.py +297 -0
- hexdag/builtin/adapters/memory/in_memory_memory.py +216 -0
- hexdag/builtin/adapters/memory/schemas.py +57 -0
- hexdag/builtin/adapters/memory/session_memory.py +178 -0
- hexdag/builtin/adapters/memory/sqlite_memory_adapter.py +215 -0
- hexdag/builtin/adapters/memory/state_memory.py +280 -0
- hexdag/builtin/adapters/mock/README.md +89 -0
- hexdag/builtin/adapters/mock/__init__.py +15 -0
- hexdag/builtin/adapters/mock/hexdag.toml +50 -0
- hexdag/builtin/adapters/mock/mock_database.py +225 -0
- hexdag/builtin/adapters/mock/mock_embedding.py +223 -0
- hexdag/builtin/adapters/mock/mock_llm.py +177 -0
- hexdag/builtin/adapters/mock/mock_tool_adapter.py +192 -0
- hexdag/builtin/adapters/mock/mock_tool_router.py +232 -0
- hexdag/builtin/adapters/openai/__init__.py +5 -0
- hexdag/builtin/adapters/openai/openai_adapter.py +634 -0
- hexdag/builtin/adapters/secret/__init__.py +7 -0
- hexdag/builtin/adapters/secret/local_secret_adapter.py +248 -0
- hexdag/builtin/adapters/unified_tool_router.py +280 -0
- hexdag/builtin/macros/__init__.py +17 -0
- hexdag/builtin/macros/conversation_agent.py +390 -0
- hexdag/builtin/macros/llm_macro.py +151 -0
- hexdag/builtin/macros/reasoning_agent.py +423 -0
- hexdag/builtin/macros/tool_macro.py +380 -0
- hexdag/builtin/nodes/__init__.py +38 -0
- hexdag/builtin/nodes/_discovery.py +123 -0
- hexdag/builtin/nodes/agent_node.py +696 -0
- hexdag/builtin/nodes/base_node_factory.py +242 -0
- hexdag/builtin/nodes/composite_node.py +926 -0
- hexdag/builtin/nodes/data_node.py +201 -0
- hexdag/builtin/nodes/expression_node.py +487 -0
- hexdag/builtin/nodes/function_node.py +454 -0
- hexdag/builtin/nodes/llm_node.py +491 -0
- hexdag/builtin/nodes/loop_node.py +920 -0
- hexdag/builtin/nodes/mapped_input.py +518 -0
- hexdag/builtin/nodes/port_call_node.py +269 -0
- hexdag/builtin/nodes/tool_call_node.py +195 -0
- hexdag/builtin/nodes/tool_utils.py +390 -0
- hexdag/builtin/prompts/__init__.py +68 -0
- hexdag/builtin/prompts/base.py +422 -0
- hexdag/builtin/prompts/chat_prompts.py +303 -0
- hexdag/builtin/prompts/error_correction_prompts.py +320 -0
- hexdag/builtin/prompts/tool_prompts.py +160 -0
- hexdag/builtin/tools/builtin_tools.py +84 -0
- hexdag/builtin/tools/database_tools.py +164 -0
- hexdag/cli/__init__.py +17 -0
- hexdag/cli/__main__.py +7 -0
- hexdag/cli/commands/__init__.py +27 -0
- hexdag/cli/commands/build_cmd.py +812 -0
- hexdag/cli/commands/create_cmd.py +208 -0
- hexdag/cli/commands/docs_cmd.py +293 -0
- hexdag/cli/commands/generate_types_cmd.py +252 -0
- hexdag/cli/commands/init_cmd.py +188 -0
- hexdag/cli/commands/pipeline_cmd.py +494 -0
- hexdag/cli/commands/plugin_dev_cmd.py +529 -0
- hexdag/cli/commands/plugins_cmd.py +441 -0
- hexdag/cli/commands/studio_cmd.py +101 -0
- hexdag/cli/commands/validate_cmd.py +221 -0
- hexdag/cli/main.py +84 -0
- hexdag/core/__init__.py +83 -0
- hexdag/core/config/__init__.py +20 -0
- hexdag/core/config/loader.py +479 -0
- hexdag/core/config/models.py +150 -0
- hexdag/core/configurable.py +294 -0
- hexdag/core/context/__init__.py +37 -0
- hexdag/core/context/execution_context.py +378 -0
- hexdag/core/docs/__init__.py +26 -0
- hexdag/core/docs/extractors.py +678 -0
- hexdag/core/docs/generators.py +890 -0
- hexdag/core/docs/models.py +120 -0
- hexdag/core/domain/__init__.py +10 -0
- hexdag/core/domain/dag.py +1225 -0
- hexdag/core/exceptions.py +234 -0
- hexdag/core/expression_parser.py +569 -0
- hexdag/core/logging.py +449 -0
- hexdag/core/models/__init__.py +17 -0
- hexdag/core/models/base.py +138 -0
- hexdag/core/orchestration/__init__.py +46 -0
- hexdag/core/orchestration/body_executor.py +481 -0
- hexdag/core/orchestration/components/__init__.py +97 -0
- hexdag/core/orchestration/components/adapter_lifecycle_manager.py +113 -0
- hexdag/core/orchestration/components/checkpoint_manager.py +134 -0
- hexdag/core/orchestration/components/execution_coordinator.py +360 -0
- hexdag/core/orchestration/components/health_check_manager.py +176 -0
- hexdag/core/orchestration/components/input_mapper.py +143 -0
- hexdag/core/orchestration/components/lifecycle_manager.py +583 -0
- hexdag/core/orchestration/components/node_executor.py +377 -0
- hexdag/core/orchestration/components/secret_manager.py +202 -0
- hexdag/core/orchestration/components/wave_executor.py +158 -0
- hexdag/core/orchestration/constants.py +17 -0
- hexdag/core/orchestration/events/README.md +312 -0
- hexdag/core/orchestration/events/__init__.py +104 -0
- hexdag/core/orchestration/events/batching.py +330 -0
- hexdag/core/orchestration/events/decorators.py +139 -0
- hexdag/core/orchestration/events/events.py +573 -0
- hexdag/core/orchestration/events/observers/__init__.py +30 -0
- hexdag/core/orchestration/events/observers/core_observers.py +690 -0
- hexdag/core/orchestration/events/observers/models.py +111 -0
- hexdag/core/orchestration/events/taxonomy.py +269 -0
- hexdag/core/orchestration/hook_context.py +237 -0
- hexdag/core/orchestration/hooks.py +437 -0
- hexdag/core/orchestration/models.py +418 -0
- hexdag/core/orchestration/orchestrator.py +910 -0
- hexdag/core/orchestration/orchestrator_factory.py +275 -0
- hexdag/core/orchestration/port_wrappers.py +327 -0
- hexdag/core/orchestration/prompt/__init__.py +32 -0
- hexdag/core/orchestration/prompt/template.py +332 -0
- hexdag/core/pipeline_builder/__init__.py +21 -0
- hexdag/core/pipeline_builder/component_instantiator.py +386 -0
- hexdag/core/pipeline_builder/include_tag.py +265 -0
- hexdag/core/pipeline_builder/pipeline_config.py +133 -0
- hexdag/core/pipeline_builder/py_tag.py +223 -0
- hexdag/core/pipeline_builder/tag_discovery.py +268 -0
- hexdag/core/pipeline_builder/yaml_builder.py +1196 -0
- hexdag/core/pipeline_builder/yaml_validator.py +569 -0
- hexdag/core/ports/__init__.py +65 -0
- hexdag/core/ports/api_call.py +133 -0
- hexdag/core/ports/database.py +489 -0
- hexdag/core/ports/embedding.py +215 -0
- hexdag/core/ports/executor.py +237 -0
- hexdag/core/ports/file_storage.py +117 -0
- hexdag/core/ports/healthcheck.py +87 -0
- hexdag/core/ports/llm.py +551 -0
- hexdag/core/ports/memory.py +70 -0
- hexdag/core/ports/observer_manager.py +130 -0
- hexdag/core/ports/secret.py +145 -0
- hexdag/core/ports/tool_router.py +94 -0
- hexdag/core/ports_builder.py +623 -0
- hexdag/core/protocols.py +273 -0
- hexdag/core/resolver.py +304 -0
- hexdag/core/schema/__init__.py +9 -0
- hexdag/core/schema/generator.py +742 -0
- hexdag/core/secrets.py +242 -0
- hexdag/core/types.py +413 -0
- hexdag/core/utils/async_warnings.py +206 -0
- hexdag/core/utils/schema_conversion.py +78 -0
- hexdag/core/utils/sql_validation.py +86 -0
- hexdag/core/validation/secure_json.py +148 -0
- hexdag/core/yaml_macro.py +517 -0
- hexdag/mcp_server.py +3120 -0
- hexdag/studio/__init__.py +10 -0
- hexdag/studio/build_ui.py +92 -0
- hexdag/studio/server/__init__.py +1 -0
- hexdag/studio/server/main.py +100 -0
- hexdag/studio/server/routes/__init__.py +9 -0
- hexdag/studio/server/routes/execute.py +208 -0
- hexdag/studio/server/routes/export.py +558 -0
- hexdag/studio/server/routes/files.py +207 -0
- hexdag/studio/server/routes/plugins.py +419 -0
- hexdag/studio/server/routes/validate.py +220 -0
- hexdag/studio/ui/index.html +13 -0
- hexdag/studio/ui/package-lock.json +2992 -0
- hexdag/studio/ui/package.json +31 -0
- hexdag/studio/ui/postcss.config.js +6 -0
- hexdag/studio/ui/public/hexdag.svg +5 -0
- hexdag/studio/ui/src/App.tsx +251 -0
- hexdag/studio/ui/src/components/Canvas.tsx +408 -0
- hexdag/studio/ui/src/components/ContextMenu.tsx +187 -0
- hexdag/studio/ui/src/components/FileBrowser.tsx +123 -0
- hexdag/studio/ui/src/components/Header.tsx +181 -0
- hexdag/studio/ui/src/components/HexdagNode.tsx +193 -0
- hexdag/studio/ui/src/components/NodeInspector.tsx +512 -0
- hexdag/studio/ui/src/components/NodePalette.tsx +262 -0
- hexdag/studio/ui/src/components/NodePortsSection.tsx +403 -0
- hexdag/studio/ui/src/components/PluginManager.tsx +347 -0
- hexdag/studio/ui/src/components/PortsEditor.tsx +481 -0
- hexdag/studio/ui/src/components/PythonEditor.tsx +195 -0
- hexdag/studio/ui/src/components/ValidationPanel.tsx +105 -0
- hexdag/studio/ui/src/components/YamlEditor.tsx +196 -0
- hexdag/studio/ui/src/components/index.ts +8 -0
- hexdag/studio/ui/src/index.css +92 -0
- hexdag/studio/ui/src/main.tsx +10 -0
- hexdag/studio/ui/src/types/index.ts +123 -0
- hexdag/studio/ui/src/vite-env.d.ts +1 -0
- hexdag/studio/ui/tailwind.config.js +29 -0
- hexdag/studio/ui/tsconfig.json +37 -0
- hexdag/studio/ui/tsconfig.node.json +13 -0
- hexdag/studio/ui/vite.config.ts +35 -0
- hexdag/visualization/__init__.py +69 -0
- hexdag/visualization/dag_visualizer.py +1020 -0
- hexdag-0.5.0.dev1.dist-info/METADATA +369 -0
- hexdag-0.5.0.dev1.dist-info/RECORD +261 -0
- hexdag-0.5.0.dev1.dist-info/WHEEL +4 -0
- hexdag-0.5.0.dev1.dist-info/entry_points.txt +4 -0
- hexdag-0.5.0.dev1.dist-info/licenses/LICENSE +190 -0
- hexdag_plugins/.gitignore +43 -0
- hexdag_plugins/README.md +73 -0
- hexdag_plugins/__init__.py +1 -0
- hexdag_plugins/azure/LICENSE +21 -0
- hexdag_plugins/azure/README.md +414 -0
- hexdag_plugins/azure/__init__.py +21 -0
- hexdag_plugins/azure/azure_blob_adapter.py +450 -0
- hexdag_plugins/azure/azure_cosmos_adapter.py +383 -0
- hexdag_plugins/azure/azure_keyvault_adapter.py +314 -0
- hexdag_plugins/azure/azure_openai_adapter.py +415 -0
- hexdag_plugins/azure/pyproject.toml +107 -0
- hexdag_plugins/azure/tests/__init__.py +1 -0
- hexdag_plugins/azure/tests/test_azure_blob_adapter.py +350 -0
- hexdag_plugins/azure/tests/test_azure_cosmos_adapter.py +323 -0
- hexdag_plugins/azure/tests/test_azure_keyvault_adapter.py +330 -0
- hexdag_plugins/azure/tests/test_azure_openai_adapter.py +329 -0
- hexdag_plugins/hexdag_etl/README.md +168 -0
- hexdag_plugins/hexdag_etl/__init__.py +53 -0
- hexdag_plugins/hexdag_etl/examples/01_simple_pandas_transform.py +270 -0
- hexdag_plugins/hexdag_etl/examples/02_simple_pandas_only.py +149 -0
- hexdag_plugins/hexdag_etl/examples/03_file_io_pipeline.py +109 -0
- hexdag_plugins/hexdag_etl/examples/test_pandas_transform.py +84 -0
- hexdag_plugins/hexdag_etl/hexdag.toml +25 -0
- hexdag_plugins/hexdag_etl/hexdag_etl/__init__.py +48 -0
- hexdag_plugins/hexdag_etl/hexdag_etl/nodes/__init__.py +13 -0
- hexdag_plugins/hexdag_etl/hexdag_etl/nodes/api_extract.py +230 -0
- hexdag_plugins/hexdag_etl/hexdag_etl/nodes/base_node_factory.py +181 -0
- hexdag_plugins/hexdag_etl/hexdag_etl/nodes/file_io.py +415 -0
- hexdag_plugins/hexdag_etl/hexdag_etl/nodes/outlook.py +492 -0
- hexdag_plugins/hexdag_etl/hexdag_etl/nodes/pandas_transform.py +563 -0
- hexdag_plugins/hexdag_etl/hexdag_etl/nodes/sql_extract_load.py +112 -0
- hexdag_plugins/hexdag_etl/pyproject.toml +82 -0
- hexdag_plugins/hexdag_etl/test_transform.py +54 -0
- hexdag_plugins/hexdag_etl/tests/test_plugin_integration.py +62 -0
- hexdag_plugins/mysql_adapter/LICENSE +21 -0
- hexdag_plugins/mysql_adapter/README.md +224 -0
- hexdag_plugins/mysql_adapter/__init__.py +6 -0
- hexdag_plugins/mysql_adapter/mysql_adapter.py +408 -0
- hexdag_plugins/mysql_adapter/pyproject.toml +93 -0
- hexdag_plugins/mysql_adapter/tests/test_mysql_adapter.py +259 -0
- hexdag_plugins/storage/README.md +184 -0
- hexdag_plugins/storage/__init__.py +19 -0
- hexdag_plugins/storage/file/__init__.py +5 -0
- hexdag_plugins/storage/file/local.py +325 -0
- hexdag_plugins/storage/ports/__init__.py +5 -0
- hexdag_plugins/storage/ports/vector_store.py +236 -0
- hexdag_plugins/storage/sql/__init__.py +7 -0
- hexdag_plugins/storage/sql/base.py +187 -0
- hexdag_plugins/storage/sql/mysql.py +27 -0
- hexdag_plugins/storage/sql/postgresql.py +27 -0
- hexdag_plugins/storage/tests/__init__.py +1 -0
- hexdag_plugins/storage/tests/test_local_file_storage.py +161 -0
- hexdag_plugins/storage/tests/test_sql_adapters.py +212 -0
- hexdag_plugins/storage/vector/__init__.py +7 -0
- hexdag_plugins/storage/vector/chromadb.py +223 -0
- hexdag_plugins/storage/vector/in_memory.py +285 -0
- hexdag_plugins/storage/vector/pgvector.py +502 -0
|
@@ -0,0 +1,623 @@
|
|
|
1
|
+
"""Fluent builder for organizing orchestrator ports into logical categories.
|
|
2
|
+
|
|
3
|
+
This builder provides a clean, type-safe interface for configuring orchestrator
|
|
4
|
+
dependencies while maintaining backward compatibility with the flat dictionary format.
|
|
5
|
+
|
|
6
|
+
Enhanced with per-node and per-type port configuration support for fine-grained
|
|
7
|
+
control over port assignment across different node types and specific nodes.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from typing import TYPE_CHECKING, Any, Self
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from hexdag.core.ports import (
|
|
16
|
+
LLM,
|
|
17
|
+
APICall,
|
|
18
|
+
DatabasePort,
|
|
19
|
+
Memory,
|
|
20
|
+
ObserverManagerPort,
|
|
21
|
+
ToolRouter,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
# Type alias for all valid port types
|
|
25
|
+
PortType = (
|
|
26
|
+
LLM
|
|
27
|
+
| APICall
|
|
28
|
+
| DatabasePort
|
|
29
|
+
| Memory
|
|
30
|
+
| ObserverManagerPort
|
|
31
|
+
| ToolRouter
|
|
32
|
+
| Any # Allow Any for backward compatibility with custom ports
|
|
33
|
+
)
|
|
34
|
+
else:
|
|
35
|
+
# At runtime, just use Any (backward compatible)
|
|
36
|
+
PortType = Any
|
|
37
|
+
|
|
38
|
+
from hexdag.core.orchestration.models import PortConfig, PortsConfiguration
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class PortsBuilder:
|
|
42
|
+
"""Fluent builder for organizing ports into logical categories.
|
|
43
|
+
|
|
44
|
+
Provides a type-safe, discoverable API for configuring orchestrator
|
|
45
|
+
dependencies while maintaining backward compatibility.
|
|
46
|
+
|
|
47
|
+
Example
|
|
48
|
+
-------
|
|
49
|
+
```python
|
|
50
|
+
# Traditional flat dictionary approach
|
|
51
|
+
ports = {
|
|
52
|
+
"llm": OpenAIAdapter(),
|
|
53
|
+
"database": PostgresAdapter(),
|
|
54
|
+
"observer_manager": ObserverManager(),
|
|
55
|
+
# ... many more mixed together
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
# New builder approach - organized and type-safe
|
|
59
|
+
ports = (
|
|
60
|
+
PortsBuilder()
|
|
61
|
+
.with_llm(OpenAIAdapter())
|
|
62
|
+
.with_database(PostgresAdapter())
|
|
63
|
+
.with_defaults()
|
|
64
|
+
.build()
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
# Or use with_defaults() for automatic setup
|
|
68
|
+
ports = PortsBuilder().with_defaults().build()
|
|
69
|
+
```
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
def __init__(self) -> None:
|
|
73
|
+
"""Initialize an empty ports builder."""
|
|
74
|
+
self._ports: dict[str, PortType] = {}
|
|
75
|
+
self._type_ports: dict[str, dict[str, PortType]] = {}
|
|
76
|
+
self._node_ports: dict[str, dict[str, PortType]] = {}
|
|
77
|
+
|
|
78
|
+
def _add_port(self, key: str, port: PortType) -> Self:
|
|
79
|
+
"""Add a port to the internal registry.
|
|
80
|
+
|
|
81
|
+
Args
|
|
82
|
+
----
|
|
83
|
+
key: Port identifier
|
|
84
|
+
port: Port implementation
|
|
85
|
+
|
|
86
|
+
Returns
|
|
87
|
+
-------
|
|
88
|
+
Self for method chaining
|
|
89
|
+
"""
|
|
90
|
+
self._ports[key] = port
|
|
91
|
+
return self
|
|
92
|
+
|
|
93
|
+
# Core AI Capabilities
|
|
94
|
+
# --------------------
|
|
95
|
+
|
|
96
|
+
def with_llm(self, llm: LLM) -> Self:
|
|
97
|
+
"""Add a Language Model adapter.
|
|
98
|
+
|
|
99
|
+
Args
|
|
100
|
+
----
|
|
101
|
+
llm: LLM adapter instance (OpenAI, Anthropic, etc.)
|
|
102
|
+
|
|
103
|
+
Returns
|
|
104
|
+
-------
|
|
105
|
+
Self for method chaining
|
|
106
|
+
"""
|
|
107
|
+
return self._add_port("llm", llm)
|
|
108
|
+
|
|
109
|
+
def with_tool_router(self, router: ToolRouter) -> Self:
|
|
110
|
+
"""Add a tool router for function calling.
|
|
111
|
+
|
|
112
|
+
Args
|
|
113
|
+
----
|
|
114
|
+
router: Tool router instance for managing tool execution
|
|
115
|
+
|
|
116
|
+
Returns
|
|
117
|
+
-------
|
|
118
|
+
Self for method chaining
|
|
119
|
+
"""
|
|
120
|
+
return self._add_port("tool_router", router)
|
|
121
|
+
|
|
122
|
+
# Storage & Persistence
|
|
123
|
+
# ---------------------
|
|
124
|
+
|
|
125
|
+
def with_database(self, database: DatabasePort) -> Self:
|
|
126
|
+
"""Add a database adapter.
|
|
127
|
+
|
|
128
|
+
Args
|
|
129
|
+
----
|
|
130
|
+
database: Database adapter instance
|
|
131
|
+
|
|
132
|
+
Returns
|
|
133
|
+
-------
|
|
134
|
+
Self for method chaining
|
|
135
|
+
"""
|
|
136
|
+
return self._add_port("database", database)
|
|
137
|
+
|
|
138
|
+
def with_memory(self, memory: Memory) -> Self:
|
|
139
|
+
"""Add a memory system for agents.
|
|
140
|
+
|
|
141
|
+
Args
|
|
142
|
+
----
|
|
143
|
+
memory: Memory adapter for conversation history
|
|
144
|
+
|
|
145
|
+
Returns
|
|
146
|
+
-------
|
|
147
|
+
Self for method chaining
|
|
148
|
+
"""
|
|
149
|
+
return self._add_port("memory", memory)
|
|
150
|
+
|
|
151
|
+
# Event & Control Systems
|
|
152
|
+
# -----------------------
|
|
153
|
+
|
|
154
|
+
def with_observer_manager(self, manager: ObserverManagerPort) -> Self:
|
|
155
|
+
"""Add an observer manager for event monitoring.
|
|
156
|
+
|
|
157
|
+
Args
|
|
158
|
+
----
|
|
159
|
+
manager: Observer manager for read-only event handling
|
|
160
|
+
|
|
161
|
+
Returns
|
|
162
|
+
-------
|
|
163
|
+
Self for method chaining
|
|
164
|
+
"""
|
|
165
|
+
return self._add_port("observer_manager", manager)
|
|
166
|
+
|
|
167
|
+
# External Integrations
|
|
168
|
+
# ---------------------
|
|
169
|
+
|
|
170
|
+
def with_api_call(self, api_call: APICall) -> Self:
|
|
171
|
+
"""Add an API call adapter.
|
|
172
|
+
|
|
173
|
+
Args
|
|
174
|
+
----
|
|
175
|
+
api_call: API call adapter for external services
|
|
176
|
+
|
|
177
|
+
Returns
|
|
178
|
+
-------
|
|
179
|
+
Self for method chaining
|
|
180
|
+
"""
|
|
181
|
+
return self._add_port("api_call", api_call)
|
|
182
|
+
|
|
183
|
+
# Convenience Methods
|
|
184
|
+
# ------------------
|
|
185
|
+
|
|
186
|
+
def with_defaults(self) -> Self:
|
|
187
|
+
"""Add default implementations for common ports.
|
|
188
|
+
|
|
189
|
+
This method provides sensible defaults:
|
|
190
|
+
- LocalObserverManager for event observation
|
|
191
|
+
- MockLLM for testing (should be overridden in production)
|
|
192
|
+
|
|
193
|
+
Returns
|
|
194
|
+
-------
|
|
195
|
+
Self for method chaining
|
|
196
|
+
"""
|
|
197
|
+
# Only add defaults if not already configured
|
|
198
|
+
if "observer_manager" not in self._ports:
|
|
199
|
+
try:
|
|
200
|
+
from hexdag.builtin.adapters.local import LocalObserverManager
|
|
201
|
+
|
|
202
|
+
self._ports["observer_manager"] = LocalObserverManager()
|
|
203
|
+
except ImportError:
|
|
204
|
+
pass # Optional dependency
|
|
205
|
+
|
|
206
|
+
if "llm" not in self._ports:
|
|
207
|
+
try:
|
|
208
|
+
from hexdag.builtin.adapters.mock import MockLLM
|
|
209
|
+
|
|
210
|
+
self._ports["llm"] = MockLLM()
|
|
211
|
+
except ImportError:
|
|
212
|
+
pass # Optional dependency
|
|
213
|
+
|
|
214
|
+
return self
|
|
215
|
+
|
|
216
|
+
def with_custom(self, key: str, port: Any) -> Self:
|
|
217
|
+
"""Add a custom port with any key.
|
|
218
|
+
|
|
219
|
+
This provides backward compatibility and flexibility for
|
|
220
|
+
custom port implementations.
|
|
221
|
+
|
|
222
|
+
Args
|
|
223
|
+
----
|
|
224
|
+
key: The port key name
|
|
225
|
+
port: The port implementation
|
|
226
|
+
|
|
227
|
+
Returns
|
|
228
|
+
-------
|
|
229
|
+
Self for method chaining
|
|
230
|
+
"""
|
|
231
|
+
return self._add_port(key, port)
|
|
232
|
+
|
|
233
|
+
def update(self, ports: dict[str, Any]) -> Self:
|
|
234
|
+
"""Update multiple ports at once from a dictionary.
|
|
235
|
+
|
|
236
|
+
Useful for migrating from existing dictionary-based configs.
|
|
237
|
+
|
|
238
|
+
Args
|
|
239
|
+
----
|
|
240
|
+
ports: Dictionary of port implementations
|
|
241
|
+
|
|
242
|
+
Returns
|
|
243
|
+
-------
|
|
244
|
+
Self for method chaining
|
|
245
|
+
"""
|
|
246
|
+
self._ports.update(ports)
|
|
247
|
+
return self
|
|
248
|
+
|
|
249
|
+
# Builder Methods
|
|
250
|
+
# --------------
|
|
251
|
+
|
|
252
|
+
def build(self) -> dict[str, Any]:
|
|
253
|
+
"""Build and return the final ports dictionary.
|
|
254
|
+
|
|
255
|
+
Returns
|
|
256
|
+
-------
|
|
257
|
+
Dictionary of configured ports for orchestrator use
|
|
258
|
+
"""
|
|
259
|
+
return self._ports.copy()
|
|
260
|
+
|
|
261
|
+
def clear(self) -> Self:
|
|
262
|
+
"""Clear all configured ports.
|
|
263
|
+
|
|
264
|
+
Returns
|
|
265
|
+
-------
|
|
266
|
+
Self for method chaining
|
|
267
|
+
"""
|
|
268
|
+
self._ports.clear()
|
|
269
|
+
return self
|
|
270
|
+
|
|
271
|
+
# Inspection Methods
|
|
272
|
+
# -----------------
|
|
273
|
+
|
|
274
|
+
def has(self, key: str) -> bool:
|
|
275
|
+
"""Check if a port is configured.
|
|
276
|
+
|
|
277
|
+
Args
|
|
278
|
+
----
|
|
279
|
+
key: The port key to check
|
|
280
|
+
|
|
281
|
+
Returns
|
|
282
|
+
-------
|
|
283
|
+
True if the port is configured
|
|
284
|
+
"""
|
|
285
|
+
return key in self._ports
|
|
286
|
+
|
|
287
|
+
def __contains__(self, key: str) -> bool:
|
|
288
|
+
"""Check if a port is configured using 'in' operator.
|
|
289
|
+
|
|
290
|
+
Parameters
|
|
291
|
+
----------
|
|
292
|
+
key : str
|
|
293
|
+
Port key to check
|
|
294
|
+
|
|
295
|
+
Returns
|
|
296
|
+
-------
|
|
297
|
+
bool
|
|
298
|
+
True if the port is configured at global level
|
|
299
|
+
|
|
300
|
+
Examples
|
|
301
|
+
--------
|
|
302
|
+
>>> builder = PortsBuilder()
|
|
303
|
+
>>> class MockPort: pass
|
|
304
|
+
>>> builder["llm"] = MockPort()
|
|
305
|
+
>>> "llm" in builder
|
|
306
|
+
True
|
|
307
|
+
>>> "database" in builder
|
|
308
|
+
False
|
|
309
|
+
"""
|
|
310
|
+
return key in self._ports
|
|
311
|
+
|
|
312
|
+
def get(self, key: str, default: Any = None) -> Any:
|
|
313
|
+
"""Get a configured port by key.
|
|
314
|
+
|
|
315
|
+
Args
|
|
316
|
+
----
|
|
317
|
+
key: The port key
|
|
318
|
+
default: Default value if not found
|
|
319
|
+
|
|
320
|
+
Returns
|
|
321
|
+
-------
|
|
322
|
+
The port instance or default value
|
|
323
|
+
"""
|
|
324
|
+
return self._ports.get(key, default)
|
|
325
|
+
|
|
326
|
+
def keys(self) -> list[str]:
|
|
327
|
+
"""Get list of configured port keys.
|
|
328
|
+
|
|
329
|
+
Returns
|
|
330
|
+
-------
|
|
331
|
+
List of port keys
|
|
332
|
+
"""
|
|
333
|
+
return list(self._ports.keys())
|
|
334
|
+
|
|
335
|
+
def __len__(self) -> int:
|
|
336
|
+
"""Get number of configured ports.
|
|
337
|
+
|
|
338
|
+
Returns
|
|
339
|
+
-------
|
|
340
|
+
Number of configured ports
|
|
341
|
+
"""
|
|
342
|
+
return len(self._ports)
|
|
343
|
+
|
|
344
|
+
def __repr__(self) -> str:
|
|
345
|
+
"""Get string representation of builder state.
|
|
346
|
+
|
|
347
|
+
Returns
|
|
348
|
+
-------
|
|
349
|
+
String showing configured ports
|
|
350
|
+
"""
|
|
351
|
+
configured = ", ".join(self._ports.keys()) if self._ports else "none"
|
|
352
|
+
return f"PortsBuilder(configured: {configured})"
|
|
353
|
+
|
|
354
|
+
def __getitem__(self, key: str | tuple) -> Any:
|
|
355
|
+
"""Get port(s) using dictionary-style access with tuple key support.
|
|
356
|
+
|
|
357
|
+
Supports simple and hierarchical access patterns:
|
|
358
|
+
- Simple: builder["llm"] - Get global port
|
|
359
|
+
- Tuple (2 elements): builder["agent", "llm"] - Get type-level port
|
|
360
|
+
- Tuple (3 elements): builder["researcher", "agent", "llm"] - Get node-level port
|
|
361
|
+
|
|
362
|
+
Parameters
|
|
363
|
+
----------
|
|
364
|
+
key : str | tuple
|
|
365
|
+
Port key or tuple of (node, type, port) for hierarchical lookup
|
|
366
|
+
|
|
367
|
+
Returns
|
|
368
|
+
-------
|
|
369
|
+
Any
|
|
370
|
+
The port instance
|
|
371
|
+
|
|
372
|
+
Raises
|
|
373
|
+
------
|
|
374
|
+
KeyError
|
|
375
|
+
If the port is not configured
|
|
376
|
+
|
|
377
|
+
Examples
|
|
378
|
+
--------
|
|
379
|
+
>>> builder = PortsBuilder()
|
|
380
|
+
>>> class MockLLM: pass
|
|
381
|
+
>>> class AgentLLM: pass
|
|
382
|
+
>>> builder["llm"] = MockLLM()
|
|
383
|
+
>>> port = builder["llm"] # Simple access
|
|
384
|
+
>>> isinstance(port, MockLLM)
|
|
385
|
+
True
|
|
386
|
+
>>> result = builder.for_type("agent", llm=AgentLLM()) # Returns self
|
|
387
|
+
>>> port = builder["agent", "llm"] # Type-level access
|
|
388
|
+
>>> isinstance(port, AgentLLM)
|
|
389
|
+
True
|
|
390
|
+
"""
|
|
391
|
+
if isinstance(key, tuple):
|
|
392
|
+
if len(key) == 2:
|
|
393
|
+
# (type, port_name) - Get type-level port
|
|
394
|
+
node_type, port_name = key
|
|
395
|
+
if node_type in self._type_ports and port_name in self._type_ports[node_type]:
|
|
396
|
+
return self._type_ports[node_type][port_name]
|
|
397
|
+
# Fall back to global
|
|
398
|
+
return self._ports.get(port_name)
|
|
399
|
+
if len(key) == 3:
|
|
400
|
+
# (node_name, type, port_name) - Get node-level port with resolution
|
|
401
|
+
node_name, node_type, port_name = key
|
|
402
|
+
# Try node-level first
|
|
403
|
+
if node_name in self._node_ports and port_name in self._node_ports[node_name]:
|
|
404
|
+
return self._node_ports[node_name][port_name]
|
|
405
|
+
# Fall back to type-level
|
|
406
|
+
if node_type in self._type_ports and port_name in self._type_ports[node_type]:
|
|
407
|
+
return self._type_ports[node_type][port_name]
|
|
408
|
+
# Fall back to global
|
|
409
|
+
return self._ports.get(port_name)
|
|
410
|
+
raise KeyError(f"Tuple key must have 2 or 3 elements, got {len(key)}")
|
|
411
|
+
return self._ports[key]
|
|
412
|
+
|
|
413
|
+
def __setitem__(self, key: str | tuple, value: Any) -> None:
|
|
414
|
+
"""Set port(s) using dictionary-style access with tuple key support.
|
|
415
|
+
|
|
416
|
+
Supports simple and hierarchical configuration:
|
|
417
|
+
- Simple: builder["llm"] = adapter - Set global port
|
|
418
|
+
- Tuple (2 elements): builder["agent", "llm"] = adapter - Set type-level port
|
|
419
|
+
- Tuple (3 elements): builder["node", "agent", "llm"] = adapter - Set node-level port
|
|
420
|
+
|
|
421
|
+
Parameters
|
|
422
|
+
----------
|
|
423
|
+
key : str | tuple
|
|
424
|
+
Port key or tuple of (node, type, port) for hierarchical configuration
|
|
425
|
+
value : Any
|
|
426
|
+
Port implementation to configure
|
|
427
|
+
|
|
428
|
+
Examples
|
|
429
|
+
--------
|
|
430
|
+
>>> builder = PortsBuilder()
|
|
431
|
+
>>> class MockLLM: pass
|
|
432
|
+
>>> class OpenAIAdapter: pass
|
|
433
|
+
>>> class ClaudeAdapter: pass
|
|
434
|
+
>>> builder["llm"] = MockLLM() # Global
|
|
435
|
+
>>> builder["agent", "llm"] = OpenAIAdapter() # Type-level
|
|
436
|
+
>>> builder["researcher", "agent", "llm"] = ClaudeAdapter() # Node-level
|
|
437
|
+
>>> isinstance(builder["llm"], MockLLM)
|
|
438
|
+
True
|
|
439
|
+
>>> isinstance(builder["agent", "llm"], OpenAIAdapter)
|
|
440
|
+
True
|
|
441
|
+
>>> isinstance(builder["researcher", "agent", "llm"], ClaudeAdapter)
|
|
442
|
+
True
|
|
443
|
+
"""
|
|
444
|
+
if isinstance(key, tuple):
|
|
445
|
+
if len(key) == 2:
|
|
446
|
+
# (type, port_name) - Set type-level port
|
|
447
|
+
node_type, port_name = key
|
|
448
|
+
self.for_type(node_type, **{port_name: value})
|
|
449
|
+
elif len(key) == 3:
|
|
450
|
+
# (node_name, type, port_name) - Set node-level port
|
|
451
|
+
node_name, _node_type, port_name = key
|
|
452
|
+
self.for_node(node_name, **{port_name: value})
|
|
453
|
+
else:
|
|
454
|
+
raise KeyError(f"Tuple key must have 2 or 3 elements, got {len(key)}")
|
|
455
|
+
else:
|
|
456
|
+
self._add_port(key, value)
|
|
457
|
+
|
|
458
|
+
def __delitem__(self, key: str) -> None:
|
|
459
|
+
"""Remove a port from the builder.
|
|
460
|
+
|
|
461
|
+
Parameters
|
|
462
|
+
----------
|
|
463
|
+
key : str
|
|
464
|
+
Port key to remove
|
|
465
|
+
|
|
466
|
+
Examples
|
|
467
|
+
--------
|
|
468
|
+
.. code-block:: python
|
|
469
|
+
|
|
470
|
+
builder = PortsBuilder()
|
|
471
|
+
builder["llm"] = MockLLM()
|
|
472
|
+
del builder["llm"]
|
|
473
|
+
assert "llm" not in builder
|
|
474
|
+
"""
|
|
475
|
+
del self._ports[key]
|
|
476
|
+
|
|
477
|
+
# Enhanced Configuration Methods
|
|
478
|
+
# ------------------------------
|
|
479
|
+
|
|
480
|
+
def for_type(self, node_type: str, **ports: Any) -> Self:
|
|
481
|
+
"""Configure ports for all nodes of a specific type.
|
|
482
|
+
|
|
483
|
+
This method allows setting default ports for all nodes of a given type
|
|
484
|
+
(e.g., "agent", "llm", "function"). These type-level defaults override
|
|
485
|
+
global defaults but are overridden by per-node configurations.
|
|
486
|
+
|
|
487
|
+
Args
|
|
488
|
+
----
|
|
489
|
+
node_type: The node type to configure (e.g., "agent", "llm", "function")
|
|
490
|
+
**ports: Port implementations as keyword arguments
|
|
491
|
+
|
|
492
|
+
Returns
|
|
493
|
+
-------
|
|
494
|
+
Self for method chaining
|
|
495
|
+
|
|
496
|
+
Examples
|
|
497
|
+
--------
|
|
498
|
+
Example usage::
|
|
499
|
+
|
|
500
|
+
from hexdag.builtin.adapters.openai import OpenAIAdapter
|
|
501
|
+
from hexdag.builtin.adapters.mock import MockLLM
|
|
502
|
+
|
|
503
|
+
builder = (
|
|
504
|
+
PortsBuilder()
|
|
505
|
+
.with_llm(MockLLM()) # Global default
|
|
506
|
+
.for_type("agent", llm=OpenAIAdapter(model="gpt-4")) # Agent nodes
|
|
507
|
+
.build_configuration()
|
|
508
|
+
)
|
|
509
|
+
|
|
510
|
+
# All agent nodes will use OpenAI, other nodes use MockLLM
|
|
511
|
+
config = builder.build_configuration()
|
|
512
|
+
agent_ports = config.resolve_ports("my_agent", "agent")
|
|
513
|
+
assert isinstance(agent_ports["llm"].port, OpenAIAdapter)
|
|
514
|
+
"""
|
|
515
|
+
if node_type not in self._type_ports:
|
|
516
|
+
self._type_ports[node_type] = {}
|
|
517
|
+
self._type_ports[node_type].update(ports)
|
|
518
|
+
return self
|
|
519
|
+
|
|
520
|
+
def for_node(self, node_name: str, **ports: Any) -> Self:
|
|
521
|
+
"""Configure ports for a specific node by name.
|
|
522
|
+
|
|
523
|
+
This method allows overriding ports for individual nodes, providing
|
|
524
|
+
the highest level of configuration precedence. Perfect for nodes that
|
|
525
|
+
require special adapters or configurations.
|
|
526
|
+
|
|
527
|
+
Args
|
|
528
|
+
----
|
|
529
|
+
node_name: The node name to configure
|
|
530
|
+
**ports: Port implementations as keyword arguments
|
|
531
|
+
|
|
532
|
+
Returns
|
|
533
|
+
-------
|
|
534
|
+
Self for method chaining
|
|
535
|
+
|
|
536
|
+
Examples
|
|
537
|
+
--------
|
|
538
|
+
Example usage::
|
|
539
|
+
|
|
540
|
+
from hexdag.builtin.adapters.anthropic import AnthropicAdapter
|
|
541
|
+
from hexdag.builtin.adapters.openai import OpenAIAdapter
|
|
542
|
+
|
|
543
|
+
builder = (
|
|
544
|
+
PortsBuilder()
|
|
545
|
+
.for_type("agent", llm=OpenAIAdapter(model="gpt-4")) # Agent default
|
|
546
|
+
.for_node("researcher", llm=AnthropicAdapter(model="claude-3")) # Override
|
|
547
|
+
.build_configuration()
|
|
548
|
+
)
|
|
549
|
+
|
|
550
|
+
# Researcher node gets Claude, other agents get GPT-4
|
|
551
|
+
config = builder.build_configuration()
|
|
552
|
+
researcher_ports = config.resolve_ports("researcher", "agent")
|
|
553
|
+
assert isinstance(researcher_ports["llm"].port, AnthropicAdapter)
|
|
554
|
+
"""
|
|
555
|
+
if node_name not in self._node_ports:
|
|
556
|
+
self._node_ports[node_name] = {}
|
|
557
|
+
self._node_ports[node_name].update(ports)
|
|
558
|
+
return self
|
|
559
|
+
|
|
560
|
+
def build_configuration(self) -> PortsConfiguration:
|
|
561
|
+
"""Build a PortsConfiguration with full inheritance support.
|
|
562
|
+
|
|
563
|
+
Creates a PortsConfiguration object that encapsulates global, per-type,
|
|
564
|
+
and per-node port configurations. This provides more flexibility than
|
|
565
|
+
the flat dictionary returned by build().
|
|
566
|
+
|
|
567
|
+
Returns
|
|
568
|
+
-------
|
|
569
|
+
PortsConfiguration
|
|
570
|
+
Configuration with port inheritance and resolution support
|
|
571
|
+
|
|
572
|
+
Examples
|
|
573
|
+
--------
|
|
574
|
+
Example usage::
|
|
575
|
+
|
|
576
|
+
from hexdag.builtin.adapters.mock import MockLLM
|
|
577
|
+
from hexdag.builtin.adapters.openai import OpenAIAdapter
|
|
578
|
+
from hexdag.builtin.adapters.anthropic import AnthropicAdapter
|
|
579
|
+
|
|
580
|
+
config = (
|
|
581
|
+
PortsBuilder()
|
|
582
|
+
.with_llm(MockLLM()) # Global default
|
|
583
|
+
.for_type("agent", llm=OpenAIAdapter(model="gpt-4")) # Agent default
|
|
584
|
+
.for_node("researcher", llm=AnthropicAdapter(model="claude-3")) # Override
|
|
585
|
+
.build_configuration()
|
|
586
|
+
)
|
|
587
|
+
|
|
588
|
+
# Resolve ports for different nodes
|
|
589
|
+
researcher = config.to_flat_dict("researcher", "agent") # AnthropicAdapter
|
|
590
|
+
analyzer = config.to_flat_dict("analyzer", "agent") # OpenAIAdapter
|
|
591
|
+
transformer = config.to_flat_dict("transformer", "function") # MockLLM
|
|
592
|
+
|
|
593
|
+
Notes
|
|
594
|
+
-----
|
|
595
|
+
Resolution order: per-node > per-type > global defaults
|
|
596
|
+
|
|
597
|
+
See Also
|
|
598
|
+
--------
|
|
599
|
+
build : For backward-compatible flat dictionary output
|
|
600
|
+
"""
|
|
601
|
+
|
|
602
|
+
def _wrap_ports(ports_dict: dict[str, PortType] | None) -> dict[str, PortConfig] | None:
|
|
603
|
+
"""Helper to wrap ports in PortConfig."""
|
|
604
|
+
return {k: PortConfig(port=v) for k, v in ports_dict.items()} if ports_dict else None
|
|
605
|
+
|
|
606
|
+
def _wrap_nested_ports(
|
|
607
|
+
nested: dict[str, dict[str, PortType]] | None,
|
|
608
|
+
) -> dict[str, dict[str, PortConfig]] | None:
|
|
609
|
+
"""Helper to wrap nested ports in PortConfig."""
|
|
610
|
+
return (
|
|
611
|
+
{
|
|
612
|
+
name: {k: PortConfig(port=v) for k, v in ports.items()}
|
|
613
|
+
for name, ports in nested.items()
|
|
614
|
+
}
|
|
615
|
+
if nested
|
|
616
|
+
else None
|
|
617
|
+
)
|
|
618
|
+
|
|
619
|
+
return PortsConfiguration(
|
|
620
|
+
global_ports=_wrap_ports(self._ports),
|
|
621
|
+
type_ports=_wrap_nested_ports(self._type_ports),
|
|
622
|
+
node_ports=_wrap_nested_ports(self._node_ports),
|
|
623
|
+
)
|