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,573 @@
|
|
|
1
|
+
"""Simple event data classes and registry metadata."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass(frozen=True)
|
|
11
|
+
class EventSpec:
|
|
12
|
+
"""Specification defining taxonomy metadata for an event class."""
|
|
13
|
+
|
|
14
|
+
event_type: str
|
|
15
|
+
envelope_fields: dict[str, str]
|
|
16
|
+
attr_fields: tuple[str, ...] | None = None
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass
|
|
20
|
+
class Event:
|
|
21
|
+
"""Base class for all events - provides timestamp."""
|
|
22
|
+
|
|
23
|
+
timestamp: datetime = field(default_factory=datetime.now, init=False)
|
|
24
|
+
|
|
25
|
+
def log_message(self) -> str:
|
|
26
|
+
"""Get a formatted log message for this event.
|
|
27
|
+
|
|
28
|
+
Override in subclasses to provide custom formatting.
|
|
29
|
+
|
|
30
|
+
Returns
|
|
31
|
+
-------
|
|
32
|
+
str
|
|
33
|
+
A formatted string suitable for logging
|
|
34
|
+
"""
|
|
35
|
+
return f"{self.__class__.__name__} at {self.timestamp.isoformat()}"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
EVENT_REGISTRY: dict[str, EventSpec] = {
|
|
39
|
+
"PipelineStarted": EventSpec(
|
|
40
|
+
"pipeline:started",
|
|
41
|
+
{"pipeline": "name"},
|
|
42
|
+
("total_waves", "total_nodes"),
|
|
43
|
+
),
|
|
44
|
+
"PipelineCompleted": EventSpec(
|
|
45
|
+
"pipeline:completed",
|
|
46
|
+
{"pipeline": "name"},
|
|
47
|
+
("duration_ms", "node_results"),
|
|
48
|
+
),
|
|
49
|
+
"NodeStarted": EventSpec(
|
|
50
|
+
"node:started",
|
|
51
|
+
{"node": "name", "wave": "wave_index"},
|
|
52
|
+
("dependencies",),
|
|
53
|
+
),
|
|
54
|
+
"NodeCompleted": EventSpec(
|
|
55
|
+
"node:completed",
|
|
56
|
+
{"node": "name", "wave": "wave_index"},
|
|
57
|
+
("result", "duration_ms"),
|
|
58
|
+
),
|
|
59
|
+
"NodeFailed": EventSpec(
|
|
60
|
+
"node:failed",
|
|
61
|
+
{"node": "name", "wave": "wave_index"},
|
|
62
|
+
("error",),
|
|
63
|
+
),
|
|
64
|
+
"WaveStarted": EventSpec(
|
|
65
|
+
"wave:started",
|
|
66
|
+
{"wave": "wave_index"},
|
|
67
|
+
("nodes",),
|
|
68
|
+
),
|
|
69
|
+
"WaveCompleted": EventSpec(
|
|
70
|
+
"wave:completed",
|
|
71
|
+
{"wave": "wave_index"},
|
|
72
|
+
("duration_ms",),
|
|
73
|
+
),
|
|
74
|
+
"LLMPromptSent": EventSpec(
|
|
75
|
+
"llm:prompt",
|
|
76
|
+
{"node": "node_name"},
|
|
77
|
+
("messages",),
|
|
78
|
+
),
|
|
79
|
+
"LLMResponseReceived": EventSpec(
|
|
80
|
+
"llm:response",
|
|
81
|
+
{"node": "node_name"},
|
|
82
|
+
("response", "duration_ms"),
|
|
83
|
+
),
|
|
84
|
+
"ToolCalled": EventSpec(
|
|
85
|
+
"tool:called",
|
|
86
|
+
{"node": "node_name"},
|
|
87
|
+
("tool_name", "params"),
|
|
88
|
+
),
|
|
89
|
+
"ToolCompleted": EventSpec(
|
|
90
|
+
"tool:completed",
|
|
91
|
+
{"node": "node_name"},
|
|
92
|
+
("tool_name", "result", "duration_ms"),
|
|
93
|
+
),
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
# Node events
|
|
98
|
+
@dataclass(slots=True)
|
|
99
|
+
class NodeStarted(Event):
|
|
100
|
+
"""A node has started execution."""
|
|
101
|
+
|
|
102
|
+
name: str
|
|
103
|
+
wave_index: int
|
|
104
|
+
dependencies: tuple[str, ...] | list[str] = field(default_factory=tuple)
|
|
105
|
+
|
|
106
|
+
def log_message(self) -> str:
|
|
107
|
+
"""Format log message for node start event.
|
|
108
|
+
|
|
109
|
+
Returns
|
|
110
|
+
-------
|
|
111
|
+
str
|
|
112
|
+
Formatted log message for node start
|
|
113
|
+
"""
|
|
114
|
+
deps = f" (deps: {', '.join(self.dependencies)})" if self.dependencies else ""
|
|
115
|
+
return f"đ Node '{self.name}' started in wave {self.wave_index}{deps}"
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
@dataclass(slots=True)
|
|
119
|
+
class NodeCompleted(Event):
|
|
120
|
+
"""A node has completed successfully."""
|
|
121
|
+
|
|
122
|
+
name: str
|
|
123
|
+
wave_index: int
|
|
124
|
+
result: Any
|
|
125
|
+
duration_ms: float
|
|
126
|
+
|
|
127
|
+
def log_message(self) -> str:
|
|
128
|
+
"""Format log message for node completion event.
|
|
129
|
+
|
|
130
|
+
Returns
|
|
131
|
+
-------
|
|
132
|
+
str
|
|
133
|
+
Formatted log message for node completion
|
|
134
|
+
"""
|
|
135
|
+
return f"â
Node '{self.name}' completed in {self.duration_ms / 1000:.2f}s"
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
@dataclass(slots=True)
|
|
139
|
+
class NodeFailed(Event):
|
|
140
|
+
"""A node has failed."""
|
|
141
|
+
|
|
142
|
+
name: str
|
|
143
|
+
wave_index: int
|
|
144
|
+
error: Exception
|
|
145
|
+
|
|
146
|
+
def log_message(self) -> str:
|
|
147
|
+
"""Format log message for node failure event.
|
|
148
|
+
|
|
149
|
+
Returns
|
|
150
|
+
-------
|
|
151
|
+
str
|
|
152
|
+
Formatted log message for node failure
|
|
153
|
+
"""
|
|
154
|
+
return f"â Node '{self.name}' failed: {self.error}"
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
@dataclass(slots=True)
|
|
158
|
+
class NodeCancelled(Event):
|
|
159
|
+
"""A node execution was cancelled.
|
|
160
|
+
|
|
161
|
+
Attributes
|
|
162
|
+
----------
|
|
163
|
+
name : str
|
|
164
|
+
Node name
|
|
165
|
+
wave_index : int
|
|
166
|
+
Wave index where node was executing
|
|
167
|
+
reason : str | None
|
|
168
|
+
Reason for cancellation (e.g., "timeout", "user_cancel")
|
|
169
|
+
"""
|
|
170
|
+
|
|
171
|
+
name: str
|
|
172
|
+
wave_index: int
|
|
173
|
+
reason: str | None = None
|
|
174
|
+
|
|
175
|
+
def log_message(self) -> str:
|
|
176
|
+
"""Format log message for node cancellation event."""
|
|
177
|
+
return f"đĢ Node '{self.name}' cancelled: {self.reason or 'unknown'}"
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
@dataclass(slots=True)
|
|
181
|
+
class NodeSkipped(Event):
|
|
182
|
+
"""A node execution was skipped due to when clause evaluation.
|
|
183
|
+
|
|
184
|
+
Attributes
|
|
185
|
+
----------
|
|
186
|
+
name : str
|
|
187
|
+
Node name
|
|
188
|
+
wave_index : int
|
|
189
|
+
Wave index where node was scheduled
|
|
190
|
+
reason : str | None
|
|
191
|
+
Reason for skipping (e.g., "when clause 'status == active' evaluated to False")
|
|
192
|
+
"""
|
|
193
|
+
|
|
194
|
+
name: str
|
|
195
|
+
wave_index: int
|
|
196
|
+
reason: str | None = None
|
|
197
|
+
|
|
198
|
+
def log_message(self) -> str:
|
|
199
|
+
"""Format log message for node skip event."""
|
|
200
|
+
return f"âī¸ Node '{self.name}' skipped: {self.reason or 'unknown'}"
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
# Wave events
|
|
204
|
+
@dataclass(slots=True)
|
|
205
|
+
class WaveStarted(Event):
|
|
206
|
+
"""A wave of parallel nodes has started."""
|
|
207
|
+
|
|
208
|
+
wave_index: int
|
|
209
|
+
nodes: list[str]
|
|
210
|
+
|
|
211
|
+
def log_message(self) -> str:
|
|
212
|
+
"""Format log message for wave start event.
|
|
213
|
+
|
|
214
|
+
Returns
|
|
215
|
+
-------
|
|
216
|
+
str
|
|
217
|
+
Formatted log message for wave start
|
|
218
|
+
"""
|
|
219
|
+
return f"đ Wave {self.wave_index} started with {len(self.nodes)} nodes"
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
@dataclass(slots=True)
|
|
223
|
+
class WaveCompleted(Event):
|
|
224
|
+
"""A wave has completed."""
|
|
225
|
+
|
|
226
|
+
wave_index: int
|
|
227
|
+
duration_ms: float
|
|
228
|
+
|
|
229
|
+
def log_message(self) -> str:
|
|
230
|
+
"""Format log message for wave completion event.
|
|
231
|
+
|
|
232
|
+
Returns
|
|
233
|
+
-------
|
|
234
|
+
str
|
|
235
|
+
Formatted log message for wave completion
|
|
236
|
+
"""
|
|
237
|
+
return f"â
Wave {self.wave_index} completed in {self.duration_ms / 1000:.2f}s"
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
# Pipeline events
|
|
241
|
+
@dataclass(slots=True)
|
|
242
|
+
class PipelineStarted(Event):
|
|
243
|
+
"""Pipeline execution has started."""
|
|
244
|
+
|
|
245
|
+
name: str
|
|
246
|
+
total_waves: int
|
|
247
|
+
total_nodes: int
|
|
248
|
+
|
|
249
|
+
def log_message(self) -> str:
|
|
250
|
+
"""Format log message for pipeline start event.
|
|
251
|
+
|
|
252
|
+
Returns
|
|
253
|
+
-------
|
|
254
|
+
str
|
|
255
|
+
Formatted log message for pipeline start
|
|
256
|
+
"""
|
|
257
|
+
return (
|
|
258
|
+
f"đŦ Pipeline '{self.name}' started "
|
|
259
|
+
f"({self.total_nodes} nodes, {self.total_waves} waves)"
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
@dataclass(slots=True)
|
|
264
|
+
class PipelineCompleted(Event):
|
|
265
|
+
"""Pipeline has completed successfully."""
|
|
266
|
+
|
|
267
|
+
name: str
|
|
268
|
+
duration_ms: float
|
|
269
|
+
node_results: dict[str, Any] = field(default_factory=dict)
|
|
270
|
+
|
|
271
|
+
def log_message(self) -> str:
|
|
272
|
+
"""Format log message for pipeline completion event.
|
|
273
|
+
|
|
274
|
+
Returns
|
|
275
|
+
-------
|
|
276
|
+
str
|
|
277
|
+
Formatted log message for pipeline completion
|
|
278
|
+
"""
|
|
279
|
+
return f"đ Pipeline '{self.name}' completed in {self.duration_ms / 1000:.2f}s"
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
@dataclass(slots=True)
|
|
283
|
+
class PipelineCancelled(Event):
|
|
284
|
+
"""Pipeline execution was cancelled.
|
|
285
|
+
|
|
286
|
+
Attributes
|
|
287
|
+
----------
|
|
288
|
+
name : str
|
|
289
|
+
Pipeline name
|
|
290
|
+
duration_ms : float
|
|
291
|
+
Duration until cancellation in milliseconds
|
|
292
|
+
reason : str | None
|
|
293
|
+
Reason for cancellation (e.g., "timeout", "user_cancel")
|
|
294
|
+
partial_results : dict[str, Any]
|
|
295
|
+
Results from nodes that completed before cancellation
|
|
296
|
+
"""
|
|
297
|
+
|
|
298
|
+
name: str
|
|
299
|
+
duration_ms: float
|
|
300
|
+
reason: str | None = None
|
|
301
|
+
partial_results: dict[str, Any] = field(default_factory=dict)
|
|
302
|
+
|
|
303
|
+
def log_message(self) -> str:
|
|
304
|
+
"""Format log message for pipeline cancellation event."""
|
|
305
|
+
completed_nodes = len(self.partial_results)
|
|
306
|
+
return (
|
|
307
|
+
f"đ Pipeline '{self.name}' cancelled after {self.duration_ms / 1000:.2f}s "
|
|
308
|
+
f"({completed_nodes} nodes completed): {self.reason or 'unknown'}"
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
# LLM events
|
|
313
|
+
@dataclass(slots=True)
|
|
314
|
+
class LLMPromptSent(Event):
|
|
315
|
+
"""LLM prompt has been sent."""
|
|
316
|
+
|
|
317
|
+
node_name: str
|
|
318
|
+
messages: list[dict[str, str]]
|
|
319
|
+
|
|
320
|
+
def log_message(self) -> str:
|
|
321
|
+
"""Format log message for LLM prompt event.
|
|
322
|
+
|
|
323
|
+
Returns
|
|
324
|
+
-------
|
|
325
|
+
str
|
|
326
|
+
Formatted log message for LLM prompt
|
|
327
|
+
"""
|
|
328
|
+
return f"đ LLM prompt sent from '{self.node_name}' ({len(self.messages)} messages)"
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
@dataclass(slots=True)
|
|
332
|
+
class LLMResponseReceived(Event):
|
|
333
|
+
"""LLM response has been received."""
|
|
334
|
+
|
|
335
|
+
node_name: str
|
|
336
|
+
response: str
|
|
337
|
+
duration_ms: float
|
|
338
|
+
|
|
339
|
+
def log_message(self) -> str:
|
|
340
|
+
"""Format log message for LLM response event.
|
|
341
|
+
|
|
342
|
+
Returns
|
|
343
|
+
-------
|
|
344
|
+
str
|
|
345
|
+
Formatted log message for LLM response
|
|
346
|
+
"""
|
|
347
|
+
return f"đ¤ LLM response for '{self.node_name}' in {self.duration_ms / 1000:.2f}s"
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
# Tool events
|
|
351
|
+
@dataclass(slots=True)
|
|
352
|
+
class ToolCalled(Event):
|
|
353
|
+
"""A tool has been invoked."""
|
|
354
|
+
|
|
355
|
+
node_name: str
|
|
356
|
+
tool_name: str
|
|
357
|
+
params: dict[str, Any]
|
|
358
|
+
|
|
359
|
+
def log_message(self) -> str:
|
|
360
|
+
"""Format log message for tool call event.
|
|
361
|
+
|
|
362
|
+
Returns
|
|
363
|
+
-------
|
|
364
|
+
str
|
|
365
|
+
Formatted log message for tool call
|
|
366
|
+
"""
|
|
367
|
+
return f"đ§ Tool '{self.tool_name}' called from '{self.node_name}'"
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
@dataclass(slots=True)
|
|
371
|
+
class ToolCompleted(Event):
|
|
372
|
+
"""A tool has completed."""
|
|
373
|
+
|
|
374
|
+
node_name: str
|
|
375
|
+
tool_name: str
|
|
376
|
+
result: Any
|
|
377
|
+
duration_ms: float
|
|
378
|
+
|
|
379
|
+
def log_message(self) -> str:
|
|
380
|
+
"""Format log message for tool completion event.
|
|
381
|
+
|
|
382
|
+
Returns
|
|
383
|
+
-------
|
|
384
|
+
str
|
|
385
|
+
Formatted log message for tool completion
|
|
386
|
+
"""
|
|
387
|
+
return f"â
Tool '{self.tool_name}' completed in {self.duration_ms / 1000:.2f}s"
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
# Policy-related events for tracking policy evaluations and decisions
|
|
391
|
+
@dataclass(slots=True)
|
|
392
|
+
class PolicyEvaluated(Event):
|
|
393
|
+
"""Event emitted after a policy has been evaluated."""
|
|
394
|
+
|
|
395
|
+
policy_name: str
|
|
396
|
+
signal: str # The signal returned by the policy
|
|
397
|
+
duration_ms: float
|
|
398
|
+
context_node: str | None = None
|
|
399
|
+
|
|
400
|
+
def log_message(self) -> str:
|
|
401
|
+
"""Format log message for policy evaluation."""
|
|
402
|
+
return (
|
|
403
|
+
f"đ Policy '{self.policy_name}' evaluated -> {self.signal} ({self.duration_ms:.1f}ms)"
|
|
404
|
+
)
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
@dataclass(slots=True)
|
|
408
|
+
class PolicyTriggered(Event):
|
|
409
|
+
"""Event emitted when a policy's condition is triggered."""
|
|
410
|
+
|
|
411
|
+
policy_name: str
|
|
412
|
+
trigger_reason: str
|
|
413
|
+
context_node: str | None = None
|
|
414
|
+
|
|
415
|
+
def log_message(self) -> str:
|
|
416
|
+
"""Format log message for policy trigger."""
|
|
417
|
+
node_info = f" for node '{self.context_node}'" if self.context_node else ""
|
|
418
|
+
return f"đ¯ Policy '{self.policy_name}' triggered{node_info}: {self.trigger_reason}"
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
@dataclass(slots=True)
|
|
422
|
+
class PolicySkipped(Event):
|
|
423
|
+
"""Event emitted when a policy causes a node to be skipped."""
|
|
424
|
+
|
|
425
|
+
policy_name: str
|
|
426
|
+
node_name: str
|
|
427
|
+
reason: str | None = None
|
|
428
|
+
|
|
429
|
+
def log_message(self) -> str:
|
|
430
|
+
"""Format log message for policy skip."""
|
|
431
|
+
reason_info = f": {self.reason}" if self.reason else ""
|
|
432
|
+
return f"âī¸ Policy '{self.policy_name}' skipped node '{self.node_name}'{reason_info}"
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
@dataclass(slots=True)
|
|
436
|
+
class PolicyFallback(Event):
|
|
437
|
+
"""Event emitted when a policy provides a fallback value."""
|
|
438
|
+
|
|
439
|
+
policy_name: str
|
|
440
|
+
node_name: str
|
|
441
|
+
fallback_value: Any
|
|
442
|
+
reason: str | None = None
|
|
443
|
+
|
|
444
|
+
def log_message(self) -> str:
|
|
445
|
+
"""Format log message for policy fallback."""
|
|
446
|
+
reason_info = f": {self.reason}" if self.reason else ""
|
|
447
|
+
return (
|
|
448
|
+
f"đ Policy '{self.policy_name}' provided fallback for '{self.node_name}'{reason_info}"
|
|
449
|
+
)
|
|
450
|
+
|
|
451
|
+
|
|
452
|
+
@dataclass(slots=True)
|
|
453
|
+
class PolicyRetry(Event):
|
|
454
|
+
"""Event emitted when a policy triggers a retry."""
|
|
455
|
+
|
|
456
|
+
policy_name: str
|
|
457
|
+
node_name: str
|
|
458
|
+
attempt: int
|
|
459
|
+
delay_ms: float | None = None
|
|
460
|
+
max_attempts: int | None = None
|
|
461
|
+
|
|
462
|
+
def log_message(self) -> str:
|
|
463
|
+
"""Format log message for policy retry."""
|
|
464
|
+
attempt_info = f" (attempt {self.attempt}"
|
|
465
|
+
if self.max_attempts:
|
|
466
|
+
attempt_info += f"/{self.max_attempts}"
|
|
467
|
+
attempt_info += ")"
|
|
468
|
+
delay_info = f" with {self.delay_ms:.0f}ms delay" if self.delay_ms else ""
|
|
469
|
+
return (
|
|
470
|
+
f"đ Policy '{self.policy_name}' retrying node "
|
|
471
|
+
f"'{self.node_name}'{attempt_info}{delay_info}"
|
|
472
|
+
)
|
|
473
|
+
|
|
474
|
+
|
|
475
|
+
# Checkpoint events
|
|
476
|
+
@dataclass(slots=True)
|
|
477
|
+
class CheckpointSaved(Event):
|
|
478
|
+
"""Emitted when a checkpoint is saved.
|
|
479
|
+
|
|
480
|
+
Attributes
|
|
481
|
+
----------
|
|
482
|
+
run_id : str
|
|
483
|
+
The unique run identifier
|
|
484
|
+
dag_id : str
|
|
485
|
+
The DAG identifier
|
|
486
|
+
node_id : str
|
|
487
|
+
The node that triggered the checkpoint
|
|
488
|
+
checkpoint_type : str
|
|
489
|
+
Type of checkpoint ("node_completed", "wave_completed", "pipeline_paused")
|
|
490
|
+
data : dict[str, Any]
|
|
491
|
+
Additional checkpoint metadata
|
|
492
|
+
"""
|
|
493
|
+
|
|
494
|
+
run_id: str
|
|
495
|
+
dag_id: str
|
|
496
|
+
node_id: str
|
|
497
|
+
checkpoint_type: str
|
|
498
|
+
data: dict[str, Any] = field(default_factory=dict)
|
|
499
|
+
|
|
500
|
+
def log_message(self) -> str:
|
|
501
|
+
"""Format log message for checkpoint save event."""
|
|
502
|
+
return (
|
|
503
|
+
f"đž Checkpoint saved for run '{self.run_id}' "
|
|
504
|
+
f"(node: {self.node_id}, type: {self.checkpoint_type})"
|
|
505
|
+
)
|
|
506
|
+
|
|
507
|
+
|
|
508
|
+
@dataclass(slots=True)
|
|
509
|
+
class CheckpointRestored(Event):
|
|
510
|
+
"""Emitted when execution resumes from a checkpoint.
|
|
511
|
+
|
|
512
|
+
Attributes
|
|
513
|
+
----------
|
|
514
|
+
run_id : str
|
|
515
|
+
The unique run identifier
|
|
516
|
+
dag_id : str
|
|
517
|
+
The DAG identifier
|
|
518
|
+
node_id : str
|
|
519
|
+
The node where execution is resuming
|
|
520
|
+
restored_nodes : list[str]
|
|
521
|
+
List of nodes already completed (skipped on resume)
|
|
522
|
+
restored_at : str
|
|
523
|
+
ISO timestamp of restore operation
|
|
524
|
+
"""
|
|
525
|
+
|
|
526
|
+
run_id: str
|
|
527
|
+
dag_id: str
|
|
528
|
+
node_id: str
|
|
529
|
+
restored_nodes: list[str]
|
|
530
|
+
restored_at: str
|
|
531
|
+
|
|
532
|
+
def log_message(self) -> str:
|
|
533
|
+
"""Format log message for checkpoint restore event."""
|
|
534
|
+
return (
|
|
535
|
+
f"đ Checkpoint restored for run '{self.run_id}' "
|
|
536
|
+
f"({len(self.restored_nodes)} nodes already completed)"
|
|
537
|
+
)
|
|
538
|
+
|
|
539
|
+
|
|
540
|
+
# Health check events
|
|
541
|
+
@dataclass(slots=True)
|
|
542
|
+
class HealthCheckCompleted(Event):
|
|
543
|
+
"""Adapter health check completed.
|
|
544
|
+
|
|
545
|
+
Emitted when an adapter's ahealth_check() method completes during pre-DAG hooks.
|
|
546
|
+
|
|
547
|
+
Attributes
|
|
548
|
+
----------
|
|
549
|
+
adapter_name : str
|
|
550
|
+
Name of the adapter that was checked
|
|
551
|
+
port_name : str
|
|
552
|
+
Name of the port this adapter implements
|
|
553
|
+
status : HealthStatus
|
|
554
|
+
Health check result with status and details
|
|
555
|
+
"""
|
|
556
|
+
|
|
557
|
+
adapter_name: str
|
|
558
|
+
port_name: str
|
|
559
|
+
status: Any # HealthStatus, but using Any to avoid circular import
|
|
560
|
+
|
|
561
|
+
def log_message(self) -> str:
|
|
562
|
+
"""Format log message for health check event."""
|
|
563
|
+
status_emoji = {
|
|
564
|
+
"healthy": "â
",
|
|
565
|
+
"degraded": "â ī¸",
|
|
566
|
+
"unhealthy": "â",
|
|
567
|
+
}.get(getattr(self.status, "status", "unknown"), "âšī¸")
|
|
568
|
+
|
|
569
|
+
latency = ""
|
|
570
|
+
if hasattr(self.status, "latency_ms") and self.status.latency_ms:
|
|
571
|
+
latency = f" ({self.status.latency_ms:.1f}ms)"
|
|
572
|
+
|
|
573
|
+
return f"{status_emoji} Health check [{self.port_name}]: {self.status.status}{latency}"
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""Observer implementations for the hexDAG event system.
|
|
2
|
+
|
|
3
|
+
This module provides core observer implementations for common monitoring
|
|
4
|
+
and observability use cases.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from .core_observers import (
|
|
8
|
+
AlertingObserver,
|
|
9
|
+
DataQualityObserver,
|
|
10
|
+
ExecutionTracerObserver,
|
|
11
|
+
PerformanceMetricsObserver,
|
|
12
|
+
ResourceMonitorObserver,
|
|
13
|
+
SimpleLoggingObserver,
|
|
14
|
+
)
|
|
15
|
+
from .models import Alert, AlertSeverity, AlertType, NodeMetrics
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
# Core Observers
|
|
19
|
+
"PerformanceMetricsObserver",
|
|
20
|
+
"AlertingObserver",
|
|
21
|
+
"ExecutionTracerObserver",
|
|
22
|
+
"SimpleLoggingObserver",
|
|
23
|
+
"ResourceMonitorObserver",
|
|
24
|
+
"DataQualityObserver",
|
|
25
|
+
# Observer Models
|
|
26
|
+
"Alert",
|
|
27
|
+
"AlertType",
|
|
28
|
+
"AlertSeverity",
|
|
29
|
+
"NodeMetrics",
|
|
30
|
+
]
|