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 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, NetworkOutput, Formation, Member, TaskHandlingProcess
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.evaluate import Evaluation, EvaluationItem
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.22"
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",
@@ -1,3 +1,3 @@
1
1
  from versionhq._utils.logger import Logger
2
2
  from versionhq._utils.process_config import process_config
3
- from versionhq._utils.usage_metrics import UsageMetrics
3
+ from versionhq._utils.vars import KNOWLEDGE_DIRECTORY, MAX_FILE_NAME_LENGTH
@@ -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 belong as a member")
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.tokens = self.func_calling_llm._tokens
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.tokens = self.llm._tokens
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.tokens = self.llm._tokens
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.model_dump_json()
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 += "Consider adding following agents in the formation: " + ", ".join([agent.role for agent in agents if isinstance(agent, Agent)])
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
- created_agents = [Agent(role=str(item), goal=str(item)) for item in res.pydantic.agent_roles]
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:
@@ -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.task.formatter import create_raw_outputs
16
+ from versionhq.task_graph.model import TaskGraph, Node, Edge, TaskStatus, DependencyType
14
17
  from versionhq._utils.logger import Logger
15
- from versionhq._utils.usage_metrics import UsageMetrics
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
- SEQUENT = 1
44
- HIERARCHY = 2
45
- CONSENSUAL = 3 # either from managers or peers or (human) - most likely controlled by edge
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
- # formation planning
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 json file")
111
- process: TaskHandlingProcess = Field(default=TaskHandlingProcess.SEQUENT)
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[[Optional[Dict[str, Any]]], Optional[Dict[str, Any]]]] = Field(
115
- default_factory=list,
116
- description="list of callback functions to be executed before the network launch. i.e., adjust inputs"
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.SEQUENT and self.network_tasks is None:
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
- @staticmethod
213
- def handle_assigning_agents(unassigned_tasks: List[Task]) -> List[Member]:
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.member_tasks_without_agent if self.network_tasks else self.member_tasks_without_agent
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 idling_managers:
270
- idling_managers[0].tasks.extend(unassigned_tasks)
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
- new_members = self.handle_assigning_agents(unassigned_tasks=unassigned_tasks)
277
- if new_members:
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
- task_outputs: List[TaskOutput] = []
239
+ elif idling_members:
240
+ idling_members[0].tasks.extend(unassigned_tasks)
288
241
 
289
- for future_task, future, task_index in futures:
290
- task_output = future.result()
291
- task_outputs.append(task_output)
292
- future_task._store_execution_log(task_index, was_replayed)
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 _calculate_usage_metrics(self) -> UsageMetrics:
298
- """
299
- Calculate and return the usage metrics that consumed by the agent network.
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
- agent = member.agent
305
- if hasattr(agent, "_token_process"):
306
- token_sum = agent._token_process.get_summary()
307
- total_usage_metrics.add_usage_metrics(token_sum)
308
-
309
- if self.managers:
310
- for manager in self.managers:
311
- if hasattr(manager.agent, "_token_process"):
312
- token_sum = manager.agent._token_process.get_summary()
313
- total_usage_metrics.add_usage_metrics(token_sum)
314
-
315
- self.usage_metrics = total_usage_metrics
316
- return total_usage_metrics
317
-
318
-
319
- def _execute_tasks(self, tasks: List[Task], start_index: Optional[int] = 0, was_replayed: bool = False) -> NetworkOutput:
320
- """
321
- Executes tasks sequentially and returns the final output in NetworkOutput class.
322
- When we have a manager agent, we will start from executing manager agent's tasks.
323
- Priority:
324
- 1. Network tasks > 2. Manager task > 3. Member tasks (in order of index)
325
- """
326
-
327
- task_outputs: List[TaskOutput] = []
328
- lead_task_output: TaskOutput = None
329
- futures: List[Tuple[Task, Future[TaskOutput], int]] = []
330
- last_sync_output: Optional[TaskOutput] = None
331
-
332
- for task_index, task in enumerate(tasks):
333
- if start_index is not None and task_index < start_index:
334
- if task.output:
335
- if task.execution_type == TaskExecutionType.ASYNC:
336
- task_outputs.append(task.output)
337
- else:
338
- task_outputs = [task.output]
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
- if futures:
370
- task_outputs = self._process_async_tasks(futures, was_replayed)
293
+ else:
294
+ res, _ = task_graph.activate()
371
295
 
372
- if not task_outputs:
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
- final_task_output = lead_task_output if lead_task_output is not None else task_outputs[0] #! REFINEME
300
+ return res, task_graph
377
301
 
378
- # token_usage = self._calculate_usage_metrics() #! combining with Eval
379
302
 
380
- return NetworkOutput(
381
- network_id=self.id,
382
- raw=final_task_output.raw,
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
- metrics: List[UsageMetrics] = []
310
+ self._assign_tasks()
396
311
 
397
- if self.network_tasks or self.member_tasks_without_agent:
398
- self._assign_tasks()
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
- agent.networks.append(self)
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.SEQUENT
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
- result = func(result, **kwargs_post)
332
+ callback_output = func(result, **kwargs_post)
418
333
 
419
- metrics += [member.agent._token_process.get_summary() for member in self.members]
334
+ if callback_output:
335
+ match result:
336
+ case TaskOutput():
337
+ result.callback_output = callback_output
420
338
 
421
- self.usage_metrics = UsageMetrics()
422
- for metric in metrics:
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 member_tasks_without_agent(self) -> List[Task]:
394
+ def unassigned_member_tasks(self) -> List[Task]:
478
395
  res = list()
479
396
 
480
397
  if self.members: