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.
Files changed (68) hide show
  1. {versionhq-1.1.7.2 → versionhq-1.1.7.4}/PKG-INFO +10 -11
  2. {versionhq-1.1.7.2 → versionhq-1.1.7.4}/README.md +9 -10
  3. {versionhq-1.1.7.2 → versionhq-1.1.7.4}/pyproject.toml +1 -1
  4. {versionhq-1.1.7.2 → versionhq-1.1.7.4}/src/versionhq/__init__.py +5 -2
  5. {versionhq-1.1.7.2 → versionhq-1.1.7.4}/src/versionhq/agent/model.py +2 -2
  6. {versionhq-1.1.7.2 → versionhq-1.1.7.4}/src/versionhq/clients/product/model.py +11 -36
  7. versionhq-1.1.7.4/src/versionhq/clients/workflow/model.py +158 -0
  8. {versionhq-1.1.7.2 → versionhq-1.1.7.4}/src/versionhq/task/model.py +34 -5
  9. {versionhq-1.1.7.2 → versionhq-1.1.7.4}/src/versionhq/team/model.py +12 -26
  10. {versionhq-1.1.7.2 → versionhq-1.1.7.4}/src/versionhq.egg-info/PKG-INFO +10 -11
  11. {versionhq-1.1.7.2 → versionhq-1.1.7.4}/src/versionhq.egg-info/SOURCES.txt +1 -0
  12. versionhq-1.1.7.4/tests/clients/workflow_test.py +21 -0
  13. {versionhq-1.1.7.2 → versionhq-1.1.7.4}/tests/task/task_test.py +98 -7
  14. {versionhq-1.1.7.2 → versionhq-1.1.7.4}/tests/team/team_test.py +94 -12
  15. {versionhq-1.1.7.2 → versionhq-1.1.7.4}/uv.lock +13 -13
  16. versionhq-1.1.7.2/src/versionhq/clients/workflow/model.py +0 -174
  17. {versionhq-1.1.7.2 → versionhq-1.1.7.4}/.github/workflows/publish.yml +0 -0
  18. {versionhq-1.1.7.2 → versionhq-1.1.7.4}/.github/workflows/publish_testpypi.yml +0 -0
  19. {versionhq-1.1.7.2 → versionhq-1.1.7.4}/.github/workflows/run_tests.yml +0 -0
  20. {versionhq-1.1.7.2 → versionhq-1.1.7.4}/.github/workflows/security_check.yml +0 -0
  21. {versionhq-1.1.7.2 → versionhq-1.1.7.4}/.gitignore +0 -0
  22. {versionhq-1.1.7.2 → versionhq-1.1.7.4}/.pre-commit-config.yaml +0 -0
  23. {versionhq-1.1.7.2 → versionhq-1.1.7.4}/.python-version +0 -0
  24. {versionhq-1.1.7.2 → versionhq-1.1.7.4}/LICENSE +0 -0
  25. {versionhq-1.1.7.2 → versionhq-1.1.7.4}/SECURITY.md +0 -0
  26. {versionhq-1.1.7.2 → versionhq-1.1.7.4}/db/preprocess.py +0 -0
  27. {versionhq-1.1.7.2 → versionhq-1.1.7.4}/requirements.txt +0 -0
  28. {versionhq-1.1.7.2 → versionhq-1.1.7.4}/runtime.txt +0 -0
  29. {versionhq-1.1.7.2 → versionhq-1.1.7.4}/setup.cfg +0 -0
  30. {versionhq-1.1.7.2 → versionhq-1.1.7.4}/src/versionhq/_utils/__init__.py +0 -0
  31. {versionhq-1.1.7.2 → versionhq-1.1.7.4}/src/versionhq/_utils/cache_handler.py +0 -0
  32. {versionhq-1.1.7.2 → versionhq-1.1.7.4}/src/versionhq/_utils/i18n.py +0 -0
  33. {versionhq-1.1.7.2 → versionhq-1.1.7.4}/src/versionhq/_utils/logger.py +0 -0
  34. {versionhq-1.1.7.2 → versionhq-1.1.7.4}/src/versionhq/_utils/process_config.py +0 -0
  35. {versionhq-1.1.7.2 → versionhq-1.1.7.4}/src/versionhq/_utils/rpm_controller.py +0 -0
  36. {versionhq-1.1.7.2 → versionhq-1.1.7.4}/src/versionhq/_utils/usage_metrics.py +0 -0
  37. {versionhq-1.1.7.2 → versionhq-1.1.7.4}/src/versionhq/agent/TEMPLATES/Backstory.py +0 -0
  38. {versionhq-1.1.7.2 → versionhq-1.1.7.4}/src/versionhq/agent/TEMPLATES/__init__.py +0 -0
  39. {versionhq-1.1.7.2 → versionhq-1.1.7.4}/src/versionhq/agent/__init__.py +0 -0
  40. {versionhq-1.1.7.2 → versionhq-1.1.7.4}/src/versionhq/agent/parser.py +0 -0
  41. {versionhq-1.1.7.2 → versionhq-1.1.7.4}/src/versionhq/cli/__init__.py +0 -0
  42. {versionhq-1.1.7.2 → versionhq-1.1.7.4}/src/versionhq/clients/__init__.py +0 -0
  43. {versionhq-1.1.7.2 → versionhq-1.1.7.4}/src/versionhq/clients/customer/__init__.py +0 -0
  44. {versionhq-1.1.7.2 → versionhq-1.1.7.4}/src/versionhq/clients/customer/model.py +0 -0
  45. {versionhq-1.1.7.2 → versionhq-1.1.7.4}/src/versionhq/clients/product/__init__.py +0 -0
  46. {versionhq-1.1.7.2 → versionhq-1.1.7.4}/src/versionhq/clients/workflow/__init__.py +0 -0
  47. {versionhq-1.1.7.2 → versionhq-1.1.7.4}/src/versionhq/llm/__init__.py +0 -0
  48. {versionhq-1.1.7.2 → versionhq-1.1.7.4}/src/versionhq/llm/llm_vars.py +0 -0
  49. {versionhq-1.1.7.2 → versionhq-1.1.7.4}/src/versionhq/llm/model.py +0 -0
  50. {versionhq-1.1.7.2 → versionhq-1.1.7.4}/src/versionhq/task/__init__.py +0 -0
  51. {versionhq-1.1.7.2 → versionhq-1.1.7.4}/src/versionhq/task/formatter.py +0 -0
  52. {versionhq-1.1.7.2 → versionhq-1.1.7.4}/src/versionhq/team/__init__.py +0 -0
  53. {versionhq-1.1.7.2 → versionhq-1.1.7.4}/src/versionhq/team/team_planner.py +0 -0
  54. {versionhq-1.1.7.2 → versionhq-1.1.7.4}/src/versionhq/tool/__init__.py +0 -0
  55. {versionhq-1.1.7.2 → versionhq-1.1.7.4}/src/versionhq/tool/decorator.py +0 -0
  56. {versionhq-1.1.7.2 → versionhq-1.1.7.4}/src/versionhq/tool/model.py +0 -0
  57. {versionhq-1.1.7.2 → versionhq-1.1.7.4}/src/versionhq/tool/tool_handler.py +0 -0
  58. {versionhq-1.1.7.2 → versionhq-1.1.7.4}/src/versionhq.egg-info/dependency_links.txt +0 -0
  59. {versionhq-1.1.7.2 → versionhq-1.1.7.4}/src/versionhq.egg-info/requires.txt +0 -0
  60. {versionhq-1.1.7.2 → versionhq-1.1.7.4}/src/versionhq.egg-info/top_level.txt +0 -0
  61. {versionhq-1.1.7.2 → versionhq-1.1.7.4}/tests/__init__.py +0 -0
  62. {versionhq-1.1.7.2 → versionhq-1.1.7.4}/tests/agent/__init__.py +0 -0
  63. {versionhq-1.1.7.2 → versionhq-1.1.7.4}/tests/agent/agent_test.py +0 -0
  64. {versionhq-1.1.7.2 → versionhq-1.1.7.4}/tests/cli/__init__.py +0 -0
  65. {versionhq-1.1.7.2 → versionhq-1.1.7.4}/tests/conftest.py +0 -0
  66. {versionhq-1.1.7.2 → versionhq-1.1.7.4}/tests/task/__init__.py +0 -0
  67. {versionhq-1.1.7.2 → versionhq-1.1.7.4}/tests/team/Prompts/Demo_test.py +0 -0
  68. {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.2
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
- ![MIT license](https://img.shields.io/badge/License-MIT-green) [![Publisher](https://github.com/versionHQ/multi-agent-system/actions/workflows/publish.yml/badge.svg)](https://github.com/versionHQ/multi-agent-system/actions/workflows/publish.yml) ![PyPi](https://img.shields.io/badge/pypi-v1.1.7.0-blue) ![python ver](https://img.shields.io/badge/Python-3.12/3.13-purple) ![pyenv ver](https://img.shields.io/badge/pyenv-2.4.23-orange)
55
+ ![MIT license](https://img.shields.io/badge/License-MIT-green) [![Publisher](https://github.com/versionHQ/multi-agent-system/actions/workflows/publish.yml/badge.svg)](https://github.com/versionHQ/multi-agent-system/actions/workflows/publish.yml) ![PyPI](https://img.shields.io/badge/PyPI-v1.1.7.3-blue) ![python ver](https://img.shields.io/badge/Python-3.12/3.13-purple) ![pyenv ver](https://img.shields.io/badge/pyenv-2.4.23-orange)
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
- For more details:
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
- [PyPi package](https://pypi.org/project/versionhq/)
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
- ![MIT license](https://img.shields.io/badge/License-MIT-green) [![Publisher](https://github.com/versionHQ/multi-agent-system/actions/workflows/publish.yml/badge.svg)](https://github.com/versionHQ/multi-agent-system/actions/workflows/publish.yml) ![PyPi](https://img.shields.io/badge/pypi-v1.1.7.0-blue) ![python ver](https://img.shields.io/badge/Python-3.12/3.13-purple) ![pyenv ver](https://img.shields.io/badge/pyenv-2.4.23-orange)
3
+ ![MIT license](https://img.shields.io/badge/License-MIT-green) [![Publisher](https://github.com/versionHQ/multi-agent-system/actions/workflows/publish.yml/badge.svg)](https://github.com/versionHQ/multi-agent-system/actions/workflows/publish.yml) ![PyPI](https://img.shields.io/badge/PyPI-v1.1.7.3-blue) ![python ver](https://img.shields.io/badge/Python-3.12/3.13-purple) ![pyenv ver](https://img.shields.io/badge/pyenv-2.4.23-orange)
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
- For more details:
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
- [PyPi package](https://pypi.org/project/versionhq/)
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.2"
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.2"
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
- default=None, description="region of client's main business operation"
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
- "may_not_set_field", "This field is not to be set by the user.", {}
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
- default=None, description="marketing url of the product if any"
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
- """Execute the task asynchronously with context handling."""
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(self.output)
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
- UUID4,
12
- InstanceOf,
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 | None = Field(default=None)
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
- self,
305
- futures: List[Tuple[Task, Future[TaskOutput], int]],
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.2
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
- ![MIT license](https://img.shields.io/badge/License-MIT-green) [![Publisher](https://github.com/versionHQ/multi-agent-system/actions/workflows/publish.yml/badge.svg)](https://github.com/versionHQ/multi-agent-system/actions/workflows/publish.yml) ![PyPi](https://img.shields.io/badge/pypi-v1.1.7.0-blue) ![python ver](https://img.shields.io/badge/Python-3.12/3.13-purple) ![pyenv ver](https://img.shields.io/badge/pyenv-2.4.23-orange)
55
+ ![MIT license](https://img.shields.io/badge/License-MIT-green) [![Publisher](https://github.com/versionHQ/multi-agent-system/actions/workflows/publish.yml/badge.svg)](https://github.com/versionHQ/multi-agent-system/actions/workflows/publish.yml) ![PyPI](https://img.shields.io/badge/PyPI-v1.1.7.3-blue) ![python ver](https://img.shields.io/badge/Python-3.12/3.13-purple) ![pyenv ver](https://img.shields.io/badge/pyenv-2.4.23-orange)
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
- For more details:
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
- [PyPi package](https://pypi.org/project/versionhq/)
168
+ For more info: [PyPI package](https://pypi.org/project/versionhq/)
170
169
 
171
170
  <hr />
172
171
 
@@ -57,6 +57,7 @@ tests/conftest.py
57
57
  tests/agent/__init__.py
58
58
  tests/agent/agent_test.py
59
59
  tests/cli/__init__.py
60
+ tests/clients/workflow_test.py
60
61
  tests/task/__init__.py
61
62
  tests/task/task_test.py
62
63
  tests/team/__init__.py
@@ -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