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/task/model.py CHANGED
@@ -4,7 +4,7 @@ import uuid
4
4
  from concurrent.futures import Future
5
5
  from hashlib import md5
6
6
  from typing import Any, Dict, List, Set, Optional, Tuple, Callable, Type
7
- from typing_extensions import Annotated
7
+ from typing_extensions import Annotated, Self
8
8
 
9
9
  from pydantic import UUID4, BaseModel, Field, PrivateAttr, field_validator, model_validator, create_model, InstanceOf
10
10
  from pydantic_core import PydanticCustomError
@@ -72,6 +72,7 @@ class TaskOutput(BaseModel):
72
72
  raw: str = Field(default="", description="Raw output of the task")
73
73
  json_dict: Dict[str, Any] = Field(default=None, description="`raw` converted to dictionary")
74
74
  pydantic: Optional[Any] = Field(default=None, description="`raw` converted to the abs. pydantic model")
75
+ tool_output: Optional[Any] = Field(default=None, description="store tool result when the task takes tool output as its final output")
75
76
 
76
77
  def __str__(self) -> str:
77
78
  return str(self.pydantic) if self.pydantic else str(self.json_dict) if self.json_dict else self.raw
@@ -125,6 +126,7 @@ class Task(BaseModel):
125
126
  _original_description: str = PrivateAttr(default=None)
126
127
  _logger: Logger = PrivateAttr()
127
128
  _task_output_handler = TaskOutputStorageHandler()
129
+ config: Optional[Dict[str, Any]] = Field(default=None, description="configuration for the agent")
128
130
 
129
131
  id: UUID4 = Field(default_factory=uuid.uuid4, frozen=True, description="unique identifier for the object, not set by user")
130
132
  name: Optional[str] = Field(default=None)
@@ -134,33 +136,35 @@ class Task(BaseModel):
134
136
  expected_output_json: bool = Field(default=True)
135
137
  expected_output_pydantic: bool = Field(default=False)
136
138
  output_field_list: List[ResponseField] = Field(
137
- default=[ResponseField(title="output", type=str, required=False)],
139
+ default_factory=list,
138
140
  description="provide output key and data type. this will be cascaded to the agent via task.prompt()"
139
141
  )
140
142
  output: Optional[TaskOutput] = Field(default=None, description="store the final task output in TaskOutput class")
141
143
 
142
144
  # task setup
143
145
  context: Optional[List["Task"]] = Field(default=None, description="other tasks whose outputs should be used as context")
144
- tools_called: Optional[List[ToolSet]] = Field(default_factory=list, description="tools that the agent can use for this task")
146
+ prompt_context: Optional[str] = Field(default=None)
147
+
148
+ # tool usage
149
+ tools: Optional[List[ToolSet | Tool | Any]] = Field(default_factory=list, description="tools that the agent can use aside from their tools")
150
+ can_use_agent_tools: bool = Field(default=False, description="whether the agent can use their own tools when executing the task")
145
151
  take_tool_res_as_final: bool = Field(default=False, description="when set True, tools res will be stored in the `TaskOutput`")
146
- allow_delegation: bool = Field(default=False, description="ask other agents for help and run the task instead")
147
152
 
148
- prompt_context: Optional[str] = Field(default=None)
153
+ # execution rules
154
+ allow_delegation: bool = Field(default=False, description="ask other agents for help and run the task instead")
149
155
  async_execution: bool = Field(default=False,description="whether the task should be executed asynchronously or not")
150
- config: Optional[Dict[str, Any]] = Field(default=None, description="configuration for the agent")
151
156
  callback: Optional[Any] = Field(default=None, description="callback to be executed after the task is completed.")
152
157
  callback_kwargs: Optional[Dict[str, Any]] = Field(default_factory=dict, description="kwargs for the callback when the callback is callable")
153
158
 
154
159
  # recording
155
160
  processed_by_agents: Set[str] = Field(default_factory=set, description="store responsible agents' roles")
