versionhq 1.2.0.4__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 +2 -2
- versionhq/_utils/logger.py +1 -1
- versionhq/{network → graph}/model.py +174 -82
- versionhq/task/formation.py +2 -2
- versionhq/task/model.py +8 -9
- versionhq/team/model.py +38 -60
- {versionhq-1.2.0.4.dist-info → versionhq-1.2.1.0.dist-info}/METADATA +21 -8
- {versionhq-1.2.0.4.dist-info → versionhq-1.2.1.0.dist-info}/RECORD +12 -12
- /versionhq/{network → graph}/__init__.py +0 -0
- {versionhq-1.2.0.4.dist-info → versionhq-1.2.1.0.dist-info}/LICENSE +0 -0
- {versionhq-1.2.0.4.dist-info → versionhq-1.2.1.0.dist-info}/WHEEL +0 -0
- {versionhq-1.2.0.4.dist-info → versionhq-1.2.1.0.dist-info}/top_level.txt +0 -0
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.
|
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
|
33
|
+
__version__ = "1.2.1.0"
|
34
34
|
__all__ = [
|
35
35
|
"Agent",
|
36
36
|
|
versionhq/_utils/logger.py
CHANGED
@@ -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
|
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)
|
@@ -5,7 +5,7 @@ import matplotlib.pyplot as plt
|
|
5
5
|
from abc import ABC
|
6
6
|
from typing import List, Any, Optional, Callable, Dict, Type, Tuple
|
7
7
|
|
8
|
-
from pydantic import BaseModel, InstanceOf, Field, UUID4, PrivateAttr, field_validator
|
8
|
+
from pydantic import BaseModel, InstanceOf, Field, UUID4, PrivateAttr, field_validator
|
9
9
|
from pydantic_core import PydanticCustomError
|
10
10
|
|
11
11
|
from versionhq.task.model import Task, TaskOutput
|
@@ -13,6 +13,26 @@ from versionhq.agent.model import Agent
|
|
13
13
|
from versionhq._utils.logger import Logger
|
14
14
|
|
15
15
|
|
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]
|
24
|
+
|
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)
|
35
|
+
|
16
36
|
class TaskStatus(enum.Enum):
|
17
37
|
"""
|
18
38
|
Enum to track the task execution status
|
@@ -37,21 +57,21 @@ class DependencyType(enum.Enum):
|
|
37
57
|
|
38
58
|
|
39
59
|
|
40
|
-
class TriggerEvent(enum.Enum):
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
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
|
55
75
|
|
56
76
|
|
57
77
|
|
@@ -62,8 +82,8 @@ class Node(BaseModel):
|
|
62
82
|
id: UUID4 = Field(default_factory=uuid.uuid4, frozen=True)
|
63
83
|
task: InstanceOf[Task] = Field(default=None)
|
64
84
|
# trigger_event: TriggerEvent = Field(default=TriggerEvent.IMMEDIATE, description="store trigger event for starting the task execution")
|
65
|
-
in_degree_nodes: List[Any] = Field(
|
66
|
-
out_degree_nodes: List[Any] = Field(
|
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")
|
67
87
|
assigned_to: InstanceOf[Agent] = Field(default=None)
|
68
88
|
status: TaskStatus = Field(default=TaskStatus.NOT_STARTED)
|
69
89
|
|
@@ -77,22 +97,6 @@ class Node(BaseModel):
|
|
77
97
|
def is_independent(self) -> bool:
|
78
98
|
return not self.in_degree_nodes and not self.out_degree_nodes
|
79
99
|
|
80
|
-
@property
|
81
|
-
def in_degrees(self) -> int:
|
82
|
-
return len(self.in_degree_nodes) if self.in_degree_nodes else 0
|
83
|
-
|
84
|
-
@property
|
85
|
-
def out_degrees(self) -> int:
|
86
|
-
return len(self.out_degree_nodes) if self.out_degree_nodes else 0
|
87
|
-
|
88
|
-
@property
|
89
|
-
def degrees(self) -> int:
|
90
|
-
return self.in_degrees + self.out_degrees
|
91
|
-
|
92
|
-
@property
|
93
|
-
def identifier(self) -> str:
|
94
|
-
"""Unique identifier for the node"""
|
95
|
-
return f"{str(self.id)}"
|
96
100
|
|
97
101
|
def handle_task_execution(self, agent: Agent = None, context: str = None) -> TaskOutput | None:
|
98
102
|
"""
|
@@ -110,15 +114,33 @@ class Node(BaseModel):
|
|
110
114
|
self.status = TaskStatus.COMPLETED if res else TaskStatus.ERROR
|
111
115
|
return res
|
112
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
|
+
|
113
135
|
def __str__(self):
|
114
136
|
return self.identifier
|
115
137
|
|
116
138
|
|
117
|
-
|
118
139
|
class Edge(BaseModel):
|
119
140
|
"""
|
120
141
|
A class to store an edge object that connects source and target nodes.
|
121
142
|
"""
|
143
|
+
|
122
144
|
source: Node = Field(default=None)
|
123
145
|
target: Node = Field(default=None)
|
124
146
|
|
@@ -152,28 +174,28 @@ class Edge(BaseModel):
|
|
152
174
|
case DependencyType.FINISH_TO_START:
|
153
175
|
"""target starts after source finishes"""
|
154
176
|
if self.source.status == TaskStatus.COMPLETED:
|
155
|
-
return self.condition(**self.conditon_kwargs) if self.
|
177
|
+
return self.condition(**self.conditon_kwargs) if self.condition else True
|
156
178
|
else:
|
157
179
|
return False
|
158
180
|
|
159
181
|
case DependencyType.START_TO_START:
|
160
182
|
"""target starts when source starts"""
|
161
183
|
if self.source.status != TaskStatus.NOT_STARTED:
|
162
|
-
return self.condition(**self.conditon_kwargs) if self.
|
184
|
+
return self.condition(**self.conditon_kwargs) if self.condition else True
|
163
185
|
else:
|
164
186
|
return False
|
165
187
|
|
166
188
|
case DependencyType.FINISH_TO_FINISH:
|
167
189
|
"""target finish when source start"""
|
168
190
|
if self.source.status != TaskStatus.COMPLETED:
|
169
|
-
return self.condition(**self.conditon_kwargs) if self.
|
191
|
+
return self.condition(**self.conditon_kwargs) if self.condition else True
|
170
192
|
else:
|
171
193
|
return False
|
172
194
|
|
173
195
|
case DependencyType.START_TO_FINISH:
|
174
196
|
"""target finishes when source start"""
|
175
197
|
if self.source.status == TaskStatus.IN_PROGRESS:
|
176
|
-
return self.condition(**self.conditon_kwargs) if self.
|
198
|
+
return self.condition(**self.conditon_kwargs) if self.condition else True
|
177
199
|
else:
|
178
200
|
return False
|
179
201
|
|
@@ -183,19 +205,20 @@ class Edge(BaseModel):
|
|
183
205
|
Activates the edge to initiate task execution of the target node.
|
184
206
|
"""
|
185
207
|
|
186
|
-
if not self.dependency_met():
|
187
|
-
Logger(verbose=True).log(level="warning", message="Dependencies not met. We'll return None.", color="yellow")
|
188
|
-
return None
|
189
|
-
|
190
208
|
if not self.source or not self.target:
|
191
209
|
Logger(verbose=True).log(level="warning", message="Cannot find source or target nodes. We'll return None.", color="yellow")
|
192
210
|
return None
|
193
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
|
+
|
194
217
|
if self.lag:
|
195
218
|
import time
|
196
219
|
time.sleep(self.lag)
|
197
220
|
|
198
|
-
context = self.source.task.
|
221
|
+
context = self.source.task.output.raw if self.data_transfer else None
|
199
222
|
res = self.target.handle_task_execution(context=context)
|
200
223
|
return res
|
201
224
|
|
@@ -205,8 +228,6 @@ class Graph(ABC, BaseModel):
|
|
205
228
|
An abstract class to store G using NetworkX library.
|
206
229
|
"""
|
207
230
|
|
208
|
-
|
209
|
-
_logger: Logger = PrivateAttr(default_factory=lambda: Logger(verbose=True))
|
210
231
|
directed: bool = Field(default=False, description="Whether the graph is directed")
|
211
232
|
graph: Type[nx.Graph] = Field(default=None)
|
212
233
|
nodes: Dict[str, InstanceOf[Node]] = Field(default_factory=dict, description="identifier: Node - for the sake of ")
|
@@ -226,7 +247,9 @@ class Graph(ABC, BaseModel):
|
|
226
247
|
def add_edge(self, source: str, target: str, edge: Edge) -> None:
|
227
248
|
self.graph.add_edge(source, target, **edge.model_dump())
|
228
249
|
edge.source = self._return_node_object(source)
|
250
|
+
edge.source.out_degree_nodes.append(target)
|
229
251
|
edge.target = self._return_node_object(target)
|
252
|
+
edge.target.in_degree_nodes.append(source)
|
230
253
|
self.edges[(source, target)] = edge
|
231
254
|
|
232
255
|
def add_weighted_edges_from(self, edges):
|
@@ -241,6 +264,28 @@ class Graph(ABC, BaseModel):
|
|
241
264
|
def get_out_degree(self, node: Node) -> int:
|
242
265
|
return self.graph.out_degree(node)
|
243
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
|
+
|
244
289
|
def find_path(self, source: Optional[str] | None, target: str, weight: Optional[Any] | None) -> Any:
|
245
290
|
try:
|
246
291
|
return nx.shortest_path(self.graph, source=source, target=target, weight=weight)
|
@@ -255,14 +300,14 @@ class Graph(ABC, BaseModel):
|
|
255
300
|
Finds the critical path in the graph.
|
256
301
|
Returns:
|
257
302
|
A tuple containing:
|
258
|
-
- The critical path (a list of
|
303
|
+
- The critical path (a list of edge identifiers).
|
259
304
|
- The duration of the critical path.
|
260
305
|
- A dictionary of all paths and their durations.
|
261
306
|
"""
|
262
307
|
|
263
308
|
all_paths = {}
|
264
|
-
for start_node in
|
265
|
-
for end_node in
|
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
|
266
311
|
for edge in nx.all_simple_paths(self.graph, source=start_node.identifier, target=end_node.identifier):
|
267
312
|
edge_weight = sum(self.edges.get(item).weight if self.edges.get(item) else 0 for item in edge)
|
268
313
|
all_paths[tuple(edge)] = edge_weight
|
@@ -314,7 +359,26 @@ class TaskGraph(Graph):
|
|
314
359
|
id: UUID4 = Field(default_factory=uuid.uuid4, frozen=True)
|
315
360
|
should_reform: bool = Field(default=False)
|
316
361
|
status: Dict[str, TaskStatus] = Field(default_factory=dict, description="store identifier (str) and TaskStatus of all task_nodes")
|
317
|
-
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
|
+
|
318
382
|
|
319
383
|
def add_task(self, task: Node | Task) -> Node:
|
320
384
|
"""Convert `task` to a Node object and add it to G"""
|
@@ -332,7 +396,7 @@ class TaskGraph(Graph):
|
|
332
396
|
"""
|
333
397
|
|
334
398
|
if not edge_attributes:
|
335
|
-
|
399
|
+
Logger(verbose=True).log(level="error", message="Edge attributes are missing.", color="red")
|
336
400
|
|
337
401
|
edge = Edge()
|
338
402
|
for k in Edge.model_fields.keys():
|
@@ -349,14 +413,15 @@ class TaskGraph(Graph):
|
|
349
413
|
if identifier in self.status:
|
350
414
|
self.status[identifier] = status
|
351
415
|
else:
|
352
|
-
|
416
|
+
Logger().log(level="warning", message=f"Task '{identifier}' not found in the graph.", color="yellow")
|
353
417
|
pass
|
354
418
|
|
419
|
+
|
355
420
|
def get_task_status(self, identifier):
|
356
421
|
if identifier in self.status:
|
357
422
|
return self.status[identifier]
|
358
423
|
else:
|
359
|
-
|
424
|
+
Logger().log(level="warning", message=f"Task '{identifier}' not found in the graph.", color="yellow")
|
360
425
|
return None
|
361
426
|
|
362
427
|
|
@@ -415,23 +480,6 @@ class TaskGraph(Graph):
|
|
415
480
|
plt.show()
|
416
481
|
|
417
482
|
|
418
|
-
def _save(self, abs_file_path: str = None) -> None:
|
419
|
-
"""
|
420
|
-
Save the graph image in the local directory.
|
421
|
-
"""
|
422
|
-
|
423
|
-
try:
|
424
|
-
import os
|
425
|
-
project_root = os.path.abspath(os.getcwd())
|
426
|
-
abs_file_path = abs_file_path if abs_file_path else f"{project_root}/uploads"
|
427
|
-
|
428
|
-
os.makedirs(abs_file_path, exist_ok=True)
|
429
|
-
plt.savefig(f"{abs_file_path}/{str(self.id)}.png")
|
430
|
-
|
431
|
-
except Exception as e:
|
432
|
-
self._logger.log(level="error", message=f"Failed to save the graph {str(self.id)}: {str(e)}", color="red")
|
433
|
-
|
434
|
-
|
435
483
|
def activate(self, target_node_identifier: Optional[str] = None) -> Tuple[TaskOutput | None, Dict[str, TaskOutput]]:
|
436
484
|
"""
|
437
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.
|
@@ -439,7 +487,7 @@ class TaskGraph(Graph):
|
|
439
487
|
"""
|
440
488
|
if target_node_identifier:
|
441
489
|
if not [k for k in self.nodes.keys() if k == target_node_identifier]:
|
442
|
-
|
490
|
+
Logger().log(level="error", message=f"The node {str(target_node_identifier)} is not in the graph.", color="red")
|
443
491
|
return None
|
444
492
|
|
445
493
|
# find a shortest path to each in-degree node of the node and see if dependency met.
|
@@ -460,18 +508,62 @@ class TaskGraph(Graph):
|
|
460
508
|
return res, self.outputs
|
461
509
|
|
462
510
|
else:
|
463
|
-
if not self.edges or self.nodes:
|
464
|
-
|
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")
|
465
513
|
return None
|
466
514
|
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
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]
|
536
|
+
|
537
|
+
if edge.target.status == TaskStatus.COMPLETED:
|
538
|
+
res = edge.target.output
|
471
539
|
|
472
|
-
|
473
|
-
|
474
|
-
|
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 })
|
475
545
|
|
476
|
-
|
477
|
-
|
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
|
versionhq/task/formation.py
CHANGED
@@ -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(
|
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
|
-
|
36
|
+
SQUAD = 3
|
46
37
|
RANDOM = 4
|
47
38
|
HYBRID = 10
|
48
39
|
|
49
40
|
|
50
41
|
class TaskHandlingProcess(str, Enum):
|
51
42
|
"""
|
52
|
-
|
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
|
-
|
55
|
-
|
56
|
-
|
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
|
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
|
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
|
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.
|
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
|
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
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
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.
|
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
|
-
|
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.
|
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
|
+
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
|
@@ -81,10 +81,10 @@ Requires-Dist: pygraphviz>=1.14; extra == "pygraphviz"
|
|
81
81
|
|
82
82
|
# Overview
|
83
83
|
|
84
|
-
[](https://clickpy.clickhouse.com/dashboard/versionhq)
|
85
85
|

|
86
86
|
[](https://github.com/versionHQ/multi-agent-system/actions/workflows/publish.yml)
|
87
|
-

|
88
88
|

|
89
89
|

|
90
90
|
|
@@ -106,9 +106,10 @@ A Python framework for agentic orchestration that handles complex task automatio
|
|
106
106
|
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
|
107
107
|
|
108
108
|
- [Key Features](#key-features)
|
109
|
-
- [Agent
|
109
|
+
- [Agent Network](#agent-network)
|
110
110
|
- [Graph Theory Concept](#graph-theory-concept)
|
111
|
-
- [
|
111
|
+
- [Task Graph](#task-graph)
|
112
|
+
- [Optimization](#optimization)
|
112
113
|
- [Quick Start](#quick-start)
|
113
114
|
- [Package installation](#package-installation)
|
114
115
|
- [Forming a agent network](#forming-a-agent-network)
|
@@ -139,14 +140,14 @@ A Python framework for agentic orchestration that handles complex task automatio
|
|
139
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.
|
140
141
|
|
141
142
|
|
142
|
-
### Agent
|
143
|
+
### Agent Network
|
143
144
|
|
144
145
|
Agents adapt their formation based on task complexity.
|
145
146
|
|
146
147
|
You can specify a desired formation or allow the agents to determine it autonomously (default).
|
147
148
|
|
148
149
|
|
149
|
-
| | **Solo Agent** | **Supervising** | **
|
150
|
+
| | **Solo Agent** | **Supervising** | **Squad** | **Random** |
|
150
151
|
| :--- | :--- | :--- | :--- | :--- |
|
151
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"> |
|
152
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> |
|
@@ -192,7 +193,19 @@ task_graph.visualize()
|
|
192
193
|
|
193
194
|
<hr />
|
194
195
|
|
195
|
-
###
|
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
|
196
209
|
|
197
210
|
Agents are model-agnostic and can handle multiple tasks, leveraging their own and their peers' knowledge sources, memories, and tools.
|
198
211
|
|
@@ -1,7 +1,7 @@
|
|
1
|
-
versionhq/__init__.py,sha256=
|
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=
|
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,6 +20,8 @@ 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
|
@@ -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=fKKMTviIS2uOwLYT_bGN_2-4R0gmnPB8x-dd05k5jFE,19700
|
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=
|
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=
|
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=
|
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.
|
64
|
-
versionhq-1.2.0.
|
65
|
-
versionhq-1.2.0.
|
66
|
-
versionhq-1.2.0.
|
67
|
-
versionhq-1.2.0.
|
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
|
File without changes
|
File without changes
|
File without changes
|