versionhq 1.1.12.5__py3-none-any.whl → 1.1.13.1__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/team/model.py CHANGED
@@ -39,12 +39,12 @@ load_dotenv(override=True)
39
39
 
40
40
 
41
41
  class Formation(str, Enum):
42
+ UNDEFINED = 0
42
43
  SOLO = 1
43
44
  SUPERVISING = 2
44
45
  NETWORK = 3
45
46
  RANDOM = 4
46
47
  HYBRID = 10
47
- UNDEFINED = 0
48
48
 
49
49
 
50
50
  class TaskHandlingProcess(str, Enum):
@@ -75,6 +75,7 @@ class TeamOutput(TaskOutput):
75
75
  def __str__(self):
76
76
  return (str(self.pydantic) if self.pydantic else str(self.json_dict) if self.json_dict else self.raw)
77
77
 
78
+
78
79
  def __getitem__(self, key):
79
80
  if self.pydantic and hasattr(self.pydantic, key):
80
81
  return getattr(self.pydantic, key)
@@ -85,25 +86,24 @@ class TeamOutput(TaskOutput):
85
86
 
86
87
 
87
88
 
88
- class TeamMember(BaseModel):
89
+ class Member(BaseModel):
89
90
  """
90
- A class to store a team member
91
+ A class to store a member in the network and connect the agent as a member with tasks and sharable settings.
91
92
  """
92
93
  agent: Agent | None = Field(default=None)
93
94
  is_manager: bool = Field(default=False)
94
95
  can_share_knowledge: bool = Field(default=True, description="whether to share the agent's knowledge in the team")
95
96
  can_share_memory: bool = Field(default=True, description="whether to share the agent's memory in the team")
96
- task: Optional[Task] = Field(default=None, description="task assigned to the agent")
97
+ tasks: Optional[List[Task]] = Field(default_factory=list, description="tasks explicitly assigned to the agent")
97
98
 
98
99
  @property
99
100
  def is_idling(self):
100
- return bool(self.task is None)
101
+ return bool(self.tasks)
101
102
 
102
103
 
103
104
  class Team(BaseModel):
104
105
  """
105
- A collaborative team of agents that handles complex, multiple tasks.
106
- We define strategies for task executions and overall workflow.
106
+ A class to store agent network that shares knowledge, memory and tools among the members.
107
107
  """
108
108
 
109
109
  __hash__ = object.__hash__
@@ -113,31 +113,31 @@ class Team(BaseModel):
113
113
 
114
114
  id: UUID4 = Field(default_factory=uuid.uuid4, frozen=True)
115
115
  name: Optional[str] = Field(default=None)
116
- members: List[TeamMember] = Field(default_factory=list)
116
+ members: List[Member] = Field(default_factory=list)
117
117
  formation: Optional[Formation] = Field(default=None)
118
+ should_reform: bool = Field(default=False, description="True if task exe. failed or eval scores below threshold")
118
119
 
119
120
  # formation planning
120
- planning_llm: Optional[Any] = Field(default=None, description="llm to generate formation")
121
- team_tasks: Optional[List[Task]] = Field(default_factory=list, description="optional tasks for the team. can be assigned to team members later")
121
+ planner_llm: Optional[Any] = Field(default=None, description="llm to generate and evaluate formation")
122
+ team_tasks: Optional[List[Task]] = Field(default_factory=list, description="tasks without dedicated agents to handle")
122
123
 
123
124
  # task execution rules
124
- prompt_file: str = Field(default="", description="path to the prompt json file to be used by the team.")
125
+ prompt_file: str = Field(default="", description="absolute path to the prompt json file")
125
126
  process: TaskHandlingProcess = Field(default=TaskHandlingProcess.sequential)
126
127
 
127
128
  # callbacks
