versionhq 1.1.8__py3-none-any.whl → 1.1.9.0__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.
versionhq/__init__.py CHANGED
@@ -15,9 +15,10 @@ from versionhq.llm.model import LLM
15
15
  from versionhq.task.model import Task, TaskOutput
16
16
  from versionhq.team.model import Team, TeamOutput
17
17
  from versionhq.tool.model import Tool
18
+ from versionhq.tool.composio import Composio
18
19
 
19
20
 
20
- __version__ = "1.1.8"
21
+ __version__ = "1.1.9.0"
21
22
  __all__ = [
22
23
  "Agent",
23
24
  "Customer",
@@ -33,4 +34,5 @@ __all__ = [
33
34
  "Team",
34
35
  "TeamOutput",
35
36
  "Tool",
37
+ "Composio"
36
38
  ]
versionhq/agent/model.py CHANGED
@@ -15,7 +15,7 @@ from versionhq.llm.llm_vars import LLM_VARS
15
15
  from versionhq.llm.model import LLM, DEFAULT_CONTEXT_WINDOW
16
16
  from versionhq.task import TaskOutputFormat
17
17
  from versionhq.task.model import ResponseField
18
- from versionhq.tool.model import Tool, ToolCalled
18
+ from versionhq.tool.model import Tool, ToolSet
19
19
  from versionhq.tool.tool_handler import ToolHandler
20
20
 
21
21
  load_dotenv(override=True)
@@ -117,15 +117,6 @@ class MessagingWorkflow(ABC, BaseModel):
117
117
  description="store metrics that used to predict and track the performance of this workflow."
118
118
  )
119
119
 
120
-
121
- @property
122
- def name(self):
123
- if self.customer.id:
124
- return f"Workflow ID: {self.id} - on {self.product.id} for {self.customer.id}"
125
- else:
126
- return f"Workflow ID: {self.id} - on {self.product.id}"
127
-
128
-
129
120
  @field_validator("id", mode="before")
130
121
  @classmethod
131
122
  def _deny_user_set_id(cls, v: Optional[UUID4]) -> None:
@@ -160,3 +151,11 @@ class MessagingWorkflow(ABC, BaseModel):
160
151
  self.agents = agents
161
152
  self.team = team
162
153
  self.updated_at = datetime.datetime.now()
154
+
155
+
156
+ @property
157
+ def name(self):
158
+ if self.customer.id:
159
+ return f"Workflow ID: {self.id} - on {self.product.id} for {self.customer.id}"
160
+ else:
161
+ return f"Workflow ID: {self.id} - on {self.product.id}"
versionhq/task/model.py CHANGED
@@ -12,7 +12,7 @@ from pydantic_core import PydanticCustomError
12
12
  from versionhq._utils.process_config import process_config
13
13
  from versionhq.task import TaskOutputFormat
14
14
  from versionhq.task.log_handler import TaskOutputStorageHandler
15
- from versionhq.tool.model import Tool, ToolCalled
15
+ from versionhq.tool.model import Tool, ToolSet
16
16
  from versionhq._utils.logger import Logger
17
17
 
18
18
 
@@ -76,18 +76,6 @@ class TaskOutput(BaseModel):
76
76
  def __str__(self) -> str:
77
77
  return str(self.pydantic) if self.pydantic else str(self.json_dict) if self.json_dict else self.raw
78
78
 
79
- @property
80
- def json(self) -> Optional[str]:
81
- if self.output_format != TaskOutputFormat.JSON:
82
- raise ValueError(
83
- """
84
- Invalid output format requested.
85
- If you would like to access the JSON output,
86
- pleae make sure to set the output_json property for the task
87
- """
88
- )
89
- return json.dumps(self.json_dict)
90
-
91
79
 
92
80
  def to_dict(self) -> Dict[str, Any]:
93
81
  """
@@ -112,6 +100,19 @@ class TaskOutput(BaseModel):
112
100
  return json.dumps(self.json_dict) if self.json_dict else self.raw[0: 127]
113
101
 
114
102
 
103
+ @property
104
+ def json(self) -> Optional[str]:
105
+ if self.output_format != TaskOutputFormat.JSON:
106
+ raise ValueError(
107
+ """
108
+ Invalid output format requested.
109
+ If you would like to access the JSON output,
110
+ pleae make sure to set the output_json property for the task
111
+ """
112
+ )
113
+ return json.dumps(self.json_dict)
114
+
115
+
115
116
 
116
117
  class Task(BaseModel):
117
118
  """
