Nodes
Overview
Nodes are the atomic execution units in Dhenara Agent DSL (DAD). Each node performs a specific function, such as making an AI model call, analyzing a folder, or performing file operations. Nodes form the fundamental building blocks of flows and agents, enabling complex behavior through composition.
Core Concepts
Nodes in DAD follow these key principles:
- Single Responsibility: Each node focuses on a specific function
- Typed Input/Output: Nodes have well-defined input and output types
- Configuration: Nodes are configured through settings classes
- Event-Driven: Nodes can emit and respond to events
- Extensible: The node system can be extended with custom node types
Built-in Node Types
DAD includes several built-in node types for common operations:
AIModelNode
The AIModelNode
makes calls to AI models (like GPT-4, Claude) to process prompts and generate responses.
from dhenara.agent.dsl import AIModelNode, AIModelNodeSettings
from dhenara.ai.types import Prompt, ResourceConfigItem
ai_node = AIModelNode(
resources=ResourceConfigItem.with_model("claude-3-7-sonnet"),
settings=AIModelNodeSettings(
system_instructions=["You are a helpful assistant."],
prompt=Prompt.with_dad_text("Generate ideas for: $var{topic}"),
model_call_config=AIModelCallConfig(
max_output_tokens=8000,
options={"temperature": 0.7}
)
),
)
Key Features:
- Configure which AI models to use via
resources
- Set system instructions and prompts
- Control model parameters like temperature and max tokens
- Request structured outputs using Pydantic models
- Use tools by providing tool definitions
FileOperationNode
The FileOperationNode
performs file system operations like creating, editing, deleting, and moving files.
from dhenara.agent.dsl import FileOperationNode, FileOperationNodeSettings
from dhenara.ai.types import ObjectTemplate
file_node = FileOperationNode(
settings=FileOperationNodeSettings(
base_directory="/path/to/workspace",
operations_template=ObjectTemplate(
expression="$hier{code_generator}.outcome.structured.file_operations"
),
stage=True,
commit=True,
commit_message="Update files based on analysis",
),
)
Key Features:
- Perform multiple file operations in a single node
- Dynamic operations through templates
- Git integration for staging and committing changes
- Support for various operation types (create, edit, delete, move)
FolderAnalyzerNode
The FolderAnalyzerNode
analyzes directory structures and file contents to provide context for other nodes.
from dhenara.agent.dsl import FolderAnalyzerNode, FolderAnalyzerSettings
from dhenara.agent.dsl.inbuilt.flow_nodes.defs.types import FolderAnalysisOperation
analyzer_node = FolderAnalyzerNode(
settings=FolderAnalyzerSettings(
base_directory="/path/to/repo",
operations=[
FolderAnalysisOperation(
operation_type="analyze_folder",
path="src",
include_patterns=["*.py"],
exclude_patterns=["__pycache__"],
include_content=True,
recursive=True,
)
],
),
)
Key Features:
- Analyze directory structures and file contents
- Filter files by patterns (include/exclude)
- Control recursion and content extraction
- Generate structured representations of repositories
CommandNode
The CommandNode
executes shell commands and captures outputs.
from dhenara.agent.dsl import CommandNode, CommandNodeSettings
command_node = CommandNode(
settings=CommandNodeSettings(
command=["git", "status"],
working_directory="/path/to/project",
timeout=30,
),
)
Key Features:
- Execute shell commands with arguments
- Control execution environment (working directory, environment variables)
- Set timeouts for command execution
- Capture command output and exit code
Node Input and Output
Nodes use typed inputs and outputs to ensure type safety and clear interfaces:
Node Input
Each node type has a specific input class derived from NodeInput
:
from dhenara.agent.dsl.base import NodeInput
from pydantic import Field
class AIModelNodeInput(NodeInput):
prompt_variables: dict[str, Any] = Field(default_factory=dict)
instruction_variables: dict[str, Any] = Field(default_factory=dict)
settings_override: AIModelNodeSettings | None = None
resources_override: list[ResourceConfigItem] | None = None
Inputs can be provided through several mechanisms:
- Event Handlers: Respond to
node_input_required
events - Static Registration: Register inputs in advance
- Direct Execution: Provide inputs when executing a node directly
Node Output
Node execution produces a NodeOutput
containing the results:
class AIModelNodeOutput(NodeOutput[AIModelNodeOutputData]):
pass
class AIModelNodeOutcome(NodeOutcome):
text: str | None
structured: dict | None
file: GenericFile | None
files: list[GenericFile] | None
The standardized NodeOutcome
format makes it easy to access results consistently across different node types.
Extending the Node System
You can create custom node types by extending NodeDefinition
and implementing the required interfaces:
from dhenara.agent.dsl.base import NodeDefinition, NodeSettings
from pydantic import BaseModel, Field
class MyCustomNodeSettings(NodeSettings):
param1: str
param2: int = 42
class MyCustomNodeInput(NodeInput):
input_data: str
class MyCustomNodeOutput(NodeOutput):
result: str
class MyCustomNode(NodeDefinition):
node_type = "custom"
settings_class = MyCustomNodeSettings
input_class = MyCustomNodeInput
output_class = MyCustomNodeOutput
# You'll need to register an executor for this node type
Custom nodes need an executor that implements the actual execution logic.
Best Practices
- Keep Nodes Focused: Each node should have a single responsibility
- Use Typed Inputs/Outputs: Leverage Pydantic models for type safety
- Handle Events: Use the event system for dynamic configuration
- Document Node Behavior: Clearly document what each node does and expects
- Error Handling: Implement proper error handling in node executors
By following these practices, you can create reusable, maintainable nodes that work consistently in different contexts.