128
- before_kickoff_callbacks: List[Callable[[Optional[Dict[str, Any]]], Optional[Dict[str, Any]]]] = Field(
129
+ pre_launch_callbacks: List[Callable[[Optional[Dict[str, Any]]], Optional[Dict[str, Any]]]] = Field(
129
130
  default_factory=list,
130
- description="list of callback functions to be executed before the team kickoff. i.e., adjust inputs"
131
+ description="list of callback functions to be executed before the team launch. i.e., adjust inputs"
131
132
  )
132
- after_kickoff_callbacks: List[Callable[[TeamOutput], TeamOutput]] = Field(
133
+ post_launch_callbacks: List[Callable[[TeamOutput], TeamOutput]] = Field(
133
134
  default_factory=list,
134
- description="list of callback functions to be executed after the team kickoff. i.e., store the result in repo"
135
+ description="list of callback functions to be executed after the team launch. i.e., store the result in repo"
135
136
  )
136
137
  step_callback: Optional[Any] = Field(default=None, description="callback to be executed after each step for all agents execution")
137
138
 
138
139
  cache: bool = Field(default=True)
139
- memory: bool = Field(default=False, description="whether the team should use memory to store memories of its execution")
140
- execution_logs: List[Dict[str, Any]] = Field(default=[], description="list of execution logs for tasks")
140
+ execution_logs: List[Dict[str, Any]] = Field(default_factory=list, description="list of execution logs of the tasks handled by members")
141
141
  usage_metrics: Optional[UsageMetrics] = Field(default=None, description="usage metrics for all the llm executions")
142
142
 
143
143
 
@@ -163,40 +163,45 @@ class Team(BaseModel):
163
163
  if all(task in self.tasks for task in self.team_tasks) == False:
164
164
  raise PydanticCustomError("task_validation_error", "`team_tasks` needs to be recognized in the task.", {})
165
165
 
166
- if len(self.tasks) != len(self.team_tasks) + len([member for member in self.members if member.task is not None]):
167
- raise PydanticCustomError("task_validation_error", "Some tasks are missing.", {})
166
+
167
+ num_member_tasks = 0
168
+ for member in self.members:
169
+ num_member_tasks += len(member.tasks)
170
+
171
+ # if len(self.tasks) != len(self.team_tasks) + num_member_tasks:
172
+ # raise PydanticCustomError("task_validation_error", "Some tasks are missing.", {})
168
173
  return self
169
174
 
170
175
 
171
176
  @model_validator(mode="after")
172
177
  def check_manager_llm(self):
173
178
  """
174
- Validates that the language model is set when using hierarchical process.
179
+ Check if the team has a manager
175
180
  """
176
181
 
177
- if self.process == TaskHandlingProcess.hierarchical:
178
- if self.managers is None:
182
+ if self.process == TaskHandlingProcess.hierarchical or self.formation == Formation.SUPERVISING:
183
+ if not self.managers:
184
+ self._logger.log(level="error", message="The process or formation created needs at least 1 manager agent.", color="red")
179
185
  raise PydanticCustomError(
180
- "missing_manager_llm_or_manager",
181
- "Attribute `manager_llm` or `manager` is required when using hierarchical process.",
182
- {},
183
- )
186
+ "missing_manager_llm_or_manager","Attribute `manager_llm` or `manager` is required when using hierarchical process.", {})
184
187
 
185
- if self.managers and (self.manager_tasks is None or self.team_tasks is None):
186
- raise PydanticCustomError("missing_manager_task", "manager needs to have at least one manager task or team task.", {})
188
+ ## comment out for the formation flexibilities
189
+ # if self.managers and (self.manager_tasks is None or self.team_tasks is None):
190
+ # self._logger.log(level="error", message="The manager is idling. At least 1 task needs to be assigned to the manager.", color="red")
191
+ # raise PydanticCustomError("missing_manager_task", "manager needs to have at least one manager task or team task.", {})
187
192
 
188
193
  return self
189
194
 
190
195
 
191
196
  @model_validator(mode="after")
192
- def validate_tasks(self):
197
+ def validate_task_member_paring(self):
193
198
  """
194
199
  Sequential task processing without any team tasks require a task-agent pairing.
195
200
  """
196
-
197
201
  if self.process == TaskHandlingProcess.sequential and self.team_tasks is None:
198
- for member in self.members:
199
- if member.task is None:
202
+ for task in self.tasks:
203
+ if not [member.task == task for member in self.members]:
204
+ self._logger.log(level="error", message=f"The following task needs a dedicated agent to be assinged: {task.description}", color="red")
200
205
  raise PydanticCustomError("missing_agent_in_task", "Sequential process error: Agent is missing the task", {})
201
206
  return self
202
207
 
@@ -224,11 +229,14 @@ class Team(BaseModel):
224
229
  if task is None:
225
230
  return None
226
231
  else:
227
- res = [member.agent for member in self.members if member.task and member.task.id == task.id]
228
- return None if len(res) == 0 else res[0]
232
+ for member in self.members:
233
+ if member.tasks and [item for item in member.tasks if item.id == task.id]:
234
+ return member.agent
235
+
236
+ return None
229
237
 
230
238
 
231
- def _handle_agent_formation(self) -> None:
239
+ def _assign_tasks(self) -> None:
232
240
  """
233
241
  Form a team considering agents and tasks given, and update `self.members` field:
234
242
  1. Idling managers to take the team tasks.
@@ -236,42 +244,26 @@ class Team(BaseModel):
236
244
  3. Create agents to handle the rest tasks.
237
245
  """
238
246
 
239
- team_planner = TeamPlanner(tasks=self.tasks, planner_llm=self.planning_llm)
240
- idling_managers: List[TeamMember] = [member for member in self.members if member.is_idling and member.is_manager is True]
241
- idling_members: List[TeamMember] = [member for member in self.members if member.is_idling and member.is_manager is False]
242
- unassigned_tasks: List[Task] = self.member_tasks_without_agent
243
- new_team_members: List[TeamMember] = []
244
-
245
- if self.team_tasks:
246
- candidates = idling_managers + idling_members
247
- if candidates:
248
- i = 0
249
- while i < len(candidates):
250
- if self.team_tasks[i]:
251
- candidates[i].task = self.team_tasks[i]
252
- i += 1
253
-
254
- if len(self.team_tasks) > i:
255
- for item in self.team_tasks[i:]:
256
- if item not in unassigned_tasks:
257
- unassigned_tasks = [item, ] + unassigned_tasks
247
+ team_planner = TeamPlanner(tasks=self.tasks, planner_llm=self.planner_llm)
248
+ idling_managers: List[Member] = [member for member in self.members if member.is_idling and member.is_manager == True]
249
+ idling_members: List[Member] = [member for member in self.members if member.is_idling and member.is_manager == False]
250
+ unassigned_tasks: List[Task] = self.team_tasks + self.member_tasks_without_agent if self.team_tasks else self.member_tasks_without_agent
251
+ new_team_members: List[Member] = []
258
252
 
259
- else:
260
- for item in self.team_tasks:
261
- if item not in unassigned_tasks:
262
- unassigned_tasks = [item, ] + unassigned_tasks
253
+ if idling_managers:
254
+ idling_managers[0].tasks.extend(unassigned_tasks)
263
255
 
264
- if unassigned_tasks:
265
- new_team_members = team_planner._handle_assign_agents(unassigned_tasks=unassigned_tasks)
256
+ elif idling_members:
257
+ idling_members[0].tasks.extend(unassigned_tasks)
266
258
 
267
- if new_team_members:
268
- self.members += new_team_members
259
+ else:
260
+ new_team_members = team_planner._handle_assign_agents(unassigned_tasks=unassigned_tasks)
261
+ if new_team_members:
262
+ self.members += new_team_members
269
263
 
270
264
 
271
265
  # task execution
272
- def _process_async_tasks(
273
- self, futures: List[Tuple[Task, Future[TaskOutput], int]], was_replayed: bool = False
274
- ) -> List[TaskOutput]:
266
+ def _process_async_tasks(self, futures: List[Tuple[Task, Future[TaskOutput], int]], was_replayed: bool = False) -> List[TaskOutput]:
275
267
  """
276
268
  When we have `Future` tasks, updated task outputs and task execution logs accordingly.
277
269
  """
@@ -292,10 +284,11 @@ class Team(BaseModel):
292
284
  Note that `tasks` are already sorted by the importance.
293
285
  """
294
286
 
295
- if len(task_outputs) < 1:
296
- raise ValueError("Something went wrong. Kickoff should return only one task output.")
287
+ if not task_outputs:
288
+ self._logger.log(level="error", message="Missing task outcomes. Failed to launch the task.", color="red")
289
+ raise ValueError("Failed to launch tasks")
297
290
 
298
- final_task_output = lead_task_output if lead_task_output is not None else task_outputs[0]
291
+ final_task_output = lead_task_output if lead_task_output is not None else task_outputs[0] #! REFINEME
299
292
  # final_string_output = final_task_output.raw
300
293
  # self._finish_execution(final_string_output)
301
294
  token_usage = self._calculate_usage_metrics()
@@ -357,7 +350,7 @@ class Team(BaseModel):
357
350
 
358
351
  responsible_agent = self._get_responsible_agent(task)
359
352
  if responsible_agent is None:
360
- self._handle_agent_formation()
353
+ self._assign_tasks()
361
354
 
362
355
  if isinstance(task, ConditionalTask):
363
356
  skipped_task_output = task._handle_conditional_task(task_outputs, futures, task_index, was_replayed)
@@ -387,29 +380,27 @@ class Team(BaseModel):
387
380
  return self._create_team_output(task_outputs, lead_task_output)
388
381
 
389
382
 
390
- def launch(self, kwargs_before: Optional[Dict[str, str]] = None, kwargs_after: Optional[Dict[str, Any]] = None) -> TeamOutput:
383
+ def launch(self, kwargs_pre: Optional[Dict[str, str]] = None, kwargs_post: Optional[Dict[str, Any]] = None) -> TeamOutput:
391
384
  """
392
385
  Confirm and launch the formation - execute tasks and record outputs.
393
- 0. Assign an agent to a task - using conditions (manager prioritizes team_tasks) and planning_llm.
394
- 1. Address `before_kickoff_callbacks` if any.
386
+ 0. Assign an agent to a task - using conditions (manager prioritizes team_tasks) and planner_llm.
387
+ 1. Address `pre_launch_callbacks` if any.
395
388
  2. Handle team members' tasks in accordance with the process.
396
- 3. Address `after_kickoff_callbacks` if any.
389
+ 3. Address `post_launch_callbacks` if any.
397
390
  """
398
391
 
399
392
  metrics: List[UsageMetrics] = []
400
393
 
401
394
  if self.team_tasks or self.member_tasks_without_agent:
402
- self._handle_agent_formation()
395
+ self._assign_tasks()
403
396
 
404
- if kwargs_before is not None:
405
- for before_callback in self.before_kickoff_callbacks:
406
- before_callback(**kwargs_before)
397
+ if kwargs_pre is not None:
398
+ for func in self.pre_launch_callbacks:
399
+ func(**kwargs_pre)
407
400
 
408
401
  # self._execution_span = self._telemetry.team_execution_span(self, inputs)
409
402
  # self._task_output_handler.reset()
410
403
  # self._logging_color = "bold_purple"
411
-
412
-
413
404
  # i18n = I18N(prompt_file=self.prompt_file)
414
405
 
415
406
  for member in self.members:
@@ -424,8 +415,8 @@ class Team(BaseModel):
424
415
 
425
416
  result = self._execute_tasks(self.tasks)
426
417
 
427
- for after_callback in self.after_kickoff_callbacks:
428
- result = after_callback(result, **kwargs_after)
418
+ for func in self.post_launch_callbacks:
419
+ result = func(result, **kwargs_post)
429
420
 
430
421
  metrics += [member.agent._token_process.get_summary() for member in self.members]
431
422
 
@@ -443,25 +434,28 @@ class Team(BaseModel):
443
434
 
444
435
 
445
436
  @property
446
- def managers(self) -> List[TeamMember] | None:
437
+ def managers(self) -> List[Member] | None:
447
438
  managers = [member for member in self.members if member.is_manager == True]
448
439
  return managers if len(managers) > 0 else None
449
440
 
450
441
 
451
442
  @property
452
- def manager_tasks(self) -> List[Task] | None:
443
+ def manager_tasks(self) -> List[Task]:
453
444
  """
454
445
  Tasks (incl. team tasks) handled by managers in the team.
455
446
  """
447
+ res = list()
448
+
456
449
  if self.managers:
457
- tasks = [manager.task for manager in self.managers if manager.task is not None]
458
- return tasks if len(tasks) > 0 else None
450
+ for manager in self.managers:
451
+ if manager.tasks:
452
+ res.extend(manager.tasks)
459
453
 
460
- return None
454
+ return res
461
455
 
462
456
 
463
457
  @property
464
- def tasks(self):
458
+ def tasks(self) -> List[Task]:
465
459
  """
466
460
  Return all the tasks that the team needs to handle in order of priority:
467
461
  1. team tasks, -> assigned to the member
@@ -470,15 +464,24 @@ class Team(BaseModel):
470
464
  """
471
465
 
472
466
  team_tasks = self.team_tasks
473
- 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]
474
- 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]
467
+ manager_tasks = self.manager_tasks
468
+ member_tasks = []
469
+
470
+ for member in self.members:
471
+ if member.is_manager == False and member.tasks:
472
+ a = [item for item in member.tasks if item not in team_tasks and item not in manager_tasks]
473
+ member_tasks += a
475
474
 
476
475
  return team_tasks + manager_tasks + member_tasks
477
476
 
478
477
 
479
478
  @property
480
- def member_tasks_without_agent(self) -> List[Task] | None:
479
+ def member_tasks_without_agent(self) -> List[Task]:
480
+ res = list()
481
+
481
482
  if self.members:
482
- return [member.task for member in self.members if member.agent is None]
483
+ for member in self.members:
484
+ if member.agent is None and member.tasks:
485
+ res.extend(member.tasks)
483
486
 
484
- return None
487
+ return res
@@ -1,9 +1,8 @@
1
1
  import os
2
- from dotenv import load_dotenv
3
2
  from typing import Any, List, Optional, Dict
4
3
  from pydantic import BaseModel, Field
5
4
 
6
- load_dotenv(override=True)
5
+
7
6
 
8
7
 
9
8
  class TeamPlanner:
@@ -26,14 +25,14 @@ class TeamPlanner:
26
25
 
27
26
  def _handle_assign_agents(self, unassigned_tasks: List[Task]) -> List[Any]:
28
27
  """
29
- Build an agent and assign it a task, then return a list of TeamMember connecting the agent created and the task given.
28
+ Build an agent and assign it a task, then return a list of Member connecting the agent created and the task given.
30
29
  """
31
30
 
32
31
  from versionhq.agent.model import Agent
33
32
  from versionhq.task.model import Task, ResponseField
34
- from versionhq.team.model import TeamMember
33
+ from versionhq.team.model import Member
35
34
 
36
- new_member_list: List[TeamMember] = []
35
+ new_member_list: List[Member] = []
37
36
  agent_creator = Agent(
38
37
  role="agent_creator",
39
38
  goal="build an ai agent that can competitively handle the task given",
@@ -57,7 +56,7 @@ class TeamPlanner:
57
56
  goal=res.json_dict["goal"] if "goal" in res.json_dict else task.description
58
57
  )
59
58
  if agent.id:
60
- team_member = TeamMember(agent=agent, task=unassgined_task, is_manager=False)
59
+ team_member = Member(agent=agent, tasks=[unassgined_task], is_manager=False)
61
60
  new_member_list.append(team_member)
62
61
 
63
62
  return new_member_list