@@ -140,7 +141,7 @@ class Task(BaseModel):
140
141
 
141
142
  # task setup
142
143
  context: Optional[List["Task"]] = Field(default=None, description="other tasks whose outputs should be used as context")
143
- tools_called: Optional[List[ToolCalled]] = Field(default_factory=list, description="tools that the agent can use for this task")
144
+ tools_called: Optional[List[ToolSet]] = Field(default_factory=list, description="tools that the agent can use for this task")
144
145
  take_tool_res_as_final: bool = Field(default=False, description="when set True, tools res will be stored in the `TaskOutput`")
145
146
  allow_delegation: bool = Field(default=False, description="ask other agents for help and run the task instead")
146
147
 
@@ -157,63 +158,6 @@ class Task(BaseModel):
157
158
  delegations: int = 0
158
159
 
159
160
 
160
- @property
161
- def output_prompt(self) -> str:
162
- """
163
- Draft prompts on the output format by converting `output_field_list` to dictionary.
164
- """
165
-
166
- output_prompt, output_formats_to_follow = "", dict()
167
- for item in self.output_field_list:
168
- output_formats_to_follow[item.title] = f"<Return your answer in {item.type.__name__}>"
169
-
170
- output_prompt = f"""
171
- Your outputs MUST adhere to the following format and should NOT include any irrelevant elements:
172
- {output_formats_to_follow}
173
- """
174
- return output_prompt
175
-
176
-
177
- @property
178
- def expected_output_formats(self) -> List[TaskOutputFormat]:
179
- """
180
- Return output formats in list with the ENUM item.
181
- `TaskOutputFormat.RAW` is set as default.
182
- """
183
- outputs = [TaskOutputFormat.RAW,]
184
- if self.expected_output_json:
185
- outputs.append(TaskOutputFormat.JSON)
186
- if self.expected_output_pydantic:
187
- outputs.append(TaskOutputFormat.PYDANTIC)
188
- return outputs
189
-
190
-
191
- @property
192
- def key(self) -> str:
193
- output_format = (
194
- TaskOutputFormat.JSON
195
- if self.expected_output_json == True
196
- else (
197
- TaskOutputFormat.PYDANTIC
198
- if self.expected_output_pydantic == True
199
- else TaskOutputFormat.RAW
200
- )
201
- )
202
- source = [self.description, output_format]
203
- return md5("|".join(source).encode(), usedforsecurity=False).hexdigest()
204
-
205
-
206
- @property
207
- def summary(self) -> str:
208
- return f"""
209
- Task ID: {str(self.id)}
210
- "Description": {self.description}
211
- "Prompt": {self.output_prompt}
212
- "Tools": {", ".join([tool_called.tool.name for tool_called in self.tools_called])}
213
- """
214
-
215
-
216
- # validators
217
161
  @model_validator(mode="before")
218
162
  @classmethod
219
163
  def process_model_config(cls, values: Dict[str, Any]):
@@ -399,7 +343,7 @@ Your outputs MUST adhere to the following format and should NOT include any irre
399
343
  """
400
344
  Run the core execution logic of the task.
401
345
  To speed up the process, when the format is not expected to return, we will skip the conversion process.
402
- When the task is allowed to delegate to another agent, we will select a responsible one in order of manager_agent > peer_agent > anoymous agent.
346
+ When the task is allowed to delegate to another agent, we will select a responsible one in order of manager > peer_agent > anoymous agent.
403
347
  """
404
348
  from versionhq.agent.model import Agent
405
349
  from versionhq.team.model import Team
@@ -410,8 +354,9 @@ Your outputs MUST adhere to the following format and should NOT include any irre
410
354
  agent_to_delegate = None
411
355
 
412
356
  if hasattr(agent, "team") and isinstance(agent.team, Team):
413
- if agent.team.manager_agent:
414
- agent_to_delegate = agent.team.manager_agent
357
+ if agent.team.managers:
358
+ idling_manager_agents = [manager.agent for manager in agent.team.managers if manager.task is None]
359
+ agent_to_delegate = idling_manager_agents[0] if idling_manager_agents else agent.team.managers[0]
415
360
  else:
