versionhq 1.1.7.2__tar.gz → 1.1.7.3__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.3}/PKG-INFO +9 -10
- {versionhq-1.1.7.2 → versionhq-1.1.7.3}/README.md +8 -9
- {versionhq-1.1.7.2 → versionhq-1.1.7.3}/pyproject.toml +1 -1
- {versionhq-1.1.7.2 → versionhq-1.1.7.3}/src/versionhq/__init__.py +5 -2
- {versionhq-1.1.7.2 → versionhq-1.1.7.3}/src/versionhq/agent/model.py +2 -2
- {versionhq-1.1.7.2 → versionhq-1.1.7.3}/src/versionhq/clients/product/model.py +11 -36
- versionhq-1.1.7.3/src/versionhq/clients/workflow/model.py +158 -0
- {versionhq-1.1.7.2 → versionhq-1.1.7.3}/src/versionhq/task/model.py +26 -12
- {versionhq-1.1.7.2 → versionhq-1.1.7.3}/src/versionhq/team/model.py +8 -18
- {versionhq-1.1.7.2 → versionhq-1.1.7.3}/src/versionhq.egg-info/PKG-INFO +9 -10
- {versionhq-1.1.7.2 → versionhq-1.1.7.3}/src/versionhq.egg-info/SOURCES.txt +1 -0
- versionhq-1.1.7.3/tests/clients/workflow_test.py +21 -0
- {versionhq-1.1.7.2 → versionhq-1.1.7.3}/tests/task/task_test.py +97 -7
- {versionhq-1.1.7.2 → versionhq-1.1.7.3}/tests/team/team_test.py +5 -11
- {versionhq-1.1.7.2 → versionhq-1.1.7.3}/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.3}/.github/workflows/publish.yml +0 -0
- {versionhq-1.1.7.2 → versionhq-1.1.7.3}/.github/workflows/publish_testpypi.yml +0 -0
- {versionhq-1.1.7.2 → versionhq-1.1.7.3}/.github/workflows/run_tests.yml +0 -0
- {versionhq-1.1.7.2 → versionhq-1.1.7.3}/.github/workflows/security_check.yml +0 -0
- {versionhq-1.1.7.2 → versionhq-1.1.7.3}/.gitignore +0 -0
- {versionhq-1.1.7.2 → versionhq-1.1.7.3}/.pre-commit-config.yaml +0 -0
- {versionhq-1.1.7.2 → versionhq-1.1.7.3}/.python-version +0 -0
- {versionhq-1.1.7.2 → versionhq-1.1.7.3}/LICENSE +0 -0
- {versionhq-1.1.7.2 → versionhq-1.1.7.3}/SECURITY.md +0 -0
- {versionhq-1.1.7.2 → versionhq-1.1.7.3}/db/preprocess.py +0 -0
- {versionhq-1.1.7.2 → versionhq-1.1.7.3}/requirements.txt +0 -0
- {versionhq-1.1.7.2 → versionhq-1.1.7.3}/runtime.txt +0 -0
- {versionhq-1.1.7.2 → versionhq-1.1.7.3}/setup.cfg +0 -0
- {versionhq-1.1.7.2 → versionhq-1.1.7.3}/src/versionhq/_utils/__init__.py +0 -0
- {versionhq-1.1.7.2 → versionhq-1.1.7.3}/src/versionhq/_utils/cache_handler.py +0 -0
- {versionhq-1.1.7.2 → versionhq-1.1.7.3}/src/versionhq/_utils/i18n.py +0 -0
- {versionhq-1.1.7.2 → versionhq-1.1.7.3}/src/versionhq/_utils/logger.py +0 -0
- {versionhq-1.1.7.2 → versionhq-1.1.7.3}/src/versionhq/_utils/process_config.py +0 -0
- {versionhq-1.1.7.2 → versionhq-1.1.7.3}/src/versionhq/_utils/rpm_controller.py +0 -0
- {versionhq-1.1.7.2 → versionhq-1.1.7.3}/src/versionhq/_utils/usage_metrics.py +0 -0
- {versionhq-1.1.7.2 → versionhq-1.1.7.3}/src/versionhq/agent/TEMPLATES/Backstory.py +0 -0
- {versionhq-1.1.7.2 → versionhq-1.1.7.3}/src/versionhq/agent/TEMPLATES/__init__.py +0 -0
- {versionhq-1.1.7.2 → versionhq-1.1.7.3}/src/versionhq/agent/__init__.py +0 -0
- {versionhq-1.1.7.2 → versionhq-1.1.7.3}/src/versionhq/agent/parser.py +0 -0
- {versionhq-1.1.7.2 → versionhq-1.1.7.3}/src/versionhq/cli/__init__.py +0 -0
- {versionhq-1.1.7.2 → versionhq-1.1.7.3}/src/versionhq/clients/__init__.py +0 -0
- {versionhq-1.1.7.2 → versionhq-1.1.7.3}/src/versionhq/clients/customer/__init__.py +0 -0
- {versionhq-1.1.7.2 → versionhq-1.1.7.3}/src/versionhq/clients/customer/model.py +0 -0
- {versionhq-1.1.7.2 → versionhq-1.1.7.3}/src/versionhq/clients/product/__init__.py +0 -0
- {versionhq-1.1.7.2 → versionhq-1.1.7.3}/src/versionhq/clients/workflow/__init__.py +0 -0
- {versionhq-1.1.7.2 → versionhq-1.1.7.3}/src/versionhq/llm/__init__.py +0 -0
- {versionhq-1.1.7.2 → versionhq-1.1.7.3}/src/versionhq/llm/llm_vars.py +0 -0
- {versionhq-1.1.7.2 → versionhq-1.1.7.3}/src/versionhq/llm/model.py +0 -0
- {versionhq-1.1.7.2 → versionhq-1.1.7.3}/src/versionhq/task/__init__.py +0 -0
- {versionhq-1.1.7.2 → versionhq-1.1.7.3}/src/versionhq/task/formatter.py +0 -0
- {versionhq-1.1.7.2 → versionhq-1.1.7.3}/src/versionhq/team/__init__.py +0 -0
- {versionhq-1.1.7.2 → versionhq-1.1.7.3}/src/versionhq/team/team_planner.py +0 -0
- {versionhq-1.1.7.2 → versionhq-1.1.7.3}/src/versionhq/tool/__init__.py +0 -0
- {versionhq-1.1.7.2 → versionhq-1.1.7.3}/src/versionhq/tool/decorator.py +0 -0
- {versionhq-1.1.7.2 → versionhq-1.1.7.3}/src/versionhq/tool/model.py +0 -0
- {versionhq-1.1.7.2 → versionhq-1.1.7.3}/src/versionhq/tool/tool_handler.py +0 -0
- {versionhq-1.1.7.2 → versionhq-1.1.7.3}/src/versionhq.egg-info/dependency_links.txt +0 -0
- {versionhq-1.1.7.2 → versionhq-1.1.7.3}/src/versionhq.egg-info/requires.txt +0 -0
- {versionhq-1.1.7.2 → versionhq-1.1.7.3}/src/versionhq.egg-info/top_level.txt +0 -0
- {versionhq-1.1.7.2 → versionhq-1.1.7.3}/tests/__init__.py +0 -0
- {versionhq-1.1.7.2 → versionhq-1.1.7.3}/tests/agent/__init__.py +0 -0
- {versionhq-1.1.7.2 → versionhq-1.1.7.3}/tests/agent/agent_test.py +0 -0
- {versionhq-1.1.7.2 → versionhq-1.1.7.3}/tests/cli/__init__.py +0 -0
- {versionhq-1.1.7.2 → versionhq-1.1.7.3}/tests/conftest.py +0 -0
- {versionhq-1.1.7.2 → versionhq-1.1.7.3}/tests/task/__init__.py +0 -0
- {versionhq-1.1.7.2 → versionhq-1.1.7.3}/tests/team/Prompts/Demo_test.py +0 -0
- {versionhq-1.1.7.2 → versionhq-1.1.7.3}/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.3
|
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
|
@@ -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
|
|
@@ -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.3"
|
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.3"
|
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,7 +151,8 @@ 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")
|
@@ -370,7 +371,7 @@ Your outputs MUST adhere to the following format and should NOT include any irre
|
|
370
371
|
|
371
372
|
|
372
373
|
# task execution
|
373
|
-
def execute_sync(self, agent, context: Optional[str] = None) -> TaskOutput:
|
374
|
+
def execute_sync(self, agent, context: Optional[str] = None, callback_kwargs: Dict[str, Any] = None) -> TaskOutput:
|
374
375
|
"""
|
375
376
|
Execute the task synchronously.
|
376
377
|
When the task has context, make sure we have executed all the tasks in the context first.
|
@@ -379,12 +380,12 @@ Your outputs MUST adhere to the following format and should NOT include any irre
|
|
379
380
|
if self.context:
|
380
381
|
for task in self.context:
|
381
382
|
if task.output is None:
|
382
|
-
task._execute_core(agent, context)
|
383
|
+
task._execute_core(agent, context, callback_kwargs)
|
383
384
|
|
384
385
|
return self._execute_core(agent, context)
|
385
386
|
|
386
387
|
|
387
|
-
def execute_async(self, agent, context: Optional[str] = None) -> Future[TaskOutput]:
|
388
|
+
def execute_async(self, agent, context: Optional[str] = None, callback_kwargs: Dict[str, Any] = None) -> Future[TaskOutput]:
|
388
389
|
"""
|
389
390
|
Execute the task asynchronously.
|
390
391
|
"""
|
@@ -393,26 +394,36 @@ Your outputs MUST adhere to the following format and should NOT include any irre
|
|
393
394
|
threading.Thread(
|
394
395
|
daemon=True,
|
395
396
|
target=self._execute_task_async,
|
396
|
-
args=(agent, context, future),
|
397
|
+
args=(agent, context, callback_kwargs, future),
|
397
398
|
).start()
|
398
399
|
return future
|
399
400
|
|
400
401
|
|
401
|
-
def _execute_task_async(self, agent, context: Optional[str], future: Future[TaskOutput]) -> None:
|
402
|
-
"""
|
403
|
-
|
402
|
+
def _execute_task_async(self, agent, context: Optional[str], callback_kwargs: Dict[str, Any], future: Future[TaskOutput]) -> None:
|
403
|
+
"""
|
404
|
+
Execute the task asynchronously with context handling.
|
405
|
+
"""
|
406
|
+
|
407
|
+
result = self._execute_core(agent, context, callback_kwargs)
|
404
408
|
future.set_result(result)
|
405
409
|
|
406
410
|
|
407
|
-
def _execute_core(self, agent, context: Optional[str]) -> TaskOutput:
|
411
|
+
def _execute_core(self, agent, context: Optional[str], callback_kwargs: Optional[Dict[str, Any]] = None) -> TaskOutput:
|
408
412
|
"""
|
409
413
|
Run the core execution logic of the task.
|
414
|
+
To speed up the process, when the format is not expected to return, we will skip the conversion process.
|
410
415
|
"""
|
416
|
+
from versionhq.agent.model import Agent
|
411
417
|
|
412
418
|
self.prompt_context = context
|
419
|
+
|
420
|
+
if self.allow_delegation:
|
421
|
+
agent = Agent(role="delegated_agent", goal=agent.goal, llm=agent.llm) #! REFINEME - logic to pick up the high performer
|
422
|
+
self.delegations += 1
|
423
|
+
|
413
424
|
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)
|
425
|
+
output_json_dict = self.create_json_output(raw_result=output_raw) if self.expected_output_json is True else None
|
426
|
+
output_pydantic = self.create_pydantic_output(output_json_dict=output_json_dict) if self.expected_output_pydantic else None
|
416
427
|
task_output = TaskOutput(
|
417
428
|
task_id=self.id,
|
418
429
|
raw=output_raw,
|
@@ -425,7 +436,10 @@ Your outputs MUST adhere to the following format and should NOT include any irre
|
|
425
436
|
# self._set_end_execution_time(start_time)
|
426
437
|
|
427
438
|
if self.callback:
|
428
|
-
self.callback
|
439
|
+
if isinstance(self.callback, Callable):
|
440
|
+
self.callback(**callback_kwargs)
|
441
|
+
else:
|
442
|
+
self.callback(self.output)
|
429
443
|
|
430
444
|
# if self._execution_span:
|
431
445
|
# # 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
|
|
@@ -301,10 +290,8 @@ class Team(BaseModel):
|
|
301
290
|
|
302
291
|
# task execution
|
303
292
|
def _process_async_tasks(
|
304
|
-
|
305
|
-
|
306
|
-
was_replayed: bool = False,
|
307
|
-
) -> List[TaskOutput]:
|
293
|
+
self, futures: List[Tuple[Task, Future[TaskOutput], int]], was_replayed: bool = False
|
294
|
+
) -> List[TaskOutput]:
|
308
295
|
task_outputs: List[TaskOutput] = []
|
309
296
|
for future_task, future, task_index in futures:
|
310
297
|
task_output = future.result()
|
@@ -315,6 +302,7 @@ class Team(BaseModel):
|
|
315
302
|
)
|
316
303
|
return task_outputs
|
317
304
|
|
305
|
+
|
318
306
|
def _handle_conditional_task(
|
319
307
|
self,
|
320
308
|
task: ConditionalTask,
|
@@ -323,6 +311,7 @@ class Team(BaseModel):
|
|
323
311
|
task_index: int,
|
324
312
|
was_replayed: bool,
|
325
313
|
) -> Optional[TaskOutput]:
|
314
|
+
|
326
315
|
if futures:
|
327
316
|
task_outputs = self._process_async_tasks(futures, was_replayed)
|
328
317
|
futures.clear()
|
@@ -347,6 +336,7 @@ class Team(BaseModel):
|
|
347
336
|
Take the output of the first task or the lead task output as the team output `raw` value.
|
348
337
|
Note that `tasks` are already sorted by the importance.
|
349
338
|
"""
|
339
|
+
|
350
340
|
if len(task_outputs) < 1:
|
351
341
|
raise ValueError("Something went wrong. Kickoff should return only one task output.")
|
352
342
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: versionhq
|
3
|
-
Version: 1.1.7.
|
3
|
+
Version: 1.1.7.3
|
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
|
@@ -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
|
@@ -1,5 +1,6 @@
|
|
1
1
|
import os
|
2
2
|
import pytest
|
3
|
+
from unittest.mock import patch
|
3
4
|
from typing import Union
|
4
5
|
from versionhq.agent.model import Agent
|
5
6
|
from versionhq.task.model import Task, ResponseField, TaskOutput, AgentOutput
|
@@ -10,7 +11,7 @@ LITELLM_API_KEY = os.environ.get("LITELLM_API_KEY")
|
|
10
11
|
|
11
12
|
def test_sync_execute_task():
|
12
13
|
agent = Agent(
|
13
|
-
role="demo agent
|
14
|
+
role="demo agent 1",
|
14
15
|
goal="My amazing goals",
|
15
16
|
backstory="My amazing backstory",
|
16
17
|
verbose=True,
|
@@ -51,13 +52,36 @@ def test_sync_execute_task():
|
|
51
52
|
assert type(res.pydantic.test2) == list
|
52
53
|
|
53
54
|
|
55
|
+
def test_async_execute_task():
|
56
|
+
agent = Agent(
|
57
|
+
role="demo agent 2",
|
58
|
+
goal="My amazing goals",
|
59
|
+
)
|
60
|
+
|
61
|
+
task = Task(
|
62
|
+
description="Analyze the client's business model and define the optimal cohort timeframe.",
|
63
|
+
expected_output_json=True,
|
64
|
+
expected_output_pydantic=False,
|
65
|
+
output_field_list=[
|
66
|
+
ResponseField(title="test1", type=str, required=True),
|
67
|
+
ResponseField(title="test2", type=list, required=True),
|
68
|
+
],
|
69
|
+
)
|
70
|
+
|
71
|
+
with patch.object(Agent, "execute_task", return_value="ok") as execute:
|
72
|
+
execution = task.execute_async(agent=agent)
|
73
|
+
result = execution.result()
|
74
|
+
assert result.raw == "ok"
|
75
|
+
execute.assert_called_once_with(task=task, context=None)
|
76
|
+
|
77
|
+
|
54
78
|
def test_sync_execute_task_with_task_context():
|
55
79
|
"""
|
56
80
|
Use case = One agent handling multiple tasks sequentially using context set in the main task.
|
57
81
|
"""
|
58
82
|
|
59
83
|
agent = Agent(
|
60
|
-
role="demo agent
|
84
|
+
role="demo agent 3",
|
61
85
|
goal="My amazing goals",
|
62
86
|
backstory="My amazing backstory",
|
63
87
|
verbose=True,
|
@@ -110,8 +134,14 @@ def test_sync_execute_task_with_task_context():
|
|
110
134
|
|
111
135
|
|
112
136
|
def test_sync_execute_task_with_prompt_context():
|
137
|
+
"""
|
138
|
+
Use case:
|
139
|
+
- One agent handling multiple tasks sequentially using context set in the main task.
|
140
|
+
- On top of that, the agent receives context when they execute the task.
|
141
|
+
"""
|
142
|
+
|
113
143
|
agent = Agent(
|
114
|
-
role="demo agent
|
144
|
+
role="demo agent 4",
|
115
145
|
goal="My amazing goals",
|
116
146
|
backstory="My amazing backstory",
|
117
147
|
verbose=True,
|
@@ -137,7 +167,7 @@ def test_sync_execute_task_with_prompt_context():
|
|
137
167
|
],
|
138
168
|
context=[sub_task]
|
139
169
|
)
|
140
|
-
res = main_task.execute_sync(agent=agent, context="
|
170
|
+
res = main_task.execute_sync(agent=agent, context="plan a Black Friday campaign.")
|
141
171
|
|
142
172
|
assert isinstance(res, TaskOutput)
|
143
173
|
assert res.task_id is main_task.id
|
@@ -160,9 +190,69 @@ def test_sync_execute_task_with_prompt_context():
|
|
160
190
|
|
161
191
|
assert sub_task.output is not None
|
162
192
|
assert sub_task.output.json_dict is not None
|
193
|
+
assert sub_task.output.pydantic is None
|
194
|
+
|
163
195
|
assert "result" in main_task.prompt()
|
164
|
-
assert main_task.prompt_context == "
|
165
|
-
assert "
|
196
|
+
assert main_task.prompt_context == "plan a Black Friday campaign."
|
197
|
+
assert "plan a Black Friday campaign." in main_task.prompt()
|
198
|
+
|
199
|
+
|
200
|
+
def test_callback():
|
201
|
+
"""
|
202
|
+
See if the callback function is executed well with kwargs.
|
203
|
+
"""
|
204
|
+
|
205
|
+
def callback_func(item: str = None):
|
206
|
+
return f"I am callback with {item}."
|
207
|
+
|
208
|
+
agent = Agent(
|
209
|
+
role="demo agent 5",
|
210
|
+
goal="My amazing goals",
|
211
|
+
)
|
212
|
+
|
213
|
+
task = Task(
|
214
|
+
description="Analyze the client's business model and define the optimal cohort timeframe.",
|
215
|
+
expected_output_json=True,
|
216
|
+
expected_output_pydantic=False,
|
217
|
+
output_field_list=[
|
218
|
+
ResponseField(title="test1", type=str, required=True),
|
219
|
+
],
|
220
|
+
callback=callback_func
|
221
|
+
)
|
222
|
+
|
223
|
+
with patch.object(Agent, "execute_task", return_value="ok") as execute:
|
224
|
+
execution = task.execute_async(agent=agent, callback_kwargs={"item": "demo for pytest"})
|
225
|
+
result = execution.result()
|
226
|
+
assert result.raw == "ok"
|
227
|
+
execute.assert_called_once_with(task=task, context=None)
|
228
|
+
|
229
|
+
|
230
|
+
|
231
|
+
def test_delegate():
|
232
|
+
agent = Agent(
|
233
|
+
role="demo agent 6",
|
234
|
+
goal="My amazing goals",
|
235
|
+
)
|
236
|
+
|
237
|
+
task = Task(
|
238
|
+
description="Analyze the client's business model and define the optimal cohort timeframe.",
|
239
|
+
expected_output_json=True,
|
240
|
+
expected_output_pydantic=False,
|
241
|
+
output_field_list=[
|
242
|
+
ResponseField(title="test1", type=str, required=True),
|
243
|
+
],
|
244
|
+
allow_delegation=True
|
245
|
+
)
|
246
|
+
|
247
|
+
task.execute_sync(agent=agent)
|
248
|
+
|
249
|
+
assert task.output is not None
|
250
|
+
assert "delegated_agent" in task.processed_by_agents
|
251
|
+
assert task.delegations != 0
|
252
|
+
|
253
|
+
|
254
|
+
# def test_conditional_task():
|
255
|
+
|
166
256
|
|
167
257
|
|
168
|
-
#
|
258
|
+
# tools, CONDITIONAL, token usage
|
@@ -36,7 +36,6 @@ def test_form_team():
|
|
36
36
|
],
|
37
37
|
expected_output_pydantic=True,
|
38
38
|
context=[],
|
39
|
-
tools=[],
|
40
39
|
callback=None,
|
41
40
|
)
|
42
41
|
|
@@ -49,7 +48,6 @@ def test_form_team():
|
|
49
48
|
ResponseField(title="test2", type=list, required=True),
|
50
49
|
],
|
51
50
|
context=[],
|
52
|
-
tools=[],
|
53
51
|
callback=None,
|
54
52
|
)
|
55
53
|
|
@@ -97,7 +95,6 @@ def test_form_team_without_leader():
|
|
97
95
|
],
|
98
96
|
expected_output_pydantic=True,
|
99
97
|
context=[],
|
100
|
-
tools=[],
|
101
98
|
callback=None,
|
102
99
|
)
|
103
100
|
|
@@ -110,7 +107,6 @@ def test_form_team_without_leader():
|
|
110
107
|
ResponseField(title="test2", type=list, required=True),
|
111
108
|
],
|
112
109
|
context=[],
|
113
|
-
tools=[],
|
114
110
|
callback=None,
|
115
111
|
)
|
116
112
|
|
@@ -158,7 +154,6 @@ def test_kickoff_team_without_leader():
|
|
158
154
|
],
|
159
155
|
expected_output_pydantic=True,
|
160
156
|
context=[],
|
161
|
-
tools=[],
|
162
157
|
callback=None,
|
163
158
|
)
|
164
159
|
|
@@ -171,7 +166,6 @@ def test_kickoff_team_without_leader():
|
|
171
166
|
ResponseField(title="test2", type=list, required=True),
|
172
167
|
],
|
173
168
|
context=[],
|
174
|
-
tools=[],
|
175
169
|
callback=None,
|
176
170
|
)
|
177
171
|
|
@@ -198,11 +192,11 @@ def test_kickoff_team_without_leader():
|
|
198
192
|
assert len(res_all) == 2
|
199
193
|
for item in res_all:
|
200
194
|
assert isinstance(item, dict)
|
201
|
-
if not hasattr(item, "output")
|
202
|
-
|
203
|
-
|
204
|
-
else:
|
205
|
-
|
195
|
+
# if not hasattr(item, "output") and not hasattr(res_all, "output"):
|
196
|
+
# assert "test1" in item
|
197
|
+
# assert "test2" in item
|
198
|
+
# else:
|
199
|
+
# assert "output" in item
|
206
200
|
|
207
201
|
assert isinstance(res.token_usage, UsageMetrics)
|
208
202
|
assert res.token_usage.total_tokens == 0 # as we dont set token usage on agent
|
@@ -529,11 +529,11 @@ wheels = [
|
|
529
529
|
|
530
530
|
[[package]]
|
531
531
|
name = "identify"
|
532
|
-
version = "2.6.
|
532
|
+
version = "2.6.4"
|
533
533
|
source = { registry = "https://pypi.org/simple" }
|
534
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
534
|
+
sdist = { url = "https://files.pythonhosted.org/packages/49/a5/7de3053524ee006b91099968d7ecb2e0b420f7ae728094394c33e8a2a2b9/identify-2.6.4.tar.gz", hash = "sha256:285a7d27e397652e8cafe537a6cc97dd470a970f48fb2e9d979aa38eae5513ac", size = 99209 }
|
535
535
|
wheels = [
|
536
|
-
{ url = "https://files.pythonhosted.org/packages/
|
536
|
+
{ url = "https://files.pythonhosted.org/packages/a2/9d/52f036403ae86474804f699c0d084b4b071e333a390b20269bb8accc65e0/identify-2.6.4-py2.py3-none-any.whl", hash = "sha256:993b0f01b97e0568c179bb9196391ff391bfb88a99099dbf5ce392b68f42d0af", size = 99072 },
|
537
537
|
]
|
538
538
|
|
539
539
|
[[package]]
|
@@ -666,11 +666,11 @@ wheels = [
|
|
666
666
|
|
667
667
|
[[package]]
|
668
668
|
name = "json-repair"
|
669
|
-
version = "0.
|
669
|
+
version = "0.34.0"
|
670
670
|
source = { registry = "https://pypi.org/simple" }
|
671
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
671
|
+
sdist = { url = "https://files.pythonhosted.org/packages/2b/2b/82a1f1430b723cb5585679f66338e5e5fdfe4a7dade1fe934929e9531d6c/json_repair-0.34.0.tar.gz", hash = "sha256:401d454e039e24425659cfb41e1a7a3800123abfb0d81653282585dc289862cb", size = 28872 }
|
672
672
|
wheels = [
|
673
|
-
{ url = "https://files.pythonhosted.org/packages/
|
673
|
+
{ url = "https://files.pythonhosted.org/packages/20/8b/dd78678b6135956c3f73c4e8de8e20348c3c702d8906b11420a7c7f96ad2/json_repair-0.34.0-py3-none-any.whl", hash = "sha256:a0bb0d3993838b320adf6c82c11c92419d3df794901689bffb3abed208472adf", size = 19739 },
|
674
674
|
]
|
675
675
|
|
676
676
|
[[package]]
|
@@ -711,7 +711,7 @@ wheels = [
|
|
711
711
|
|
712
712
|
[[package]]
|
713
713
|
name = "keyring"
|
714
|
-
version = "25.
|
714
|
+
version = "25.6.0"
|
715
715
|
source = { registry = "https://pypi.org/simple" }
|
716
716
|
dependencies = [
|
717
717
|
{ name = "jaraco-classes" },
|
@@ -721,14 +721,14 @@ dependencies = [
|
|
721
721
|
{ name = "pywin32-ctypes", marker = "sys_platform == 'win32'" },
|
722
722
|
{ name = "secretstorage", marker = "sys_platform == 'linux'" },
|
723
723
|
]
|
724
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
724
|
+
sdist = { url = "https://files.pythonhosted.org/packages/70/09/d904a6e96f76ff214be59e7aa6ef7190008f52a0ab6689760a98de0bf37d/keyring-25.6.0.tar.gz", hash = "sha256:0b39998aa941431eb3d9b0d4b2460bc773b9df6fed7621c2dfb291a7e0187a66", size = 62750 }
|
725
725
|
wheels = [
|
726
|
-
{ url = "https://files.pythonhosted.org/packages/32/
|
726
|
+
{ url = "https://files.pythonhosted.org/packages/d3/32/da7f44bcb1105d3e88a0b74ebdca50c59121d2ddf71c9e34ba47df7f3a56/keyring-25.6.0-py3-none-any.whl", hash = "sha256:552a3f7af126ece7ed5c89753650eec89c7eaae8617d0aa4d9ad2b75111266bd", size = 39085 },
|
727
727
|
]
|
728
728
|
|
729
729
|
[[package]]
|
730
730
|
name = "litellm"
|
731
|
-
version = "1.
|
731
|
+
version = "1.56.4"
|
732
732
|
source = { registry = "https://pypi.org/simple" }
|
733
733
|
dependencies = [
|
734
734
|
{ name = "aiohttp" },
|
@@ -743,9 +743,9 @@ dependencies = [
|
|
743
743
|
{ name = "tiktoken" },
|
744
744
|
{ name = "tokenizers" },
|
745
745
|
]
|
746
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
746
|
+
sdist = { url = "https://files.pythonhosted.org/packages/83/ea/2c51d16c244a64dd3f0bdb1757aef798cf943b92e5695da04e3e42ba09e0/litellm-1.56.4.tar.gz", hash = "sha256:2808ca21878d200f7676a3d11e5bf2b5e3349ae504628f279cd7297c7dbd2038", size = 6284983 }
|
747
747
|
wheels = [
|
748
|
-
{ url = "https://files.pythonhosted.org/packages/
|
748
|
+
{ url = "https://files.pythonhosted.org/packages/8f/25/2fd7b28a270b2963e8fa0ecf6aab4db47c54d932cc5aac8bc87e7ebc3755/litellm-1.56.4-py3-none-any.whl", hash = "sha256:699a8db46f7de045069a77c435e13244b5fdaf5df1c8cb5e6ad675ef7e104ccd", size = 6564370 },
|
749
749
|
]
|
750
750
|
|
751
751
|
[[package]]
|
@@ -1647,7 +1647,7 @@ wheels = [
|
|
1647
1647
|
|
1648
1648
|
[[package]]
|
1649
1649
|
name = "versionhq"
|
1650
|
-
version = "1.1.7.
|
1650
|
+
version = "1.1.7.3"
|
1651
1651
|
source = { editable = "." }
|
1652
1652
|
dependencies = [
|
1653
1653
|
{ name = "composio" },
|
@@ -1,174 +0,0 @@
|
|
1
|
-
import uuid
|
2
|
-
from abc import ABC
|
3
|
-
from datetime import date, datetime, time, timedelta
|
4
|
-
from typing import (
|
5
|
-
Any,
|
6
|
-
Dict,
|
7
|
-
List,
|
8
|
-
Union,
|
9
|
-
Callable,
|
10
|
-
Type,
|
11
|
-
Optional,
|
12
|
-
get_args,
|
13
|
-
get_origin,
|
14
|
-
)
|
15
|
-
from pydantic import (
|
16
|
-
UUID4,
|
17
|
-
InstanceOf,
|
18
|
-
BaseModel,
|
19
|
-
ConfigDict,
|
20
|
-
Field,
|
21
|
-
create_model,
|
22
|
-
field_validator,
|
23
|
-
model_validator,
|
24
|
-
)
|
25
|
-
from pydantic_core import PydanticCustomError
|
26
|
-
|
27
|
-
from versionhq.clients.product.model import Product
|
28
|
-
from versionhq.clients.customer.model import Customer
|
29
|
-
from versionhq.agent.model import Agent
|
30
|
-
from versionhq.team.model import Team
|
31
|
-
|
32
|
-
|
33
|
-
class ScoreFormat:
|
34
|
-
def __init__(self, rate: float, weight: int = 1):
|
35
|
-
self.rate = rate
|
36
|
-
self.weight = weight
|
37
|
-
self.aggregate = rate * weight
|
38
|
-
|
39
|
-
|
40
|
-
class Score:
|
41
|
-
"""
|
42
|
-
Evaluate the score on 0 (no performance) to 1 scale.
|
43
|
-
`rate`: Any float from 0.0 to 1.0 given by an agent.
|
44
|
-
`weight`: Importance of each factor to the aggregated score.
|
45
|
-
"""
|
46
|
-
|
47
|
-
def __init__(
|
48
|
-
self,
|
49
|
-
brand_tone: ScoreFormat,
|
50
|
-
audience: ScoreFormat,
|
51
|
-
track_record: ScoreFormat,
|
52
|
-
*args: List[ScoreFormat],
|
53
|
-
):
|
54
|
-
self.brand_tone = brand_tone
|
55
|
-
self.audience = audience
|
56
|
-
self.track_record = track_record
|
57
|
-
self.args = args
|
58
|
-
|
59
|
-
def result(self):
|
60
|
-
aggregated_score = sum(
|
61
|
-
self.brand_tone.aggregate,
|
62
|
-
self.audience.aggregate,
|
63
|
-
self.track_record.aggrigate,
|
64
|
-
)
|
65
|
-
denominator = sum(
|
66
|
-
self.brand_tone.weight, self.audience.weight, self.track_record.weight
|
67
|
-
)
|
68
|
-
try:
|
69
|
-
if self.args:
|
70
|
-
for item in self.args:
|
71
|
-
if isinstance(item, ScoreFormat):
|
72
|
-
aggregate_score += item.rate * item.weight
|
73
|
-
denominator += item.weight
|
74
|
-
except:
|
75
|
-
pass
|
76
|
-
return round(aggregated_score / denominator, 2)
|
77
|
-
|
78
|
-
|
79
|
-
class MessagingComponent(ABC, BaseModel):
|
80
|
-
layer_id: int = Field(default=0, description="add id of the layer: 0, 1, 2")
|
81
|
-
message: str = Field(
|
82
|
-
default=None, max_length=1024, description="text message content to be sent"
|
83
|
-
)
|
84
|
-
interval: Optional[str] = Field(
|
85
|
-
default=None,
|
86
|
-
description="interval to move on to the next layer. if this is the last layer, set as `None`",
|
87
|
-
)
|
88
|
-
score: Union[float, InstanceOf[Score]] = Field(default=None)
|
89
|
-
|
90
|
-
|
91
|
-
class MessagingWorkflow(ABC, BaseModel):
|
92
|
-
"""
|
93
|
-
Store 3 layers of messaging workflow sent to `customer` on the `product`
|
94
|
-
"""
|
95
|
-
|
96
|
-
_created_at: Optional[datetime]
|
97
|
-
_updated_at: Optional[datetime]
|
98
|
-
|
99
|
-
model_config = ConfigDict()
|
100
|
-
|
101
|
-
id: UUID4 = Field(default_factory=uuid.uuid4, frozen=True)
|
102
|
-
comps: List[MessagingComponent] = Field(
|
103
|
-
default=list, description="store at least 3 messaging components"
|
104
|
-
)
|
105
|
-
|
106
|
-
# responsible tean or agents
|
107
|
-
team: Optional[Team] = Field(
|
108
|
-
default=None,
|
109
|
-
description="store `Team` instance responsibile for autopiloting this workflow",
|
110
|
-
)
|
111
|
-
agents: Optional[List[Agent]] = Field(
|
112
|
-
default=None,
|
113
|
-
description="store `Agent` instances responsible for autopiloting this workflow. if the team exsits, this field remains as `None`",
|
114
|
-
)
|
115
|
-
|
116
|
-
# metrics
|
117
|
-
destination: Optional[str] = Field(
|
118
|
-
default=None, description="destination service to launch this workflow"
|
119
|
-
)
|
120
|
-
product: InstanceOf[Product] = Field(default=None)
|
121
|
-
customer: InstanceOf[Customer] = Field(default=None)
|
122
|
-
|
123
|
-
metrics: Union[List[Dict[str, Any]], List[str]] = Field(
|
124
|
-
default=None,
|
125
|
-
max_length=256,
|
126
|
-
description="store metrics that used to predict and track the performance of this workflow.",
|
127
|
-
)
|
128
|
-
|
129
|
-
@property
|
130
|
-
def name(self):
|
131
|
-
if self.customer.id:
|
132
|
-
return (
|
133
|
-
f"Workflow ID: {self.id} - on {self.product.id} for {self.customer.id}"
|
134
|
-
)
|
135
|
-
else:
|
136
|
-
return f"Workflow ID: {self.id} - on {self.product.id}"
|
137
|
-
|
138
|
-
@field_validator("id", mode="before")
|
139
|
-
@classmethod
|
140
|
-
def _deny_user_set_id(cls, v: Optional[UUID4]) -> None:
|
141
|
-
if v:
|
142
|
-
raise PydanticCustomError(
|
143
|
-
"may_not_set_field", "This field is not to be set by the user.", {}
|
144
|
-
)
|
145
|
-
|
146
|
-
@model_validator(mode="after")
|
147
|
-
def set_up_destination(self):
|
148
|
-
"""
|
149
|
-
Set up the destination service when self.destination is None.
|
150
|
-
Prioritize customer's destination to the product provider's destination list.
|
151
|
-
"""
|
152
|
-
if self.destination is None:
|
153
|
-
if self.customer is not None:
|
154
|
-
self.destination = self.customer.on
|
155
|
-
|
156
|
-
else:
|
157
|
-
destination_list = self.product.provider.destinations
|
158
|
-
if destination_list:
|
159
|
-
self.destination = destination_list[0]
|
160
|
-
return self
|
161
|
-
|
162
|
-
def reassign_agent_or_team(
|
163
|
-
self, agents: List[Agent] = None, team: Team = None
|
164
|
-
) -> None:
|
165
|
-
"""
|
166
|
-
Fire unresponsible agents/team and assign new one.
|
167
|
-
"""
|
168
|
-
|
169
|
-
if not agents and not team:
|
170
|
-
raise ValueError("Need to add at least 1 agent or team.")
|
171
|
-
|
172
|
-
self.agents = agents
|
173
|
-
self.team = team
|
174
|
-
self.updated_at = datetime.datetime.now()
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|