Skip to content

Commit 690799e

Browse files
Merge pull request #570 from MervinPraison/claude/issue-122-20250601_041558
feat: add --merge CLI parameter for merging agents.yaml files
2 parents ecfe547 + 04f5988 commit 690799e

2 files changed

Lines changed: 97 additions & 23 deletions

File tree

src/praisonai/praisonai/auto.py

Lines changed: 94 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -121,12 +121,12 @@ def __init__(self, topic="Movie Story writing about AI", agent_file="test.yaml",
121121
mode=instructor.Mode.JSON,
122122
)
123123

124-
def generate(self):
124+
def generate(self, merge=False):
125125
"""
126126
Generates a team structure for the specified topic.
127127
128128
Args:
129-
None
129+
merge (bool): Whether to merge with existing agents.yaml file instead of overwriting.
130130
131131
Returns:
132132
str: The full path of the YAML file containing the generated team structure.
@@ -149,45 +149,118 @@ def generate(self):
149149
]
150150
)
151151
json_data = json.loads(response.model_dump_json())
152-
self.convert_and_save(json_data)
152+
self.convert_and_save(json_data, merge=merge)
153153
full_path = os.path.abspath(self.agent_file)
154154
return full_path
155155

156-
def convert_and_save(self, json_data):
156+
def convert_and_save(self, json_data, merge=False):
157157
"""Converts the provided JSON data into the desired YAML format and saves it to a file.
158158
159159
Args:
160160
json_data (dict): The JSON data representing the team structure.
161-
topic (str, optional): The topic to be inserted into the YAML. Defaults to "Artificial Intelligence".
162-
agent_file (str, optional): The name of the YAML file to save. Defaults to "test.yaml".
161+
merge (bool): Whether to merge with existing agents.yaml file instead of overwriting.
163162
"""
164163

165-
yaml_data = {
166-
"framework": self.framework,
167-
"topic": self.topic,
168-
"roles": {},
169-
"dependencies": []
170-
}
164+
# Handle merge functionality
165+
if merge and os.path.exists(self.agent_file):
166+
yaml_data = self.merge_with_existing_agents(json_data)
167+
else:
168+
# Original behavior: create new yaml_data structure
169+
yaml_data = {
170+
"framework": self.framework,
171+
"topic": self.topic,
172+
"roles": {},
173+
"dependencies": []
174+
}
175+
176+
for role_id, role_details in json_data['roles'].items():
177+
yaml_data['roles'][role_id] = {
178+
"backstory": "" + role_details['backstory'],
179+
"goal": role_details['goal'],
180+
"role": role_details['role'],
181+
"tasks": {},
182+
# "tools": role_details.get('tools', []),
183+
"tools": ['']
184+
}
185+
186+
for task_id, task_details in role_details['tasks'].items():
187+
yaml_data['roles'][role_id]['tasks'][task_id] = {
188+
"description": "" + task_details['description'],
189+
"expected_output": "" + task_details['expected_output']
190+
}
191+
192+
# Save to YAML file, maintaining the order
193+
with open(self.agent_file, 'w') as f:
194+
yaml.dump(yaml_data, f, allow_unicode=True, sort_keys=False)
171195

172-
for role_id, role_details in json_data['roles'].items():
173-
yaml_data['roles'][role_id] = {
196+
def merge_with_existing_agents(self, new_json_data):
197+
"""
198+
Merge existing agents.yaml with new auto-generated agents.
199+
200+
Args:
201+
new_json_data (dict): The JSON data representing the new team structure.
202+
203+
Returns:
204+
dict: The merged YAML data structure.
205+
"""
206+
try:
207+
# Load existing agents.yaml
208+
with open(self.agent_file, 'r') as f:
209+
existing_data = yaml.safe_load(f)
210+
211+
if not existing_data:
212+
# If existing file is empty, treat as new file
213+
existing_data = {"roles": {}, "dependencies": []}
214+
except (yaml.YAMLError, FileNotFoundError) as e:
215+
logging.warning(f"Could not load existing agents file {self.agent_file}: {e}")
216+
logging.warning("Creating new file instead of merging")
217+
existing_data = {"roles": {}, "dependencies": []}
218+
219+
# Start with existing data structure
220+
merged_data = existing_data.copy()
221+
222+
# Ensure required fields exist
223+
if 'roles' not in merged_data:
224+
merged_data['roles'] = {}
225+
if 'dependencies' not in merged_data:
226+
merged_data['dependencies'] = []
227+
if 'framework' not in merged_data:
228+
merged_data['framework'] = self.framework
229+
230+
# Handle topic merging
231+
existing_topic = merged_data.get('topic', '')
232+
new_topic = self.topic
233+
if existing_topic and existing_topic != new_topic:
234+
merged_data['topic'] = f"{existing_topic} + {new_topic}"
235+
else:
236+
merged_data['topic'] = new_topic
237+
238+
# Merge new roles with existing ones
239+
for role_id, role_details in new_json_data['roles'].items():
240+
# Check for conflicts and rename if necessary
241+
final_role_id = role_id
242+
counter = 1
243+
while final_role_id in merged_data['roles']:
244+
final_role_id = f"{role_id}_auto_{counter}"
245+
counter += 1
246+
247+
# Add the new role
248+
merged_data['roles'][final_role_id] = {
174249
"backstory": "" + role_details['backstory'],
175250
"goal": role_details['goal'],
176251
"role": role_details['role'],
177252
"tasks": {},
178-
# "tools": role_details.get('tools', []),
179253
"tools": ['']
180254
}
181-
255+
256+
# Add tasks for this role
182257
for task_id, task_details in role_details['tasks'].items():
183-
yaml_data['roles'][role_id]['tasks'][task_id] = {
258+
merged_data['roles'][final_role_id]['tasks'][task_id] = {
184259
"description": "" + task_details['description'],
185260
"expected_output": "" + task_details['expected_output']
186261
}
187-
188-
# Save to YAML file, maintaining the order
189-
with open(self.agent_file, 'w') as f:
190-
yaml.dump(yaml_data, f, allow_unicode=True, sort_keys=False)
262+
263+
return merged_data
191264

192265
def get_user_content(self):
193266
"""