156
- used_tools: int = 0
157
161
  tools_errors: int = 0
158
162
  delegations: int = 0
159
163
 
160
164
 
161
165
  @model_validator(mode="before")
162
166
  @classmethod
163
- def process_model_config(cls, values: Dict[str, Any]):
167
+ def process_model_config(cls, values: Dict[str, Any]) -> None:
164
168
  return process_config(values_to_update=values, model_class=cls)
165
169
 
166
170
 
@@ -172,7 +176,7 @@ class Task(BaseModel):
172
176
 
173
177
 
174
178
  @model_validator(mode="after")
175
- def validate_required_fields(self):
179
+ def validate_required_fields(self) -> Self:
176
180
  required_fields = ["description",]
177
181
  for field in required_fields:
178
182
  if getattr(self, field) is None:
@@ -181,7 +185,7 @@ class Task(BaseModel):
181
185
 
182
186
 
183
187
  @model_validator(mode="after")
184
- def set_attributes_based_on_config(self) -> "Task":
188
+ def set_attributes_based_on_config(self) -> Self:
185
189
  """
186
190
  Set attributes based on the agent configuration.
187
191
  """
@@ -192,12 +196,21 @@ class Task(BaseModel):
192
196
  return self
193
197
 
194
198
 
195
- ## comment out as we set raw as the default TaskOutputFormat
196
- # @model_validator(mode="after")
197
- # def validate_output_format(self):
198
- # if self.expected_output_json == False and self.expected_output_pydantic == False:
199
- # raise PydanticCustomError("Need to choose at least one output format.")
200
- # return self
199
+ @model_validator(mode="after")
200
+ def set_up_tools(self) -> Self:
201
+ if not self.tools:
202
+ pass
203
+ else:
204
+ tool_list = []
205
+ for item in self.tools:
206
+ if isinstance(item, Tool) or isinstance(item, ToolSet):
207
+ tool_list.append(item)
208
+ elif (isinstance(item, dict) and "function" not in item) or isinstance(item, str):
209
+ pass
210
+ else:
211
+ tool_list.append(item) # address custom tool
212
+ self.tools = tool_list
213
+ return self
201
214
 
202
215
 
203
216
  @model_validator(mode="after")
@@ -261,7 +274,6 @@ class Task(BaseModel):
261
274
  return output_json_dict
262
275
 
263
276
 
264
-
265
277
  def create_pydantic_output(self, output_json_dict: Dict[str, Any], raw_result: Any = None) -> Optional[Any]:
266
278
  """
267
279
  Create pydantic output from the `raw` result.
@@ -302,11 +314,10 @@ class Task(BaseModel):
302
314
  """
303
315
  if inputs:
304
316
  self.description = self._original_description.format(**inputs)
305
- # self.expected_output = self._original_expected_output.format(**inputs)
306
317
 
307
318
 
308
319
  # task execution
309
- def execute_sync(self, agent, context: Optional[str] = None, tools: Optional[List[Any]] = None) -> TaskOutput:
320
+ def execute_sync(self, agent, context: Optional[str] = None) -> TaskOutput:
310
321
  """
311
322
  Execute the task synchronously.
312
323
  When the task has context, make sure we have executed all the tasks in the context first.
@@ -320,26 +331,26 @@ class Task(BaseModel):
320
331
  return self._execute_core(agent, context)
321
332
 
322
333
 
323
- def execute_async(self, agent, context: Optional[str] = None, tools: Optional[List[Any]] = None) -> Future[TaskOutput]:
334
+ def execute_async(self, agent, context: Optional[str] = None) -> Future[TaskOutput]:
324
335
  """
325
336
  Execute the task asynchronously.
326
337
  """
327
338
 
328
339
  future: Future[TaskOutput] = Future()
329
- threading.Thread(daemon=True, target=self._execute_task_async, args=(agent, context, tools, future)).start()
340
+ threading.Thread(daemon=True, target=self._execute_task_async, args=(agent, context, future)).start()
330
341
  return future
331
342
 
332
343
 
333
- def _execute_task_async(self, agent, context: Optional[str], tools: Optional[List[Any]], future: Future[TaskOutput]) -> None:
344
+ def _execute_task_async(self, agent, context: Optional[str], future: Future[TaskOutput]) -> None:
334
345
  """
