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.
@@ -0,0 +1,394 @@
1
+ import enum
2
+ import uuid
3
+ from abc import ABC
4
+ from typing import List, Any, Optional, Callable, Dict, Type
5
+
6
+ from pydantic import BaseModel, InstanceOf, Field, UUID4, PrivateAttr, field_validator
7
+ from pydantic_core import PydanticCustomError
8
+
9
+ from versionhq.task.model import Task
10
+ from versionhq.agent.model import Agent
11
+ from versionhq._utils.logger import Logger
12
+
13
+
14
+ try:
15
+ import networkx as ntx
16
+ except ImportError:
17
+ try:
18
+ import os
19
+ os.system("uv add networkx --optional networkx")
20
+ import networkx as ntx
21
+ except:
22
+ raise ImportError("networkx is not installed. Please install it with: uv add networkx --optional networkx")
23
+
24
+
25
+ try:
26
+ import matplotlib.pyplot as plt
27
+ except ImportError:
28
+ try:
29
+ import os
30
+ os.system("uv add matplotlib --optional matplotlib")
31
+ import matplotlib.pyplot as plt
32
+ except:
33
+ raise ImportError("matplotlib is not installed. Please install it with: uv add matplotlib --optional matplotlib")
34
+
35
+ import networkx as nx
36
+ import matplotlib.pyplot as plt
37
+
38
+
39
+
40
+ class TaskStatus(enum.Enum):
41
+ """
42
+ Enum to track the task execution status
43
+ """
44
+ NOT_STARTED = 1
45
+ IN_PROGRESS = 2
46
+ BLOCKED = 3 # task is waiting for its dependant tasks to complete. resumption set as AUTO.
47
+ COMPLETED = 4
48
+ DELAYED = 5 # task has begun - but is taking longer than expected duration and behind schedule.
49
+ ON_HOLD = 6 # task is temporarily & intentionally paused due to external factors and/or decisions. resumption set as DECISION.
50
+
51
+
52
+
53
+ class DependencyType(enum.Enum):
54
+ """
55
+ Concise enumeration of the edge type.
56
+ """
57
+
58
+ FINISH_TO_START = "FS" # Task B starts after Task A finishes
59
+ START_TO_START = "SS" # Task B starts when Task A starts
60
+ FINISH_TO_FINISH = "FF" # Task B finishes when Task A finishes
61
+ START_TO_FINISH = "SF" # Task B finishes when Task A starts
62
+
63
+
64
+
65
+ class TriggerEvent(enum.Enum):
66
+ """
67
+ Concise enumeration of key trigger events for task execution.
68
+ """
69
+ IMMEDIATE = 0 # execute immediately
70
+ DEPENDENCIES_MET = 1 # All/required dependencies are satisfied
71
+ RESOURCES_AVAILABLE = 2 # Necessary resources are available
72
+ SCHEDULED_TIME = 3 # Scheduled start time or time window reached
73
+ EXTERNAL_EVENT = 4 # Triggered by an external event/message
74
+ DATA_AVAILABLE = 5 # Required data is available both internal/external
75
+ APPROVAL_RECEIVED = 6 # Necessary approvals have been granted
76
+ STATUS_CHANGED = 7 # Relevant task/system status has changed
77
+ RULE_MET = 8 # A predefined rule or condition has been met
78
+ MANUAL_TRIGGER = 9 # Manually initiated by a user
79
+ ERROR_HANDLED = 10 # A previous error/exception has been handled
80
+
81
+
82
+
83
+ class Node(BaseModel):
84
+ """
85
+ A class to store a node object.
86
+ """
87
+ id: UUID4 = Field(default_factory=uuid.uuid4, frozen=True)
88
+ task: InstanceOf[Task] = Field(default=None)
89
+ trigger_event: TriggerEvent = Field(default=TriggerEvent.IMMEDIATE, description="store trigger event to execute the task")
90
+ in_degree_nodes: List[Any] = Field(default=None, description="list of Node objects")
91
+ out_degree_nodes: List[Any] = Field(default=None, description="list of Node objects")
92
+ assigned_to: InstanceOf[Agent] = Field(default=None)
93
+ status: TaskStatus = Field(default=TaskStatus.NOT_STARTED)
94
+
95
+
96
+ @field_validator("id", mode="before")
97
+ @classmethod
98
+ def _deny_id(cls, v: Optional[UUID4]) -> None:
99
+ if v:
100
+ raise PydanticCustomError("may_not_set_field", "This field is not to be set by the user.", {})
101
+
102
+ def is_independent(self) -> bool:
103
+ return not self.in_degree_nodes and not self.out_degree_nodes
104
+
105
+ @property
106
+ def in_degrees(self) -> int:
107
+ return len(self.in_degree_nodes) if self.in_degree_nodes else 0
108
+
109
+ @property
110
+ def out_degrees(self) -> int:
111
+ return len(self.out_degree_nodes) if self.out_degree_nodes else 0
112
+
113
+ @property
114
+ def degrees(self) -> int:
115
+ return self.in_degrees + self.out_degrees
116
+
117
+ @property
118
+ def identifier(self) -> str:
119
+ """Unique identifier for the node"""
120
+ return f"{str(self.id)}"
121
+
122
+ def __str__(self):
123
+ return self.identifier
124
+
125
+
126
+ class Edge(BaseModel):
127
+ """
128
+ A class to store an edge object that connects multiple nodes as dependencies.
129
+ """
130
+ description: Optional[str] = Field(default=None)
131
+
132
+ type: DependencyType = Field(default=DependencyType.FINISH_TO_START)
133
+ weight: Optional[float] = Field(default=None, description="duration or weight of the dependency: 1 light 10 heavy")
134
+ lag: Optional[float] = Field(default=None, description="lag time for the dependency to be executed")
135
+ constraint: Optional[str] = Field(default=None, description="constraint to consider executing the dependency")
136
+ priority: Optional[int] = Field(default=None, description="priority of the dependency if multiple depencencies are given")
137
+
138
+ data_transfer: bool = Field(True, description="whether the data transfer is required")
139
+ data_format: Optional[str] = Field(default=None, description="Format of data transfer")
140
+
141
+ # execution logic
142
+ required: bool = Field(True, description="whether the execution of the dependency is required - False = conditional, optional task")
143
+ condition: Optional[Callable] = Field(default=None, description="conditional function to start executing the dependency")
144
+
145
+
146
+ def is_dependency_met(self, predecessor_node: Node = None) -> bool:
147
+ """
148
+ Define if the dependency is ready to execute:
149
+
150
+ required condition Dependency Met? Dependent Task Can Start?
151
+ True Not Given Predecessor task finished Yes (if other deps met)
152
+ True Given Predecessor task finished and condition True Yes (if other deps met)
153
+ False Not Given Always (regardless of predecessor status) Yes (if other deps met)
154
+ False Given Condition True (predecessor status irrelevant) Yes (if other deps met)
155
+ """
156
+
157
+ if self.required:
158
+ if predecessor_node.status == TaskStatus.COMPLETED:
159
+ return self.condition() if self.condtion else True
160
+ else:
161
+ return False
162
+ else:
163
+ return self.condition() if self.condition else True
164
+
165
+
166
+
167
+ class Graph(ABC, BaseModel):
168
+ """
169
+ An abstract class to store G using NetworkX library.
170
+ """
171
+
172
+ _logger: Logger = PrivateAttr(default_factory=lambda: Logger(verbose=True))
173
+ directed: bool = Field(default=False, description="Whether the graph is directed")
174
+ graph: Type[nx.Graph] = Field(default=None)
175
+ nodes: Dict[str, InstanceOf[Node]] = Field(default_factory=dict, description="identifier: Node - for the sake of ")
176
+ edges: Dict[str, InstanceOf[Edge]] = Field(default_factory=dict)
177
+
178
+ def __init__(self, directed: bool = False, **kwargs):
179
+
180
+ super().__init__(directed=directed, **kwargs)
181
+ self.graph = nx.DiGraph() if self.directed else nx.Graph()
182
+
183
+ def add_node(self, node: Node) -> None:
184
+ self.graph.add_node(node.identifier, **node.model_dump())
185
+ self.nodes[node.identifier] = node
186
+
187
+ def add_edge(self, source: str, target: str, edge: Edge) -> None:
188
+ self.graph.add_edge(source, target, **edge.model_dump())
189
+ self.edges[(source, target)] = edge
190
+
191
+ def add_weighted_edges_from(self, edges):
192
+ self.graph.add_weighted_edges_from(edges)
193
+
194
+ def get_neighbors(self, node: Node) -> List[Node]:
195
+ return list(self.graph.neighbors(node))
196
+
197
+ def get_in_degree(self, node: Node) -> int:
198
+ return self.graph.in_degree(node)
199
+
200
+ def get_out_degree(self, node: Node) -> int:
201
+ return self.graph.out_degree(node)
202
+
203
+ def find_path(self, source: str, target: str, weight: Any) -> Any:
204
+ try:
205
+ return nx.shortest_path(self.graph, source=source, target=target, weight=weight)
206
+ except nx.NetworkXNoPath:
207
+ return None
208
+
209
+ def find_all_paths(self, source: str, target: str) -> List[Any]:
210
+ return list(nx.all_simple_paths(self.graph, source=source, target=target))
211
+
212
+
213
+ def find_critical_path(self) -> tuple[List[Any], int, Dict[str, int]]:
214
+ """
215
+ Finds the critical path in the graph.
216
+ Returns:
217
+ A tuple containing:
218
+ - The critical path (a list of task names).
219
+ - The duration of the critical path.
220
+ - A dictionary of all paths and their durations.
221
+ """
222
+
223
+ all_paths = {}
224
+ for start_node in (v for k, v in self.nodes.items() if v.in_degrees == 0): # Start from nodes with 0 in-degree
225
+ for end_node in (v for k, v in self.nodes.items() if v.out_degrees == 0): # End at nodes with 0 out-degree
226
+ for edge in nx.all_simple_paths(self.graph, source=start_node.identifier, target=end_node.identifier):
227
+ edge_weight = sum(self.edges.get(item).weight if self.edges.get(item) else 0 for item in edge)
228
+ all_paths[tuple(edge)] = edge_weight
229
+
230
+ if not all_paths:
231
+ return [], 0, all_paths
232
+
233
+ critical_path = max(all_paths, key=all_paths.get)
234
+ critical_duration = all_paths[critical_path]
235
+
236
+ return list(critical_path), critical_duration, all_paths
237
+
238
+
239
+ def is_circled(self, node: Node) -> bool:
240
+ """Check if there's a path from the node to itself and return bool."""
241
+ try:
242
+ path = nx.shortest_path(self.graph, source=node, target=node)
243
+ return True if path else False
244
+ except nx.NetworkXNoPath:
245
+ return False
246
+
247
+
248
+ def visualize(self, title: str = "Graph Visualization", pos: Any = None, **graph_config):
249
+ pos = pos if pos else nx.spring_layout(self.graph, seed=42)
250
+ nx.draw(
251
+ self.graph,
252
+ pos,
253
+ with_labels=True, node_size=700, node_color="skyblue", font_size=10, font_color="black", arrowstyle='-|>', arrowsize=20, arrows=True,
254
+ **graph_config
255
+ )
256
+ edge_labels = {}
257
+ for u, v, data in self.graph.edges(data=True):
258
+ edge = self.edges.get((u,v))
259
+ if edge:
260
+ label_parts = []
261
+ if edge.type:
262
+ label_parts.append(f"Type: {edge.type}")
263
+ if edge.duration is not None:
264
+ label_parts.append(f"Duration: {edge.duration}")
265
+ if edge.lag is not None:
266
+ label_parts.append(f"Lag: {edge.lag}")
267
+ edge_labels[(u, v)] = "\n".join(label_parts) # Combine labels with newlines
268
+ nx.draw_networkx_edge_labels(self.graph, pos, edge_labels=edge_labels)
269
+ plt.title(title)
270
+ plt.show()
271
+
272
+
273
+
274
+ class TaskGraph(Graph):
275
+ id: UUID4 = Field(default_factory=uuid.uuid4, frozen=True)
276
+ should_reform: bool = Field(default=False)
277
+ status: Dict[str, TaskStatus] = Field(default_factory=dict, description="store identifier (str) and TaskStatus of all task_nodes")
278
+
279
+
280
+ def add_task(self, task: Node | Task) -> Node:
281
+ """Convert `task` to a Node object and add it to G"""
282
+ task_node = task if isinstance(task, Node) else Node(task=task)
283
+ self.add_node(task_node)
284
+ self.status[task_node.identifier] = TaskStatus.NOT_STARTED
285
+ return task_node
286
+
287
+
288
+ def add_dependency(
289
+ self, source_task_node_identifier: str, target_task_node_identifier: str, **edge_attributes
290
+ ) -> None:
291
+ """
292
+ Add an edge that connect task 1 (source) and task 2 (target) using task_node.name as an identifier
293
+ """
294
+
295
+ if not edge_attributes:
296
+ self._logger.log(level="error", message="Edge attributes are missing.", color="red")
297
+
298
+ edge = Edge()
299
+ for k in Edge.model_fields.keys():
300
+ v = edge_attributes.get(k, None)
301
+ if v:
302
+ setattr(edge, k, v)
303
+ else:
304
+ pass
305
+
306
+ self.add_edge(source_task_node_identifier, target_task_node_identifier, edge)
307
+
308
+
309
+ def set_task_status(self, identifier: str, status: TaskStatus) -> None:
310
+ if identifier in self.status:
311
+ self.status[identifier] = status
312
+ else:
313
+ self._logger.log(level="warning", message=f"Task '{identifier}' not found in the graph.", color="yellow")
314
+ pass
315
+
316
+ def get_task_status(self, identifier):
317
+ if identifier in self.status:
318
+ return self.status[identifier]
319
+ else:
320
+ self._logger.log(level="warning", message=f"Task '{identifier}' not found in the graph.", color="yellow")
321
+ return None
322
+
323
+
324
+ def visualize(self, layout: str = None):
325
+ try:
326
+ pos = nx.drawing.nx_agraph.graphviz_layout(self.graph, prog='dot') # 'dot', 'neato', 'fdp', 'sfdp'
327
+ except ImportError:
328
+ pos = nx.spring_layout(self.graph, seed=42) # REFINEME - layout
329
+
330
+ node_colors = list()
331
+ for k, v in self.graph.nodes.items():
332
+ status = self.get_task_status(identifier=k)
333
+ if status == TaskStatus.NOT_STARTED:
334
+ node_colors.append("skyblue")
335
+ elif status == TaskStatus.IN_PROGRESS:
336
+ node_colors.append("lightgreen")
337
+ elif status == TaskStatus.BLOCKED:
338
+ node_colors.append("lightcoral")
339
+ elif status == TaskStatus.COMPLETED:
340
+ node_colors.append("black")
341
+ elif status == TaskStatus.DELAYED:
342
+ node_colors.append("orange")
343
+ elif status == TaskStatus.ON_HOLD:
344
+ node_colors.append("yellow")
345
+ else:
346
+ node_colors.append("grey")
347
+
348
+ critical_paths, duration, paths = self.find_critical_path()
349
+ edge_colors = ['red' if (u, v) in zip(critical_paths, critical_paths[1:]) else 'black' for u, v in self.graph.edges()]
350
+ edge_widths = []
351
+
352
+ for k, v in self.edges.items():
353
+ # edge_weights = nx.get_edge_attributes(self.graph, 'weight')
354
+ # edge_colors.append(plt.cm.viridis(v.weight / max(edge_weights.values())))
355
+ edge_widths.append(v.weight * 0.5) # Width proportional to weight (adjust scaling as needed)
356
+
357
+ nx.draw(
358
+ self.graph, pos,
359
+ with_labels=True,
360
+ node_size=700,
361
+ node_color=node_colors,
362
+ font_size=10,
363
+ font_color="black",
364
+ edge_color=edge_colors,
365
+ width=edge_widths,
366
+ arrows=True,
367
+ arrowsize=20,
368
+ arrowstyle='-|>'
369
+ )
370
+
371
+ edge_labels = nx.get_edge_attributes(G=self.graph, name="edges")
372
+ nx.draw_networkx_edge_labels(self.graph, pos, edge_labels=edge_labels)
373
+
374
+ plt.title("Project Network Diagram")
375
+ self._save()
376
+ plt.show()
377
+
378
+
379
+ def _save(self, abs_file_path: str = None) -> None:
380
+ """
381
+ Save the graph image in the local directory.
382
+ """
383
+
384
+ try:
385
+ import os
386
+ project_root = os.path.abspath(os.getcwd())
387
+ abs_file_path = abs_file_path if abs_file_path else f"{project_root}/uploads"
388
+
389
+ os.makedirs(abs_file_path, exist_ok=True)
390
+
391
+ plt.savefig(f"{abs_file_path}/{str(self.id)}.png")
392
+
393
+ except Exception as e:
394
+ self._logger.log(level="error", message=f"Failed to save the graph {str(self.id)}: {str(e)}", color="red")
@@ -70,17 +70,16 @@ class EvaluationItem(BaseModel):
70
70
  else: return None
