versionhq 1.1.8__py3-none-any.whl → 1.1.9.0__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.
@@ -0,0 +1,53 @@
1
+ from enum import Enum
2
+
3
+ DEFAULT_AUTH_SCHEME = "OAUTH2"
4
+
5
+ class ComposioAuthScheme(str, Enum):
6
+ OAUTH2 = "OAUTH2"
7
+ BEARER_TOKEN = "BEARER_TOKEN"
8
+ API_KEY = "API_KEY"
9
+
10
+
11
+ class ComposioAppName(str, Enum):
12
+ """
13
+ Enum to store app names that we can connect via Composio as data pipelines or destination services.
14
+ """
15
+
16
+ SALESFORCE = "salesforce"
17
+ AIRTABLE = "airtable"
18
+ MAILCHIMP = "mailchimp"
19
+ HUBSPOT = "hubspot"
20
+ KLAVIYO = "klaviyo"
21
+ GOOGLESHEET = "googlesheets"
22
+ GMAIL = "gmail"
23
+ FACEBOOK = "facebook"
24
+ TWITTER = "twitter"
25
+ TWITTER_MEDIA = "twitter_media"
26
+ LINKEDIN = "linkedin"
27
+
28
+
29
+ composio_app_set = [
30
+ (ComposioAppName.SALESFORCE, ComposioAuthScheme.OAUTH2),
31
+ (ComposioAppName.AIRTABLE, ComposioAuthScheme.OAUTH2, ComposioAuthScheme.API_KEY, ComposioAuthScheme.BEARER_TOKEN),
32
+ (ComposioAppName.MAILCHIMP, ComposioAuthScheme.OAUTH2),
33
+ (ComposioAppName.HUBSPOT, ComposioAuthScheme.OAUTH2, ComposioAuthScheme.BEARER_TOKEN),
34
+ (ComposioAppName.KLAVIYO, ComposioAuthScheme.OAUTH2, ComposioAuthScheme.API_KEY),
35
+ (ComposioAppName.GOOGLESHEET, ComposioAuthScheme.OAUTH2),
36
+ (ComposioAppName.GMAIL, ComposioAuthScheme.OAUTH2, ComposioAuthScheme.BEARER_TOKEN),
37
+ (ComposioAppName.TWITTER, ComposioAuthScheme.OAUTH2),
38
+ (ComposioAppName.TWITTER_MEDIA, ComposioAuthScheme.OAUTH2),
39
+ (ComposioAppName.FACEBOOK, ComposioAuthScheme.OAUTH2),
40
+ (ComposioAppName.LINKEDIN, ComposioAuthScheme.OAUTH2),
41
+ ]
42
+
43
+ class COMPOSIO_STATUS(str, Enum):
44
+ INITIATED = "initiated"
45
+ ACTIVE = "active"
46
+ FAILED = "failed"
47
+
48
+
49
+
50
+ class ComposioAction(str, Enum):
51
+ """
52
+ Enum to store composio's action that can be called via `Actions.xxx`
53
+ """
@@ -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
@@ -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, func=f, args_schema=args_schema)
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,42 +1,26 @@
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 pydantic import InstanceOf, BaseModel, ConfigDict, Field, create_model, field_validator, model_validator
4
+ from pydantic import InstanceOf, BaseModel, ConfigDict, Field, field_validator, model_validator
5
5
 
6
6
  from versionhq._utils.cache_handler import CacheHandler
7
7
 
8
8
 
9
- class Tool(ABC, BaseModel):
10
- """
11
- The function that will be executed when the tool is called.
12
- """
9
+ class BaseTool(ABC, BaseModel):
13
10
 
14
- class ArgsSchema(BaseModel):
11
+ class _ArgsSchemaPlaceholder(BaseModel):
15
12
  pass
16
13
 
17
- model_config = ConfigDict()
18
14
  name: str = Field(default=None)
19
- func: Callable = Field(default=None)
15
+ goal: str = Field(default=None)
16
+ args_schema: Type[BaseModel] = Field(default_factory=_ArgsSchemaPlaceholder)
20
17
  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}"
34
18
 
35
19
 
36
20
  @field_validator("args_schema", mode="before")
37
21
  @classmethod
38
22
  def _default_args_schema(cls, v: Type[BaseModel]) -> Type[BaseModel]:
