versionhq 1.2.0.3__py3-none-any.whl → 1.2.1.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
@@ -16,7 +16,7 @@ from versionhq.clients.workflow.model import MessagingWorkflow, MessagingCompone
16
16
  from versionhq.knowledge.model import Knowledge, KnowledgeStorage
17
17
  from versionhq.knowledge.source import PDFKnowledgeSource, CSVKnowledgeSource, JSONKnowledgeSource, TextFileKnowledgeSource, ExcelKnowledgeSource, StringKnowledgeSource
18
18
  from versionhq.knowledge.source_docling import DoclingSource
19
- from versionhq.network.model import TaskStatus, TaskGraph, Node, Edge, DependencyType
19
+ from versionhq.graph.model import TaskStatus, TaskGraph, Node, Edge, DependencyType
20
20
  from versionhq.task.model import Task, TaskOutput, ResponseField, TaskExecutionType
21
21
  from versionhq.task.evaluate import Evaluation, EvaluationItem
22
22
  from versionhq.team.model import Team, TeamOutput, Formation, Member, TaskHandlingProcess
@@ -30,7 +30,7 @@ from versionhq.memory.model import ShortTermMemory,LongTermMemory, UserMemory, M
30
30
  from versionhq.task.formation import form_agent_network
31
31
 
32
32
 
