File size: 10,343 Bytes
bea2d8c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
from pathlib import Path
import yaml
from typing import Dict, List, Any


class AgentBuilder:
    @staticmethod
    def initialize(agent_name: str):
        """Create agent folder and an empty 'yaml' file

        Args:
            agent_name: Name of the agent to create
        """
        # Create base agents directory if it doesn't exist
        agents_base_dir = Path("agents")
        agents_base_dir.mkdir(exist_ok=True)

        # Create agent-specific directory
        agent_dir = agents_base_dir / agent_name
        agent_dir.mkdir(exist_ok=True)

        # Create YAML file with initial content
        yaml_file = agent_dir / f"design.yaml"

        # Initial YAML content with nodes list and example node
        initial_content = {
            "nodes": [
                {
                    "name": "START",
                    "connections": ["example_node"],
                    "description": "This is the mandatory initial node `START` !"
                },
                {
                    "name": "example_node",
                    "connections": [],
                    "description": "This is an example node"
                }
            ]
        }

        # Write the YAML content to the file
        with open(yaml_file, "w") as f:
            yaml.dump(initial_content, f, default_flow_style=False, sort_keys=False)

    @classmethod
    def setup(cls, agent_name: str):
        """Create the graph and the test python files as well as a 'puml' diagram

        Args:
            agent_name: Name of the agent to set up
        """
        design_data = cls._validate_design(agent_name)

        cls._create_graph_file(agent_name, design_data)
        cls._create_test_file(agent_name)
        cls._create_puml_file(agent_name, design_data)

    @classmethod
    def _validate_design(cls, agent_name: str) -> Dict[str, List[Dict[str, Any]]]:
        """Validate the design.yaml file structure

        Args:
            agent_name: Name of the agent to validate

        Returns:
            The parsed design data if valid

        Raises:
            ValueError: If design file is invalid or missing required elements
        """
        yaml_path = Path(f"agents/{agent_name}/design.yaml")

        if not yaml_path.exists():
            raise ValueError(f"Design file not found at {yaml_path}")

        with open(yaml_path, 'r') as f:
            design_data = yaml.safe_load(f)

        # Check if nodes list exists
        if not design_data or 'nodes' not in design_data or not isinstance(design_data['nodes'], list):
            raise ValueError("Design file must contain a 'nodes' list")
            
        # Check if START node is defined
        start_node_exists = any(node.get('name') == "START" for node in design_data['nodes'])
        if not start_node_exists:
            raise ValueError("Design file must contain a 'START' node")

        # Validate each node
        for i, node in enumerate(design_data['nodes']):
            if not isinstance(node, dict):
                raise ValueError(f"Node at index {i} must be a dictionary")

            if 'name' not in node:
                raise ValueError(f"Node at index {i} is missing a 'name' field")

            if 'description' not in node:
                raise ValueError(f"Node '{node.get('name', f'at index {i}')}' is missing a 'description' field")

            if 'connections' not in node or not isinstance(node['connections'], list):
                raise ValueError(f"Node '{node.get('name')}' must have a 'connections' list")

        return design_data

    @classmethod
    def _create_graph_file(cls, agent_name: str, design_data: Dict[str, List[Dict[str, Any]]]):
        """Create the graph.py file with the necessary classes

        Args:
            agent_name: Name of the agent
            design_data: The validated design data
        """
        nodes = design_data['nodes']

        # Prepare node methods and conditional edge methods
        node_methods = []
        edge_methods = []

        # Generate node method for each node
        for node in nodes:
            node_name = node['name']
            node_desc = node['description']

            node_method = f'''
    def {node_name}_node(self, state):
        """
        {node_desc}
        """
        # TODO: To implement...
        pass
'''
            if node_name != "START":
                node_methods.append(node_method)

            # Check if this node has more than one connection (needs conditional edge)
            if len(node['connections']) > 1:
                connections_str = ", ".join([f'"{conn}"' for conn in node['connections']])
                edge_method = f'''
    def {node_name}_edge(self, state):
        """
        Conditional edge for {node_name} node.
        Returns one of: {connections_str}
        """
        # TODO: To implement...
        pass
'''
                edge_methods.append(edge_method)

        # Build the file content
        file_content = f'''from typing import Dict, Any
from langgraph.graph import StateGraph, END, START
from langgraph.graph.state import CompiledStateGraph


class State:
    """
    State class for the agent graph.
    """
    # TODO: Define state structure
    pass


class Nodes:
    """
    Collection of node functions for the agent graph.
    """
{"".join(node_methods)}


class Edges:
    """
    Collection of conditional edge functions for the agent graph.
    """
{"".join(edge_methods)}


class GraphBuilder:
    def __init__(self):
        """
        Initializes the GraphBuilder.
        """
        self.nodes = Nodes()
        self.edges = Edges()
        # TODO: Implement the desired constructor.
        pass
        
    def build_agent_graph(self) -> CompiledStateGraph:
        """Build and return the agent graph."""
        graph = StateGraph(State)
        
        # Add all nodes
{cls._generate_add_nodes_code(nodes)}
        
        # Add edges
{cls._generate_regular_edges_code(nodes)}
{cls._generate_conditional_edges_code(nodes)}        
        return graph.compile()
'''
        # Write to file
        graph_file_path = Path(f"agents/{agent_name}/graph.py")
        with open(graph_file_path, 'w') as f:
            f.write(file_content)

    @staticmethod
    def _generate_add_nodes_code(nodes):
        """Generate code for adding nodes to the graph"""
        code_lines = []
        for node in nodes:
            if node["name"] != "START":
                code_lines.append(f'        graph.add_node("{node["name"]}", self.nodes.{node["name"]}_node)')
        return "\n".join(code_lines)

    @staticmethod
    def _generate_conditional_edges_code(nodes):
        """Generate code for adding conditional edges to the graph"""
        code_lines = []
        for node in nodes:
            if len(node['connections']) > 1:
                destinations = ", ".join([f'{conn}: {conn}' if conn in ["START", "END"] else f'"{conn}": "{conn}"' for conn in node['connections']])
                code_lines.append(f'''        graph.add_conditional_edges(
            "{node["name"]}",
            self.edges.{node["name"]}_edge,
            {{
                {destinations}
            }}
        )''')
        return "\n".join(code_lines) if code_lines else ""

    @staticmethod
    def _generate_regular_edges_code(nodes):
        """Generate code for adding regular edges to the graph"""
        code_lines = []
        for node in nodes:
            if len(node['connections']) == 1:
                start_key = node["name"] if node["name"] in ["START", "END"] else f'"{node["name"]}"'
                end_key = node["connections"][0] if node["connections"][0] in ["START", "END"] else f'"{node["connections"][0]}"'
                code_lines.append(f'        graph.add_edge({start_key}, {end_key})')
        return "\n".join(code_lines) if code_lines else ""

    @staticmethod
    def _create_test_file(agent_name: str):
        """Create the test.py file

        Args:
            agent_name: Name of the agent
        """
        test_file_content = f'''# Test file for {agent_name} agent
from graph import GraphBuilder

def test_agent():
    """
    Test the {agent_name} agent functionality.
    """
    # Create the graph
    builder = GraphBuilder()
    graph = builder.build_agent_graph()
    
    # TODO: Add test code here
    print("Testing {agent_name} agent...")
    
    # Example test
    # result = graph.invoke({{"input": "Test input"}})
    # print(f"Result: {{result}}")

if __name__ == "__main__":
    test_agent()
'''

        # Write to file
        test_file_path = Path(f"agents/{agent_name}/test.py")
        with open(test_file_path, 'w') as f:
            f.write(test_file_content)

    @staticmethod
    def _create_puml_file(agent_name: str, design_data: Dict[str, List[Dict[str, Any]]]):
        """Create the design.puml file for diagram visualization

        Args:
            agent_name: Name of the agent
            design_data: The validated design data
        """
        nodes = design_data['nodes']

        # Start the PlantUML content
        puml_content = f'''@startuml {agent_name}
!define NOT_IMPLEMENTED_NODE_COLOR #IndianRed
!define IMPLEMENTED_NODE_COLOR #Gold
!define TESTED_NODE_COLOR #LawnGreen
!define TERMINAL_NODE_COLOR #DodgerBlue

'''

        # Add node descriptions
        for node in nodes:
            puml_content += f'node {node["name"]} {node["status"]}_NODE_COLOR[\n  {node["description"]}\n]\n\n'
        puml_content += f'node END TERMINAL_NODE_COLOR[\n  This is the final Node !\n]\n\n'

        # Add connections
        for node in nodes:
            node_name = node["name"]
            for connection in node["connections"]:
                if connection == "END":
                    puml_content += f'{node_name} --> END\n'
                else:
                    puml_content += f'{node_name} --> {connection}\n'

        # End the PlantUML content
        puml_content += '\n@enduml'

        # Write to file
        puml_file_path = Path(f"agents/{agent_name}/design.puml")
        with open(puml_file_path, 'w') as f:
            f.write(puml_content)

    @staticmethod
    def validate(agent_name: str):
        pass


if __name__ == "__main__":
    # AgentBuilder.initialize("universal_solver")
    AgentBuilder.setup("universal_solver")