416
361
  peers = [member.agent for member in agent.team.members if member.is_manager == False and member.agent.id is not agent.id]
417
362
  if len(peers) > 0:
@@ -466,6 +411,63 @@ Your outputs MUST adhere to the following format and should NOT include any irre
466
411
  self._task_output_handler.update(task=self, task_index=task_index, was_replayed=was_replayed, inputs=inputs)
467
412
 
468
413
 
414
+ @property
415
+ def output_prompt(self) -> str:
416
+ """
417
+ Draft prompts on the output format by converting `output_field_list` to dictionary.
418
+ """
419
+
420
+ output_prompt, output_formats_to_follow = "", dict()
421
+ for item in self.output_field_list:
422
+ output_formats_to_follow[item.title] = f"<Return your answer in {item.type.__name__}>"
423
+
424
+ output_prompt = f"""
425
+ Your outputs MUST adhere to the following format and should NOT include any irrelevant elements:
426
+ {output_formats_to_follow}
427
+ """
428
+ return output_prompt
429
+
430
+
431
+ @property
432
+ def expected_output_formats(self) -> List[TaskOutputFormat]:
433
+ """
434
+ Return output formats in list with the ENUM item.
435
+ `TaskOutputFormat.RAW` is set as default.
436
+ """
437
+ outputs = [TaskOutputFormat.RAW,]
438
+ if self.expected_output_json:
439
+ outputs.append(TaskOutputFormat.JSON)
440
+ if self.expected_output_pydantic:
441
+ outputs.append(TaskOutputFormat.PYDANTIC)
442
+ return outputs
443
+
444
+
445
+ @property
446
+ def key(self) -> str:
447
+ output_format = (
448
+ TaskOutputFormat.JSON
449
+ if self.expected_output_json == True
450
+ else (
451
+ TaskOutputFormat.PYDANTIC
452
+ if self.expected_output_pydantic == True
453
+ else TaskOutputFormat.RAW
454
+ )
455
+ )
456
+ source = [self.description, output_format]
457
+ return md5("|".join(source).encode(), usedforsecurity=False).hexdigest()
458
+
459
+
460
+ @property
461
+ def summary(self) -> str:
462
+ return f"""
463
+ Task ID: {str(self.id)}
464
+ "Description": {self.description}
465
+ "Prompt": {self.output_prompt}
466
+ "Tools": {", ".join([tool_called.tool.name for tool_called in self.tools_called])}
467
+ """
468
+
469
+
470
+
469
471
 
470
472
  class ConditionalTask(Task):
471
473
  """
versionhq/team/model.py CHANGED
@@ -113,6 +113,9 @@ class TeamMember(ABC, BaseModel):
113
113
  is_manager: bool = Field(default=False)
114
114
  task: Optional[Task] = Field(default=None)
115
115
 
116
+ def update(self, task: Task):
117
+ self.task = task
118
+
116
119
 
117
120
  class Team(BaseModel):
118
121
  """
@@ -158,41 +161,6 @@ class Team(BaseModel):
158
161
  return self.name if self.name is not None else self.id.__str__
159
162
 
160
163
 
161
- @property
162
- def key(self) -> str:
163
- source = [str(member.agent.id.__str__) for member in self.members] + [str(task.id.__str__) for task in self.tasks]
164
- return md5("|".join(source).encode(), usedforsecurity=False).hexdigest()
165
-
166
-
167
- @property
168
- def manager_agent(self) -> Agent:
169
- manager_agent = [member.agent for member in self.members if member.is_manager == True]
170
- return manager_agent[0] if len(manager_agent) > 0 else None
171
-
172
-
173
- @property
174
- def manager_task(self) -> Task:
175
- """
176
- Aside from the team task, return the task that the `manager_agent` needs to handle.
177
- The task is set as second priority following to the team tasks.
178
- """
179
- task = [member.task for member in self.members if member.is_manager == True]
180
- return task[0] if len(task) > 0 else None
181
-
182
-
183
- @property
184
- def tasks(self):
185
- """
186
- Return all the tasks that the team needs to handle in order of priority:
187
- 1. team tasks,
188
- 2. manager_task,
189
- 3. members' tasks
190
- """
191
- sorted_member_tasks = [member.task for member in self.members if member.is_manager == True] + [member.task for member in self.members if member.is_manager == False]
192
- return self.team_tasks + sorted_member_tasks if len(self.team_tasks) > 0 else sorted_member_tasks
193
-
194
-
195
- # validators
196
164
  @field_validator("id", mode="before")