33
- __version__ = "1.2.0.3"
33
+ __version__ = "1.2.1.0"
34
34
  __all__ = [
35
35
  "Agent",
36
36
 
@@ -38,7 +38,7 @@ class Printer:
38
38
  class Logger(BaseModel):
39
39
  """
40
40
  Control CLI messages.
41
- Color: red = error, yellow = warning, blue = info (from vhq), green = info (from third party)
41
+ Color: red = error, yellow = warning, blue = info (from vhq), green = info (from third parties)
42
42
  """
43
43
 
44
44
  verbose: bool = Field(default=True)
@@ -1,51 +1,37 @@
1
1
  import enum
2
2
  import uuid
3
+ import networkx as nx
4
+ import matplotlib.pyplot as plt
3
5
  from abc import ABC
4
6
  from typing import List, Any, Optional, Callable, Dict, Type, Tuple
5
7
 
6
- from pydantic import BaseModel, InstanceOf, Field, UUID4, PrivateAttr, field_validator, model_validator
8
+ from pydantic import BaseModel, InstanceOf, Field, UUID4, PrivateAttr, field_validator
7
9
  from pydantic_core import PydanticCustomError
8
10
 
9
11
  from versionhq.task.model import Task, TaskOutput
10
12
  from versionhq.agent.model import Agent
11
13
  from versionhq._utils.logger import Logger
12
14
 
13
- try:
14
- import networkx as nx
15
- except ImportError:
16
- try:
17
- import os
18
- os.system("uv add networkx --optional networkx")
19
- import networkx as nx
20
- except:
21
- try:
22
- import os
23
- os.system("pip install network --save")
24
- import networkx as nx
25
- except:
26
- raise ImportError("networkx is not installed. Please install it with: uv add networkx --optional networkx")
27
-
28
- try:
29
- import matplotlib.pyplot as plt
30
- except ImportError:
31
- try:
32
- import os
33
- os.system("uv add matplotlib --optional matplotlib")
34
- import matplotlib.pyplot as plt
35
- except:
36
- try:
37
- import os
38
- os.system("pip install matplotlib --save")
39
- import matplotlib.pyplot as plt
40
-
41
- except:
42
- raise ImportError("matplotlib is not installed. Please install it with: uv add matplotlib --optional matplotlib")
43
15
 
44
-
45
- import networkx as nx
46
- import matplotlib.pyplot as plt
16
+ def gen_network():
17
+ goal = "make a promo plan to increase gross sales of Temu, an ecommerce site with affordable items."
18
+ agent = Agent(
19
+ role="Network Generator", goal="draft a best graph with nodes (tasks) and edges", llm="gemini-2.0", maxit=1,
20
+ knowledge_sources=["https://en.wikipedia.org/wiki/Graph_theory", "https://www.temu.com", ]
21
+ )
22
+ class Outcome(BaseModel):
23
+ task_descriptions: list[str]
47
24
 
48
25
 
26
+ task = Task(
27
+ description=" 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. ",
28
+ pydantic_output=Outcome
29
+ )
30
+ task = Task(
31
+ description="Form best network with nodes and edges for the task described as following: Draft a promo plan for the client.",
32
+ pydantic_output=Edge
33
+ )
34
+ res = task.execute(agent=agent)
49
35
 
50
36
  class TaskStatus(enum.Enum):
51
37
  """
@@ -71,21 +57,21 @@ class DependencyType(enum.Enum):
71
57
 
72
58
 
73
59
 
74
- class TriggerEvent(enum.Enum):
75
- """
76
- Concise enumeration of key trigger events for task execution.
77
- """
78
- IMMEDIATE = 0 # execute immediately
79
- DEPENDENCIES_MET = 1 # All/required dependencies are satisfied
80
- RESOURCES_AVAILABLE = 2 # Necessary resources are available
81
- SCHEDULED_TIME = 3 # Scheduled start time or time window reached
82
- EXTERNAL_EVENT = 4 # Triggered by an external event/message
83
- DATA_AVAILABLE = 5 # Required data is available both internal/external
84
- APPROVAL_RECEIVED = 6 # Necessary approvals have been granted
85
- STATUS_CHANGED = 7 # Relevant task/system status has changed
86
- RULE_MET = 8 # A predefined rule or condition has been met
87
- MANUAL_TRIGGER = 9 # Manually initiated by a user
88
- ERROR_HANDLED = 10 # A previous error/exception has been handled
60
+ # class TriggerEvent(enum.Enum):
61
+ # """
62
+ # Concise enumeration of key trigger events for task execution.
63
+ # """
64
+ # IMMEDIATE = 0 # execute immediately
65
+ # DEPENDENCIES_MET = 1 # All/required dependencies are satisfied
66
+ # RESOURCES_AVAILABLE = 2 # Necessary resources are available
67
+ # SCHEDULED_TIME = 3 # Scheduled start time or time window reached
68
+ # EXTERNAL_EVENT = 4 # Triggered by an external event/message
69
+ # DATA_AVAILABLE = 5 # Required data is available both internal/external
70
+ # APPROVAL_RECEIVED = 6 # Necessary approvals have been granted
71
+ # STATUS_CHANGED = 7 # Relevant task/system status has changed
72
+ # RULE_MET = 8 # A predefined rule or condition has been met
73
+ # MANUAL_TRIGGER = 9 # Manually initiated by a user
74
+ # ERROR_HANDLED = 10 # A previous error/exception has been handled
89
75
 
90
76
 
91
77
 
@@ -96,8 +82,8 @@ class Node(BaseModel):
96
82
  id: UUID4 = Field(default_factory=uuid.uuid4, frozen=True)
97
83
  task: InstanceOf[Task] = Field(default=None)
98
84
  # trigger_event: TriggerEvent = Field(default=TriggerEvent.IMMEDIATE, description="store trigger event for starting the task execution")
99
- in_degree_nodes: List[Any] = Field(default=None, description="list of Node objects")
100
- out_degree_nodes: List[Any] = Field(default=None, description="list of Node objects")
85
+ in_degree_nodes: List[Any] = Field(default_factory=list, description="list of Node objects")
86
+ out_degree_nodes: List[Any] = Field(default_factory=list, description="list of Node objects")
101
87
  assigned_to: InstanceOf[Agent] = Field(default=None)
102
88
  status: TaskStatus = Field(default=TaskStatus.NOT_STARTED)
103
89
 
@@ -111,22 +97,6 @@ class Node(BaseModel):
111
97
  def is_independent(self) -> bool:
112
98
  return not self.in_degree_nodes and not self.out_degree_nodes
113
99
 
114
- @property
115
- def in_degrees(self) -> int:
116
- return len(self.in_degree_nodes) if self.in_degree_nodes else 0
117
-
118
- @property
119
- def out_degrees(self) -> int:
120
- return len(self.out_degree_nodes) if self.out_degree_nodes else 0
121
-
122
- @property
123
- def degrees(self) -> int:
124
- return self.in_degrees + self.out_degrees
125
-
126
- @property
127
- def identifier(self) -> str:
128
- """Unique identifier for the node"""
129
- return f"{str(self.id)}"
130
100
 
131
101
  def handle_task_execution(self, agent: Agent = None, context: str = None) -> TaskOutput | None:
132
102
  """
@@ -144,15 +114,33 @@ class Node(BaseModel):
144
114
  self.status = TaskStatus.COMPLETED if res else TaskStatus.ERROR
145
115
  return res
146
116
 
117
+
118
+ @property
119
+ def in_degrees(self) -> int:
120
+ return len(self.in_degree_nodes) if self.in_degree_nodes else 0
121
+
122
+ @property
123
+ def out_degrees(self) -> int:
124
+ return len(self.out_degree_nodes) if self.out_degree_nodes else 0
125
+
126
+ @property
127
+ def degrees(self) -> int:
128
+ return self.in_degrees + self.out_degrees
129
+
130
+ @property
131
+ def identifier(self) -> str:
132
+ """Unique identifier for the node"""
133
+ return f"{str(self.id)}"
134
+
147
135
  def __str__(self):
148
136
  return self.identifier
149
137
 
150
138
 
151
-
152
139
  class Edge(BaseModel):
153
140
  """
154
141
  A class to store an edge object that connects source and target nodes.
155
142
  """
143
+
156
144
  source: Node = Field(default=None)
157
145
  target: Node = Field(default=None)
158
146
 
@@ -186,28 +174,28 @@ class Edge(BaseModel):
186
174
  case DependencyType.FINISH_TO_START:
187
175
  """target starts after source finishes"""
188
176
  if self.source.status == TaskStatus.COMPLETED:
189
- return self.condition(**self.conditon_kwargs) if self.condtion else True
177
+ return self.condition(**self.conditon_kwargs) if self.condition else True
190
178
  else:
191
179
  return False
192
180
 
193
181
  case DependencyType.START_TO_START:
194
182
  """target starts when source starts"""
195
183
  if self.source.status != TaskStatus.NOT_STARTED:
196
- return self.condition(**self.conditon_kwargs) if self.condtion else True
184
+ return self.condition(**self.conditon_kwargs) if self.condition else True
197
185
  else:
198
186
  return False
199
187
 
200
188
  case DependencyType.FINISH_TO_FINISH:
201
189
  """target finish when source start"""
202
190
  if self.source.status != TaskStatus.COMPLETED:
203
- return self.condition(**self.conditon_kwargs) if self.condtion else True
191
+ return self.condition(**self.conditon_kwargs) if self.condition else True
204
192
  else:
205
193
  return False
206
194
 
207
195
  case DependencyType.START_TO_FINISH:
208
196
  """target finishes when source start"""
209
197
  if self.source.status == TaskStatus.IN_PROGRESS:
210
- return self.condition(**self.conditon_kwargs) if self.condtion else True
198
+ return self.condition(**self.conditon_kwargs) if self.condition else True
211
199
  else:
212
200
  return False
213
201
 
@@ -217,19 +205,20 @@ class Edge(BaseModel):
217
205
  Activates the edge to initiate task execution of the target node.
218
206
  """
219
207
 
220
- if not self.dependency_met():
221
- Logger(verbose=True).log(level="warning", message="Dependencies not met. We'll return None.", color="yellow")
222
- return None
223
-
224
208
  if not self.source or not self.target:
225
209
  Logger(verbose=True).log(level="warning", message="Cannot find source or target nodes. We'll return None.", color="yellow")
226
210
  return None
227
211
 
212
+ if not self.dependency_met():
213
+ Logger(verbose=True).log(level="warning", message="Dependencies not met. We'll see the source node status.", color="yellow")
214
+ return None
215
+
216
+
228
217
  if self.lag:
229
218
  import time
230
219
  time.sleep(self.lag)
231
220
 
232
- context = self.source.task.task_output.raw if self.data_transfer else None
221
+ context = self.source.task.output.raw if self.data_transfer else None
233
222
  res = self.target.handle_task_execution(context=context)
234
223
  return res
235
224
 
@@ -239,15 +228,14 @@ class Graph(ABC, BaseModel):
239
228
  An abstract class to store G using NetworkX library.
240
229
  """
241
230
 
242
- _logger: Logger = PrivateAttr(default_factory=lambda: Logger(verbose=True))
243
231
  directed: bool = Field(default=False, description="Whether the graph is directed")
244
232
  graph: Type[nx.Graph] = Field(default=None)
245
233
  nodes: Dict[str, InstanceOf[Node]] = Field(default_factory=dict, description="identifier: Node - for the sake of ")
246
234
  edges: Dict[str, InstanceOf[Edge]] = Field(default_factory=dict)
247
235
 
248
236
  def __init__(self, directed: bool = False, **kwargs):
249
- super().__init__(directed=directed, **kwargs)
250
- self.graph = nx.DiGraph() if self.directed else nx.Graph()
237
+ super().__init__(directed=directed, **kwargs)
238
+ self.graph = nx.DiGraph() if self.directed else nx.Graph()
251
239
 
252
240
  def _return_node_object(self, node_identifier) -> Node | None:
253
241
  return [v for k, v in self.nodes.items() if k == node_identifier][0] if [v for k, v in self.nodes.items() if k == node_identifier] else None
@@ -259,7 +247,9 @@ class Graph(ABC, BaseModel):
259
247
  def add_edge(self, source: str, target: str, edge: Edge) -> None:
260
248
  self.graph.add_edge(source, target, **edge.model_dump())
261
249
  edge.source = self._return_node_object(source)
250
+ edge.source.out_degree_nodes.append(target)
262
251
  edge.target = self._return_node_object(target)
252
+ edge.target.in_degree_nodes.append(source)
263
253
  self.edges[(source, target)] = edge
264
254
 
265
255
  def add_weighted_edges_from(self, edges):
@@ -274,6 +264,28 @@ class Graph(ABC, BaseModel):
274
264
  def get_out_degree(self, node: Node) -> int:
275
265
  return self.graph.out_degree(node)
276
266
 
267
+ def find_start_nodes(self) -> Tuple[Node]:
268
+ return [v for k, v in self.nodes.items() if v.in_degrees == 0 and v.out_degrees > 0]
269
+
270
+ def find_end_nodes(self) -> Tuple[Node]:
271
+ return [v for k, v in self.nodes.items() if v.out_degrees == 0 and v.in_degrees > 0]
272
+
273
+ def find_critical_end_node(self) -> Node | None:
274
+ """
275
+ Find a critical end node from all the end nodes to lead a conclusion of the entire graph.
276
+ """
277
+ end_nodes = self.find_end_nodes()
278
+ if not end_nodes:
279
+ return None
280
+
281
+ if len(end_nodes) == 1:
282
+ return end_nodes[0]
283
+
284
+ edges = [v for k, v in self.edges if isinstance(v, Edge) and v.source in end_nodes]
285
+ critical_edge = max(edges, key=lambda item: item['weight']) if edges else None
286
+ return critical_edge.target if critical_edge else None
287
+
288
+
277
289
  def find_path(self, source: Optional[str] | None, target: str, weight: Optional[Any] | None) -> Any:
278
290
  try:
279
291
  return nx.shortest_path(self.graph, source=source, target=target, weight=weight)
@@ -288,14 +300,14 @@ class Graph(ABC, BaseModel):
288
300
  Finds the critical path in the graph.
289
301
  Returns:
290
302
  A tuple containing:
291
- - The critical path (a list of task names).
303
+ - The critical path (a list of edge identifiers).
292
304
  - The duration of the critical path.
293
305
  - A dictionary of all paths and their durations.
294
306
  """
295
307
 
296
308
  all_paths = {}
297
- for start_node in (v for k, v in self.nodes.items() if v.in_degrees == 0): # Start from nodes with 0 in-degree
298
- for end_node in (v for k, v in self.nodes.items() if v.out_degrees == 0): # End at nodes with 0 out-degree
309
+ for start_node in self.find_start_nodes():
310
+ for end_node in self.find_end_nodes(): # End at nodes with 0 out-degree
299
311
  for edge in nx.all_simple_paths(self.graph, source=start_node.identifier, target=end_node.identifier):
300
312
  edge_weight = sum(self.edges.get(item).weight if self.edges.get(item) else 0 for item in edge)
301
313
  all_paths[tuple(edge)] = edge_weight
@@ -347,7 +359,26 @@ class TaskGraph(Graph):
347
359
  id: UUID4 = Field(default_factory=uuid.uuid4, frozen=True)
348
360
  should_reform: bool = Field(default=False)
349
361
  status: Dict[str, TaskStatus] = Field(default_factory=dict, description="store identifier (str) and TaskStatus of all task_nodes")
350
- outputs: Dict[str, TaskOutput] = Field(default_factory=dict, description="store identifire and TaskOutput")
362
+ outputs: Dict[str, TaskOutput] = Field(default_factory=dict, description="store node identifire and TaskOutput")
363
+ conclusion: Any = Field(default=None, description="store the final result of the entire task graph. critical path target/end node")
364
+
365
+
366
+ def _save(self, abs_file_path: str = None) -> None:
367
+ """
368
+ Save the graph image in the local directory.
369
+ """
370
+
371
+ try:
372
+ import os
373
+ project_root = os.path.abspath(os.getcwd())
374
+ abs_file_path = abs_file_path if abs_file_path else f"{project_root}/uploads"
375
+
376
+ os.makedirs(abs_file_path, exist_ok=True)
377
+ plt.savefig(f"{abs_file_path}/{str(self.id)}.png")
378
+
379
+ except Exception as e:
380
+ Logger().log(level="error", message=f"Failed to save the graph {str(self.id)}: {str(e)}", color="red")
381
+
351
382
 
352
383
  def add_task(self, task: Node | Task) -> Node:
353
384
  """Convert `task` to a Node object and add it to G"""
@@ -365,7 +396,7 @@ class TaskGraph(Graph):
365
396
  """
366
397
 
367
398
  if not edge_attributes:
368
- self._logger.log(level="error", message="Edge attributes are missing.", color="red")
399
+ Logger(verbose=True).log(level="error", message="Edge attributes are missing.", color="red")
369
400
 
370
401
  edge = Edge()
371
402
  for k in Edge.model_fields.keys():
@@ -382,14 +413,15 @@ class TaskGraph(Graph):
382
413
  if identifier in self.status:
383
414
  self.status[identifier] = status
384
415
  else:
385
- self._logger.log(level="warning", message=f"Task '{identifier}' not found in the graph.", color="yellow")
416
+ Logger().log(level="warning", message=f"Task '{identifier}' not found in the graph.", color="yellow")
386
417
  pass
387
418
 
419
+
388
420
  def get_task_status(self, identifier):
389
421
  if identifier in self.status:
390
422
  return self.status[identifier]
391
423
  else:
392
- self._logger.log(level="warning", message=f"Task '{identifier}' not found in the graph.", color="yellow")
424
+ Logger().log(level="warning", message=f"Task '{identifier}' not found in the graph.", color="yellow")
393
425
  return None
394
426
 
395
427
 
@@ -448,24 +480,6 @@ class TaskGraph(Graph):
448
480
  plt.show()
449
481
 
450
482
 
451
- def _save(self, abs_file_path: str = None) -> None:
452
- """
453
- Save the graph image in the local directory.
454
- """
455
-
456
- try:
457
- import os
458
- project_root = os.path.abspath(os.getcwd())
459
- abs_file_path = abs_file_path if abs_file_path else f"{project_root}/uploads"
460
-
461
- os.makedirs(abs_file_path, exist_ok=True)
462
-
463
- plt.savefig(f"{abs_file_path}/{str(self.id)}.png")
464
-
465
- except Exception as e:
466
- self._logger.log(level="error", message=f"Failed to save the graph {str(self.id)}: {str(e)}", color="red")
467
-
468
-
469
483
  def activate(self, target_node_identifier: Optional[str] = None) -> Tuple[TaskOutput | None, Dict[str, TaskOutput]]:
470
484
  """
471
485
  Starts to execute all nodes in the graph or a specific node if the target is given, following the given conditons of the edge obeject.
@@ -473,7 +487,7 @@ class TaskGraph(Graph):
473
487
  """
474
488
  if target_node_identifier:
475
489
  if not [k for k in self.nodes.keys() if k == target_node_identifier]:
476
- self._logger.log(level="error", message=f"The node {str(target_node_identifier)} is not in the graph.", color="red")
490
+ Logger().log(level="error", message=f"The node {str(target_node_identifier)} is not in the graph.", color="red")
477
491
  return None
478
492
 
479
493
  # find a shortest path to each in-degree node of the node and see if dependency met.
@@ -494,18 +508,62 @@ class TaskGraph(Graph):
494
508
  return res, self.outputs
495
509
 
496
510
  else:
497
- if not self.edges or self.nodes:
498
- self._logger.log(level="error", message="Needs at least 2 nodes and 1 edge to activate the graph. We'll return None.", color="red")
511
+ if not self.edges or not self.nodes:
512
+ Logger().log(level="error", message="TaskGraph needs at least 2 nodes and 1 edge to activate. We'll return None.", color="red")
499
513
  return None
500
514
 
501
- for edge in self.edges:
502
- res = edge.activate()
503
- if not res:
504
- break
515
+ start_nodes = self.find_start_nodes()
516
+ end_nodes = self.find_end_nodes()
517
+ critical_end_node = self.find_critical_end_node()
518
+ critical_path, _, _ = self.find_critical_path()
519
+ res = None
520
+
521
+ # When all nodes are completed, return the output of the critical end node or end node.
522
+ if end_nodes and len([node for node in end_nodes if node.status == TaskStatus.COMPLETED]) == len(end_nodes):
523
+ if critical_end_node:
524
+ return critical_end_node.task.output, self.outputs
525
+
526
+ else:
527
+ return [v.task.output.raw for k, v in end_nodes.items()][0], self.outputs
528
+
529
+ # Else, execute nodes connected with the critical_path
530
+ elif critical_path:
531
+ for item in critical_path:
532
+ edge = [v for k, v in self.edges.items() if item in k]
533
+
534
+ if edge:
535
+ edge = edge[0]
505
536
 
506
- node_identifier = edge.target.identifier
507
- self.outputs.update({ node_identifier: res })
508
- self.status.update({ node_identifier: edge.target.status })
537
+ if edge.target.status == TaskStatus.COMPLETED:
538
+ res = edge.target.output
509
539
 
510
- last_task_output = [v for v in self.outputs.values()][len([v for v in self.outputs.values()]) - 1] if [v for v in self.outputs.values()] else None
511
- return last_task_output, self.outputs
540
+ else:
541
+ res = edge.activate()
542
+ node_identifier = edge.target.identifier
543
+ self.outputs.update({ node_identifier: res })
544
+ self.status.update({ node_identifier: edge.target.status })
545
+
546
+ if not res and start_nodes:
547
+ for node in start_nodes:
548
+ res = node.handle_task_execution()
549
+ self.outputs.update({ node.identifier: res })
550
+ self.status.update({ node.identifier: node.status })
551
+
552
+ # if no critical paths in the graph, simply start from the start nodes.
553
+ elif start_nodes:
554
+ for node in start_nodes:
555
+ res = node.handle_task_execution()
556
+ self.outputs.update({ node.identifier: res })
557
+ self.status.update({ node.identifier: node.status })
558
+
559
+
560
+ # if none of above is applicable, try to activate all the edges.
561
+ else:
562
+ for k, edge in self.edges.items():
563
+ res = edge.activate()
564
+ node_identifier = edge.target.identifier
565
+ self.outputs.update({ node_identifier: res })
566
+ self.status.update({ node_identifier: edge.target.status })
567
+
568
+ # last_task_output = [v for v in self.outputs.values()][len([v for v in self.outputs.values()]) - 1] if [v for v in self.outputs.values()] else None
569
+ return res, self.outputs
@@ -265,7 +265,7 @@ class PDFKnowledgeSource(BaseFileKnowledgeSource):
265
265
  import os
266
266
  os.system("uv add pdfplumber --optional pdfplumber")
267
267
  except:
268
- raise ImportError("pdfplumber is not installed. Please install it with: uv add pdfplumber")
268
+ raise ImportError("pdfplumber is not installed. Please install it with: uv add pdfplumber --optional pdfplumber")
269
269
 
270
270
 
271
271
  def add(self) -> None:
@@ -68,7 +68,7 @@ def form_agent_network(
68
68
 
69
69
  vhq_task = Task(
70
70
  description=f"""
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.
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. When you have a leader_agent, the formation must be SUPERVISING or HYBRID.
72
72
  Task: {str(task)}
73
73
  Expected outcome: {str(expected_outcome)}
74
74
  Formation: {prompt_formation}
@@ -109,7 +109,7 @@ def form_agent_network(
109
109
  team_tasks.extend(created_tasks[len(created_agents):len(created_tasks)])
110
110
 
111
111
  members.sort(key=lambda x: x.is_manager == False)
112
- team = Team( members=members, formation=_formation, team_tasks=team_tasks, planner_llm=vhq_formation_planner.llm)
112
+ team = Team(members=members, formation=_formation, team_tasks=team_tasks, planner_llm=vhq_formation_planner.llm)
113
113
  return team
114
114
 
115
115
  else:
versionhq/task/model.py CHANGED
@@ -149,9 +149,16 @@ class ResponseField(BaseModel):
149
149
  return value
150
150
 
151
151
 
152
+ def _annotate(self, value: Any) -> Annotated:
153
+ """
154
+ Address Pydantic's `create_model`
155
+ """
156
+ return Annotated[self.type, value] if isinstance(value, self.type) else Annotated[str, str(value)]
157
+
158
+
152
159
  def create_pydantic_model(self, result: Dict, base_model: InstanceOf[BaseModel] | Any) -> Any:
153
160
  """
154
- Create a Pydantic model from the given result
161
+ Create a Pydantic model from the given result.
155
162
  """
156
163
  for k, v in result.items():
157
164
  if k is not self.title:
@@ -164,14 +171,6 @@ class ResponseField(BaseModel):
164
171
  return base_model
165
172
 
166
173
 
167
- def _annotate(self, value: Any) -> Annotated:
168
- """
169
- Address Pydantic's `create_model`
170
- """
171
- return Annotated[self.type, value] if isinstance(value, self.type) else Annotated[str, str(value)]
172
-
173
-
174
-
175
174
  class TaskOutput(BaseModel):
176
175
  """
177
176
  A class to store the final output of the given task in raw (string), json_dict, and pydantic class formats.
versionhq/team/model.py CHANGED
@@ -27,33 +27,25 @@ def match_type(self, obj):
27
27
 
28
28
  GenerateSchema.match_type = match_type
29
29
  warnings.filterwarnings("ignore", category=SyntaxWarning, module="pysbd")
30
- load_dotenv(override=True)
31
-
32
- # agentops = None
33
- # if os.environ.get("AGENTOPS_API_KEY"):
34
- # try:
35
- # import agentops # type: ignore
36
- # except ImportError:
37
- # pass
38
-
39
30
 
40
31
 
41
32
  class Formation(str, Enum):
42
33
  UNDEFINED = 0
43
34
  SOLO = 1
44
35
  SUPERVISING = 2
45
- NETWORK = 3
36
+ SQUAD = 3
46
37
  RANDOM = 4
47
38
  HYBRID = 10
48
39
 
49
40
 
50
41
  class TaskHandlingProcess(str, Enum):
51
42
  """
52
- Class representing the different processes that can be used to tackle multiple tasks.
43
+ A class representing task handling processes to tackle multiple tasks.
44
+ When the team has multiple tasks that connect with edges, follow the edge conditions.
53
45
  """
54
- sequential = "sequential"
55
- hierarchical = "hierarchical"
56
- consensual = "consensual"
46
+ SEQUENT = 1
47
+ HIERARCHY = 2
48
+ CONSENSUAL = 3 # either from managers or peers or (human) - most likely controlled by edge
57
49
 
58
50
 
59
51
  class TeamOutput(TaskOutput):
@@ -63,19 +55,16 @@ class TeamOutput(TaskOutput):
63
55
 
64
56
  team_id: UUID4 = Field(default_factory=uuid.uuid4, frozen=True, description="store the team ID that generate the TeamOutput")
65
57
  task_description: str = Field(default=None, description="store initial request (task description) from the client")
66
- task_outputs: list[TaskOutput] = Field(default=list, description="store outputs of all tasks that the team has executed")
67
- token_usage: UsageMetrics = Field(default=dict, description="processed token summary")
68
-
58
+ task_outputs: list[TaskOutput] = Field(default=list, description="store TaskOutput objects of all tasks that the team has executed")
59
+ # token_usage: UsageMetrics = Field(default=dict, description="processed token summary")
69
60
 
70
61
  def return_all_task_outputs(self) -> List[Dict[str, Any]]:
71
62
  res = [output.json_dict for output in self.task_outputs]
72
63
  return res
73
64
 
74
-
75
65
  def __str__(self):
76
66
  return (str(self.pydantic) if self.pydantic else str(self.json_dict) if self.json_dict else self.raw)
77
67
 
78
-
79
68
  def __getitem__(self, key):
80
69
  if self.pydantic and hasattr(self.pydantic, key):
81
70
  return getattr(self.pydantic, key)
@@ -88,7 +77,7 @@ class TeamOutput(TaskOutput):
88
77
 
89
78
  class Member(BaseModel):
90
79
  """
91
- A class to store a member in the network and connect the agent as a member with tasks and sharable settings.
80
+ A class to store a member in the team and connect the agent as a member with tasks and memory/knowledge share settings.
92
81
  """
93
82
  agent: Agent | None = Field(default=None)
94
83
  is_manager: bool = Field(default=False)
@@ -103,7 +92,8 @@ class Member(BaseModel):
103
92
 
104
93
  class Team(BaseModel):
105
94
  """
106
- A class to store agent network that shares knowledge, memory and tools among the members.
95
+ A class to store a team with members and tasks.
96
+ Tasks can be 1. multiple individual tasks, 2. multiple dependant tasks connected via Graph, and 3. hybrid.
107
97
  """
108
98
 
109
99
  __hash__ = object.__hash__
@@ -123,7 +113,7 @@ class Team(BaseModel):
123
113
 
124
114
  # task execution rules
125
115
  prompt_file: str = Field(default="", description="absolute path to the prompt json file")
126
- process: TaskHandlingProcess = Field(default=TaskHandlingProcess.sequential)
116
+ process: TaskHandlingProcess = Field(default=TaskHandlingProcess.SEQUENT)
127
117
 
128
118
  # callbacks
129
119
  pre_launch_callbacks: List[Callable[[Optional[Dict[str, Any]]], Optional[Dict[str, Any]]]] = Field(
@@ -174,21 +164,19 @@ class Team(BaseModel):
174
164
 
175
165
 
176
166
  @model_validator(mode="after")
177
- def check_manager_llm(self):
167
+ def check_manager(self):
178
168
  """
179
169
  Check if the team has a manager
180
170
  """
181
-
182
- if self.process == TaskHandlingProcess.hierarchical or self.formation == Formation.SUPERVISING:
171
+ if self.process == TaskHandlingProcess.HIERARCHY or self.formation == Formation.SUPERVISING:
183
172
  if not self.managers:
184
173
  self._logger.log(level="error", message="The process or formation created needs at least 1 manager agent.", color="red")
185
- raise PydanticCustomError(
186
- "missing_manager_llm_or_manager","Attribute `manager_llm` or `manager` is required when using hierarchical process.", {})
174
+ raise PydanticCustomError("missing_manager", "`manager` is required when using hierarchical process.", {})
187
175
 
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.", {})
176
+ ## comment out for the formation flexibilities
177
+ # if self.managers and (self.manager_tasks is None or self.team_tasks is None):
178
+ # self._logger.log(level="error", message="The manager is idling. At least 1 task needs to be assigned to the manager.", color="red")
179
+ # raise PydanticCustomError("missing_manager_task", "manager needs to have at least one manager task or team task.", {})
192
180
 
193
181
  return self
194
182
 
@@ -198,7 +186,7 @@ class Team(BaseModel):
198
186
  """
199
187
  Sequential task processing without any team tasks require a task-agent pairing.
200
188
  """
201
- if self.process == TaskHandlingProcess.sequential and self.team_tasks is None:
189
+ if self.process == TaskHandlingProcess.SEQUENT and self.team_tasks is None:
202
190
  for task in self.tasks:
203
191
  if not [member.task == task for member in self.members]:
204
192
  self._logger.log(level="error", message=f"The following task needs a dedicated agent to be assinged: {task.description}", color="red")
@@ -278,31 +266,6 @@ class Team(BaseModel):
278
266
  return task_outputs
279
267
 
280
268
 
281
- def _create_team_output(self, task_outputs: List[TaskOutput], lead_task_output: TaskOutput = None) -> TeamOutput:
282
- """
283
- Take the output of the first task or the lead task output as the team output `raw` value.
284
- Note that `tasks` are already sorted by the importance.
285
- """
286
-
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")
290
-
291
- final_task_output = lead_task_output if lead_task_output is not None else task_outputs[0] #! REFINEME
292
- # final_string_output = final_task_output.raw
293
- # self._finish_execution(final_string_output)
294
- token_usage = self._calculate_usage_metrics()
295
-
296
- return TeamOutput(
297
- team_id=self.id,
298
- raw=final_task_output.raw,
299
- json_dict=final_task_output.json_dict,
300
- pydantic=final_task_output.pydantic,
301
- task_outputs=task_outputs,
302
- token_usage=token_usage,
303
- )
304
-
305
-
306
269
  def _calculate_usage_metrics(self) -> UsageMetrics:
307
270
  """
308
271
  Calculate and return the usage metrics that consumed by the team.
@@ -371,14 +334,29 @@ class Team(BaseModel):
371
334
  lead_task_output = task_output
372
335
 
373
336
  task_outputs.append(task_output)
374
- # self._process_task_result(task, task_output)
375
337
  task._store_execution_log(task_index, was_replayed, self._inputs)
376
338
 
377
339
 
378
340
  if futures:
379
341
  task_outputs = self._process_async_tasks(futures, was_replayed)
380
342
 
381
- return self._create_team_output(task_outputs, lead_task_output)
343
+ if not task_outputs:
344
+ self._logger.log(level="error", message="Missing task outcomes. Failed to launch the task.", color="red")
345
+ raise ValueError("Failed to launch tasks")
346
+
347
+ final_task_output = lead_task_output if lead_task_output is not None else task_outputs[0] #! REFINEME
348
+
349
+ # token_usage = self._calculate_usage_metrics() #! combining with Eval
350
+
351
+ return TeamOutput(
352
+ team_id=self.id,
353
+ raw=final_task_output.raw,
354
+ json_dict=final_task_output.json_dict,
355
+ pydantic=final_task_output.pydantic,
356
+ task_outputs=task_outputs,
357
+ # token_usage=token_usage,
358
+ )
359
+
382
360
 
383
361
 
384
362
  def launch(self, kwargs_pre: Optional[Dict[str, str]] = None, kwargs_post: Optional[Dict[str, Any]] = None) -> TeamOutput:
@@ -412,7 +390,7 @@ class Team(BaseModel):
412
390
  agent.callbacks.append(self.step_callback)
413
391
 
414
392
  if self.process is None:
415
- self.process = TaskHandlingProcess.sequential
393
+ self.process = TaskHandlingProcess.SEQUENT
416
394
 
417
395
  result = self._execute_tasks(self.tasks)
418
396
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: versionhq
3
- Version: 1.2.0.3
3
+ Version: 1.2.1.0
4
4
  Summary: An agentic orchestration framework for building agent networks that handle task automation.
5
5
  Author-email: Kuriko Iwai <kuriko@versi0n.io>
6
6
  License: MIT License
@@ -64,6 +64,8 @@ Requires-Dist: chromadb>=0.6.3
64
64
  Requires-Dist: wheel>=0.45.1
65
65
  Requires-Dist: envoy>=0.0.3
66
66
  Requires-Dist: composio-core==0.7.0
67
+ Requires-Dist: networkx>=3.4.2
68
+ Requires-Dist: matplotlib>=3.10.0
67
69
  Provides-Extra: docling
68
70
  Requires-Dist: docling>=2.17.0; extra == "docling"
69
71
  Provides-Extra: mem0ai
@@ -76,17 +78,13 @@ Provides-Extra: numpy
76
78
  Requires-Dist: numpy>=1.26.4; extra == "numpy"
77
79
  Provides-Extra: pygraphviz
78
80
  Requires-Dist: pygraphviz>=1.14; extra == "pygraphviz"
79
- Provides-Extra: networkx
80
- Requires-Dist: networkx>=3.4.2; extra == "networkx"
81
- Provides-Extra: matplotlib
82
- Requires-Dist: matplotlib>=3.10.0; extra == "matplotlib"
83
81
 
84
82
  # Overview
85
83
 
86
- [![DL](https://img.shields.io/badge/Download-15K+-red)](https://clickpy.clickhouse.com/dashboard/versionhq)
84
+ [![DL](https://img.shields.io/badge/Download-17K+-red)](https://clickpy.clickhouse.com/dashboard/versionhq)
87
85
  ![MIT license](https://img.shields.io/badge/License-MIT-green)
88
86
  [![Publisher](https://github.com/versionHQ/multi-agent-system/actions/workflows/publish.yml/badge.svg)](https://github.com/versionHQ/multi-agent-system/actions/workflows/publish.yml)
89
- ![PyPI](https://img.shields.io/badge/PyPI-v1.2.0+-blue)
87
+ ![PyPI](https://img.shields.io/badge/PyPI-v1.2.1+-blue)
90
88
  ![python ver](https://img.shields.io/badge/Python-3.11/3.12-purple)
91
89
  ![pyenv ver](https://img.shields.io/badge/pyenv-2.5.0-orange)
92
90
 
@@ -108,9 +106,10 @@ A Python framework for agentic orchestration that handles complex task automatio
108
106
  <!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
109
107
 
110
108
  - [Key Features](#key-features)
111
- - [Agent formation](#agent-formation)
109
+ - [Agent Network](#agent-network)
112
110
  - [Graph Theory Concept](#graph-theory-concept)
113
- - [Agent optimization](#agent-optimization)
111
+ - [Task Graph](#task-graph)
112
+ - [Optimization](#optimization)
114
113
  - [Quick Start](#quick-start)
115
114
  - [Package installation](#package-installation)
116
115
  - [Forming a agent network](#forming-a-agent-network)
@@ -141,14 +140,14 @@ A Python framework for agentic orchestration that handles complex task automatio
141
140
  Agents are model-agnostic, and will improve task output, while oprimizing token cost and job latency, by sharing their memory, knowledge base, and RAG tools with other agents in the network.
142
141
 
143
142
 
144
- ### Agent formation
143
+ ### Agent Network
145
144
 
146
145
  Agents adapt their formation based on task complexity.
147
146
 
148
147
  You can specify a desired formation or allow the agents to determine it autonomously (default).
149
148
 
150
149
 
151
- | | **Solo Agent** | **Supervising** | **Network** | **Random** |
150
+ | | **Solo Agent** | **Supervising** | **Squad** | **Random** |
152
151
  | :--- | :--- | :--- | :--- | :--- |
153
152
  | **Formation** | <img src="https://res.cloudinary.com/dfeirxlea/image/upload/v1738818211/pj_m_agents/rbgxttfoeqqis1ettlfz.png" alt="solo" width="200"> | <img src="https://res.cloudinary.com/dfeirxlea/image/upload/v1738818211/pj_m_agents/zhungor3elxzer5dum10.png" alt="solo" width="200"> | <img src="https://res.cloudinary.com/dfeirxlea/image/upload/v1738818211/pj_m_agents/dnusl7iy7kiwkxwlpmg8.png" alt="solo" width="200"> | <img src="https://res.cloudinary.com/dfeirxlea/image/upload/v1738818211/pj_m_agents/sndpczatfzbrosxz9ama.png" alt="solo" width="200"> |
154
153
  | **Usage** | <ul><li>A single agent with tools, knowledge, and memory.</li><li>When self-learning mode is on - it will turn into **Random** formation.</li></ul> | <ul><li>Leader agent gives directions, while sharing its knowledge and memory.</li><li>Subordinates can be solo agents or networks.</li></ul> | <ul><li>Share tasks, knowledge, and memory among network members.</li></ul> | <ul><li>A single agent handles tasks, asking help from other agents without sharing its memory or knowledge.</li></ul> |
@@ -194,7 +193,19 @@ task_graph.visualize()
194
193
 
195
194
  <hr />
196
195
 
197
- ### Agent optimization
196
+ ### Task Graph
197
+
198
+ A `TaskGraph` represents tasks as `nodes` and their execution dependencies as `edges`, automating rule-based execution.
199
+
200
+ `Agent Networks` can handle `TaskGraph` objects by optimizing their formations.
201
+
202
+ The following example demonstrates a simple concept of a `supervising` agent network handling a task graph with three tasks and one critical edge.
203
+
204
+ <img src="https://res.cloudinary.com/dfeirxlea/image/upload/v1739337639/pj_m_home/zfg4ccw1m1ww1tpnb0pa.png">
205
+
206
+ <hr />
207
+
208
+ ### Optimization
198
209
 
199
210
  Agents are model-agnostic and can handle multiple tasks, leveraging their own and their peers' knowledge sources, memories, and tools.
200
211
 
@@ -1,7 +1,7 @@
1
- versionhq/__init__.py,sha256=RZZvNni2Slrj-OaK0N9me8CaK5AvO4Ui566sGeF1oVc,2783
1
+ versionhq/__init__.py,sha256=OvDVdnABXYcdjKCYL58tOw2duy59rQ2lxc7e1AoxUjc,2781
2
2
  versionhq/_utils/__init__.py,sha256=dzoZr4cBlh-2QZuPzTdehPUCe9lP1dmRtauD7qTjUaA,158
3
3
  versionhq/_utils/i18n.py,sha256=TwA_PnYfDLA6VqlUDPuybdV9lgi3Frh_ASsb_X8jJo8,1483
4
- versionhq/_utils/logger.py,sha256=j9SlQPIefdVUlwpGfJY83E2BUt1ejWgZ2M2I8aMyQ3c,1579
4
+ versionhq/_utils/logger.py,sha256=2qkODR6y8ApOoMjQZVecTboXvCLrMy2v_mTSOnNMKFY,1581
5
5
  versionhq/_utils/process_config.py,sha256=jbPGXK2Kb4iyCugJ3FwRJuU0wL5Trq2x4xFQz2uOyFY,746
6
6
  versionhq/_utils/usage_metrics.py,sha256=NXF18dn5NNvGK7EsQ4AAghpR8ppYOjMx6ABenLLHnmM,1066
7
7
  versionhq/_utils/vars.py,sha256=bZ5Dx_bFKlt3hi4-NNGXqdk7B23If_WaTIju2fiTyPQ,57
@@ -20,11 +20,13 @@ versionhq/clients/product/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJW
20
20
  versionhq/clients/product/model.py,sha256=3w__pug9XRe4LIm9wX8C8WKqi40r081Eb1q2vWk9UaU,3694
21
21
  versionhq/clients/workflow/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
22
22
  versionhq/clients/workflow/model.py,sha256=FNftenLLoha0bkivrjId32awLHAkBwIT8iNljdic_bw,6003
23
+ versionhq/graph/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
24
+ versionhq/graph/model.py,sha256=k4NbNn0D3r3ujfkQHOsfiiEJRxZx7iK5DczBnOyn1ik,23912
23
25
  versionhq/knowledge/__init__.py,sha256=qW7IgssTA4_bFFV9ziOcYRfGjlq1c8bkb-HnfWknpuQ,567
24
26
  versionhq/knowledge/_utils.py,sha256=YWRF8U533cfZes_gZqUvdj-K24MD2ri1R0gjc_aPYyc,402
25
27
  versionhq/knowledge/embedding.py,sha256=KfHc__1THxb5jrg1EMrF-v944RDuIr2hE0l-MtM3Bp0,6826
26
28
  versionhq/knowledge/model.py,sha256=w29mrJv1kiznCh4P4yJMUQxIuyRw1Sk0XYtBXzCxaG4,1786
27
- versionhq/knowledge/source.py,sha256=30VXsl3uHdM0wK0Dik3XfFxpNpEiy539PBNBvg0Y4-g,13609
29
+ versionhq/knowledge/source.py,sha256=HxlwK7E5qWMknJeswnFCCdTIOB01hV4JrEiMGkp7j1E,13631
28
30
  versionhq/knowledge/source_docling.py,sha256=hhHn3rS4KVsFKEPWcfllM8VxSL86PckZdAHDZNQNOq8,5411
29
31
  versionhq/knowledge/storage.py,sha256=7oxCg3W9mFjYH1YmuH9kFtTbNxquzYFjuUjd_TlsB9E,8170
30
32
  versionhq/llm/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -33,8 +35,6 @@ versionhq/llm/model.py,sha256=wlzDUMEyIOm808d1vzqu9gmbB4ch-s_EUvwFR60gR80,17177
33
35
  versionhq/memory/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
34
36
  versionhq/memory/contextual_memory.py,sha256=tCsOOAUnfrOL7YiakqGoi3uShzzS870TmGnlGd3z_A4,3556
35
37
  versionhq/memory/model.py,sha256=4wow2O3UuMZ0AbC2NyxddGZac3-_GjNZbK9wsA015NA,8145
36
- versionhq/network/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
37
- versionhq/network/model.py,sha256=AfakEac4twHIIextUzrV5S-9qzrpaEEoaku6b9cg74A,20612
38
38
  versionhq/storage/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
39
39
  versionhq/storage/base.py,sha256=p-Jas0fXQan_qotnRD6seQxrT2lj-uw9-SmHQhdppcs,355
40
40
  versionhq/storage/ltm_sqlite_storage.py,sha256=wdUiuwHfJocdk0UGqyrdU4S5Nae1rgsoRNu3LWmGFcI,3951
@@ -44,14 +44,14 @@ versionhq/storage/task_output_storage.py,sha256=E1t_Fkt78dPYIOl3MP7LfQ8oGtjlzxBu
44
44
  versionhq/storage/utils.py,sha256=ByYXPoEIGJYLUqz-DWjbCAnneNrH1otiYbp12SCILpM,747
45
45
  versionhq/task/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
46
46
  versionhq/task/evaluate.py,sha256=WdUgjbZL62XrxyWe5MTz29scfzwmuAHGxJ7GvAB8Fmk,3954
47
- versionhq/task/formation.py,sha256=iHhW2webMirYC78G8kHpaMTYjCdq7c3yFD2u2Egd8Eo,6350
47
+ versionhq/task/formation.py,sha256=8YQxGZ7cs7Q-a7UnwheHxXei9LSt1AWpCQmQiN1ZkgI,6423
48
48
  versionhq/task/formatter.py,sha256=N8Kmk9vtrMtBdgJ8J7RmlKNMdZWSmV8O1bDexmCWgU0,643
49
49
  versionhq/task/log_handler.py,sha256=LT7YnO7gcPR9IZS7eRvMjnHh8crMBFtqduxd8dxIbkk,1680
50
- versionhq/task/model.py,sha256=zAnZ-_5YKZRs43gpM2AL9IVtttq5GqGfqfCMj_Oyz9g,30624
50
+ versionhq/task/model.py,sha256=aXsFRhpmIyfoYpF8BU2cBU4nh4OsaSxc9D98DJ9auqE,30624
51
51
  versionhq/task/structured_response.py,sha256=4q-hQPu7oMMHHXEzh9YW4SJ7N5eCZ7OfZ65juyl_jCI,5000
52
52
  versionhq/task/TEMPLATES/Description.py,sha256=V-4kh8xpQTKOcDMi2xnuP-fcNk6kuoz1_5tYBlDLQWQ,420
53
53
  versionhq/team/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
54
- versionhq/team/model.py,sha256=kxrjF3RFGO9l3IsJIffI1nqgl_Vh5RLuhqrwIqbEoBY,19214
54
+ versionhq/team/model.py,sha256=88l2lxlAZGIauRJr607IMbV7jP3FIvveVHrT7O4AVpk,18614
55
55
  versionhq/team/team_planner.py,sha256=Zc3zvhPR7T0O8RQoq9CKOrvrll4klemY1rONLFeJ96M,3663
56
56
  versionhq/tool/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
57
57
  versionhq/tool/cache_handler.py,sha256=iL8FH7X0G-cdT0uhJwzuhLDaadTXOdfybZcDy151-es,1085
@@ -60,8 +60,8 @@ versionhq/tool/composio_tool_vars.py,sha256=FvBuEXsOQUYnN7RTFxT20kAkiEYkxWKkiVtg
60
60
  versionhq/tool/decorator.py,sha256=C4ZM7Xi2gwtEMaSeRo-geo_g_MAkY77WkSLkAuY0AyI,1205
61
61
  versionhq/tool/model.py,sha256=PO4zNWBZcJhYVur381YL1dy6zqurio2jWjtbxOxZMGI,12194
62
62
  versionhq/tool/tool_handler.py,sha256=2m41K8qo5bGCCbwMFferEjT-XZ-mE9F0mDUOBkgivOI,1416
63
- versionhq-1.2.0.3.dist-info/LICENSE,sha256=cRoGGdM73IiDs6nDWKqPlgSv7aR4n-qBXYnJlCMHCeE,1082
64
- versionhq-1.2.0.3.dist-info/METADATA,sha256=PCPwZZLO_mShcOc9JS8M41tukqGVlk_cdYtFvU-OJ5s,21462
65
- versionhq-1.2.0.3.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
66
- versionhq-1.2.0.3.dist-info/top_level.txt,sha256=DClQwxDWqIUGeRJkA8vBlgeNsYZs4_nJWMonzFt5Wj0,10
67
- versionhq-1.2.0.3.dist-info/RECORD,,
63
+ versionhq-1.2.1.0.dist-info/LICENSE,sha256=cRoGGdM73IiDs6nDWKqPlgSv7aR4n-qBXYnJlCMHCeE,1082
64
+ versionhq-1.2.1.0.dist-info/METADATA,sha256=Ilxj9ZEYzoFzEnzdDE6umFKruSRN3pmcm_D2LvpVi8A,21854
65
+ versionhq-1.2.1.0.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
66
+ versionhq-1.2.1.0.dist-info/top_level.txt,sha256=DClQwxDWqIUGeRJkA8vBlgeNsYZs4_nJWMonzFt5Wj0,10
67
+ versionhq-1.2.1.0.dist-info/RECORD,,
File without changes