39
- if not isinstance(v, cls.ArgsSchema):
23
+ if not isinstance(v, cls._ArgsSchemaPlaceholder):
40
24
  return v
41
25
 
42
26
  return type(
@@ -49,14 +33,15 @@ class Tool(ABC, BaseModel):
49
33
  },
50
34
  )
51
35
 
52
- @model_validator(mode="after")
53
- def set_up_tool_handler_instance(self):
54
- from versionhq.tool.tool_handler import ToolHandler
55
36
 
56
- if self.tool_handler:
57
- ToolHandler(**self.tool_handler)
37
+ @abstractmethod
38
+ def _run(self, *args: Any, **kwargs: Any,) -> Any:
39
+ """any handling"""
40
+
41
+
42
+ def run(self, *args: Any, **kwargs: Any) -> Any:
43
+ return self._run(*args, **kwargs)
58
44
 
59
- return self
60
45
 
61
46
  @staticmethod
62
47
  def _get_arg_annotations(annotation: type[Any] | None) -> str:
@@ -67,11 +52,7 @@ class Tool(ABC, BaseModel):
67
52
  args = get_args(annotation)
68
53
 
69
54
  if origin is None:
70
- return (
71
- annotation.__name__
72
- if hasattr(annotation, "__name__")
73
- else str(annotation)
74
- )
55
+ return annotation.__name__ if hasattr(annotation, "__name__") else str(annotation)
75
56
 
76
57
  if args:
77
58
  args_str = ", ".join(Tool._get_arg_annotations(arg) for arg in args)
@@ -79,113 +60,157 @@ class Tool(ABC, BaseModel):
79
60
 
80
61
  return origin.__name__
81
62
 
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
63
 
98
- @classmethod
99
- def from_composio(
100
- cls, func: Callable = None, tool_name: str = "Composio tool"
101
- ) -> "Tool":
64
+ def _parse_args(self, raw_args: str | dict) -> dict:
102
65
  """
103
- Create a Pydantic BaseModel instance from Composio tools, ensuring the Tool instance has a func to be executed.
104
- Refer to the `args_schema` from the func signature if any. Else, create an `args_schema`.
66
+ Parse and validate the input arguments against the schema
105
67
  """
68
+ if isinstance(raw_args, str):
69
+ try:
70
+ import json
71
+ raw_args = json.loads(raw_args)
72
+ except json.JSONDecodeError as e:
73
+ raise ValueError(f"Failed to parse arguments as JSON: {e}")
74
+
75
+ try:
76
+ validated_args = self.args_schema.model_validate(raw_args)
77
+ return validated_args.model_dump()
106
78
 
