versionhq 1.1.8.1__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.
- versionhq/__init__.py +3 -1
- versionhq/agent/model.py +1 -1
- versionhq/clients/workflow/model.py +8 -9
- versionhq/task/model.py +72 -71
- versionhq/team/model.py +51 -57
- versionhq/tool/__init__.py +53 -0
- versionhq/tool/composio.py +149 -0
- versionhq/tool/decorator.py +3 -1
- versionhq/tool/model.py +129 -103
- versionhq/tool/tool_handler.py +16 -21
- {versionhq-1.1.8.1.dist-info → versionhq-1.1.9.0.dist-info}/METADATA +2 -2
- {versionhq-1.1.8.1.dist-info → versionhq-1.1.9.0.dist-info}/RECORD +15 -14
- {versionhq-1.1.8.1.dist-info → versionhq-1.1.9.0.dist-info}/LICENSE +0 -0
- {versionhq-1.1.8.1.dist-info → versionhq-1.1.9.0.dist-info}/WHEEL +0 -0
- {versionhq-1.1.8.1.dist-info → versionhq-1.1.9.0.dist-info}/top_level.txt +0 -0
versionhq/__init__.py
CHANGED
@@ -15,9 +15,10 @@ from versionhq.llm.model import LLM
|
|
15
15
|
from versionhq.task.model import Task, TaskOutput
|
16
16
|
from versionhq.team.model import Team, TeamOutput
|
17
17
|
from versionhq.tool.model import Tool
|
18
|
+
from versionhq.tool.composio import Composio
|
18
19
|
|
19
20
|
|
20
|
-
__version__ = "1.1.
|
21
|
+
__version__ = "1.1.9.0"
|
21
22
|
__all__ = [
|
22
23
|
"Agent",
|
23
24
|
"Customer",
|
@@ -33,4 +34,5 @@ __all__ = [
|
|
33
34
|
"Team",
|
34
35
|
"TeamOutput",
|
35
36
|
"Tool",
|
37
|
+
"Composio"
|
36
38
|
]
|
versionhq/agent/model.py
CHANGED
@@ -15,7 +15,7 @@ from versionhq.llm.llm_vars import LLM_VARS
|
|
15
15
|
from versionhq.llm.model import LLM, DEFAULT_CONTEXT_WINDOW
|
16
16
|
from versionhq.task import TaskOutputFormat
|
17
17
|
from versionhq.task.model import ResponseField
|
18
|
-
from versionhq.tool.model import Tool,
|
18
|
+
from versionhq.tool.model import Tool, ToolSet
|
19
19
|
from versionhq.tool.tool_handler import ToolHandler
|
20
20
|
|
21
21
|
load_dotenv(override=True)
|
@@ -117,15 +117,6 @@ class MessagingWorkflow(ABC, BaseModel):
|
|
117
117
|
description="store metrics that used to predict and track the performance of this workflow."
|
118
118
|
)
|
119
119
|
|
120
|
-
|
121
|
-
@property
|
122
|
-
def name(self):
|
123
|
-
if self.customer.id:
|
124
|
-
return f"Workflow ID: {self.id} - on {self.product.id} for {self.customer.id}"
|
125
|
-
else:
|
126
|
-
return f"Workflow ID: {self.id} - on {self.product.id}"
|
127
|
-
|
128
|
-
|
129
120
|
@field_validator("id", mode="before")
|
130
121
|
@classmethod
|
131
122
|
def _deny_user_set_id(cls, v: Optional[UUID4]) -> None:
|
@@ -160,3 +151,11 @@ class MessagingWorkflow(ABC, BaseModel):
|
|
160
151
|
self.agents = agents
|
161
152
|
self.team = team
|
162
153
|
self.updated_at = datetime.datetime.now()
|
154
|
+
|
155
|
+
|
156
|
+
@property
|
157
|
+
def name(self):
|
158
|
+
if self.customer.id:
|
159
|
+
return f"Workflow ID: {self.id} - on {self.product.id} for {self.customer.id}"
|
160
|
+
else:
|
161
|
+
return f"Workflow ID: {self.id} - on {self.product.id}"
|
versionhq/task/model.py
CHANGED
@@ -12,7 +12,7 @@ from pydantic_core import PydanticCustomError
|
|
12
12
|
from versionhq._utils.process_config import process_config
|
13
13
|
from versionhq.task import TaskOutputFormat
|
14
14
|
from versionhq.task.log_handler import TaskOutputStorageHandler
|
15
|
-
from versionhq.tool.model import Tool,
|
15
|
+
from versionhq.tool.model import Tool, ToolSet
|
16
16
|
from versionhq._utils.logger import Logger
|
17
17
|
|
18
18
|
|
@@ -76,18 +76,6 @@ class TaskOutput(BaseModel):
|
|
76
76
|
def __str__(self) -> str:
|
77
77
|
return str(self.pydantic) if self.pydantic else str(self.json_dict) if self.json_dict else self.raw
|
78
78
|
|
79
|
-
@property
|
80
|
-
def json(self) -> Optional[str]:
|
81
|
-
if self.output_format != TaskOutputFormat.JSON:
|
82
|
-
raise ValueError(
|
83
|
-
"""
|
84
|
-
Invalid output format requested.
|
85
|
-
If you would like to access the JSON output,
|
86
|
-
pleae make sure to set the output_json property for the task
|
87
|
-
"""
|
88
|
-
)
|
89
|
-
return json.dumps(self.json_dict)
|
90
|
-
|
91
79
|
|
92
80
|
def to_dict(self) -> Dict[str, Any]:
|
93
81
|
"""
|
@@ -112,6 +100,19 @@ class TaskOutput(BaseModel):
|
|
112
100
|
return json.dumps(self.json_dict) if self.json_dict else self.raw[0: 127]
|
113
101
|
|
114
102
|
|
103
|
+
@property
|
104
|
+
def json(self) -> Optional[str]:
|
105
|
+
if self.output_format != TaskOutputFormat.JSON:
|
106
|
+
raise ValueError(
|
107
|
+
"""
|
108
|
+
Invalid output format requested.
|
109
|
+
If you would like to access the JSON output,
|
110
|
+
pleae make sure to set the output_json property for the task
|
111
|
+
"""
|
112
|
+
)
|
113
|
+
return json.dumps(self.json_dict)
|
114
|
+
|
115
|
+
|
115
116
|
|
116
117
|
class Task(BaseModel):
|
117
118
|
"""
|
@@ -140,7 +141,7 @@ class Task(BaseModel):
|
|
140
141
|
|
141
142
|
# task setup
|
142
143
|
context: Optional[List["Task"]] = Field(default=None, description="other tasks whose outputs should be used as context")
|
143
|
-
tools_called: Optional[List[
|
144
|
+
tools_called: Optional[List[ToolSet]] = Field(default_factory=list, description="tools that the agent can use for this task")
|
144
145
|
take_tool_res_as_final: bool = Field(default=False, description="when set True, tools res will be stored in the `TaskOutput`")
|
145
146
|
allow_delegation: bool = Field(default=False, description="ask other agents for help and run the task instead")
|
146
147
|
|
@@ -157,63 +158,6 @@ class Task(BaseModel):
|
|
157
158
|
delegations: int = 0
|
158
159
|
|
159
160
|
|
160
|
-
@property
|
161
|
-
def output_prompt(self) -> str:
|
162
|
-
"""
|
163
|
-
Draft prompts on the output format by converting `output_field_list` to dictionary.
|
164
|
-
"""
|
165
|
-
|
166
|
-
output_prompt, output_formats_to_follow = "", dict()
|
167
|
-
for item in self.output_field_list:
|
168
|
-
output_formats_to_follow[item.title] = f"<Return your answer in {item.type.__name__}>"
|
169
|
-
|
170
|
-
output_prompt = f"""
|
171
|
-
Your outputs MUST adhere to the following format and should NOT include any irrelevant elements:
|
172
|
-
{output_formats_to_follow}
|
173
|
-
"""
|
174
|
-
return output_prompt
|
175
|
-
|
176
|
-
|
177
|
-
@property
|
178
|
-
def expected_output_formats(self) -> List[TaskOutputFormat]:
|
179
|
-
"""
|
180
|
-
Return output formats in list with the ENUM item.
|
181
|
-
`TaskOutputFormat.RAW` is set as default.
|
182
|
-
"""
|
183
|
-
outputs = [TaskOutputFormat.RAW,]
|
184
|
-
if self.expected_output_json:
|
185
|
-
outputs.append(TaskOutputFormat.JSON)
|
186
|
-
if self.expected_output_pydantic:
|
187
|
-
outputs.append(TaskOutputFormat.PYDANTIC)
|
188
|
-
return outputs
|
189
|
-
|
190
|
-
|
191
|
-
@property
|
192
|
-
def key(self) -> str:
|
193
|
-
output_format = (
|
194
|
-
TaskOutputFormat.JSON
|
195
|
-
if self.expected_output_json == True
|
196
|
-
else (
|
197
|
-
TaskOutputFormat.PYDANTIC
|
198
|
-
if self.expected_output_pydantic == True
|
199
|
-
else TaskOutputFormat.RAW
|
200
|
-
)
|
201
|
-
)
|
202
|
-
source = [self.description, output_format]
|
203
|
-
return md5("|".join(source).encode(), usedforsecurity=False).hexdigest()
|
204
|
-
|
205
|
-
|
206
|
-
@property
|
207
|
-
def summary(self) -> str:
|
208
|
-
return f"""
|
209
|
-
Task ID: {str(self.id)}
|
210
|
-
"Description": {self.description}
|
211
|
-
"Prompt": {self.output_prompt}
|
212
|
-
"Tools": {", ".join([tool_called.tool.name for tool_called in self.tools_called])}
|
213
|
-
"""
|
214
|
-
|
215
|
-
|
216
|
-
# validators
|
217
161
|
@model_validator(mode="before")
|
218
162
|
@classmethod
|
219
163
|
def process_model_config(cls, values: Dict[str, Any]):
|
@@ -467,6 +411,63 @@ Your outputs MUST adhere to the following format and should NOT include any irre
|
|
467
411
|
self._task_output_handler.update(task=self, task_index=task_index, was_replayed=was_replayed, inputs=inputs)
|
468
412
|
|
469
413
|
|
414
|
+
@property
|
415
|
+
def output_prompt(self) -> str:
|
416
|
+
"""
|
417
|
+
Draft prompts on the output format by converting `output_field_list` to dictionary.
|
418
|
+
"""
|
419
|
+
|
420
|
+
output_prompt, output_formats_to_follow = "", dict()
|
421
|
+
for item in self.output_field_list:
|
422
|
+
output_formats_to_follow[item.title] = f"<Return your answer in {item.type.__name__}>"
|
423
|
+
|
424
|
+
output_prompt = f"""
|
425
|
+
Your outputs MUST adhere to the following format and should NOT include any irrelevant elements:
|
426
|
+
{output_formats_to_follow}
|
427
|
+
"""
|
428
|
+
return output_prompt
|
429
|
+
|
430
|
+
|
431
|
+
@property
|
432
|
+
def expected_output_formats(self) -> List[TaskOutputFormat]:
|
433
|
+
"""
|
434
|
+
Return output formats in list with the ENUM item.
|
435
|
+
`TaskOutputFormat.RAW` is set as default.
|
436
|
+
"""
|
437
|
+
outputs = [TaskOutputFormat.RAW,]
|
438
|
+
if self.expected_output_json:
|
439
|
+
outputs.append(TaskOutputFormat.JSON)
|
440
|
+
if self.expected_output_pydantic:
|
441
|
+
outputs.append(TaskOutputFormat.PYDANTIC)
|
442
|
+
return outputs
|
443
|
+
|
444
|
+
|
445
|
+
@property
|
446
|
+
def key(self) -> str:
|
447
|
+
output_format = (
|
448
|
+
TaskOutputFormat.JSON
|
449
|
+
if self.expected_output_json == True
|
450
|
+
else (
|
451
|
+
TaskOutputFormat.PYDANTIC
|
452
|
+
if self.expected_output_pydantic == True
|
453
|
+
else TaskOutputFormat.RAW
|
454
|
+
)
|
455
|
+
)
|
456
|
+
source = [self.description, output_format]
|
457
|
+
return md5("|".join(source).encode(), usedforsecurity=False).hexdigest()
|
458
|
+
|
459
|
+
|
460
|
+
@property
|
461
|
+
def summary(self) -> str:
|
462
|
+
return f"""
|
463
|
+
Task ID: {str(self.id)}
|
464
|
+
"Description": {self.description}
|
465
|
+
"Prompt": {self.output_prompt}
|
466
|
+
"Tools": {", ".join([tool_called.tool.name for tool_called in self.tools_called])}
|
467
|
+
"""
|
468
|
+
|
469
|
+
|
470
|
+
|
470
471
|
|
471
472
|
class ConditionalTask(Task):
|
472
473
|
"""
|
versionhq/team/model.py
CHANGED
@@ -161,54 +161,6 @@ class Team(BaseModel):
|
|
161
161
|
return self.name if self.name is not None else self.id.__str__
|
162
162
|
|
163
163
|
|
164
|
-
@property
|
165
|
-
def key(self) -> str:
|
166
|
-
source = [str(member.agent.id.__str__) for member in self.members] + [str(task.id.__str__) for task in self.tasks]
|
167
|
-
return md5("|".join(source).encode(), usedforsecurity=False).hexdigest()
|
168
|
-
|
169
|
-
|
170
|
-
@property
|
171
|
-
def managers(self) -> List[TeamMember] | None:
|
172
|
-
managers = [member for member in self.members if member.is_manager == True]
|
173
|
-
return managers if len(managers) > 0 else None
|
174
|
-
|
175
|
-
|
176
|
-
@property
|
177
|
-
def manager_tasks(self) -> List[Task] | None:
|
178
|
-
"""
|
179
|
-
Tasks (incl. team tasks) handled by managers in the team.
|
180
|
-
"""
|
181
|
-
if self.managers:
|
182
|
-
tasks = [manager.task for manager in self.managers if manager.task is not None]
|
183
|
-
return tasks if len(tasks) > 0 else None
|
184
|
-
|
185
|
-
return None
|
186
|
-
|
187
|
-
|
188
|
-
@property
|
189
|
-
def tasks(self):
|
190
|
-
"""
|
191
|
-
Return all the tasks that the team needs to handle in order of priority:
|
192
|
-
1. team tasks, -> assigned to the member
|
193
|
-
2. manager_task,
|
194
|
-
3. members' tasks
|
195
|
-
"""
|
196
|
-
|
197
|
-
team_tasks = self.team_tasks
|
198
|
-
manager_tasks = [member.task for member in self.members if member.is_manager == True and member.task is not None and member.task not in team_tasks]
|
199
|
-
member_tasks = [member.task for member in self.members if member.is_manager == False and member.task is not None and member.task not in team_tasks]
|
200
|
-
|
201
|
-
return team_tasks + manager_tasks + member_tasks
|
202
|
-
|
203
|
-
|
204
|
-
@property
|
205
|
-
def member_tasks_without_agent(self) -> List[Task] | None:
|
206
|
-
if self.members:
|
207
|
-
return [member.task for member in self.members if member.agent is None]
|
208
|
-
|
209
|
-
return None
|
210
|
-
|
211
|
-
|
212
164
|
@field_validator("id", mode="before")
|
213
165
|
@classmethod
|
214
166
|
def _deny_user_set_id(cls, v: Optional[UUID4]) -> None:
|
@@ -218,7 +170,7 @@ class Team(BaseModel):
|
|
218
170
|
|
219
171
|
|
220
172
|
@model_validator(mode="after")
|
221
|
-
def
|
173
|
+
def assess_tasks(self):
|
222
174
|
"""
|
223
175
|
Validates if the model recognize all tasks that the team needs to handle.
|
224
176
|
"""
|
@@ -261,7 +213,7 @@ class Team(BaseModel):
|
|
261
213
|
if self.process == TaskHandlingProcess.sequential and self.team_tasks is None:
|
262
214
|
for member in self.members:
|
263
215
|
if member.task is None:
|
264
|
-
raise PydanticCustomError("missing_agent_in_task",
|
216
|
+
raise PydanticCustomError("missing_agent_in_task", "Sequential process error: Agent is missing the task", {})
|
265
217
|
return self
|
266
218
|
|
267
219
|
@model_validator(mode="after")
|
@@ -280,11 +232,7 @@ class Team(BaseModel):
|
|
280
232
|
break
|
281
233
|
|
282
234
|
if async_task_count > 1:
|
283
|
-
raise PydanticCustomError(
|
284
|
-
"async_task_count",
|
285
|
-
"The team must end with max. one asynchronous task.",
|
286
|
-
{},
|
287
|
-
)
|
235
|
+
raise PydanticCustomError("async_task_count", "The team must end with max. one asynchronous task.", {})
|
288
236
|
return self
|
289
237
|
|
290
238
|
|
@@ -310,7 +258,6 @@ class Team(BaseModel):
|
|
310
258
|
unassigned_tasks: List[Task] = self.member_tasks_without_agent
|
311
259
|
new_team_members: List[TeamMember] = []
|
312
260
|
|
313
|
-
|
314
261
|
if self.team_tasks:
|
315
262
|
candidates = idling_managers + idling_members
|
316
263
|
if candidates:
|
@@ -468,7 +415,6 @@ class Team(BaseModel):
|
|
468
415
|
|
469
416
|
metrics: List[UsageMetrics] = []
|
470
417
|
|
471
|
-
|
472
418
|
if self.team_tasks or self.member_tasks_without_agent:
|
473
419
|
self._handle_team_planning()
|
474
420
|
|
@@ -511,3 +457,51 @@ class Team(BaseModel):
|
|
511
457
|
self.usage_metrics.add_usage_metrics(metric)
|
512
458
|
|
513
459
|
return result
|
460
|
+
|
461
|
+
|
462
|
+
@property
|
463
|
+
def key(self) -> str:
|
464
|
+
source = [str(member.agent.id.__str__) for member in self.members] + [str(task.id.__str__) for task in self.tasks]
|
465
|
+
return md5("|".join(source).encode(), usedforsecurity=False).hexdigest()
|
466
|
+
|
467
|
+
|
468
|
+
@property
|
469
|
+
def managers(self) -> List[TeamMember] | None:
|
470
|
+
managers = [member for member in self.members if member.is_manager == True]
|
471
|
+
return managers if len(managers) > 0 else None
|
472
|
+
|
473
|
+
|
474
|
+
@property
|
475
|
+
def manager_tasks(self) -> List[Task] | None:
|
476
|
+
"""
|
477
|
+
Tasks (incl. team tasks) handled by managers in the team.
|
478
|
+
"""
|
479
|
+
if self.managers:
|
480
|
+
tasks = [manager.task for manager in self.managers if manager.task is not None]
|
481
|
+
return tasks if len(tasks) > 0 else None
|
482
|
+
|
483
|
+
return None
|
484
|
+
|
485
|
+
|
486
|
+
@property
|
487
|
+
def tasks(self):
|
488
|
+
"""
|
489
|
+
Return all the tasks that the team needs to handle in order of priority:
|
490
|
+
1. team tasks, -> assigned to the member
|
491
|
+
2. manager_task,
|
492
|
+
3. members' tasks
|
493
|
+
"""
|
494
|
+
|
495
|
+
team_tasks = self.team_tasks
|
496
|
+
manager_tasks = [member.task for member in self.members if member.is_manager == True and member.task is not None and member.task not in team_tasks]
|
497
|
+
member_tasks = [member.task for member in self.members if member.is_manager == False and member.task is not None and member.task not in team_tasks]
|
498
|
+
|
499
|
+
return team_tasks + manager_tasks + member_tasks
|
500
|
+
|
501
|
+
|
502
|
+
@property
|
503
|
+
def member_tasks_without_agent(self) -> List[Task] | None:
|
504
|
+
if self.members:
|
505
|
+
return [member.task for member in self.members if member.agent is None]
|
506
|
+
|
507
|
+
return None
|
versionhq/tool/__init__.py
CHANGED
@@ -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
|
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,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,
|
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
|
10
|
-
"""
|
11
|
-
The function that will be executed when the tool is called.
|
12
|
-
"""
|
9
|
+
class BaseTool(ABC, BaseModel):
|
13
10
|
|
14
|
-
class
|
11
|
+
class _ArgsSchemaPlaceholder(BaseModel):
|
15
12
|
pass
|
16
13
|
|
17
|
-
model_config = ConfigDict()
|
18
14
|
name: str = Field(default=None)
|
19
|
-
|
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.
|
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
|
-
|
57
|
-
|
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,112 +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
|
-
|
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
|
-
|
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
|
-
|
108
|
-
raise ValueError("
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
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
|
-
|
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
|
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
141
|
result = None
|
141
142
|
|
142
|
-
if self.
|
143
|
-
result = self.
|
144
|
-
*args, **kwargs
|
145
|
-
) #! REFINEME - format - json dict, pydantic, raw
|
143
|
+
if self.function is not None:
|
144
|
+
result = self.function(*args, **kwargs)
|
146
145
|
|
147
146
|
else:
|
148
147
|
acceptable_args = self.args_schema.model_json_schema()["properties"].keys()
|
149
|
-
|
150
|
-
tool_called =
|
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)
|
151
150
|
|
152
151
|
if self.tool_handler:
|
153
152
|
if self.tool_handler.has_called_before(tool_called):
|
154
|
-
self.tool_handler.error = "Agent execution error
|
153
|
+
self.tool_handler.error = "Agent execution error"
|
155
154
|
|
156
155
|
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
|
156
|
+
result = self.tools_handler.cache.read(tool=tool_called.tool.name, input=tool_called.kwargs)
|
161
157
|
if result is None:
|
162
|
-
|
158
|
+
parsed_kwargs = self._parse_args(input=acceptable_kwargs)
|
159
|
+
result = self.function(**parsed_kwargs)
|
163
160
|
|
164
161
|
else:
|
165
|
-
|
166
|
-
|
167
|
-
tool_handler = ToolHandler(
|
168
|
-
last_used_tool=tool_called, cache=CacheHandler()
|
169
|
-
)
|
162
|
+
tool_handler = ToolHandler(last_used_tool=tool_called, cache_handler=CacheHandler())
|
170
163
|
self.tool_handler = tool_handler
|
171
|
-
|
164
|
+
parsed_kwargs = self._parse_args(input=acceptable_kwargs)
|
165
|
+
result = self.function(**parsed_kwargs)
|
172
166
|
|
173
167
|
return result
|
174
168
|
|
175
169
|
|
176
|
-
|
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):
|
177
204
|
"""
|
178
205
|
Store the tool called and any kwargs used.
|
179
206
|
"""
|
180
|
-
|
181
207
|
tool: InstanceOf[Tool] = Field(..., description="store the tool instance to be called.")
|
182
|
-
|
208
|
+
kwargs: Optional[Dict[str, Any]] = Field(..., description="kwargs passed to the tool")
|
183
209
|
|
184
210
|
|
185
|
-
class
|
211
|
+
class InstructorToolSet(BaseModel):
|
186
212
|
tool: InstanceOf[Tool] = Field(..., description="store the tool instance to be called.")
|
187
|
-
|
213
|
+
kwargs: Optional[Dict[str, Any]] = Field(..., description="kwargs passed to the tool")
|
188
214
|
|
189
215
|
|
190
216
|
class CacheTool(BaseModel):
|
versionhq/tool/tool_handler.py
CHANGED
@@ -1,48 +1,43 @@
|
|
1
1
|
from typing import Any, Optional
|
2
2
|
from pydantic import InstanceOf
|
3
|
-
from versionhq.tool.model import
|
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
|
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:
|
12
|
+
last_used_tool: InstanceOf[ToolSet] | InstanceOf[InstructorToolSet]
|
14
13
|
cache: Optional[CacheHandler]
|
15
14
|
error: Optional[str]
|
16
15
|
|
17
|
-
def __init__(
|
18
|
-
|
19
|
-
|
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[
|
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
|
-
|
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:
|
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.
|
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`
|
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,4 +1,4 @@
|
|
1
|
-
versionhq/__init__.py,sha256=
|
1
|
+
versionhq/__init__.py,sha256=rtE16vRgpvQ78kBeWEl5kWQgGtGW0G5vHOgwC5hlu1w,931
|
2
2
|
versionhq/_utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
3
3
|
versionhq/_utils/cache_handler.py,sha256=zDQKzIn7vp-M2-uepHFxgJstjfftZS5mzXKL_-4uVvI,370
|
4
4
|
versionhq/_utils/i18n.py,sha256=TwA_PnYfDLA6VqlUDPuybdV9lgi3Frh_ASsb_X8jJo8,1483
|
@@ -7,7 +7,7 @@ versionhq/_utils/process_config.py,sha256=UqoWD5IR4VLxEDGxIyVUylw_ppXwk8Wx1ynVuD
|
|
7
7
|
versionhq/_utils/rpm_controller.py,sha256=T7waIGeblu5K58erY4lqVLcPsWM7W9UFdU3DG9Dsk0w,2214
|
8
8
|
versionhq/_utils/usage_metrics.py,sha256=c33a_28y8ECUgflsKN3mkNm0fNkWgZmXwybMwIqoKXA,1098
|
9
9
|
versionhq/agent/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
10
|
-
versionhq/agent/model.py,sha256=
|
10
|
+
versionhq/agent/model.py,sha256=Sx4NJf3rElndcVzmPdgDgUFM3ATuvtXBwdM4AxB84UQ,18595
|
11
11
|
versionhq/agent/parser.py,sha256=db5kfk-lR1Ph9-rsTvSeW1NjR6GJ00iaqTNYxJy3N8o,5487
|
12
12
|
versionhq/agent/TEMPLATES/Backstory.py,sha256=cdngBx1GEv7nroR46FEhnysnBJ9mEVL763_9np6Skkc,395
|
13
13
|
versionhq/agent/TEMPLATES/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -18,7 +18,7 @@ versionhq/clients/customer/model.py,sha256=rQnCv_wdCdrYAsUjyB6X6ULiuWfqcBBoarXcQ
|
|
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=Suaih9odeAZOvNJ_wEy_iGAi8KnNJ1f_jIaXuhZTeAY,5833
|
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=Q2PqyepTLLeoAm7htHHtbc2j2ZXY0lrP2BVbhBCuPq4,19373
|
31
31
|
versionhq/team/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
32
|
-
versionhq/team/model.py,sha256=
|
32
|
+
versionhq/team/model.py,sha256=gkw0rLkEcyAQqYk_fLA83oSvcl7p9dZSocWU0cKeHV4,20301
|
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=ovoI3PaEuz9UvxLUKLidvSRdXDmaYxTMsZqpQPRugi0,7557
|
38
|
+
versionhq/tool/tool_handler.py,sha256=r30Fwp32IX716pcERm67Z7kHWQ7eUa6EYBNSyWrPcjo,1485
|
39
|
+
versionhq-1.1.9.0.dist-info/LICENSE,sha256=7CCXuMrAjPVsUvZrsBq9DsxI2rLDUSYXR_qj4yO_ZII,1077
|
40
|
+
versionhq-1.1.9.0.dist-info/METADATA,sha256=qZaCrHOVr6y8nzrSNHtvDo0iHQSYtHqzE4LwKmZIpR8,15955
|
41
|
+
versionhq-1.1.9.0.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
|
42
|
+
versionhq-1.1.9.0.dist-info/top_level.txt,sha256=DClQwxDWqIUGeRJkA8vBlgeNsYZs4_nJWMonzFt5Wj0,10
|
43
|
+
versionhq-1.1.9.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|