versionhq 1.2.1.22__py3-none-any.whl → 1.2.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- versionhq/__init__.py +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/model.py +193 -189
- 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.0.dist-info}/METADATA +8 -7
- {versionhq-1.2.1.22.dist-info → versionhq-1.2.2.0.dist-info}/RECORD +20 -21
- versionhq/task/log_handler.py +0 -59
- {versionhq-1.2.1.22.dist-info → versionhq-1.2.2.0.dist-info}/LICENSE +0 -0
- {versionhq-1.2.1.22.dist-info → versionhq-1.2.2.0.dist-info}/WHEEL +0 -0
- {versionhq-1.2.1.22.dist-info → versionhq-1.2.2.0.dist-info}/top_level.txt +0 -0
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,212 @@ 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
|
236
|
+
if idling_managers:
|
237
|
+
idling_managers[0].tasks.extend(unassigned_tasks)
|
279
238
|
|
239
|
+
elif idling_members:
|
240
|
+
idling_members[0].tasks.extend(unassigned_tasks)
|
280
241
|
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
"""
|
242
|
+
else:
|
243
|
+
new_members = self._generate_agents(unassigned_tasks=unassigned_tasks)
|
244
|
+
if new_members:
|
245
|
+
self.members += new_members
|
286
246
|
|
287
|
-
task_outputs: List[TaskOutput] = []
|
288
247
|
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
future_task._store_execution_log(task_index, was_replayed)
|
248
|
+
def _get_responsible_agent(self, task: Task) -> Agent | None:
|
249
|
+
if not task:
|
250
|
+
return None
|
293
251
|
|
294
|
-
|
252
|
+
self._assign_tasks()
|
253
|
+
for member in self.members:
|
254
|
+
if member.tasks and [item for item in member.tasks if item.id == task.id]:
|
255
|
+
return member.agent
|
295
256
|
|
296
257
|
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
258
|
+
# task execution
|
259
|
+
# def _process_async_tasks(self, futures: List[Tuple[Task, Future[TaskOutput], int]], was_replayed: bool = False) -> List[TaskOutput]:
|
260
|
+
# """
|
261
|
+
# When we have `Future` tasks, updated task outputs and task execution logs accordingly.
|
262
|
+
# """
|
263
|
+
|
264
|
+
# task_outputs: List[TaskOutput] = []
|
265
|
+
|
266
|
+
# for future_task, future, task_index in futures:
|
267
|
+
# task_output = future.result()
|
268
|
+
# task_outputs.append(task_output)
|
269
|
+
# future_task._store_execution_log(task_index, was_replayed)
|
270
|
+
|
271
|
+
# return task_outputs
|
272
|
+
|
273
|
+
|
274
|
+
# def _calculate_usage_metrics(self) -> UsageMetrics:
|
275
|
+
# """
|
276
|
+
# Calculate and return the usage metrics that consumed by the agent network.
|
277
|
+
# """
|
278
|
+
# total_usage_metrics = UsageMetrics()
|
279
|
+
|
280
|
+
# for member in self.members:
|
281
|
+
# agent = member.agent
|
282
|
+
# if hasattr(agent, "_token_process"):
|
283
|
+
# token_sum = agent._token_process.get_summary()
|
284
|
+
# total_usage_metrics.add_usage_metrics(token_sum)
|
285
|
+
|
286
|
+
# if self.managers:
|
287
|
+
# for manager in self.managers:
|
288
|
+
# if hasattr(manager.agent, "_token_process"):
|
289
|
+
# token_sum = manager.agent._token_process.get_summary()
|
290
|
+
# total_usage_metrics.add_usage_metrics(token_sum)
|
291
|
+
|
292
|
+
# self.usage_metrics = total_usage_metrics
|
293
|
+
# return total_usage_metrics
|
294
|
+
|
295
|
+
|
296
|
+
def _execute_tasks(self, tasks: List[Task], start_index: Optional[int] = None) -> Tuple[TaskOutput, TaskGraph]:
|
297
|
+
"""Executes tasks and returns TaskOutput object as concl or latest response in the network."""
|
298
|
+
res, task_graph = None, None
|
299
|
+
|
300
|
+
if len(tasks) == 1:
|
301
|
+
task = self.tasks[0]
|
302
|
+
responsible_agent = self._get_responsible_agent(task=task)
|
303
|
+
res = task.execute(agent=responsible_agent)
|
304
|
+
return res, task_graph
|
305
|
+
|
306
|
+
|
307
|
+
nodes = [
|
308
|
+
Node(
|
309
|
+
task=task,
|
310
|
+
assigned_to=self._get_responsible_agent(task=task),
|
311
|
+
status=TaskStatus.NOT_STARTED if not start_index or i >= start_index else TaskStatus.COMPLETED,
|
312
|
+
) for i, task in enumerate(tasks)
|
313
|
+
]
|
314
|
+
task_graph = TaskGraph(nodes={node.identifier: node for node in nodes})
|
315
|
+
|
316
|
+
for i in range(0, len(nodes) - 1):
|
317
|
+
task_graph.add_edge(
|
318
|
+
source=nodes[i].identifier,
|
319
|
+
target=nodes[i+1].identifier,
|
320
|
+
edge=Edge(
|
321
|
+
weight=3 if nodes[i].task in self.manager_tasks else 1,
|
322
|
+
dependency_type=DependencyType.FINISH_TO_START if self.process == TaskHandlingProcess.HIERARCHY else DependencyType.START_TO_START,
|
323
|
+
required=bool(self.process == TaskHandlingProcess.CONSENSUAL),
|
324
|
+
condition=self.consent_trigger,
|
325
|
+
data_transfer=bool(self.process == TaskHandlingProcess.HIERARCHY),
|
326
|
+
)
|
327
|
+
)
|
302
328
|
|
303
|
-
|
304
|
-
|
305
|
-
if hasattr(agent, "_token_process"):
|
306
|
-
token_sum = agent._token_process.get_summary()
|
307
|
-
total_usage_metrics.add_usage_metrics(token_sum)
|
329
|
+
if start_index is not None:
|
330
|
+
res, _ = task_graph.activate(target=nodes[start_index].indentifier)
|
308
331
|
|
309
|
-
|
310
|
-
|
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)
|
332
|
+
else:
|
333
|
+
res, _ = task_graph.activate()
|
314
334
|
|
315
|
-
|
316
|
-
|
335
|
+
# task_outputs: List[TaskOutput] = []
|
336
|
+
# lead_task_output: TaskOutput = None
|
337
|
+
# futures: List[Tuple[Task, Future[TaskOutput], int]] = []
|
338
|
+
# last_sync_output: Optional[TaskOutput] = None
|
317
339
|
|
340
|
+
# for task_index, task in enumerate(tasks):
|
341
|
+
# if start_index is not None and task_index < start_index:
|
342
|
+
# if task.output:
|
343
|
+
# if task.execution_type == TaskExecutionType.ASYNC:
|
344
|
+
# task_outputs.append(task.output)
|
345
|
+
# else:
|
346
|
+
# task_outputs = [task.output]
|
347
|
+
# last_sync_output = task.output
|
348
|
+
# continue
|
318
349
|
|
319
|
-
|
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
|
-
"""
|
350
|
+
# responsible_agent = self._get_responsible_agent(task=task)
|
326
351
|
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
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)
|
352
|
+
# ## commented out - this will be handled by node objects
|
353
|
+
# # if isinstance(task, ConditionalTask):
|
354
|
+
# # skipped_task_output = task._handle_conditional_task(task_outputs, futures, task_index, was_replayed)
|
355
|
+
# # if skipped_task_output:
|
356
|
+
# # continue
|
357
|
+
|
358
|
+
# # self._log_task_start(task, responsible_agent)
|
361
359
|
|
362
|
-
|
363
|
-
|
360
|
+
# context = create_raw_outputs(tasks=[task, ], task_outputs=([last_sync_output,] if last_sync_output else []))
|
361
|
+
# res = task.execute(agent=responsible_agent, context=context)
|
364
362
|
|
365
|
-
|
366
|
-
|
363
|
+
# if isinstance(res, Future):
|
364
|
+
# futures.append((task, res, task_index))
|
365
|
+
# else:
|
366
|
+
# # if self.managers and responsible_agent in [manager.agent for manager in self.managers]:
|
367
|
+
# # lead_task_output = task_output
|
367
368
|
|
369
|
+
# task_outputs.append(res)
|
370
|
+
# task._store_log(task_index, was_replayed, self._inputs)
|
368
371
|
|
369
|
-
if futures:
|
370
|
-
task_outputs = self._process_async_tasks(futures, was_replayed)
|
371
372
|
|
372
|
-
if
|
373
|
+
# if futures:
|
374
|
+
# task_outputs = self._process_async_tasks(futures, was_replayed)
|
375
|
+
|
376
|
+
if not res:
|
373
377
|
Logger().log(level="error", message="Missing task outputs.", color="red")
|
374
378
|
raise ValueError("Missing task outputs")
|
375
379
|
|
376
|
-
|
377
|
-
|
378
|
-
# token_usage = self._calculate_usage_metrics() #! combining with Eval
|
380
|
+
return res, task_graph
|
379
381
|
|
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
382
|
|
389
383
|
|
390
|
-
def launch(
|
384
|
+
def launch(
|
385
|
+
self, kwargs_pre: Optional[Dict[str, str]] = None, kwargs_post: Optional[Dict[str, Any]] = None, start_index: int = None
|
386
|
+
) -> Tuple[TaskOutput, TaskGraph]:
|
391
387
|
"""
|
392
388
|
Launch the agent network - executing tasks and recording their outputs.
|
393
389
|
"""
|
394
390
|
|
395
|
-
|
391
|
+
self._assign_tasks()
|
396
392
|
|
397
|
-
if
|
398
|
-
self._assign_tasks()
|
399
|
-
|
400
|
-
if kwargs_pre is not None:
|
393
|
+
if kwargs_pre:
|
401
394
|
for func in self.pre_launch_callbacks: # signature check
|
402
395
|
func(**kwargs_pre)
|
403
396
|
|
404
397
|
for member in self.members:
|
405
398
|
agent = member.agent
|
406
|
-
|
399
|
+
|
400
|
+
if not agent.networks:
|
401
|
+
agent.networks.append(self)
|
407
402
|
|
408
403
|
if self.step_callback:
|
409
404
|
agent.callbacks.append(self.step_callback)
|
410
405
|
|
411
406
|
if self.process is None:
|
412
|
-
self.process = TaskHandlingProcess.
|
407
|
+
self.process = TaskHandlingProcess.SEQUENTIAL
|
413
408
|
|
414
|
-
result = self._execute_tasks(self.tasks)
|
409
|
+
result, tg = self._execute_tasks(self.tasks, start_index=start_index)
|
410
|
+
callback_output = None
|
415
411
|
|
416
412
|
for func in self.post_launch_callbacks:
|
417
|
-
|
413
|
+
callback_output = func(result, **kwargs_post)
|
414
|
+
|
415
|
+
if callback_output:
|
416
|
+
match result:
|
417
|
+
case TaskOutput():
|
418
|
+
result.callback_output = callback_output
|
419
|
+
|
420
|
+
case _:
|
421
|
+
pass
|
418
422
|
|
419
|
-
metrics += [member.agent._token_process.get_summary() for member in self.members]
|
423
|
+
# metrics += [member.agent._token_process.get_summary() for member in self.members]
|
420
424
|
|
421
|
-
self.usage_metrics = UsageMetrics()
|
422
|
-
for metric in metrics:
|
423
|
-
|
425
|
+
# self.usage_metrics = UsageMetrics()
|
426
|
+
# for metric in metrics:
|
427
|
+
# self.usage_metrics.add_usage_metrics(metric)
|
424
428
|
|
425
|
-
return result
|
429
|
+
return result, tg
|
426
430
|
|
427
431
|
|
428
432
|
@property
|
@@ -474,7 +478,7 @@ class AgentNetwork(BaseModel):
|
|
474
478
|
|
475
479
|
|
476
480
|
@property
|
477
|
-
def
|
481
|
+
def unassigned_member_tasks(self) -> List[Task]:
|
478
482
|
res = list()
|
479
483
|
|
480
484
|
if self.members:
|
versionhq/llm/model.py
CHANGED
@@ -237,6 +237,41 @@ class LLM(BaseModel):
|
|
237
237
|
return valid_params
|
238
238
|
|
239
239
|
|
240
|
+
def _supports_function_calling(self) -> bool:
|
241
|
+
try:
|
242
|
+
if self.model:
|
243
|
+
params = litellm.get_supported_openai_params(model=self.model)
|
244
|
+
return "response_format" in params if params else False
|
245
|
+
except Exception as e:
|
246
|
+
self._logger.log(level="warning", message=f"Failed to get supported params: {str(e)}", color="yellow")
|
247
|
+
return False
|
248
|
+
|
249
|
+
|
250
|
+
def _supports_stop_words(self) -> bool:
|
251
|
+
supported_params = litellm.get_supported_openai_params(model=self.model, custom_llm_provider=self.endpoint_provider)
|
252
|
+
return "stop" in supported_params if supported_params else False
|
253
|
+
|
254
|
+
|
255
|
+
def _get_context_window_size(self) -> int:
|
256
|
+
"""
|
257
|
+
Only use 75% of the context window size to avoid cutting the message in the middle.
|
258
|
+
"""
|
259
|
+
return int(LLM_CONTEXT_WINDOW_SIZES.get(self.model) * 0.75) if LLM_CONTEXT_WINDOW_SIZES.get(self.model) is not None else DEFAULT_CONTEXT_WINDOW_SIZE
|
260
|
+
|
261
|
+
|
262
|
+
def _set_callbacks(self, callbacks: List[Any]):
|
263
|
+
callback_types = [type(callback) for callback in callbacks]
|
264
|
+
for callback in litellm.success_callback[:]:
|
265
|
+
if type(callback) in callback_types:
|
266
|
+
litellm.success_callback.remove(callback)
|
267
|
+
|
268
|
+
for callback in litellm._async_success_callback[:]:
|
269
|
+
if type(callback) in callback_types:
|
270
|
+
litellm._async_success_callback.remove(callback)
|
271
|
+
|
272
|
+
litellm.callbacks = callbacks
|
273
|
+
|
274
|
+
|
240
275
|
def call(
|
241
276
|
self,
|
242
277
|
messages: List[Dict[str, str]],
|
@@ -345,38 +380,3 @@ class LLM(BaseModel):
|
|
345
380
|
self._logger.log(level="error", message=f"{self.model} failed to execute: {str(e)}", color="red")
|
346
381
|
if "litellm.RateLimitError" in str(e):
|
347
382
|
raise e
|
348
|
-
|
349
|
-
|
350
|
-
def _supports_function_calling(self) -> bool:
|
351
|
-
try:
|
352
|
-
if self.model:
|
353
|
-
params = litellm.get_supported_openai_params(model=self.model)
|
354
|
-
return "response_format" in params if params else False
|
355
|
-
except Exception as e:
|
356
|
-
self._logger.log(level="warning", message=f"Failed to get supported params: {str(e)}", color="yellow")
|
357
|
-
return False
|
358
|
-
|
359
|
-
|
360
|
-
def _supports_stop_words(self) -> bool:
|
361
|
-
supported_params = litellm.get_supported_openai_params(model=self.model, custom_llm_provider=self.endpoint_provider)
|
362
|
-
return "stop" in supported_params if supported_params else False
|
363
|
-
|
364
|
-
|
365
|
-
def _get_context_window_size(self) -> int:
|
366
|
-
"""
|
367
|
-
Only use 75% of the context window size to avoid cutting the message in the middle.
|
368
|
-
"""
|
369
|
-
return int(LLM_CONTEXT_WINDOW_SIZES.get(self.model) * 0.75) if LLM_CONTEXT_WINDOW_SIZES.get(self.model) is not None else DEFAULT_CONTEXT_WINDOW_SIZE
|
370
|
-
|
371
|
-
|
372
|
-
def _set_callbacks(self, callbacks: List[Any]):
|
373
|
-
callback_types = [type(callback) for callback in callbacks]
|
374
|
-
for callback in litellm.success_callback[:]:
|
375
|
-
if type(callback) in callback_types:
|
376
|
-
litellm.success_callback.remove(callback)
|
377
|
-
|
378
|
-
for callback in litellm._async_success_callback[:]:
|
379
|
-
if type(callback) in callback_types:
|
380
|
-
litellm._async_success_callback.remove(callback)
|
381
|
-
|
382
|
-
litellm.callbacks = callbacks
|
versionhq/memory/model.py
CHANGED
@@ -34,12 +34,14 @@ class MemoryMetadata:
|
|
34
34
|
|
35
35
|
def __init__(
|
36
36
|
self,
|
37
|
+
task_description: str = None,
|
37
38
|
eval_criteria: Optional[str] = None,
|
38
39
|
score: Optional[int | float] = None,
|
39
40
|
suggestion: Optional[str] = None,
|
40
41
|
eval_by: Optional[str] = None, # task evaluator agent
|
41
42
|
config: Optional[Dict[str, Any]] = None
|
42
43
|
):
|
44
|
+
self.task_description = task_description
|
43
45
|
self.eval_criteria = eval_criteria
|
44
46
|
self.score = score
|
45
47
|
self.suggestion = suggestion
|
@@ -104,7 +106,7 @@ class MemoryItem:
|
|
104
106
|
|
105
107
|
class ShortTermMemory(Memory):
|
106
108
|
"""
|
107
|
-
A class
|
109
|
+
A Pydantic class to store agents' short-term memories.
|
108
110
|
- Type: stm
|
109
111
|
- Storage: Mem0Storage | RAGStorage
|
110
112
|
"""
|
@@ -165,7 +167,7 @@ class ShortTermMemory(Memory):
|
|
165
167
|
|
166
168
|
class LongTermMemory(Memory):
|
167
169
|
"""
|
168
|
-
A class for
|
170
|
+
A Pydantic class for storing agents' long-term memories. Query task outputs for the evaluation.
|
169
171
|
- Type: ltm
|
170
172
|
- Storage: LTMSQLiteStorage | RAGStorage
|
171
173
|
"""
|