versionhq 1.1.11.8__py3-none-any.whl → 1.1.12.2__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 +49 -13
- versionhq/_utils/__init__.py +3 -0
- versionhq/_utils/logger.py +6 -1
- versionhq/agent/inhouse_agents.py +41 -0
- versionhq/agent/model.py +10 -7
- versionhq/agent/rpm_controller.py +1 -1
- versionhq/clients/product/model.py +0 -1
- versionhq/knowledge/__init__.py +22 -0
- versionhq/knowledge/model.py +0 -2
- versionhq/knowledge/source.py +0 -1
- versionhq/llm/llm_vars.py +14 -124
- versionhq/llm/model.py +35 -37
- versionhq/memory/model.py +109 -47
- versionhq/storage/ltm_sqlite_storage.py +29 -43
- versionhq/storage/mem0_storage.py +1 -1
- versionhq/storage/rag_storage.py +23 -22
- versionhq/storage/task_output_storage.py +6 -6
- versionhq/task/TEMPLATES/Description.py +3 -3
- versionhq/task/__init__.py +0 -9
- versionhq/task/evaluate.py +19 -8
- versionhq/task/formation.py +123 -0
- versionhq/task/model.py +88 -110
- versionhq/task/structured_response.py +1 -1
- versionhq/team/model.py +43 -62
- versionhq/team/team_planner.py +5 -2
- versionhq/tool/model.py +1 -1
- {versionhq-1.1.11.8.dist-info → versionhq-1.1.12.2.dist-info}/LICENSE +1 -1
- {versionhq-1.1.11.8.dist-info → versionhq-1.1.12.2.dist-info}/METADATA +15 -16
- {versionhq-1.1.11.8.dist-info → versionhq-1.1.12.2.dist-info}/RECORD +31 -30
- versionhq/agent/default_agents.py +0 -15
- {versionhq-1.1.11.8.dist-info → versionhq-1.1.12.2.dist-info}/WHEEL +0 -0
- {versionhq-1.1.11.8.dist-info → versionhq-1.1.12.2.dist-info}/top_level.txt +0 -0
versionhq/task/model.py
CHANGED
@@ -5,18 +5,17 @@ import uuid
|
|
5
5
|
import inspect
|
6
6
|
from concurrent.futures import Future
|
7
7
|
from hashlib import md5
|
8
|
-
from typing import Any, Dict, List, Set, Optional,
|
8
|
+
from typing import Any, Dict, List, Set, Optional, Callable, Type
|
9
9
|
from typing_extensions import Annotated, Self
|
10
10
|
|
11
|
-
from pydantic import UUID4, BaseModel, Field, PrivateAttr, field_validator, model_validator,
|
11
|
+
from pydantic import UUID4, BaseModel, Field, PrivateAttr, field_validator, model_validator, InstanceOf, field_validator
|
12
12
|
from pydantic_core import PydanticCustomError
|
13
13
|
|
14
|
-
|
15
|
-
from versionhq.task import TaskOutputFormat
|
14
|
+
|
16
15
|
from versionhq.task.log_handler import TaskOutputStorageHandler
|
17
16
|
from versionhq.task.evaluate import Evaluation, EvaluationItem
|
18
17
|
from versionhq.tool.model import Tool, ToolSet
|
19
|
-
from versionhq._utils
|
18
|
+
from versionhq._utils import process_config, Logger
|
20
19
|
|
21
20
|
|
22
21
|
class ResponseField(BaseModel):
|
@@ -77,9 +76,7 @@ class ResponseField(BaseModel):
|
|
77
76
|
if self.properties:
|
78
77
|
for item in self.properties:
|
79
78
|
nested_p.update(**item._format_props())
|
80
|
-
|
81
|
-
if item.required:
|
82
|
-
nested_r.append(item.title)
|
79
|
+
nested_r.append(item.title)
|
83
80
|
|
84
81
|
props = {
|
85
82
|
"type": schema_type,
|
@@ -97,8 +94,6 @@ class ResponseField(BaseModel):
|
|
97
94
|
if self.properties:
|
98
95
|
for item in self.properties:
|
99
96
|
p.update(**item._format_props())
|
100
|
-
|
101
|
-
# if item.required:
|
102
97
|
r.append(item.title)
|
103
98
|
|
104
99
|
props = {
|
@@ -204,12 +199,25 @@ class TaskOutput(BaseModel):
|
|
204
199
|
eval_criteria = task.eval_criteria if task.eval_criteria else ["Overall competitiveness", ]
|
205
200
|
|
206
201
|
for item in eval_criteria:
|
207
|
-
|
202
|
+
task_eval = Task(
|
208
203
|
description=EVALUATE.format(task_description=task.description, task_output=self.raw, eval_criteria=str(item)),
|
209
204
|
pydantic_output=EvaluationItem
|
210
205
|
)
|
211
|
-
|
212
|
-
|
206
|
+
res = task_eval.execute_sync(agent=self.evaluation.responsible_agent)
|
207
|
+
|
208
|
+
if res.pydantic:
|
209
|
+
item = EvaluationItem(score=res.pydantic.score, suggestion=res.pydantic.suggestion, criteria=res.pydantic.criteria)
|
210
|
+
self.evaluation.items.append(item)
|
211
|
+
|
212
|
+
else:
|
213
|
+
try:
|
214
|
+
item = EvaluationItem(
|
215
|
+
score=float(res.json_dict["score"]), suggestion=res.json_dict["suggestion"], criteria=res.json_dict["criteria"]
|
216
|
+
)
|
217
|
+
self.evaluation.items.append(item)
|
218
|
+
except Exception as e:
|
219
|
+
Logger(verbose=True).log(level="error", message=f"Failed to convert the evaluation items: {str(e)}", color="red")
|
220
|
+
pass
|
213
221
|
|
214
222
|
return self.evaluation
|
215
223
|
|
@@ -224,14 +232,6 @@ class TaskOutput(BaseModel):
|
|
224
232
|
|
225
233
|
@property
|
226
234
|
def json(self) -> Optional[str]:
|
227
|
-
if self.output_format != TaskOutputFormat.JSON:
|
228
|
-
raise ValueError(
|
229
|
-
"""
|
230
|
-
Invalid output format requested.
|
231
|
-
If you would like to access the JSON output,
|
232
|
-
pleae make sure to set the output_json property for the task
|
233
|
-
"""
|
234
|
-
)
|
235
235
|
return json.dumps(self.json_dict)
|
236
236
|
|
237
237
|
|
@@ -239,7 +239,6 @@ class TaskOutput(BaseModel):
|
|
239
239
|
return str(self.pydantic) if self.pydantic else str(self.json_dict) if self.json_dict else self.raw
|
240
240
|
|
241
241
|
|
242
|
-
|
243
242
|
class Task(BaseModel):
|
244
243
|
"""
|
245
244
|
Task to be executed by agents or teams.
|
@@ -286,7 +285,7 @@ class Task(BaseModel):
|
|
286
285
|
processed_by_agents: Set[str] = Field(default_factory=set, description="store responsible agents' roles")
|
287
286
|
tools_errors: int = 0
|
288
287
|
delegations: int = 0
|
289
|
-
latency: int | float = 0 #
|
288
|
+
latency: int | float = 0 # job latency in sec
|
290
289
|
tokens: int = 0 # tokens consumed
|
291
290
|
|
292
291
|
|
@@ -394,14 +393,6 @@ Ref. Output image: {output_formats_to_follow}
|
|
394
393
|
return "\n".join(task_slices)
|
395
394
|
|
396
395
|
|
397
|
-
def _get_output_format(self) -> TaskOutputFormat:
|
398
|
-
if self.output_json == True:
|
399
|
-
return TaskOutputFormat.JSON
|
400
|
-
if self.output_pydantic == True:
|
401
|
-
return TaskOutputFormat.PYDANTIC
|
402
|
-
return TaskOutputFormat.RAW
|
403
|
-
|
404
|
-
|
405
396
|
def _structure_response_format(self, data_type: str = "object", model_provider: str = "gemini") -> Dict[str, Any] | None:
|
406
397
|
"""
|
407
398
|
Structure a response format either from`response_fields` or `pydantic_output`.
|
@@ -412,37 +403,38 @@ Ref. Output image: {output_formats_to_follow}
|
|
412
403
|
|
413
404
|
response_format: Dict[str, Any] = None
|
414
405
|
|
415
|
-
|
416
|
-
|
417
|
-
if self.response_fields:
|
418
|
-
properties, required_fields = {}, []
|
419
|
-
for i, item in enumerate(self.response_fields):
|
420
|
-
if item:
|
421
|
-
if item.data_type is dict:
|
422
|
-
properties.update(item._format_props())
|
423
|
-
else:
|
424
|
-
properties.update(item._format_props())
|
425
|
-
|
426
|
-
required_fields.append(item.title)
|
427
|
-
|
428
|
-
response_schema = {
|
429
|
-
"type": "object",
|
430
|
-
"properties": properties,
|
431
|
-
"required": required_fields,
|
432
|
-
"additionalProperties": False,
|
433
|
-
}
|
406
|
+
if model_provider == "openrouter":
|
407
|
+
return response_format
|
434
408
|
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
409
|
+
else:
|
410
|
+
if self.response_fields:
|
411
|
+
properties, required_fields = {}, []
|
412
|
+
for i, item in enumerate(self.response_fields):
|
413
|
+
if item:
|
414
|
+
if item.data_type is dict:
|
415
|
+
properties.update(item._format_props())
|
416
|
+
else:
|
417
|
+
properties.update(item._format_props())
|
418
|
+
|
419
|
+
required_fields.append(item.title)
|
420
|
+
|
421
|
+
response_schema = {
|
422
|
+
"type": "object",
|
423
|
+
"properties": properties,
|
424
|
+
"required": required_fields,
|
425
|
+
"additionalProperties": False,
|
426
|
+
}
|
427
|
+
|
428
|
+
response_format = {
|
429
|
+
"type": "json_schema",
|
430
|
+
"json_schema": { "name": "outcome", "schema": response_schema }
|
431
|
+
}
|
439
432
|
|
440
433
|
|
441
|
-
|
442
|
-
|
434
|
+
elif self.pydantic_output:
|
435
|
+
response_format = StructuredOutput(response_format=self.pydantic_output)._format()
|
443
436
|
|
444
|
-
|
445
|
-
return response_format
|
437
|
+
return response_format
|
446
438
|
|
447
439
|
|
448
440
|
def _create_json_output(self, raw: str) -> Dict[str, Any]:
|
@@ -507,75 +499,59 @@ Ref. Output image: {output_formats_to_follow}
|
|
507
499
|
self.description = self._original_description.format(**inputs)
|
508
500
|
|
509
501
|
|
510
|
-
def
|
502
|
+
def _create_short_and_long_term_memories(self, agent: Any, task_output: TaskOutput) -> None:
|
511
503
|
"""
|
512
|
-
After the task execution, create and save short-term
|
504
|
+
After the task execution, create and save short-term/long-term memories in the storage.
|
513
505
|
"""
|
514
|
-
|
515
506
|
from versionhq.agent.model import Agent
|
516
|
-
from versionhq.memory.model import ShortTermMemory
|
507
|
+
from versionhq.memory.model import ShortTermMemory, MemoryMetadata, LongTermMemory
|
517
508
|
|
518
|
-
|
519
|
-
if isinstance(agent, Agent) and agent.use_memory == True:
|
520
|
-
if hasattr(agent, "short_term_memory"):
|
521
|
-
agent.short_term_memory.save(value=task_output.raw, metadata={ "observation": self.description, }, agent=agent.role)
|
522
|
-
else:
|
523
|
-
agent.short_term_memory = ShortTermMemory(agent=agent, embedder_config=agent.embedder_config)
|
524
|
-
agent.short_term_memory.save(value=task_output.raw, metadata={ "observation": self.description, }, agent=agent.role)
|
509
|
+
agent = agent if isinstance(agent, Agent) else Agent(role=str(agent), goal=str(agent), use_memory=True)
|
525
510
|
|
526
|
-
|
527
|
-
|
528
|
-
pass
|
529
|
-
|
530
|
-
|
531
|
-
def _create_long_term_memory(self, agent, task_output: TaskOutput) -> None:
|
532
|
-
"""
|
533
|
-
Create and save long-term and entity memory items based on evaluation.
|
534
|
-
"""
|
535
|
-
from versionhq.agent.model import Agent
|
536
|
-
from versionhq.memory.model import LongTermMemory, LongTermMemoryItem
|
511
|
+
if agent.use_memory == False:
|
512
|
+
return None
|
537
513
|
|
538
514
|
try:
|
539
|
-
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
|
549
|
-
"quality": evaluation.aggregate_score,
|
550
|
-
},
|
551
|
-
)
|
515
|
+
evaluation = task_output.evaluation if task_output.evaluation else None
|
516
|
+
memory_metadata = evaluation._create_memory_metadata() if evaluation else MemoryMetadata()
|
517
|
+
|
518
|
+
agent.short_term_memory = agent.short_term_memory if agent.short_term_memory else ShortTermMemory(agent=agent, embedder_config=agent.embedder_config)
|
519
|
+
agent.short_term_memory.save(
|
520
|
+
task_description=str(self.description),
|
521
|
+
task_output=str(task_output.raw),
|
522
|
+
agent=str(agent.role),
|
523
|
+
metadata=memory_metadata
|
524
|
+
)
|
552
525
|
|
553
|
-
|
554
|
-
|
555
|
-
|
556
|
-
|
557
|
-
|
526
|
+
agent.long_term_memory = agent.long_term_memory if agent.long_term_memory else LongTermMemory()
|
527
|
+
agent.long_term_memory.save(
|
528
|
+
task_description=str(self.description),
|
529
|
+
task_output=str(task_output.raw),
|
530
|
+
agent=str(agent.role),
|
531
|
+
metadata=memory_metadata
|
532
|
+
)
|
558
533
|
|
559
534
|
except AttributeError as e:
|
560
535
|
self._logger.log(level="error", message=f"Missing attributes for long term memory: {str(e)}", color="red")
|
561
536
|
pass
|
562
537
|
|
563
538
|
except Exception as e:
|
564
|
-
self._logger.log(level="error", message=f"Failed to add to
|
539
|
+
self._logger.log(level="error", message=f"Failed to add to the memory: {str(e)}", color="red")
|
565
540
|
pass
|
566
541
|
|
567
542
|
|
568
543
|
# task execution
|
569
|
-
def execute_sync(self, agent, context: Optional[str] = None) -> TaskOutput:
|
544
|
+
def execute_sync(self, agent, context: Optional[str | List[Any]] = None) -> TaskOutput:
|
570
545
|
"""
|
571
546
|
Execute the task synchronously.
|
572
547
|
When the task has context, make sure we have executed all the tasks in the context first.
|
573
548
|
"""
|
574
549
|
|
575
550
|
if self.context:
|
576
|
-
|
577
|
-
|
578
|
-
task
|
551
|
+
if isinstance(self.context, list):
|
552
|
+
for task in self.context:
|
553
|
+
if isinstance(task, Task) and task.output is None:
|
554
|
+
task._execute_core(agent, context)
|
579
555
|
|
580
556
|
return self._execute_core(agent, context)
|
581
557
|
|
@@ -612,7 +588,7 @@ Ref. Output image: {output_formats_to_follow}
|
|
612
588
|
task_output: InstanceOf[TaskOutput] = None
|
613
589
|
tool_output: str | list = None
|
614
590
|
task_tools: List[List[InstanceOf[Tool]| InstanceOf[ToolSet] | Type[Tool]]] = []
|
615
|
-
started_at = datetime.datetime.now()
|
591
|
+
started_at, ended_at = datetime.datetime.now(), datetime.datetime.now()
|
616
592
|
|
617
593
|
if self.tools:
|
618
594
|
for item in self.tools:
|
@@ -638,11 +614,16 @@ Ref. Output image: {output_formats_to_follow}
|
|
638
614
|
|
639
615
|
|
640
616
|
if self.tool_res_as_final == True:
|
617
|
+
started_at = datetime.datetime.now()
|
641
618
|
tool_output = agent.execute_task(task=self, context=context, task_tools=task_tools)
|
619
|
+
ended_at = datetime.datetime.now()
|
642
620
|
task_output = TaskOutput(task_id=self.id, tool_output=tool_output, raw=str(tool_output) if tool_output else "")
|
643
621
|
|
644
622
|
else:
|
623
|
+
started_at = datetime.datetime.now()
|
645
624
|
raw_output = agent.execute_task(task=self, context=context, task_tools=task_tools)
|
625
|
+
ended_at = datetime.datetime.now()
|
626
|
+
|
646
627
|
json_dict_output = self._create_json_output(raw=raw_output)
|
647
628
|
if "outcome" in json_dict_output:
|
648
629
|
json_dict_output = self._create_json_output(raw=str(json_dict_output["outcome"]))
|
@@ -656,18 +637,15 @@ Ref. Output image: {output_formats_to_follow}
|
|
656
637
|
json_dict=json_dict_output
|
657
638
|
)
|
658
639
|
|
659
|
-
ended_at = datetime.datetime.now()
|
660
|
-
self.latency = (ended_at - started_at).total_seconds()
|
661
640
|
|
641
|
+
self.latency = (ended_at - started_at).total_seconds()
|
662
642
|
self.output = task_output
|
663
643
|
self.processed_by_agents.add(agent.role)
|
664
644
|
|
665
645
|
if self.should_evaluate:
|
666
646
|
task_output.evaluate(task=self, latency=self.latency, tokens=self.tokens)
|
667
647
|
|
668
|
-
self.
|
669
|
-
self._create_long_term_memory(agent=agent, task_output=task_output)
|
670
|
-
|
648
|
+
self._create_short_and_long_term_memories(agent=agent, task_output=task_output)
|
671
649
|
|
672
650
|
if self.callback and isinstance(self.callback, Callable):
|
673
651
|
kwargs = { **self.callback_kwargs, **task_output.json_dict }
|
@@ -697,7 +675,7 @@ Ref. Output image: {output_formats_to_follow}
|
|
697
675
|
|
698
676
|
@property
|
699
677
|
def key(self) -> str:
|
700
|
-
output_format =
|
678
|
+
output_format = "json" if self.response_fields else "pydantic" if self.pydantic_output is not None else "raw"
|
701
679
|
source = [self.description, output_format]
|
702
680
|
return md5("|".join(source).encode(), usedforsecurity=False).hexdigest()
|
703
681
|
|
versionhq/team/model.py
CHANGED
@@ -1,17 +1,16 @@
|
|
1
1
|
import uuid
|
2
2
|
import warnings
|
3
|
-
import json
|
4
3
|
from enum import Enum
|
5
4
|
from dotenv import load_dotenv
|
6
5
|
from concurrent.futures import Future
|
7
6
|
from hashlib import md5
|
8
|
-
from typing import Any, Dict, List,
|
9
|
-
from pydantic import UUID4,
|
7
|
+
from typing import Any, Dict, List, Callable, Optional, Tuple
|
8
|
+
from pydantic import UUID4, BaseModel, Field, PrivateAttr, field_validator, model_validator
|
10
9
|
from pydantic._internal._generate_schema import GenerateSchema
|
11
10
|
from pydantic_core import PydanticCustomError, core_schema
|
12
11
|
|
13
12
|
from versionhq.agent.model import Agent
|
14
|
-
from versionhq.task.model import Task, TaskOutput, ConditionalTask
|
13
|
+
from versionhq.task.model import Task, TaskOutput, ConditionalTask
|
15
14
|
from versionhq.task.formatter import create_raw_outputs
|
16
15
|
from versionhq.team.team_planner import TeamPlanner
|
17
16
|
from versionhq._utils.logger import Logger
|
@@ -20,7 +19,6 @@ from versionhq._utils.usage_metrics import UsageMetrics
|
|
20
19
|
|
21
20
|
initial_match_type = GenerateSchema.match_type
|
22
21
|
|
23
|
-
|
24
22
|
def match_type(self, obj):
|
25
23
|
if getattr(obj, "__name__", None) == "datetime":
|
26
24
|
return core_schema.datetime_schema()
|
@@ -28,7 +26,6 @@ def match_type(self, obj):
|
|
28
26
|
|
29
27
|
|
30
28
|
GenerateSchema.match_type = match_type
|
31
|
-
|
32
29
|
warnings.filterwarnings("ignore", category=SyntaxWarning, module="pysbd")
|
33
30
|
load_dotenv(override=True)
|
34
31
|
|
@@ -40,6 +37,16 @@ load_dotenv(override=True)
|
|
40
37
|
# pass
|
41
38
|
|
42
39
|
|
40
|
+
|
41
|
+
class Formation(str, Enum):
|
42
|
+
SOLO = 1
|
43
|
+
SUPERVISING = 2
|
44
|
+
NETWORK = 3
|
45
|
+
RANDOM = 4
|
46
|
+
HYBRID = 10
|
47
|
+
UNDEFINED = 0
|
48
|
+
|
49
|
+
|
43
50
|
class TaskHandlingProcess(str, Enum):
|
44
51
|
"""
|
45
52
|
Class representing the different processes that can be used to tackle multiple tasks.
|
@@ -49,25 +56,25 @@ class TaskHandlingProcess(str, Enum):
|
|
49
56
|
consensual = "consensual"
|
50
57
|
|
51
58
|
|
52
|
-
class TeamOutput(
|
59
|
+
class TeamOutput(TaskOutput):
|
53
60
|
"""
|
54
|
-
|
55
|
-
`json_dict` and `raw` store overall output of tasks that handled by the team,
|
56
|
-
while `task_output_list` stores each TaskOutput instance to the tasks handled by the team members.
|
57
|
-
Note that `raw` and `json_dict` will be prioritized as TeamOutput to refer over `task_output_list`.
|
61
|
+
A class to store output from the team, inherited from TaskOutput class.
|
58
62
|
"""
|
59
63
|
|
60
64
|
team_id: UUID4 = Field(default_factory=uuid.uuid4, frozen=True, description="store the team ID that generate the TeamOutput")
|
61
|
-
|
62
|
-
|
63
|
-
json_dict: Dict[str, Any] = Field(default=None, description="`raw` converted to dictionary")
|
64
|
-
task_output_list: list[TaskOutput] = Field(default=list, description="store output of all the tasks that the team has executed")
|
65
|
+
task_description: str = Field(default=None, description="store initial request (task description) from the client")
|
66
|
+
task_outputs: list[TaskOutput] = Field(default=list, description="store outputs of all tasks that the team has executed")
|
65
67
|
token_usage: UsageMetrics = Field(default=dict, description="processed token summary")
|
66
68
|
|
69
|
+
|
70
|
+
def return_all_task_outputs(self) -> List[Dict[str, Any]]:
|
71
|
+
res = [output.json_dict for output in self.task_outputs]
|
72
|
+
return res
|
73
|
+
|
74
|
+
|
67
75
|
def __str__(self):
|
68
76
|
return (str(self.pydantic) if self.pydantic else str(self.json_dict) if self.json_dict else self.raw)
|
69
77
|
|
70
|
-
|
71
78
|
def __getitem__(self, key):
|
72
79
|
if self.pydantic and hasattr(self.pydantic, key):
|
73
80
|
return getattr(self.pydantic, key)
|
@@ -77,40 +84,16 @@ class TeamOutput(BaseModel):
|
|
77
84
|
raise KeyError(f"Key '{key}' not found in the team output.")
|
78
85
|
|
79
86
|
|
80
|
-
@property
|
81
|
-
def json(self) -> Optional[str]:
|
82
|
-
if self.tasks_output[-1].output_format != TaskOutputFormat.JSON:
|
83
|
-
raise ValueError(
|
84
|
-
"No JSON output found in the final task. Please make sure to set the output_json property in the final task in your team."
|
85
|
-
)
|
86
|
-
return json.dumps(self.json_dict)
|
87
|
-
|
88
|
-
|
89
|
-
def to_dict(self) -> Dict[str, Any]:
|
90
|
-
"""
|
91
|
-
Convert pydantic / raw output into dict and return the dict.
|
92
|
-
When we only have `raw` output, return `{ output: raw }` to avoid an error
|
93
|
-
"""
|
94
|
-
|
95
|
-
output_dict = {}
|
96
|
-
if self.json_dict:
|
97
|
-
output_dict.update(self.json_dict)
|
98
|
-
elif self.pydantic:
|
99
|
-
output_dict.update(self.pydantic.model_dump())
|
100
|
-
else:
|
101
|
-
output_dict.upate({ "output": self.raw })
|
102
|
-
return output_dict
|
103
|
-
|
104
|
-
|
105
|
-
def return_all_task_outputs(self) -> List[Dict[str, Any]]:
|
106
|
-
res = [output.json_dict for output in self.task_output_list]
|
107
|
-
return res
|
108
|
-
|
109
87
|
|
110
88
|
class TeamMember(BaseModel):
|
111
|
-
|
89
|
+
"""
|
90
|
+
A class to store a team member
|
91
|
+
"""
|
92
|
+
agent: Agent | None = Field(default=None)
|
112
93
|
is_manager: bool = Field(default=False)
|
113
|
-
|
94
|
+
can_share_knowledge: bool = Field(default=True, description="whether to share the agent's knowledge in the team")
|
95
|
+
can_share_memory: bool = Field(default=True, description="whether to share the agent's memory in the team")
|
96
|
+
task: Optional[Task] = Field(default=None, description="task assigned to the agent")
|
114
97
|
|
115
98
|
@property
|
116
99
|
def is_idling(self):
|
@@ -125,17 +108,19 @@ class Team(BaseModel):
|
|
125
108
|
|
126
109
|
__hash__ = object.__hash__
|
127
110
|
_execution_span: Any = PrivateAttr()
|
128
|
-
_logger: Logger = PrivateAttr()
|
111
|
+
_logger: Logger = PrivateAttr(default_factory=lambda: Logger(verbose=True))
|
129
112
|
_inputs: Optional[Dict[str, Any]] = PrivateAttr(default=None)
|
130
113
|
|
131
114
|
id: UUID4 = Field(default_factory=uuid.uuid4, frozen=True)
|
132
115
|
name: Optional[str] = Field(default=None)
|
133
|
-
members: List[TeamMember] = Field(default_factory=list
|
116
|
+
members: List[TeamMember] = Field(default_factory=list)
|
117
|
+
formation: Optional[Formation] = Field(default=None)
|
118
|
+
|
119
|
+
# formation planning
|
120
|
+
planning_llm: Optional[Any] = Field(default=None, description="llm to generate formation")
|
121
|
+
team_tasks: Optional[List[Task]] = Field(default_factory=list, description="optional tasks for the team. can be assigned to team members later")
|
134
122
|
|
135
|
-
#
|
136
|
-
team_tasks: Optional[List[Task]] = Field(default_factory=list, description="optional tasks for the team")
|
137
|
-
planning_llm: Optional[Any] = Field(default=None, description="llm to handle the planning of the team tasks (if any)")
|
138
|
-
function_calling_llm: Optional[Any] = Field(default=None, description="llm to execute func after all agent execution (if any)")
|
123
|
+
# task execution rules
|
139
124
|
prompt_file: str = Field(default="", description="path to the prompt json file to be used by the team.")
|
140
125
|
process: TaskHandlingProcess = Field(default=TaskHandlingProcess.sequential)
|
141
126
|
|
@@ -150,7 +135,6 @@ class Team(BaseModel):
|
|
150
135
|
)
|
151
136
|
step_callback: Optional[Any] = Field(default=None, description="callback to be executed after each step for all agents execution")
|
152
137
|
|
153
|
-
verbose: bool = Field(default=True)
|
154
138
|
cache: bool = Field(default=True)
|
155
139
|
memory: bool = Field(default=False, description="whether the team should use memory to store memories of its execution")
|
156
140
|
execution_logs: List[Dict[str, Any]] = Field(default=[], description="list of execution logs for tasks")
|
@@ -236,7 +220,7 @@ class Team(BaseModel):
|
|
236
220
|
return self
|
237
221
|
|
238
222
|
|
239
|
-
def _get_responsible_agent(self, task: Task) -> Agent:
|
223
|
+
def _get_responsible_agent(self, task: Task) -> Agent | None:
|
240
224
|
if task is None:
|
241
225
|
return None
|
242
226
|
else:
|
@@ -244,7 +228,7 @@ class Team(BaseModel):
|
|
244
228
|
return None if len(res) == 0 else res[0]
|
245
229
|
|
246
230
|
|
247
|
-
def
|
231
|
+
def _handle_agent_formation(self) -> None:
|
248
232
|
"""
|
249
233
|
Form a team considering agents and tasks given, and update `self.members` field:
|
250
234
|
1. Idling managers to take the team tasks.
|
@@ -321,7 +305,7 @@ class Team(BaseModel):
|
|
321
305
|
raw=final_task_output.raw,
|
322
306
|
json_dict=final_task_output.json_dict,
|
323
307
|
pydantic=final_task_output.pydantic,
|
324
|
-
|
308
|
+
task_outputs=task_outputs,
|
325
309
|
token_usage=token_usage,
|
326
310
|
)
|
327
311
|
|
@@ -373,7 +357,7 @@ class Team(BaseModel):
|
|
373
357
|
|
374
358
|
responsible_agent = self._get_responsible_agent(task)
|
375
359
|
if responsible_agent is None:
|
376
|
-
self.
|
360
|
+
self._handle_agent_formation()
|
377
361
|
|
378
362
|
if isinstance(task, ConditionalTask):
|
379
363
|
skipped_task_output = task._handle_conditional_task(task_outputs, futures, task_index, was_replayed)
|
@@ -415,7 +399,7 @@ class Team(BaseModel):
|
|
415
399
|
metrics: List[UsageMetrics] = []
|
416
400
|
|
417
401
|
if self.team_tasks or self.member_tasks_without_agent:
|
418
|
-
self.
|
402
|
+
self._handle_agent_formation()
|
419
403
|
|
420
404
|
if kwargs_before is not None:
|
421
405
|
for before_callback in self.before_kickoff_callbacks:
|
@@ -432,9 +416,6 @@ class Team(BaseModel):
|
|
432
416
|
agent = member.agent
|
433
417
|
agent.team = self
|
434
418
|
|
435
|
-
if not agent.function_calling_llm and self.function_calling_llm:
|
436
|
-
agent.function_calling_llm = self.function_calling_llm
|
437
|
-
|
438
419
|
if self.step_callback:
|
439
420
|
agent.callbacks.append(self.step_callback)
|
440
421
|
|
versionhq/team/team_planner.py
CHANGED
@@ -8,7 +8,11 @@ load_dotenv(override=True)
|
|
8
8
|
|
9
9
|
class TeamPlanner:
|
10
10
|
"""
|
11
|
-
|
11
|
+
A class to handle agent formations based on the given task description.
|
12
|
+
1) complexity
|
13
|
+
2) agent built (or use given agents)
|
14
|
+
3) knowledge, memory sharing
|
15
|
+
4) form a team
|
12
16
|
"""
|
13
17
|
|
14
18
|
from versionhq.task.model import Task, ResponseField, TaskOutput
|
@@ -59,7 +63,6 @@ class TeamPlanner:
|
|
59
63
|
return new_member_list
|
60
64
|
|
61
65
|
|
62
|
-
|
63
66
|
def _handle_task_planning(self, context: Optional[str] = None, tools: Optional[str] = None) -> TaskOutput:
|
64
67
|
"""
|
65
68
|
Handles the team planning by creating detailed step-by-step plans for each task.
|
versionhq/tool/model.py
CHANGED
@@ -2,7 +2,7 @@ from abc import ABC, abstractmethod
|
|
2
2
|
from inspect import signature
|
3
3
|
from typing import Any, Dict, Callable, Type, Optional, get_args, get_origin, get_type_hints
|
4
4
|
from typing_extensions import Self
|
5
|
-
from pydantic import InstanceOf, BaseModel,
|
5
|
+
from pydantic import InstanceOf, BaseModel, Field, field_validator, model_validator, PrivateAttr, create_model
|
6
6
|
from pydantic_core import PydanticCustomError
|
7
7
|
|
8
8
|
from versionhq.llm.llm_vars import SchemaType
|