versionhq 1.1.9.2__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 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.composio import Composio
18
+ from versionhq.tool.composio_tool import ComposioHandler
19
19
 
20
20
 
21
- __version__ = "1.1.9.2"
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
- "Composio"
37
+ "ComposioHandler"
38
38
  ]
@@ -40,10 +40,11 @@ composio_app_set = [
40
40
  (ComposioAppName.LINKEDIN, ComposioAuthScheme.OAUTH2),
41
41
  ]
42
42
 
43
- class COMPOSIO_STATUS(str, Enum):
44
- INITIATED = "initiated"
45
- ACTIVE = "active"
46
- FAILED = "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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: versionhq
3
- Version: 1.1.9.2
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
  ![MIT license](https://img.shields.io/badge/License-MIT-green)
59
62
  [![Publisher](https://github.com/versionHQ/multi-agent-system/actions/workflows/publish.yml/badge.svg)](https://github.com/versionHQ/multi-agent-system/actions/workflows/publish.yml)
60
- ![PyPI](https://img.shields.io/badge/PyPI-v1.1.7.9-blue)
63
+ ![PyPI](https://img.shields.io/badge/PyPI-v1.1.9.3-blue)
61
64
  ![python ver](https://img.shields.io/badge/Python-3.12/3.13-purple)
62
65
  ![pyenv ver](https://img.shields.io/badge/pyenv-2.5.0-orange)
63
66
 
@@ -1,4 +1,4 @@
1
- versionhq/__init__.py,sha256=EsxPGTpj4pzGKPFd3wdJLr3zDbMpJDoSXkpz4OKuEks,931
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
@@ -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=oU2Y84b7vywWq1xmFaBdXdH8Y9lGv7dmk2LEcj4dL-s,1692
35
- versionhq/tool/composio.py,sha256=Uh9lwk5f47hJq7YaY3ctjEYJzzBm12GTtvFUuaK7KO4,6253
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
37
  versionhq/tool/model.py,sha256=cWfLQVjNkag5cYYqhABBK7-jcpl0UJQWuhDciG3MtPQ,8116
38
38
  versionhq/tool/tool_handler.py,sha256=A3zUkZkx4JEpFHI2uBkHDpzWfADw-bCYUQhgm6rpITM,1569
39
- versionhq-1.1.9.2.dist-info/LICENSE,sha256=7CCXuMrAjPVsUvZrsBq9DsxI2rLDUSYXR_qj4yO_ZII,1077
40
- versionhq-1.1.9.2.dist-info/METADATA,sha256=NJvqqKMP4sCZG42GkTsmP8UTbzd_btKgWyjORAHO2-s,15955
41
- versionhq-1.1.9.2.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
42
- versionhq-1.1.9.2.dist-info/top_level.txt,sha256=DClQwxDWqIUGeRJkA8vBlgeNsYZs4_nJWMonzFt5Wj0,10
43
- versionhq-1.1.9.2.dist-info/RECORD,,
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,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.6.0)
2
+ Generator: setuptools (75.7.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,161 +0,0 @@
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
6
-
7
- from pydantic import BaseModel, Field, model_validator, field_validator, UUID4, PrivateAttr
8
- from pydantic_core import PydanticCustomError
9
-
10
- from composio import ComposioToolSet, Action, App, action
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
15
-
16
- load_dotenv(override=True)
17
-
18
- DEFAULT_REDIRECT_URL = os.environ.get("DEFAULT_REDIRECT_URL", None)
19
- DEFAULT_USER_ID = os.environ.get("DEFAULT_USER_ID", None)
20
-
21
-
22
- class Composio(ABC, BaseModel):
23
- """
24
- Class to handle composio tools.
25
- """
26
-
27
- _logger: Logger = PrivateAttr(default_factory=lambda: Logger(verbose=True))
28
- _cache: CacheHandler
29
-
30
- id: UUID4 = Field(default_factory=uuid.uuid4, frozen=True)
31
- app_name: str = Field(default=ComposioAppName.HUBSPOT)
32
- user_id: str = Field(default=DEFAULT_USER_ID)
33
- auth_scheme: str = Field(default=ComposioAuthScheme.OAUTH2)
34
- redirect_url: str = Field(default=DEFAULT_REDIRECT_URL, description="redirect url after successful oauth2 connection")
35
- connect_request_id: str = Field(default=None, description="store the client's composio id to connect with the app")
36
-
37
- @property
38
- def toolset(self) -> ComposioToolSet:
39
- return ComposioToolSet(api_key=os.environ.get("COMPOSIO_API_KEY"))
40
-
41
-
42
- def __name__(self):
43
- return self.app_name
44
-
45
-
46
- @field_validator("id", mode="before")
47
- @classmethod
48
- def _deny_user_set_id(cls, v: Optional[UUID4]) -> None:
49
- if v:
50
- raise PydanticCustomError("may_not_set_field", "This field is not to be set by the user.", {})
51
-
52
-
53
- # @model_validator("user_id", mode="before")
54
- # @classmethod
55
- # def _deny_no_user_id(cls, v: Optional[str]) -> None:
56
- # if v is None:
57
- # raise PydanticCustomError("user_id_missing", "Need user_id to connect with the tool", {})
58
-
59
-
60
- @model_validator(mode="after")
61
- def validate_app_name(self):
62
- if self.app_name not in ComposioAppName:
63
- raise PydanticCustomError("no_app_name", f"The app name {self.app_name} is not valid.", {})
64
-
65
- return self
66
-
67
-
68
- @model_validator(mode="after")
69
- def validate_auth_scheme(self):
70
- """
71
- Raise error when the client uses auth scheme unavailable for the app.
72
- """
73
- app_set = next(filter(lambda tup: self.app_name in tup, composio_app_set), None)
74
- if not app_set:
75
- raise PydanticCustomError("no_app_set", f"The app set of {self.app_name} is missing.", {})
76
-
77
- else:
78
- acceptable_auth_scheme = next(filter(lambda item: self.auth_scheme in item, app_set), None)
79
- if acceptable_auth_scheme is None:
80
- raise PydanticCustomError("invalid_auth_scheme", f"The app {self.app_name} must have different auth_scheme.", {})
81
-
82
- return self
83
-
84
-
85
- # connect with composio to use the tool
86
- def connect(self, token: Optional[str] = None, api_key: Optional[str] = None) -> Tuple[str | Any]:
87
- """
88
- Connect with Composio, retrieve `connect_request_id`, and validate the connection.
89
- """
90
-
91
- if not self.user_id:
92
- raise PydanticCustomError("user_id_missing", "Need user_id to connect with the tool", {})
93
-
94
-
95
- connection_request, connected_account = None, None
96
-
97
- if self.auth_scheme == ComposioAuthScheme.API_KEY:
98
- collected_from_user = {}
99
- collected_from_user["api_key"] = api_key
100
- connection_request = self.toolset.initiate_connection(
101
- connected_account_params = collected_from_user,
102
- app=self.app_name,
103
- entity_id=self.user_id,
104
- auth_scheme=self.auth_scheme,
105
- )
106
-
107
- if self.auth_scheme == ComposioAuthScheme.BEARER_TOKEN:
108
- collected_from_user = {}
109
- collected_from_user["token"] = token
110
- connection_request = self.toolset.initiate_connection(
111
- connected_account_params = collected_from_user,
112
- app=self.app_name,
113
- entity_id=self.user_id,
114
- auth_scheme=self.auth_scheme,
115
- )
116
-
117
- if self.auth_scheme == ComposioAuthScheme.OAUTH2:
118
- connection_request = self.toolset.initiate_connection(
119
- app=self.app_name,
120
- redirect_url = self.redirect_url, # clients will be redirected to this url after successful auth.
121
- entity_id=self.user_id,
122
- auth_scheme=self.auth_scheme,
123
- )
124
-
125
- # connection_request.wait_until_active(self.toolset.client, timeout=60)
126
-
127
- if connection_request.connectionStatus is not COMPOSIO_STATUS.FAILED:
128
- self.connect_request_id = connection_request.connectedAccountId
129
- connected_account = self.toolset.get_connected_account(id=self.connect_request_id)
130
-
131
- if connected_account.status is not COMPOSIO_STATUS.FAILED:
132
- setattr(self.toolset, "entity_id", self.user_id)
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
-
140
- return connected_account, connected_account.status if connected_account else connection_request.connectionStatus
141
-
142
-
143
-
144
-
145
- # @action(toolname=ComposioAppName.HUBSPOT)
146
- # def deploy(self, param1: str, param2: str, execute_request: Callable) -> str:
147
- # """
148
- # Define custom actions
149
- # my custom action description which will be passed to llm
150
-
151
- # :param param1: param1 description which will be passed to llm
152
- # :param param2: param2 description which will be passed to llm
153
- # :return info: return description
154
- # """
155
-
156
- # response = execute_request(
157
- # "/my_action_endpoint",
158
- # "GET",
159
- # {} # body can be added here
160
- # ) # execute requests by appending credentials to the request
161
- # return str(response) # complete auth dict is available for local use if needed