335
346
  Execute the task asynchronously with context handling.
336
347
  """
337
348
 
338
- result = self._execute_core(agent, context, tools)
349
+ result = self._execute_core(agent, context)
339
350
  future.set_result(result)
340
351
 
341
352
 
342
- def _execute_core(self, agent, context: Optional[str], tools: Optional[List[Any]] = []) -> TaskOutput:
353
+ def _execute_core(self, agent, context: Optional[str]) -> TaskOutput:
343
354
  """
344
355
  Run the core execution logic of the task.
345
356
  To speed up the process, when the format is not expected to return, we will skip the conversion process.
@@ -349,13 +360,14 @@ class Task(BaseModel):
349
360
  from versionhq.team.model import Team
350
361
 
351
362
  self.prompt_context = context
363
+ task_output: InstanceOf[TaskOutput] = None
352
364
 
353
365
  if self.allow_delegation:
354
366
  agent_to_delegate = None
355
367
 
356
368
  if hasattr(agent, "team") and isinstance(agent.team, Team):
357
369
  if agent.team.managers:
358
- idling_manager_agents = [manager.agent for manager in agent.team.managers if manager.task is None]
370
+ idling_manager_agents = [manager.agent for manager in agent.team.managers if manager.is_idling]
359
371
  agent_to_delegate = idling_manager_agents[0] if idling_manager_agents else agent.team.managers[0]
360
372
  else:
361
373
  peers = [member.agent for member in agent.team.members if member.is_manager == False and member.agent.id is not agent.id]
@@ -367,20 +379,26 @@ class Task(BaseModel):
367
379
  agent = agent_to_delegate
368
380
  self.delegations += 1
369
381
 
370
- output_raw, output_json_dict, output_pydantic = agent.execute_task(task=self, context=context, tools=tools), None, None
382
+ if self.take_tool_res_as_final is True:
383
+ output = agent.execute_task(task=self, context=context)
384
+ task_output = TaskOutput(task_id=self.id, tool_output=output)
371
385
 
372
- if self.expected_output_json:
373
- output_json_dict = self.create_json_output(raw_result=output_raw)
386
+ else:
387
+ output_raw, output_json_dict, output_pydantic = agent.execute_task(task=self, context=context), None, None
374
388
 
375
- if self.expected_output_pydantic:
376
- output_pydantic = self.create_pydantic_output(output_json_dict=output_json_dict)
389
+ if self.expected_output_json:
390
+ output_json_dict = self.create_json_output(raw_result=output_raw)
391
+
392
+ if self.expected_output_pydantic:
393
+ output_pydantic = self.create_pydantic_output(output_json_dict=output_json_dict)
394
+
395
+ task_output = TaskOutput(
396
+ task_id=self.id,
397
+ raw=output_raw,
398
+ pydantic=output_pydantic,
399
+ json_dict=output_json_dict
400
+ )
377
401
 
378
- task_output = TaskOutput(
379
- task_id=self.id,
380
- raw=output_raw,
381
- pydantic=output_pydantic,
382
- json_dict=output_json_dict
383
- )
384
402
  self.output = task_output
385
403
  self.processed_by_agents.add(agent.role)
386
404
 
@@ -400,6 +418,7 @@ class Task(BaseModel):
400
418
  # else pydantic_output.model_dump_json() if pydantic_output else result
401
419
  # )
402
420
  # self._save_file(content)
421
+
403
422
  return task_output
404
423
 
405
424
 
@@ -463,7 +482,7 @@ Your outputs MUST adhere to the following format and should NOT include any irre
463
482
  Task ID: {str(self.id)}
464
483
  "Description": {self.description}
465
484
  "Prompt": {self.output_prompt}
