versionhq 1.1.9.0__py3-none-any.whl → 1.1.9.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- versionhq/__init__.py +1 -1
- versionhq/_utils/cache_handler.py +2 -2
- versionhq/_utils/logger.py +12 -19
- versionhq/_utils/rpm_controller.py +6 -5
- versionhq/_utils/usage_metrics.py +1 -1
- versionhq/agent/model.py +59 -46
- versionhq/agent/parser.py +1 -12
- versionhq/clients/customer/__init__.py +5 -0
- versionhq/clients/customer/model.py +27 -31
- versionhq/clients/product/model.py +9 -8
- versionhq/clients/workflow/model.py +18 -24
- versionhq/task/model.py +56 -37
- versionhq/team/model.py +8 -9
- versionhq/tool/composio.py +14 -2
- versionhq/tool/model.py +70 -62
- versionhq/tool/tool_handler.py +17 -15
- {versionhq-1.1.9.0.dist-info → versionhq-1.1.9.2.dist-info}/METADATA +1 -1
- {versionhq-1.1.9.0.dist-info → versionhq-1.1.9.2.dist-info}/RECORD +21 -21
- {versionhq-1.1.9.0.dist-info → versionhq-1.1.9.2.dist-info}/LICENSE +0 -0
- {versionhq-1.1.9.0.dist-info → versionhq-1.1.9.2.dist-info}/WHEEL +0 -0
- {versionhq-1.1.9.0.dist-info → versionhq-1.1.9.2.dist-info}/top_level.txt +0 -0
versionhq/__init__.py
CHANGED
@@ -6,8 +6,8 @@ from pydantic import BaseModel, PrivateAttr
|
|
6
6
|
class CacheHandler(BaseModel):
|
7
7
|
_cache: Dict[str, Any] = PrivateAttr(default_factory=dict)
|
8
8
|
|
9
|
-
def add(self, tool, input, output):
|
9
|
+
def add(self, tool: str, input: str, output: Any) -> None:
|
10
10
|
self._cache[f"{tool}-{input}"] = output
|
11
11
|
|
12
|
-
def read(self, tool, input) -> Optional[str]:
|
12
|
+
def read(self, tool: str, input: str) -> Optional[str]:
|
13
13
|
return self._cache.get(f"{tool}-{input}")
|
versionhq/_utils/logger.py
CHANGED
@@ -10,23 +10,21 @@ class Printer:
|
|
10
10
|
self._print_purple(content)
|
11
11
|
elif color == "red":
|
12
12
|
self._print_red(content)
|
13
|
-
elif color == "
|
14
|
-
self.
|
15
|
-
elif color == "
|
16
|
-
self.
|
17
|
-
elif color == "
|
18
|
-
self.
|
13
|
+
elif color == "green":
|
14
|
+
self._print_green(content)
|
15
|
+
elif color == "purple":
|
16
|
+
self._print_purple(content)
|
17
|
+
elif color == "blue":
|
18
|
+
self._print_blue(content)
|
19
19
|
elif color == "yellow":
|
20
20
|
self._print_yellow(content)
|
21
|
-
elif color == "bold_yellow":
|
22
|
-
self._print_bold_yellow(content)
|
23
21
|
else:
|
24
22
|
print(content)
|
25
23
|
|
26
|
-
def
|
24
|
+
def _print_purple(self, content):
|
27
25
|
print("\033[1m\033[95m {}\033[00m".format(content))
|
28
26
|
|
29
|
-
def
|
27
|
+
def _print_green(self, content):
|
30
28
|
print("\033[1m\033[92m {}\033[00m".format(content))
|
31
29
|
|
32
30
|
def _print_purple(self, content):
|
@@ -35,23 +33,18 @@ class Printer:
|
|
35
33
|
def _print_red(self, content):
|
36
34
|
print("\033[91m {}\033[00m".format(content))
|
37
35
|
|
38
|
-
def
|
36
|
+
def _print_blue(self, content):
|
39
37
|
print("\033[1m\033[94m {}\033[00m".format(content))
|
40
38
|
|
41
39
|
def _print_yellow(self, content):
|
42
|
-
print("\033[93m {}\033[00m".format(content))
|
43
|
-
|
44
|
-
def _print_bold_yellow(self, content):
|
45
40
|
print("\033[1m\033[93m {}\033[00m".format(content))
|
46
41
|
|
47
42
|
|
48
43
|
class Logger(BaseModel):
|
49
|
-
verbose: bool = Field(default=
|
44
|
+
verbose: bool = Field(default=True)
|
50
45
|
_printer: Printer = PrivateAttr(default_factory=Printer)
|
51
46
|
|
52
|
-
def log(self, level, message, color="
|
47
|
+
def log(self, level, message, color="yellow"):
|
53
48
|
if self.verbose:
|
54
49
|
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
55
|
-
self._printer.print(
|
56
|
-
f"\n[{timestamp}][{level.upper()}]: {message}", color=color
|
57
|
-
)
|
50
|
+
self._printer.print(f"\n[{timestamp}][{level.upper()}]: {message}", color=color)
|
@@ -1,6 +1,7 @@
|
|
1
1
|
import threading
|
2
2
|
import time
|
3
3
|
from typing import Optional
|
4
|
+
from typing_extensions import Self
|
4
5
|
|
5
6
|
from pydantic import BaseModel, Field, PrivateAttr, model_validator
|
6
7
|
|
@@ -16,14 +17,14 @@ class RPMController(BaseModel):
|
|
16
17
|
_shutdown_flag: bool = PrivateAttr(default=False)
|
17
18
|
|
18
19
|
@model_validator(mode="after")
|
19
|
-
def reset_counter(self):
|
20
|
+
def reset_counter(self) -> Self:
|
20
21
|
if self.max_rpm is not None:
|
21
22
|
if not self._shutdown_flag:
|
22
23
|
self._lock = threading.Lock()
|
23
24
|
self._reset_request_count()
|
24
25
|
return self
|
25
26
|
|
26
|
-
def check_or_wait(self):
|
27
|
+
def check_or_wait(self) -> bool:
|
27
28
|
if self.max_rpm is None:
|
28
29
|
return True
|
29
30
|
|
@@ -46,16 +47,16 @@ class RPMController(BaseModel):
|
|
46
47
|
else:
|
47
48
|
return _check_and_increment()
|
48
49
|
|
49
|
-
def stop_rpm_counter(self):
|
50
|
+
def stop_rpm_counter(self) -> None:
|
50
51
|
if self._timer:
|
51
52
|
self._timer.cancel()
|
52
53
|
self._timer = None
|
53
54
|
|
54
|
-
def _wait_for_next_minute(self):
|
55
|
+
def _wait_for_next_minute(self) -> None:
|
55
56
|
time.sleep(60)
|
56
57
|
self._current_rpm = 0
|
57
58
|
|
58
|
-
def _reset_request_count(self):
|
59
|
+
def _reset_request_count(self) -> None:
|
59
60
|
def _reset():
|
60
61
|
self._current_rpm = 0
|
61
62
|
if not self._shutdown_flag:
|
@@ -12,7 +12,7 @@ class UsageMetrics(BaseModel):
|
|
12
12
|
completion_tokens: int = Field(default=0, description="Number of tokens used in completions")
|
13
13
|
successful_requests: int = Field(default=0, description="Number of successful requests made")
|
14
14
|
|
15
|
-
def add_usage_metrics(self, usage_metrics: "UsageMetrics"):
|
15
|
+
def add_usage_metrics(self, usage_metrics: "UsageMetrics") -> None:
|
16
16
|
"""
|
17
17
|
Add the usage metrics from another UsageMetrics object.
|
18
18
|
"""
|
versionhq/agent/model.py
CHANGED
@@ -1,22 +1,19 @@
|
|
1
1
|
import os
|
2
2
|
import uuid
|
3
|
-
from abc import ABC
|
4
3
|
from typing import Any, Dict, List, Optional, TypeVar
|
4
|
+
from typing_extensions import Self
|
5
5
|
from dotenv import load_dotenv
|
6
6
|
from pydantic import UUID4, BaseModel, Field, InstanceOf, PrivateAttr, model_validator, field_validator
|
7
7
|
from pydantic_core import PydanticCustomError
|
8
8
|
|
9
|
-
from versionhq._utils.cache_handler import CacheHandler
|
10
9
|
from versionhq._utils.logger import Logger
|
11
10
|
from versionhq._utils.rpm_controller import RPMController
|
12
11
|
from versionhq._utils.usage_metrics import UsageMetrics
|
13
|
-
from versionhq.agent.parser import AgentAction
|
14
12
|
from versionhq.llm.llm_vars import LLM_VARS
|
15
13
|
from versionhq.llm.model import LLM, DEFAULT_CONTEXT_WINDOW
|
16
14
|
from versionhq.task import TaskOutputFormat
|
17
15
|
from versionhq.task.model import ResponseField
|
18
16
|
from versionhq.tool.model import Tool, ToolSet
|
19
|
-
from versionhq.tool.tool_handler import ToolHandler
|
20
17
|
|
21
18
|
load_dotenv(override=True)
|
22
19
|
T = TypeVar("T", bound="Agent")
|
@@ -51,18 +48,18 @@ class TokenProcess:
|
|
51
48
|
completion_tokens: int = 0
|
52
49
|
successful_requests: int = 0
|
53
50
|
|
54
|
-
def sum_prompt_tokens(self, tokens: int):
|
51
|
+
def sum_prompt_tokens(self, tokens: int) -> None:
|
55
52
|
self.prompt_tokens = self.prompt_tokens + tokens
|
56
53
|
self.total_tokens = self.total_tokens + tokens
|
57
54
|
|
58
|
-
def sum_completion_tokens(self, tokens: int):
|
55
|
+
def sum_completion_tokens(self, tokens: int) -> None:
|
59
56
|
self.completion_tokens = self.completion_tokens + tokens
|
60
57
|
self.total_tokens = self.total_tokens + tokens
|
61
58
|
|
62
|
-
def sum_cached_prompt_tokens(self, tokens: int):
|
59
|
+
def sum_cached_prompt_tokens(self, tokens: int) -> None:
|
63
60
|
self.cached_prompt_tokens = self.cached_prompt_tokens + tokens
|
64
61
|
|
65
|
-
def sum_successful_requests(self, requests: int):
|
62
|
+
def sum_successful_requests(self, requests: int) -> None:
|
66
63
|
self.successful_requests = self.successful_requests + requests
|
67
64
|
|
68
65
|
def get_summary(self) -> UsageMetrics:
|
@@ -76,7 +73,7 @@ class TokenProcess:
|
|
76
73
|
|
77
74
|
|
78
75
|
# @track_agent()
|
79
|
-
class Agent(
|
76
|
+
class Agent(BaseModel):
|
80
77
|
"""
|
81
78
|
Agent class that run on LLM.
|
82
79
|
Agents execute tasks alone or in the team, using RAG tools and knowledge base if any.
|
@@ -97,10 +94,7 @@ class Agent(ABC, BaseModel):
|
|
97
94
|
backstory: Optional[str] = Field(default=None, description="system context passed to the LLM")
|
98
95
|
knowledge: Optional[str] = Field(default=None, description="external knowledge fed to the agent")
|
99
96
|
skillsets: Optional[List[str]] = Field(default_factory=list)
|
100
|
-
|
101
|
-
# tools
|
102
|
-
tools: Optional[List[Any]] = Field(default_factory=list)
|
103
|
-
tool_handler: InstanceOf[ToolHandler] = Field(default=None, description="handle tool cache and last used tool")
|
97
|
+
tools: Optional[List[Tool | Any]] = Field(default_factory=list)
|
104
98
|
|
105
99
|
# team, task execution rules
|
106
100
|
team: Optional[List[Any]] = Field(default=None, description="Team to which the agent belongs")
|
@@ -126,10 +120,7 @@ class Agent(ABC, BaseModel):
|
|
126
120
|
|
127
121
|
# config, cache, error handling
|
128
122
|
config: Optional[Dict[str, Any]] = Field(default=None, exclude=True, description="Configuration for the agent")
|
129
|
-
cache: bool = Field(default=True, description="Whether the agent should use a cache for tool usage.")
|
130
|
-
cache_handler: InstanceOf[CacheHandler] = Field(default=None, description="An instance of the CacheHandler class.")
|
131
123
|
formatting_errors: int = Field(default=0, description="Number of formatting errors.")
|
132
|
-
verbose: bool = Field(default=True, description="Verbose mode for the Agent Execution")
|
133
124
|
agent_ops_agent_name: str = None
|
134
125
|
agent_ops_agent_id: str = None
|
135
126
|
|
@@ -146,7 +137,7 @@ class Agent(ABC, BaseModel):
|
|
146
137
|
|
147
138
|
|
148
139
|
@model_validator(mode="after")
|
149
|
-
def validate_required_fields(self):
|
140
|
+
def validate_required_fields(self) -> Self:
|
150
141
|
required_fields = ["role", "goal"]
|
151
142
|
for field in required_fields:
|
152
143
|
if getattr(self, field) is None:
|
@@ -155,7 +146,7 @@ class Agent(ABC, BaseModel):
|
|
155
146
|
|
156
147
|
|
157
148
|
@model_validator(mode="after")
|
158
|
-
def set_up_llm(self):
|
149
|
+
def set_up_llm(self) -> Self:
|
159
150
|
"""
|
160
151
|
Set up the base model and function calling model (if any) using the LLM class.
|
161
152
|
Pass the model config params: `llm`, `max_tokens`, `max_execution_time`, `step_callback`,`respect_context_window` to the LLM class.
|
@@ -270,31 +261,42 @@ class Agent(ABC, BaseModel):
|
|
270
261
|
|
271
262
|
|
272
263
|
@model_validator(mode="after")
|
273
|
-
def set_up_tools(self):
|
264
|
+
def set_up_tools(self) -> Self:
|
274
265
|
"""
|
275
266
|
Similar to the LLM set up, when the agent has tools, we will declare them using the Tool class.
|
276
267
|
"""
|
277
|
-
|
278
268
|
if not self.tools:
|
279
269
|
pass
|
280
270
|
|
281
271
|
else:
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
272
|
+
tool_list = []
|
273
|
+
def empty_func():
|
274
|
+
return "empty function"
|
275
|
+
|
276
|
+
for item in self.tools:
|
277
|
+
if isinstance(item, Tool):
|
278
|
+
tool_list.append(item)
|
279
|
+
|
280
|
+
elif isinstance(item, dict):
|
281
|
+
if "function" not in item:
|
282
|
+
setattr(item, "function", empty_func)
|
283
|
+
tool = Tool(**item)
|
284
|
+
tool_list.append(tool)
|
285
|
+
|
286
|
+
elif isinstance(item, str):
|
287
|
+
tool = Tool(name=item, function=empty_func)
|
288
|
+
tool_list.append(tool)
|
289
|
+
|
289
290
|
else:
|
290
|
-
|
291
|
-
|
291
|
+
tool_list.append(item) # address custom tool
|
292
|
+
|
293
|
+
self.tools = tool_list
|
292
294
|
|
293
295
|
return self
|
294
296
|
|
295
297
|
|
296
298
|
@model_validator(mode="after")
|
297
|
-
def set_up_backstory(self):
|
299
|
+
def set_up_backstory(self) -> Self:
|
298
300
|
"""
|
299
301
|
Set up the backstory using a templated BACKSTORY when the backstory is None
|
300
302
|
"""
|
@@ -305,7 +307,7 @@ class Agent(ABC, BaseModel):
|
|
305
307
|
role=self.role,
|
306
308
|
knowledge=self.knowledge if isinstance(self.knowledge, str) else None,
|
307
309
|
skillsets=", ".join([item for item in self.skillsets]),
|
308
|
-
rag_tool_overview=", ".join([item.name for item in self.tools]),
|
310
|
+
rag_tool_overview=", ".join([item.name for item in self.tools if hasattr(item, "name")]) if self.tools else "",
|
309
311
|
goal=self.goal,
|
310
312
|
)
|
311
313
|
self.backstory = backstory
|
@@ -313,9 +315,7 @@ class Agent(ABC, BaseModel):
|
|
313
315
|
return self
|
314
316
|
|
315
317
|
|
316
|
-
def invoke(
|
317
|
-
self, prompts: str, output_formats: List[TaskOutputFormat], response_fields: List[ResponseField], **kwargs
|
318
|
-
) -> Dict[str, Any]:
|
318
|
+
def invoke(self, prompts: str, output_formats: List[TaskOutputFormat], response_fields: List[ResponseField], **kwargs) -> Dict[str, Any]:
|
319
319
|
"""
|
320
320
|
Receive the system prompt in string and create formatted prompts using the system prompt and the agent's backstory.
|
321
321
|
Then call the base model.
|
@@ -358,11 +358,10 @@ 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
|
361
|
+
def execute_task(self, task, context: Optional[str] = None) -> str:
|
362
362
|
"""
|
363
|
-
Execute the task and return the
|
364
|
-
|
365
|
-
When the tools are given, the agent must use them.
|
363
|
+
Execute the task and return the response in string.
|
364
|
+
The agent utilizes the tools in task or their own tools if the task.can_use_agent_tools is True.
|
366
365
|
The agent must consider the context to excute the task as well when it is given.
|
367
366
|
"""
|
368
367
|
|
@@ -371,13 +370,29 @@ class Agent(ABC, BaseModel):
|
|
371
370
|
task_prompt += context
|
372
371
|
|
373
372
|
tool_results = []
|
374
|
-
if task.
|
375
|
-
for
|
376
|
-
|
373
|
+
if task.tools:
|
374
|
+
for item in task.tools:
|
375
|
+
if isinstance(item, ToolSet):
|
376
|
+
tool_result = item.tool.run(**item.kwargs)
|
377
|
+
tool_results.append(tool_result)
|
378
|
+
elif isinstance(item, Tool):
|
379
|
+
tool_result = item.run()
|
380
|
+
tool_results.append(tool_result)
|
381
|
+
else:
|
382
|
+
try:
|
383
|
+
item.run()
|
384
|
+
except:
|
385
|
+
pass
|
386
|
+
|
387
|
+
if task.can_use_agent_tools is True and self.tools:
|
388
|
+
for tool in self.tools:
|
389
|
+
tool_result = tool.run()
|
377
390
|
tool_results.append(tool_result)
|
378
391
|
|
379
|
-
|
380
|
-
|
392
|
+
if task.take_tool_res_as_final:
|
393
|
+
return tool_results
|
394
|
+
|
395
|
+
|
381
396
|
|
382
397
|
# if self.team and self.team._train:
|
383
398
|
# task_prompt = self._training_handler(task_prompt=task_prompt)
|
@@ -395,9 +410,7 @@ class Agent(ABC, BaseModel):
|
|
395
410
|
self._times_executed += 1
|
396
411
|
if self._times_executed > self.max_retry_limit:
|
397
412
|
raise e
|
398
|
-
result = self.execute_task(
|
399
|
-
task, context, [tool_called.tool for tool_called in task.tools_called]
|
400
|
-
)
|
413
|
+
result = self.execute_task(task, context)
|
401
414
|
|
402
415
|
if self.max_rpm and self._rpm_controller:
|
403
416
|
self._rpm_controller.stop_rpm_counter()
|
versionhq/agent/parser.py
CHANGED
@@ -73,9 +73,7 @@ class AgentParser:
|
|
73
73
|
def parse(self, text: str) -> AgentAction | AgentFinish:
|
74
74
|
thought = self._extract_thought(text)
|
75
75
|
includes_answer = FINAL_ANSWER_ACTION in text
|
76
|
-
regex = (
|
77
|
-
r"Action\s*\d*\s*:[\s]*(.*?)[\s]*Action\s*\d*\s*Input\s*\d*\s*:[\s]*(.*)"
|
78
|
-
)
|
76
|
+
regex = r"Action\s*\d*\s*:[\s]*(.*?)[\s]*Action\s*\d*\s*Input\s*\d*\s*:[\s]*(.*)"
|
79
77
|
action_match = re.search(regex, text, re.DOTALL)
|
80
78
|
if action_match:
|
81
79
|
if includes_answer:
|
@@ -127,18 +125,9 @@ class AgentParser:
|
|
127
125
|
def _safe_repair_json(self, tool_input: str) -> str:
|
128
126
|
UNABLE_TO_REPAIR_JSON_RESULTS = ['""', "{}"]
|
129
127
|
|
130
|
-
# Skip repair if the input starts and ends with square brackets
|
131
|
-
# Explanation: The JSON parser has issues handling inputs that are enclosed in square brackets ('[]').
|
132
|
-
# These are typically valid JSON arrays or strings that do not require repair. Attempting to repair such inputs
|
133
|
-
# might lead to unintended alterations, such as wrapping the entire input in additional layers or modifying
|
134
|
-
# the structure in a way that changes its meaning. By skipping the repair for inputs that start and end with
|
135
|
-
# square brackets, we preserve the integrity of these valid JSON structures and avoid unnecessary modifications.
|
136
128
|
if tool_input.startswith("[") and tool_input.endswith("]"):
|
137
129
|
return tool_input
|
138
130
|
|
139
|
-
# Before repair, handle common LLM issues:
|
140
|
-
# 1. Replace """ with " to avoid JSON parser errors
|
141
|
-
|
142
131
|
tool_input = tool_input.replace('"""', '"')
|
143
132
|
|
144
133
|
result = repair_json(tool_input)
|
@@ -1,57 +1,53 @@
|
|
1
1
|
import uuid
|
2
|
-
from abc import ABC
|
2
|
+
from abc import ABC, abstractmethod
|
3
3
|
from typing import Any, Dict, List, Callable, Type, Optional, get_args, get_origin
|
4
|
-
from pydantic import
|
5
|
-
UUID4,
|
6
|
-
InstanceOf,
|
7
|
-
BaseModel,
|
8
|
-
ConfigDict,
|
9
|
-
Field,
|
10
|
-
create_model,
|
11
|
-
field_validator,
|
12
|
-
model_validator,
|
13
|
-
)
|
4
|
+
from pydantic import UUID4, InstanceOf, BaseModel, ConfigDict, Field, create_model, field_validator, model_validator
|
14
5
|
from pydantic_core import PydanticCustomError
|
15
6
|
|
16
7
|
from versionhq.clients.product.model import Product, ProductProvider
|
8
|
+
from versionhq.clients.customer import Status
|
17
9
|
|
18
10
|
|
19
|
-
class
|
11
|
+
class BaseCustomer(ABC, BaseModel):
|
20
12
|
"""
|
21
|
-
|
13
|
+
Abstract base class for the base customer
|
22
14
|
"""
|
23
15
|
|
24
16
|
id: UUID4 = Field(default_factory=uuid.uuid4, frozen=True)
|
25
17
|
name: Optional[str] = Field(default=None, description="customer's name if any")
|
26
|
-
|
27
|
-
|
28
|
-
)
|
29
|
-
|
30
|
-
default=None, description="store the latest analysis results on the customer"
|
31
|
-
)
|
32
|
-
on_workflow: bool = Field(
|
33
|
-
default=False, description="`True` if they are on some messaging workflows"
|
34
|
-
)
|
35
|
-
on: Optional[str] = Field(
|
36
|
-
default=None, description="destination service for this customer if any"
|
37
|
-
)
|
18
|
+
products: Optional[List[Product]] = Field(default=list, description="store products that the customer is associated with")
|
19
|
+
analysis: str = Field(default=None, description="store the latest analysis results on the customer")
|
20
|
+
status: str = Field(default=Status.ON_WORKFLOW)
|
21
|
+
|
38
22
|
|
39
23
|
@field_validator("id", mode="before")
|
40
24
|
@classmethod
|
41
25
|
def _deny_user_set_id(cls, v: Optional[UUID4]) -> None:
|
42
26
|
if v:
|
43
|
-
raise PydanticCustomError(
|
44
|
-
|
45
|
-
)
|
27
|
+
raise PydanticCustomError("may_not_set_field", "This field is not to be set by the user.", {})
|
28
|
+
|
46
29
|
|
47
30
|
def customer_to(self) -> List[ProductProvider]:
|
48
31
|
"""
|
49
32
|
Return list of ProductProvider if the customer has `product_list`
|
50
33
|
"""
|
51
34
|
|
52
|
-
res =
|
53
|
-
if self.
|
54
|
-
for item in self.
|
35
|
+
res = []
|
36
|
+
if self.products:
|
37
|
+
for item in self.products:
|
55
38
|
if item.provider not in res:
|
56
39
|
res.appned(item.provider)
|
57
40
|
return res
|
41
|
+
|
42
|
+
|
43
|
+
@abstractmethod
|
44
|
+
def _deploy(self, *args, **kwargs) -> Any:
|
45
|
+
"""Any method to deploy targeting the customer"""
|
46
|
+
|
47
|
+
|
48
|
+
class Customer(BaseCustomer):
|
49
|
+
id: UUID4 = Field(default_factory=uuid.uuid4, frozen=True)
|
50
|
+
name: Optional[str] = Field(default=None, description="customer's name if any")
|
51
|
+
products: Optional[List[Product]] = Field(default=list, description="store products that the customer is associated with")
|
52
|
+
analysis: str = Field(default=None, description="store the latest analysis results on the customer")
|
53
|
+
status: str = Field(default=Status.ON_WORKFLOW)
|
@@ -1,22 +1,23 @@
|
|
1
1
|
import uuid
|
2
|
+
from abc import ABC, abstractmethod
|
2
3
|
from typing import Any, Dict, List, Callable, Type, Optional, get_args, get_origin
|
4
|
+
|
3
5
|
from pydantic import UUID4, InstanceOf, BaseModel, ConfigDict, Field, create_model, field_validator, model_validator
|
4
6
|
from pydantic_core import PydanticCustomError
|
5
7
|
|
8
|
+
from versionhq.tool import ComposioAppName
|
9
|
+
|
6
10
|
|
7
|
-
class ProductProvider(BaseModel):
|
11
|
+
class ProductProvider(ABC, BaseModel):
|
8
12
|
"""
|
9
|
-
|
10
|
-
`data_pipeline` and `destinations` are for composio plug-in.
|
11
|
-
(!REFINEME) Create an Enum list for the options.
|
12
|
-
(!REFINEME) Create an Enum list for regions.
|
13
|
+
Abstract class for the product provider entity.
|
13
14
|
"""
|
14
15
|
|
15
16
|
id: UUID4 = Field(default_factory=uuid.uuid4, frozen=True)
|
16
|
-
name: Optional[str] = Field(default=None
|
17
|
+
name: Optional[str] = Field(default=None)
|
17
18
|
region: Optional[str] = Field(default=None, description="region of client's main business operation")
|
18
|
-
|
19
|
-
|
19
|
+
data_pipelines: Optional[List[ComposioAppName | str]] = Field(default_factory=list)
|
20
|
+
destination_services: Optional[List[ComposioAppName | str]] = Field(default=None)
|
20
21
|
|
21
22
|
@field_validator("id", mode="before")
|
22
23
|
@classmethod
|
@@ -2,6 +2,7 @@ import uuid
|
|
2
2
|
from abc import ABC
|
3
3
|
from datetime import date, datetime, time, timedelta
|
4
4
|
from typing import Any, Dict, List, Callable, Type, Optional, get_args, get_origin
|
5
|
+
from typing_extensions import Self
|
5
6
|
from pydantic import UUID4, InstanceOf, BaseModel, ConfigDict, Field, field_validator, model_validator
|
6
7
|
from pydantic_core import PydanticCustomError
|
7
8
|
|
@@ -9,6 +10,7 @@ from versionhq.clients.product.model import Product
|
|
9
10
|
from versionhq.clients.customer.model import Customer
|
10
11
|
from versionhq.agent.model import Agent
|
11
12
|
from versionhq.team.model import Team
|
13
|
+
from versionhq.tool import ComposioAppName
|
12
14
|
|
13
15
|
|
14
16
|
class ScoreFormat:
|
@@ -56,14 +58,12 @@ class Score:
|
|
56
58
|
class MessagingComponent(ABC, BaseModel):
|
57
59
|
layer_id: int = Field(default=0, description="add id of the layer: 0, 1, 2")
|
58
60
|
message: str = Field(default=None, max_length=1024, description="text message content to be sent")
|
59
|
-
|
60
|
-
|
61
|
-
)
|
62
|
-
score: float | InstanceOf[Score] = Field(default=None)
|
63
|
-
condition: str = Field(default=None, max_length=128, description="condition to execute the next messaging component")
|
61
|
+
score: InstanceOf[Score] = Field(default=None)
|
62
|
+
condition: str = Field(default=None, max_length=128, description="condition to execute the next component")
|
63
|
+
interval: Optional[str] = Field(default=None, description="ideal interval to set to assess the condition")
|
64
64
|
|
65
65
|
|
66
|
-
def store_scoring_result(self,
|
66
|
+
def store_scoring_result(self, subject: str, score_raw: int | Score | ScoreFormat = None) -> Self:
|
67
67
|
"""
|
68
68
|
Set up the `score` field
|
69
69
|
"""
|
@@ -73,12 +73,12 @@ class MessagingComponent(ABC, BaseModel):
|
|
73
73
|
|
74
74
|
elif isinstance(score_raw, ScoreFormat):
|
75
75
|
score_instance = Score()
|
76
|
-
setattr(score_instance,
|
76
|
+
setattr(score_instance, subject, score_raw)
|
77
77
|
setattr(self, "score", score_instance)
|
78
78
|
|
79
79
|
elif isinstance(score_raw, int) or isinstance(score_raw, float):
|
80
80
|
score_instance, score_format_instance = Score(), ScoreFormat(rate=score_raw, weight=1)
|
81
|
-
setattr(score_instance, "kwargs", {
|
81
|
+
setattr(score_instance, "kwargs", { subject: score_format_instance })
|
82
82
|
setattr(self, "score", score_instance)
|
83
83
|
|
84
84
|
else:
|
@@ -99,23 +99,17 @@ class MessagingWorkflow(ABC, BaseModel):
|
|
99
99
|
model_config = ConfigDict()
|
100
100
|
|
101
101
|
id: UUID4 = Field(default_factory=uuid.uuid4, frozen=True)
|
102
|
-
|
102
|
+
messaging_components: List[MessagingComponent] = Field(default_factory=list, description="store messaging components in the workflow")
|
103
103
|
|
104
104
|
# responsible tean or agents
|
105
|
-
team: Optional[Team] = Field(default=None, description="store
|
106
|
-
agents: Optional[List[Agent]] = Field(
|
107
|
-
default=None, description="store `Agent` instances responsible for autopiloting this workflow. if the team exsits, this field remains as `None`")
|
105
|
+
team: Optional[Team] = Field(default=None, description="store a responsibile team to autopilot the workflow")
|
106
|
+
agents: Optional[List[Agent]] = Field(default=None, description="store responsible agents. None when the team exists")
|
108
107
|
|
109
108
|
# metrics
|
110
|
-
destination: Optional[
|
109
|
+
destination: Optional[ComposioAppName | str] = Field(default=None, description="destination service to launch the workflow")
|
111
110
|
product: InstanceOf[Product] = Field(default=None)
|
112
111
|
customer: InstanceOf[Customer] = Field(default=None)
|
113
|
-
|
114
|
-
metrics: List[Dict[str, Any]] | List[str] = Field(
|
115
|
-
default=None,
|
116
|
-
max_length=256,
|
117
|
-
description="store metrics that used to predict and track the performance of this workflow."
|
118
|
-
)
|
112
|
+
performance_metrics: List[Dict[str, Any]] | List[str] = Field(default=None, max_length=256, description="performance metrics to track")
|
119
113
|
|
120
114
|
@field_validator("id", mode="before")
|
121
115
|
@classmethod
|
@@ -131,11 +125,11 @@ class MessagingWorkflow(ABC, BaseModel):
|
|
131
125
|
Prioritize customer's destination to the product provider's destination list.
|
132
126
|
"""
|
133
127
|
if self.destination is None:
|
134
|
-
if self.customer is not None:
|
135
|
-
|
128
|
+
# if self.customer is not None:
|
129
|
+
# self.destination = self.customer.on
|
136
130
|
|
137
|
-
|
138
|
-
self.destination = self.product.provider.
|
131
|
+
if self.product.provider is not None and self.product.provider.destination_services:
|
132
|
+
self.destination = self.product.provider.destination_services[0]
|
139
133
|
|
140
134
|
return self
|
141
135
|
|
@@ -154,7 +148,7 @@ class MessagingWorkflow(ABC, BaseModel):
|
|
154
148
|
|
155
149
|
|
156
150
|
@property
|
157
|
-
def name(self):
|
151
|
+
def name(self) -> str:
|
158
152
|
if self.customer.id:
|
159
153
|
return f"Workflow ID: {self.id} - on {self.product.id} for {self.customer.id}"
|
160
154
|
else:
|