Code Generation Agent
This example demonstrates how to build a sophisticated code generation agent using Dhenara Agent DSL (DAD). The Autocoder agent can analyze code repositories, plan implementation steps, and execute precise file operations to implement code changes.
Agent Overview
The Autocoder agent follows a three-step process:
- Analysis: Analyzes the repository structure to understand the codebase
- Planning: Creates a structured plan with implementation tasks
- Implementation: Executes file operations to implement code changes
This agent showcases advanced DAD features including:
- Complex flows with multiple stages
- Conditional branching
- Folder analysis operations
- File operations for code implementation
- Structured data processing
Agent Structure
The Autocoder agent consists of several components:
src/agents/autocoder/
├── __init__.py
├── agent.py # Main agent definition
├── handler.py # Event handlers
├── types.py # Pydantic data models
└── flows/ # Flow definitions
├── __init__.py
├── defs.py # Common definitions
├── flow.py # Main coordinator flow
├── planner.py # Planning stage flow
├── implementation.py # Implementation stage flow
└── structured_cleaner.py # Helper flow for fixing structured output
Main Agent Definition
The agent definition connects the coordinator flow:
from dhenara.agent.dsl import AgentDefinition
from .flows.flow import coordinator_flow
# Main Agent Definition
agent = AgentDefinition()
agent.flow(
"coordinator_flow_1",
coordinator_flow,
)
Coordinator Flow
The coordinator flow orchestrates the entire process:
from dhenara.agent.dsl import FlowDefinition
from dhenara.ai.types import ObjectTemplate
from .implementation import implementation_flow
from .planner import planner_flow
# Load task information
task_background = read_background()
task_description = read_description()
# Coordinator Agent Flow
coordinator_flow = FlowDefinition()
# First stage: Planning
coordinator_flow.subflow(
"planner",
planner_flow,
variables={
"task_background": task_background,
"task_description": task_description,
},
)
# Conditional execution based on plan generation success
coordinator_flow.conditional(
id="plan_executor",
statement=ObjectTemplate(
expression="$expr{py: $hier{planner.plan_generator}.outcome.structured is not None}",
),
# Loop through all implementation tasks if plan was generated
true_branch=FlowDefinition().for_each(
id="implementation_loop",
statement=ObjectTemplate(
expression="$expr{py: $hier{planner.plan_generator}.outcome.structured.implementation_tasks }",
),
item_var="task_spec",
index_var="task_index",
max_iterations=20,
body=implementation_flow,
body_variables={
"task_background": task_background,
# task_spec is passed via index_var
},
),
# Handle failure case
false_branch=FlowDefinition().node(
"no_plan_generated",
CommandNode(
settings=CommandNodeSettings(
commands=[
"echo 'Planner is unsuccessful.'",
],
)
),
),
)
Planning Flow
The planning flow analyzes the repository and generates an implementation plan:
from dhenara.agent.dsl import (
AIModelNode, AIModelNodeSettings, EventType, FlowDefinition, FolderAnalyzerNode
)
from dhenara.ai.types import AIModelCallConfig, Prompt
from src.agents.autocoder.types import Plan
# Planner Agent Flow
planner_flow = FlowDefinition().vars(
{
"task_background": PLACEHOLDER,
"task_description": PLACEHOLDER,
}
)
# Repository analysis
planner_flow.node(
"pre_plan_repo_analysis",
FolderAnalyzerNode(
pre_events=[EventType.node_input_required],
settings=None, # Configured through the handler
),
)
# Planning node
planner_flow.node(
"plan_generator",
AIModelNode(
pre_events=[EventType.node_input_required],
settings=AIModelNodeSettings(
models=models,
system_instructions=[
"You are a professional implementation planner for coding tasks",
# Additional instructions...
],
prompt=Prompt.with_dad_text(
text=(
"Task Background: $var{task_background} \n"
"Task: $var{task_description} \n"
"Context Files info:\n $expr{ $hier{pre_plan_repo_analysis}.outcome.results }\n\n"
# Additional prompt text...
),
),
model_call_config=AIModelCallConfig(
structured_output=Plan,
test_mode=test_mode,
),
),
),
)
Implementation Flow
The implementation flow executes a single task from the plan:
from dhenara.agent.dsl import (
AIModelNode, AIModelNodeSettings, EventType, FileOperationNode,
FileOperationNodeSettings, FlowDefinition, FolderAnalyzerNode, FolderAnalyzerSettings
)
from dhenara.ai.types import AIModelCallConfig, ObjectTemplate, Prompt
from src.agents.autocoder.types import TaskImplementation
# Implementation flow variables
implementation_flow = FlowDefinition().vars(
{
"task_background": PLACEHOLDER,
"task_spec": "No Spec", # Should be a TaskSpec type
}
)
# Dynamic analysis for this specific task
implementation_flow.node(
"dynamic_repo_analysis",
FolderAnalyzerNode(
settings=FolderAnalyzerSettings(
base_directory=global_data_directory,
operations_template=ObjectTemplate(expression="$expr{task_spec.required_context}"),
),
),
)
# Code generation node
implementation_flow.node(
"code_generator",
AIModelNode(
pre_events=[EventType.node_input_required],
settings=AIModelNodeSettings(
models=models,
system_instructions=[
"You are a professional code implementation agent",
# Additional instructions...
],
prompt=Prompt.with_dad_text(
text=(
"## Background\n"
"$var{task_background}\n\n"
"## Task Specification\n"
"Task ID: $expr{task_spec.task_id}\n"
"Description: $expr{task_spec.description}\n\n"
"## Repository Context\n"
"$expr{$hier{dynamic_repo_analysis}.outcome.results}\n\n"
# Additional prompt text...
),
disable_checks=True,
),
model_call_config=AIModelCallConfig(
structured_output=TaskImplementation,
test_mode=test_mode,
max_output_tokens=64000,
reasoning=True,
),
),
),
)
# Execute file operations
implementation_flow.node(
"code_generator_file_ops",
FileOperationNode(
settings=FileOperationNodeSettings(
base_directory=global_data_directory,
operations_template=ObjectTemplate(
expression="$expr{ $hier{code_generator}.outcome.structured.file_operations }",
),
stage=True,
commit=False,
),
),
)
Data Models
The agent uses structured data models defined with Pydantic:
from dhenara.agent.dsl.inbuilt.flow_nodes.defs.types import (
FileOperation, FileSystemAnalysisOperation, FolderAnalysisOperation
)
from pydantic import BaseModel, Field
class TaskSpec(BaseModel):
"""
Specification for a logical development task with its required context.
Each task is a discrete unit of work in the overall plan.
"""
order: int = Field(..., description="Execution order of this task in the overall plan")
task_id: str = Field(..., description="Unique identifier for this task")
description: str = Field(..., description="Detailed description of what this task accomplishes")
required_context: list[FileSystemAnalysisOperation] = Field(
default_factory=list,
description="File-system analysis operations needed to provide context",
)
class TaskImplementation(BaseModel):
"""
Contains the concrete file operations to implement a specific task of the plan.
"""
task_id: str | None = Field(default=None, description="ID of the corresponding TaskSpec")
file_operations: list[FileOperation] | None = Field(
default_factory=list,
description="File operations to execute for this implementation task",
)
execution_commands: list[dict] | None = Field(
None,
description="Optional shell commands to run after file operations",
)
verification_commands: list[dict] | None = Field(
None,
description="Optional commands to verify the changes work as expected",
)
class Plan(BaseModel):
"""
A comprehensive, structured plan for implementing a specific task.
"""
title: str = Field(..., description="Concise title of the plan")
description: str = Field(..., description="Detailed explanation of the implementation approach")
implementation_tasks: list[TaskSpec] = Field(..., description="Ordered implementation tasks")
validation_steps: list[str] = Field(..., description="Steps to validate the implementation")
estimated_complexity: int | None = Field(None, description="Complexity estimate (1-10)")
Running the Agent
To run the Autocoder agent, you would use a runner script similar to the one below:
from dhenara.agent.dsl.events import EventType
from dhenara.agent.run import RunContext
from dhenara.agent.runner import AgentRunner
from src.agents.autocoder.agent import agent
from src.agents.autocoder.handler import autocoder_input_handler
from src.runners.defs import observability_settings, project_root
root_component_id = "coding_agent_root"
agent.root_id = root_component_id
run_context = RunContext(
root_component_id=root_component_id,
observability_settings=observability_settings,
project_root=project_root,
run_root_subpath="agent_autocoder",
)
run_context.register_event_handlers(
handlers_map={
EventType.node_input_required: autocoder_input_handler,
# Additional event handlers...
}
)
runner = AgentRunner(agent, run_context)
Conclusion
The Autocoder agent demonstrates how DAD can be used to create sophisticated AI agents for code generation. By orchestrating repository analysis, planning, and implementation stages, the agent can handle complex coding tasks with minimal human intervention.
This example highlights several advanced DAD features:
- Multi-stage flows: Breaking complex tasks into manageable stages
- Structured data: Using Pydantic models for structured inputs and outputs
- Dynamic analysis: Analyzing specific files based on task requirements
- File operations: Implementing code changes through file system operations
- Conditional logic: Handling success and failure cases
- Looping: Processing multiple implementation tasks sequentially
By understanding this example, you can create your own specialized agents for various software development tasks.