466
- "Tools": {", ".join([tool_called.tool.name for tool_called in self.tools_called])}
485
+ "Tools": {", ".join([tool.name for tool in self.tools])}
467
486
  """
468
487
 
469
488
 
versionhq/team/model.py CHANGED
@@ -1,7 +1,6 @@
1
1
  import uuid
2
2
  import warnings
3
3
  import json
4
- from abc import ABC
5
4
  from enum import Enum
6
5
  from dotenv import load_dotenv
7
6
  from concurrent.futures import Future
@@ -108,13 +107,14 @@ class TeamOutput(BaseModel):
108
107
  return res
109
108
 
110
109
 
111
- class TeamMember(ABC, BaseModel):
110
+ class TeamMember(BaseModel):
112
111
  agent: Agent | None = Field(default=None, description="store the agent to be a member")
113
112
  is_manager: bool = Field(default=False)
114
113
  task: Optional[Task] = Field(default=None)
115
114
 
116
- def update(self, task: Task):
117
- self.task = task
115
+ @property
116
+ def is_idling(self):
117
+ return bool(self.task is None)
118
118
 
119
119
 
120
120
  class Team(BaseModel):
@@ -253,8 +253,8 @@ class Team(BaseModel):
253
253
  """
254
254
 
255
255
  team_planner = TeamPlanner(tasks=self.tasks, planner_llm=self.planning_llm)
256
- idling_managers: List[TeamMember] = [member for member in self.members if member.task is None and member.is_manager is True]
257
- idling_members: List[TeamMember] = [member for member in self.members if member.task is None and member.is_manager is False]
256
+ idling_managers: List[TeamMember] = [member for member in self.members if member.is_idling and member.is_manager is True]
257
+ idling_members: List[TeamMember] = [member for member in self.members if member.is_idling and member.is_manager is False]
258
258
  unassigned_tasks: List[Task] = self.member_tasks_without_agent
259
259
  new_team_members: List[TeamMember] = []
260
260
 
@@ -380,16 +380,15 @@ class Team(BaseModel):
380
380
  if skipped_task_output:
381
381
  continue
382
382
 
383
- # self._prepare_agent_tools(task)
384
383
  # self._log_task_start(task, responsible_agent)
385
384
 
386
385
  if task.async_execution:
387
386
  context = create_raw_outputs(tasks=[task, ], task_outputs=([last_sync_output,] if last_sync_output else []))
388
- future = task.execute_async(agent=responsible_agent, context=context, tools=responsible_agent.tools)
387
+ future = task.execute_async(agent=responsible_agent, context=context)
389
388
  futures.append((task, future, task_index))
390
389
  else:
391
390
  context = create_raw_outputs(tasks=[task,], task_outputs=([last_sync_output,] if last_sync_output else [] ))
392
- task_output = task.execute_sync(agent=responsible_agent, context=context, tools=responsible_agent.tools)
391
+ task_output = task.execute_sync(agent=responsible_agent, context=context)
393
392
  if self.managers and responsible_agent in [manager.agent for manager in self.managers]:
394
393
  lead_task_output = task_output
395
394
 
@@ -1,14 +1,17 @@
1
1
  import os
2
2
  import uuid
3
+ from abc import ABC
3
4
  from dotenv import load_dotenv
4
5
  from typing import Any, Callable, Type, get_args, get_origin, Optional, Tuple
5
6
 
6
- from pydantic import BaseModel, Field, model_validator, field_validator, UUID4
7
+ from pydantic import BaseModel, Field, model_validator, field_validator, UUID4, PrivateAttr
7
8
  from pydantic_core import PydanticCustomError
8
9
 
9
10
  from composio import ComposioToolSet, Action, App, action
10
11
 
11
12
  from versionhq.tool import ComposioAppName, ComposioAuthScheme, composio_app_set, COMPOSIO_STATUS
13
+ from versionhq._utils.logger import Logger
14
+ from versionhq._utils.cache_handler import CacheHandler
12
15
 
13
16
  load_dotenv(override=True)