197
165
  @classmethod
198
166
  def _deny_user_set_id(cls, v: Optional[UUID4]) -> None:
@@ -201,6 +169,21 @@ class Team(BaseModel):
201
169
  raise PydanticCustomError("may_not_set_field", "The 'id' field cannot be set by the user.", {})
202
170
 
203
171
 
172
+ @model_validator(mode="after")
173
+ def assess_tasks(self):
174
+ """
175
+ Validates if the model recognize all tasks that the team needs to handle.
176
+ """
177
+
178
+ if self.tasks:
179
+ if all(task in self.tasks for task in self.team_tasks) == False:
180
+ raise PydanticCustomError("task_validation_error", "`team_tasks` needs to be recognized in the task.", {})
181
+
182
+ if len(self.tasks) != len(self.team_tasks) + len([member for member in self.members if member.task is not None]):
183
+ raise PydanticCustomError("task_validation_error", "Some tasks are missing.", {})
184
+ return self
185
+
186
+
204
187
  @model_validator(mode="after")
205
188
  def check_manager_llm(self):
206
189
  """
@@ -208,37 +191,29 @@ class Team(BaseModel):
208
191
  """
209
192
 
210
193
  if self.process == TaskHandlingProcess.hierarchical:
211
- if self.manager_agent is None:
194
+ if self.managers is None:
212
195
  raise PydanticCustomError(
213
- "missing_manager_llm_or_manager_agent",
214
- "Attribute `manager_llm` or `manager_agent` is required when using hierarchical process.",
196
+ "missing_manager_llm_or_manager",
197
+ "Attribute `manager_llm` or `manager` is required when using hierarchical process.",
215
198
  {},
216
199
  )
217
200
 
218
- if (self.manager_agent is not None) and (
219
- self.members.count(self.manager_agent) > 0
220
- ):
221
- raise PydanticCustomError(
222
- "manager_agent_in_agents",
223
- "Manager agent should not be included in agents list.",
224
- {},
225
- )
201
+ if self.managers and (self.manager_tasks is None or self.team_tasks is None):
202
+ raise PydanticCustomError("missing_manager_task", "manager needs to have at least one manager task or team task.", {})
203
+
226
204
  return self
227
205
 
228
206
 
229
207
  @model_validator(mode="after")
230
208
  def validate_tasks(self):
231
209
  """
232
- Every team member should have a task to handle.
210
+ Sequential task processing without any team tasks require a task-agent pairing.
233
211
  """
234
- if self.process == TaskHandlingProcess.sequential:
212
+
213
+ if self.process == TaskHandlingProcess.sequential and self.team_tasks is None:
235
214
  for member in self.members:
236
215
  if member.task is None:
237
- raise PydanticCustomError(
238
- "missing_agent_in_task",
239
- f"Sequential process error: Agent is missing in the task with the following description: {member.task.description}",
240
- {},
241
- )
216
+ raise PydanticCustomError("missing_agent_in_task", "Sequential process error: Agent is missing the task", {})
242
217
  return self
243
218
 
244
219
  @model_validator(mode="after")
@@ -249,34 +224,64 @@ class Team(BaseModel):
249
224
 
250
225
  async_task_count = 0
251
226
  for task in reversed(self.tasks):
252
- if task.async_execution:
227
+ if not task:
228
+ break
229
+ elif task.async_execution:
253
230
  async_task_count += 1
254
231
  else:
255
232
  break
256
233
 
257
234
  if async_task_count > 1:
258
- raise PydanticCustomError(
259
- "async_task_count",
260
- "The team must end with max. one asynchronous task.",
261
- {},
262
- )
235
+ raise PydanticCustomError("async_task_count", "The team must end with max. one asynchronous task.", {})
263
236
  return self
264
237
 
238
+
265
239
  def _get_responsible_agent(self, task: Task) -> Agent:
