versionhq 1.1.4.4__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 +33 -0
- versionhq/_utils/__init__.py +0 -0
- versionhq/_utils/cache_handler.py +13 -0
- versionhq/_utils/i18n.py +48 -0
- versionhq/_utils/logger.py +57 -0
- versionhq/_utils/process_config.py +28 -0
- versionhq/_utils/rpm_controller.py +73 -0
- versionhq/_utils/usage_metrics.py +31 -0
- versionhq/agent/__init__.py +0 -0
- versionhq/agent/model.py +472 -0
- versionhq/agent/parser.py +148 -0
- versionhq/cli/__init__.py +0 -0
- versionhq/clients/__init__.py +0 -0
- versionhq/clients/customer/__init__.py +0 -0
- versionhq/clients/customer/model.py +57 -0
- versionhq/clients/product/__init__.py +0 -0
- versionhq/clients/product/model.py +74 -0
- versionhq/clients/workflow/__init__.py +0 -0
- versionhq/clients/workflow/model.py +174 -0
- versionhq/llm/__init__.py +0 -0
- versionhq/llm/llm_vars.py +173 -0
- versionhq/llm/model.py +245 -0
- versionhq/task/__init__.py +9 -0
- versionhq/task/formatter.py +22 -0
- versionhq/task/model.py +430 -0
- versionhq/team/__init__.py +0 -0
- versionhq/team/model.py +585 -0
- versionhq/team/team_planner.py +55 -0
- versionhq/tool/__init__.py +0 -0
- versionhq/tool/composio.py +102 -0
- versionhq/tool/decorator.py +40 -0
- versionhq/tool/model.py +220 -0
- versionhq/tool/tool_handler.py +47 -0
- versionhq-1.1.4.4.dist-info/LICENSE +21 -0
- versionhq-1.1.4.4.dist-info/METADATA +353 -0
- versionhq-1.1.4.4.dist-info/RECORD +38 -0
- versionhq-1.1.4.4.dist-info/WHEEL +5 -0
- versionhq-1.1.4.4.dist-info/top_level.txt +1 -0
@@ -0,0 +1,102 @@
|
|
1
|
+
import os
|
2
|
+
from dotenv import load_dotenv
|
3
|
+
from typing import Any, Callable, Type, get_args, get_origin
|
4
|
+
from composio import ComposioToolSet, Action, App
|
5
|
+
|
6
|
+
|
7
|
+
load_dotenv(override=True)
|
8
|
+
DEFAULT_REDIRECT_URL = os.environ.get("DEFAULT_REDIRECT_URL")
|
9
|
+
DEFAULT_APP_NAME = "hubspot"
|
10
|
+
DEFAULT_USER_ID = os.environ.get("DEFAULT_USER_ID")
|
11
|
+
|
12
|
+
|
13
|
+
def connect(app_name: str = DEFAULT_APP_NAME, user_id: str = DEFAULT_USER_ID, redirect_url: str = DEFAULT_REDIRECT_URL, *args, **kwargs):
|
14
|
+
"""
|
15
|
+
Connect with the data pipelines or destination services.
|
16
|
+
"""
|
17
|
+
|
18
|
+
composio_toolset = ComposioToolSet(api_key=os.environ.get("COMPOSIO_API_KEY"))
|
19
|
+
|
20
|
+
|
21
|
+
if not user_id:
|
22
|
+
return None
|
23
|
+
|
24
|
+
auth_scheme = "OAUTH2"
|
25
|
+
connection_request = composio_toolset.initiate_connection(
|
26
|
+
app=app_name,
|
27
|
+
redirect_url = redirect_url, # user comes here after oauth flow
|
28
|
+
entity_id=user_id,
|
29
|
+
auth_scheme=auth_scheme,
|
30
|
+
)
|
31
|
+
|
32
|
+
print(connection_request.connectedAccountId,connection_request.connectionStatus)
|
33
|
+
print(connection_request.redirectUrl)
|
34
|
+
|
35
|
+
|
36
|
+
|
37
|
+
# connection_request_id = "connection_request_id" # replace connection_request_id from earlier response
|
38
|
+
# # validate the connection is active
|
39
|
+
# connected_account = composio_toolset.get_connected_account(id=connection_request_id)
|
40
|
+
# print(connected_account.status) # should be
|
41
|
+
|
42
|
+
|
43
|
+
# @action(toolname="hubspot")
|
44
|
+
# def deploy_on_hubspot(param1: str, param2: str, execute_request: Callable) -> str:
|
45
|
+
# """
|
46
|
+
# Deploy the messaging workflow on the third-party service using Composio.
|
47
|
+
# List of the services: https://composio.dev/tools?category=marketing
|
48
|
+
|
49
|
+
# my custom action description which will be passed to llm
|
50
|
+
|
51
|
+
# :param param1: param1 description which will be passed to llm
|
52
|
+
# :param param2: param2 description which will be passed to llm
|
53
|
+
# :return info: return description
|
54
|
+
# """
|
55
|
+
|
56
|
+
# response = execute_request(
|
57
|
+
# "/my_action_endpoint",
|
58
|
+
# "GET",
|
59
|
+
# {} # body can be added here
|
60
|
+
# ) # execute requests by appending credentials to the request
|
61
|
+
# return str(response) # complete auth dict is available for local use if needed
|
62
|
+
|
63
|
+
|
64
|
+
# toolset = ComposioToolSet(entity_id=D)
|
65
|
+
# tools = composio_toolset.get_tools(actions=[deploy_on_hubspot])
|
66
|
+
|
67
|
+
|
68
|
+
# rag_tools = composio_toolset.get_tools(
|
69
|
+
# apps=[App.RAGTOOL],
|
70
|
+
# actions=[
|
71
|
+
# Action.FILETOOL_LIST_FILES,
|
72
|
+
# Action.FILETOOL_CHANGE_WORKING_DIRECTORY,
|
73
|
+
# Action.FILETOOL_FIND_FILE,
|
74
|
+
# ],
|
75
|
+
# )
|
76
|
+
|
77
|
+
# rag_query_tools = composio_toolset.get_tools(
|
78
|
+
# apps=[App.RAGTOOL],
|
79
|
+
# )
|
80
|
+
|
81
|
+
# # can pass multiple actions
|
82
|
+
# tools = composio_toolset.get_tools(
|
83
|
+
# actions=[Action.GITHUB_CREATE_AN_ISSUE]
|
84
|
+
# )
|
85
|
+
|
86
|
+
# rag_tools = composio_toolset.get_tools(
|
87
|
+
# apps=[App.RAGTOOL],
|
88
|
+
# actions=[
|
89
|
+
# Action.FILETOOL_LIST_FILES,
|
90
|
+
# Action.FILETOOL_CHANGE_WORKING_DIRECTORY,
|
91
|
+
# Action.FILETOOL_FIND_FILE,
|
92
|
+
# ],
|
93
|
+
# )
|
94
|
+
|
95
|
+
# rag_query_tools = composio_toolset.get_tools(
|
96
|
+
# apps=[App.RAGTOOL],
|
97
|
+
# )
|
98
|
+
|
99
|
+
|
100
|
+
if __name__ == "__main__":
|
101
|
+
connect()
|
102
|
+
|
@@ -0,0 +1,40 @@
|
|
1
|
+
from typing import Callable
|
2
|
+
from pydantic import BaseModel
|
3
|
+
from versionhq.tool.model import Tool
|
4
|
+
|
5
|
+
|
6
|
+
def tool(*args):
|
7
|
+
"""
|
8
|
+
Decorator to create a tool from a function.
|
9
|
+
"""
|
10
|
+
|
11
|
+
def create_tool(tool_name: str) -> Callable:
|
12
|
+
|
13
|
+
def _make_tool(f: Callable) -> Tool:
|
14
|
+
if f.__doc__ is None:
|
15
|
+
raise ValueError("Function must have a docstring")
|
16
|
+
if f.__annotations__ is None:
|
17
|
+
raise ValueError("Function must have type annotations")
|
18
|
+
|
19
|
+
class_name = "".join(tool_name.split()).title()
|
20
|
+
args_schema = type(
|
21
|
+
class_name,
|
22
|
+
(BaseModel,),
|
23
|
+
{
|
24
|
+
"__annotations__": {
|
25
|
+
k: v for k, v in f.__annotations__.items() if k != "return"
|
26
|
+
},
|
27
|
+
},
|
28
|
+
)
|
29
|
+
return Tool(name=tool_name, func=f, args_schema=args_schema)
|
30
|
+
|
31
|
+
return _make_tool
|
32
|
+
|
33
|
+
if len(args) == 1 and callable(args[0]):
|
34
|
+
return create_tool(args[0].__name__)(args[0])
|
35
|
+
|
36
|
+
elif len(args) == 1 and isinstance(args[0], str):
|
37
|
+
return create_tool(args[0])
|
38
|
+
|
39
|
+
else:
|
40
|
+
raise ValueError("Invalid arguments")
|
versionhq/tool/model.py
ADDED
@@ -0,0 +1,220 @@
|
|
1
|
+
from abc import ABC
|
2
|
+
from inspect import signature
|
3
|
+
from typing import Any, Dict, Callable, Type, Optional, get_args, get_origin
|
4
|
+
from pydantic import (
|
5
|
+
InstanceOf,
|
6
|
+
BaseModel,
|
7
|
+
ConfigDict,
|
8
|
+
Field,
|
9
|
+
create_model,
|
10
|
+
field_validator,
|
11
|
+
model_validator,
|
12
|
+
)
|
13
|
+
|
14
|
+
from versionhq._utils.cache_handler import CacheHandler
|
15
|
+
|
16
|
+
|
17
|
+
class Tool(ABC, BaseModel):
|
18
|
+
"""
|
19
|
+
The function that will be executed when the tool is called.
|
20
|
+
"""
|
21
|
+
|
22
|
+
class ArgsSchema(BaseModel):
|
23
|
+
pass
|
24
|
+
|
25
|
+
model_config = ConfigDict()
|
26
|
+
name: str = Field(default=None)
|
27
|
+
func: Callable = Field(default=None)
|
28
|
+
cache_function: Callable = lambda _args=None, _result=None: True
|
29
|
+
args_schema: Type[BaseModel] = Field(default_factory=ArgsSchema)
|
30
|
+
tool_handler: Optional[Dict[str, Any]] = Field(
|
31
|
+
default=None,
|
32
|
+
description="store tool_handler to record the usage of this tool. to avoid circular import, set as Dict format",
|
33
|
+
)
|
34
|
+
|
35
|
+
@property
|
36
|
+
def description(self):
|
37
|
+
args_schema = {
|
38
|
+
name: {
|
39
|
+
"description": field.description,
|
40
|
+
"type": Tool._get_arg_annotations(field.annotation),
|
41
|
+
}
|
42
|
+
for name, field in self.args_schema.model_fields.items()
|
43
|
+
}
|
44
|
+
return f"Tool Name: {self.name}\nTool Arguments: {args_schema}\nTool Description: {self.description}"
|
45
|
+
|
46
|
+
@field_validator("args_schema", mode="before")
|
47
|
+
@classmethod
|
48
|
+
def _default_args_schema(cls, v: Type[BaseModel]) -> Type[BaseModel]:
|
49
|
+
if not isinstance(v, cls.ArgsSchema):
|
50
|
+
return v
|
51
|
+
|
52
|
+
return type(
|
53
|
+
f"{cls.__name__}Schema",
|
54
|
+
(BaseModel,),
|
55
|
+
{
|
56
|
+
"__annotations__": {
|
57
|
+
k: v for k, v in cls._run.__annotations__.items() if k != "return"
|
58
|
+
},
|
59
|
+
},
|
60
|
+
)
|
61
|
+
|
62
|
+
@model_validator(mode="after")
|
63
|
+
def set_up_tool_handler_instance(self):
|
64
|
+
from versionhq.tool.tool_handler import ToolHandler
|
65
|
+
|
66
|
+
if self.tool_handler:
|
67
|
+
ToolHandler(**self.tool_handler)
|
68
|
+
|
69
|
+
return self
|
70
|
+
|
71
|
+
@staticmethod
|
72
|
+
def _get_arg_annotations(annotation: type[Any] | None) -> str:
|
73
|
+
if annotation is None:
|
74
|
+
return "None"
|
75
|
+
|
76
|
+
origin = get_origin(annotation)
|
77
|
+
args = get_args(annotation)
|
78
|
+
|
79
|
+
if origin is None:
|
80
|
+
return (
|
81
|
+
annotation.__name__
|
82
|
+
if hasattr(annotation, "__name__")
|
83
|
+
else str(annotation)
|
84
|
+
)
|
85
|
+
|
86
|
+
if args:
|
87
|
+
args_str = ", ".join(Tool._get_arg_annotations(arg) for arg in args)
|
88
|
+
return f"{origin.__name__}[{args_str}]"
|
89
|
+
|
90
|
+
return origin.__name__
|
91
|
+
|
92
|
+
def _set_args_schema(self):
|
93
|
+
if self.args_schema is None:
|
94
|
+
class_name = f"{self.__class__.__name__}Schema"
|
95
|
+
|
96
|
+
self.args_schema = type(
|
97
|
+
class_name,
|
98
|
+
(BaseModel,),
|
99
|
+
{
|
100
|
+
"__annotations__": {
|
101
|
+
k: v
|
102
|
+
for k, v in self._run.__annotations__.items()
|
103
|
+
if k != "return"
|
104
|
+
},
|
105
|
+
},
|
106
|
+
)
|
107
|
+
|
108
|
+
@classmethod
|
109
|
+
def from_composio(
|
110
|
+
cls, func: Callable = None, tool_name: str = "Composio tool"
|
111
|
+
) -> "Tool":
|
112
|
+
"""
|
113
|
+
Create a Pydantic BaseModel instance from Composio tools, ensuring the Tool instance has a func to be executed.
|
114
|
+
Refer to the `args_schema` from the func signature if any. Else, create an `args_schema`.
|
115
|
+
"""
|
116
|
+
|
117
|
+
if not func:
|
118
|
+
raise ValueError("Params must have a callable 'func' attribute.")
|
119
|
+
|
120
|
+
# args_schema = getattr(tool, "args_schema", None)
|
121
|
+
args_fields = {}
|
122
|
+
func_signature = signature(func)
|
123
|
+
annotations = func_signature.parameters
|
124
|
+
for name, param in annotations.items():
|
125
|
+
if name != "self":
|
126
|
+
param_annotation = (
|
127
|
+
param.annotation if param.annotation != param.empty else Any
|
128
|
+
)
|
129
|
+
field_info = Field(default=..., description="")
|
130
|
+
args_fields[name] = (param_annotation, field_info)
|
131
|
+
|
132
|
+
args_schema = (
|
133
|
+
create_model(f"{tool_name}Input", **args_fields)
|
134
|
+
if args_fields
|
135
|
+
else create_model(f"{tool_name}Input", __base__=BaseModel)
|
136
|
+
)
|
137
|
+
|
138
|
+
return cls(name=tool_name, func=func, args_schema=args_schema)
|
139
|
+
|
140
|
+
def run(self, *args, **kwargs) -> Any:
|
141
|
+
"""
|
142
|
+
Use the tool.
|
143
|
+
When the tool has a func, execute the func and return any response from the func.
|
144
|
+
Else,
|
145
|
+
|
146
|
+
The response will be cascaded to the Task and stored in the TaskOutput.
|
147
|
+
"""
|
148
|
+
|
149
|
+
print(f"Using the tool: {self.name}")
|
150
|
+
result = None
|
151
|
+
|
152
|
+
if self.func is not None:
|
153
|
+
result = self.func(
|
154
|
+
*args, **kwargs
|
155
|
+
) #! REFINEME - format - json dict, pydantic, raw
|
156
|
+
|
157
|
+
else:
|
158
|
+
acceptable_args = self.args_schema.model_json_schema()["properties"].keys()
|
159
|
+
arguments = {k: v for k, v in kwargs.items() if k in acceptable_args}
|
160
|
+
tool_called = ToolCalled(tool=self, arguments=arguments)
|
161
|
+
|
162
|
+
if self.tool_handler:
|
163
|
+
if self.tool_handler.has_called_before(tool_called):
|
164
|
+
self.tool_handler.error = "Agent execution error."
|
165
|
+
|
166
|
+
elif self.tool_handler.cache:
|
167
|
+
result = self.tools_handler.cache.read(
|
168
|
+
tool=tool_called.tool.name, input=tool_called.arguments
|
169
|
+
)
|
170
|
+
# from_cache = result is not None
|
171
|
+
if result is None:
|
172
|
+
result = self.invoke(input=arguments)
|
173
|
+
|
174
|
+
else:
|
175
|
+
from versionhq.tool.tool_handler import ToolHandler
|
176
|
+
|
177
|
+
tool_handler = ToolHandler(
|
178
|
+
last_used_tool=tool_called, cache=CacheHandler()
|
179
|
+
)
|
180
|
+
self.tool_handler = tool_handler
|
181
|
+
result = self.invoke(input=arguments)
|
182
|
+
|
183
|
+
return result
|
184
|
+
|
185
|
+
|
186
|
+
class ToolCalled(BaseModel):
|
187
|
+
"""
|
188
|
+
Store the tool called and any kwargs used.
|
189
|
+
"""
|
190
|
+
|
191
|
+
tool: InstanceOf[Tool] = Field(
|
192
|
+
..., description="store the tool instance to be called."
|
193
|
+
)
|
194
|
+
arguments: Optional[Dict[str, Any]] = Field(
|
195
|
+
..., description="kwargs passed to the tool"
|
196
|
+
)
|
197
|
+
|
198
|
+
|
199
|
+
class InstructorToolCalled(BaseModel):
|
200
|
+
tool: InstanceOf[Tool] = Field(
|
201
|
+
..., description="store the tool instance to be called."
|
202
|
+
)
|
203
|
+
arguments: Optional[Dict[str, Any]] = Field(
|
204
|
+
..., description="kwargs passed to the tool"
|
205
|
+
)
|
206
|
+
|
207
|
+
|
208
|
+
class CacheTool(BaseModel):
|
209
|
+
"""
|
210
|
+
Default tools to hit the cache.
|
211
|
+
"""
|
212
|
+
|
213
|
+
name: str = "Hit Cache"
|
214
|
+
cache_handler: CacheHandler = Field(default_factory=CacheHandler)
|
215
|
+
|
216
|
+
def hit_cache(self, key):
|
217
|
+
split = key.split("tool:")
|
218
|
+
tool = split[1].split("|input:")[0].strip()
|
219
|
+
tool_input = split[1].split("|input:")[1].strip()
|
220
|
+
return self.cache_handler.read(tool, tool_input)
|
@@ -0,0 +1,47 @@
|
|
1
|
+
from typing import Any, Optional, Union
|
2
|
+
from versionhq.tool.model import ToolCalled, InstructorToolCalled, CacheTool
|
3
|
+
from versionhq._utils.cache_handler import CacheHandler
|
4
|
+
|
5
|
+
|
6
|
+
class ToolHandler:
|
7
|
+
"""
|
8
|
+
Record the tool usage by ToolCalled instance with cache and error recording.
|
9
|
+
Use as a callback function.
|
10
|
+
"""
|
11
|
+
|
12
|
+
last_used_tool: ToolCalled = {}
|
13
|
+
cache: Optional[CacheHandler]
|
14
|
+
error: Optional[str]
|
15
|
+
|
16
|
+
def __init__(self, cache_handler: Optional[CacheHandler] = None):
|
17
|
+
"""
|
18
|
+
Initialize the callback handler.
|
19
|
+
"""
|
20
|
+
|
21
|
+
self.cache = cache_handler
|
22
|
+
self.last_used_tool = {}
|
23
|
+
|
24
|
+
def record_last_tool_used(
|
25
|
+
self,
|
26
|
+
last_used_tool: Union[ToolCalled, InstructorToolCalled],
|
27
|
+
output: str,
|
28
|
+
should_cache: bool = True,
|
29
|
+
) -> Any:
|
30
|
+
self.last_used_tool = last_used_tool
|
31
|
+
|
32
|
+
if self.cache and should_cache and last_used_tool.tool_name != CacheTool().name:
|
33
|
+
self.cache.add(
|
34
|
+
last_used_tool.tool_name,
|
35
|
+
input.last_used_tool.arguments,
|
36
|
+
output=output,
|
37
|
+
)
|
38
|
+
|
39
|
+
def has_called_before(self, tool_called: ToolCalled = None) -> bool:
|
40
|
+
if tool_called is None or not self.last_used_tool:
|
41
|
+
return False
|
42
|
+
|
43
|
+
if tool_called := self.last_used_tool:
|
44
|
+
return bool(
|
45
|
+
(tool_called.tool.name == self.last_used_tool.tool.name)
|
46
|
+
and (tool_called.arguments == self.last_used_tool.arguments)
|
47
|
+
)
|
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2024 Version IO Sdn. Bhd.
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|