versionhq 1.1.7.2__tar.gz → 1.1.7.4__tar.gz
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-1.1.7.2 → versionhq-1.1.7.4}/PKG-INFO +10 -11
- {versionhq-1.1.7.2 → versionhq-1.1.7.4}/README.md +9 -10
- {versionhq-1.1.7.2 → versionhq-1.1.7.4}/pyproject.toml +1 -1
- {versionhq-1.1.7.2 → versionhq-1.1.7.4}/src/versionhq/__init__.py +5 -2
- {versionhq-1.1.7.2 → versionhq-1.1.7.4}/src/versionhq/agent/model.py +2 -2
- {versionhq-1.1.7.2 → versionhq-1.1.7.4}/src/versionhq/clients/product/model.py +11 -36
- versionhq-1.1.7.4/src/versionhq/clients/workflow/model.py +158 -0
- {versionhq-1.1.7.2 → versionhq-1.1.7.4}/src/versionhq/task/model.py +34 -5
- {versionhq-1.1.7.2 → versionhq-1.1.7.4}/src/versionhq/team/model.py +12 -26
- {versionhq-1.1.7.2 → versionhq-1.1.7.4}/src/versionhq.egg-info/PKG-INFO +10 -11
- {versionhq-1.1.7.2 → versionhq-1.1.7.4}/src/versionhq.egg-info/SOURCES.txt +1 -0
- versionhq-1.1.7.4/tests/clients/workflow_test.py +21 -0
- {versionhq-1.1.7.2 → versionhq-1.1.7.4}/tests/task/task_test.py +98 -7
- {versionhq-1.1.7.2 → versionhq-1.1.7.4}/tests/team/team_test.py +94 -12
- {versionhq-1.1.7.2 → versionhq-1.1.7.4}/uv.lock +13 -13
- versionhq-1.1.7.2/src/versionhq/clients/workflow/model.py +0 -174
- {versionhq-1.1.7.2 → versionhq-1.1.7.4}/.github/workflows/publish.yml +0 -0
- {versionhq-1.1.7.2 → versionhq-1.1.7.4}/.github/workflows/publish_testpypi.yml +0 -0
- {versionhq-1.1.7.2 → versionhq-1.1.7.4}/.github/workflows/run_tests.yml +0 -0
- {versionhq-1.1.7.2 → versionhq-1.1.7.4}/.github/workflows/security_check.yml +0 -0
- {versionhq-1.1.7.2 → versionhq-1.1.7.4}/.gitignore +0 -0
- {versionhq-1.1.7.2 → versionhq-1.1.7.4}/.pre-commit-config.yaml +0 -0
- {versionhq-1.1.7.2 → versionhq-1.1.7.4}/.python-version +0 -0
- {versionhq-1.1.7.2 → versionhq-1.1.7.4}/LICENSE +0 -0
- {versionhq-1.1.7.2 → versionhq-1.1.7.4}/SECURITY.md +0 -0
- {versionhq-1.1.7.2 → versionhq-1.1.7.4}/db/preprocess.py +0 -0
- {versionhq-1.1.7.2 → versionhq-1.1.7.4}/requirements.txt +0 -0
- {versionhq-1.1.7.2 → versionhq-1.1.7.4}/runtime.txt +0 -0
- {versionhq-1.1.7.2 → versionhq-1.1.7.4}/setup.cfg +0 -0
- {versionhq-1.1.7.2 → versionhq-1.1.7.4}/src/versionhq/_utils/__init__.py +0 -0
- {versionhq-1.1.7.2 → versionhq-1.1.7.4}/src/versionhq/_utils/cache_handler.py +0 -0
- {versionhq-1.1.7.2 → versionhq-1.1.7.4}/src/versionhq/_utils/i18n.py +0 -0
- {versionhq-1.1.7.2 → versionhq-1.1.7.4}/src/versionhq/_utils/logger.py +0 -0
- {versionhq-1.1.7.2 → versionhq-1.1.7.4}/src/versionhq/_utils/process_config.py +0 -0
- {versionhq-1.1.7.2 → versionhq-1.1.7.4}/src/versionhq/_utils/rpm_controller.py +0 -0
- {versionhq-1.1.7.2 → versionhq-1.1.7.4}/src/versionhq/_utils/usage_metrics.py +0 -0
- {versionhq-1.1.7.2 → versionhq-1.1.7.4}/src/versionhq/agent/TEMPLATES/Backstory.py +0 -0
- {versionhq-1.1.7.2 → versionhq-1.1.7.4}/src/versionhq/agent/TEMPLATES/__init__.py +0 -0
- {versionhq-1.1.7.2 → versionhq-1.1.7.4}/src/versionhq/agent/__init__.py +0 -0
- {versionhq-1.1.7.2 → versionhq-1.1.7.4}/src/versionhq/agent/parser.py +0 -0
- {versionhq-1.1.7.2 → versionhq-1.1.7.4}/src/versionhq/cli/__init__.py +0 -0
- {versionhq-1.1.7.2 → versionhq-1.1.7.4}/src/versionhq/clients/__init__.py +0 -0
- {versionhq-1.1.7.2 → versionhq-1.1.7.4}/src/versionhq/clients/customer/__init__.py +0 -0
- {versionhq-1.1.7.2 → versionhq-1.1.7.4}/src/versionhq/clients/customer/model.py +0 -0
- {versionhq-1.1.7.2 → versionhq-1.1.7.4}/src/versionhq/clients/product/__init__.py +0 -0
- {versionhq-1.1.7.2 → versionhq-1.1.7.4}/src/versionhq/clients/workflow/__init__.py +0 -0
- {versionhq-1.1.7.2 → versionhq-1.1.7.4}/src/versionhq/llm/__init__.py +0 -0
- {versionhq-1.1.7.2 → versionhq-1.1.7.4}/src/versionhq/llm/llm_vars.py +0 -0
- {versionhq-1.1.7.2 → versionhq-1.1.7.4}/src/versionhq/llm/model.py +0 -0
- {versionhq-1.1.7.2 → versionhq-1.1.7.4}/src/versionhq/task/__init__.py +0 -0
- {versionhq-1.1.7.2 → versionhq-1.1.7.4}/src/versionhq/task/formatter.py +0 -0
- {versionhq-1.1.7.2 → versionhq-1.1.7.4}/src/versionhq/team/__init__.py +0 -0
- {versionhq-1.1.7.2 → versionhq-1.1.7.4}/src/versionhq/team/team_planner.py +0 -0
- {versionhq-1.1.7.2 → versionhq-1.1.7.4}/src/versionhq/tool/__init__.py +0 -0
- {versionhq-1.1.7.2 → versionhq-1.1.7.4}/src/versionhq/tool/decorator.py +0 -0
- {versionhq-1.1.7.2 → versionhq-1.1.7.4}/src/versionhq/tool/model.py +0 -0
- {versionhq-1.1.7.2 → versionhq-1.1.7.4}/src/versionhq/tool/tool_handler.py +0 -0
- {versionhq-1.1.7.2 → versionhq-1.1.7.4}/src/versionhq.egg-info/dependency_links.txt +0 -0
- {versionhq-1.1.7.2 → versionhq-1.1.7.4}/src/versionhq.egg-info/requires.txt +0 -0
- {versionhq-1.1.7.2 → versionhq-1.1.7.4}/src/versionhq.egg-info/top_level.txt +0 -0
- {versionhq-1.1.7.2 → versionhq-1.1.7.4}/tests/__init__.py +0 -0
- {versionhq-1.1.7.2 → versionhq-1.1.7.4}/tests/agent/__init__.py +0 -0
- {versionhq-1.1.7.2 → versionhq-1.1.7.4}/tests/agent/agent_test.py +0 -0
- {versionhq-1.1.7.2 → versionhq-1.1.7.4}/tests/cli/__init__.py +0 -0
- {versionhq-1.1.7.2 → versionhq-1.1.7.4}/tests/conftest.py +0 -0
- {versionhq-1.1.7.2 → versionhq-1.1.7.4}/tests/task/__init__.py +0 -0
- {versionhq-1.1.7.2 → versionhq-1.1.7.4}/tests/team/Prompts/Demo_test.py +0 -0
- {versionhq-1.1.7.2 → versionhq-1.1.7.4}/tests/team/__init__.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: versionhq
|
3
|
-
Version: 1.1.7.
|
3
|
+
Version: 1.1.7.4
|
4
4
|
Summary: LLM orchestration frameworks for model-agnostic AI agents that handle complex outbound workflows
|
5
5
|
Author-email: Kuriko Iwai <kuriko@versi0n.io>
|
6
6
|
License: MIT License
|
@@ -52,7 +52,7 @@ Requires-Dist: wheel>=0.45.1
|
|
52
52
|
|
53
53
|
# Overview
|
54
54
|
|
55
|
-
 [](https://github.com/versionHQ/multi-agent-system/actions/workflows/publish.yml)  [](https://github.com/versionHQ/multi-agent-system/actions/workflows/publish.yml)   
|
56
56
|
|
57
57
|
|
58
58
|
An LLM orchestration frameworks for multi-agent systems with RAG to autopilot outbound workflows.
|
@@ -142,7 +142,7 @@ Multiple `agents` can form a `team` to complete complex tasks together.
|
|
142
142
|
role="demo",
|
143
143
|
goal="amazing project goal",
|
144
144
|
skillsets=["skill_1", "skill_2", ],
|
145
|
-
llm="llm-of-choice"
|
145
|
+
llm="llm-of-your-choice"
|
146
146
|
)
|
147
147
|
|
148
148
|
task = Task(
|
@@ -153,20 +153,19 @@ Multiple `agents` can form a `team` to complete complex tasks together.
|
|
153
153
|
ResponseField(title="test1", type=str, required=True),
|
154
154
|
ResponseField(title="test2", type=list, required=True),
|
155
155
|
],
|
156
|
-
context=["amazing context",],
|
157
|
-
tools=["amazing tool"],
|
158
156
|
callback=None,
|
159
157
|
)
|
160
|
-
|
161
|
-
res = task.execute_sync(agent=agent)
|
162
|
-
|
158
|
+
res = task.execute_sync(agent=agent, context="amazing context to consider.")
|
163
159
|
return res.to_dict()
|
164
|
-
|
165
160
|
```
|
166
161
|
|
167
|
-
|
162
|
+
This will return a dictionary with keys defined in the `ResponseField`.
|
163
|
+
|
164
|
+
```
|
165
|
+
{ test1: "answer1", "test2": ["answer2-1", "answer2-2", "answer2-3",] }
|
166
|
+
```
|
168
167
|
|
169
|
-
[
|
168
|
+
For more info: [PyPI package](https://pypi.org/project/versionhq/)
|
170
169
|
|
171
170
|
<hr />
|
172
171
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# Overview
|
2
2
|
|
3
|
-
 [](https://github.com/versionHQ/multi-agent-system/actions/workflows/publish.yml)  [](https://github.com/versionHQ/multi-agent-system/actions/workflows/publish.yml)   
|
4
4
|
|
5
5
|
|
6
6
|
An LLM orchestration frameworks for multi-agent systems with RAG to autopilot outbound workflows.
|
@@ -90,7 +90,7 @@ Multiple `agents` can form a `team` to complete complex tasks together.
|
|
90
90
|
role="demo",
|
91
91
|
goal="amazing project goal",
|
92
92
|
skillsets=["skill_1", "skill_2", ],
|
93
|
-
llm="llm-of-choice"
|
93
|
+
llm="llm-of-your-choice"
|
94
94
|
)
|
95
95
|
|
96
96
|
task = Task(
|
@@ -101,20 +101,19 @@ Multiple `agents` can form a `team` to complete complex tasks together.
|
|
101
101
|
ResponseField(title="test1", type=str, required=True),
|
102
102
|
ResponseField(title="test2", type=list, required=True),
|
103
103
|
],
|
104
|
-
context=["amazing context",],
|
105
|
-
tools=["amazing tool"],
|
106
104
|
callback=None,
|
107
105
|
)
|
108
|
-
|
109
|
-
res = task.execute_sync(agent=agent)
|
110
|
-
|
106
|
+
res = task.execute_sync(agent=agent, context="amazing context to consider.")
|
111
107
|
return res.to_dict()
|
112
|
-
|
113
108
|
```
|
114
109
|
|
115
|
-
|
110
|
+
This will return a dictionary with keys defined in the `ResponseField`.
|
111
|
+
|
112
|
+
```
|
113
|
+
{ test1: "answer1", "test2": ["answer2-1", "answer2-2", "answer2-3",] }
|
114
|
+
```
|
116
115
|
|
117
|
-
[
|
116
|
+
For more info: [PyPI package](https://pypi.org/project/versionhq/)
|
118
117
|
|
119
118
|
<hr />
|
120
119
|
|
@@ -15,7 +15,7 @@ exclude = ["test*", "__pycache__"]
|
|
15
15
|
|
16
16
|
[project]
|
17
17
|
name = "versionhq"
|
18
|
-
version = "1.1.7.
|
18
|
+
version = "1.1.7.4"
|
19
19
|
authors = [{ name = "Kuriko Iwai", email = "kuriko@versi0n.io" }]
|
20
20
|
description = "LLM orchestration frameworks for model-agnostic AI agents that handle complex outbound workflows"
|
21
21
|
readme = "README.md"
|
@@ -10,20 +10,23 @@ warnings.filterwarnings(
|
|
10
10
|
from versionhq.agent.model import Agent
|
11
11
|
from versionhq.clients.customer.model import Customer
|
12
12
|
from versionhq.clients.product.model import Product, ProductProvider
|
13
|
-
from versionhq.clients.workflow.model import MessagingWorkflow
|
13
|
+
from versionhq.clients.workflow.model import MessagingWorkflow, MessagingComponent, Score, ScoreFormat
|
14
14
|
from versionhq.llm.model import LLM
|
15
15
|
from versionhq.task.model import Task, TaskOutput
|
16
16
|
from versionhq.team.model import Team, TeamOutput
|
17
17
|
from versionhq.tool.model import Tool
|
18
18
|
|
19
19
|
|
20
|
-
__version__ = "1.1.7.
|
20
|
+
__version__ = "1.1.7.4"
|
21
21
|
__all__ = [
|
22
22
|
"Agent",
|
23
23
|
"Customer",
|
24
24
|
"Product",
|
25
25
|
"ProductProvider",
|
26
26
|
"MessagingWorkflow",
|
27
|
+
"MessagingComponent",
|
28
|
+
"Score",
|
29
|
+
"ScoreFormat",
|
27
30
|
"LLM",
|
28
31
|
"Task",
|
29
32
|
"TaskOutput",
|
@@ -95,7 +95,7 @@ class Agent(ABC, BaseModel):
|
|
95
95
|
role: str = Field(description="role of the agent - used in summary and logs")
|
96
96
|
goal: str = Field(description="concise goal of the agent (details are set in the Task instance)")
|
97
97
|
backstory: Optional[str] = Field(default=None, description="system context passed to the LLM")
|
98
|
-
knowledge: Optional[str] = Field(default=None)
|
98
|
+
knowledge: Optional[str] = Field(default=None, description="external knowledge fed to the agent")
|
99
99
|
skillsets: Optional[List[str]] = Field(default_factory=list)
|
100
100
|
|
101
101
|
# tools
|
@@ -358,7 +358,7 @@ class Agent(ABC, BaseModel):
|
|
358
358
|
return {"output": response.output if hasattr(response, "output") else response}
|
359
359
|
|
360
360
|
|
361
|
-
def execute_task(self, task, context: Optional[str] = None) -> str:
|
361
|
+
def execute_task(self, task, context: Optional[str] = None, tools: Optional[str] = None) -> str:
|
362
362
|
"""
|
363
363
|
Execute the task and return the output in string.
|
364
364
|
To simplify, the tools are cascaded from the `tools_called` under the `task` Task instance if any.
|
@@ -1,15 +1,6 @@
|
|
1
1
|
import uuid
|
2
2
|
from typing import Any, Dict, List, Callable, Type, Optional, get_args, get_origin
|
3
|
-
from pydantic import
|
4
|
-
UUID4,
|
5
|
-
InstanceOf,
|
6
|
-
BaseModel,
|
7
|
-
ConfigDict,
|
8
|
-
Field,
|
9
|
-
create_model,
|
10
|
-
field_validator,
|
11
|
-
model_validator,
|
12
|
-
)
|
3
|
+
from pydantic import UUID4, InstanceOf, BaseModel, ConfigDict, Field, create_model, field_validator, model_validator
|
13
4
|
from pydantic_core import PydanticCustomError
|
14
5
|
|
15
6
|
|
@@ -23,24 +14,16 @@ class ProductProvider(BaseModel):
|
|
23
14
|
|
24
15
|
id: UUID4 = Field(default_factory=uuid.uuid4, frozen=True)
|
25
16
|
name: Optional[str] = Field(default=None, description="client name")
|
26
|
-
region: Optional[str] = Field(
|
27
|
-
|
28
|
-
)
|
29
|
-
data_pipeline: Optional[List[str]] = Field(
|
30
|
-
default=None, description="store the data pipelines that the client is using"
|
31
|
-
)
|
32
|
-
destinations: Optional[List[str]] = Field(
|
33
|
-
default=None,
|
34
|
-
description="store the destination services that the client is using",
|
35
|
-
)
|
17
|
+
region: Optional[str] = Field(default=None, description="region of client's main business operation")
|
18
|
+
data_pipeline: Optional[List[str]] = Field(default=None, description="store the data pipelines that the client is using")
|
19
|
+
destinations: Optional[List[str]] = Field(default=None,description="store the destination services that the client is using")
|
36
20
|
|
37
21
|
@field_validator("id", mode="before")
|
38
22
|
@classmethod
|
39
23
|
def _deny_user_set_id(cls, v: Optional[UUID4]) -> None:
|
40
24
|
if v:
|
41
|
-
raise PydanticCustomError(
|
42
|
-
|
43
|
-
)
|
25
|
+
raise PydanticCustomError("may_not_set_field", "This field is not to be set by the user.", {})
|
26
|
+
|
44
27
|
|
45
28
|
|
46
29
|
class Product(BaseModel):
|
@@ -51,24 +34,16 @@ class Product(BaseModel):
|
|
51
34
|
id: UUID4 = Field(default_factory=uuid.uuid4, frozen=True)
|
52
35
|
name: Optional[str] = Field(default=None, description="product name")
|
53
36
|
description: Optional[str] = Field(
|
54
|
-
default=None,
|
55
|
-
max_length=256,
|
56
|
-
description="product description scraped from landing url or client input. cascade to the agent",
|
57
|
-
)
|
37
|
+
default=None,max_length=256,description="product description scraped from landing url or client input. cascade to the agent")
|
58
38
|
provider: Optional[ProductProvider] = Field(default=None)
|
59
39
|
audience: Optional[str] = Field(default=None, description="target audience")
|
60
40
|
usp: Optional[str] = Field(default=None)
|
61
|
-
landing_url: Optional[str] = Field(
|
62
|
-
|
63
|
-
|
64
|
-
cohort_timeframe: Optional[int] = Field(
|
65
|
-
default=30, description="ideal cohort timeframe of the product in days"
|
66
|
-
)
|
41
|
+
landing_url: Optional[str] = Field(default=None, description="marketing url of the product if any")
|
42
|
+
cohort_timeframe: Optional[int] = Field(default=30, description="ideal cohort timeframe of the product in days")
|
43
|
+
|
67
44
|
|
68
45
|
@field_validator("id", mode="before")
|
69
46
|
@classmethod
|
70
47
|
def _deny_user_set_id(cls, v: Optional[UUID4]) -> None:
|
71
48
|
if v:
|
72
|
-
raise PydanticCustomError(
|
73
|
-
"may_not_set_field", "This field is not to be set by the user.", {}
|
74
|
-
)
|
49
|
+
raise PydanticCustomError("may_not_set_field", "This field is not to be set by the user.", {})
|
@@ -0,0 +1,158 @@
|
|
1
|
+
import uuid
|
2
|
+
from abc import ABC
|
3
|
+
from datetime import date, datetime, time, timedelta
|
4
|
+
from typing import Any, Dict, List, Union, Callable, Type, Optional, get_args, get_origin
|
5
|
+
from pydantic import UUID4, InstanceOf, BaseModel, ConfigDict, Field, create_model, field_validator, model_validator
|
6
|
+
from pydantic_core import PydanticCustomError
|
7
|
+
|
8
|
+
from versionhq.clients.product.model import Product
|
9
|
+
from versionhq.clients.customer.model import Customer
|
10
|
+
from versionhq.agent.model import Agent
|
11
|
+
from versionhq.team.model import Team
|
12
|
+
|
13
|
+
|
14
|
+
class ScoreFormat:
|
15
|
+
def __init__(self, rate: Union[float, int] = 0, weight: int = 1):
|
16
|
+
self.rate = rate
|
17
|
+
self.weight = weight
|
18
|
+
self.aggregate = rate * weight
|
19
|
+
|
20
|
+
|
21
|
+
class Score:
|
22
|
+
"""
|
23
|
+
Evaluate the score on 0 (no performance) to 1 scale.
|
24
|
+
`rate`: Any float from 0.0 to 1.0 given by an agent.
|
25
|
+
`weight`: Importance of each factor to the aggregated score.
|
26
|
+
"""
|
27
|
+
|
28
|
+
def __init__(
|
29
|
+
self,
|
30
|
+
brand_tone: ScoreFormat = ScoreFormat(0, 0),
|
31
|
+
audience: ScoreFormat = ScoreFormat(0, 0),
|
32
|
+
track_record: ScoreFormat = ScoreFormat(0, 0),
|
33
|
+
**kwargs: Optional[Dict[str, ScoreFormat]],
|
34
|
+
):
|
35
|
+
self.brand_tone = brand_tone
|
36
|
+
self.audience = audience
|
37
|
+
self.track_record = track_record
|
38
|
+
self.kwargs = kwargs
|
39
|
+
|
40
|
+
|
41
|
+
def result(self) -> int:
|
42
|
+
aggregate_score = self.brand_tone.aggregate + self.audience.aggregate + self.track_record.aggregate
|
43
|
+
denominator = self.brand_tone.weight + self.audience.weight + self.track_record.weight
|
44
|
+
|
45
|
+
for k, v in self.kwargs.items():
|
46
|
+
aggregate_score += v.aggregate
|
47
|
+
denominator += v.weight
|
48
|
+
|
49
|
+
if denominator == 0:
|
50
|
+
return 0
|
51
|
+
|
52
|
+
return round(aggregate_score / denominator, 2)
|
53
|
+
|
54
|
+
|
55
|
+
|
56
|
+
class MessagingComponent(ABC, BaseModel):
|
57
|
+
layer_id: int = Field(default=0, description="add id of the layer: 0, 1, 2")
|
58
|
+
message: str = Field(default=None, max_length=1024, description="text message content to be sent")
|
59
|
+
interval: Optional[str] = Field(
|
60
|
+
default=None,description="interval to move on to the next layer. if this is the last layer, set as `None`")
|
61
|
+
score: Union[float, InstanceOf[Score]] = Field(default=None)
|
62
|
+
|
63
|
+
|
64
|
+
def store_scoring_result(self, scoring_subject: str, score: Union[int, Score, ScoreFormat] = None):
|
65
|
+
"""
|
66
|
+
Set up the `score` field
|
67
|
+
"""
|
68
|
+
|
69
|
+
if isinstance(score, Score):
|
70
|
+
setattr(self, "score", score)
|
71
|
+
|
72
|
+
elif isinstance(score, ScoreFormat):
|
73
|
+
score_instance = Score()
|
74
|
+
setattr(score_instance, scoring_subject, score)
|
75
|
+
setattr(self, "score", score_instance)
|
76
|
+
|
77
|
+
elif isinstance(score, int) or isinstance(score, float):
|
78
|
+
score_instance, score_format_instance = Score(), ScoreFormat(rate=score, weight=1)
|
79
|
+
setattr(score_instance, "kwargs", { scoring_subject: score_format_instance })
|
80
|
+
setattr(self, "score", score_instance)
|
81
|
+
|
82
|
+
else:
|
83
|
+
pass
|
84
|
+
|
85
|
+
return self
|
86
|
+
|
87
|
+
|
88
|
+
|
89
|
+
class MessagingWorkflow(ABC, BaseModel):
|
90
|
+
"""
|
91
|
+
Store 3 layers of messaging workflow sent to `customer` on the `product`
|
92
|
+
"""
|
93
|
+
|
94
|
+
_created_at: Optional[datetime]
|
95
|
+
_updated_at: Optional[datetime]
|
96
|
+
|
97
|
+
model_config = ConfigDict()
|
98
|
+
|
99
|
+
id: UUID4 = Field(default_factory=uuid.uuid4, frozen=True)
|
100
|
+
components: List[MessagingComponent] = Field(default_factory=list, description="store messaging components in the workflow")
|
101
|
+
|
102
|
+
# responsible tean or agents
|
103
|
+
team: Optional[Team] = Field(default=None, description="store `Team` instance responsibile for autopiloting this workflow")
|
104
|
+
agents: Optional[List[Agent]] = Field(
|
105
|
+
default=None, description="store `Agent` instances responsible for autopiloting this workflow. if the team exsits, this field remains as `None`")
|
106
|
+
|
107
|
+
# metrics
|
108
|
+
destination: Optional[str] = Field( default=None, description="destination service to launch this workflow")
|
109
|
+
product: InstanceOf[Product] = Field(default=None)
|
110
|
+
customer: InstanceOf[Customer] = Field(default=None)
|
111
|
+
|
112
|
+
metrics: Union[List[Dict[str, Any]], List[str]] = Field(
|
113
|
+
default=None, max_length=256, description="store metrics that used to predict and track the performance of this workflow.")
|
114
|
+
|
115
|
+
|
116
|
+
@property
|
117
|
+
def name(self):
|
118
|
+
if self.customer.id:
|
119
|
+
return f"Workflow ID: {self.id} - on {self.product.id} for {self.customer.id}"
|
120
|
+
else:
|
121
|
+
return f"Workflow ID: {self.id} - on {self.product.id}"
|
122
|
+
|
123
|
+
|
124
|
+
@field_validator("id", mode="before")
|
125
|
+
@classmethod
|
126
|
+
def _deny_user_set_id(cls, v: Optional[UUID4]) -> None:
|
127
|
+
if v:
|
128
|
+
raise PydanticCustomError("may_not_set_field", "This field is not to be set by the user.", {})
|
129
|
+
|
130
|
+
|
131
|
+
@model_validator(mode="after")
|
132
|
+
def set_up_destination(self):
|
133
|
+
"""
|
134
|
+
Set up the destination service when self.destination is None.
|
135
|
+
Prioritize customer's destination to the product provider's destination list.
|
136
|
+
"""
|
137
|
+
if self.destination is None:
|
138
|
+
if self.customer is not None:
|
139
|
+
self.destination = self.customer.on
|
140
|
+
|
141
|
+
else:
|
142
|
+
destination_list = self.product.provider.destinations
|
143
|
+
if destination_list:
|
144
|
+
self.destination = destination_list[0]
|
145
|
+
return self
|
146
|
+
|
147
|
+
|
148
|
+
def reassign_agent_or_team(self, agents: List[Agent] = None, team: Team = None) -> None:
|
149
|
+
"""
|
150
|
+
Fire unresponsible agents/team and assign new one.
|
151
|
+
"""
|
152
|
+
|
153
|
+
if not agents and not team:
|
154
|
+
raise ValueError("Need to add at least 1 agent or team.")
|
155
|
+
|
156
|
+
self.agents = agents
|
157
|
+
self.team = team
|
158
|
+
self.updated_at = datetime.datetime.now()
|
@@ -151,12 +151,14 @@ class Task(BaseModel):
|
|
151
151
|
# task setup
|
152
152
|
context: Optional[List["Task"]] = Field(default=None, description="other tasks whose outputs should be used as context")
|
153
153
|
tools_called: Optional[List[ToolCalled]] = Field(default_factory=list, description="tools that the agent can use for this task")
|
154
|
-
take_tool_res_as_final: bool = Field(default=False,description="when set True, tools res will be stored in the `TaskOutput`")
|
154
|
+
take_tool_res_as_final: bool = Field(default=False, description="when set True, tools res will be stored in the `TaskOutput`")
|
155
|
+
allow_delegation: bool = Field(default=False, description="ask other agents for help and run the task instead")
|
155
156
|
|
156
157
|
prompt_context: Optional[str] = Field(default=None)
|
157
158
|
async_execution: bool = Field(default=False,description="whether the task should be executed asynchronously or not")
|
158
159
|
config: Optional[Dict[str, Any]] = Field(default=None, description="configuration for the agent")
|
159
160
|
callback: Optional[Any] = Field(default=None, description="callback to be executed after the task is completed.")
|
161
|
+
callback_kwargs: Optional[Dict[str, Any]] = Field(default_factory=dict, description="kwargs for the callback when the callback is callable")
|
160
162
|
|
161
163
|
# recording
|
162
164
|
processed_by_agents: Set[str] = Field(default_factory=set)
|
@@ -399,7 +401,10 @@ Your outputs MUST adhere to the following format and should NOT include any irre
|
|
399
401
|
|
400
402
|
|
401
403
|
def _execute_task_async(self, agent, context: Optional[str], future: Future[TaskOutput]) -> None:
|
402
|
-
"""
|
404
|
+
"""
|
405
|
+
Execute the task asynchronously with context handling.
|
406
|
+
"""
|
407
|
+
|
403
408
|
result = self._execute_core(agent, context)
|
404
409
|
future.set_result(result)
|
405
410
|
|
@@ -407,12 +412,33 @@ Your outputs MUST adhere to the following format and should NOT include any irre
|
|
407
412
|
def _execute_core(self, agent, context: Optional[str]) -> TaskOutput:
|
408
413
|
"""
|
409
414
|
Run the core execution logic of the task.
|
415
|
+
To speed up the process, when the format is not expected to return, we will skip the conversion process.
|
416
|
+
When the task is allowed to delegate to another agent, we will select a responsible one in order of manager_agent > peer_agent > anoymous agent.
|
410
417
|
"""
|
418
|
+
from versionhq.agent.model import Agent
|
419
|
+
from versionhq.team.model import Team
|
411
420
|
|
412
421
|
self.prompt_context = context
|
422
|
+
|
423
|
+
if self.allow_delegation:
|
424
|
+
agent_to_delegate = None
|
425
|
+
|
426
|
+
if hasattr(agent, "team") and isinstance(agent.team, Team):
|
427
|
+
if agent.team.manager_agent:
|
428
|
+
agent_to_delegate = agent.team.manager_agent
|
429
|
+
else:
|
430
|
+
peers = [member.agent for member in agent.team.members if member.is_manager == False and member.agent.id is not agent.id]
|
431
|
+
if len(peers) > 0:
|
432
|
+
agent_to_delegate = peers[0]
|
433
|
+
else:
|
434
|
+
agent_to_delegate = Agent(role="delegated_agent", goal=agent.goal, llm=agent.llm)
|
435
|
+
|
436
|
+
agent = agent_to_delegate
|
437
|
+
self.delegations += 1
|
438
|
+
|
413
439
|
output_raw = agent.execute_task(task=self, context=context)
|
414
|
-
output_json_dict = self.create_json_output(raw_result=output_raw)
|
415
|
-
output_pydantic = self.create_pydantic_output(output_json_dict=output_json_dict)
|
440
|
+
output_json_dict = self.create_json_output(raw_result=output_raw) if self.expected_output_json is True else None
|
441
|
+
output_pydantic = self.create_pydantic_output(output_json_dict=output_json_dict) if self.expected_output_pydantic else None
|
416
442
|
task_output = TaskOutput(
|
417
443
|
task_id=self.id,
|
418
444
|
raw=output_raw,
|
@@ -425,7 +451,10 @@ Your outputs MUST adhere to the following format and should NOT include any irre
|
|
425
451
|
# self._set_end_execution_time(start_time)
|
426
452
|
|
427
453
|
if self.callback:
|
428
|
-
self.callback
|
454
|
+
if isinstance(self.callback, Callable):
|
455
|
+
self.callback(**self.callback_kwargs)
|
456
|
+
else:
|
457
|
+
self.callback(self.output)
|
429
458
|
|
430
459
|
# if self._execution_span:
|
431
460
|
# # self._telemetry.task_ended(self._execution_span, self, agent.team)
|
@@ -7,17 +7,9 @@ from dotenv import load_dotenv
|
|
7
7
|
from concurrent.futures import Future
|
8
8
|
from hashlib import md5
|
9
9
|
from typing import Any, Dict, List, TYPE_CHECKING, Callable, Optional, Tuple, Union
|
10
|
-
from pydantic import
|
11
|
-
|
12
|
-
|
13
|
-
Json,
|
14
|
-
BaseModel,
|
15
|
-
Field,
|
16
|
-
PrivateAttr,
|
17
|
-
field_validator,
|
18
|
-
model_validator,
|
19
|
-
)
|
20
|
-
from pydantic_core import PydanticCustomError
|
10
|
+
from pydantic import UUID4, InstanceOf, Json, BaseModel, Field, PrivateAttr, field_validator, model_validator
|
11
|
+
from pydantic._internal._generate_schema import GenerateSchema
|
12
|
+
from pydantic_core import PydanticCustomError, core_schema
|
21
13
|
|
22
14
|
from versionhq.agent.model import Agent
|
23
15
|
from versionhq.task.model import Task, TaskOutput, ConditionalTask, TaskOutputFormat
|
@@ -27,9 +19,6 @@ from versionhq._utils.logger import Logger
|
|
27
19
|
from versionhq._utils.usage_metrics import UsageMetrics
|
28
20
|
|
29
21
|
|
30
|
-
from pydantic._internal._generate_schema import GenerateSchema
|
31
|
-
from pydantic_core import core_schema
|
32
|
-
|
33
22
|
initial_match_type = GenerateSchema.match_type
|
34
23
|
|
35
24
|
|
@@ -122,7 +111,7 @@ class TeamOutput(BaseModel):
|
|
122
111
|
class TeamMember(ABC, BaseModel):
|
123
112
|
agent: Agent | None = Field(default=None, description="store the agent to be a member")
|
124
113
|
is_manager: bool = Field(default=False)
|
125
|
-
task: Task
|
114
|
+
task: Optional[Task] = Field(default=None)
|
126
115
|
|
127
116
|
|
128
117
|
class Team(BaseModel):
|
@@ -156,7 +145,6 @@ class Team(BaseModel):
|
|
156
145
|
default_factory=list,
|
157
146
|
description="list of callback functions to be executed after the team kickoff. i.e., store the result in repo"
|
158
147
|
)
|
159
|
-
task_callback: Optional[Any] = Field(default=None, description="callback to be executed after each task for all agents execution")
|
160
148
|
step_callback: Optional[Any] = Field(default=None, description="callback to be executed after each step for all agents execution")
|
161
149
|
|
162
150
|
verbose: bool = Field(default=True)
|
@@ -301,10 +289,8 @@ class Team(BaseModel):
|
|
301
289
|
|
302
290
|
# task execution
|
303
291
|
def _process_async_tasks(
|
304
|
-
|
305
|
-
|
306
|
-
was_replayed: bool = False,
|
307
|
-
) -> List[TaskOutput]:
|
292
|
+
self, futures: List[Tuple[Task, Future[TaskOutput], int]], was_replayed: bool = False
|
293
|
+
) -> List[TaskOutput]:
|
308
294
|
task_outputs: List[TaskOutput] = []
|
309
295
|
for future_task, future, task_index in futures:
|
310
296
|
task_output = future.result()
|
@@ -315,6 +301,7 @@ class Team(BaseModel):
|
|
315
301
|
)
|
316
302
|
return task_outputs
|
317
303
|
|
304
|
+
|
318
305
|
def _handle_conditional_task(
|
319
306
|
self,
|
320
307
|
task: ConditionalTask,
|
@@ -323,6 +310,7 @@ class Team(BaseModel):
|
|
323
310
|
task_index: int,
|
324
311
|
was_replayed: bool,
|
325
312
|
) -> Optional[TaskOutput]:
|
313
|
+
|
326
314
|
if futures:
|
327
315
|
task_outputs = self._process_async_tasks(futures, was_replayed)
|
328
316
|
futures.clear()
|
@@ -347,6 +335,7 @@ class Team(BaseModel):
|
|
347
335
|
Take the output of the first task or the lead task output as the team output `raw` value.
|
348
336
|
Note that `tasks` are already sorted by the importance.
|
349
337
|
"""
|
338
|
+
|
350
339
|
if len(task_outputs) < 1:
|
351
340
|
raise ValueError("Something went wrong. Kickoff should return only one task output.")
|
352
341
|
|
@@ -389,7 +378,7 @@ class Team(BaseModel):
|
|
389
378
|
"""
|
390
379
|
Executes tasks sequentially and returns the final output in TeamOutput class.
|
391
380
|
When we have a manager agent, we will start from executing manager agent's tasks.
|
392
|
-
Priority
|
381
|
+
Priority:
|
393
382
|
1. Team tasks > 2. Manager task > 3. Member tasks (in order of index)
|
394
383
|
"""
|
395
384
|
|
@@ -422,7 +411,7 @@ class Team(BaseModel):
|
|
422
411
|
|
423
412
|
if task.async_execution:
|
424
413
|
context = create_raw_outputs(tasks=[task, ],task_outputs=([last_sync_output,] if last_sync_output else []))
|
425
|
-
future = task.execute_async(agent=responsible_agent, context=context
|
414
|
+
future = task.execute_async(agent=responsible_agent, context=context
|
426
415
|
# tools=responsible_agent.tools
|
427
416
|
)
|
428
417
|
futures.append((task, future, task_index))
|
@@ -432,7 +421,7 @@ class Team(BaseModel):
|
|
432
421
|
futures.clear()
|
433
422
|
|
434
423
|
context = create_raw_outputs(tasks=[task,], task_outputs=([ last_sync_output,] if last_sync_output else [] ))
|
435
|
-
task_output = task.execute_sync(agent=responsible_agent, context=context
|
424
|
+
task_output = task.execute_sync(agent=responsible_agent, context=context
|
436
425
|
# tools=responsible_agent.tools
|
437
426
|
)
|
438
427
|
if responsible_agent is self.manager_agent:
|
@@ -473,9 +462,6 @@ class Team(BaseModel):
|
|
473
462
|
# self._inputs = inputs
|
474
463
|
# self._interpolate_inputs(inputs)
|
475
464
|
|
476
|
-
for task in self.tasks:
|
477
|
-
if not task.callback:
|
478
|
-
task.callback = self.task_callback
|
479
465
|
|
480
466
|
# i18n = I18N(prompt_file=self.prompt_file)
|
481
467
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: versionhq
|
3
|
-
Version: 1.1.7.
|
3
|
+
Version: 1.1.7.4
|
4
4
|
Summary: LLM orchestration frameworks for model-agnostic AI agents that handle complex outbound workflows
|
5
5
|
Author-email: Kuriko Iwai <kuriko@versi0n.io>
|
6
6
|
License: MIT License
|
@@ -52,7 +52,7 @@ Requires-Dist: wheel>=0.45.1
|
|
52
52
|
|
53
53
|
# Overview
|
54
54
|
|
55
|
-
 [](https://github.com/versionHQ/multi-agent-system/actions/workflows/publish.yml)  [](https://github.com/versionHQ/multi-agent-system/actions/workflows/publish.yml)   
|
56
56
|
|
57
57
|
|
58
58
|
An LLM orchestration frameworks for multi-agent systems with RAG to autopilot outbound workflows.
|
@@ -142,7 +142,7 @@ Multiple `agents` can form a `team` to complete complex tasks together.
|
|
142
142
|
role="demo",
|
143
143
|
goal="amazing project goal",
|
144
144
|
skillsets=["skill_1", "skill_2", ],
|
145
|
-
llm="llm-of-choice"
|
145
|
+
llm="llm-of-your-choice"
|
146
146
|
)
|
147
147
|
|
148
148
|
task = Task(
|
@@ -153,20 +153,19 @@ Multiple `agents` can form a `team` to complete complex tasks together.
|
|
153
153
|
ResponseField(title="test1", type=str, required=True),
|
154
154
|
ResponseField(title="test2", type=list, required=True),
|
155
155
|
],
|
156
|
-
context=["amazing context",],
|
157
|
-
tools=["amazing tool"],
|
158
156
|
callback=None,
|
159
157
|
)
|
160
|
-
|
161
|
-
res = task.execute_sync(agent=agent)
|
162
|
-
|
158
|
+
res = task.execute_sync(agent=agent, context="amazing context to consider.")
|
163
159
|
return res.to_dict()
|
164
|
-
|
165
160
|
```
|
166
161
|
|
167
|
-
|
162
|
+
This will return a dictionary with keys defined in the `ResponseField`.
|
163
|
+
|
164
|
+
```
|
165
|
+
{ test1: "answer1", "test2": ["answer2-1", "answer2-2", "answer2-3",] }
|
166
|
+
```
|
168
167
|
|
169
|
-
[
|
168
|
+
For more info: [PyPI package](https://pypi.org/project/versionhq/)
|
170
169
|
|
171
170
|
<hr />
|
172
171
|
|
@@ -0,0 +1,21 @@
|
|
1
|
+
import os
|
2
|
+
import pytest
|
3
|
+
from versionhq.agent.model import Agent
|
4
|
+
from versionhq.llm.model import LLM
|
5
|
+
from versionhq.clients.workflow.model import Score, ScoreFormat, MessagingWorkflow, MessagingComponent
|
6
|
+
|
7
|
+
MODEL_NAME = os.environ.get("LITELLM_MODEL_NAME", "gpt-3.5-turbo")
|
8
|
+
LITELLM_API_KEY = os.environ.get("LITELLM_API_KEY")
|
9
|
+
|
10
|
+
|
11
|
+
def test_store_scores():
|
12
|
+
"""
|
13
|
+
Test if the final result will be calcurated using a random subject
|
14
|
+
"""
|
15
|
+
|
16
|
+
messaging_component = MessagingComponent(message="demo")
|
17
|
+
score_raw = 15
|
18
|
+
messaging_component.store_scoring_result("demo", score=score_raw)
|
19
|
+
|
20
|
+
assert messaging_component.score is not None
|
21
|
+
assert messaging_component.score.result() is not None
|