versionhq 1.1.9.1__py3-none-any.whl → 1.1.9.3__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 -3
- versionhq/_utils/logger.py +12 -19
- versionhq/agent/model.py +0 -5
- versionhq/clients/customer/__init__.py +5 -0
- versionhq/clients/customer/model.py +24 -12
- versionhq/clients/product/model.py +9 -8
- versionhq/clients/workflow/model.py +16 -23
- versionhq/tool/__init__.py +7 -4
- versionhq/tool/composio_tool.py +191 -0
- versionhq/tool/model.py +35 -24
- versionhq/tool/tool_handler.py +10 -10
- {versionhq-1.1.9.1.dist-info → versionhq-1.1.9.3.dist-info}/METADATA +5 -2
- {versionhq-1.1.9.1.dist-info → versionhq-1.1.9.3.dist-info}/RECORD +16 -16
- {versionhq-1.1.9.1.dist-info → versionhq-1.1.9.3.dist-info}/WHEEL +1 -1
- versionhq/tool/composio.py +0 -149
- {versionhq-1.1.9.1.dist-info → versionhq-1.1.9.3.dist-info}/LICENSE +0 -0
- {versionhq-1.1.9.1.dist-info → versionhq-1.1.9.3.dist-info}/top_level.txt +0 -0
versionhq/__init__.py
CHANGED
@@ -15,10 +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.
|
18
|
+
from versionhq.tool.composio_tool import ComposioHandler
|
19
19
|
|
20
20
|
|
21
|
-
__version__ = "1.1.9.
|
21
|
+
__version__ = "1.1.9.3"
|
22
22
|
__all__ = [
|
23
23
|
"Agent",
|
24
24
|
"Customer",
|
@@ -34,5 +34,5 @@ __all__ = [
|
|
34
34
|
"Team",
|
35
35
|
"TeamOutput",
|
36
36
|
"Tool",
|
37
|
-
"
|
37
|
+
"ComposioHandler"
|
38
38
|
]
|
versionhq/_utils/logger.py
CHANGED
@@ -10,23 +10,21 @@ class Printer:
|
|
10
10
|
self._print_purple(content)
|
11
11
|
elif color == "red":
|
12
12
|
self._print_red(content)
|
13
|
-
elif color == "
|
14
|
-
self.
|
15
|
-
elif color == "
|
16
|
-
self.
|
17
|
-
elif color == "
|
18
|
-
self.
|
13
|
+
elif color == "green":
|
14
|
+
self._print_green(content)
|
15
|
+
elif color == "purple":
|
16
|
+
self._print_purple(content)
|
17
|
+
elif color == "blue":
|
18
|
+
self._print_blue(content)
|
19
19
|
elif color == "yellow":
|
20
20
|
self._print_yellow(content)
|
21
|
-
elif color == "bold_yellow":
|
22
|
-
self._print_bold_yellow(content)
|
23
21
|
else:
|
24
22
|
print(content)
|
25
23
|
|
26
|
-
def
|
24
|
+
def _print_purple(self, content):
|
27
25
|
print("\033[1m\033[95m {}\033[00m".format(content))
|
28
26
|
|
29
|
-
def
|
27
|
+
def _print_green(self, content):
|
30
28
|
print("\033[1m\033[92m {}\033[00m".format(content))
|
31
29
|
|
32
30
|
def _print_purple(self, content):
|
@@ -35,23 +33,18 @@ class Printer:
|
|
35
33
|
def _print_red(self, content):
|
36
34
|
print("\033[91m {}\033[00m".format(content))
|
37
35
|
|
38
|
-
def
|
36
|
+
def _print_blue(self, content):
|
39
37
|
print("\033[1m\033[94m {}\033[00m".format(content))
|
40
38
|
|
41
39
|
def _print_yellow(self, content):
|
42
|
-
print("\033[93m {}\033[00m".format(content))
|
43
|
-
|
44
|
-
def _print_bold_yellow(self, content):
|
45
40
|
print("\033[1m\033[93m {}\033[00m".format(content))
|
46
41
|
|
47
42
|
|
48
43
|
class Logger(BaseModel):
|
49
|
-
verbose: bool = Field(default=
|
44
|
+
verbose: bool = Field(default=True)
|
50
45
|
_printer: Printer = PrivateAttr(default_factory=Printer)
|
51
46
|
|
52
|
-
def log(self, level, message, color="
|
47
|
+
def log(self, level, message, color="yellow"):
|
53
48
|
if self.verbose:
|
54
49
|
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
55
|
-
self._printer.print(
|
56
|
-
f"\n[{timestamp}][{level.upper()}]: {message}", color=color
|
57
|
-
)
|
50
|
+
self._printer.print(f"\n[{timestamp}][{level.upper()}]: {message}", color=color)
|
versionhq/agent/model.py
CHANGED
@@ -6,11 +6,9 @@ from dotenv import load_dotenv
|
|
6
6
|
from pydantic import UUID4, BaseModel, Field, InstanceOf, PrivateAttr, model_validator, field_validator
|
7
7
|
from pydantic_core import PydanticCustomError
|
8
8
|
|
9
|
-
from versionhq._utils.cache_handler import CacheHandler
|
10
9
|
from versionhq._utils.logger import Logger
|
11
10
|
from versionhq._utils.rpm_controller import RPMController
|
12
11
|
from versionhq._utils.usage_metrics import UsageMetrics
|
13
|
-
from versionhq.agent.parser import AgentAction
|
14
12
|
from versionhq.llm.llm_vars import LLM_VARS
|
15
13
|
from versionhq.llm.model import LLM, DEFAULT_CONTEXT_WINDOW
|
16
14
|
from versionhq.task import TaskOutputFormat
|
@@ -122,10 +120,7 @@ class Agent(BaseModel):
|
|
122
120
|
|
123
121
|
# config, cache, error handling
|
124
122
|
config: Optional[Dict[str, Any]] = Field(default=None, exclude=True, description="Configuration for the agent")
|
125
|
-
cache: bool = Field(default=True, description="Whether the agent should use a cache for tool usage.")
|
126
|
-
cache_handler: InstanceOf[CacheHandler] = Field(default=None, description="An instance of the CacheHandler class.")
|
127
123
|
formatting_errors: int = Field(default=0, description="Number of formatting errors.")
|
128
|
-
verbose: bool = Field(default=True, description="Verbose mode for the Agent Execution")
|
129
124
|
agent_ops_agent_name: str = None
|
130
125
|
agent_ops_agent_id: str = None
|
131
126
|
|
@@ -1,41 +1,53 @@
|
|
1
1
|
import uuid
|
2
|
-
from abc import ABC
|
2
|
+
from abc import ABC, abstractmethod
|
3
3
|
from typing import Any, Dict, List, Callable, Type, Optional, get_args, get_origin
|
4
4
|
from pydantic import UUID4, InstanceOf, BaseModel, ConfigDict, Field, create_model, field_validator, model_validator
|
5
5
|
from pydantic_core import PydanticCustomError
|
6
6
|
|
7
7
|
from versionhq.clients.product.model import Product, ProductProvider
|
8
|
+
from versionhq.clients.customer import Status
|
8
9
|
|
9
10
|
|
10
|
-
class
|
11
|
+
class BaseCustomer(ABC, BaseModel):
|
11
12
|
"""
|
12
|
-
|
13
|
+
Abstract base class for the base customer
|
13
14
|
"""
|
14
15
|
|
15
16
|
id: UUID4 = Field(default_factory=uuid.uuid4, frozen=True)
|
16
17
|
name: Optional[str] = Field(default=None, description="customer's name if any")
|
17
|
-
|
18
|
+
products: Optional[List[Product]] = Field(default=list, description="store products that the customer is associated with")
|
18
19
|
analysis: str = Field(default=None, description="store the latest analysis results on the customer")
|
19
|
-
|
20
|
-
on: Optional[str] = Field(default=None, description="destination service for this customer if any")
|
20
|
+
status: str = Field(default=Status.ON_WORKFLOW)
|
21
21
|
|
22
22
|
|
23
23
|
@field_validator("id", mode="before")
|
24
24
|
@classmethod
|
25
25
|
def _deny_user_set_id(cls, v: Optional[UUID4]) -> None:
|
26
26
|
if v:
|
27
|
-
raise PydanticCustomError(
|
28
|
-
|
29
|
-
)
|
27
|
+
raise PydanticCustomError("may_not_set_field", "This field is not to be set by the user.", {})
|
28
|
+
|
30
29
|
|
31
30
|
def customer_to(self) -> List[ProductProvider]:
|
32
31
|
"""
|
33
32
|
Return list of ProductProvider if the customer has `product_list`
|
34
33
|
"""
|
35
34
|
|
36
|
-
res =
|
37
|
-
if self.
|
38
|
-
for item in self.
|
35
|
+
res = []
|
36
|
+
if self.products:
|
37
|
+
for item in self.products:
|
39
38
|
if item.provider not in res:
|
40
39
|
res.appned(item.provider)
|
41
40
|
return res
|
41
|
+
|
42
|
+
|
43
|
+
@abstractmethod
|
44
|
+
def _deploy(self, *args, **kwargs) -> Any:
|
45
|
+
"""Any method to deploy targeting the customer"""
|
46
|
+
|
47
|
+
|
48
|
+
class Customer(BaseCustomer):
|
49
|
+
id: UUID4 = Field(default_factory=uuid.uuid4, frozen=True)
|
50
|
+
name: Optional[str] = Field(default=None, description="customer's name if any")
|
51
|
+
products: Optional[List[Product]] = Field(default=list, description="store products that the customer is associated with")
|
52
|
+
analysis: str = Field(default=None, description="store the latest analysis results on the customer")
|
53
|
+
status: str = Field(default=Status.ON_WORKFLOW)
|
@@ -1,22 +1,23 @@
|
|
1
1
|
import uuid
|
2
|
+
from abc import ABC, abstractmethod
|
2
3
|
from typing import Any, Dict, List, Callable, Type, Optional, get_args, get_origin
|
4
|
+
|
3
5
|
from pydantic import UUID4, InstanceOf, BaseModel, ConfigDict, Field, create_model, field_validator, model_validator
|
4
6
|
from pydantic_core import PydanticCustomError
|
5
7
|
|
8
|
+
from versionhq.tool import ComposioAppName
|
9
|
+
|
6
10
|
|
7
|
-
class ProductProvider(BaseModel):
|
11
|
+
class ProductProvider(ABC, BaseModel):
|
8
12
|
"""
|
9
|
-
|
10
|
-
`data_pipeline` and `destinations` are for composio plug-in.
|
11
|
-
(!REFINEME) Create an Enum list for the options.
|
12
|
-
(!REFINEME) Create an Enum list for regions.
|
13
|
+
Abstract class for the product provider entity.
|
13
14
|
"""
|
14
15
|
|
15
16
|
id: UUID4 = Field(default_factory=uuid.uuid4, frozen=True)
|
16
|
-
name: Optional[str] = Field(default=None
|
17
|
+
name: Optional[str] = Field(default=None)
|
17
18
|
region: Optional[str] = Field(default=None, description="region of client's main business operation")
|
18
|
-
|
19
|
-
|
19
|
+
data_pipelines: Optional[List[ComposioAppName | str]] = Field(default_factory=list)
|
20
|
+
destination_services: Optional[List[ComposioAppName | str]] = Field(default=None)
|
20
21
|
|
21
22
|
@field_validator("id", mode="before")
|
22
23
|
@classmethod
|
@@ -10,6 +10,7 @@ from versionhq.clients.product.model import Product
|
|
10
10
|
from versionhq.clients.customer.model import Customer
|
11
11
|
from versionhq.agent.model import Agent
|
12
12
|
from versionhq.team.model import Team
|
13
|
+
from versionhq.tool import ComposioAppName
|
13
14
|
|
14
15
|
|
15
16
|
class ScoreFormat:
|
@@ -57,14 +58,12 @@ class Score:
|
|
57
58
|
class MessagingComponent(ABC, BaseModel):
|
58
59
|
layer_id: int = Field(default=0, description="add id of the layer: 0, 1, 2")
|
59
60
|
message: str = Field(default=None, max_length=1024, description="text message content to be sent")
|
60
|
-
|
61
|
-
|
62
|
-
)
|
63
|
-
score: float | InstanceOf[Score] = Field(default=None)
|
64
|
-
condition: str = Field(default=None, max_length=128, description="condition to execute the next messaging component")
|
61
|
+
score: InstanceOf[Score] = Field(default=None)
|
62
|
+
condition: str = Field(default=None, max_length=128, description="condition to execute the next component")
|
63
|
+
interval: Optional[str] = Field(default=None, description="ideal interval to set to assess the condition")
|
65
64
|
|
66
65
|
|
67
|
-
def store_scoring_result(self,
|
66
|
+
def store_scoring_result(self, subject: str, score_raw: int | Score | ScoreFormat = None) -> Self:
|
68
67
|
"""
|
69
68
|
Set up the `score` field
|
70
69
|
"""
|
@@ -74,12 +73,12 @@ class MessagingComponent(ABC, BaseModel):
|
|
74
73
|
|
75
74
|
elif isinstance(score_raw, ScoreFormat):
|
76
75
|
score_instance = Score()
|
77
|
-
setattr(score_instance,
|
76
|
+
setattr(score_instance, subject, score_raw)
|
78
77
|
setattr(self, "score", score_instance)
|
79
78
|
|
80
79
|
elif isinstance(score_raw, int) or isinstance(score_raw, float):
|
81
80
|
score_instance, score_format_instance = Score(), ScoreFormat(rate=score_raw, weight=1)
|
82
|
-
setattr(score_instance, "kwargs", {
|
81
|
+
setattr(score_instance, "kwargs", { subject: score_format_instance })
|
83
82
|
setattr(self, "score", score_instance)
|
84
83
|
|
85
84
|
else:
|
@@ -100,23 +99,17 @@ class MessagingWorkflow(ABC, BaseModel):
|
|
100
99
|
model_config = ConfigDict()
|
101
100
|
|
102
101
|
id: UUID4 = Field(default_factory=uuid.uuid4, frozen=True)
|
103
|
-
|
102
|
+
messaging_components: List[MessagingComponent] = Field(default_factory=list, description="store messaging components in the workflow")
|
104
103
|
|
105
104
|
# responsible tean or agents
|
106
|
-
team: Optional[Team] = Field(default=None, description="store
|
107
|
-
agents: Optional[List[Agent]] = Field(
|
108
|
-
default=None, description="store `Agent` instances responsible for autopiloting this workflow. if the team exsits, this field remains as `None`")
|
105
|
+
team: Optional[Team] = Field(default=None, description="store a responsibile team to autopilot the workflow")
|
106
|
+
agents: Optional[List[Agent]] = Field(default=None, description="store responsible agents. None when the team exists")
|
109
107
|
|
110
108
|
# metrics
|
111
|
-
destination: Optional[
|
109
|
+
destination: Optional[ComposioAppName | str] = Field(default=None, description="destination service to launch the workflow")
|
112
110
|
product: InstanceOf[Product] = Field(default=None)
|
113
111
|
customer: InstanceOf[Customer] = Field(default=None)
|
114
|
-
|
115
|
-
metrics: List[Dict[str, Any]] | List[str] = Field(
|
116
|
-
default=None,
|
117
|
-
max_length=256,
|
118
|
-
description="store metrics that used to predict and track the performance of this workflow."
|
119
|
-
)
|
112
|
+
performance_metrics: List[Dict[str, Any]] | List[str] = Field(default=None, max_length=256, description="performance metrics to track")
|
120
113
|
|
121
114
|
@field_validator("id", mode="before")
|
122
115
|
@classmethod
|
@@ -132,11 +125,11 @@ class MessagingWorkflow(ABC, BaseModel):
|
|
132
125
|
Prioritize customer's destination to the product provider's destination list.
|
133
126
|
"""
|
134
127
|
if self.destination is None:
|
135
|
-
if self.customer is not None:
|
136
|
-
|
128
|
+
# if self.customer is not None:
|
129
|
+
# self.destination = self.customer.on
|
137
130
|
|
138
|
-
|
139
|
-
self.destination = self.product.provider.
|
131
|
+
if self.product.provider is not None and self.product.provider.destination_services:
|
132
|
+
self.destination = self.product.provider.destination_services[0]
|
140
133
|
|
141
134
|
return self
|
142
135
|
|
versionhq/tool/__init__.py
CHANGED
@@ -40,10 +40,11 @@ composio_app_set = [
|
|
40
40
|
(ComposioAppName.LINKEDIN, ComposioAuthScheme.OAUTH2),
|
41
41
|
]
|
42
42
|
|
43
|
-
class
|
44
|
-
INITIATED = "
|
45
|
-
ACTIVE = "
|
46
|
-
FAILED = "
|
43
|
+
class ComposioStatus(str, Enum):
|
44
|
+
INITIATED = "INITIATED"
|
45
|
+
ACTIVE = "ACTIVE"
|
46
|
+
FAILED = "FAILED"
|
47
|
+
|
47
48
|
|
48
49
|
|
49
50
|
|
@@ -51,3 +52,5 @@ class ComposioAction(str, Enum):
|
|
51
52
|
"""
|
52
53
|
Enum to store composio's action that can be called via `Actions.xxx`
|
53
54
|
"""
|
55
|
+
# HUBSPOT_INITIATE_DATA_IMPORT_PROCESS = "hubspot_initate_date_import_process"
|
56
|
+
HUBSPOT_CREATE_PIPELINE_STAGE = "hubspot_create_pipeline_stage"
|
@@ -0,0 +1,191 @@
|
|
1
|
+
import os
|
2
|
+
import uuid
|
3
|
+
from abc import ABC
|
4
|
+
from dotenv import load_dotenv
|
5
|
+
from typing import Any, Callable, Type, get_args, get_origin, Optional, Tuple, Dict
|
6
|
+
from typing_extensions import Self
|
7
|
+
|
8
|
+
from pydantic import BaseModel, Field, model_validator, field_validator, UUID4, PrivateAttr
|
9
|
+
from pydantic_core import PydanticCustomError
|
10
|
+
|
11
|
+
from composio import ComposioToolSet
|
12
|
+
from composio_langchain import action
|
13
|
+
|
14
|
+
from versionhq.tool import ComposioAppName, ComposioAuthScheme, composio_app_set, ComposioStatus, ComposioAction
|
15
|
+
from versionhq._utils.logger import Logger
|
16
|
+
from versionhq._utils.cache_handler import CacheHandler
|
17
|
+
|
18
|
+
load_dotenv(override=True)
|
19
|
+
|
20
|
+
DEFAULT_REDIRECT_URL = os.environ.get("DEFAULT_REDIRECT_URL", None)
|
21
|
+
DEFAULT_USER_ID = os.environ.get("DEFAULT_USER_ID", None)
|
22
|
+
OPENAI_API_KEY = os.environ.get("OPENAI_API_KEY", None)
|
23
|
+
|
24
|
+
|
25
|
+
class ComposioHandler(ABC, BaseModel):
|
26
|
+
"""
|
27
|
+
A class to handle connecting account with Composio and executing actions using Composio ecosystem.
|
28
|
+
`connected_account_id` is set up per `app_name` to call the actions on the given app. i.e., salesforce
|
29
|
+
"""
|
30
|
+
|
31
|
+
_logger: Logger = PrivateAttr(default_factory=lambda: Logger(verbose=True))
|
32
|
+
_cache: CacheHandler = PrivateAttr(default_factory=lambda: CacheHandler())
|
33
|
+
|
34
|
+
id: UUID4 = Field(default_factory=uuid.uuid4, frozen=True)
|
35
|
+
app_name: str = Field(default=ComposioAppName.HUBSPOT, max_length=128, description="app name defined by composio")
|
36
|
+
user_id: str = Field(default=DEFAULT_USER_ID, description="composio entity id")
|
37
|
+
auth_scheme: str = Field(default=ComposioAuthScheme.OAUTH2)
|
38
|
+
redirect_url: str = Field(default=DEFAULT_REDIRECT_URL, description="redirect url after successful oauth2 connection")
|
39
|
+
connected_account_id: str = Field(
|
40
|
+
default=None,
|
41
|
+
description="store the client id generated by composio after auth validation. use the id to connect with a given app and execute composio actions"
|
42
|
+
)
|
43
|
+
tools: Any = Field(default=None, descritpion="retrieved composio tools")
|
44
|
+
|
45
|
+
@field_validator("id", mode="before")
|
46
|
+
@classmethod
|
47
|
+
def _deny_user_set_id(cls, v: Optional[UUID4]) -> None:
|
48
|
+
if v:
|
49
|
+
raise PydanticCustomError("may_not_set_field", "This field is not to be set by the user.", {})
|
50
|
+
|
51
|
+
|
52
|
+
@model_validator(mode="after")
|
53
|
+
def validate_app_name(self):
|
54
|
+
if self.app_name not in ComposioAppName:
|
55
|
+
raise PydanticCustomError("no_app_name", f"The given app name {self.app_name} is not valid.", {})
|
56
|
+
|
57
|
+
return self
|
58
|
+
|
59
|
+
|
60
|
+
@model_validator(mode="after")
|
61
|
+
def validate_auth_scheme(self):
|
62
|
+
"""
|
63
|
+
Raise error when the client uses auth scheme unavailable for the app.
|
64
|
+
"""
|
65
|
+
app_set = next(filter(lambda tup: self.app_name in tup, composio_app_set), None)
|
66
|
+
if not app_set:
|
67
|
+
raise PydanticCustomError("no_app_set", f"The app set of {self.app_name} is missing.", {})
|
68
|
+
|
69
|
+
else:
|
70
|
+
acceptable_auth_scheme = next(filter(lambda item: self.auth_scheme in item, app_set), None)
|
71
|
+
if acceptable_auth_scheme is None:
|
72
|
+
raise PydanticCustomError("invalid_auth_scheme", f"The app {self.app_name} must have different auth_scheme.", {})
|
73
|
+
|
74
|
+
return self
|
75
|
+
|
76
|
+
|
77
|
+
def _setup_langchain_toolset(self, metadata: Dict[str, Any] = dict()):
|
78
|
+
"""
|
79
|
+
Composio toolset on LangChain for action execution using LLM.
|
80
|
+
"""
|
81
|
+
from composio_langchain import ComposioToolSet
|
82
|
+
return ComposioToolSet(api_key=os.environ.get("COMPOSIO_API_KEY"), metadata={**metadata})
|
83
|
+
|
84
|
+
|
85
|
+
def _connect(
|
86
|
+
self, token: Optional[str] = None, api_key: Optional[str] = None, connected_account_id: str = None
|
87
|
+
) -> Tuple[Self | str | Any]:
|
88
|
+
"""
|
89
|
+
Send connection request to Composio, retrieve `connected_account_id`, and proceed with OAuth process of the given app to activate the connection.
|
90
|
+
"""
|
91
|
+
|
92
|
+
connection_request, connected_account = None, None
|
93
|
+
connected_account_id = connected_account_id or self.connected_account_id
|
94
|
+
if connected_account_id:
|
95
|
+
connected_account = self.toolset.get_connected_account(id=connected_account_id)
|
96
|
+
|
97
|
+
if connected_account and connected_account.status == ComposioStatus.ACTIVE.value:
|
98
|
+
return self, ComposioStatus.ACTIVE.value
|
99
|
+
|
100
|
+
if not self.user_id:
|
101
|
+
raise PydanticCustomError("entity_id_missing", "Need entity_id to connect with the tool", {})
|
102
|
+
|
103
|
+
if self.auth_scheme == ComposioAuthScheme.API_KEY:
|
104
|
+
collected_from_user = {}
|
105
|
+
collected_from_user["api_key"] = api_key
|
106
|
+
connection_request = self.toolset.initiate_connection(
|
107
|
+
connected_account_params = collected_from_user,
|
108
|
+
app=self.app_name,
|
109
|
+
entity_id=self.user_id,
|
110
|
+
auth_scheme=self.auth_scheme,
|
111
|
+
)
|
112
|
+
|
113
|
+
if self.auth_scheme == ComposioAuthScheme.BEARER_TOKEN:
|
114
|
+
collected_from_user = {}
|
115
|
+
collected_from_user["token"] = token
|
116
|
+
connection_request = self.toolset.initiate_connection(
|
117
|
+
connected_account_params = collected_from_user,
|
118
|
+
app=self.app_name,
|
119
|
+
entity_id=self.user_id,
|
120
|
+
auth_scheme=self.auth_scheme,
|
121
|
+
)
|
122
|
+
|
123
|
+
if self.auth_scheme == ComposioAuthScheme.OAUTH2:
|
124
|
+
connection_request = self.toolset.initiate_connection(
|
125
|
+
app=self.app_name,
|
126
|
+
redirect_url = self.redirect_url, # clients will be redirected to this url after successful auth.
|
127
|
+
entity_id=self.user_id,
|
128
|
+
auth_scheme=self.auth_scheme,
|
129
|
+
)
|
130
|
+
|
131
|
+
if connection_request.connectionStatus == ComposioStatus.FAILED.value:
|
132
|
+
self._logger.log(level="error", message="Connection to composio failed.", color="red")
|
133
|
+
raise PydanticCustomError("connection_failed", "Connection to composio has failed", {})
|
134
|
+
|
135
|
+
|
136
|
+
connected_account = self.toolset.get_connected_account(id=connection_request.connectedAccountId)
|
137
|
+
# Note: connected_account.id === connection_request.connectedAccountId === self.connected_account_id
|
138
|
+
|
139
|
+
if connected_account.status == ComposioStatus.ACTIVE.value:
|
140
|
+
setattr(self.toolset, "entity_id", self.user_id)
|
141
|
+
self.connected_account_id = connection_request.connectedAccountId
|
142
|
+
|
143
|
+
elif connected_account.status == ComposioStatus.INITIATED.value:
|
144
|
+
setattr(self.toolset, "entity_id", self.user_id)
|
145
|
+
self.connected_account_id = connection_request.connectedAccountId
|
146
|
+
|
147
|
+
if connection_request.redirectUrl:
|
148
|
+
import webbrowser
|
149
|
+
webbrowser.open(connection_request.redirectUrl)
|
150
|
+
|
151
|
+
else:
|
152
|
+
self._logger.log(level="error", message="The account is invalid.", color="red")
|
153
|
+
raise PydanticCustomError("connection_failed", "Connection to composio has failed", {})
|
154
|
+
|
155
|
+
return self, connected_account.status if connected_account else connection_request.connectionStatus
|
156
|
+
|
157
|
+
|
158
|
+
def execute_composio_action_with_langchain(self, action_name: str | ComposioAction, task_in_natural_language: str) -> Tuple[Self, str]:
|
159
|
+
"""
|
160
|
+
Execute Composio's Action using Langchain's agent ecosystem.
|
161
|
+
"""
|
162
|
+
from langchain import hub
|
163
|
+
from langchain_openai import ChatOpenAI
|
164
|
+
from langchain.agents import create_openai_functions_agent, AgentExecutor
|
165
|
+
from composio_langchain import Action
|
166
|
+
|
167
|
+
action_name = action_name.value if isinstance(action_name, ComposioAction) else action_name
|
168
|
+
action = Action(action_name)
|
169
|
+
metadata = { action: { "OPENAI_API_KEY": OPENAI_API_KEY } }
|
170
|
+
toolset = self._setup_langchain_toolset(metadata=metadata)
|
171
|
+
tools = toolset.get_tools(actions=[action_name,], entity_id=self.user_id)
|
172
|
+
if not tools:
|
173
|
+
self._logger.log(level="error", message=f"Tools related to {action_name} are not found on Langchain", color="red")
|
174
|
+
raise PydanticCustomError("tool_not_found", "Tools not found on Langchain", {})
|
175
|
+
|
176
|
+
self.tools = tools
|
177
|
+
llm = ChatOpenAI()
|
178
|
+
prompt = hub.pull("hwchase17/openai-functions-agent")
|
179
|
+
agent = create_openai_functions_agent(llm, tools, prompt)
|
180
|
+
agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)
|
181
|
+
result = agent_executor.invoke(dict(input=task_in_natural_language))
|
182
|
+
return self, result["output"]
|
183
|
+
|
184
|
+
|
185
|
+
@property
|
186
|
+
def toolset(self) -> ComposioToolSet:
|
187
|
+
return ComposioToolSet(api_key=os.environ.get("COMPOSIO_API_KEY"))
|
188
|
+
|
189
|
+
|
190
|
+
def __name__(self):
|
191
|
+
return self.app_name
|
versionhq/tool/model.py
CHANGED
@@ -41,18 +41,22 @@ class Tool(BaseTool):
|
|
41
41
|
name: str = Field(default=None)
|
42
42
|
goal: str = Field(default=None)
|
43
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")
|
44
46
|
cache_function: Callable = lambda _args=None, _result=None: True
|
45
|
-
|
46
|
-
|
47
|
-
description="store tool_handler to record the usage of this tool. to avoid circular import, set as Dict format",
|
48
|
-
)
|
47
|
+
cache_handler: Optional[InstanceOf[CacheHandler]] = Field(default=None)
|
48
|
+
|
49
49
|
|
50
50
|
@model_validator(mode="after")
|
51
51
|
def set_up_tool_handler(self) -> Self:
|
52
52
|
from versionhq.tool.tool_handler import ToolHandler
|
53
53
|
|
54
|
-
if self.tool_handler:
|
54
|
+
if self.tool_handler and not isinstance(self.tool_handler, ToolHandler):
|
55
55
|
ToolHandler(**self.tool_handler)
|
56
|
+
|
57
|
+
else:
|
58
|
+
self.tool_handler = ToolHandler(cache_handler=self.cache_handler, should_cache=self.should_cache)
|
59
|
+
|
56
60
|
return self
|
57
61
|
|
58
62
|
|
@@ -119,33 +123,40 @@ class Tool(BaseTool):
|
|
119
123
|
|
120
124
|
def run(self, *args, **kwargs) -> Any:
|
121
125
|
"""
|
122
|
-
Use tool
|
126
|
+
Use tool and record its usage if should_cache is True.
|
123
127
|
"""
|
124
128
|
from versionhq.tool.tool_handler import ToolHandler
|
125
129
|
|
130
|
+
result = None
|
131
|
+
tool_set = ToolSet(tool=self, kwargs={})
|
132
|
+
|
126
133
|
if self.function:
|
127
|
-
|
134
|
+
result = self.function(*args, **kwargs)
|
128
135
|
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
136
|
+
else:
|
137
|
+
acceptable_args = self.args_schema.model_json_schema()["properties"].keys()
|
138
|
+
acceptable_kwargs = { k: v for k, v in kwargs.items() if k in acceptable_args }
|
139
|
+
tool_set = ToolSet(tool=self, kwargs=acceptable_kwargs)
|
133
140
|
|
134
|
-
|
135
|
-
|
136
|
-
|
141
|
+
if self.tool_handler:
|
142
|
+
if self.tool_handler.has_called_before(tool_set):
|
143
|
+
self.tool_handler.error = "Agent execution error"
|
137
144
|
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
145
|
+
elif self.tool_handler.cache:
|
146
|
+
result = self.tools_handler.cache.read(tool=tool_set.tool.name, input=tool_set.kwargs)
|
147
|
+
if result is None:
|
148
|
+
parsed_kwargs = self._parse_args(raw_args=acceptable_kwargs)
|
149
|
+
result = self.function(**parsed_kwargs) if self.function else None
|
143
150
|
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
151
|
+
else:
|
152
|
+
tool_handler = ToolHandler(last_used_tool=tool_set, cache_handler=self.cache_handler, should_cache=self.should_cache)
|
153
|
+
self.tool_handler = tool_handler
|
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)
|
149
160
|
|
150
161
|
return result
|
151
162
|
|
versionhq/tool/tool_handler.py
CHANGED
@@ -13,33 +13,33 @@ class ToolHandler:
|
|
13
13
|
last_used_tool: InstanceOf[ToolSet] | InstanceOf[InstructorToolSet]
|
14
14
|
cache: Optional[CacheHandler]
|
15
15
|
error: Optional[str]
|
16
|
+
should_cache: bool
|
16
17
|
|
17
18
|
def __init__(
|
18
19
|
self,
|
19
20
|
last_used_tool: InstanceOf[ToolSet] | InstanceOf[InstructorToolSet] = None,
|
20
|
-
cache_handler: Optional[CacheHandler] = None
|
21
|
+
cache_handler: Optional[CacheHandler] = None,
|
22
|
+
should_cache: bool = True
|
21
23
|
):
|
22
24
|
self.cache = cache_handler
|
23
25
|
self.last_used_tool = last_used_tool
|
26
|
+
self.should_cache = should_cache
|
24
27
|
|
25
28
|
|
26
29
|
def record_last_tool_used(
|
27
|
-
self,
|
28
|
-
last_used_tool: InstanceOf[ToolSet] | InstanceOf[InstructorToolSet],
|
29
|
-
output: str,
|
30
|
-
should_cache: bool = True,
|
30
|
+
self, last_used_tool: InstanceOf[ToolSet] | InstanceOf[InstructorToolSet], output: str, should_cache: bool = True
|
31
31
|
) -> Any:
|
32
|
+
from versionhq.tool.model import CacheTool
|
32
33
|
|
33
34
|
self.last_used_tool = last_used_tool
|
34
35
|
|
35
|
-
from versionhq.tool.model import CacheTool
|
36
36
|
if self.cache and should_cache and last_used_tool.tool.name != CacheTool().name:
|
37
37
|
self.cache.add(tool=last_used_tool.tool.name, input=last_used_tool.kwargs, output=output)
|
38
38
|
|
39
39
|
|
40
|
-
def has_called_before(self,
|
41
|
-
if
|
40
|
+
def has_called_before(self, tool_set: ToolSet = None) -> bool:
|
41
|
+
if tool_set is None or not self.last_used_tool:
|
42
42
|
return False
|
43
43
|
|
44
|
-
if
|
45
|
-
return bool((
|
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.
|
3
|
+
Version: 1.1.9.3
|
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
|
@@ -52,12 +52,15 @@ Requires-Dist: setuptools>=75.6.0
|
|
52
52
|
Requires-Dist: wheel>=0.45.1
|
53
53
|
Requires-Dist: python-dotenv>=1.0.0
|
54
54
|
Requires-Dist: appdirs>=1.4.4
|
55
|
+
Requires-Dist: langchain>=0.3.14
|
56
|
+
Requires-Dist: langchain-openai>=0.2.14
|
57
|
+
Requires-Dist: composio-langchain>=0.6.12
|
55
58
|
|
56
59
|
# Overview
|
57
60
|
|
58
61
|

|
59
62
|
[](https://github.com/versionHQ/multi-agent-system/actions/workflows/publish.yml)
|
60
|
-

|
61
64
|

|
62
65
|

|
63
66
|
|
@@ -1,24 +1,24 @@
|
|
1
|
-
versionhq/__init__.py,sha256
|
1
|
+
versionhq/__init__.py,sha256=-0hi_ADxpAQM8is-cYDuL5N9F3c4CsqGFYC5txzYx0U,950
|
2
2
|
versionhq/_utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
3
3
|
versionhq/_utils/cache_handler.py,sha256=3-lw_5ZMWC8hnPAkSQULJ2V1FvZZ-wg9mQaUJGSOjI8,403
|
4
4
|
versionhq/_utils/i18n.py,sha256=TwA_PnYfDLA6VqlUDPuybdV9lgi3Frh_ASsb_X8jJo8,1483
|
5
|
-
versionhq/_utils/logger.py,sha256=
|
5
|
+
versionhq/_utils/logger.py,sha256=cPxDz1YrMH4ZsLzLc11XrKBfQbLWNNXRUEVUgjAeeOE,1591
|
6
6
|
versionhq/_utils/process_config.py,sha256=UqoWD5IR4VLxEDGxIyVUylw_ppXwk8Wx1ynVuD-pUSg,822
|
7
7
|
versionhq/_utils/rpm_controller.py,sha256=dUgFd6JtdjiLLTRmrjsBHdTaLn73XFuKpLbJh7thf2A,2289
|
8
8
|
versionhq/_utils/usage_metrics.py,sha256=hhq1OCW8Z4V93vwW2O2j528EyjOlF8wlTsX5IL-7asA,1106
|
9
9
|
versionhq/agent/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
10
|
-
versionhq/agent/model.py,sha256=
|
10
|
+
versionhq/agent/model.py,sha256=c5beL2iGx7Zpxla59JJPQPUjpBa3hJdeVqspYGsHTm8,18731
|
11
11
|
versionhq/agent/parser.py,sha256=Z_swUPO3piJQuYU8oVYwXWeR2zjmNb4PxbXZeR-GlIg,4694
|
12
12
|
versionhq/agent/TEMPLATES/Backstory.py,sha256=cdngBx1GEv7nroR46FEhnysnBJ9mEVL763_9np6Skkc,395
|
13
13
|
versionhq/agent/TEMPLATES/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
14
14
|
versionhq/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
15
15
|
versionhq/clients/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
16
|
-
versionhq/clients/customer/__init__.py,sha256=
|
17
|
-
versionhq/clients/customer/model.py,sha256=
|
16
|
+
versionhq/clients/customer/__init__.py,sha256=rVcesoCFFl46job8Ppf8tRpEWy8A4ArRElfVjWykcRo,81
|
17
|
+
versionhq/clients/customer/model.py,sha256=Dl2dzo2FUdzqPEgoymeImM18InOmIhytmxAkjTiK_M8,2119
|
18
18
|
versionhq/clients/product/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
19
|
-
versionhq/clients/product/model.py,sha256=
|
19
|
+
versionhq/clients/product/model.py,sha256=N8_Oe7W20yYQuJ66owbLD_zNyiSSrYA7i7WI50UuKyQ,2228
|
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=ss-kXclc6ZRBjY4tGqAStIhEKezkBANR17n0yJNn8Ow,5717
|
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
|
@@ -31,13 +31,13 @@ versionhq/task/model.py,sha256=EbgYHLNq8l1zfRDnF-yEcuSZ0aNvzbRmHYgfVyJq84c,19910
|
|
31
31
|
versionhq/team/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
32
32
|
versionhq/team/model.py,sha256=E52OUVzUtvR--51SFRJos3JdYKri1t2jbvvzoOvShQc,20181
|
33
33
|
versionhq/team/team_planner.py,sha256=uzX2yed7A7gNSs6qH5jIq2zXMVF5BwQQ4HPATsB9DSQ,3675
|
34
|
-
versionhq/tool/__init__.py,sha256=
|
35
|
-
versionhq/tool/
|
34
|
+
versionhq/tool/__init__.py,sha256=FvBuEXsOQUYnN7RTFxT20kAkiEYkxWKkiVtgpqOzKZQ,1843
|
35
|
+
versionhq/tool/composio_tool.py,sha256=BJqaA1NhV0BT9AdY7OLCGpsAI3VEuCKnOS6D9vuU4zQ,8630
|
36
36
|
versionhq/tool/decorator.py,sha256=W_WjzZy8y43AoiFjHLPUQfNipmpOPe-wQknCWloPwmY,1195
|
37
|
-
versionhq/tool/model.py,sha256=
|
38
|
-
versionhq/tool/tool_handler.py,sha256=
|
39
|
-
versionhq-1.1.9.
|
40
|
-
versionhq-1.1.9.
|
41
|
-
versionhq-1.1.9.
|
42
|
-
versionhq-1.1.9.
|
43
|
-
versionhq-1.1.9.
|
37
|
+
versionhq/tool/model.py,sha256=cWfLQVjNkag5cYYqhABBK7-jcpl0UJQWuhDciG3MtPQ,8116
|
38
|
+
versionhq/tool/tool_handler.py,sha256=A3zUkZkx4JEpFHI2uBkHDpzWfADw-bCYUQhgm6rpITM,1569
|
39
|
+
versionhq-1.1.9.3.dist-info/LICENSE,sha256=7CCXuMrAjPVsUvZrsBq9DsxI2rLDUSYXR_qj4yO_ZII,1077
|
40
|
+
versionhq-1.1.9.3.dist-info/METADATA,sha256=MiNTSV--oEyOAWcxDsqOMRvI37Ys8dmIuzI6gSg3p1s,16070
|
41
|
+
versionhq-1.1.9.3.dist-info/WHEEL,sha256=A3WOREP4zgxI0fKrHUG8DC8013e3dK3n7a6HDbcEIwE,91
|
42
|
+
versionhq-1.1.9.3.dist-info/top_level.txt,sha256=DClQwxDWqIUGeRJkA8vBlgeNsYZs4_nJWMonzFt5Wj0,10
|
43
|
+
versionhq-1.1.9.3.dist-info/RECORD,,
|
versionhq/tool/composio.py
DELETED
@@ -1,149 +0,0 @@
|
|
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
|
File without changes
|
File without changes
|