versionhq 1.1.8.1__py3-none-any.whl → 1.1.9.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 -1
- versionhq/_utils/cache_handler.py +2 -2
- versionhq/_utils/rpm_controller.py +6 -5
- versionhq/_utils/usage_metrics.py +1 -1
- versionhq/agent/model.py +60 -42
- versionhq/agent/parser.py +1 -12
- versionhq/clients/customer/model.py +6 -22
- versionhq/clients/workflow/model.py +10 -10
- versionhq/task/model.py +126 -106
- versionhq/team/model.py +59 -66
- versionhq/tool/__init__.py +53 -0
- versionhq/tool/composio.py +149 -0
- versionhq/tool/decorator.py +3 -1
- versionhq/tool/model.py +138 -115
- versionhq/tool/tool_handler.py +20 -23
- {versionhq-1.1.8.1.dist-info → versionhq-1.1.9.1.dist-info}/METADATA +2 -2
- {versionhq-1.1.8.1.dist-info → versionhq-1.1.9.1.dist-info}/RECORD +20 -19
- {versionhq-1.1.8.1.dist-info → versionhq-1.1.9.1.dist-info}/LICENSE +0 -0
- {versionhq-1.1.8.1.dist-info → versionhq-1.1.9.1.dist-info}/WHEEL +0 -0
- {versionhq-1.1.8.1.dist-info → versionhq-1.1.9.1.dist-info}/top_level.txt +0 -0
versionhq/task/model.py
CHANGED
@@ -4,7 +4,7 @@ import uuid
|
|
4
4
|
from concurrent.futures import Future
|
5
5
|
from hashlib import md5
|
6
6
|
from typing import Any, Dict, List, Set, Optional, Tuple, Callable, Type
|
7
|
-
from typing_extensions import Annotated
|
7
|
+
from typing_extensions import Annotated, Self
|
8
8
|
|
9
9
|
from pydantic import UUID4, BaseModel, Field, PrivateAttr, field_validator, model_validator, create_model, InstanceOf
|
10
10
|
from pydantic_core import PydanticCustomError
|
@@ -12,7 +12,7 @@ from pydantic_core import PydanticCustomError
|
|
12
12
|
from versionhq._utils.process_config import process_config
|
13
13
|
from versionhq.task import TaskOutputFormat
|
14
14
|
from versionhq.task.log_handler import TaskOutputStorageHandler
|
15
|
-
from versionhq.tool.model import Tool,
|
15
|
+
from versionhq.tool.model import Tool, ToolSet
|
16
16
|
from versionhq._utils.logger import Logger
|
17
17
|
|
18
18
|
|
@@ -72,22 +72,11 @@ class TaskOutput(BaseModel):
|
|
72
72
|
raw: str = Field(default="", description="Raw output of the task")
|
73
73
|
json_dict: Dict[str, Any] = Field(default=None, description="`raw` converted to dictionary")
|
74
74
|
pydantic: Optional[Any] = Field(default=None, description="`raw` converted to the abs. pydantic model")
|
75
|
+
tool_output: Optional[Any] = Field(default=None, description="store tool result when the task takes tool output as its final output")
|
75
76
|
|
76
77
|
def __str__(self) -> str:
|
77
78
|
return str(self.pydantic) if self.pydantic else str(self.json_dict) if self.json_dict else self.raw
|
78
79
|
|
79
|
-
@property
|
80
|
-
def json(self) -> Optional[str]:
|
81
|
-
if self.output_format != TaskOutputFormat.JSON:
|
82
|
-
raise ValueError(
|
83
|
-
"""
|
84
|
-
Invalid output format requested.
|
85
|
-
If you would like to access the JSON output,
|
86
|
-
pleae make sure to set the output_json property for the task
|
87
|
-
"""
|
88
|
-
)
|
89
|
-
return json.dumps(self.json_dict)
|
90
|
-
|
91
80
|
|
92
81
|
def to_dict(self) -> Dict[str, Any]:
|
93
82
|
"""
|
@@ -112,6 +101,19 @@ class TaskOutput(BaseModel):
|
|
112
101
|
return json.dumps(self.json_dict) if self.json_dict else self.raw[0: 127]
|
113
102
|
|
114
103
|
|
104
|
+
@property
|
105
|
+
def json(self) -> Optional[str]:
|
106
|
+
if self.output_format != TaskOutputFormat.JSON:
|
107
|
+
raise ValueError(
|
108
|
+
"""
|
109
|
+
Invalid output format requested.
|
110
|
+
If you would like to access the JSON output,
|
111
|
+
pleae make sure to set the output_json property for the task
|
112
|
+
"""
|
113
|
+
)
|
114
|
+
return json.dumps(self.json_dict)
|
115
|
+
|
116
|
+
|
115
117
|
|
116
118
|
class Task(BaseModel):
|
117
119
|
"""
|
@@ -124,6 +126,7 @@ class Task(BaseModel):
|
|
124
126
|
_original_description: str = PrivateAttr(default=None)
|
125
127
|
_logger: Logger = PrivateAttr()
|
126
128
|
_task_output_handler = TaskOutputStorageHandler()
|
129
|
+
config: Optional[Dict[str, Any]] = Field(default=None, description="configuration for the agent")
|
127
130
|
|
128
131
|
id: UUID4 = Field(default_factory=uuid.uuid4, frozen=True, description="unique identifier for the object, not set by user")
|
129
132
|
name: Optional[str] = Field(default=None)
|
@@ -133,90 +136,35 @@ class Task(BaseModel):
|
|
133
136
|
expected_output_json: bool = Field(default=True)
|
134
137
|
expected_output_pydantic: bool = Field(default=False)
|
135
138
|
output_field_list: List[ResponseField] = Field(
|
136
|
-
|
139
|
+
default_factory=list,
|
137
140
|
description="provide output key and data type. this will be cascaded to the agent via task.prompt()"
|
138
141
|
)
|
139
142
|
output: Optional[TaskOutput] = Field(default=None, description="store the final task output in TaskOutput class")
|
140
143
|
|
141
144
|
# task setup
|
142
145
|
context: Optional[List["Task"]] = Field(default=None, description="other tasks whose outputs should be used as context")
|
143
|
-
|
146
|
+
prompt_context: Optional[str] = Field(default=None)
|
147
|
+
|
148
|
+
# tool usage
|
149
|
+
tools: Optional[List[ToolSet | Tool | Any]] = Field(default_factory=list, description="tools that the agent can use aside from their tools")
|
150
|
+
can_use_agent_tools: bool = Field(default=False, description="whether the agent can use their own tools when executing the task")
|
144
151
|
take_tool_res_as_final: bool = Field(default=False, description="when set True, tools res will be stored in the `TaskOutput`")
|
145
|
-
allow_delegation: bool = Field(default=False, description="ask other agents for help and run the task instead")
|
146
152
|
|
147
|
-
|
153
|
+
# execution rules
|
154
|
+
allow_delegation: bool = Field(default=False, description="ask other agents for help and run the task instead")
|
148
155
|
async_execution: bool = Field(default=False,description="whether the task should be executed asynchronously or not")
|
149
|
-
config: Optional[Dict[str, Any]] = Field(default=None, description="configuration for the agent")
|
150
156
|
callback: Optional[Any] = Field(default=None, description="callback to be executed after the task is completed.")
|
151
157
|
callback_kwargs: Optional[Dict[str, Any]] = Field(default_factory=dict, description="kwargs for the callback when the callback is callable")
|
152
158
|
|
153
159
|
# recording
|
154
160
|
processed_by_agents: Set[str] = Field(default_factory=set, description="store responsible agents' roles")
|
155
|
-
used_tools: int = 0
|
156
161
|
tools_errors: int = 0
|
157
162
|
delegations: int = 0
|
158
163
|
|
159
164
|
|
160
|
-
@property
|
161
|
-
def output_prompt(self) -> str:
|
162
|
-
"""
|
163
|
-
Draft prompts on the output format by converting `output_field_list` to dictionary.
|
164
|
-
"""
|
165
|
-
|
166
|
-
output_prompt, output_formats_to_follow = "", dict()
|
167
|
-
for item in self.output_field_list:
|
168
|
-
output_formats_to_follow[item.title] = f"<Return your answer in {item.type.__name__}>"
|
169
|
-
|
170
|
-
output_prompt = f"""
|
171
|
-
Your outputs MUST adhere to the following format and should NOT include any irrelevant elements:
|
172
|
-
{output_formats_to_follow}
|
173
|
-
"""
|
174
|
-
return output_prompt
|
175
|
-
|
176
|
-
|
177
|
-
@property
|
178
|
-
def expected_output_formats(self) -> List[TaskOutputFormat]:
|
179
|
-
"""
|
180
|
-
Return output formats in list with the ENUM item.
|
181
|
-
`TaskOutputFormat.RAW` is set as default.
|
182
|
-
"""
|
183
|
-
outputs = [TaskOutputFormat.RAW,]
|
184
|
-
if self.expected_output_json:
|
185
|
-
outputs.append(TaskOutputFormat.JSON)
|
186
|
-
if self.expected_output_pydantic:
|
187
|
-
outputs.append(TaskOutputFormat.PYDANTIC)
|
188
|
-
return outputs
|
189
|
-
|
190
|
-
|
191
|
-
@property
|
192
|
-
def key(self) -> str:
|
193
|
-
output_format = (
|
194
|
-
TaskOutputFormat.JSON
|
195
|
-
if self.expected_output_json == True
|
196
|
-
else (
|
197
|
-
TaskOutputFormat.PYDANTIC
|
198
|
-
if self.expected_output_pydantic == True
|
199
|
-
else TaskOutputFormat.RAW
|
200
|
-
)
|
201
|
-
)
|
202
|
-
source = [self.description, output_format]
|
203
|
-
return md5("|".join(source).encode(), usedforsecurity=False).hexdigest()
|
204
|
-
|
205
|
-
|
206
|
-
@property
|
207
|
-
def summary(self) -> str:
|
208
|
-
return f"""
|
209
|
-
Task ID: {str(self.id)}
|
210
|
-
"Description": {self.description}
|
211
|
-
"Prompt": {self.output_prompt}
|
212
|
-
"Tools": {", ".join([tool_called.tool.name for tool_called in self.tools_called])}
|
213
|
-
"""
|
214
|
-
|
215
|
-
|
216
|
-
# validators
|
217
165
|
@model_validator(mode="before")
|
218
166
|
@classmethod
|
219
|
-
def process_model_config(cls, values: Dict[str, Any]):
|
167
|
+
def process_model_config(cls, values: Dict[str, Any]) -> None:
|
220
168
|
return process_config(values_to_update=values, model_class=cls)
|
221
169
|
|
222
170
|
|
@@ -228,7 +176,7 @@ Your outputs MUST adhere to the following format and should NOT include any irre
|
|
228
176
|
|
229
177
|
|
230
178
|
@model_validator(mode="after")
|
231
|
-
def validate_required_fields(self):
|
179
|
+
def validate_required_fields(self) -> Self:
|
232
180
|
required_fields = ["description",]
|
233
181
|
for field in required_fields:
|
234
182
|
if getattr(self, field) is None:
|
@@ -237,7 +185,7 @@ Your outputs MUST adhere to the following format and should NOT include any irre
|
|
237
185
|
|
238
186
|
|
239
187
|
@model_validator(mode="after")
|
240
|
-
def set_attributes_based_on_config(self) ->
|
188
|
+
def set_attributes_based_on_config(self) -> Self:
|
241
189
|
"""
|
242
190
|
Set attributes based on the agent configuration.
|
243
191
|
"""
|
@@ -248,12 +196,21 @@ Your outputs MUST adhere to the following format and should NOT include any irre
|
|
248
196
|
return self
|
249
197
|
|
250
198
|
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
199
|
+
@model_validator(mode="after")
|
200
|
+
def set_up_tools(self) -> Self:
|
201
|
+
if not self.tools:
|
202
|
+
pass
|
203
|
+
else:
|
204
|
+
tool_list = []
|
205
|
+
for item in self.tools:
|
206
|
+
if isinstance(item, Tool) or isinstance(item, ToolSet):
|
207
|
+
tool_list.append(item)
|
208
|
+
elif (isinstance(item, dict) and "function" not in item) or isinstance(item, str):
|
209
|
+
pass
|
210
|
+
else:
|
211
|
+
tool_list.append(item) # address custom tool
|
212
|
+
self.tools = tool_list
|
213
|
+
return self
|
257
214
|
|
258
215
|
|
259
216
|
@model_validator(mode="after")
|
@@ -317,7 +274,6 @@ Your outputs MUST adhere to the following format and should NOT include any irre
|
|
317
274
|
return output_json_dict
|
318
275
|
|
319
276
|
|
320
|
-
|
321
277
|
def create_pydantic_output(self, output_json_dict: Dict[str, Any], raw_result: Any = None) -> Optional[Any]:
|
322
278
|
"""
|
323
279
|
Create pydantic output from the `raw` result.
|
@@ -358,11 +314,10 @@ Your outputs MUST adhere to the following format and should NOT include any irre
|
|
358
314
|
"""
|
359
315
|
if inputs:
|
360
316
|
self.description = self._original_description.format(**inputs)
|
361
|
-
# self.expected_output = self._original_expected_output.format(**inputs)
|
362
317
|
|
363
318
|
|
364
319
|
# task execution
|
365
|
-
def execute_sync(self, agent, context: Optional[str] = None
|
320
|
+
def execute_sync(self, agent, context: Optional[str] = None) -> TaskOutput:
|
366
321
|
"""
|
367
322
|
Execute the task synchronously.
|
368
323
|
When the task has context, make sure we have executed all the tasks in the context first.
|
@@ -376,26 +331,26 @@ Your outputs MUST adhere to the following format and should NOT include any irre
|
|
376
331
|
return self._execute_core(agent, context)
|
377
332
|
|
378
333
|
|
379
|
-
def execute_async(self, agent, context: Optional[str] = None
|
334
|
+
def execute_async(self, agent, context: Optional[str] = None) -> Future[TaskOutput]:
|
380
335
|
"""
|
381
336
|
Execute the task asynchronously.
|
382
337
|
"""
|
383
338
|
|
384
339
|
future: Future[TaskOutput] = Future()
|
385
|
-
threading.Thread(daemon=True, target=self._execute_task_async, args=(agent, context,
|
340
|
+
threading.Thread(daemon=True, target=self._execute_task_async, args=(agent, context, future)).start()
|
386
341
|
return future
|
387
342
|
|
388
343
|
|
389
|
-
def _execute_task_async(self, agent, context: Optional[str],
|
344
|
+
def _execute_task_async(self, agent, context: Optional[str], future: Future[TaskOutput]) -> None:
|
390
345
|
"""
|
391
346
|
Execute the task asynchronously with context handling.
|
392
347
|
"""
|
393
348
|
|
394
|
-
result = self._execute_core(agent, context
|
349
|
+
result = self._execute_core(agent, context)
|
395
350
|
future.set_result(result)
|
396
351
|
|
397
352
|
|
398
|
-
def _execute_core(self, agent, context: Optional[str]
|
353
|
+
def _execute_core(self, agent, context: Optional[str]) -> TaskOutput:
|
399
354
|
"""
|
400
355
|
Run the core execution logic of the task.
|
401
356
|
To speed up the process, when the format is not expected to return, we will skip the conversion process.
|
@@ -405,13 +360,14 @@ Your outputs MUST adhere to the following format and should NOT include any irre
|
|
405
360
|
from versionhq.team.model import Team
|
406
361
|
|
407
362
|
self.prompt_context = context
|
363
|
+
task_output: InstanceOf[TaskOutput] = None
|
408
364
|
|
409
365
|
if self.allow_delegation:
|
410
366
|
agent_to_delegate = None
|
411
367
|
|
412
368
|
if hasattr(agent, "team") and isinstance(agent.team, Team):
|
413
369
|
if agent.team.managers:
|
414
|
-
idling_manager_agents = [manager.agent for manager in agent.team.managers if manager.
|
370
|
+
idling_manager_agents = [manager.agent for manager in agent.team.managers if manager.is_idling]
|
415
371
|
agent_to_delegate = idling_manager_agents[0] if idling_manager_agents else agent.team.managers[0]
|
416
372
|
else:
|
417
373
|
peers = [member.agent for member in agent.team.members if member.is_manager == False and member.agent.id is not agent.id]
|
@@ -423,20 +379,26 @@ Your outputs MUST adhere to the following format and should NOT include any irre
|
|
423
379
|
agent = agent_to_delegate
|
424
380
|
self.delegations += 1
|
425
381
|
|
426
|
-
|
382
|
+
if self.take_tool_res_as_final is True:
|
383
|
+
output = agent.execute_task(task=self, context=context)
|
384
|
+
task_output = TaskOutput(task_id=self.id, tool_output=output)
|
427
385
|
|
428
|
-
|
429
|
-
output_json_dict =
|
386
|
+
else:
|
387
|
+
output_raw, output_json_dict, output_pydantic = agent.execute_task(task=self, context=context), None, None
|
430
388
|
|
431
|
-
|
432
|
-
|
389
|
+
if self.expected_output_json:
|
390
|
+
output_json_dict = self.create_json_output(raw_result=output_raw)
|
391
|
+
|
392
|
+
if self.expected_output_pydantic:
|
393
|
+
output_pydantic = self.create_pydantic_output(output_json_dict=output_json_dict)
|
394
|
+
|
395
|
+
task_output = TaskOutput(
|
396
|
+
task_id=self.id,
|
397
|
+
raw=output_raw,
|
398
|
+
pydantic=output_pydantic,
|
399
|
+
json_dict=output_json_dict
|
400
|
+
)
|
433
401
|
|
434
|
-
task_output = TaskOutput(
|
435
|
-
task_id=self.id,
|
436
|
-
raw=output_raw,
|
437
|
-
pydantic=output_pydantic,
|
438
|
-
json_dict=output_json_dict
|
439
|
-
)
|
440
402
|
self.output = task_output
|
441
403
|
self.processed_by_agents.add(agent.role)
|
442
404
|
|
@@ -456,6 +418,7 @@ Your outputs MUST adhere to the following format and should NOT include any irre
|
|
456
418
|
# else pydantic_output.model_dump_json() if pydantic_output else result
|
457
419
|
# )
|
458
420
|
# self._save_file(content)
|
421
|
+
|
459
422
|
return task_output
|
460
423
|
|
461
424
|
|
@@ -467,6 +430,63 @@ Your outputs MUST adhere to the following format and should NOT include any irre
|
|
467
430
|
self._task_output_handler.update(task=self, task_index=task_index, was_replayed=was_replayed, inputs=inputs)
|
468
431
|
|
469
432
|
|
433
|
+
@property
|
434
|
+
def output_prompt(self) -> str:
|
435
|
+
"""
|
436
|
+
Draft prompts on the output format by converting `output_field_list` to dictionary.
|
437
|
+
"""
|
438
|
+
|
439
|
+
output_prompt, output_formats_to_follow = "", dict()
|
440
|
+
for item in self.output_field_list:
|
441
|
+
output_formats_to_follow[item.title] = f"<Return your answer in {item.type.__name__}>"
|
442
|
+
|
443
|
+
output_prompt = f"""
|
444
|
+
Your outputs MUST adhere to the following format and should NOT include any irrelevant elements:
|
445
|
+
{output_formats_to_follow}
|
446
|
+
"""
|
447
|
+
return output_prompt
|
448
|
+
|
449
|
+
|
450
|
+
@property
|
451
|
+
def expected_output_formats(self) -> List[TaskOutputFormat]:
|
452
|
+
"""
|
453
|
+
Return output formats in list with the ENUM item.
|
454
|
+
`TaskOutputFormat.RAW` is set as default.
|
455
|
+
"""
|
456
|
+
outputs = [TaskOutputFormat.RAW,]
|
457
|
+
if self.expected_output_json:
|
458
|
+
outputs.append(TaskOutputFormat.JSON)
|
459
|
+
if self.expected_output_pydantic:
|
460
|
+
outputs.append(TaskOutputFormat.PYDANTIC)
|
461
|
+
return outputs
|
462
|
+
|
463
|
+
|
464
|
+
@property
|
465
|
+
def key(self) -> str:
|
466
|
+
output_format = (
|
467
|
+
TaskOutputFormat.JSON
|
468
|
+
if self.expected_output_json == True
|
469
|
+
else (
|
470
|
+
TaskOutputFormat.PYDANTIC
|
471
|
+
if self.expected_output_pydantic == True
|
472
|
+
else TaskOutputFormat.RAW
|
473
|
+
)
|
474
|
+
)
|
475
|
+
source = [self.description, output_format]
|
476
|
+
return md5("|".join(source).encode(), usedforsecurity=False).hexdigest()
|
477
|
+
|
478
|
+
|
479
|
+
@property
|
480
|
+
def summary(self) -> str:
|
481
|
+
return f"""
|
482
|
+
Task ID: {str(self.id)}
|
483
|
+
"Description": {self.description}
|
484
|
+
"Prompt": {self.output_prompt}
|
485
|
+
"Tools": {", ".join([tool.name for tool in self.tools])}
|
486
|
+
"""
|
487
|
+
|
488
|
+
|
489
|
+
|
470
490
|
|
471
491
|
class ConditionalTask(Task):
|
472
492
|
"""
|
versionhq/team/model.py
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
import uuid
|
2
2
|
import warnings
|
3
3
|
import json
|
4
|
-
from abc import ABC
|
5
4
|
from enum import Enum
|
6
5
|
from dotenv import load_dotenv
|
7
6
|
from concurrent.futures import Future
|
@@ -108,13 +107,14 @@ class TeamOutput(BaseModel):
|
|
108
107
|
return res
|
109
108
|
|
110
109
|
|
111
|
-
class TeamMember(
|
110
|
+
class TeamMember(BaseModel):
|
112
111
|
agent: Agent | None = Field(default=None, description="store the agent to be a member")
|
113
112
|
is_manager: bool = Field(default=False)
|
114
113
|
task: Optional[Task] = Field(default=None)
|
115
114
|
|
116
|
-
|
117
|
-
|
115
|
+
@property
|
116
|
+
def is_idling(self):
|
117
|
+
return bool(self.task is None)
|
118
118
|
|
119
119
|
|
120
120
|
class Team(BaseModel):
|
@@ -161,54 +161,6 @@ class Team(BaseModel):
|
|
161
161
|
return self.name if self.name is not None else self.id.__str__
|
162
162
|
|
163
163
|
|
164
|
-
@property
|
165
|
-
def key(self) -> str:
|
166
|
-
source = [str(member.agent.id.__str__) for member in self.members] + [str(task.id.__str__) for task in self.tasks]
|
167
|
-
return md5("|".join(source).encode(), usedforsecurity=False).hexdigest()
|
168
|
-
|
169
|
-
|
170
|
-
@property
|
171
|
-
def managers(self) -> List[TeamMember] | None:
|
172
|
-
managers = [member for member in self.members if member.is_manager == True]
|
173
|
-
return managers if len(managers) > 0 else None
|
174
|
-
|
175
|
-
|
176
|
-
@property
|
177
|
-
def manager_tasks(self) -> List[Task] | None:
|
178
|
-
"""
|
179
|
-
Tasks (incl. team tasks) handled by managers in the team.
|
180
|
-
"""
|
181
|
-
if self.managers:
|
182
|
-
tasks = [manager.task for manager in self.managers if manager.task is not None]
|
183
|
-
return tasks if len(tasks) > 0 else None
|
184
|
-
|
185
|
-
return None
|
186
|
-
|
187
|
-
|
188
|
-
@property
|
189
|
-
def tasks(self):
|
190
|
-
"""
|
191
|
-
Return all the tasks that the team needs to handle in order of priority:
|
192
|
-
1. team tasks, -> assigned to the member
|
193
|
-
2. manager_task,
|
194
|
-
3. members' tasks
|
195
|
-
"""
|
196
|
-
|
197
|
-
team_tasks = self.team_tasks
|
198
|
-
manager_tasks = [member.task for member in self.members if member.is_manager == True and member.task is not None and member.task not in team_tasks]
|
199
|
-
member_tasks = [member.task for member in self.members if member.is_manager == False and member.task is not None and member.task not in team_tasks]
|
200
|
-
|
201
|
-
return team_tasks + manager_tasks + member_tasks
|
202
|
-
|
203
|
-
|
204
|
-
@property
|
205
|
-
def member_tasks_without_agent(self) -> List[Task] | None:
|
206
|
-
if self.members:
|
207
|
-
return [member.task for member in self.members if member.agent is None]
|
208
|
-
|
209
|
-
return None
|
210
|
-
|
211
|
-
|
212
164
|
@field_validator("id", mode="before")
|
213
165
|
@classmethod
|
214
166
|
def _deny_user_set_id(cls, v: Optional[UUID4]) -> None:
|
@@ -218,7 +170,7 @@ class Team(BaseModel):
|
|
218
170
|
|
219
171
|
|
220
172
|
@model_validator(mode="after")
|
221
|
-
def
|
173
|
+
def assess_tasks(self):
|
222
174
|
"""
|
223
175
|
Validates if the model recognize all tasks that the team needs to handle.
|
224
176
|
"""
|
@@ -261,7 +213,7 @@ class Team(BaseModel):
|
|
261
213
|
if self.process == TaskHandlingProcess.sequential and self.team_tasks is None:
|
262
214
|
for member in self.members:
|
263
215
|
if member.task is None:
|
264
|
-
raise PydanticCustomError("missing_agent_in_task",
|
216
|
+
raise PydanticCustomError("missing_agent_in_task", "Sequential process error: Agent is missing the task", {})
|
265
217
|
return self
|
266
218
|
|
267
219
|
@model_validator(mode="after")
|
@@ -280,11 +232,7 @@ class Team(BaseModel):
|
|
280
232
|
break
|
281
233
|
|
282
234
|
if async_task_count > 1:
|
283
|
-
raise PydanticCustomError(
|
284
|
-
"async_task_count",
|
285
|
-
"The team must end with max. one asynchronous task.",
|
286
|
-
{},
|
287
|
-
)
|
235
|
+
raise PydanticCustomError("async_task_count", "The team must end with max. one asynchronous task.", {})
|
288
236
|
return self
|
289
237
|
|
290
238
|
|
@@ -305,12 +253,11 @@ class Team(BaseModel):
|
|
305
253
|
"""
|
306
254
|
|
307
255
|
team_planner = TeamPlanner(tasks=self.tasks, planner_llm=self.planning_llm)
|
308
|
-
idling_managers: List[TeamMember] = [member for member in self.members if member.
|
309
|
-
idling_members: List[TeamMember] = [member for member in self.members if member.
|
256
|
+
idling_managers: List[TeamMember] = [member for member in self.members if member.is_idling and member.is_manager is True]
|
257
|
+
idling_members: List[TeamMember] = [member for member in self.members if member.is_idling and member.is_manager is False]
|
310
258
|
unassigned_tasks: List[Task] = self.member_tasks_without_agent
|
311
259
|
new_team_members: List[TeamMember] = []
|
312
260
|
|
313
|
-
|
314
261
|
if self.team_tasks:
|
315
262
|
candidates = idling_managers + idling_members
|
316
263
|
if candidates:
|
@@ -433,16 +380,15 @@ class Team(BaseModel):
|
|
433
380
|
if skipped_task_output:
|
434
381
|
continue
|
435
382
|
|
436
|
-
# self._prepare_agent_tools(task)
|
437
383
|
# self._log_task_start(task, responsible_agent)
|
438
384
|
|
439
385
|
if task.async_execution:
|
440
386
|
context = create_raw_outputs(tasks=[task, ], task_outputs=([last_sync_output,] if last_sync_output else []))
|
441
|
-
future = task.execute_async(agent=responsible_agent, context=context
|
387
|
+
future = task.execute_async(agent=responsible_agent, context=context)
|
442
388
|
futures.append((task, future, task_index))
|
443
389
|
else:
|
444
390
|
context = create_raw_outputs(tasks=[task,], task_outputs=([last_sync_output,] if last_sync_output else [] ))
|
445
|
-
task_output = task.execute_sync(agent=responsible_agent, context=context
|
391
|
+
task_output = task.execute_sync(agent=responsible_agent, context=context)
|
446
392
|
if self.managers and responsible_agent in [manager.agent for manager in self.managers]:
|
447
393
|
lead_task_output = task_output
|
448
394
|
|
@@ -468,7 +414,6 @@ class Team(BaseModel):
|
|
468
414
|
|
469
415
|
metrics: List[UsageMetrics] = []
|
470
416
|
|
471
|
-
|
472
417
|
if self.team_tasks or self.member_tasks_without_agent:
|
473
418
|
self._handle_team_planning()
|
474
419
|
|
@@ -511,3 +456,51 @@ class Team(BaseModel):
|
|
511
456
|
self.usage_metrics.add_usage_metrics(metric)
|
512
457
|
|
513
458
|
return result
|
459
|
+
|
460
|
+
|
461
|
+
@property
|
462
|
+
def key(self) -> str:
|
463
|
+
source = [str(member.agent.id.__str__) for member in self.members] + [str(task.id.__str__) for task in self.tasks]
|
464
|
+
return md5("|".join(source).encode(), usedforsecurity=False).hexdigest()
|
465
|
+
|
466
|
+
|
467
|
+
@property
|
468
|
+
def managers(self) -> List[TeamMember] | None:
|
469
|
+
managers = [member for member in self.members if member.is_manager == True]
|
470
|
+
return managers if len(managers) > 0 else None
|
471
|
+
|
472
|
+
|
473
|
+
@property
|
474
|
+
def manager_tasks(self) -> List[Task] | None:
|
475
|
+
"""
|
476
|
+
Tasks (incl. team tasks) handled by managers in the team.
|
477
|
+
"""
|
478
|
+
if self.managers:
|
479
|
+
tasks = [manager.task for manager in self.managers if manager.task is not None]
|
480
|
+
return tasks if len(tasks) > 0 else None
|
481
|
+
|
482
|
+
return None
|
483
|
+
|
484
|
+
|
485
|
+
@property
|
486
|
+
def tasks(self):
|
487
|
+
"""
|
488
|
+
Return all the tasks that the team needs to handle in order of priority:
|
489
|
+
1. team tasks, -> assigned to the member
|
490
|
+
2. manager_task,
|
491
|
+
3. members' tasks
|
492
|
+
"""
|
493
|
+
|
494
|
+
team_tasks = self.team_tasks
|
495
|
+
manager_tasks = [member.task for member in self.members if member.is_manager == True and member.task is not None and member.task not in team_tasks]
|
496
|
+
member_tasks = [member.task for member in self.members if member.is_manager == False and member.task is not None and member.task not in team_tasks]
|
497
|
+
|
498
|
+
return team_tasks + manager_tasks + member_tasks
|
499
|
+
|
500
|
+
|
501
|
+
@property
|
502
|
+
def member_tasks_without_agent(self) -> List[Task] | None:
|
503
|
+
if self.members:
|
504
|
+
return [member.task for member in self.members if member.agent is None]
|
505
|
+
|
506
|
+
return None
|
versionhq/tool/__init__.py
CHANGED
@@ -0,0 +1,53 @@
|
|
1
|
+
from enum import Enum
|
2
|
+
|
3
|
+
DEFAULT_AUTH_SCHEME = "OAUTH2"
|
4
|
+
|
5
|
+
class ComposioAuthScheme(str, Enum):
|
6
|
+
OAUTH2 = "OAUTH2"
|
7
|
+
BEARER_TOKEN = "BEARER_TOKEN"
|
8
|
+
API_KEY = "API_KEY"
|
9
|
+
|
10
|
+
|
11
|
+
class ComposioAppName(str, Enum):
|
12
|
+
"""
|
13
|
+
Enum to store app names that we can connect via Composio as data pipelines or destination services.
|
14
|
+
"""
|
15
|
+
|
16
|
+
SALESFORCE = "salesforce"
|
17
|
+
AIRTABLE = "airtable"
|
18
|
+
MAILCHIMP = "mailchimp"
|
19
|
+
HUBSPOT = "hubspot"
|
20
|
+
KLAVIYO = "klaviyo"
|
21
|
+
GOOGLESHEET = "googlesheets"
|
22
|
+
GMAIL = "gmail"
|
23
|
+
FACEBOOK = "facebook"
|
24
|
+
TWITTER = "twitter"
|
25
|
+
TWITTER_MEDIA = "twitter_media"
|
26
|
+
LINKEDIN = "linkedin"
|
27
|
+
|
28
|
+
|
29
|
+
composio_app_set = [
|
30
|
+
(ComposioAppName.SALESFORCE, ComposioAuthScheme.OAUTH2),
|
31
|
+
(ComposioAppName.AIRTABLE, ComposioAuthScheme.OAUTH2, ComposioAuthScheme.API_KEY, ComposioAuthScheme.BEARER_TOKEN),
|
32
|
+
(ComposioAppName.MAILCHIMP, ComposioAuthScheme.OAUTH2),
|
33
|
+
(ComposioAppName.HUBSPOT, ComposioAuthScheme.OAUTH2, ComposioAuthScheme.BEARER_TOKEN),
|
34
|
+
(ComposioAppName.KLAVIYO, ComposioAuthScheme.OAUTH2, ComposioAuthScheme.API_KEY),
|
35
|
+
(ComposioAppName.GOOGLESHEET, ComposioAuthScheme.OAUTH2),
|
36
|
+
(ComposioAppName.GMAIL, ComposioAuthScheme.OAUTH2, ComposioAuthScheme.BEARER_TOKEN),
|
37
|
+
(ComposioAppName.TWITTER, ComposioAuthScheme.OAUTH2),
|
38
|
+
(ComposioAppName.TWITTER_MEDIA, ComposioAuthScheme.OAUTH2),
|
39
|
+
(ComposioAppName.FACEBOOK, ComposioAuthScheme.OAUTH2),
|
40
|
+
(ComposioAppName.LINKEDIN, ComposioAuthScheme.OAUTH2),
|
41
|
+
]
|
42
|
+
|
43
|
+
class COMPOSIO_STATUS(str, Enum):
|
44
|
+
INITIATED = "initiated"
|
45
|
+
ACTIVE = "active"
|
46
|
+
FAILED = "failed"
|
47
|
+
|
48
|
+
|
49
|
+
|
50
|
+
class ComposioAction(str, Enum):
|
51
|
+
"""
|
52
|
+
Enum to store composio's action that can be called via `Actions.xxx`
|
53
|
+
"""
|