versionhq 1.2.1.22__py3-none-any.whl → 1.2.2.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 +3 -4
- versionhq/_utils/__init__.py +1 -1
- versionhq/_utils/usage_metrics.py +32 -0
- versionhq/agent/inhouse_agents.py +5 -1
- versionhq/agent/model.py +4 -37
- versionhq/agent_network/formation.py +13 -3
- versionhq/agent_network/model.py +117 -200
- versionhq/llm/model.py +35 -35
- versionhq/memory/model.py +4 -2
- versionhq/storage/task_output_storage.py +51 -16
- versionhq/storage/utils.py +1 -0
- versionhq/task/TEMPLATES/Description.py +6 -1
- versionhq/task/{evaluate.py → evaluation.py} +38 -22
- versionhq/task/model.py +60 -61
- versionhq/task_graph/draft.py +1 -1
- versionhq/task_graph/model.py +73 -48
- {versionhq-1.2.1.22.dist-info → versionhq-1.2.2.1.dist-info}/METADATA +15 -13
- {versionhq-1.2.1.22.dist-info → versionhq-1.2.2.1.dist-info}/RECORD +21 -22
- versionhq/task/log_handler.py +0 -59
- {versionhq-1.2.1.22.dist-info → versionhq-1.2.2.1.dist-info}/LICENSE +0 -0
- {versionhq-1.2.1.22.dist-info → versionhq-1.2.2.1.dist-info}/WHEEL +0 -0
- {versionhq-1.2.1.22.dist-info → versionhq-1.2.2.1.dist-info}/top_level.txt +0 -0
versionhq/__init__.py
CHANGED
@@ -8,7 +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,
|
11
|
+
from versionhq.agent_network.model import AgentNetwork, Formation, Member, TaskHandlingProcess
|
12
12
|
from versionhq.llm.model import LLM
|
13
13
|
from versionhq.llm.llm_vars import LLM_CONTEXT_WINDOW_SIZES, PARAMS, PROVIDERS, MODELS
|
14
14
|
from versionhq.clients.customer.model import Customer
|
@@ -19,7 +19,7 @@ from versionhq.knowledge.source import PDFKnowledgeSource, CSVKnowledgeSource, J
|
|
19
19
|
from versionhq.knowledge.source_docling import DoclingSource
|
20
20
|
from versionhq.task_graph.model import TaskStatus, TaskGraph, Node, Edge, DependencyType
|
21
21
|
from versionhq.task.model import Task, TaskOutput, ResponseField, TaskExecutionType
|
22
|
-
from versionhq.task.
|
22
|
+
from versionhq.task.evaluation import Evaluation, EvaluationItem
|
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
|
@@ -31,12 +31,11 @@ from versionhq.agent_network.formation import form_agent_network
|
|
31
31
|
from versionhq.task_graph.draft import workflow
|
32
32
|
|
33
33
|
|
34
|
-
__version__ = "1.2.1
|
34
|
+
__version__ = "1.2.2.1"
|
35
35
|
__all__ = [
|
36
36
|
"Agent",
|
37
37
|
|
38
38
|
"AgentNetwork",
|
39
|
-
"NetworkOutput",
|
40
39
|
"Formation",
|
41
40
|
"Member",
|
42
41
|
"TaskHandlingProcess",
|
versionhq/_utils/__init__.py
CHANGED
@@ -21,3 +21,35 @@ class UsageMetrics(BaseModel):
|
|
21
21
|
self.cached_prompt_tokens += usage_metrics.cached_prompt_tokens
|
22
22
|
self.completion_tokens += usage_metrics.completion_tokens
|
23
23
|
self.successful_requests += usage_metrics.successful_requests
|
24
|
+
|
25
|
+
|
26
|
+
|
27
|
+
# class TokenProcess:
|
28
|
+
# total_tokens: int = 0
|
29
|
+
# prompt_tokens: int = 0
|
30
|
+
# cached_prompt_tokens: int = 0
|
31
|
+
# completion_tokens: int = 0
|
32
|
+
# successful_requests: int = 0
|
33
|
+
|
34
|
+
# def sum_prompt_tokens(self, tokens: int) -> None:
|
35
|
+
# self.prompt_tokens = self.prompt_tokens + tokens
|
36
|
+
# self.total_tokens = self.total_tokens + tokens
|
37
|
+
|
38
|
+
# def sum_completion_tokens(self, tokens: int) -> None:
|
39
|
+
# self.completion_tokens = self.completion_tokens + tokens
|
40
|
+
# self.total_tokens = self.total_tokens + tokens
|
41
|
+
|
42
|
+
# def sum_cached_prompt_tokens(self, tokens: int) -> None:
|
43
|
+
# self.cached_prompt_tokens = self.cached_prompt_tokens + tokens
|
44
|
+
|
45
|
+
# def sum_successful_requests(self, requests: int) -> None:
|
46
|
+
# self.successful_requests = self.successful_requests + requests
|
47
|
+
|
48
|
+
# def get_summary(self) -> UsageMetrics:
|
49
|
+
# return UsageMetrics(
|
50
|
+
# total_tokens=self.total_tokens,
|
51
|
+
# prompt_tokens=self.prompt_tokens,
|
52
|
+
# cached_prompt_tokens=self.cached_prompt_tokens,
|
53
|
+
# completion_tokens=self.completion_tokens,
|
54
|
+
# successful_requests=self.successful_requests,
|
55
|
+
# )
|
@@ -10,13 +10,15 @@ vhq_client_manager = Agent(
|
|
10
10
|
role="vhq-Client Manager",
|
11
11
|
goal="Efficiently communicate with the client on the task progress",
|
12
12
|
llm=DEFAULT_MODEL_NAME,
|
13
|
+
maxit=1,
|
14
|
+
max_retry_limit=1,
|
13
15
|
with_memory=True,
|
14
16
|
)
|
15
17
|
|
16
18
|
|
17
19
|
vhq_task_evaluator = Agent(
|
18
20
|
role="vhq-Task Evaluator",
|
19
|
-
goal="score the output according to the given evaluation criteria.",
|
21
|
+
goal="score the output according to the given evaluation criteria, taking a step by step approach.",
|
20
22
|
llm=DEFAULT_MODEL_NAME,
|
21
23
|
llm_config=dict(top_p=0.8, top_k=30, max_tokens=5000, temperature=0.9),
|
22
24
|
maxit=1,
|
@@ -45,4 +47,6 @@ vhq_agent_creator = Agent(
|
|
45
47
|
role="vhq-Agent Creator",
|
46
48
|
goal="build an agent that can handle the given task",
|
47
49
|
llm="gemini/gemini-2.0-flash-exp",
|
50
|
+
maxit=1,
|
51
|
+
max_retry_limit=1,
|
48
52
|
)
|
versionhq/agent/model.py
CHANGED
@@ -14,7 +14,6 @@ from versionhq.memory.contextual_memory import ContextualMemory
|
|
14
14
|
from versionhq.memory.model import ShortTermMemory, LongTermMemory, UserMemory
|
15
15
|
from versionhq._utils.logger import Logger
|
16
16
|
from versionhq.agent.rpm_controller import RPMController
|
17
|
-
from versionhq._utils.usage_metrics import UsageMetrics
|
18
17
|
from versionhq._utils.process_config import process_config
|
19
18
|
|
20
19
|
|
@@ -22,37 +21,6 @@ load_dotenv(override=True)
|
|
22
21
|
T = TypeVar("T", bound="Agent")
|
23
22
|
|
24
23
|
|
25
|
-
class TokenProcess:
|
26
|
-
total_tokens: int = 0
|
27
|
-
prompt_tokens: int = 0
|
28
|
-
cached_prompt_tokens: int = 0
|
29
|
-
completion_tokens: int = 0
|
30
|
-
successful_requests: int = 0
|
31
|
-
|
32
|
-
def sum_prompt_tokens(self, tokens: int) -> None:
|
33
|
-
self.prompt_tokens = self.prompt_tokens + tokens
|
34
|
-
self.total_tokens = self.total_tokens + tokens
|
35
|
-
|
36
|
-
def sum_completion_tokens(self, tokens: int) -> None:
|
37
|
-
self.completion_tokens = self.completion_tokens + tokens
|
38
|
-
self.total_tokens = self.total_tokens + tokens
|
39
|
-
|
40
|
-
def sum_cached_prompt_tokens(self, tokens: int) -> None:
|
41
|
-
self.cached_prompt_tokens = self.cached_prompt_tokens + tokens
|
42
|
-
|
43
|
-
def sum_successful_requests(self, requests: int) -> None:
|
44
|
-
self.successful_requests = self.successful_requests + requests
|
45
|
-
|
46
|
-
def get_summary(self) -> UsageMetrics:
|
47
|
-
return UsageMetrics(
|
48
|
-
total_tokens=self.total_tokens,
|
49
|
-
prompt_tokens=self.prompt_tokens,
|
50
|
-
cached_prompt_tokens=self.cached_prompt_tokens,
|
51
|
-
completion_tokens=self.completion_tokens,
|
52
|
-
successful_requests=self.successful_requests,
|
53
|
-
)
|
54
|
-
|
55
|
-
|
56
24
|
# @track_agent()
|
57
25
|
class Agent(BaseModel):
|
58
26
|
"""
|
@@ -62,7 +30,6 @@ class Agent(BaseModel):
|
|
62
30
|
__hash__ = object.__hash__
|
63
31
|
_rpm_controller: Optional[RPMController] = PrivateAttr(default=None)
|
64
32
|
_request_within_rpm_limit: Any = PrivateAttr(default=None)
|
65
|
-
_token_process: TokenProcess = PrivateAttr(default_factory=TokenProcess)
|
66
33
|
_times_executed: int = PrivateAttr(default=0)
|
67
34
|
_logger_config: Dict[str, Any] = PrivateAttr(default=dict(verbose=True, info_file_save=True))
|
68
35
|
config: Optional[Dict[str, Any]] = Field(default=None, exclude=True, description="values to add to the Agent class")
|
@@ -92,7 +59,7 @@ class Agent(BaseModel):
|
|
92
59
|
user_prompt_template: Optional[str] = Field(default=None, description="abs. file path to user prompt template")
|
93
60
|
|
94
61
|
# task execution rules
|
95
|
-
networks: Optional[List[Any]] = Field(default_factory=list, description="store a list of agent networks that the agent
|
62
|
+
networks: Optional[List[Any]] = Field(default_factory=list, description="store a list of agent networks that the agent belongs to as a member")
|
96
63
|
allow_delegation: bool = Field(default=False, description="whether to delegate the task to another agent")
|
97
64
|
max_retry_limit: int = Field(default=2, description="max. number of task retries when an error occurs")
|
98
65
|
maxit: Optional[int] = Field(default=25, description="max. number of total optimization loops conducted when an error occurs")
|
@@ -428,10 +395,10 @@ class Agent(BaseModel):
|
|
428
395
|
|
429
396
|
if tool_res_as_final:
|
430
397
|
raw_response = self.func_calling_llm.call(messages=messages, tools=tools, tool_res_as_final=True)
|
431
|
-
task.
|
398
|
+
task._tokens = self.func_calling_llm._tokens
|
432
399
|
else:
|
433
400
|
raw_response = self.llm.call(messages=messages, response_format=response_format, tools=tools)
|
434
|
-
task.
|
401
|
+
task._tokens = self.llm._tokens
|
435
402
|
|
436
403
|
task_execution_counter += 1
|
437
404
|
Logger(**self._logger_config, filename=self.key).log(level="info", message=f"Agent response: {raw_response}", color="green")
|
@@ -446,7 +413,7 @@ class Agent(BaseModel):
|
|
446
413
|
self._rpm_controller.check_or_wait()
|
447
414
|
|
448
415
|
raw_response = self.llm.call(messages=messages, response_format=response_format, tools=tools)
|
449
|
-
task.
|
416
|
+
task._tokens = self.llm._tokens
|
450
417
|
iterations += 1
|
451
418
|
|
452
419
|
task_execution_counter += 1
|
@@ -61,7 +61,7 @@ def form_agent_network(
|
|
61
61
|
# try:
|
62
62
|
prompt_formation = formation.name if formation and isinstance(formation, Formation) else f"Select the best formation to effectively execute the tasks from the given Enum sets: {str(Formation.__dict__)}."
|
63
63
|
|
64
|
-
prompt_expected_outcome = expected_outcome if isinstance(expected_outcome, str) else expected_outcome.
|
64
|
+
prompt_expected_outcome = expected_outcome if isinstance(expected_outcome, str) else str(expected_outcome.model_dump()) if type(expected_outcome) == BaseModel else ""
|
65
65
|
|
66
66
|
class Outcome(BaseModel):
|
67
67
|
formation: Enum
|
@@ -76,7 +76,7 @@ def form_agent_network(
|
|
76
76
|
)
|
77
77
|
|
78
78
|
if agents:
|
79
|
-
vhq_task.description += "
|
79
|
+
vhq_task.description += "You MUST add the following agents' roles in the network formation: " + ", ".join([agent.role for agent in agents if isinstance(agent, Agent)])
|
80
80
|
|
81
81
|
res = vhq_task.execute(agent=vhq_formation_planner, context=context)
|
82
82
|
|
@@ -88,7 +88,17 @@ def form_agent_network(
|
|
88
88
|
members = []
|
89
89
|
leader = str(res.pydantic.leader_agent) if res.pydantic else str(res.json_dict["leader_agent"])
|
90
90
|
|
91
|
-
|
91
|
+
agent_roles = res.pydantic.agent_roles if res.pydantic else res.json_dict["agent_roles"]
|
92
|
+
created_agents = [Agent(role=str(item), goal=str(item)) for item in agent_roles]
|
93
|
+
|
94
|
+
if agents:
|
95
|
+
for i, agent in enumerate(created_agents):
|
96
|
+
matches = [item for item in agents if item.role == agent.role]
|
97
|
+
if matches:
|
98
|
+
created_agents[i] = matches[0]
|
99
|
+
else:
|
100
|
+
pass
|
101
|
+
|
92
102
|
created_tasks = []
|
93
103
|
|
94
104
|
if res.pydantic:
|
versionhq/agent_network/model.py
CHANGED
@@ -4,15 +4,18 @@ from enum import Enum
|
|
4
4
|
from concurrent.futures import Future
|
5
5
|
from hashlib import md5
|
6
6
|
from typing import Any, Dict, List, Callable, Optional, Tuple
|
7
|
+
from typing_extensions import Self
|
8
|
+
|
7
9
|
from pydantic import UUID4, BaseModel, Field, PrivateAttr, field_validator, model_validator
|
8
10
|
from pydantic._internal._generate_schema import GenerateSchema
|
9
11
|
from pydantic_core import PydanticCustomError, core_schema
|
10
12
|
|
13
|
+
|
11
14
|
from versionhq.agent.model import Agent
|
12
15
|
from versionhq.task.model import Task, TaskOutput, TaskExecutionType, ResponseField
|
13
|
-
from versionhq.
|
16
|
+
from versionhq.task_graph.model import TaskGraph, Node, Edge, TaskStatus, DependencyType
|
14
17
|
from versionhq._utils.logger import Logger
|
15
|
-
from versionhq.
|
18
|
+
# from versionhq.recording.usage_metrics import UsageMetrics
|
16
19
|
|
17
20
|
|
18
21
|
initial_match_type = GenerateSchema.match_type
|
@@ -40,36 +43,9 @@ class TaskHandlingProcess(str, Enum):
|
|
40
43
|
A class representing task handling processes to tackle multiple tasks.
|
41
44
|
When the agent network has multiple tasks that connect with edges, follow the edge conditions.
|
42
45
|
"""
|
43
|
-
|
44
|
-
|
45
|
-
CONSENSUAL = 3 # either from
|
46
|
-
|
47
|
-
|
48
|
-
class NetworkOutput(TaskOutput):
|
49
|
-
"""
|
50
|
-
A class to store output from the network, inherited from TaskOutput class.
|
51
|
-
"""
|
52
|
-
|
53
|
-
network_id: UUID4 = Field(default_factory=uuid.uuid4, frozen=True, description="store the network ID as an identifier that generate the output")
|
54
|
-
task_description: str = Field(default=None, description="store initial request (task description) from the client")
|
55
|
-
task_outputs: list[TaskOutput] = Field(default=list, description="store TaskOutput objects of all tasks that the network has executed")
|
56
|
-
# token_usage: UsageMetrics = Field(default=dict, description="processed token summary")
|
57
|
-
|
58
|
-
def return_all_task_outputs(self) -> List[Dict[str, Any]]:
|
59
|
-
res = [output.json_dict for output in self.task_outputs]
|
60
|
-
return res
|
61
|
-
|
62
|
-
def __str__(self):
|
63
|
-
return (str(self.pydantic) if self.pydantic else str(self.json_dict) if self.json_dict else self.raw)
|
64
|
-
|
65
|
-
def __getitem__(self, key):
|
66
|
-
if self.pydantic and hasattr(self.pydantic, key):
|
67
|
-
return getattr(self.pydantic, key)
|
68
|
-
elif self.json_dict and key in self.json_dict:
|
69
|
-
return self.json_dict[key]
|
70
|
-
else:
|
71
|
-
raise KeyError(f"Key '{key}' not found in the output.")
|
72
|
-
|
46
|
+
HIERARCHY = 1
|
47
|
+
SEQUENTIAL = 2
|
48
|
+
CONSENSUAL = 3 # either from manager class agents from diff network or human. need to define a trigger
|
73
49
|
|
74
50
|
|
75
51
|
class Member(BaseModel):
|
@@ -88,10 +64,7 @@ class Member(BaseModel):
|
|
88
64
|
|
89
65
|
|
90
66
|
class AgentNetwork(BaseModel):
|
91
|
-
"""
|
92
|
-
A class to store a agent network with agent members and their tasks.
|
93
|
-
Tasks can be 1. multiple individual tasks, 2. multiple dependant tasks connected via TaskGraph, and 3. hybrid.
|
94
|
-
"""
|
67
|
+
"""A Pydantic class to store an agent network with agent members and tasks."""
|
95
68
|
|
96
69
|
__hash__ = object.__hash__
|
97
70
|
_execution_span: Any = PrivateAttr()
|
@@ -103,27 +76,21 @@ class AgentNetwork(BaseModel):
|
|
103
76
|
formation: Optional[Formation] = Field(default=None)
|
104
77
|
should_reform: bool = Field(default=False, description="True if task exe. failed or eval scores below threshold")
|
105
78
|
|
106
|
-
|
107
|
-
network_tasks: Optional[List[Task]] = Field(default_factory=list, description="tasks without dedicated agents")
|
79
|
+
network_tasks: Optional[List[Task]] = Field(default_factory=list, description="tasks without dedicated agents - network's common tasks")
|
108
80
|
|
109
81
|
# task execution rules
|
110
|
-
prompt_file: str = Field(default="", description="absolute path to the prompt
|
111
|
-
process: TaskHandlingProcess = Field(default=TaskHandlingProcess.
|
82
|
+
prompt_file: str = Field(default="", description="absolute file path to the prompt file that stores jsonified prompts")
|
83
|
+
process: TaskHandlingProcess = Field(default=TaskHandlingProcess.SEQUENTIAL)
|
84
|
+
consent_trigger: Optional[Callable] = Field(default=None, description="returns bool")
|
112
85
|
|
113
86
|
# callbacks
|
114
|
-
pre_launch_callbacks: List[Callable[
|
115
|
-
|
116
|
-
|
117
|
-
)
|
118
|
-
post_launch_callbacks: List[Callable[[NetworkOutput], NetworkOutput]] = Field(
|
119
|
-
default_factory=list,
|
120
|
-
description="list of callback functions to be executed after the network launch. i.e., store the result in repo"
|
121
|
-
)
|
122
|
-
step_callback: Optional[Any] = Field(default=None, description="callback to be executed after each step for all agents execution")
|
87
|
+
pre_launch_callbacks: List[Callable[..., Any]]= Field(default_factory=list, description="list of callback funcs called before the network launch")
|
88
|
+
post_launch_callbacks: List[Callable[..., Any]] = Field(default_factory=list, description="list of callback funcs called after the network launch")
|
89
|
+
step_callback: Optional[Any] = Field(default=None, description="callback to be executed after each step of all agents execution")
|
123
90
|
|
124
91
|
cache: bool = Field(default=True)
|
125
92
|
execution_logs: List[Dict[str, Any]] = Field(default_factory=list, description="list of execution logs of the tasks handled by members")
|
126
|
-
usage_metrics: Optional[UsageMetrics] = Field(default=None, description="usage metrics for all the llm executions")
|
93
|
+
# usage_metrics: Optional[UsageMetrics] = Field(default=None, description="usage metrics for all the llm executions")
|
127
94
|
|
128
95
|
|
129
96
|
def __name__(self) -> str:
|
@@ -138,6 +105,24 @@ class AgentNetwork(BaseModel):
|
|
138
105
|
raise PydanticCustomError("may_not_set_field", "The 'id' field cannot be set by the user.", {})
|
139
106
|
|
140
107
|
|
108
|
+
@model_validator(mode="after")
|
109
|
+
def validate_process(self) -> Self:
|
110
|
+
if self.process == TaskHandlingProcess.CONSENSUAL and not self.consent_trigger:
|
111
|
+
Logger().log(level="error", message="Need to define the consent trigger function that returns bool", color="red")
|
112
|
+
raise PydanticCustomError("invalid_process", "Need to define the consent trigger function that returns bool", {})
|
113
|
+
|
114
|
+
return self
|
115
|
+
|
116
|
+
|
117
|
+
@model_validator(mode="after")
|
118
|
+
def set_up_network_for_members(self) -> Self:
|
119
|
+
if self.members:
|
120
|
+
for member in self.members:
|
121
|
+
member.agent.networks.append(self)
|
122
|
+
|
123
|
+
return self
|
124
|
+
|
125
|
+
|
141
126
|
@model_validator(mode="after")
|
142
127
|
def assess_tasks(self):
|
143
128
|
"""
|
@@ -181,7 +166,7 @@ class AgentNetwork(BaseModel):
|
|
181
166
|
"""
|
182
167
|
Sequential task processing without any network_tasks require a task-agent pairing.
|
183
168
|
"""
|
184
|
-
if self.process == TaskHandlingProcess.
|
169
|
+
if self.process == TaskHandlingProcess.SEQUENTIAL and self.network_tasks is None:
|
185
170
|
for task in self.tasks:
|
186
171
|
if not [member.task == task for member in self.members]:
|
187
172
|
Logger().log(level="error", message=f"The following task needs a dedicated agent to be assinged: {task.description}", color="red")
|
@@ -209,11 +194,8 @@ class AgentNetwork(BaseModel):
|
|
209
194
|
return self
|
210
195
|
|
211
196
|
|
212
|
-
|
213
|
-
|
214
|
-
"""
|
215
|
-
Build an agent and assign it with a task. Return a list of Member connecting the agent created and the task given.
|
216
|
-
"""
|
197
|
+
def _generate_agents(self, unassigned_tasks: List[Task]) -> List[Member]:
|
198
|
+
"""Generates agents as members in the network."""
|
217
199
|
|
218
200
|
from versionhq.agent.inhouse_agents import vhq_agent_creator
|
219
201
|
|
@@ -221,10 +203,7 @@ class AgentNetwork(BaseModel):
|
|
221
203
|
|
222
204
|
for unassgined_task in unassigned_tasks:
|
223
205
|
task = Task(
|
224
|
-
description=f""
|
225
|
-
Based on the following task summary, draft a AI agent's role and goal in concise manner.
|
226
|
-
Task summary: {unassgined_task.summary}
|
227
|
-
""",
|
206
|
+
description=f"Based on the following task summary, draft an agent's role and goal in concise manner. Task summary: {unassgined_task.summary}",
|
228
207
|
response_fields=[
|
229
208
|
ResponseField(title="goal", data_type=str, required=True),
|
230
209
|
ResponseField(title="role", data_type=str, required=True),
|
@@ -242,187 +221,125 @@ class AgentNetwork(BaseModel):
|
|
242
221
|
return new_member_list
|
243
222
|
|
244
223
|
|
245
|
-
def _get_responsible_agent(self, task: Task) -> Agent | None:
|
246
|
-
if task is None:
|
247
|
-
return None
|
248
|
-
else:
|
249
|
-
for member in self.members:
|
250
|
-
if member.tasks and [item for item in member.tasks if item.id == task.id]:
|
251
|
-
return member.agent
|
252
|
-
|
253
|
-
return None
|
254
|
-
|
255
|
-
|
256
224
|
def _assign_tasks(self) -> None:
|
257
|
-
"""
|
258
|
-
Form a agent network considering given agents and tasks, and update `self.members` field:
|
259
|
-
1. Idling managers to take the network tasks.
|
260
|
-
2. Idling members to take the remaining tasks starting from the network tasks to member tasks.
|
261
|
-
3. Create agents to handle the rest tasks.
|
262
|
-
"""
|
225
|
+
"""Assigns tasks to member agents."""
|
263
226
|
|
264
227
|
idling_managers: List[Member] = [member for member in self.members if member.is_idling and member.is_manager == True]
|
265
228
|
idling_members: List[Member] = [member for member in self.members if member.is_idling and member.is_manager == False]
|
266
|
-
unassigned_tasks: List[Task] = self.network_tasks + self.
|
229
|
+
unassigned_tasks: List[Task] = self.network_tasks + self.unassigned_member_tasks if self.network_tasks else self.unassigned_member_tasks
|
267
230
|
new_members: List[Member] = []
|
268
231
|
|
269
|
-
if
|
270
|
-
|
271
|
-
|
272
|
-
elif idling_members:
|
273
|
-
idling_members[0].tasks.extend(unassigned_tasks)
|
232
|
+
if not unassigned_tasks:
|
233
|
+
return
|
274
234
|
|
275
235
|
else:
|
276
|
-
|
277
|
-
|
278
|
-
self.members += new_members
|
279
|
-
|
280
|
-
|
281
|
-
# task execution
|
282
|
-
def _process_async_tasks(self, futures: List[Tuple[Task, Future[TaskOutput], int]], was_replayed: bool = False) -> List[TaskOutput]:
|
283
|
-
"""
|
284
|
-
When we have `Future` tasks, updated task outputs and task execution logs accordingly.
|
285
|
-
"""
|
236
|
+
if idling_managers:
|
237
|
+
idling_managers[0].tasks.extend(unassigned_tasks)
|
286
238
|
|
287
|
-
|
239
|
+
elif idling_members:
|
240
|
+
idling_members[0].tasks.extend(unassigned_tasks)
|
288
241
|
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
return task_outputs
|
242
|
+
else:
|
243
|
+
new_members = self._generate_agents(unassigned_tasks=unassigned_tasks)
|
244
|
+
if new_members:
|
245
|
+
self.members += new_members
|
295
246
|
|
296
247
|
|
297
|
-
def
|
298
|
-
|
299
|
-
|
300
|
-
"""
|
301
|
-
total_usage_metrics = UsageMetrics()
|
248
|
+
def _get_responsible_agent(self, task: Task) -> Agent | None:
|
249
|
+
if not task:
|
250
|
+
return None
|
302
251
|
|
252
|
+
self._assign_tasks()
|
303
253
|
for member in self.members:
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
last_sync_output = task.output
|
340
|
-
continue
|
341
|
-
|
342
|
-
responsible_agent = self._get_responsible_agent(task)
|
343
|
-
if responsible_agent is None:
|
344
|
-
self._assign_tasks()
|
345
|
-
|
346
|
-
## commented out - this will be handled by node objects
|
347
|
-
# if isinstance(task, ConditionalTask):
|
348
|
-
# skipped_task_output = task._handle_conditional_task(task_outputs, futures, task_index, was_replayed)
|
349
|
-
# if skipped_task_output:
|
350
|
-
# continue
|
351
|
-
|
352
|
-
# self._log_task_start(task, responsible_agent)
|
353
|
-
|
354
|
-
if task.execution_type == TaskExecutionType.ASYNC:
|
355
|
-
context = create_raw_outputs(tasks=[task, ], task_outputs=([last_sync_output,] if last_sync_output else []))
|
356
|
-
future = task._execute_async(agent=responsible_agent, context=context)
|
357
|
-
futures.append((task, future, task_index))
|
358
|
-
else:
|
359
|
-
context = create_raw_outputs(tasks=[task,], task_outputs=([last_sync_output,] if last_sync_output else [] ))
|
360
|
-
task_output = task.execute(agent=responsible_agent, context=context)
|
361
|
-
|
362
|
-
if self.managers and responsible_agent in [manager.agent for manager in self.managers]:
|
363
|
-
lead_task_output = task_output
|
364
|
-
|
365
|
-
task_outputs.append(task_output)
|
366
|
-
task._store_execution_log(task_index, was_replayed, self._inputs)
|
254
|
+
if member.tasks and [item for item in member.tasks if item.id == task.id]:
|
255
|
+
return member.agent
|
256
|
+
|
257
|
+
|
258
|
+
def _execute_tasks(self, tasks: List[Task], start_index: Optional[int] = None) -> Tuple[TaskOutput, TaskGraph]:
|
259
|
+
"""Executes tasks and returns TaskOutput object as concl or latest response in the network."""
|
260
|
+
res, task_graph = None, None
|
261
|
+
|
262
|
+
if len(tasks) == 1:
|
263
|
+
task = self.tasks[0]
|
264
|
+
responsible_agent = self._get_responsible_agent(task=task)
|
265
|
+
res = task.execute(agent=responsible_agent)
|
266
|
+
return res, task_graph
|
267
|
+
|
268
|
+
nodes = [
|
269
|
+
Node(
|
270
|
+
task=task,
|
271
|
+
assigned_to=self._get_responsible_agent(task=task),
|
272
|
+
status=TaskStatus.NOT_STARTED if not start_index or i >= start_index else TaskStatus.COMPLETED,
|
273
|
+
) for i, task in enumerate(tasks)
|
274
|
+
]
|
275
|
+
task_graph = TaskGraph(nodes={node.identifier: node for node in nodes})
|
276
|
+
|
277
|
+
for i in range(0, len(nodes) - 1):
|
278
|
+
task_graph.add_edge(
|
279
|
+
source=nodes[i].identifier,
|
280
|
+
target=nodes[i+1].identifier,
|
281
|
+
edge=Edge(
|
282
|
+
weight=3 if nodes[i].task in self.manager_tasks else 1,
|
283
|
+
dependency_type=DependencyType.FINISH_TO_START if self.process == TaskHandlingProcess.HIERARCHY else DependencyType.START_TO_START,
|
284
|
+
required=bool(self.process == TaskHandlingProcess.CONSENSUAL),
|
285
|
+
condition=self.consent_trigger,
|
286
|
+
data_transfer=bool(self.process == TaskHandlingProcess.HIERARCHY),
|
287
|
+
)
|
288
|
+
)
|
367
289
|
|
290
|
+
if start_index is not None:
|
291
|
+
res, _ = task_graph.activate(target=nodes[start_index].indentifier)
|
368
292
|
|
369
|
-
|
370
|
-
|
293
|
+
else:
|
294
|
+
res, _ = task_graph.activate()
|
371
295
|
|
372
|
-
if not
|
296
|
+
if not res:
|
373
297
|
Logger().log(level="error", message="Missing task outputs.", color="red")
|
374
298
|
raise ValueError("Missing task outputs")
|
375
299
|
|
376
|
-
|
300
|
+
return res, task_graph
|
377
301
|
|
378
|
-
# token_usage = self._calculate_usage_metrics() #! combining with Eval
|
379
302
|
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
json_dict=final_task_output.json_dict,
|
384
|
-
pydantic=final_task_output.pydantic,
|
385
|
-
task_outputs=task_outputs,
|
386
|
-
# token_usage=token_usage,
|
387
|
-
)
|
388
|
-
|
389
|
-
|
390
|
-
def launch(self, kwargs_pre: Optional[Dict[str, str]] = None, kwargs_post: Optional[Dict[str, Any]] = None) -> NetworkOutput:
|
303
|
+
def launch(
|
304
|
+
self, kwargs_pre: Optional[Dict[str, str]] = None, kwargs_post: Optional[Dict[str, Any]] = None, start_index: int = None
|
305
|
+
) -> Tuple[TaskOutput, TaskGraph]:
|
391
306
|
"""
|
392
307
|
Launch the agent network - executing tasks and recording their outputs.
|
393
308
|
"""
|
394
309
|
|
395
|
-
|
310
|
+
self._assign_tasks()
|
396
311
|
|
397
|
-
if
|
398
|
-
self.
|
399
|
-
|
400
|
-
if kwargs_pre is not None:
|
401
|
-
for func in self.pre_launch_callbacks: # signature check
|
312
|
+
if kwargs_pre:
|
313
|
+
for func in self.pre_launch_callbacks: #! REFINEME - signature check
|
402
314
|
func(**kwargs_pre)
|
403
315
|
|
404
316
|
for member in self.members:
|
405
317
|
agent = member.agent
|
406
|
-
|
318
|
+
|
319
|
+
if not agent.networks:
|
320
|
+
agent.networks.append(self)
|
407
321
|
|
408
322
|
if self.step_callback:
|
409
323
|
agent.callbacks.append(self.step_callback)
|
410
324
|
|
411
325
|
if self.process is None:
|
412
|
-
self.process = TaskHandlingProcess.
|
326
|
+
self.process = TaskHandlingProcess.SEQUENTIAL
|
413
327
|
|
414
|
-
result = self._execute_tasks(self.tasks)
|
328
|
+
result, tg = self._execute_tasks(self.tasks, start_index=start_index)
|
329
|
+
callback_output = None
|
415
330
|
|
416
331
|
for func in self.post_launch_callbacks:
|
417
|
-
|
332
|
+
callback_output = func(result, **kwargs_post)
|
418
333
|
|
419
|
-
|
334
|
+
if callback_output:
|
335
|
+
match result:
|
336
|
+
case TaskOutput():
|
337
|
+
result.callback_output = callback_output
|
420
338
|
|
421
|
-
|
422
|
-
|
423
|
-
self.usage_metrics.add_usage_metrics(metric)
|
339
|
+
case _:
|
340
|
+
pass
|
424
341
|
|
425
|
-
return result
|
342
|
+
return result, tg
|
426
343
|
|
427
344
|
|
428
345
|
@property
|
@@ -474,7 +391,7 @@ class AgentNetwork(BaseModel):
|
|
474
391
|
|
475
392
|
|
476
393
|
@property
|
477
|
-
def
|
394
|
+
def unassigned_member_tasks(self) -> List[Task]:
|
478
395
|
res = list()
|
479
396
|
|
480
397
|
if self.members:
|