266
- res = [member.agent for member in self.members if member.task.id == task.id]
267
- return None if len(res) == 0 else res[0]
240
+ if task is None:
241
+ return None
242
+ else:
243
+ res = [member.agent for member in self.members if member.task and member.task.id == task.id]
244
+ return None if len(res) == 0 else res[0]
245
+
246
+
247
+ def _handle_team_planning(self) -> None:
248
+ """
249
+ Form a team considering agents and tasks given, and update `self.members` field:
250
+ 1. Idling managers to take the team tasks.
251
+ 2. Idling members to take the remaining tasks starting from the team tasks to member tasks.
252
+ 3. Create agents to handle the rest tasks.
253
+ """
268
254
 
269
- # setup team planner
270
- def _handle_team_planning(self):
271
255
  team_planner = TeamPlanner(tasks=self.tasks, planner_llm=self.planning_llm)
272
- result = team_planner._handle_task_planning()
256
+ idling_managers: List[TeamMember] = [member for member in self.members if member.task is None and member.is_manager is True]
257
+ idling_members: List[TeamMember] = [member for member in self.members if member.task is None and member.is_manager is False]
258
+ unassigned_tasks: List[Task] = self.member_tasks_without_agent
259
+ new_team_members: List[TeamMember] = []
260
+
261
+ if self.team_tasks:
262
+ candidates = idling_managers + idling_members
263
+ if candidates:
264
+ i = 0
265
+ while i < len(candidates):
266
+ if self.team_tasks[i]:
267
+ candidates[i].task = self.team_tasks[i]
268
+ i += 1
269
+
270
+ if len(self.team_tasks) > i:
271
+ for item in self.team_tasks[i:]:
272
+ if item not in unassigned_tasks:
273
+ unassigned_tasks = [item, ] + unassigned_tasks
273
274
 
274
- if result is not None:
275
- for task in self.tasks:
276
- task_id = task.id
277
- task.description += (
278
- result[task_id] if hasattr(result, str(task_id)) else result
279
- )
275
+ else:
276
+ for item in self.team_tasks:
277
+ if item not in unassigned_tasks:
278
+ unassigned_tasks = [item, ] + unassigned_tasks
279
+
280
+ if unassigned_tasks:
281
+ new_team_members = team_planner._handle_assign_agents(unassigned_tasks=unassigned_tasks)
282
+
283
+ if new_team_members:
284
+ self.members += new_team_members
280
285
 
281
286
 
282
287
  # task execution
@@ -333,9 +338,11 @@ class Team(BaseModel):
333
338
  token_sum = agent._token_process.get_summary()
334
339
  total_usage_metrics.add_usage_metrics(token_sum)
335
340
 
336
- if self.manager_agent and hasattr(self.manager_agent, "_token_process"):
337
- token_sum = self.manager_agent._token_process.get_summary()
338
- total_usage_metrics.add_usage_metrics(token_sum)
341
+ if self.managers:
342
+ for manager in self.managers:
343
+ if hasattr(manager.agent, "_token_process"):
344
+ token_sum = manager.agent._token_process.get_summary()
345
+ total_usage_metrics.add_usage_metrics(token_sum)
339
346
 
340
347
  self.usage_metrics = total_usage_metrics
341
348
  return total_usage_metrics
@@ -366,7 +373,7 @@ class Team(BaseModel):
366
373
 
367
374
  responsible_agent = self._get_responsible_agent(task)
368
375
  if responsible_agent is None:
369
- responsible_agent = self.manager_agent if self.manager_agent else self.members[0].agent
376
+ self._handle_team_planning()
370
377
 
371
378
  if isinstance(task, ConditionalTask):
372
379
  skipped_task_output = task._handle_conditional_task(task_outputs, futures, task_index, was_replayed)
@@ -377,13 +384,13 @@ class Team(BaseModel):
377
384
  # self._log_task_start(task, responsible_agent)
378
385
 
379
386
  if task.async_execution:
380
- context = create_raw_outputs(tasks=[task, ],task_outputs=([last_sync_output,] if last_sync_output else []))
387
+ context = create_raw_outputs(tasks=[task, ], task_outputs=([last_sync_output,] if last_sync_output else []))
381
388
  future = task.execute_async(agent=responsible_agent, context=context, tools=responsible_agent.tools)
