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/__init__.py +21 -3
- versionhq/_utils/usage_metrics.py +6 -6
- versionhq/agent/inhouse_agents.py +1 -1
- versionhq/agent/model.py +90 -38
- versionhq/llm/llm_vars.py +5 -1
- versionhq/llm/model.py +1 -1
- versionhq/network/__init__.py +0 -0
- versionhq/network/model.py +394 -0
- versionhq/task/evaluate.py +5 -6
- versionhq/task/formation.py +64 -30
- versionhq/task/model.py +2 -5
- versionhq/team/model.py +95 -92
- versionhq/team/team_planner.py +5 -6
- {versionhq-1.1.12.5.dist-info → versionhq-1.1.13.1.dist-info}/METADATA +232 -130
- {versionhq-1.1.12.5.dist-info → versionhq-1.1.13.1.dist-info}/RECORD +18 -16
- {versionhq-1.1.12.5.dist-info → versionhq-1.1.13.1.dist-info}/LICENSE +0 -0
- {versionhq-1.1.12.5.dist-info → versionhq-1.1.13.1.dist-info}/WHEEL +0 -0
- {versionhq-1.1.12.5.dist-info → versionhq-1.1.13.1.dist-info}/top_level.txt +0 -0
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
|
89
|
+
class Member(BaseModel):
|
89
90
|
"""
|
90
|
-
A class to store a
|
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
|
-
|
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.
|
101
|
+
return bool(self.tasks)
|
101
102
|
|
102
103
|
|
103
104
|
class Team(BaseModel):
|
104
105
|
"""
|
105
|
-
A
|
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[
|
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
|
-
|
121
|
-
team_tasks: Optional[List[Task]] = Field(default_factory=list, description="
|
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
|
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
|
-
|
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
|
131
|
+
description="list of callback functions to be executed before the team launch. i.e., adjust inputs"
|
131
132
|
)
|
132
|
-
|
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
|
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
|
-
|
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
|
-
|
167
|
-
|
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
|
-
|
179
|
+
Check if the team has a manager
|
175
180
|
"""
|
176
181
|
|
177
|
-
if self.process == TaskHandlingProcess.hierarchical:
|
178
|
-
if self.managers
|
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
|
-
|
186
|
-
|
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
|
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
|
199
|
-
if member.task
|
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
|
-
|
228
|
-
|
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
|
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.
|
240
|
-
idling_managers: List[
|
241
|
-
idling_members: List[
|
242
|
-
unassigned_tasks: List[Task] = self.member_tasks_without_agent
|
243
|
-
new_team_members: List[
|
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
|
-
|
260
|
-
|
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
|
-
|
265
|
-
|
256
|
+
elif idling_members:
|
257
|
+
idling_members[0].tasks.extend(unassigned_tasks)
|
266
258
|
|
267
|
-
|
268
|
-
|
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
|
296
|
-
|
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.
|
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,
|
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
|
394
|
-
1. Address `
|
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 `
|
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.
|
395
|
+
self._assign_tasks()
|
403
396
|
|
404
|
-
if
|
405
|
-
for
|
406
|
-
|
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
|
428
|
-
result =
|
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[
|
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]
|
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
|
-
|
458
|
-
|
450
|
+
for manager in self.managers:
|
451
|
+
if manager.tasks:
|
452
|
+
res.extend(manager.tasks)
|
459
453
|
|
460
|
-
return
|
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 =
|
474
|
-
member_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]
|
479
|
+
def member_tasks_without_agent(self) -> List[Task]:
|
480
|
+
res = list()
|
481
|
+
|
481
482
|
if self.members:
|
482
|
-
|
483
|
+
for member in self.members:
|
484
|
+
if member.agent is None and member.tasks:
|
485
|
+
res.extend(member.tasks)
|
483
486
|
|
484
|
-
return
|
487
|
+
return res
|
versionhq/team/team_planner.py
CHANGED
@@ -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
|
-
|
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
|
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
|
33
|
+
from versionhq.team.model import Member
|
35
34
|
|
36
|
-
new_member_list: List[
|
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 =
|
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
|