14
17
 
@@ -16,11 +19,14 @@ DEFAULT_REDIRECT_URL = os.environ.get("DEFAULT_REDIRECT_URL", None)
16
19
  DEFAULT_USER_ID = os.environ.get("DEFAULT_USER_ID", None)
17
20
 
18
21
 
19
- class Composio(BaseModel):
22
+ class Composio(ABC, BaseModel):
20
23
  """
21
24
  Class to handle composio tools.
22
25
  """
23
26
 
27
+ _logger: Logger = PrivateAttr(default_factory=lambda: Logger(verbose=True))
28
+ _cache: CacheHandler
29
+
24
30
  id: UUID4 = Field(default_factory=uuid.uuid4, frozen=True)
25
31
  app_name: str = Field(default=ComposioAppName.HUBSPOT)
26
32
  user_id: str = Field(default=DEFAULT_USER_ID)
@@ -125,6 +131,12 @@ class Composio(BaseModel):
125
131
  if connected_account.status is not COMPOSIO_STATUS.FAILED:
126
132
  setattr(self.toolset, "entity_id", self.user_id)
127
133
 
134
+ else:
135
+ self._logger.log(level="error", message="The account is not valid.", color="red")
136
+
137
+ else:
138
+ self._logger.log(level="error", message="Connection to composio failed.", color="red")
139
+
128
140
  return connected_account, connected_account.status if connected_account else connection_request.connectionStatus
129
141
 
130
142
 
versionhq/tool/model.py CHANGED
@@ -1,20 +1,21 @@
1
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 typing_extensions import Self
4
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
10
  class BaseTool(ABC, BaseModel):
11
+ """
12
+ Abstract class for Tool class.
13
+ """
10
14
 
11
15
  class _ArgsSchemaPlaceholder(BaseModel):
12
16
  pass
13
17
 
14
- name: str = Field(default=None)
15
- goal: str = Field(default=None)
16
18
  args_schema: Type[BaseModel] = Field(default_factory=_ArgsSchemaPlaceholder)
17
- cache_function: Callable = lambda _args=None, _result=None: True
18
19
 
19
20
 
20
21
  @field_validator("args_schema", mode="before")
@@ -26,11 +27,7 @@ class BaseTool(ABC, BaseModel):
26
27
  return type(
27
28
  f"{cls.__name__}Schema",
28
29
  (BaseModel,),
29
- {
30
- "__annotations__": {
31
- k: v for k, v in cls._run.__annotations__.items() if k != "return"
32
- },
33
- },
30
+ { "__annotations__": { k: v for k, v in cls._run.__annotations__.items() if k != "return" }},
34
31
  )
35
32
 
36
33
 
@@ -39,8 +36,36 @@ class BaseTool(ABC, BaseModel):
39
36
  """any handling"""
40
37
 
41
38
 
42
- def run(self, *args: Any, **kwargs: Any) -> Any:
43
- return self._run(*args, **kwargs)
39
+
40
+ class Tool(BaseTool):
41
+ name: str = Field(default=None)
42
+ goal: str = Field(default=None)
43
+ function: Callable = Field(default=None)
44
+ tool_handler: Optional[Dict[str, Any] | Any] = Field(default=None, description="store tool_handler to record the usage of this tool")
45
+ should_cache: bool = Field(default=True, description="whether the tool usage should be cached")
46
+ cache_function: Callable = lambda _args=None, _result=None: True
47
+ cache_handler: Optional[InstanceOf[CacheHandler]] = Field(default=None)
48
+
49
+
50
+ @model_validator(mode="after")
51
+ def set_up_tool_handler(self) -> Self:
52
+ from versionhq.tool.tool_handler import ToolHandler
53
+
54
+ if self.tool_handler and not isinstance(self.tool_handler, ToolHandler):
55
+ ToolHandler(**self.tool_handler)
56
+
57
+ else:
58
+ self.tool_handler = ToolHandler(cache_handler=self.cache_handler, should_cache=self.should_cache)
59
+
60
+ return self
61
+
62
+
63
+ @model_validator(mode="after")
64
+ def set_up_function(self) -> Self:
65
+ if self.function is None:
66
+ self.function = self._run
67
+ self._set_args_schema_from_func()
68
+ return self
44
69
 
