versionhq 1.1.8.1__py3-none-any.whl → 1.1.9.1__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 +3 -1
- versionhq/_utils/cache_handler.py +2 -2
- versionhq/_utils/rpm_controller.py +6 -5
- versionhq/_utils/usage_metrics.py +1 -1
- versionhq/agent/model.py +60 -42
- versionhq/agent/parser.py +1 -12
- versionhq/clients/customer/model.py +6 -22
- versionhq/clients/workflow/model.py +10 -10
- versionhq/task/model.py +126 -106
- versionhq/team/model.py +59 -66
- versionhq/tool/__init__.py +53 -0
- versionhq/tool/composio.py +149 -0
- versionhq/tool/decorator.py +3 -1
- versionhq/tool/model.py +138 -115
- versionhq/tool/tool_handler.py +20 -23
- {versionhq-1.1.8.1.dist-info → versionhq-1.1.9.1.dist-info}/METADATA +2 -2
- {versionhq-1.1.8.1.dist-info → versionhq-1.1.9.1.dist-info}/RECORD +20 -19
- {versionhq-1.1.8.1.dist-info → versionhq-1.1.9.1.dist-info}/LICENSE +0 -0
- {versionhq-1.1.8.1.dist-info → versionhq-1.1.9.1.dist-info}/WHEEL +0 -0
- {versionhq-1.1.8.1.dist-info → versionhq-1.1.9.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,149 @@
|
|
1
|
+
import os
|
2
|
+
import uuid
|
3
|
+
from dotenv import load_dotenv
|
4
|
+
from typing import Any, Callable, Type, get_args, get_origin, Optional, Tuple
|
5
|
+
|
6
|
+
from pydantic import BaseModel, Field, model_validator, field_validator, UUID4
|
7
|
+
from pydantic_core import PydanticCustomError
|
8
|
+
|
9
|
+
from composio import ComposioToolSet, Action, App, action
|
10
|
+
|
11
|
+
from versionhq.tool import ComposioAppName, ComposioAuthScheme, composio_app_set, COMPOSIO_STATUS
|
12
|
+
|
13
|
+
load_dotenv(override=True)
|
14
|
+
|
15
|
+
DEFAULT_REDIRECT_URL = os.environ.get("DEFAULT_REDIRECT_URL", None)
|
16
|
+
DEFAULT_USER_ID = os.environ.get("DEFAULT_USER_ID", None)
|
17
|
+
|
18
|
+
|
19
|
+
class Composio(BaseModel):
|
20
|
+
"""
|
21
|
+
Class to handle composio tools.
|
22
|
+
"""
|
23
|
+
|
24
|
+
id: UUID4 = Field(default_factory=uuid.uuid4, frozen=True)
|
25
|
+
app_name: str = Field(default=ComposioAppName.HUBSPOT)
|
26
|
+
user_id: str = Field(default=DEFAULT_USER_ID)
|
27
|
+
auth_scheme: str = Field(default=ComposioAuthScheme.OAUTH2)
|
28
|
+
redirect_url: str = Field(default=DEFAULT_REDIRECT_URL, description="redirect url after successful oauth2 connection")
|
29
|
+
connect_request_id: str = Field(default=None, description="store the client's composio id to connect with the app")
|
30
|
+
|
31
|
+
@property
|
32
|
+
def toolset(self) -> ComposioToolSet:
|
33
|
+
return ComposioToolSet(api_key=os.environ.get("COMPOSIO_API_KEY"))
|
34
|
+
|
35
|
+
|
36
|
+
def __name__(self):
|
37
|
+
return self.app_name
|
38
|
+
|
39
|
+
|
40
|
+
@field_validator("id", mode="before")
|
41
|
+
@classmethod
|
42
|
+
def _deny_user_set_id(cls, v: Optional[UUID4]) -> None:
|
43
|
+
if v:
|
44
|
+
raise PydanticCustomError("may_not_set_field", "This field is not to be set by the user.", {})
|
45
|
+
|
46
|
+
|
47
|
+
# @model_validator("user_id", mode="before")
|
48
|
+
# @classmethod
|
49
|
+
# def _deny_no_user_id(cls, v: Optional[str]) -> None:
|
50
|
+
# if v is None:
|
51
|
+
# raise PydanticCustomError("user_id_missing", "Need user_id to connect with the tool", {})
|
52
|
+
|
53
|
+
|
54
|
+
@model_validator(mode="after")
|
55
|
+
def validate_app_name(self):
|
56
|
+
if self.app_name not in ComposioAppName:
|
57
|
+
raise PydanticCustomError("no_app_name", f"The app name {self.app_name} is not valid.", {})
|
58
|
+
|
59
|
+
return self
|
60
|
+
|
61
|
+
|
62
|
+
@model_validator(mode="after")
|
63
|
+
def validate_auth_scheme(self):
|
64
|
+
"""
|
65
|
+
Raise error when the client uses auth scheme unavailable for the app.
|
66
|
+
"""
|
67
|
+
app_set = next(filter(lambda tup: self.app_name in tup, composio_app_set), None)
|
68
|
+
if not app_set:
|
69
|
+
raise PydanticCustomError("no_app_set", f"The app set of {self.app_name} is missing.", {})
|
70
|
+
|
71
|
+
else:
|
72
|
+
acceptable_auth_scheme = next(filter(lambda item: self.auth_scheme in item, app_set), None)
|
73
|
+
if acceptable_auth_scheme is None:
|
74
|
+
raise PydanticCustomError("invalid_auth_scheme", f"The app {self.app_name} must have different auth_scheme.", {})
|
75
|
+
|
76
|
+
return self
|
77
|
+
|
78
|
+
|
79
|
+
# connect with composio to use the tool
|
80
|
+
def connect(self, token: Optional[str] = None, api_key: Optional[str] = None) -> Tuple[str | Any]:
|
81
|
+
"""
|
82
|
+
Connect with Composio, retrieve `connect_request_id`, and validate the connection.
|
83
|
+
"""
|
84
|
+
|
85
|
+
if not self.user_id:
|
86
|
+
raise PydanticCustomError("user_id_missing", "Need user_id to connect with the tool", {})
|
87
|
+
|
88
|
+
|
89
|
+
connection_request, connected_account = None, None
|
90
|
+
|
91
|
+
if self.auth_scheme == ComposioAuthScheme.API_KEY:
|
92
|
+
collected_from_user = {}
|
93
|
+
collected_from_user["api_key"] = api_key
|
94
|
+
connection_request = self.toolset.initiate_connection(
|
95
|
+
connected_account_params = collected_from_user,
|
96
|
+
app=self.app_name,
|
97
|
+
entity_id=self.user_id,
|
98
|
+
auth_scheme=self.auth_scheme,
|
99
|
+
)
|
100
|
+
|
101
|
+
if self.auth_scheme == ComposioAuthScheme.BEARER_TOKEN:
|
102
|
+
collected_from_user = {}
|
103
|
+
collected_from_user["token"] = token
|
104
|
+
connection_request = self.toolset.initiate_connection(
|
105
|
+
connected_account_params = collected_from_user,
|
106
|
+
app=self.app_name,
|
107
|
+
entity_id=self.user_id,
|
108
|
+
auth_scheme=self.auth_scheme,
|
109
|
+
)
|
110
|
+
|
111
|
+
if self.auth_scheme == ComposioAuthScheme.OAUTH2:
|
112
|
+
connection_request = self.toolset.initiate_connection(
|
113
|
+
app=self.app_name,
|
114
|
+
redirect_url = self.redirect_url, # clients will be redirected to this url after successful auth.
|
115
|
+
entity_id=self.user_id,
|
116
|
+
auth_scheme=self.auth_scheme,
|
117
|
+
)
|
118
|
+
|
119
|
+
# connection_request.wait_until_active(self.toolset.client, timeout=60)
|
120
|
+
|
121
|
+
if connection_request.connectionStatus is not COMPOSIO_STATUS.FAILED:
|
122
|
+
self.connect_request_id = connection_request.connectedAccountId
|
123
|
+
connected_account = self.toolset.get_connected_account(id=self.connect_request_id)
|
124
|
+
|
125
|
+
if connected_account.status is not COMPOSIO_STATUS.FAILED:
|
126
|
+
setattr(self.toolset, "entity_id", self.user_id)
|
127
|
+
|
128
|
+
return connected_account, connected_account.status if connected_account else connection_request.connectionStatus
|
129
|
+
|
130
|
+
|
131
|
+
|
132
|
+
|
133
|
+
# @action(toolname=ComposioAppName.HUBSPOT)
|
134
|
+
# def deploy(self, param1: str, param2: str, execute_request: Callable) -> str:
|
135
|
+
# """
|
136
|
+
# Define custom actions
|
137
|
+
# my custom action description which will be passed to llm
|
138
|
+
|
139
|
+
# :param param1: param1 description which will be passed to llm
|
140
|
+
# :param param2: param2 description which will be passed to llm
|
141
|
+
# :return info: return description
|
142
|
+
# """
|
143
|
+
|
144
|
+
# response = execute_request(
|
145
|
+
# "/my_action_endpoint",
|
146
|
+
# "GET",
|
147
|
+
# {} # body can be added here
|
148
|
+
# ) # execute requests by appending credentials to the request
|
149
|
+
# return str(response) # complete auth dict is available for local use if needed
|
versionhq/tool/decorator.py
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
from typing import Callable
|
2
2
|
from pydantic import BaseModel
|
3
|
+
|
3
4
|
from versionhq.tool.model import Tool
|
4
5
|
|
5
6
|
|
@@ -13,6 +14,7 @@ def tool(*args):
|
|
13
14
|
def _make_tool(f: Callable) -> Tool:
|
14
15
|
if f.__doc__ is None:
|
15
16
|
raise ValueError("Function must have a docstring")
|
17
|
+
|
16
18
|
if f.__annotations__ is None:
|
17
19
|
raise ValueError("Function must have type annotations")
|
18
20
|
|
@@ -26,7 +28,7 @@ def tool(*args):
|
|
26
28
|
},
|
27
29
|
},
|
28
30
|
)
|
29
|
-
return Tool(name=tool_name,
|
31
|
+
return Tool(name=tool_name, function=f, args_schema=args_schema)
|
30
32
|
|
31
33
|
return _make_tool
|
32
34
|
|
versionhq/tool/model.py
CHANGED
@@ -1,63 +1,69 @@
|
|
1
|
-
from abc import ABC
|
1
|
+
from abc import ABC, abstractmethod
|
2
2
|
from inspect import signature
|
3
3
|
from typing import Any, Dict, Callable, Type, Optional, get_args, get_origin
|
4
|
-
from
|
4
|
+
from typing_extensions import Self
|
5
|
+
from pydantic import InstanceOf, BaseModel, ConfigDict, Field, field_validator, model_validator
|
5
6
|
|
6
7
|
from versionhq._utils.cache_handler import CacheHandler
|
7
8
|
|
8
9
|
|
9
|
-
class
|
10
|
+
class BaseTool(ABC, BaseModel):
|
10
11
|
"""
|
11
|
-
|
12
|
+
Abstract class for Tool class.
|
12
13
|
"""
|
13
14
|
|
14
|
-
class
|
15
|
+
class _ArgsSchemaPlaceholder(BaseModel):
|
15
16
|
pass
|
16
17
|
|
17
|
-
|
18
|
-
name: str = Field(default=None)
|
19
|
-
func: Callable = Field(default=None)
|
20
|
-
cache_function: Callable = lambda _args=None, _result=None: True
|
21
|
-
args_schema: Type[BaseModel] = Field(default_factory=ArgsSchema)
|
22
|
-
tool_handler: Optional[Dict[str, Any]] = Field(
|
23
|
-
default=None,
|
24
|
-
description="store tool_handler to record the usage of this tool. to avoid circular import, set as Dict format",
|
25
|
-
)
|
26
|
-
|
27
|
-
@property
|
28
|
-
def description(self):
|
29
|
-
args_schema = {
|
30
|
-
name: { "description": field.description, "type": Tool._get_arg_annotations(field.annotation) }
|
31
|
-
for name, field in self.args_schema.model_fields.items()
|
32
|
-
}
|
33
|
-
return f"Tool Name: {self.name}\nTool Arguments: {args_schema}\nTool Description: {self.description}"
|
18
|
+
args_schema: Type[BaseModel] = Field(default_factory=_ArgsSchemaPlaceholder)
|
34
19
|
|
35
20
|
|
36
21
|
@field_validator("args_schema", mode="before")
|
37
22
|
@classmethod
|
38
23
|
def _default_args_schema(cls, v: Type[BaseModel]) -> Type[BaseModel]:
|
39
|
-
if not isinstance(v, cls.
|
24
|
+
if not isinstance(v, cls._ArgsSchemaPlaceholder):
|
40
25
|
return v
|
41
26
|
|
42
27
|
return type(
|
43
28
|
f"{cls.__name__}Schema",
|
44
29
|
(BaseModel,),
|
45
|
-
{
|
46
|
-
"__annotations__": {
|
47
|
-
k: v for k, v in cls._run.__annotations__.items() if k != "return"
|
48
|
-
},
|
49
|
-
},
|
30
|
+
{ "__annotations__": { k: v for k, v in cls._run.__annotations__.items() if k != "return" }},
|
50
31
|
)
|
51
32
|
|
33
|
+
|
34
|
+
@abstractmethod
|
35
|
+
def _run(self, *args: Any, **kwargs: Any,) -> Any:
|
36
|
+
"""any handling"""
|
37
|
+
|
38
|
+
|
39
|
+
|
40
|
+
class Tool(BaseTool):
|
41
|
+
name: str = Field(default=None)
|
42
|
+
goal: str = Field(default=None)
|
43
|
+
function: Callable = Field(default=None)
|
44
|
+
cache_function: Callable = lambda _args=None, _result=None: True
|
45
|
+
tool_handler: Optional[Dict[str, Any]] = Field(
|
46
|
+
default=None,
|
47
|
+
description="store tool_handler to record the usage of this tool. to avoid circular import, set as Dict format",
|
48
|
+
)
|
49
|
+
|
52
50
|
@model_validator(mode="after")
|
53
|
-
def
|
51
|
+
def set_up_tool_handler(self) -> Self:
|
54
52
|
from versionhq.tool.tool_handler import ToolHandler
|
55
53
|
|
56
54
|
if self.tool_handler:
|
57
55
|
ToolHandler(**self.tool_handler)
|
56
|
+
return self
|
57
|
+
|
58
58
|
|
59
|
+
@model_validator(mode="after")
|
60
|
+
def set_up_function(self) -> Self:
|
61
|
+
if self.function is None:
|
62
|
+
self.function = self._run
|
63
|
+
self._set_args_schema_from_func()
|
59
64
|
return self
|
60
65
|
|
66
|
+
|
61
67
|
@staticmethod
|
62
68
|
def _get_arg_annotations(annotation: type[Any] | None) -> str:
|
63
69
|
if annotation is None:
|
@@ -67,11 +73,7 @@ class Tool(ABC, BaseModel):
|
|
67
73
|
args = get_args(annotation)
|
68
74
|
|
69
75
|
if origin is None:
|
70
|
-
return (
|
71
|
-
annotation.__name__
|
72
|
-
if hasattr(annotation, "__name__")
|
73
|
-
else str(annotation)
|
74
|
-
)
|
76
|
+
return annotation.__name__ if hasattr(annotation, "__name__") else str(annotation)
|
75
77
|
|
76
78
|
if args:
|
77
79
|
args_str = ", ".join(Tool._get_arg_annotations(arg) for arg in args)
|
@@ -79,112 +81,133 @@ class Tool(ABC, BaseModel):
|
|
79
81
|
|
80
82
|
return origin.__name__
|
81
83
|
|
82
|
-
def _set_args_schema(self):
|
83
|
-
if self.args_schema is None:
|
84
|
-
class_name = f"{self.__class__.__name__}Schema"
|
85
|
-
|
86
|
-
self.args_schema = type(
|
87
|
-
class_name,
|
88
|
-
(BaseModel,),
|
89
|
-
{
|
90
|
-
"__annotations__": {
|
91
|
-
k: v
|
92
|
-
for k, v in self._run.__annotations__.items()
|
93
|
-
if k != "return"
|
94
|
-
},
|
95
|
-
},
|
96
|
-
)
|
97
84
|
|
98
|
-
|
99
|
-
def from_composio(
|
100
|
-
cls, func: Callable = None, tool_name: str = "Composio tool"
|
101
|
-
) -> "Tool":
|
85
|
+
def _parse_args(self, raw_args: str | dict) -> dict:
|
102
86
|
"""
|
103
|
-
|
104
|
-
Refer to the `args_schema` from the func signature if any. Else, create an `args_schema`.
|
87
|
+
Parse and validate the input arguments against the schema
|
105
88
|
"""
|
89
|
+
if isinstance(raw_args, str):
|
90
|
+
try:
|
91
|
+
import json
|
92
|
+
raw_args = json.loads(raw_args)
|
93
|
+
except json.JSONDecodeError as e:
|
94
|
+
raise ValueError(f"Failed to parse arguments as JSON: {e}")
|
106
95
|
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
create_model(f"{tool_name}Input", **args_fields)
|
124
|
-
if args_fields
|
125
|
-
else create_model(f"{tool_name}Input", __base__=BaseModel)
|
96
|
+
try:
|
97
|
+
validated_args = self.args_schema.model_validate(raw_args)
|
98
|
+
return validated_args.model_dump()
|
99
|
+
|
100
|
+
except Exception as e:
|
101
|
+
raise ValueError(f"Arguments validation failed: {e}")
|
102
|
+
|
103
|
+
|
104
|
+
def _set_args_schema_from_func(self):
|
105
|
+
class_name = f"{self.__class__.__name__}Schema"
|
106
|
+
self.args_schema = type(
|
107
|
+
class_name,
|
108
|
+
(BaseModel,),
|
109
|
+
{ "__annotations__": {
|
110
|
+
k: v for k, v in self._run.__annotations__.items() if k != "return"
|
111
|
+
} },
|
126
112
|
)
|
113
|
+
return self
|
114
|
+
|
127
115
|
|
128
|
-
|
116
|
+
def _run(self, *args: Any, **kwargs: Any) -> Any:
|
117
|
+
return self.run(*args, **kwargs)
|
129
118
|
|
130
119
|
|
131
120
|
def run(self, *args, **kwargs) -> Any:
|
132
121
|
"""
|
133
|
-
Use
|
134
|
-
When the tool has a func, execute the func and return any response from the func.
|
135
|
-
Else,
|
136
|
-
|
137
|
-
The response will be cascaded to the Task and stored in the TaskOutput.
|
122
|
+
Use tool
|
138
123
|
"""
|
124
|
+
from versionhq.tool.tool_handler import ToolHandler
|
125
|
+
|
126
|
+
if self.function:
|
127
|
+
return self.function(*args, **kwargs)
|
139
128
|
|
140
129
|
result = None
|
130
|
+
acceptable_args = self.args_schema.model_json_schema()["properties"].keys()
|
131
|
+
acceptable_kwargs = { k: v for k, v in kwargs.items() if k in acceptable_args }
|
132
|
+
tool_called = ToolSet(tool=self, kwargs=acceptable_kwargs)
|
133
|
+
|
134
|
+
if self.tool_handler:
|
135
|
+
if self.tool_handler.has_called_before(tool_called):
|
136
|
+
self.tool_handler.error = "Agent execution error"
|
141
137
|
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
138
|
+
elif self.tool_handler.cache:
|
139
|
+
result = self.tools_handler.cache.read(tool=tool_called.tool.name, input=tool_called.kwargs)
|
140
|
+
if result is None:
|
141
|
+
parsed_kwargs = self._parse_args(raw_args=acceptable_kwargs)
|
142
|
+
result = self.function(**parsed_kwargs) if self.function else None
|
146
143
|
|
147
144
|
else:
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
if self.tool_handler:
|
153
|
-
if self.tool_handler.has_called_before(tool_called):
|
154
|
-
self.tool_handler.error = "Agent execution error."
|
155
|
-
|
156
|
-
elif self.tool_handler.cache:
|
157
|
-
result = self.tools_handler.cache.read(
|
158
|
-
tool=tool_called.tool.name, input=tool_called.arguments
|
159
|
-
)
|
160
|
-
# from_cache = result is not None
|
161
|
-
if result is None:
|
162
|
-
result = self.invoke(input=arguments)
|
163
|
-
|
164
|
-
else:
|
165
|
-
from versionhq.tool.tool_handler import ToolHandler
|
166
|
-
|
167
|
-
tool_handler = ToolHandler(
|
168
|
-
last_used_tool=tool_called, cache=CacheHandler()
|
169
|
-
)
|
170
|
-
self.tool_handler = tool_handler
|
171
|
-
result = self.invoke(input=arguments)
|
145
|
+
tool_handler = ToolHandler(last_used_tool=tool_called, cache_handler=CacheHandler())
|
146
|
+
self.tool_handler = tool_handler
|
147
|
+
parsed_kwargs = self._parse_args(raw_args=acceptable_kwargs)
|
148
|
+
result = self.function(**parsed_kwargs) if self.function else None
|
172
149
|
|
173
150
|
return result
|
174
151
|
|
175
152
|
|
176
|
-
|
153
|
+
@property
|
154
|
+
def description(self) -> str:
|
155
|
+
args_schema = {
|
156
|
+
name: {
|
157
|
+
"description": field.description,
|
158
|
+
"type": self._get_arg_annotations(field.annotation),
|
159
|
+
}
|
160
|
+
for name, field in self.args_schema.model_fields.items()
|
161
|
+
}
|
162
|
+
|
163
|
+
return f"Tool Name: {self.name}\nTool Arguments: {args_schema}\nGoal: {self.goal}"
|
164
|
+
|
165
|
+
|
166
|
+
|
167
|
+
# @classmethod
|
168
|
+
# def from_composio(
|
169
|
+
# cls, func: Callable = None, tool_name: str = "Composio tool"
|
170
|
+
# ) -> "Tool":
|
171
|
+
# """
|
172
|
+
# Create Tool from the composio tool, ensuring the Tool instance has a func to be executed.
|
173
|
+
# Refer to the `args_schema` from the func signature if any. Else, create an `args_schema`.
|
174
|
+
# """
|
175
|
+
|
176
|
+
# if not func:
|
177
|
+
# raise ValueError("Params must have a callable 'function' attribute.")
|
178
|
+
|
179
|
+
# # args_schema = getattr(tool, "args_schema", None)
|
180
|
+
# args_fields = {}
|
181
|
+
# func_signature = signature(func)
|
182
|
+
# annotations = func_signature.parameters
|
183
|
+
# for name, param in annotations.items():
|
184
|
+
# if name != "self":
|
185
|
+
# param_annotation = (
|
186
|
+
# param.annotation if param.annotation != param.empty else Any
|
187
|
+
# )
|
188
|
+
# field_info = Field(default=..., description="")
|
189
|
+
# args_fields[name] = (param_annotation, field_info)
|
190
|
+
|
191
|
+
# args_schema = (
|
192
|
+
# create_model(f"{tool_name}Input", **args_fields)
|
193
|
+
# if args_fields
|
194
|
+
# else create_model(f"{tool_name}Input", __base__=BaseModel)
|
195
|
+
# )
|
196
|
+
|
197
|
+
# return cls(name=tool_name, func=func, args_schema=args_schema)
|
198
|
+
|
199
|
+
|
200
|
+
class ToolSet(BaseModel):
|
177
201
|
"""
|
178
202
|
Store the tool called and any kwargs used.
|
179
203
|
"""
|
180
|
-
|
181
|
-
|
182
|
-
arguments: Optional[Dict[str, Any]] = Field(..., description="kwargs passed to the tool")
|
204
|
+
tool: InstanceOf[Tool] | Any = Field(..., description="store the tool instance to be called.")
|
205
|
+
kwargs: Optional[Dict[str, Any]] = Field(..., description="kwargs passed to the tool")
|
183
206
|
|
184
207
|
|
185
|
-
class
|
186
|
-
tool: InstanceOf[Tool] = Field(..., description="store the tool instance to be called.")
|
187
|
-
|
208
|
+
class InstructorToolSet(BaseModel):
|
209
|
+
tool: InstanceOf[Tool] | Any = Field(..., description="store the tool instance to be called.")
|
210
|
+
kwargs: Optional[Dict[str, Any]] = Field(..., description="kwargs passed to the tool")
|
188
211
|
|
189
212
|
|
190
213
|
class CacheTool(BaseModel):
|
versionhq/tool/tool_handler.py
CHANGED
@@ -1,48 +1,45 @@
|
|
1
|
-
from typing import
|
1
|
+
from typing import Optional, Any
|
2
2
|
from pydantic import InstanceOf
|
3
|
-
|
3
|
+
|
4
|
+
from versionhq.tool.model import ToolSet, InstructorToolSet
|
4
5
|
from versionhq._utils.cache_handler import CacheHandler
|
5
6
|
|
6
7
|
|
7
8
|
class ToolHandler:
|
8
9
|
"""
|
9
|
-
Record the tool usage by
|
10
|
-
Use as a callback function.
|
10
|
+
Record the tool usage by ToolSet instance with cache and error recording.
|
11
11
|
"""
|
12
12
|
|
13
|
-
last_used_tool:
|
13
|
+
last_used_tool: InstanceOf[ToolSet] | InstanceOf[InstructorToolSet]
|
14
14
|
cache: Optional[CacheHandler]
|
15
15
|
error: Optional[str]
|
16
16
|
|
17
|
-
def __init__(
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
17
|
+
def __init__(
|
18
|
+
self,
|
19
|
+
last_used_tool: InstanceOf[ToolSet] | InstanceOf[InstructorToolSet] = None,
|
20
|
+
cache_handler: Optional[CacheHandler] = None
|
21
|
+
):
|
22
22
|
self.cache = cache_handler
|
23
|
-
self.last_used_tool =
|
23
|
+
self.last_used_tool = last_used_tool
|
24
|
+
|
24
25
|
|
25
26
|
def record_last_tool_used(
|
26
27
|
self,
|
27
|
-
last_used_tool: InstanceOf[
|
28
|
+
last_used_tool: InstanceOf[ToolSet] | InstanceOf[InstructorToolSet],
|
28
29
|
output: str,
|
29
30
|
should_cache: bool = True,
|
30
31
|
) -> Any:
|
32
|
+
|
31
33
|
self.last_used_tool = last_used_tool
|
32
34
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
output=output,
|
38
|
-
)
|
35
|
+
from versionhq.tool.model import CacheTool
|
36
|
+
if self.cache and should_cache and last_used_tool.tool.name != CacheTool().name:
|
37
|
+
self.cache.add(tool=last_used_tool.tool.name, input=last_used_tool.kwargs, output=output)
|
38
|
+
|
39
39
|
|
40
|
-
def has_called_before(self, tool_called:
|
40
|
+
def has_called_before(self, tool_called: ToolSet = None) -> bool:
|
41
41
|
if tool_called is None or not self.last_used_tool:
|
42
42
|
return False
|
43
43
|
|
44
44
|
if tool_called := self.last_used_tool:
|
45
|
-
return bool(
|
46
|
-
(tool_called.tool.name == self.last_used_tool.tool.name)
|
47
|
-
and (tool_called.arguments == self.last_used_tool.arguments)
|
48
|
-
)
|
45
|
+
return bool((tool_called.tool.name == self.last_used_tool.tool.name) and (tool_called.kwargs == self.last_used_tool.kwargs))
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: versionhq
|
3
|
-
Version: 1.1.
|
3
|
+
Version: 1.1.9.1
|
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
|
@@ -312,7 +312,7 @@ src/
|
|
312
312
|
4. Test the features using the `tests` directory.
|
313
313
|
|
314
314
|
- Add a test function to respective components in the `tests` directory.
|
315
|
-
- Add your `LITELLM_API_KEY`
|
315
|
+
- Add your `LITELLM_API_KEY`, `OPENAI_API_KEY`, `COMPOSIO_API_KEY`, `DEFAULT_USER_ID` to the Github `repository secrets` @ settings > secrets & variables > Actions.
|
316
316
|
- Run a test.
|
317
317
|
```
|
318
318
|
uv run pytest tests -vv
|
@@ -1,24 +1,24 @@
|
|
1
|
-
versionhq/__init__.py,sha256=
|
1
|
+
versionhq/__init__.py,sha256=HIOFr73z0KeJs9qFVvW1DjRFj7JyiJcRGGxylbQT_bQ,931
|
2
2
|
versionhq/_utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
3
|
-
versionhq/_utils/cache_handler.py,sha256=
|
3
|
+
versionhq/_utils/cache_handler.py,sha256=3-lw_5ZMWC8hnPAkSQULJ2V1FvZZ-wg9mQaUJGSOjI8,403
|
4
4
|
versionhq/_utils/i18n.py,sha256=TwA_PnYfDLA6VqlUDPuybdV9lgi3Frh_ASsb_X8jJo8,1483
|
5
5
|
versionhq/_utils/logger.py,sha256=lqRYH45KHMQ4mwE1woa5xNmngYu4O749AYECsnWWpmA,1851
|
6
6
|
versionhq/_utils/process_config.py,sha256=UqoWD5IR4VLxEDGxIyVUylw_ppXwk8Wx1ynVuD-pUSg,822
|
7
|
-
versionhq/_utils/rpm_controller.py,sha256=
|
8
|
-
versionhq/_utils/usage_metrics.py,sha256=
|
7
|
+
versionhq/_utils/rpm_controller.py,sha256=dUgFd6JtdjiLLTRmrjsBHdTaLn73XFuKpLbJh7thf2A,2289
|
8
|
+
versionhq/_utils/usage_metrics.py,sha256=hhq1OCW8Z4V93vwW2O2j528EyjOlF8wlTsX5IL-7asA,1106
|
9
9
|
versionhq/agent/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
10
|
-
versionhq/agent/model.py,sha256=
|
11
|
-
versionhq/agent/parser.py,sha256=
|
10
|
+
versionhq/agent/model.py,sha256=8QtZfbeys9cCujc4whKfXdoP0aQKMuvL2qN8WQmugew,19152
|
11
|
+
versionhq/agent/parser.py,sha256=Z_swUPO3piJQuYU8oVYwXWeR2zjmNb4PxbXZeR-GlIg,4694
|
12
12
|
versionhq/agent/TEMPLATES/Backstory.py,sha256=cdngBx1GEv7nroR46FEhnysnBJ9mEVL763_9np6Skkc,395
|
13
13
|
versionhq/agent/TEMPLATES/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
14
14
|
versionhq/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
15
15
|
versionhq/clients/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
16
16
|
versionhq/clients/customer/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
17
|
-
versionhq/clients/customer/model.py,sha256=
|
17
|
+
versionhq/clients/customer/model.py,sha256=ruxqSvjBHrSJnNq9Jj5Ko1CW6l8RLiPzhbC_F7tbOnM,1670
|
18
18
|
versionhq/clients/product/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
19
19
|
versionhq/clients/product/model.py,sha256=HxiSv8zq5L0H210jXWfjX_Yg1oyWhi2YASR68JEtmDY,2408
|
20
20
|
versionhq/clients/workflow/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
21
|
-
versionhq/clients/workflow/model.py,sha256=
|
21
|
+
versionhq/clients/workflow/model.py,sha256=LPet39sK7vUpBQ1mymdNX1xUPseGWv_5W7je_7sif_M,5883
|
22
22
|
versionhq/llm/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
23
23
|
versionhq/llm/llm_vars.py,sha256=YZoXqFBW7XpclUZ14_AAz7WOjoyCXnGcI959GSpX2q0,5343
|
24
24
|
versionhq/llm/model.py,sha256=mXzSuf1s6MebGT7_yqgNppde0NIlAF8bjIXAp2MZ9Uw,8247
|
@@ -27,16 +27,17 @@ versionhq/storage/task_output_storage.py,sha256=xoBJHeqUyQt6iJoR1WQTghP-fyxXL66q
|
|
27
27
|
versionhq/task/__init__.py,sha256=g4mCATnn1mUXxsfQ5p6IpPawr8O421wVIT8kMKEcxQw,180
|
28
28
|
versionhq/task/formatter.py,sha256=N8Kmk9vtrMtBdgJ8J7RmlKNMdZWSmV8O1bDexmCWgU0,643
|
29
29
|
versionhq/task/log_handler.py,sha256=KJRrcNZgFSKhlNzvtYFnvtp6xukaF1s7ifX9u4zWrN8,1683
|
30
|
-
versionhq/task/model.py,sha256=
|
30
|
+
versionhq/task/model.py,sha256=EbgYHLNq8l1zfRDnF-yEcuSZ0aNvzbRmHYgfVyJq84c,19910
|
31
31
|
versionhq/team/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
32
|
-
versionhq/team/model.py,sha256=
|
32
|
+
versionhq/team/model.py,sha256=E52OUVzUtvR--51SFRJos3JdYKri1t2jbvvzoOvShQc,20181
|
33
33
|
versionhq/team/team_planner.py,sha256=uzX2yed7A7gNSs6qH5jIq2zXMVF5BwQQ4HPATsB9DSQ,3675
|
34
|
-
versionhq/tool/__init__.py,sha256=
|
35
|
-
versionhq/tool/
|
36
|
-
versionhq/tool/
|
37
|
-
versionhq/tool/
|
38
|
-
versionhq
|
39
|
-
versionhq-1.1.
|
40
|
-
versionhq-1.1.
|
41
|
-
versionhq-1.1.
|
42
|
-
versionhq-1.1.
|
34
|
+
versionhq/tool/__init__.py,sha256=oU2Y84b7vywWq1xmFaBdXdH8Y9lGv7dmk2LEcj4dL-s,1692
|
35
|
+
versionhq/tool/composio.py,sha256=e-Vfr-eFm0ipiOerB_zAC1Sl90A39OD_k4QqgszWXWQ,5779
|
36
|
+
versionhq/tool/decorator.py,sha256=W_WjzZy8y43AoiFjHLPUQfNipmpOPe-wQknCWloPwmY,1195
|
37
|
+
versionhq/tool/model.py,sha256=8A1x8gEdTuP5teUf6o3VqJhrPI5m-XBoBXxgHiWCKcI,7499
|
38
|
+
versionhq/tool/tool_handler.py,sha256=rmm8snegwug4jl0Sbi_CteFajkbPAZ5koTQWDMwcIrQ,1510
|
39
|
+
versionhq-1.1.9.1.dist-info/LICENSE,sha256=7CCXuMrAjPVsUvZrsBq9DsxI2rLDUSYXR_qj4yO_ZII,1077
|
40
|
+
versionhq-1.1.9.1.dist-info/METADATA,sha256=-mFMCnD5q2XGVgaNyE8ddBpX8tgAR4sqlJ-Cmm0hBQA,15955
|
41
|
+
versionhq-1.1.9.1.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
|
42
|
+
versionhq-1.1.9.1.dist-info/top_level.txt,sha256=DClQwxDWqIUGeRJkA8vBlgeNsYZs4_nJWMonzFt5Wj0,10
|
43
|
+
versionhq-1.1.9.1.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|