versionhq 1.1.13.1__py3-none-any.whl → 1.2.0.2__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 +3 -3
- versionhq/agent/model.py +1 -1
- versionhq/network/model.py +150 -44
- versionhq/task/evaluate.py +1 -1
- versionhq/task/formation.py +1 -1
- versionhq/task/model.py +120 -86
- versionhq/task/structured_response.py +16 -14
- versionhq/team/model.py +11 -10
- versionhq/team/team_planner.py +2 -4
- {versionhq-1.1.13.1.dist-info → versionhq-1.2.0.2.dist-info}/METADATA +7 -7
- {versionhq-1.1.13.1.dist-info → versionhq-1.2.0.2.dist-info}/RECORD +14 -14
- {versionhq-1.1.13.1.dist-info → versionhq-1.2.0.2.dist-info}/LICENSE +0 -0
- {versionhq-1.1.13.1.dist-info → versionhq-1.2.0.2.dist-info}/WHEEL +0 -0
- {versionhq-1.1.13.1.dist-info → versionhq-1.2.0.2.dist-info}/top_level.txt +0 -0
versionhq/__init__.py
CHANGED
@@ -17,7 +17,7 @@ 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
19
|
from versionhq.network.model import TaskStatus, TaskGraph, Node, Edge, DependencyType
|
20
|
-
from versionhq.task.model import Task, TaskOutput,
|
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
|
23
23
|
from versionhq.tool.model import Tool, ToolSet
|
@@ -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.
|
33
|
+
__version__ = "1.2.0.2"
|
34
34
|
__all__ = [
|
35
35
|
"Agent",
|
36
36
|
|
@@ -64,8 +64,8 @@ __all__ = [
|
|
64
64
|
|
65
65
|
"Task",
|
66
66
|
"TaskOutput",
|
67
|
-
"ConditionalTask",
|
68
67
|
"ResponseField",
|
68
|
+
"TaskExecutionType",
|
69
69
|
|
70
70
|
"Evaluation",
|
71
71
|
"EvaluationItem",
|
versionhq/agent/model.py
CHANGED
versionhq/network/model.py
CHANGED
@@ -1,23 +1,22 @@
|
|
1
1
|
import enum
|
2
2
|
import uuid
|
3
3
|
from abc import ABC
|
4
|
-
from typing import List, Any, Optional, Callable, Dict, Type
|
4
|
+
from typing import List, Any, Optional, Callable, Dict, Type, Tuple
|
5
5
|
|
6
|
-
from pydantic import BaseModel, InstanceOf, Field, UUID4, PrivateAttr, field_validator
|
6
|
+
from pydantic import BaseModel, InstanceOf, Field, UUID4, PrivateAttr, field_validator, model_validator
|
7
7
|
from pydantic_core import PydanticCustomError
|
8
8
|
|
9
|
-
from versionhq.task.model import Task
|
9
|
+
from versionhq.task.model import Task, TaskOutput
|
10
10
|
from versionhq.agent.model import Agent
|
11
11
|
from versionhq._utils.logger import Logger
|
12
12
|
|
13
|
-
|
14
13
|
try:
|
15
|
-
import networkx as
|
14
|
+
import networkx as nx
|
16
15
|
except ImportError:
|
17
16
|
try:
|
18
17
|
import os
|
19
18
|
os.system("uv add networkx --optional networkx")
|
20
|
-
import networkx as
|
19
|
+
import networkx as nx
|
21
20
|
except:
|
22
21
|
raise ImportError("networkx is not installed. Please install it with: uv add networkx --optional networkx")
|
23
22
|
|
@@ -43,18 +42,17 @@ class TaskStatus(enum.Enum):
|
|
43
42
|
"""
|
44
43
|
NOT_STARTED = 1
|
45
44
|
IN_PROGRESS = 2
|
46
|
-
|
45
|
+
WAITING = 3 # waiting for its dependant tasks to complete. resumption set as AUTO.
|
47
46
|
COMPLETED = 4
|
48
|
-
DELAYED = 5
|
49
|
-
ON_HOLD = 6
|
50
|
-
|
47
|
+
DELAYED = 5 # task in progress - but taking longer than expected duration
|
48
|
+
ON_HOLD = 6 # intentionally paused due to external factors and decisions. resumption set as DECISION.
|
49
|
+
ERROR = 7 # tried task execute but returned error. resupmtion follows edge weights and agent settings
|
51
50
|
|
52
51
|
|
53
52
|
class DependencyType(enum.Enum):
|
54
53
|
"""
|
55
54
|
Concise enumeration of the edge type.
|
56
55
|
"""
|
57
|
-
|
58
56
|
FINISH_TO_START = "FS" # Task B starts after Task A finishes
|
59
57
|
START_TO_START = "SS" # Task B starts when Task A starts
|
60
58
|
FINISH_TO_FINISH = "FF" # Task B finishes when Task A finishes
|
@@ -86,7 +84,7 @@ class Node(BaseModel):
|
|
86
84
|
"""
|
87
85
|
id: UUID4 = Field(default_factory=uuid.uuid4, frozen=True)
|
88
86
|
task: InstanceOf[Task] = Field(default=None)
|
89
|
-
trigger_event: TriggerEvent = Field(default=TriggerEvent.IMMEDIATE, description="store trigger event
|
87
|
+
# trigger_event: TriggerEvent = Field(default=TriggerEvent.IMMEDIATE, description="store trigger event for starting the task execution")
|
90
88
|
in_degree_nodes: List[Any] = Field(default=None, description="list of Node objects")
|
91
89
|
out_degree_nodes: List[Any] = Field(default=None, description="list of Node objects")
|
92
90
|
assigned_to: InstanceOf[Agent] = Field(default=None)
|
@@ -119,49 +117,110 @@ class Node(BaseModel):
|
|
119
117
|
"""Unique identifier for the node"""
|
120
118
|
return f"{str(self.id)}"
|
121
119
|
|
120
|
+
def handle_task_execution(self, agent: Agent = None, context: str = None) -> TaskOutput | None:
|
121
|
+
"""
|
122
|
+
Start task execution and update status accordingly.
|
123
|
+
"""
|
124
|
+
|
125
|
+
self.status = TaskStatus.IN_PROGRESS
|
126
|
+
|
127
|
+
if not self.task:
|
128
|
+
Logger(verbose=True).log(level="error", message="Missing a task to execute. We'll return None.", color="red")
|
129
|
+
self.status = TaskStatus.ERROR
|
130
|
+
return None
|
131
|
+
|
132
|
+
res = self.task.execute(agent=agent, context=context)
|
133
|
+
self.status = TaskStatus.COMPLETED if res else TaskStatus.ERROR
|
134
|
+
return res
|
135
|
+
|
122
136
|
def __str__(self):
|
123
137
|
return self.identifier
|
124
138
|
|
125
139
|
|
140
|
+
|
126
141
|
class Edge(BaseModel):
|
127
142
|
"""
|
128
|
-
A class to store an edge object that connects
|
143
|
+
A class to store an edge object that connects source and target nodes.
|
129
144
|
"""
|
145
|
+
source: Node = Field(default=None)
|
146
|
+
target: Node = Field(default=None)
|
147
|
+
|
130
148
|
description: Optional[str] = Field(default=None)
|
149
|
+
weight: Optional[float | int] = Field(default=1, description="est. duration for the task execution or respective weight of the target node (1 low - 10 high priority)")
|
131
150
|
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
priority: Optional[int] = Field(default=None, description="priority of the dependency if multiple depencencies are given")
|
151
|
+
dependency_type: DependencyType = Field(default=DependencyType.FINISH_TO_START)
|
152
|
+
required: bool = Field(default=True, description="whether to consider the source's status")
|
153
|
+
condition: Optional[Callable] = Field(default=None, description="conditional function to start executing the dependency")
|
154
|
+
condition_kwargs: Optional[Dict[str, Any]] = Field(default_factory=dict)
|
137
155
|
|
138
|
-
|
139
|
-
|
156
|
+
lag: Optional[float | int] = Field(default=None, description="lag time (sec) from the dependency met to the task execution")
|
157
|
+
data_transfer: bool = Field(default=True, description="whether the data transfer is required. by default transfer plane text output from in-degree nodes as context")
|
140
158
|
|
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
159
|
|
160
|
+
def dependency_met(self) -> bool:
|
161
|
+
"""
|
162
|
+
Defines if the dependency is ready to execute:
|
145
163
|
|
146
|
-
|
164
|
+
required - condition - Dependency Met? - Dependent Task Can Start?
|
165
|
+
True Not Given Predecessor task finished Yes
|
166
|
+
True Given Predecessor task finished and condition True Yes
|
167
|
+
False Not Given Always (regardless of predecessor status) Yes
|
168
|
+
False Given Condition True (predecessor status irrelevant) Yes
|
147
169
|
"""
|
148
|
-
Define if the dependency is ready to execute:
|
149
170
|
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
171
|
+
if not self.required:
|
172
|
+
return self.condition(**self.condition_kwargs) if self.condition else True
|
173
|
+
|
174
|
+
match self.dependency_type:
|
175
|
+
case DependencyType.FINISH_TO_START:
|
176
|
+
"""target starts after source finishes"""
|
177
|
+
if self.source.status == TaskStatus.COMPLETED:
|
178
|
+
return self.condition(**self.conditon_kwargs) if self.condtion else True
|
179
|
+
else:
|
180
|
+
return False
|
181
|
+
|
182
|
+
case DependencyType.START_TO_START:
|
183
|
+
"""target starts when source starts"""
|
184
|
+
if self.source.status != TaskStatus.NOT_STARTED:
|
185
|
+
return self.condition(**self.conditon_kwargs) if self.condtion else True
|
186
|
+
else:
|
187
|
+
return False
|
188
|
+
|
189
|
+
case DependencyType.FINISH_TO_FINISH:
|
190
|
+
"""target finish when source start"""
|
191
|
+
if self.source.status != TaskStatus.COMPLETED:
|
192
|
+
return self.condition(**self.conditon_kwargs) if self.condtion else True
|
193
|
+
else:
|
194
|
+
return False
|
195
|
+
|
196
|
+
case DependencyType.START_TO_FINISH:
|
197
|
+
"""target finishes when source start"""
|
198
|
+
if self.source.status == TaskStatus.IN_PROGRESS:
|
199
|
+
return self.condition(**self.conditon_kwargs) if self.condtion else True
|
200
|
+
else:
|
201
|
+
return False
|
202
|
+
|
203
|
+
|
204
|
+
def activate(self) -> TaskOutput | None:
|
205
|
+
"""
|
206
|
+
Activates the edge to initiate task execution of the target node.
|
155
207
|
"""
|
156
208
|
|
157
|
-
if self.
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
return
|
209
|
+
if not self.dependency_met():
|
210
|
+
Logger(verbose=True).log(level="warning", message="Dependencies not met. We'll return None.", color="yellow")
|
211
|
+
return None
|
212
|
+
|
213
|
+
if not self.source or not self.target:
|
214
|
+
Logger(verbose=True).log(level="warning", message="Cannot find source or target nodes. We'll return None.", color="yellow")
|
215
|
+
return None
|
216
|
+
|
217
|
+
if self.lag:
|
218
|
+
import time
|
219
|
+
time.sleep(self.lag)
|
164
220
|
|
221
|
+
context = self.source.task.task_output.raw if self.data_transfer else None
|
222
|
+
res = self.target.handle_task_execution(context=context)
|
223
|
+
return res
|
165
224
|
|
166
225
|
|
167
226
|
class Graph(ABC, BaseModel):
|
@@ -176,16 +235,20 @@ class Graph(ABC, BaseModel):
|
|
176
235
|
edges: Dict[str, InstanceOf[Edge]] = Field(default_factory=dict)
|
177
236
|
|
178
237
|
def __init__(self, directed: bool = False, **kwargs):
|
179
|
-
|
180
238
|
super().__init__(directed=directed, **kwargs)
|
181
239
|
self.graph = nx.DiGraph() if self.directed else nx.Graph()
|
182
240
|
|
241
|
+
def _return_node_object(self, node_identifier) -> Node | None:
|
242
|
+
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
|
243
|
+
|
183
244
|
def add_node(self, node: Node) -> None:
|
184
245
|
self.graph.add_node(node.identifier, **node.model_dump())
|
185
246
|
self.nodes[node.identifier] = node
|
186
247
|
|
187
248
|
def add_edge(self, source: str, target: str, edge: Edge) -> None:
|
188
249
|
self.graph.add_edge(source, target, **edge.model_dump())
|
250
|
+
edge.source = self._return_node_object(source)
|
251
|
+
edge.target = self._return_node_object(target)
|
189
252
|
self.edges[(source, target)] = edge
|
190
253
|
|
191
254
|
def add_weighted_edges_from(self, edges):
|
@@ -200,7 +263,7 @@ class Graph(ABC, BaseModel):
|
|
200
263
|
def get_out_degree(self, node: Node) -> int:
|
201
264
|
return self.graph.out_degree(node)
|
202
265
|
|
203
|
-
def find_path(self, source: str, target: str, weight: Any) -> Any:
|
266
|
+
def find_path(self, source: Optional[str] | None, target: str, weight: Optional[Any] | None) -> Any:
|
204
267
|
try:
|
205
268
|
return nx.shortest_path(self.graph, source=source, target=target, weight=weight)
|
206
269
|
except nx.NetworkXNoPath:
|
@@ -209,7 +272,6 @@ class Graph(ABC, BaseModel):
|
|
209
272
|
def find_all_paths(self, source: str, target: str) -> List[Any]:
|
210
273
|
return list(nx.all_simple_paths(self.graph, source=source, target=target))
|
211
274
|
|
212
|
-
|
213
275
|
def find_critical_path(self) -> tuple[List[Any], int, Dict[str, int]]:
|
214
276
|
"""
|
215
277
|
Finds the critical path in the graph.
|
@@ -245,7 +307,7 @@ class Graph(ABC, BaseModel):
|
|
245
307
|
return False
|
246
308
|
|
247
309
|
|
248
|
-
def visualize(self, title: str = "Graph
|
310
|
+
def visualize(self, title: str = "Task Graph", pos: Any = None, **graph_config):
|
249
311
|
pos = pos if pos else nx.spring_layout(self.graph, seed=42)
|
250
312
|
nx.draw(
|
251
313
|
self.graph,
|
@@ -270,12 +332,11 @@ class Graph(ABC, BaseModel):
|
|
270
332
|
plt.show()
|
271
333
|
|
272
334
|
|
273
|
-
|
274
335
|
class TaskGraph(Graph):
|
275
336
|
id: UUID4 = Field(default_factory=uuid.uuid4, frozen=True)
|
276
337
|
should_reform: bool = Field(default=False)
|
277
338
|
status: Dict[str, TaskStatus] = Field(default_factory=dict, description="store identifier (str) and TaskStatus of all task_nodes")
|
278
|
-
|
339
|
+
outputs: Dict[str, TaskOutput] = Field(default_factory=dict, description="store identifire and TaskOutput")
|
279
340
|
|
280
341
|
def add_task(self, task: Node | Task) -> Node:
|
281
342
|
"""Convert `task` to a Node object and add it to G"""
|
@@ -352,7 +413,7 @@ class TaskGraph(Graph):
|
|
352
413
|
for k, v in self.edges.items():
|
353
414
|
# edge_weights = nx.get_edge_attributes(self.graph, 'weight')
|
354
415
|
# edge_colors.append(plt.cm.viridis(v.weight / max(edge_weights.values())))
|
355
|
-
edge_widths.append(v.weight * 0.5)
|
416
|
+
edge_widths.append(v.weight * 0.5)
|
356
417
|
|
357
418
|
nx.draw(
|
358
419
|
self.graph, pos,
|
@@ -392,3 +453,48 @@ class TaskGraph(Graph):
|
|
392
453
|
|
393
454
|
except Exception as e:
|
394
455
|
self._logger.log(level="error", message=f"Failed to save the graph {str(self.id)}: {str(e)}", color="red")
|
456
|
+
|
457
|
+
|
458
|
+
def activate(self, target_node_identifier: Optional[str] = None) -> Tuple[TaskOutput | None, Dict[str, TaskOutput]]:
|
459
|
+
"""
|
460
|
+
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.
|
461
|
+
Then returns tuple of the last task output and all task outputs (self.outputs)
|
462
|
+
"""
|
463
|
+
if target_node_identifier:
|
464
|
+
if not [k for k in self.nodes.keys() if k == target_node_identifier]:
|
465
|
+
self._logger.log(level="error", message=f"The node {str(target_node_identifier)} is not in the graph.", color="red")
|
466
|
+
return None
|
467
|
+
|
468
|
+
# find a shortest path to each in-degree node of the node and see if dependency met.
|
469
|
+
node = self._return_node_object(target_node_identifier)
|
470
|
+
sources = node.in_degrees
|
471
|
+
edge_status = []
|
472
|
+
res = None
|
473
|
+
|
474
|
+
for item in sources:
|
475
|
+
edge = self.find_path(source=item, target=target_node_identifier)
|
476
|
+
edge_status.append(dict(edge=edge if edge else None, dep_met=edge.dependency_met() if edge else False))
|
477
|
+
|
478
|
+
if len([item for item in edge_status if item["dep_met"] == True]) == len(sources):
|
479
|
+
res = node.handle_task_execution()
|
480
|
+
self.outputs.update({ target_node_identifier: res })
|
481
|
+
self.status.update({ target_node_identifier: edge.target.status })
|
482
|
+
|
483
|
+
return res, self.outputs
|
484
|
+
|
485
|
+
else:
|
486
|
+
if not self.edges or self.nodes:
|
487
|
+
self._logger.log(level="error", message="Needs at least 2 nodes and 1 edge to activate the graph. We'll return None.", color="red")
|
488
|
+
return None
|
489
|
+
|
490
|
+
for edge in self.edges:
|
491
|
+
res = edge.activate()
|
492
|
+
if not res:
|
493
|
+
break
|
494
|
+
|
495
|
+
node_identifier = edge.target.identifier
|
496
|
+
self.outputs.update({ node_identifier: res })
|
497
|
+
self.status.update({ node_identifier: edge.target.status })
|
498
|
+
|
499
|
+
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
|
500
|
+
return last_task_output, self.outputs
|
versionhq/task/evaluate.py
CHANGED
@@ -72,7 +72,7 @@ class EvaluationItem(BaseModel):
|
|
72
72
|
|
73
73
|
class Evaluation(BaseModel):
|
74
74
|
items: List[EvaluationItem] = []
|
75
|
-
latency:
|
75
|
+
latency: float = Field(default=None, description="job execution latency in seconds")
|
76
76
|
tokens: int = Field(default=None, description="tokens consumed")
|
77
77
|
eval_by: Any = Field(default=None, description="stores agent object that evaluates the outcome")
|
78
78
|
|
versionhq/task/formation.py
CHANGED
@@ -79,7 +79,7 @@ def form_agent_network(
|
|
79
79
|
if agents:
|
80
80
|
vhq_task.description += "Consider adding following agents in the formation: " + ", ".join([agent.role for agent in agents if isinstance(agent, Agent)])
|
81
81
|
|
82
|
-
res = vhq_task.
|
82
|
+
res = vhq_task.execute(agent=vhq_formation_planner, context=context)
|
83
83
|
_formation = Formation.SUPERVISING
|
84
84
|
|
85
85
|
if res.pydantic:
|
versionhq/task/model.py
CHANGED
@@ -3,6 +3,7 @@ import threading
|
|
3
3
|
import datetime
|
4
4
|
import uuid
|
5
5
|
import inspect
|
6
|
+
import enum
|
6
7
|
from concurrent.futures import Future
|
7
8
|
from hashlib import md5
|
8
9
|
from typing import Any, Dict, List, Set, Optional, Callable, Type
|
@@ -11,18 +12,25 @@ from typing_extensions import Annotated, Self
|
|
11
12
|
from pydantic import UUID4, BaseModel, Field, PrivateAttr, field_validator, model_validator, InstanceOf, field_validator
|
12
13
|
from pydantic_core import PydanticCustomError
|
13
14
|
|
14
|
-
|
15
|
+
import versionhq as vhq
|
15
16
|
from versionhq.task.log_handler import TaskOutputStorageHandler
|
16
17
|
from versionhq.task.evaluate import Evaluation, EvaluationItem
|
17
18
|
from versionhq.tool.model import Tool, ToolSet
|
18
19
|
from versionhq._utils import process_config, Logger
|
19
20
|
|
20
21
|
|
22
|
+
class TaskExecutionType(enum.Enum):
|
23
|
+
"""
|
24
|
+
Enumeration to store task execution types of independent tasks without dependencies.
|
25
|
+
"""
|
26
|
+
SYNC = 1
|
27
|
+
ASYNC = 2
|
28
|
+
|
29
|
+
|
21
30
|
class ResponseField(BaseModel):
|
22
31
|
"""
|
23
|
-
A class to store
|
24
|
-
|
25
|
-
https://community.openai.com/t/official-documentation-for-supported-schemas-for-response-format-parameter-in-calls-to-client-beta-chats-completions-parse/932422/3
|
32
|
+
A class to store a response format that will generate a JSON schema.
|
33
|
+
One layer of nested child is acceptable.
|
26
34
|
"""
|
27
35
|
|
28
36
|
title: str = Field(default=None, description="title of the field")
|
@@ -31,7 +39,7 @@ class ResponseField(BaseModel):
|
|
31
39
|
properties: Optional[List[BaseModel]] = Field(default=None, description="store dict items in ResponseField format")
|
32
40
|
required: bool = Field(default=True)
|
33
41
|
nullable: bool = Field(default=False)
|
34
|
-
config: Optional[Dict[str, Any]] = Field(
|
42
|
+
config: Optional[Dict[str, Any]] = Field(default_factory=dict, description="additional rules")
|
35
43
|
|
36
44
|
|
37
45
|
@model_validator(mode="after")
|
@@ -57,36 +65,44 @@ class ResponseField(BaseModel):
|
|
57
65
|
|
58
66
|
def _format_props(self) -> Dict[str, Any]:
|
59
67
|
"""
|
60
|
-
Structure valid properties.
|
68
|
+
Structure valid properties from the ResponseField object. 1 layer of nested child is accepted.
|
61
69
|
"""
|
62
70
|
from versionhq.llm.llm_vars import SchemaType
|
63
71
|
|
64
72
|
schema_type = SchemaType(type=self.data_type).convert()
|
65
73
|
props: Dict[str, Any] = {}
|
66
74
|
|
67
|
-
if self.data_type is list
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
+
if self.data_type is list:
|
76
|
+
if self.items is dict:
|
77
|
+
nested_p, nested_r = dict(), list()
|
78
|
+
|
79
|
+
if self.properties:
|
80
|
+
for item in self.properties:
|
81
|
+
nested_p.update(**item._format_props())
|
82
|
+
nested_r.append(item.title)
|
83
|
+
|
84
|
+
props = {
|
85
|
+
"type": schema_type,
|
86
|
+
"items": {
|
87
|
+
"type": SchemaType(type=self.items).convert(),
|
88
|
+
"properties": nested_p,
|
89
|
+
"required": nested_r,
|
90
|
+
"additionalProperties": False
|
91
|
+
}
|
92
|
+
}
|
75
93
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
94
|
+
elif self.items is list:
|
95
|
+
props = {
|
96
|
+
"type": schema_type,
|
97
|
+
"items": { "type": SchemaType(type=self.items).convert(), "items": { "type": SchemaType(type=str).convert() }},
|
98
|
+
}
|
80
99
|
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
"type": SchemaType(type=self.items).convert(),
|
85
|
-
"properties": nested_p,
|
86
|
-
"required": nested_r,
|
87
|
-
"additionalProperties": False
|
100
|
+
else:
|
101
|
+
props = {
|
102
|
+
"type": schema_type,
|
103
|
+
"items": { "type": SchemaType(type=self.items).convert() },
|
88
104
|
}
|
89
|
-
|
105
|
+
|
90
106
|
|
91
107
|
elif self.data_type is dict:
|
92
108
|
p, r = dict(), list()
|
@@ -184,17 +200,16 @@ class TaskOutput(BaseModel):
|
|
184
200
|
return json.dumps(self.json_dict) if self.json_dict else self.raw[0: 127]
|
185
201
|
|
186
202
|
|
187
|
-
def evaluate(self, task
|
203
|
+
def evaluate(self, task) -> Evaluation:
|
188
204
|
"""
|
189
205
|
Evaluate the output based on the criteria, score each from 0 to 1 scale, and raise suggestions for future improvement.
|
190
206
|
"""
|
191
207
|
from versionhq.task.TEMPLATES.Description import EVALUATE
|
192
208
|
|
193
|
-
if not self.evaluation
|
194
|
-
self.evaluation = Evaluation()
|
209
|
+
self.evaluation = Evaluation() if not self.evaluation else self.evaluation
|
195
210
|
|
196
|
-
self.evaluation.latency = latency if latency is not None else task.latency
|
197
|
-
self.evaluation.tokens = tokens if tokens is not None else task.tokens
|
211
|
+
# self.evaluation.latency = latency if latency is not None else task.latency
|
212
|
+
# self.evaluation.tokens = tokens if tokens is not None else task.tokens
|
198
213
|
|
199
214
|
eval_criteria = task.eval_criteria if task.eval_criteria else ["Overall competitiveness", ]
|
200
215
|
|
@@ -203,7 +218,7 @@ class TaskOutput(BaseModel):
|
|
203
218
|
description=EVALUATE.format(task_description=task.description, task_output=self.raw, eval_criteria=str(item)),
|
204
219
|
pydantic_output=EvaluationItem
|
205
220
|
)
|
206
|
-
res = task_eval.
|
221
|
+
res = task_eval.execute(agent=self.evaluation.eval_by)
|
207
222
|
|
208
223
|
if res.pydantic:
|
209
224
|
item = EvaluationItem(score=res.pydantic.score, suggestion=res.pydantic.suggestion, criteria=res.pydantic.criteria)
|
@@ -241,7 +256,7 @@ class TaskOutput(BaseModel):
|
|
241
256
|
|
242
257
|
class Task(BaseModel):
|
243
258
|
"""
|
244
|
-
A class that stores independent task information.
|
259
|
+
A class that stores independent task information and handles task executions.
|
245
260
|
"""
|
246
261
|
|
247
262
|
__hash__ = object.__hash__
|
@@ -255,8 +270,8 @@ class Task(BaseModel):
|
|
255
270
|
description: str = Field(description="Description of the actual task")
|
256
271
|
|
257
272
|
# output
|
258
|
-
pydantic_output: Optional[
|
259
|
-
response_fields: List[ResponseField] = Field(default_factory=list, description="store
|
273
|
+
pydantic_output: Optional[Type[BaseModel]] = Field(default=None, description="store Pydantic class as structured response format")
|
274
|
+
response_fields: Optional[List[ResponseField]] = Field(default_factory=list, description="store list of ResponseField as structured response format")
|
260
275
|
output: Optional[TaskOutput] = Field(default=None, description="store the final task output in TaskOutput class")
|
261
276
|
|
262
277
|
# task setup
|
@@ -269,8 +284,8 @@ class Task(BaseModel):
|
|
269
284
|
tool_res_as_final: bool = Field(default=False, description="when set True, tools res will be stored in the `TaskOutput`")
|
270
285
|
|
271
286
|
# execution rules
|
287
|
+
execution_type: TaskExecutionType = Field(default=TaskExecutionType.SYNC)
|
272
288
|
allow_delegation: bool = Field(default=False, description="ask other agents for help and run the task instead")
|
273
|
-
async_execution: bool = Field(default=False,description="whether the task should be executed asynchronously or not")
|
274
289
|
callback: Optional[Callable] = Field(default=None, description="callback to be executed after the task is completed.")
|
275
290
|
callback_kwargs: Optional[Dict[str, Any]] = Field(default_factory=dict, description="kwargs for the callback when the callback is callable")
|
276
291
|
|
@@ -408,11 +423,7 @@ Ref. Output image: {output_formats_to_follow}
|
|
408
423
|
properties, required_fields = {}, []
|
409
424
|
for i, item in enumerate(self.response_fields):
|
410
425
|
if item:
|
411
|
-
|
412
|
-
properties.update(item._format_props())
|
413
|
-
else:
|
414
|
-
properties.update(item._format_props())
|
415
|
-
|
426
|
+
properties.update(item._format_props())
|
416
427
|
required_fields.append(item.title)
|
417
428
|
|
418
429
|
response_schema = {
|
@@ -421,7 +432,6 @@ Ref. Output image: {output_formats_to_follow}
|
|
421
432
|
"required": required_fields,
|
422
433
|
"additionalProperties": False,
|
423
434
|
}
|
424
|
-
|
425
435
|
response_format = {
|
426
436
|
"type": "json_schema",
|
427
437
|
"json_schema": { "name": "outcome", "schema": response_schema }
|
@@ -429,7 +439,7 @@ Ref. Output image: {output_formats_to_follow}
|
|
429
439
|
|
430
440
|
|
431
441
|
elif self.pydantic_output:
|
432
|
-
response_format = StructuredOutput(response_format=self.pydantic_output)._format()
|
442
|
+
response_format = StructuredOutput(response_format=self.pydantic_output, provider=model_provider)._format()
|
433
443
|
|
434
444
|
return response_format
|
435
445
|
|
@@ -481,7 +491,6 @@ Ref. Output image: {output_formats_to_follow}
|
|
481
491
|
|
482
492
|
for k, v in json_dict.items():
|
483
493
|
setattr(output_pydantic, k, v)
|
484
|
-
|
485
494
|
except:
|
486
495
|
pass
|
487
496
|
|
@@ -536,9 +545,35 @@ Ref. Output image: {output_formats_to_follow}
|
|
536
545
|
self._logger.log(level="error", message=f"Failed to add to the memory: {str(e)}", color="red")
|
537
546
|
pass
|
538
547
|
|
548
|
+
def _build_agent_from_task(self, task_description: str = None) -> InstanceOf["vhq.Agent"]:
|
549
|
+
task_description = task_description if task_description else self.description
|
550
|
+
if not task_description:
|
551
|
+
self._logger.log(level="error", message="Task is missing the description.", color="red")
|
552
|
+
pass
|
553
|
+
|
554
|
+
agent = vhq.Agent(goal=task_description, role=task_description, maxit=1) #! REFINEME
|
555
|
+
return agent
|
556
|
+
|
539
557
|
|
540
558
|
# task execution
|
541
|
-
def
|
559
|
+
def execute(self, type: TaskExecutionType = None, agent: Optional[Any] = None, context: Optional[str] = None) -> TaskOutput | Future[TaskOutput]:
|
560
|
+
"""
|
561
|
+
A main method to handle task execution. Build an agent when the agent is not given.
|
562
|
+
"""
|
563
|
+
type = type if type else self.execution_type if self.execution_type else TaskExecutionType.SYNC
|
564
|
+
|
565
|
+
if not agent:
|
566
|
+
agent = self._build_agent_from_task(task_description=self.description)
|
567
|
+
|
568
|
+
match type:
|
569
|
+
case TaskExecutionType.SYNC:
|
570
|
+
return self._execute_sync(agent=agent, context=context)
|
571
|
+
|
572
|
+
case TaskExecutionType.ASYNC:
|
573
|
+
return self._execute_async(agent=agent, context=context)
|
574
|
+
|
575
|
+
|
576
|
+
def _execute_sync(self, agent, context: Optional[str | List[Any]] = None) -> TaskOutput:
|
542
577
|
"""
|
543
578
|
Execute the task synchronously.
|
544
579
|
When the task has context, make sure we have executed all the tasks in the context first.
|
@@ -553,7 +588,7 @@ Ref. Output image: {output_formats_to_follow}
|
|
553
588
|
return self._execute_core(agent, context)
|
554
589
|
|
555
590
|
|
556
|
-
def
|
591
|
+
def _execute_async(self, agent, context: Optional[str] = None) -> Future[TaskOutput]:
|
557
592
|
"""
|
558
593
|
Execute the task asynchronously.
|
559
594
|
"""
|
@@ -565,7 +600,7 @@ Ref. Output image: {output_formats_to_follow}
|
|
565
600
|
|
566
601
|
def _execute_task_async(self, agent, context: Optional[str], future: Future[TaskOutput]) -> None:
|
567
602
|
"""
|
568
|
-
|
603
|
+
Executes the task asynchronously with context handling.
|
569
604
|
"""
|
570
605
|
|
571
606
|
result = self._execute_core(agent, context)
|
@@ -574,8 +609,8 @@ Ref. Output image: {output_formats_to_follow}
|
|
574
609
|
|
575
610
|
def _execute_core(self, agent, context: Optional[str]) -> TaskOutput:
|
576
611
|
"""
|
577
|
-
|
578
|
-
|
612
|
+
A core method for task execution.
|
613
|
+
Handles 1. agent delegation, 2. tools, 3. context to add to the prompt, and 4. callbacks
|
579
614
|
"""
|
580
615
|
|
581
616
|
from versionhq.agent.model import Agent
|
@@ -634,13 +669,13 @@ Ref. Output image: {output_formats_to_follow}
|
|
634
669
|
json_dict=json_dict_output
|
635
670
|
)
|
636
671
|
|
637
|
-
|
638
672
|
self.latency = (ended_at - started_at).total_seconds()
|
673
|
+
task_output.evaluation = Evaluation(latency=self.latency, tokens=self.tokens)
|
639
674
|
self.output = task_output
|
640
675
|
self.processed_agents.add(agent.role)
|
641
676
|
|
642
677
|
if self.should_evaluate:
|
643
|
-
task_output.evaluate(task=self
|
678
|
+
task_output.evaluate(task=self)
|
644
679
|
|
645
680
|
self._create_short_and_long_term_memories(agent=agent, task_output=task_output)
|
646
681
|
|
@@ -648,7 +683,7 @@ Ref. Output image: {output_formats_to_follow}
|
|
648
683
|
kwargs = { **self.callback_kwargs, **task_output.json_dict }
|
649
684
|
sig = inspect.signature(self.callback)
|
650
685
|
valid_keys = [param.name for param in sig.parameters.values() if param.kind == param.POSITIONAL_OR_KEYWORD]
|
651
|
-
valid_kwargs = { k: kwargs[k] for k in valid_keys }
|
686
|
+
valid_kwargs = { k: kwargs[k] if k in kwargs else None for k in valid_keys }
|
652
687
|
callback_res = self.callback(**valid_kwargs)
|
653
688
|
task_output.callback_output = callback_res
|
654
689
|
|
@@ -687,50 +722,49 @@ Task ID: {str(self.id)}
|
|
687
722
|
|
688
723
|
|
689
724
|
|
690
|
-
class ConditionalTask(Task):
|
691
|
-
|
692
|
-
|
693
|
-
|
694
|
-
|
725
|
+
# class ConditionalTask(Task):
|
726
|
+
# """
|
727
|
+
# A task that can be conditionally executed based on the output of another task.
|
728
|
+
# When the `condition` return True, execute the task, else skipped with `skipped task output`.
|
729
|
+
# """
|
695
730
|
|
696
|
-
|
697
|
-
|
698
|
-
|
699
|
-
|
731
|
+
# condition: Callable[[TaskOutput], bool] = Field(
|
732
|
+
# default=None,
|
733
|
+
# description="max. number of retries for an agent to execute a task when an error occurs",
|
734
|
+
# )
|
700
735
|
|
701
736
|
|
702
|
-
|
703
|
-
|
704
|
-
|
705
|
-
self._logger = Logger(verbose=True)
|
737
|
+
# def __init__(self, condition: Callable[[Any], bool], **kwargs):
|
738
|
+
# super().__init__(**kwargs)
|
739
|
+
# self.condition = condition
|
706
740
|
|
707
741
|
|
708
|
-
|
709
|
-
|
710
|
-
|
711
|
-
|
712
|
-
|
713
|
-
|
742
|
+
# def should_execute(self, context: TaskOutput) -> bool:
|
743
|
+
# """
|
744
|
+
# Decide whether the conditional task should be executed based on the provided context.
|
745
|
+
# Return `True` if it should be executed.
|
746
|
+
# """
|
747
|
+
# return self.condition(context)
|
714
748
|
|
715
749
|
|
716
|
-
|
717
|
-
|
750
|
+
# def get_skipped_task_output(self):
|
751
|
+
# return TaskOutput(task_id=self.id, raw="", pydantic=None, json_dict={})
|
718
752
|
|
719
753
|
|
720
|
-
|
721
|
-
|
722
|
-
|
723
|
-
|
754
|
+
# def _handle_conditional_task(self, task_outputs: List[TaskOutput], task_index: int, was_replayed: bool) -> Optional[TaskOutput]:
|
755
|
+
# """
|
756
|
+
# When the conditional task should be skipped, return `skipped_task_output` as task_output else return None
|
757
|
+
# """
|
724
758
|
|
725
|
-
|
759
|
+
# previous_output = task_outputs[task_index - 1] if task_outputs and len(task_outputs) > 1 else None
|
726
760
|
|
727
|
-
|
728
|
-
|
729
|
-
|
730
|
-
|
761
|
+
# if previous_output and not self.should_execute(previous_output):
|
762
|
+
# self._logger.log(level="warning", message=f"Skipping conditional task: {self.description}", color="yellow")
|
763
|
+
# skipped_task_output = self.get_skipped_task_output()
|
764
|
+
# self.output = skipped_task_output
|
731
765
|
|
732
|
-
|
733
|
-
|
734
|
-
|
766
|
+
# if not was_replayed:
|
767
|
+
# self._store_execution_log(self, task_index=task_index, was_replayed=was_replayed, inputs={})
|
768
|
+
# return skipped_task_output
|
735
769
|
|
736
|
-
|
770
|
+
# return None
|
@@ -3,11 +3,11 @@ from typing import Dict, Type, List, Any
|
|
3
3
|
from pydantic import BaseModel, Field, InstanceOf
|
4
4
|
|
5
5
|
from versionhq.llm.llm_vars import SchemaType
|
6
|
-
from versionhq.llm.model import LLM
|
6
|
+
from versionhq.llm.model import LLM, DEFAULT_MODEL_PROVIDER_NAME
|
7
7
|
|
8
8
|
|
9
9
|
"""
|
10
|
-
|
10
|
+
Generate a JSON schema from the given Pydantic model.
|
11
11
|
"""
|
12
12
|
|
13
13
|
|
@@ -15,7 +15,7 @@ class StructuredObject:
|
|
15
15
|
"""
|
16
16
|
A class to store the structured dictionary.
|
17
17
|
"""
|
18
|
-
provider: str =
|
18
|
+
provider: str = None
|
19
19
|
field: Type[Field]
|
20
20
|
|
21
21
|
title: str
|
@@ -24,20 +24,21 @@ class StructuredObject:
|
|
24
24
|
required: List[str] = list()
|
25
25
|
additionalProperties: bool = False
|
26
26
|
|
27
|
-
def __init__(self, name, field: Type[Field], provider: str | InstanceOf[LLM] =
|
27
|
+
def __init__(self, name, field: Type[Field], provider: str | InstanceOf[LLM] = None):
|
28
28
|
self.title = name
|
29
29
|
self.field = field
|
30
30
|
self.dtype = "object"
|
31
31
|
self.additionalProperties = False
|
32
|
-
self.provider = provider if isinstance(provider, str) else provider.provider
|
32
|
+
self.provider = provider if isinstance(provider, str) else provider.provider if isinstance(provider, LLM) else DEFAULT_MODEL_PROVIDER_NAME
|
33
33
|
|
34
34
|
def _format(self):
|
35
35
|
if not self.field:
|
36
36
|
pass
|
37
37
|
else:
|
38
38
|
description = self.field.description if hasattr(self.field, "description") and self.field.description is not None else ""
|
39
|
-
self.
|
40
|
-
self.
|
39
|
+
field_name = self.field.__name__ if hasattr(self.field, "__name__") and self.field.__name__ else self.title
|
40
|
+
self.properties.update({ field_name : { "type": SchemaType(self.field.annotation.__args__).convert() }})
|
41
|
+
self.required.append(field_name)
|
41
42
|
|
42
43
|
return {
|
43
44
|
self.title: {
|
@@ -55,13 +56,13 @@ class StructuredList:
|
|
55
56
|
"""
|
56
57
|
A class to store a structured list with 1 nested object.
|
57
58
|
"""
|
58
|
-
provider: str =
|
59
|
+
provider: str = DEFAULT_MODEL_PROVIDER_NAME
|
59
60
|
field: Type[Field]
|
60
61
|
title: str = ""
|
61
62
|
dtype: str = "array"
|
62
63
|
items: Dict[str, Dict[str, str]] = dict()
|
63
64
|
|
64
|
-
def __init__(self, name, field: Type[Field], provider: str | LLM =
|
65
|
+
def __init__(self, name, field: Type[Field], provider: str | LLM = DEFAULT_MODEL_PROVIDER_NAME):
|
65
66
|
self.provider = provider if isinstance(provider, str) else provider.provider
|
66
67
|
self.field = field
|
67
68
|
self.title = name
|
@@ -77,15 +78,15 @@ class StructuredList:
|
|
77
78
|
description = "" if field.description is None else field.description
|
78
79
|
props = {}
|
79
80
|
|
80
|
-
for item in field.annotation.__args__:
|
81
|
+
for i, item in enumerate(field.annotation.__args__):
|
81
82
|
nested_object_type = item.__origin__ if hasattr(item, "__origin__") else item
|
82
83
|
|
83
84
|
if nested_object_type == dict:
|
84
85
|
props.update({
|
85
86
|
# "nest": {
|
86
87
|
"type": "object",
|
87
|
-
"properties": { "
|
88
|
-
"required": ["
|
88
|
+
"properties": { f"{str(i)}": { "type": "string"} },
|
89
|
+
"required": [f"{str(i)}",],
|
89
90
|
"additionalProperties": False
|
90
91
|
# }
|
91
92
|
})
|
@@ -94,13 +95,14 @@ class StructuredList:
|
|
94
95
|
props.update({
|
95
96
|
# "nest": {
|
96
97
|
"type": "array",
|
97
|
-
"items": { "type": "string" }
|
98
|
+
"items": { "type": "string" },
|
98
99
|
# }
|
99
100
|
})
|
100
101
|
else:
|
101
102
|
props.update({ "type": SchemaType(nested_object_type).convert() })
|
102
103
|
|
103
104
|
self.items = { **props }
|
105
|
+
|
104
106
|
return {
|
105
107
|
self.title: {
|
106
108
|
"type": self.dtype,
|
@@ -112,7 +114,7 @@ class StructuredList:
|
|
112
114
|
|
113
115
|
class StructuredOutput(BaseModel):
|
114
116
|
response_format: Any = None # pydantic base model
|
115
|
-
provider: str =
|
117
|
+
provider: str = None
|
116
118
|
applicable_models: List[InstanceOf[LLM] | str] = list()
|
117
119
|
name: str = ""
|
118
120
|
schema: Dict[str, Any] = dict(type="object", additionalProperties=False, properties=dict(), required=list())
|
versionhq/team/model.py
CHANGED
@@ -10,7 +10,7 @@ from pydantic._internal._generate_schema import GenerateSchema
|
|
10
10
|
from pydantic_core import PydanticCustomError, core_schema
|
11
11
|
|
12
12
|
from versionhq.agent.model import Agent
|
13
|
-
from versionhq.task.model import Task, TaskOutput,
|
13
|
+
from versionhq.task.model import Task, TaskOutput, TaskExecutionType
|
14
14
|
from versionhq.task.formatter import create_raw_outputs
|
15
15
|
from versionhq.team.team_planner import TeamPlanner
|
16
16
|
from versionhq._utils.logger import Logger
|
@@ -215,7 +215,7 @@ class Team(BaseModel):
|
|
215
215
|
for task in reversed(self.tasks):
|
216
216
|
if not task:
|
217
217
|
break
|
218
|
-
elif task.
|
218
|
+
elif task.execution_type == TaskExecutionType.ASYNC:
|
219
219
|
async_task_count += 1
|
220
220
|
else:
|
221
221
|
break
|
@@ -341,7 +341,7 @@ class Team(BaseModel):
|
|
341
341
|
for task_index, task in enumerate(tasks):
|
342
342
|
if start_index is not None and task_index < start_index:
|
343
343
|
if task.output:
|
344
|
-
if task.
|
344
|
+
if task.execution_type == TaskExecutionType.ASYNC:
|
345
345
|
task_outputs.append(task.output)
|
346
346
|
else:
|
347
347
|
task_outputs = [task.output]
|
@@ -352,20 +352,21 @@ class Team(BaseModel):
|
|
352
352
|
if responsible_agent is None:
|
353
353
|
self._assign_tasks()
|
354
354
|
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
355
|
+
## commented out - this will be handled by node objects
|
356
|
+
# if isinstance(task, ConditionalTask):
|
357
|
+
# skipped_task_output = task._handle_conditional_task(task_outputs, futures, task_index, was_replayed)
|
358
|
+
# if skipped_task_output:
|
359
|
+
# continue
|
359
360
|
|
360
361
|
# self._log_task_start(task, responsible_agent)
|
361
362
|
|
362
|
-
if task.
|
363
|
+
if task.execution_type == TaskExecutionType.ASYNC:
|
363
364
|
context = create_raw_outputs(tasks=[task, ], task_outputs=([last_sync_output,] if last_sync_output else []))
|
364
|
-
future = task.
|
365
|
+
future = task._execute_async(agent=responsible_agent, context=context)
|
365
366
|
futures.append((task, future, task_index))
|
366
367
|
else:
|
367
368
|
context = create_raw_outputs(tasks=[task,], task_outputs=([last_sync_output,] if last_sync_output else [] ))
|
368
|
-
task_output = task.
|
369
|
+
task_output = task.execute(agent=responsible_agent, context=context)
|
369
370
|
if self.managers and responsible_agent in [manager.agent for manager in self.managers]:
|
370
371
|
lead_task_output = task_output
|
371
372
|
|
versionhq/team/team_planner.py
CHANGED
@@ -3,8 +3,6 @@ from typing import Any, List, Optional, Dict
|
|
3
3
|
from pydantic import BaseModel, Field
|
4
4
|
|
5
5
|
|
6
|
-
|
7
|
-
|
8
6
|
class TeamPlanner:
|
9
7
|
"""
|
10
8
|
A class to handle agent formations based on the given task description.
|
@@ -50,7 +48,7 @@ class TeamPlanner:
|
|
50
48
|
ResponseField(title="role", data_type=str, required=True),
|
51
49
|
],
|
52
50
|
)
|
53
|
-
res = task.
|
51
|
+
res = task.execute(agent=agent_creator)
|
54
52
|
agent = Agent(
|
55
53
|
role=res.json_dict["role"] if "role" in res.json_dict else res.raw,
|
56
54
|
goal=res.json_dict["goal"] if "goal" in res.json_dict else task.description
|
@@ -90,5 +88,5 @@ class TeamPlanner:
|
|
90
88
|
""",
|
91
89
|
pydantic_output=TeamPlanIdea
|
92
90
|
)
|
93
|
-
output = task.
|
91
|
+
output = task.execute(agent=team_planner, context=context, tools=tools)
|
94
92
|
return output
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.2
|
2
2
|
Name: versionhq
|
3
|
-
Version: 1.
|
3
|
+
Version: 1.2.0.2
|
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
|
@@ -86,8 +86,8 @@ Requires-Dist: matplotlib>=3.10.0; extra == "matplotlib"
|
|
86
86
|
[](https://clickpy.clickhouse.com/dashboard/versionhq)
|
87
87
|

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

|
90
|
+

|
91
91
|

|
92
92
|
|
93
93
|
|
@@ -266,9 +266,6 @@ By default, agents prioritize JSON over plane text outputs.
|
|
266
266
|
def dummy_func(message: str, test1: str, test2: list[str]) -> str:
|
267
267
|
return f"""{message}: {test1}, {", ".join(test2)}"""
|
268
268
|
|
269
|
-
|
270
|
-
agent = vhq.Agent(role="demo", goal="amazing project goal")
|
271
|
-
|
272
269
|
task = vhq.Task(
|
273
270
|
description="Amazing task",
|
274
271
|
pydantic_output=CustomOutput,
|
@@ -276,7 +273,7 @@ By default, agents prioritize JSON over plane text outputs.
|
|
276
273
|
callback_kwargs=dict(message="Hi! Here is the result: ")
|
277
274
|
)
|
278
275
|
|
279
|
-
res = task.
|
276
|
+
res = task.execute(context="amazing context to consider.")
|
280
277
|
print(res)
|
281
278
|
```
|
282
279
|
|
@@ -377,6 +374,7 @@ Tasks can be delegated to a team manager, peers in the team, or completely new a
|
|
377
374
|
└── workflows/ # Github actions
|
378
375
|
│
|
379
376
|
docs/ # Documentation built by MkDocs
|
377
|
+
mkdocs.yml # MkDocs config
|
380
378
|
│
|
381
379
|
src/
|
382
380
|
└── versionhq/ # Orchestration framework package
|
@@ -392,6 +390,8 @@ src/
|
|
392
390
|
│ └── ...
|
393
391
|
│
|
394
392
|
└── uploads/ [.gitignore] # Local directory to store uploaded files such as graphviz diagrams generatd by `Network` class
|
393
|
+
|
|
394
|
+
pyproject.toml # Project config
|
395
395
|
|
396
396
|
```
|
397
397
|
|
@@ -1,4 +1,4 @@
|
|
1
|
-
versionhq/__init__.py,sha256=
|
1
|
+
versionhq/__init__.py,sha256=RwVa60s3Puko2L4VU8tS7jAFhPy5zqbYqyJUdDhyjHA,2783
|
2
2
|
versionhq/_utils/__init__.py,sha256=dzoZr4cBlh-2QZuPzTdehPUCe9lP1dmRtauD7qTjUaA,158
|
3
3
|
versionhq/_utils/i18n.py,sha256=TwA_PnYfDLA6VqlUDPuybdV9lgi3Frh_ASsb_X8jJo8,1483
|
4
4
|
versionhq/_utils/logger.py,sha256=j9SlQPIefdVUlwpGfJY83E2BUt1ejWgZ2M2I8aMyQ3c,1579
|
@@ -7,7 +7,7 @@ versionhq/_utils/usage_metrics.py,sha256=NXF18dn5NNvGK7EsQ4AAghpR8ppYOjMx6ABenLL
|
|
7
7
|
versionhq/_utils/vars.py,sha256=bZ5Dx_bFKlt3hi4-NNGXqdk7B23If_WaTIju2fiTyPQ,57
|
8
8
|
versionhq/agent/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
9
9
|
versionhq/agent/inhouse_agents.py,sha256=vSobrH1gXDWlaNsiges3sqETeUrEssRzQvCZCY2hQZA,2374
|
10
|
-
versionhq/agent/model.py,sha256=
|
10
|
+
versionhq/agent/model.py,sha256=kSYCAiRxF62ztj-S-_KfN0mhPKikjbFZ0fy2Yj5J7Jo,25400
|
11
11
|
versionhq/agent/parser.py,sha256=riG0dkdQCxH7uJ0AbdVdg7WvL0BXhUgJht0VtQvxJBc,4082
|
12
12
|
versionhq/agent/rpm_controller.py,sha256=grezIxyBci_lDlwAlgWFRyR5KOocXeOhYkgN02dNFNE,2360
|
13
13
|
versionhq/agent/TEMPLATES/Backstory.py,sha256=IAhGnnt6VUMe3wO6IzeyZPDNu7XE7Uiu3VEXUreOcKs,532
|
@@ -34,7 +34,7 @@ versionhq/memory/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,
|
|
34
34
|
versionhq/memory/contextual_memory.py,sha256=tCsOOAUnfrOL7YiakqGoi3uShzzS870TmGnlGd3z_A4,3556
|
35
35
|
versionhq/memory/model.py,sha256=4wow2O3UuMZ0AbC2NyxddGZac3-_GjNZbK9wsA015NA,8145
|
36
36
|
versionhq/network/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
37
|
-
versionhq/network/model.py,sha256=
|
37
|
+
versionhq/network/model.py,sha256=nqRlekld7Hy0cQZCOXbOGpanTFJwWfHyJt1OodhHJI0,20316
|
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
|
@@ -43,16 +43,16 @@ versionhq/storage/rag_storage.py,sha256=ScWC0vH327vnGw8UGscAOoIfqrq3mhvXT3vEKzHZ
|
|
43
43
|
versionhq/storage/task_output_storage.py,sha256=E1t_Fkt78dPYIOl3MP7LfQ8oGtjlzxBuSNq_8ZXKho8,4573
|
44
44
|
versionhq/storage/utils.py,sha256=ByYXPoEIGJYLUqz-DWjbCAnneNrH1otiYbp12SCILpM,747
|
45
45
|
versionhq/task/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
46
|
-
versionhq/task/evaluate.py,sha256=
|
47
|
-
versionhq/task/formation.py,sha256=
|
46
|
+
versionhq/task/evaluate.py,sha256=WdUgjbZL62XrxyWe5MTz29scfzwmuAHGxJ7GvAB8Fmk,3954
|
47
|
+
versionhq/task/formation.py,sha256=iHhW2webMirYC78G8kHpaMTYjCdq7c3yFD2u2Egd8Eo,6350
|
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=
|
51
|
-
versionhq/task/structured_response.py,sha256=
|
50
|
+
versionhq/task/model.py,sha256=zAnZ-_5YKZRs43gpM2AL9IVtttq5GqGfqfCMj_Oyz9g,30624
|
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=
|
55
|
-
versionhq/team/team_planner.py,sha256=
|
54
|
+
versionhq/team/model.py,sha256=kxrjF3RFGO9l3IsJIffI1nqgl_Vh5RLuhqrwIqbEoBY,19214
|
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
|
58
58
|
versionhq/tool/composio_tool.py,sha256=38mEiVvTkuw1BLD233Bl1Gwxbpss1yfQiZLTWwX6BdA,8648
|
@@ -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.
|
64
|
-
versionhq-1.
|
65
|
-
versionhq-1.
|
66
|
-
versionhq-1.
|
67
|
-
versionhq-1.
|
63
|
+
versionhq-1.2.0.2.dist-info/LICENSE,sha256=cRoGGdM73IiDs6nDWKqPlgSv7aR4n-qBXYnJlCMHCeE,1082
|
64
|
+
versionhq-1.2.0.2.dist-info/METADATA,sha256=YT-m2KqdExbv9rb2uLSVoRjkMiB06pKExUqkduXFoM0,21462
|
65
|
+
versionhq-1.2.0.2.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
66
|
+
versionhq-1.2.0.2.dist-info/top_level.txt,sha256=DClQwxDWqIUGeRJkA8vBlgeNsYZs4_nJWMonzFt5Wj0,10
|
67
|
+
versionhq-1.2.0.2.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|