71
71
 
72
72
 
73
-
74
73
  class Evaluation(BaseModel):
75
74
  items: List[EvaluationItem] = []
76
- latency: int = Field(default=None, description="seconds")
75
+ latency: int = Field(default=None, description="job execution latency in seconds")
77
76
  tokens: int = Field(default=None, description="tokens consumed")
78
- responsible_agent: Any = Field(default=None, description="store agent instance that evaluates the outcome")
77
+ eval_by: Any = Field(default=None, description="stores agent object that evaluates the outcome")
79
78
 
80
79
  @model_validator(mode="after")
81
- def set_up_responsible_agent(self) -> Self:
80
+ def set_up_evaluator(self) -> Self:
82
81
  from versionhq.agent.inhouse_agents import vhq_task_evaluator
83
- self.responsible_agent = vhq_task_evaluator
82
+ self.eval_by = vhq_task_evaluator
84
83
  return self
85
84
 
86
85
 
@@ -88,7 +87,7 @@ class Evaluation(BaseModel):
88
87
  """
89
88
  Create and store evaluation results in the memory metadata
90
89
  """
91
- eval_by = self.responsible_agent.role if self.responsible_agent else None
90
+ eval_by = self.eval_by.role if self.eval_by else None
92
91
  score = self.aggregate_score