107
- if not func:
108
- raise ValueError("Params must have a callable 'func' attribute.")
109
-
110
- # args_schema = getattr(tool, "args_schema", None)
111
- args_fields = {}
112
- func_signature = signature(func)
113
- annotations = func_signature.parameters
114
- for name, param in annotations.items():
115
- if name != "self":
116
- param_annotation = (
117
- param.annotation if param.annotation != param.empty else Any
118
- )
119
- field_info = Field(default=..., description="")
120
- args_fields[name] = (param_annotation, field_info)
121
-
122
- args_schema = (
123
- create_model(f"{tool_name}Input", **args_fields)
124
- if args_fields
125
- else create_model(f"{tool_name}Input", __base__=BaseModel)
79
+ except Exception as e:
80
+ raise ValueError(f"Arguments validation failed: {e}")
81
+
82
+
83
+ def _set_args_schema_from_func(self):
84
+ class_name = f"{self.__class__.__name__}Schema"
85
+ self.args_schema = type(
86
+ class_name,
87
+ (BaseModel,),
88
+ { "__annotations__": { k: v for k, v in self._run.__annotations__.items() if k != "return" } },
126
89
  )
90
+ return self
127
91
 
128
- return cls(name=tool_name, func=func, args_schema=args_schema)
92
+
93
+ @property
94
+ def description(self) -> str:
95
+ args_schema = {
96
+ name: {
97
+ "description": field.description,
98
+ "type": self._get_arg_annotations(field.annotation),
99
+ }
100
+ for name, field in self.args_schema.model_fields.items()
101
+ }
102
+
103
+ return f"Tool Name: {self.name}\nTool Arguments: {args_schema}\nGoal: {self.goal}"
104
+
105
+
106
+
107
+ class Tool(BaseTool):
108
+
109
+ function: Callable = Field(default=None)
110
+ tool_handler: Optional[Dict[str, Any]] = Field(
111
+ default=None,
112
+ description="store tool_handler to record the usage of this tool. to avoid circular import, set as Dict format",
113
+ )
114
+
115
+ @model_validator(mode="after")
116
+ def set_up_tool_handler(self):
117
+ from versionhq.tool.tool_handler import ToolHandler
118
+
119
+ if self.tool_handler:
120
+ ToolHandler(**self.tool_handler)
121
+ return self
122
+
123
+ @model_validator(mode="after")
124
+ def set_up_function(self):
125
+ if self.function is None:
126
+ self.function = self._run
127
+ self._set_args_schema_from_func()
128
+ return self
129
+
130
+
131
+ def _run(self, *args: Any, **kwargs: Any) -> Any:
132
+ return self.function(*args, **kwargs)
129
133
 
130
134
 
131
135
  def run(self, *args, **kwargs) -> Any:
132
136
  """
133
- Use the tool.
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.
137
+ Use tool
138
138
  """
139
+ from versionhq.tool.tool_handler import ToolHandler
139
140
 
140
- print(f"Using the tool: {self.name}")
141
141
  result = None
142
142
 
143
- if self.func is not None:
144
- result = self.func(
145
- *args, **kwargs
146
- ) #! REFINEME - format - json dict, pydantic, raw
143
+ if self.function is not None:
144
+ result = self.function(*args, **kwargs)
147
145
 
148
146
  else:
149
147
  acceptable_args = self.args_schema.model_json_schema()["properties"].keys()
150
- arguments = {k: v for k, v in kwargs.items() if k in acceptable_args}
151
- tool_called = ToolCalled(tool=self, arguments=arguments)
148
+ acceptable_kwargs = { k: v for k, v in kwargs.items() if k in acceptable_args }
149
+ tool_called = ToolSet(tool=self, kwargs=acceptable_kwargs)
152
150
 
153
151
  if self.tool_handler:
154
152
  if self.tool_handler.has_called_before(tool_called):
155
- self.tool_handler.error = "Agent execution error."
153
+ self.tool_handler.error = "Agent execution error"
156
154
 
157
155
  elif self.tool_handler.cache:
158
- result = self.tools_handler.cache.read(
159
- tool=tool_called.tool.name, input=tool_called.arguments
160
- )
161
- # from_cache = result is not None
156
+ result = self.tools_handler.cache.read(tool=tool_called.tool.name, input=tool_called.kwargs)
162
157
  if result is None:
163
- result = self.invoke(input=arguments)
158
+ parsed_kwargs = self._parse_args(input=acceptable_kwargs)
159
+ result = self.function(**parsed_kwargs)
164
160
 
165
161
  else:
166
- from versionhq.tool.tool_handler import ToolHandler
167
-
168
- tool_handler = ToolHandler(
169
- last_used_tool=tool_called, cache=CacheHandler()
170
- )
162
+ tool_handler = ToolHandler(last_used_tool=tool_called, cache_handler=CacheHandler())
171
163
  self.tool_handler = tool_handler
172
- result = self.invoke(input=arguments)
164
+ parsed_kwargs = self._parse_args(input=acceptable_kwargs)
165
+ result = self.function(**parsed_kwargs)
173
166
 
174
167
  return result
175
168
 
176
169
 
177
- class ToolCalled(BaseModel):
170
+ # @classmethod
171
+ # def from_composio(
172
+ # cls, func: Callable = None, tool_name: str = "Composio tool"
173
+ # ) -> "Tool":
174
+ # """
175
+ # Create Tool from the composio tool, ensuring the Tool instance has a func to be executed.
176
+ # Refer to the `args_schema` from the func signature if any. Else, create an `args_schema`.
177
+ # """
178
+
179
+ # if not func:
180
+ # raise ValueError("Params must have a callable 'function' attribute.")
181
+
182
+ # # args_schema = getattr(tool, "args_schema", None)
183
+ # args_fields = {}
184
+ # func_signature = signature(func)
185
+ # annotations = func_signature.parameters
186
+ # for name, param in annotations.items():
187
+ # if name != "self":
188
+ # param_annotation = (
189
+ # param.annotation if param.annotation != param.empty else Any
190
+ # )
191
+ # field_info = Field(default=..., description="")
192
+ # args_fields[name] = (param_annotation, field_info)
193
+
194
+ # args_schema = (
195
+ # create_model(f"{tool_name}Input", **args_fields)
196
+ # if args_fields
197
+ # else create_model(f"{tool_name}Input", __base__=BaseModel)
198
+ # )
199
+
200
+ # return cls(name=tool_name, func=func, args_schema=args_schema)
201
+
202
+
203
+ class ToolSet(BaseModel):
178
204
  """
179
205
  Store the tool called and any kwargs used.
180
206
  """
181
-
182
207
  tool: InstanceOf[Tool] = Field(..., description="store the tool instance to be called.")
183
- arguments: Optional[Dict[str, Any]] = Field(..., description="kwargs passed to the tool")
208
+ kwargs: Optional[Dict[str, Any]] = Field(..., description="kwargs passed to the tool")
184
209
 
185
210
 
186
- class InstructorToolCalled(BaseModel):
211
+ class InstructorToolSet(BaseModel):
187
212
  tool: InstanceOf[Tool] = Field(..., description="store the tool instance to be called.")
188
- arguments: Optional[Dict[str, Any]] = Field(..., description="kwargs passed to the tool")
213
+ kwargs: Optional[Dict[str, Any]] = Field(..., description="kwargs passed to the tool")
189
214
 
190
215
 
191
216
  class CacheTool(BaseModel):
@@ -1,48 +1,43 @@
1
1
  from typing import Any, Optional
2
2
  from pydantic import InstanceOf
3
- from versionhq.tool.model import ToolCalled, InstructorToolCalled, CacheTool
3
+ from versionhq.tool.model import ToolSet, InstructorToolSet, CacheTool
4
4
  from versionhq._utils.cache_handler import CacheHandler
5
5
 
6
6
 
7
7
  class ToolHandler:
8
8
  """
9
- Record the tool usage by ToolCalled instance with cache and error recording.
10
- Use as a callback function.
9
+ Record the tool usage by ToolSet instance with cache and error recording.
11
10
  """
12
11
 
13
- last_used_tool: ToolCalled = {}
12
+ last_used_tool: InstanceOf[ToolSet] | InstanceOf[InstructorToolSet]
14
13
  cache: Optional[CacheHandler]
15
14
  error: Optional[str]
16
15
 
17
- def __init__(self, cache_handler: Optional[CacheHandler] = None):
18
- """
19
- Initialize the callback handler.
20
- """
21
-
16
+ def __init__(
17
+ self,
18
+ last_used_tool: InstanceOf[ToolSet] | InstanceOf[InstructorToolSet] = None,
19
+ cache_handler: Optional[CacheHandler] = None
20
+ ):
22
21
  self.cache = cache_handler
23
- self.last_used_tool = {}
22
+ self.last_used_tool = last_used_tool
23
+
24
24
 
25
25
  def record_last_tool_used(
26
26
  self,
27
- last_used_tool: InstanceOf[ToolCalled] | InstanceOf[InstructorToolCalled],
27
+ last_used_tool: InstanceOf[ToolSet] | InstanceOf[InstructorToolSet],
28
28
  output: str,
29
29
  should_cache: bool = True,
30
30
  ) -> Any:
31
+
31
32
  self.last_used_tool = last_used_tool
32
33
 
33
34
  if self.cache and should_cache and last_used_tool.tool_name != CacheTool().name:
34
- self.cache.add(
35
- last_used_tool.tool_name,
36
- input.last_used_tool.arguments,
37
- output=output,
38
- )
35
+ self.cache.add(last_used_tool.tool_name, input.last_used_tool.arguments, output=output)
36
+
39
37
 
40
- def has_called_before(self, tool_called: ToolCalled = None) -> bool:
38
+ def has_called_before(self, tool_called: ToolSet = None) -> bool:
41
39
  if tool_called is None or not self.last_used_tool:
42
40
  return False
43
41
 
44
42
  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
- )
43
+ return bool((tool_called.tool.name == self.last_used_tool.tool.name) and (tool_called.arguments == self.last_used_tool.arguments))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: versionhq
3
- Version: 1.1.8
3
+ Version: 1.1.9.0
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` and `OPENAI_API_KEY` to the Github `repository secrets` @ settings > secrets & variables > Actions.
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