45
70
 
46
71
  @staticmethod
@@ -85,88 +110,71 @@ class BaseTool(ABC, BaseModel):
85
110
  self.args_schema = type(
86
111
  class_name,
87
112
  (BaseModel,),
88
- { "__annotations__": { k: v for k, v in self._run.__annotations__.items() if k != "return" } },
113
+ { "__annotations__": {
114
+ k: v for k, v in self._run.__annotations__.items() if k != "return"
115
+ } },
89
116
  )
90
117
  return self
91
118
 
92
119
 
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
120
  def _run(self, *args: Any, **kwargs: Any) -> Any:
132
- return self.function(*args, **kwargs)
121
+ return self.run(*args, **kwargs)
133
122
 
134
123
 
135
124
  def run(self, *args, **kwargs) -> Any:
136
125
  """
137
- Use tool
126
+ Use tool and record its usage if should_cache is True.
138
127
  """
139
128
  from versionhq.tool.tool_handler import ToolHandler
140
129
 
141
130
  result = None
131
+ tool_set = ToolSet(tool=self, kwargs={})
142
132
 
143
- if self.function is not None:
133
+ if self.function:
144
134
  result = self.function(*args, **kwargs)
145
135
 
146
136
  else:
147
137
  acceptable_args = self.args_schema.model_json_schema()["properties"].keys()
148
138
  acceptable_kwargs = { k: v for k, v in kwargs.items() if k in acceptable_args }
149
- tool_called = ToolSet(tool=self, kwargs=acceptable_kwargs)
139
+ tool_set = ToolSet(tool=self, kwargs=acceptable_kwargs)
150
140
 
151
141
  if self.tool_handler:
152
- if self.tool_handler.has_called_before(tool_called):
142
+ if self.tool_handler.has_called_before(tool_set):
153
143
  self.tool_handler.error = "Agent execution error"
154
144
 
155
145
  elif self.tool_handler.cache:
156
- result = self.tools_handler.cache.read(tool=tool_called.tool.name, input=tool_called.kwargs)
146
+ result = self.tools_handler.cache.read(tool=tool_set.tool.name, input=tool_set.kwargs)
157
147
  if result is None:
158
- parsed_kwargs = self._parse_args(input=acceptable_kwargs)
159
- result = self.function(**parsed_kwargs)
148
+ parsed_kwargs = self._parse_args(raw_args=acceptable_kwargs)
149
+ result = self.function(**parsed_kwargs) if self.function else None
160
150
 
161
151
  else:
162
- tool_handler = ToolHandler(last_used_tool=tool_called, cache_handler=CacheHandler())
152
+ tool_handler = ToolHandler(last_used_tool=tool_set, cache_handler=self.cache_handler, should_cache=self.should_cache)
163
153
  self.tool_handler = tool_handler
164
- parsed_kwargs = self._parse_args(input=acceptable_kwargs)
165
- result = self.function(**parsed_kwargs)
154
+ parsed_kwargs = self._parse_args(raw_args=acceptable_kwargs)
155
+ result = self.function(**parsed_kwargs) if self.function else None
156
+
157
+
158
+ if self.should_cache is True:
159
+ self.tool_handler.record_last_tool_used(tool_set, result, self.should_cache)
166
160
 
167
161
  return result
168
162
 
169
163
 
164
+ @property
165
+ def description(self) -> str:
166
+ args_schema = {
167
+ name: {
168
+ "description": field.description,
169
+ "type": self._get_arg_annotations(field.annotation),
170
+ }
171
+ for name, field in self.args_schema.model_fields.items()
172
+ }
173
+
174
+ return f"Tool Name: {self.name}\nTool Arguments: {args_schema}\nGoal: {self.goal}"
175
+
176
+
177
+
170
178
  # @classmethod