382
389
  futures.append((task, future, task_index))
383
390
  else:
384
391
  context = create_raw_outputs(tasks=[task,], task_outputs=([last_sync_output,] if last_sync_output else [] ))
385
392
  task_output = task.execute_sync(agent=responsible_agent, context=context, tools=responsible_agent.tools)
386
- if responsible_agent is self.manager_agent:
393
+ if self.managers and responsible_agent in [manager.agent for manager in self.managers]:
387
394
  lead_task_output = task_output
388
395
 
389
396
  task_outputs.append(task_output)
@@ -400,15 +407,15 @@ class Team(BaseModel):
400
407
  def kickoff(self, kwargs_before: Optional[Dict[str, str]] = None, kwargs_after: Optional[Dict[str, Any]] = None) -> TeamOutput:
401
408
  """
402
409
  Kickoff the team:
403
- 0. Plan the team action if we have `team_tasks` using `planning_llm`.
410
+ 0. Assign an agent to a task - using conditions (manager prioritizes team_tasks) and planning_llm.
404
411
  1. Address `before_kickoff_callbacks` if any.
405
- 2. Handle team members' tasks in accordance with the `process`.
412
+ 2. Handle team members' tasks in accordance with the process.
406
413
  3. Address `after_kickoff_callbacks` if any.
407
414
  """
408
415
 
409
416
  metrics: List[UsageMetrics] = []
410
417
 
411
- if len(self.team_tasks) > 0 or self.planning_llm is not None:
418
+ if self.team_tasks or self.member_tasks_without_agent:
412
419
  self._handle_team_planning()
413
420
 
414
421
  if kwargs_before is not None:
@@ -450,3 +457,51 @@ class Team(BaseModel):
450
457
  self.usage_metrics.add_usage_metrics(metric)
451
458
 
452
459
  return result
460
+
461
+
462
+ @property
463
+ def key(self) -> str:
464
+ source = [str(member.agent.id.__str__) for member in self.members] + [str(task.id.__str__) for task in self.tasks]
465
+ return md5("|".join(source).encode(), usedforsecurity=False).hexdigest()
466
+
467
+
468
+ @property
469
+ def managers(self) -> List[TeamMember] | None:
470
+ managers = [member for member in self.members if member.is_manager == True]
471
+ return managers if len(managers) > 0 else None
472
+
473
+
474
+ @property
475
+ def manager_tasks(self) -> List[Task] | None:
476
+ """
477
+ Tasks (incl. team tasks) handled by managers in the team.
478
+ """
479
+ if self.managers:
480
+ tasks = [manager.task for manager in self.managers if manager.task is not None]
481
+ return tasks if len(tasks) > 0 else None
482
+
483
+ return None
484
+
485
+
486
+ @property
487
+ def tasks(self):
488
+ """
489
+ Return all the tasks that the team needs to handle in order of priority:
490
+ 1. team tasks, -> assigned to the member
491
+ 2. manager_task,
492
+ 3. members' tasks
493
+ """
494
+
495
+ team_tasks = self.team_tasks
496
+ manager_tasks = [member.task for member in self.members if member.is_manager == True and member.task is not None and member.task not in team_tasks]
497
+ member_tasks = [member.task for member in self.members if member.is_manager == False and member.task is not None and member.task not in team_tasks]
498
+
499
+ return team_tasks + manager_tasks + member_tasks
500
+
501
+
502
+ @property
503
+ def member_tasks_without_agent(self) -> List[Task] | None:
504
+ if self.members:
505
+ return [member.task for member in self.members if member.agent is None]
506
+
507
+ return None
@@ -3,37 +3,80 @@ from dotenv import load_dotenv
3
3
  from typing import Any, List, Optional
4
4
  from pydantic import BaseModel, Field
5
5
 
6
- from versionhq.agent.model import Agent
7
- from versionhq.task.model import Task, ResponseField
8
-
9
6
  load_dotenv(override=True)
10
7
 
11
8
 
12
9
  class TeamPlanner:
13
10
  """
14
- (Optional) Plan how the team should handle multiple tasks using LLM.
11
+ Assign agents to multiple tasks.
15
12
  """
16
13
 
