versionhq 1.2.0.4__py3-none-any.whl → 1.2.1.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- versionhq/__init__.py +9 -9
- versionhq/_utils/logger.py +1 -1
- versionhq/agent/inhouse_agents.py +7 -0
- versionhq/agent/model.py +2 -2
- versionhq/{team → agent_network}/model.py +113 -115
- versionhq/clients/workflow/model.py +10 -10
- versionhq/knowledge/source.py +2 -2
- versionhq/knowledge/source_docling.py +1 -1
- versionhq/memory/model.py +1 -1
- versionhq/task/formation.py +11 -11
- versionhq/task/model.py +15 -16
- versionhq/{network → task_graph}/model.py +154 -82
- {versionhq-1.2.0.4.dist-info → versionhq-1.2.1.1.dist-info}/METADATA +54 -32
- {versionhq-1.2.0.4.dist-info → versionhq-1.2.1.1.dist-info}/RECORD +19 -20
- versionhq/team/team_planner.py +0 -92
- /versionhq/{network → agent_network}/__init__.py +0 -0
- /versionhq/{team → task_graph}/__init__.py +0 -0
- {versionhq-1.2.0.4.dist-info → versionhq-1.2.1.1.dist-info}/LICENSE +0 -0
- {versionhq-1.2.0.4.dist-info → versionhq-1.2.1.1.dist-info}/WHEEL +0 -0
- {versionhq-1.2.0.4.dist-info → versionhq-1.2.1.1.dist-info}/top_level.txt +0 -0
versionhq/__init__.py
CHANGED
@@ -8,6 +8,7 @@ from dotenv import load_dotenv
|
|
8
8
|
load_dotenv(override=True)
|
9
9
|
|
10
10
|
from versionhq.agent.model import Agent
|
11
|
+
from versionhq.agent_network.model import AgentNetwork, NetworkOutput, Formation, Member, TaskHandlingProcess
|
11
12
|
from versionhq.llm.model import LLM
|
12
13
|
from versionhq.llm.llm_vars import LLM_CONTEXT_WINDOW_SIZES, PARAMS, PROVIDERS, MODELS
|
13
14
|
from versionhq.clients.customer.model import Customer
|
@@ -16,10 +17,9 @@ from versionhq.clients.workflow.model import MessagingWorkflow, MessagingCompone
|
|
16
17
|
from versionhq.knowledge.model import Knowledge, KnowledgeStorage
|
17
18
|
from versionhq.knowledge.source import PDFKnowledgeSource, CSVKnowledgeSource, JSONKnowledgeSource, TextFileKnowledgeSource, ExcelKnowledgeSource, StringKnowledgeSource
|
18
19
|
from versionhq.knowledge.source_docling import DoclingSource
|
19
|
-
from versionhq.
|
20
|
+
from versionhq.task_graph.model import TaskStatus, TaskGraph, Node, Edge, DependencyType
|
20
21
|
from versionhq.task.model import Task, TaskOutput, ResponseField, TaskExecutionType
|
21
22
|
from versionhq.task.evaluate import Evaluation, EvaluationItem
|
22
|
-
from versionhq.team.model import Team, TeamOutput, Formation, Member, TaskHandlingProcess
|
23
23
|
from versionhq.tool.model import Tool, ToolSet
|
24
24
|
from versionhq.tool.cache_handler import CacheHandler
|
25
25
|
from versionhq.tool.tool_handler import ToolHandler
|
@@ -30,10 +30,16 @@ 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.
|
33
|
+
__version__ = "1.2.1.1"
|
34
34
|
__all__ = [
|
35
35
|
"Agent",
|
36
36
|
|
37
|
+
"AgentNetwork",
|
38
|
+
"NetworkOutput",
|
39
|
+
"Formation",
|
40
|
+
"Member",
|
41
|
+
"TaskHandlingProcess",
|
42
|
+
|
37
43
|
"LLM",
|
38
44
|
"LLM_CONTEXT_WINDOW_SIZES",
|
39
45
|
"PARAMS",
|
@@ -70,12 +76,6 @@ __all__ = [
|
|
70
76
|
"Evaluation",
|
71
77
|
"EvaluationItem",
|
72
78
|
|
73
|
-
"Team",
|
74
|
-
"TeamOutput",
|
75
|
-
"Formation",
|
76
|
-
"Member",
|
77
|
-
"TaskHandlingProcess",
|
78
|
-
|
79
79
|
"Tool",
|
80
80
|
"ToolSet",
|
81
81
|
"CacheHandler",
|
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)
|
@@ -39,3 +39,10 @@ vhq_formation_planner = Agent(
|
|
39
39
|
"Random is a formation where a single agent handles tasks, asking help from other agents without sharing its memory or knowledge. Typical usecase is that an email agent drafts promo message for the given audience, asking insights on tones from other email agents which oversee other customer clusters, or an agent calls the external, third party agent to deploy the campaign. ",
|
40
40
|
]
|
41
41
|
)
|
42
|
+
|
43
|
+
|
44
|
+
vhq_agent_creator = Agent(
|
45
|
+
role="vhq-Agent Creator",
|
46
|
+
goal="build an agent that can handle the given task",
|
47
|
+
llm="gemini/gemini-2.0-flash-exp",
|
48
|
+
)
|
versionhq/agent/model.py
CHANGED
@@ -95,7 +95,7 @@ class Agent(BaseModel):
|
|
95
95
|
user_prompt_template: Optional[str] = Field(default=None, description="user prompt template")
|
96
96
|
|
97
97
|
# task execution rules
|
98
|
-
|
98
|
+
network: Optional[List[Any]] = Field(default=None, description="store a list of agent networks that the agent belong as a member")
|
99
99
|
allow_delegation: bool = Field(default=False,description="if the agent can delegate the task to another agent or ask some help")
|
100
100
|
max_retry_limit: int = Field(default=2 ,description="max. number of retry for the task execution when an error occurs")
|
101
101
|
maxit: Optional[int] = Field(default=25,description="max. number of total optimization loops conducted when an error occurs")
|
@@ -553,7 +553,7 @@ class Agent(BaseModel):
|
|
553
553
|
|
554
554
|
|
555
555
|
## comment out for now
|
556
|
-
# if self.
|
556
|
+
# if self.network and self.network._train:
|
557
557
|
# task_prompt = self._training_handler(task_prompt=task_prompt)
|
558
558
|
# else:
|
559
559
|
# task_prompt = self._use_trained_data(task_prompt=task_prompt)
|
@@ -1,7 +1,6 @@
|
|
1
1
|
import uuid
|
2
2
|
import warnings
|
3
3
|
from enum import Enum
|
4
|
-
from dotenv import load_dotenv
|
5
4
|
from concurrent.futures import Future
|
6
5
|
from hashlib import md5
|
7
6
|
from typing import Any, Dict, List, Callable, Optional, Tuple
|
@@ -10,9 +9,8 @@ from pydantic._internal._generate_schema import GenerateSchema
|
|
10
9
|
from pydantic_core import PydanticCustomError, core_schema
|
11
10
|
|
12
11
|
from versionhq.agent.model import Agent
|
13
|
-
from versionhq.task.model import Task, TaskOutput, TaskExecutionType
|
12
|
+
from versionhq.task.model import Task, TaskOutput, TaskExecutionType, ResponseField
|
14
13
|
from versionhq.task.formatter import create_raw_outputs
|
15
|
-
from versionhq.team.team_planner import TeamPlanner
|
16
14
|
from versionhq._utils.logger import Logger
|
17
15
|
from versionhq._utils.usage_metrics import UsageMetrics
|
18
16
|
|
@@ -27,73 +25,62 @@ def match_type(self, obj):
|
|
27
25
|
|
28
26
|
GenerateSchema.match_type = match_type
|
29
27
|
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
28
|
|
40
29
|
|
41
30
|
class Formation(str, Enum):
|
42
31
|
UNDEFINED = 0
|
43
32
|
SOLO = 1
|
44
33
|
SUPERVISING = 2
|
45
|
-
|
34
|
+
SQUAD = 3
|
46
35
|
RANDOM = 4
|
47
36
|
HYBRID = 10
|
48
37
|
|
49
38
|
|
50
39
|
class TaskHandlingProcess(str, Enum):
|
51
40
|
"""
|
52
|
-
|
41
|
+
A class representing task handling processes to tackle multiple tasks.
|
42
|
+
When the agent network has multiple tasks that connect with edges, follow the edge conditions.
|
53
43
|
"""
|
54
|
-
|
55
|
-
|
56
|
-
|
44
|
+
SEQUENT = 1
|
45
|
+
HIERARCHY = 2
|
46
|
+
CONSENSUAL = 3 # either from managers or peers or (human) - most likely controlled by edge
|
57
47
|
|
58
48
|
|
59
|
-
class
|
49
|
+
class NetworkOutput(TaskOutput):
|
60
50
|
"""
|
61
|
-
A class to store output from the
|
51
|
+
A class to store output from the network, inherited from TaskOutput class.
|
62
52
|
"""
|
63
53
|
|
64
|
-
|
54
|
+
network_id: UUID4 = Field(default_factory=uuid.uuid4, frozen=True, description="store the network ID as an identifier that generate the output")
|
65
55
|
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
|
-
|
56
|
+
task_outputs: list[TaskOutput] = Field(default=list, description="store TaskOutput objects of all tasks that the network has executed")
|
57
|
+
# token_usage: UsageMetrics = Field(default=dict, description="processed token summary")
|
69
58
|
|
70
59
|
def return_all_task_outputs(self) -> List[Dict[str, Any]]:
|
71
60
|
res = [output.json_dict for output in self.task_outputs]
|
72
61
|
return res
|
73
62
|
|
74
|
-
|
75
63
|
def __str__(self):
|
76
64
|
return (str(self.pydantic) if self.pydantic else str(self.json_dict) if self.json_dict else self.raw)
|
77
65
|
|
78
|
-
|
79
66
|
def __getitem__(self, key):
|
80
67
|
if self.pydantic and hasattr(self.pydantic, key):
|
81
68
|
return getattr(self.pydantic, key)
|
82
69
|
elif self.json_dict and key in self.json_dict:
|
83
70
|
return self.json_dict[key]
|
84
71
|
else:
|
85
|
-
raise KeyError(f"Key '{key}' not found in the
|
72
|
+
raise KeyError(f"Key '{key}' not found in the output.")
|
86
73
|
|
87
74
|
|
88
75
|
|
89
76
|
class Member(BaseModel):
|
90
77
|
"""
|
91
|
-
A class to store a member in the network
|
78
|
+
A class to store a member (agent) in the network, with its tasks and memory/knowledge share settings.
|
92
79
|
"""
|
93
80
|
agent: Agent | None = Field(default=None)
|
94
81
|
is_manager: bool = Field(default=False)
|
95
|
-
can_share_knowledge: bool = Field(default=True, description="whether to share the agent's knowledge in the
|
96
|
-
can_share_memory: bool = Field(default=True, description="whether to share the agent's memory in the
|
82
|
+
can_share_knowledge: bool = Field(default=True, description="whether to share the agent's knowledge in the network")
|
83
|
+
can_share_memory: bool = Field(default=True, description="whether to share the agent's memory in the network")
|
97
84
|
tasks: Optional[List[Task]] = Field(default_factory=list, description="tasks explicitly assigned to the agent")
|
98
85
|
|
99
86
|
@property
|
@@ -101,9 +88,10 @@ class Member(BaseModel):
|
|
101
88
|
return bool(self.tasks)
|
102
89
|
|
103
90
|
|
104
|
-
class
|
91
|
+
class AgentNetwork(BaseModel):
|
105
92
|
"""
|
106
|
-
A class to store agent network
|
93
|
+
A class to store a agent network with agent members and their tasks.
|
94
|
+
Tasks can be 1. multiple individual tasks, 2. multiple dependant tasks connected via TaskGraph, and 3. hybrid.
|
107
95
|
"""
|
108
96
|
|
109
97
|
__hash__ = object.__hash__
|
@@ -118,21 +106,20 @@ class Team(BaseModel):
|
|
118
106
|
should_reform: bool = Field(default=False, description="True if task exe. failed or eval scores below threshold")
|
119
107
|
|
120
108
|
# formation planning
|
121
|
-
|
122
|
-
team_tasks: Optional[List[Task]] = Field(default_factory=list, description="tasks without dedicated agents to handle")
|
109
|
+
network_tasks: Optional[List[Task]] = Field(default_factory=list, description="tasks without dedicated agents")
|
123
110
|
|
124
111
|
# task execution rules
|
125
112
|
prompt_file: str = Field(default="", description="absolute path to the prompt json file")
|
126
|
-
process: TaskHandlingProcess = Field(default=TaskHandlingProcess.
|
113
|
+
process: TaskHandlingProcess = Field(default=TaskHandlingProcess.SEQUENT)
|
127
114
|
|
128
115
|
# callbacks
|
129
116
|
pre_launch_callbacks: List[Callable[[Optional[Dict[str, Any]]], Optional[Dict[str, Any]]]] = Field(
|
130
117
|
default_factory=list,
|
131
|
-
description="list of callback functions to be executed before the
|
118
|
+
description="list of callback functions to be executed before the network launch. i.e., adjust inputs"
|
132
119
|
)
|
133
|
-
post_launch_callbacks: List[Callable[[
|
120
|
+
post_launch_callbacks: List[Callable[[NetworkOutput], NetworkOutput]] = Field(
|
134
121
|
default_factory=list,
|
135
|
-
description="list of callback functions to be executed after the
|
122
|
+
description="list of callback functions to be executed after the network launch. i.e., store the result in repo"
|
136
123
|
)
|
137
124
|
step_callback: Optional[Any] = Field(default=None, description="callback to be executed after each step for all agents execution")
|
138
125
|
|
@@ -156,39 +143,37 @@ class Team(BaseModel):
|
|
156
143
|
@model_validator(mode="after")
|
157
144
|
def assess_tasks(self):
|
158
145
|
"""
|
159
|
-
Validates if the model recognize all tasks that the
|
146
|
+
Validates if the model recognize all tasks that the network needs to handle.
|
160
147
|
"""
|
161
148
|
|
162
149
|
if self.tasks:
|
163
|
-
if all(task in self.tasks for task in self.
|
164
|
-
raise PydanticCustomError("task_validation_error", "`
|
150
|
+
if all(task in self.tasks for task in self.network_tasks) == False:
|
151
|
+
raise PydanticCustomError("task_validation_error", "`network_tasks` needs to be recognized in the task.", {})
|
165
152
|
|
166
153
|
|
167
154
|
num_member_tasks = 0
|
168
155
|
for member in self.members:
|
169
156
|
num_member_tasks += len(member.tasks)
|
170
157
|
|
171
|
-
# if len(self.tasks) != len(self.
|
158
|
+
# if len(self.tasks) != len(self.network_tasks) + num_member_tasks:
|
172
159
|
# raise PydanticCustomError("task_validation_error", "Some tasks are missing.", {})
|
173
160
|
return self
|
174
161
|
|
175
162
|
|
176
163
|
@model_validator(mode="after")
|
177
|
-
def
|
164
|
+
def check_manager(self):
|
178
165
|
"""
|
179
|
-
Check if the
|
166
|
+
Check if the agent network has a manager
|
180
167
|
"""
|
181
|
-
|
182
|
-
if self.process == TaskHandlingProcess.hierarchical or self.formation == Formation.SUPERVISING:
|
168
|
+
if self.process == TaskHandlingProcess.HIERARCHY or self.formation == Formation.SUPERVISING:
|
183
169
|
if not self.managers:
|
184
170
|
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.", {})
|
171
|
+
raise PydanticCustomError("missing_manager", "`manager` is required when using hierarchical process.", {})
|
187
172
|
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
173
|
+
## comment out for the formation flexibilities
|
174
|
+
# if self.managers and (self.manager_tasks is None or self.network_tasks is None):
|
175
|
+
# self._logger.log(level="error", message="The manager is idling. At least 1 task needs to be assigned to the manager.", color="red")
|
176
|
+
# raise PydanticCustomError("missing_manager_task", "manager needs to have at least one manager task or network task.", {})
|
192
177
|
|
193
178
|
return self
|
194
179
|
|
@@ -196,9 +181,9 @@ class Team(BaseModel):
|
|
196
181
|
@model_validator(mode="after")
|
197
182
|
def validate_task_member_paring(self):
|
198
183
|
"""
|
199
|
-
Sequential task processing without any
|
184
|
+
Sequential task processing without any network_tasks require a task-agent pairing.
|
200
185
|
"""
|
201
|
-
if self.process == TaskHandlingProcess.
|
186
|
+
if self.process == TaskHandlingProcess.SEQUENT and self.network_tasks is None:
|
202
187
|
for task in self.tasks:
|
203
188
|
if not [member.task == task for member in self.members]:
|
204
189
|
self._logger.log(level="error", message=f"The following task needs a dedicated agent to be assinged: {task.description}", color="red")
|
@@ -208,7 +193,7 @@ class Team(BaseModel):
|
|
208
193
|
@model_validator(mode="after")
|
209
194
|
def validate_end_with_at_most_one_async_task(self):
|
210
195
|
"""
|
211
|
-
Validates that the
|
196
|
+
Validates that the agent network completes max. one asynchronous task by counting tasks traversed backward
|
212
197
|
"""
|
213
198
|
|
214
199
|
async_task_count = 0
|
@@ -221,10 +206,43 @@ class Team(BaseModel):
|
|
221
206
|
break
|
222
207
|
|
223
208
|
if async_task_count > 1:
|
224
|
-
raise PydanticCustomError("async_task_count", "The
|
209
|
+
raise PydanticCustomError("async_task_count", "The agent network must end with at maximum one asynchronous task.", {})
|
225
210
|
return self
|
226
211
|
|
227
212
|
|
213
|
+
@staticmethod
|
214
|
+
def handle_assigning_agents(unassigned_tasks: List[Task]) -> List[Member]:
|
215
|
+
"""
|
216
|
+
Build an agent and assign it with a task. Return a list of Member connecting the agent created and the task given.
|
217
|
+
"""
|
218
|
+
|
219
|
+
from versionhq.agent.inhouse_agents import vhq_agent_creator
|
220
|
+
|
221
|
+
new_member_list: List[Member] = []
|
222
|
+
|
223
|
+
for unassgined_task in unassigned_tasks:
|
224
|
+
task = Task(
|
225
|
+
description=f"""
|
226
|
+
Based on the following task summary, draft a AI agent's role and goal in concise manner.
|
227
|
+
Task summary: {unassgined_task.summary}
|
228
|
+
""",
|
229
|
+
response_fields=[
|
230
|
+
ResponseField(title="goal", data_type=str, required=True),
|
231
|
+
ResponseField(title="role", data_type=str, required=True),
|
232
|
+
],
|
233
|
+
)
|
234
|
+
res = task.execute(agent=vhq_agent_creator)
|
235
|
+
agent = Agent(
|
236
|
+
role=res.json_dict["role"] if "role" in res.json_dict else res.raw,
|
237
|
+
goal=res.json_dict["goal"] if "goal" in res.json_dict else task.description
|
238
|
+
)
|
239
|
+
if agent.id:
|
240
|
+
member = Member(agent=agent, tasks=[unassgined_task], is_manager=False)
|
241
|
+
new_member_list.append(member)
|
242
|
+
|
243
|
+
return new_member_list
|
244
|
+
|
245
|
+
|
228
246
|
def _get_responsible_agent(self, task: Task) -> Agent | None:
|
229
247
|
if task is None:
|
230
248
|
return None
|
@@ -238,17 +256,16 @@ class Team(BaseModel):
|
|
238
256
|
|
239
257
|
def _assign_tasks(self) -> None:
|
240
258
|
"""
|
241
|
-
Form a
|
242
|
-
1. Idling managers to take the
|
243
|
-
2. Idling members to take the remaining tasks starting from the
|
259
|
+
Form a agent network considering given agents and tasks, and update `self.members` field:
|
260
|
+
1. Idling managers to take the network tasks.
|
261
|
+
2. Idling members to take the remaining tasks starting from the network tasks to member tasks.
|
244
262
|
3. Create agents to handle the rest tasks.
|
245
263
|
"""
|
246
264
|
|
247
|
-
team_planner = TeamPlanner(tasks=self.tasks, planner_llm=self.planner_llm)
|
248
265
|
idling_managers: List[Member] = [member for member in self.members if member.is_idling and member.is_manager == True]
|
249
266
|
idling_members: List[Member] = [member for member in self.members if member.is_idling and member.is_manager == False]
|
250
|
-
unassigned_tasks: List[Task] = self.
|
251
|
-
|
267
|
+
unassigned_tasks: List[Task] = self.network_tasks + self.member_tasks_without_agent if self.network_tasks else self.member_tasks_without_agent
|
268
|
+
new_members: List[Member] = []
|
252
269
|
|
253
270
|
if idling_managers:
|
254
271
|
idling_managers[0].tasks.extend(unassigned_tasks)
|
@@ -257,9 +274,9 @@ class Team(BaseModel):
|
|
257
274
|
idling_members[0].tasks.extend(unassigned_tasks)
|
258
275
|
|
259
276
|
else:
|
260
|
-
|
261
|
-
if
|
262
|
-
self.members +=
|
277
|
+
new_members = self.handle_assigning_agents(unassigned_tasks=unassigned_tasks)
|
278
|
+
if new_members:
|
279
|
+
self.members += new_members
|
263
280
|
|
264
281
|
|
265
282
|
# task execution
|
@@ -278,34 +295,9 @@ class Team(BaseModel):
|
|
278
295
|
return task_outputs
|
279
296
|
|
280
297
|
|
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
298
|
def _calculate_usage_metrics(self) -> UsageMetrics:
|
307
299
|
"""
|
308
|
-
Calculate and return the usage metrics that consumed by the
|
300
|
+
Calculate and return the usage metrics that consumed by the agent network.
|
309
301
|
"""
|
310
302
|
total_usage_metrics = UsageMetrics()
|
311
303
|
|
@@ -325,12 +317,12 @@ class Team(BaseModel):
|
|
325
317
|
return total_usage_metrics
|
326
318
|
|
327
319
|
|
328
|
-
def _execute_tasks(self, tasks: List[Task], start_index: Optional[int] = 0, was_replayed: bool = False) ->
|
320
|
+
def _execute_tasks(self, tasks: List[Task], start_index: Optional[int] = 0, was_replayed: bool = False) -> NetworkOutput:
|
329
321
|
"""
|
330
|
-
Executes tasks sequentially and returns the final output in
|
322
|
+
Executes tasks sequentially and returns the final output in NetworkOutput class.
|
331
323
|
When we have a manager agent, we will start from executing manager agent's tasks.
|
332
324
|
Priority:
|
333
|
-
1.
|
325
|
+
1. Network tasks > 2. Manager task > 3. Member tasks (in order of index)
|
334
326
|
"""
|
335
327
|
|
336
328
|
task_outputs: List[TaskOutput] = []
|
@@ -367,52 +359,58 @@ class Team(BaseModel):
|
|
367
359
|
else:
|
368
360
|
context = create_raw_outputs(tasks=[task,], task_outputs=([last_sync_output,] if last_sync_output else [] ))
|
369
361
|
task_output = task.execute(agent=responsible_agent, context=context)
|
362
|
+
|
370
363
|
if self.managers and responsible_agent in [manager.agent for manager in self.managers]:
|
371
364
|
lead_task_output = task_output
|
372
365
|
|
373
366
|
task_outputs.append(task_output)
|
374
|
-
# self._process_task_result(task, task_output)
|
375
367
|
task._store_execution_log(task_index, was_replayed, self._inputs)
|
376
368
|
|
377
369
|
|
378
370
|
if futures:
|
379
371
|
task_outputs = self._process_async_tasks(futures, was_replayed)
|
380
372
|
|
381
|
-
|
373
|
+
if not task_outputs:
|
374
|
+
self._logger.log(level="error", message="Missing task outputs.", color="red")
|
375
|
+
raise ValueError("Missing task outputs")
|
376
|
+
|
377
|
+
final_task_output = lead_task_output if lead_task_output is not None else task_outputs[0] #! REFINEME
|
382
378
|
|
379
|
+
# token_usage = self._calculate_usage_metrics() #! combining with Eval
|
383
380
|
|
384
|
-
|
381
|
+
return NetworkOutput(
|
382
|
+
network_id=self.id,
|
383
|
+
raw=final_task_output.raw,
|
384
|
+
json_dict=final_task_output.json_dict,
|
385
|
+
pydantic=final_task_output.pydantic,
|
386
|
+
task_outputs=task_outputs,
|
387
|
+
# token_usage=token_usage,
|
388
|
+
)
|
389
|
+
|
390
|
+
|
391
|
+
def launch(self, kwargs_pre: Optional[Dict[str, str]] = None, kwargs_post: Optional[Dict[str, Any]] = None) -> NetworkOutput:
|
385
392
|
"""
|
386
|
-
|
387
|
-
0. Assign an agent to a task - using conditions (manager prioritizes team_tasks) and planner_llm.
|
388
|
-
1. Address `pre_launch_callbacks` if any.
|
389
|
-
2. Handle team members' tasks in accordance with the process.
|
390
|
-
3. Address `post_launch_callbacks` if any.
|
393
|
+
Launch the agent network - executing tasks and recording their outputs.
|
391
394
|
"""
|
392
395
|
|
393
396
|
metrics: List[UsageMetrics] = []
|
394
397
|
|
395
|
-
if self.
|
398
|
+
if self.network_tasks or self.member_tasks_without_agent:
|
396
399
|
self._assign_tasks()
|
397
400
|
|
398
401
|
if kwargs_pre is not None:
|
399
402
|
for func in self.pre_launch_callbacks:
|
400
403
|
func(**kwargs_pre)
|
401
404
|
|
402
|
-
# self._execution_span = self._telemetry.team_execution_span(self, inputs)
|
403
|
-
# self._task_output_handler.reset()
|
404
|
-
# self._logging_color = "bold_purple"
|
405
|
-
# i18n = I18N(prompt_file=self.prompt_file)
|
406
|
-
|
407
405
|
for member in self.members:
|
408
406
|
agent = member.agent
|
409
|
-
agent.
|
407
|
+
agent.network = self
|
410
408
|
|
411
409
|
if self.step_callback:
|
412
410
|
agent.callbacks.append(self.step_callback)
|
413
411
|
|
414
412
|
if self.process is None:
|
415
|
-
self.process = TaskHandlingProcess.
|
413
|
+
self.process = TaskHandlingProcess.SEQUENT
|
416
414
|
|
417
415
|
result = self._execute_tasks(self.tasks)
|
418
416
|
|
@@ -443,7 +441,7 @@ class Team(BaseModel):
|
|
443
441
|
@property
|
444
442
|
def manager_tasks(self) -> List[Task]:
|
445
443
|
"""
|
446
|
-
Tasks (incl.
|
444
|
+
Tasks (incl. network tasks) handled by managers in the agent network.
|
447
445
|
"""
|
448
446
|
res = list()
|
449
447
|
|
@@ -458,22 +456,22 @@ class Team(BaseModel):
|
|
458
456
|
@property
|
459
457
|
def tasks(self) -> List[Task]:
|
460
458
|
"""
|
461
|
-
Return all the tasks that the
|
462
|
-
1.
|
459
|
+
Return all the tasks that the agent network needs to handle in order of priority:
|
460
|
+
1. network_tasks, -> assigned to the member
|
463
461
|
2. manager_task,
|
464
462
|
3. members' tasks
|
465
463
|
"""
|
466
464
|
|
467
|
-
|
465
|
+
network_tasks = self.network_tasks
|
468
466
|
manager_tasks = self.manager_tasks
|
469
467
|
member_tasks = []
|
470
468
|
|
471
469
|
for member in self.members:
|
472
470
|
if member.is_manager == False and member.tasks:
|
473
|
-
a = [item for item in member.tasks if item not in
|
471
|
+
a = [item for item in member.tasks if item not in network_tasks and item not in manager_tasks]
|
474
472
|
member_tasks += a
|
475
473
|
|
476
|
-
return
|
474
|
+
return network_tasks + manager_tasks + member_tasks
|
477
475
|
|
478
476
|
|
479
477
|
@property
|
@@ -1,7 +1,7 @@
|
|
1
1
|
import uuid
|
2
2
|
from abc import ABC
|
3
|
-
from datetime import
|
4
|
-
from typing import Any, Dict, List,
|
3
|
+
from datetime import datetime
|
4
|
+
from typing import Any, Dict, List, Optional
|
5
5
|
from typing_extensions import Self
|
6
6
|
from pydantic import UUID4, InstanceOf, BaseModel, ConfigDict, Field, field_validator, model_validator
|
7
7
|
from pydantic_core import PydanticCustomError
|
@@ -9,7 +9,7 @@ from pydantic_core import PydanticCustomError
|
|
9
9
|
from versionhq.clients.product.model import Product
|
10
10
|
from versionhq.clients.customer.model import Customer
|
11
11
|
from versionhq.agent.model import Agent
|
12
|
-
from versionhq.
|
12
|
+
from versionhq.agent_network.model import AgentNetwork
|
13
13
|
from versionhq.tool.composio_tool_vars import ComposioAppName
|
14
14
|
|
15
15
|
|
@@ -102,8 +102,8 @@ class MessagingWorkflow(ABC, BaseModel):
|
|
102
102
|
messaging_components: List[MessagingComponent] = Field(default_factory=list, description="store messaging components in the workflow")
|
103
103
|
|
104
104
|
# responsible tean or agents
|
105
|
-
|
106
|
-
agents: Optional[List[Agent]] = Field(default=None, description="store responsible agents. None when the
|
105
|
+
agent_network: Optional[AgentNetwork] = Field(default=None, description="store a responsibile agent network to autopilot the workflow")
|
106
|
+
agents: Optional[List[Agent]] = Field(default=None, description="store responsible agents. None when the `agent_network` fields has a value")
|
107
107
|
|
108
108
|
# metrics
|
109
109
|
destination: Optional[ComposioAppName | str] = Field(default=None, description="destination service to launch the workflow")
|
@@ -140,16 +140,16 @@ class MessagingWorkflow(ABC, BaseModel):
|
|
140
140
|
return self
|
141
141
|
|
142
142
|
|
143
|
-
def
|
143
|
+
def reassign_agent(self, agents: List[Agent] = None, agent_network: AgentNetwork = None) -> None:
|
144
144
|
"""
|
145
|
-
|
145
|
+
Switch agents
|
146
146
|
"""
|
147
147
|
|
148
|
-
if not agents and not
|
149
|
-
raise ValueError("
|
148
|
+
if not agents and not agent_network:
|
149
|
+
raise ValueError("Missing agent or agent network to assign.")
|
150
150
|
|
151
151
|
self.agents = agents
|
152
|
-
self.
|
152
|
+
self.agent_network = agent_network
|
153
153
|
self.updated_at = datetime.datetime.now()
|
154
154
|
|
155
155
|
|
versionhq/knowledge/source.py
CHANGED
@@ -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 versionhq[pdfplumber]")
|
269
269
|
|
270
270
|
|
271
271
|
def add(self) -> None:
|
@@ -394,7 +394,7 @@ class ExcelKnowledgeSource(BaseFileKnowledgeSource):
|
|
394
394
|
except:
|
395
395
|
missing_package = str(e).split()[-1]
|
396
396
|
raise ImportError(
|
397
|
-
f"{missing_package} is not installed. Please install it with:
|
397
|
+
f"{missing_package} is not installed. Please install it with: $ uv add versionhq[{missing_package}]"
|
398
398
|
)
|
399
399
|
|
400
400
|
|
@@ -56,7 +56,7 @@ class DoclingSource(BaseKnowledgeSource):
|
|
56
56
|
super().__init__(*args, **kwargs)
|
57
57
|
|
58
58
|
else:
|
59
|
-
raise ImportError("The docling package is required. Please install the package using: $ uv add docling
|
59
|
+
raise ImportError("The docling package is required. Please install the package using: $ uv add versionhq[docling]")
|
60
60
|
# else:
|
61
61
|
# super().__init__(*args, **kwargs)
|
62
62
|
|
versionhq/memory/model.py
CHANGED
@@ -125,7 +125,7 @@ class ShortTermMemory(Memory):
|
|
125
125
|
|
126
126
|
from versionhq.storage.mem0_storage import Mem0Storage
|
127
127
|
except:
|
128
|
-
raise ImportError("Mem0 is not installed. Please install it with
|
128
|
+
raise ImportError("Mem0 is not installed. Please install it with `$ uv add versionhq[mem0ai]`.")
|
129
129
|
|
130
130
|
storage = Mem0Storage(type="stm", agent=agent)
|
131
131
|
else:
|