171
179
  # def from_composio(
172
180
  # cls, func: Callable = None, tool_name: str = "Composio tool"
@@ -204,12 +212,12 @@ class ToolSet(BaseModel):
204
212
  """
205
213
  Store the tool called and any kwargs used.
206
214
  """
207
- tool: InstanceOf[Tool] = Field(..., description="store the tool instance to be called.")
215
+ tool: InstanceOf[Tool] | Any = Field(..., description="store the tool instance to be called.")
208
216
  kwargs: Optional[Dict[str, Any]] = Field(..., description="kwargs passed to the tool")
209
217
 
210
218
 
211
219
  class InstructorToolSet(BaseModel):
212
- tool: InstanceOf[Tool] = Field(..., description="store the tool instance to be called.")
220
+ tool: InstanceOf[Tool] | Any = Field(..., description="store the tool instance to be called.")
213
221
  kwargs: Optional[Dict[str, Any]] = Field(..., description="kwargs passed to the tool")
214
222
 
215
223
 
@@ -1,6 +1,7 @@
1
- from typing import Any, Optional
1
+ from typing import Optional, Any
2
2
  from pydantic import InstanceOf
3
- from versionhq.tool.model import ToolSet, InstructorToolSet, CacheTool
3
+
4
+ from versionhq.tool.model import ToolSet, InstructorToolSet
4
5
  from versionhq._utils.cache_handler import CacheHandler
5
6
 
6
7
 
@@ -12,32 +13,33 @@ class ToolHandler:
12
13
  last_used_tool: InstanceOf[ToolSet] | InstanceOf[InstructorToolSet]
13
14
  cache: Optional[CacheHandler]
14
15
  error: Optional[str]
16
+ should_cache: bool
15
17
 
16
18
  def __init__(
17
- self,
18
- last_used_tool: InstanceOf[ToolSet] | InstanceOf[InstructorToolSet] = None,
19
- cache_handler: Optional[CacheHandler] = None
19
+ self,
20
+ last_used_tool: InstanceOf[ToolSet] | InstanceOf[InstructorToolSet] = None,
21
+ cache_handler: Optional[CacheHandler] = None,
22
+ should_cache: bool = True
20
23
  ):
21
24
  self.cache = cache_handler
22
25
  self.last_used_tool = last_used_tool
26
+ self.should_cache = should_cache
23
27
 
24
28
 
25
29
  def record_last_tool_used(
26
- self,
27
- last_used_tool: InstanceOf[ToolSet] | InstanceOf[InstructorToolSet],
28
- output: str,
29
- should_cache: bool = True,
30
+ self, last_used_tool: InstanceOf[ToolSet] | InstanceOf[InstructorToolSet], output: str, should_cache: bool = True
30
31
  ) -> Any:
32
+ from versionhq.tool.model import CacheTool
31
33
 
32
34
  self.last_used_tool = last_used_tool
33
35
 
34
- if self.cache and should_cache and last_used_tool.tool_name != CacheTool().name:
35
- self.cache.add(last_used_tool.tool_name, input.last_used_tool.arguments, output=output)
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)
36
38
 
37
39
 
38
- def has_called_before(self, tool_called: ToolSet = None) -> bool:
39
- if tool_called is None or not self.last_used_tool:
40
+ def has_called_before(self, tool_set: ToolSet = None) -> bool:
41
+ if tool_set is None or not self.last_used_tool:
40
42
  return False
41
43
 
42
- if tool_called := self.last_used_tool:
43
- return bool((tool_called.tool.name == self.last_used_tool.tool.name) and (tool_called.arguments == self.last_used_tool.arguments))
44
+ if tool_set := self.last_used_tool:
45
+ return bool((tool_set.tool.name == self.last_used_tool.tool.name) and (tool_set.kwargs == self.last_used_tool.kwargs))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: versionhq
3
- Version: 1.1.9.0
3
+ Version: 1.1.9.2
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