14
+ from versionhq.task.model import Task, ResponseField, TaskOutput
15
+ from versionhq.agent.model import Agent
16
+
17
+
17
18
  def __init__(self, tasks: List[Task], planner_llm: Optional[Any] = None):
18
19
  self.tasks = tasks
19
- self.planner_llm = (
20
- planner_llm if planner_llm != None else os.environ.get("LITELLM_MODEL_NAME")
20
+ self.planner_llm = planner_llm if planner_llm else os.environ.get("LITELLM_MODEL_NAME")
21
+
22
+
23
+ def _handle_assign_agents(self, unassigned_tasks: List[Task]) -> List[Any]:
24
+ """
25
+ Build an agent and assign it a task, then return a list of TeamMember connecting the agent created and the task given.
26
+ """
27
+
28
+ from versionhq.agent.model import Agent
29
+ from versionhq.task.model import Task, ResponseField
30
+ from versionhq.team.model import TeamMember
31
+
32
+ new_member_list: List[TeamMember] = []
33
+ agent_creator = Agent(
34
+ role="agent_creator",
35
+ goal="build an ai agent that can competitively handle the task given",
36
+ llm=self.planner_llm,
21
37
  )
22
38
 
23
- def _handle_task_planning(self) -> BaseModel:
39
+ for unassgined_task in unassigned_tasks:
40
+ task = Task(
41
+ description=f"""
42
+ Based on the following task summary, draft a AI agent's role and goal in concise manner.
43
+ Task summary: {unassgined_task.summary}
44
+ """,
45
+ expected_output_json=True,
46
+ output_field_list=[
47
+ ResponseField(title="goal", type=str, required=True),
48
+ ResponseField(title="role", type=str, required=True),
49
+ ],
50
+ )
51
+ res = task.execute_sync(agent=agent_creator)
52
+ agent = Agent(
53
+ role=res.json_dict["role"] if "role" in res.json_dict else res.raw,
54
+ goal=res.json_dict["goal"] if "goal" in res.json_dict else task.description
55
+ )
56
+ if agent.id:
57
+ team_member = TeamMember(agent=agent, task=unassgined_task, is_manager=False)
58
+ new_member_list.append(team_member)
59
+
60
+ return new_member_list
61
+
62
+
63
+
64
+ def _handle_task_planning(self, context: Optional[str] = None, tools: Optional[str] = None) -> TaskOutput:
24
65
  """
25
66
  Handles the team planning by creating detailed step-by-step plans for each task.
26
67
  """
27
68
 
28
- planning_agent = Agent(
29
- role="Task Execution Planner",
30
- goal="Your goal is to create an extremely detailed, step-by-step plan based on the tasks and tools available to each agent so that they can perform the tasks in an exemplary manner",
31
- backstory="You have a strong ability to design efficient organizational structures and task processes, minimizing unnecessary steps.",
69
+ from versionhq.agent.model import Agent
70
+ from versionhq.task.model import Task, ResponseField
71
+
72
+ team_planner = Agent(
73
+ role="team planner",
74
+ goal="Plan extremely detailed, step-by-step plan based on the tasks and tools available to each agent so that they can perform the tasks in an exemplary manner and assign a task to each agent.",
32
75
  llm=self.planner_llm,
33
76
  )
34
77
 
35
78
  task_summary_list = [task.summary for task in self.tasks]
36
- task_to_handle = Task(
79
+ task = Task(
37
80
  description=f"""
38
81
  Based on the following task summaries, create the most descriptive plan that the team can execute most efficiently. Take all the task summaries - task's description and tools available - into consideration. Your answer only contains a dictionary.
39
82
 
@@ -42,14 +85,9 @@ class TeamPlanner:
42
85
  expected_output_json=False,
43
86
  expected_output_pydantic=True,
44
87
  output_field_list=[
45
- ResponseField(title=f"{task.id}", type=str, required=True)
88
+ ResponseField(title="task", type=str, required=True)
46
89
  for task in self.tasks
47
90
  ],
48
91
  )
49
- task_output = task_to_handle.execute_sync(agent=planning_agent)
50
-
51
- if isinstance(task_output.pydantic, BaseModel):
52
- return task_output.pydantic
53
-
54
- else:
55
- return None
92
+ output = task.execute_sync(agent=team_planner, context=context, tools=tools)
93
+ return output