93
92
  eval_criteria = ", ".join([item.criteria for item in self.items]) if self.items else None
94
93
  suggestion = self.suggestion_summary
@@ -1,11 +1,11 @@
1
- from typing import List
1
+ from typing import List, Type
2
2
  from enum import Enum
3
3
 
4
4
  from pydantic import BaseModel
5
5
 
6
6
  from versionhq.task.model import Task
7
7
  from versionhq.agent.model import Agent
8
- from versionhq.team.model import Team, TeamMember, Formation
8
+ from versionhq.team.model import Team, Member, Formation
9
9
  from versionhq.agent.inhouse_agents import vhq_formation_planner
10
10
  from versionhq._utils import Logger
11
11
 
@@ -15,10 +15,10 @@ def form_agent_network(
15
15
  expected_outcome: str,
16
16
  agents: List[Agent] = None,
17
17
  context: str = None,
18
- formation: Formation = None
18
+ formation: Type[Formation] = None
19
19
  ) -> Team | None:
20
20
  """
21
- Make a formation of agents from the given task description, agents (optional), context (optional), and expected outcome (optional).
21
+ Make a formation of agents from the given task description, expected outcome, agents (optional), and context (optional).
22
22
  """
23
23
 
24
24
  if not task:
@@ -29,8 +29,37 @@ def form_agent_network(
29
29
  Logger(verbose=True).log(level="error", message="Missing expected outcome.", color="red")
30
30
  return None
31
31
 
32
+ if formation:
33
+ try:
34
+ match formation:
35
+ case Formation():
36
+ if formation == Formation.UNDEFINED:
37
+ formation = None
38
+ else:
39
+ pass
40
+
41
+ case str():
42
+ matched = [item for item in Formation._member_names_ if item == formation.upper()]
43
+ if matched:
44
+ formation = getattr(Formation, matched[0])
45
+ else:
46
+ # Formation._generate_next_value_(name=f"CUSTOM_{formation.upper()}", start=100, count=6, last_values=Formation.HYBRID.name)
47
+ Logger(verbose=True).log(level="warning", message=f"The formation {formation} is invalid. We'll recreate a valid formation.", color="yellow")
48
+ formation = None
49
+
50
+ case int() | float():
51
+ formation = Formation(int(formation))
52
+
53
+ case _:
54
+ Logger(verbose=True).log(level="warning", message=f"The formation {formation} is invalid. We'll recreate a valid formation.", color="yellow")
55
+ formation = None
56
+
57
+ except Exception as e:
58
+ Logger(verbose=True).log(level="warning", message=f"The formation {formation} is invalid: {str(e)}. We'll recreate a formation.", color="yellow")
59
+ formation = None
32
60
 
33
61
  try:
62
+ prompt_formation = formation.name if formation and isinstance(formation, Formation) else f"Select the best formation to effectively execute the tasks from the given Enum sets: {str(Formation.__dict__)}."
34
63
  class Outcome(BaseModel):
35
64
  formation: Enum
36
65
  agent_roles: list[str]
@@ -42,73 +71,78 @@ def form_agent_network(
42
71
  Create a team of specialized agents designed to automate the following task and deliver the expected outcome. Consider the necessary roles for each agent with a clear task description. If you think we neeed a leader to handle the automation, return a leader_agent role as well, but if not, leave the a leader_agent role blank.
43
72
  Task: {str(task)}
44
73
  Expected outcome: {str(expected_outcome)}
74
+ Formation: {prompt_formation}
45
75
  """,
46
76
  pydantic_output=Outcome
47
77
  )
48
78
 
49
- if formation:
50
- vhq_task.description += f"Select 1 formation you think the best from the given Enum sets: {str(Formation.__dict__)}"
51
-
52
79
  if agents:
53
80
  vhq_task.description += "Consider adding following agents in the formation: " + ", ".join([agent.role for agent in agents if isinstance(agent, Agent)])
54
81
 
55
82
  res = vhq_task.execute_sync(agent=vhq_formation_planner, context=context)
56
- formation_ = Formation.SUPERVISING
83
+ _formation = Formation.SUPERVISING
57
84
 
58
85
  if res.pydantic:
59
86
  formation_keys = [k for k, v in Formation._member_map_.items() if k == res.pydantic.formation.upper()]
60
87
 
61
88
  if formation_keys:
62
- formation_ = Formation[formation_keys[0]]
89
+ _formation = Formation[formation_keys[0]]
63
90
 
64
91
  created_agents = [Agent(role=item, goal=item) for item in res.pydantic.agent_roles]
65
92
  created_tasks = [Task(description=item) for item in res.pydantic.task_descriptions]
93
+
66
94
  team_tasks = []
67
95
  members = []
68
96
  leader = str(res.pydantic.leader_agent)
69
97
 
70
98
  for i in range(len(created_agents)):
71
- is_manager = bool(created_agents[i].role.lower() in leader.lower())
72
- member = TeamMember(agent=created_agents[i], is_manager=is_manager)
99
+ is_manager = bool(created_agents[i].role.lower() == leader.lower())
100
+ member = Member(agent=created_agents[i], is_manager=is_manager)
101
+
102
+ if len(created_tasks) >= i and created_tasks[i]:
103
+ member.tasks.append(created_tasks[i])
104
+
105
+ members.append(member)
73
106
 
74
- if len(created_tasks) >= i:
75
- member.task = created_tasks[i]
76
- members.append(member)
77
107
 
78
108
  if len(created_agents) < len(created_tasks):
79
- team_tasks.extend(created_tasks[len(created_agents) - 1:len(created_tasks)])
109
+ team_tasks.extend(created_tasks[len(created_agents):len(created_tasks)])
80
110
 
81
111
  members.sort(key=lambda x: x.is_manager == False)
82
- team = Team(members=members, formation=formation_)
112
+ team = Team( members=members, formation=_formation, team_tasks=team_tasks, planner_llm=vhq_formation_planner.llm)
83
113
  return team
84
114
 
85
115
  else:
86
- formation_keys = [k for k, v in Formation._member_map_.items() if k == res.json_dict["formation"].upper()]
116
+ res = res.json_dict
117
+ formation_keys = [k for k, v in Formation._member_map_.items() if k == res["formation"].upper()]
87
118
 
88
119
  if formation_keys:
89
- formation_ = Formation[formation_keys[0]]
120
+ _formation = Formation[formation_keys[0]]
121
+
122
+ created_agents = [Agent(role=item, goal=item) for item in res["agent_roles"]]
123
+ created_tasks = [Task(description=item) for item in res["task_descriptions"]]
90
124
 
91
- created_agents = [Agent(role=item, goal=item) for item in res.json_dict["agent_roles"]]
92
- created_tasks = [Task(description=item) for item in res.json_dict["task_descriptions"]]
93
125
  team_tasks = []
94
126
  members = []
95
- leader = str(res.json_dict["leader_agent"])
127
+ leader = str(res["leader_agent"])
96
128
 
97
129
  for i in range(len(created_agents)):
98
- is_manager = bool(created_agents[i].role.lower() in leader.lower())
99
- member = TeamMember(agent=created_agents[i], is_manager=is_manager)
130
+ is_manager = bool(created_agents[i].role.lower() == leader.lower())
131
+ member = Member(agent=created_agents[i], is_manager=is_manager)
132
+
133
+ if len(created_tasks) >= i and created_tasks[i]:
134
+ member.tasks.append(created_tasks[i])
100
135
 
101
- if len(created_tasks) >= i:
102
- member.task = created_tasks[i]
103
- members.append(member)
136
+ members.append(member)
104
137
 
105
138
  if len(created_agents) < len(created_tasks):
106
- team_tasks.extend(created_tasks[len(created_agents) - 1:len(created_tasks)])
139
+ team_tasks.extend(created_tasks[len(created_agents):len(created_tasks)])
107
140
 
108
- members.sort(key=lambda x: x.is_manager == True)
109
- team = Team(members=members, formation=formation_)
141
+ members.sort(key=lambda x: x.is_manager == False)
142
+ team = Team( members=members, formation=_formation, team_tasks=team_tasks, planner_llm=vhq_formation_planner.llm)
110
143
  return team
111
144
 
145
+
112
146
  except Exception as e:
113
- Logger(verbose=True).log(level="error", message=f"Failed to create an agent network - return None. You can try with solo agent. Error: {str(e)}", color="red")
147
+ Logger(verbose=True).log(level="error", message=f"Failed to create a agent network - return None. You can try with solo agent. Error: {str(e)}", color="red")
114
148
  return None
versionhq/task/model.py CHANGED
@@ -203,7 +203,7 @@ class TaskOutput(BaseModel):
203
203
  description=EVALUATE.format(task_description=task.description, task_output=self.raw, eval_criteria=str(item)),
204
204
  pydantic_output=EvaluationItem
205
205
  )
206
- res = task_eval.execute_sync(agent=self.evaluation.responsible_agent)
206
+ res = task_eval.execute_sync(agent=self.evaluation.eval_by)
207
207
 
208
208
  if res.pydantic:
209
209
  item = EvaluationItem(score=res.pydantic.score, suggestion=res.pydantic.suggestion, criteria=res.pydantic.criteria)
@@ -241,10 +241,7 @@ class TaskOutput(BaseModel):
241
241
 
242
242
  class Task(BaseModel):
243
243
  """
244
- Task to be executed by agents or teams.
245
- Each task must have a description.
246
- Default response is JSON string that strictly follows `response_fields` - and will be stored in TaskOuput.raw / json_dict.
247
- When `pydantic_output` is provided, we prioritize them and store raw (json string), json_dict, pydantic in the TaskOutput class.
244
+ A class that stores independent task information.
248
245
  """
249
246
 
250
247
  __hash__ = object.__hash__