src/praisonai/praisonai/cli.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -417,7 +417,7 @@ def main(self):
417417

418418
self.agent_file = "test.yaml"
419419
generator = AutoGenerator(topic=self.topic, framework=self.framework, agent_file=self.agent_file)
420-
self.agent_file = generator.generate()
420+
self.agent_file = generator.generate(merge=getattr(args, 'merge', False))
421421
agents_generator = AgentsGenerator(self.agent_file, self.framework, self.config_list)
422422
result = agents_generator.generate_crew_and_kickoff()
423423
print(result)
@@ -430,7 +430,7 @@ def main(self):
430430

431431
self.agent_file = "agents.yaml"
432432
generator = AutoGenerator(topic=self.topic, framework=self.framework, agent_file=self.agent_file)
433-
self.agent_file = generator.generate()
433+
self.agent_file = generator.generate(merge=getattr(args, 'merge', False))
434434
print(f"File {self.agent_file} created successfully")
435435
return f"File {self.agent_file} created successfully"
436436

@@ -528,6 +528,7 @@ def parse_args(self):
528528
parser.add_argument("--realtime", action="store_true", help="Start the realtime voice interaction interface")
529529
parser.add_argument("--call", action="store_true", help="Start the PraisonAI Call server")
530530
parser.add_argument("--public", action="store_true", help="Use ngrok to expose the server publicly (only with --call)")
531+
parser.add_argument("--merge", action="store_true", help="Merge existing agents.yaml with auto-generated agents instead of overwriting")
531532

532533
# If we're in a test environment, parse with empty args to avoid pytest interference
533534
if in_test_env:

0 commit comments

Comments
 (0)