feat: add composio toolset (#3034)
* feat: add composio toolset * feat: v1 * feat: format code * feat: add support for multi tools * feat: make methods private * [autofix.ci] apply automated fixes * feat: use logger * refactor(ComposioAPI.py): reorganize import statement * refactor: update typing import in langchain_utilities/model.py * refactor: update typing import in ComposioAPI.py --------- Co-authored-by: Gabriel Luiz Freitas Almeida <gabriel@langflow.org> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
parent
f352c53e70
commit
818a17d0db
9 changed files with 559 additions and 4 deletions
|
|
@ -1,5 +1,5 @@
|
|||
from abc import abstractmethod
|
||||
from typing import Union
|
||||
from typing import Sequence, Union
|
||||
|
||||
from langflow.custom import Component
|
||||
from langflow.field_typing import Tool
|
||||
|
|
@ -31,7 +31,7 @@ class LCToolComponent(Component):
|
|||
pass
|
||||
|
||||
@abstractmethod
|
||||
def build_tool(self) -> Tool:
|
||||
def build_tool(self) -> Tool | Sequence[Tool]:
|
||||
"""
|
||||
Build the tool.
|
||||
"""
|
||||
|
|
|
|||
172
src/backend/base/langflow/components/toolkits/ComposioAPI.py
Normal file
172
src/backend/base/langflow/components/toolkits/ComposioAPI.py
Normal file
|
|
@ -0,0 +1,172 @@
|
|||
from typing import Any, Sequence
|
||||
|
||||
from composio_langchain import Action, App, ComposioToolSet # type: ignore
|
||||
from langchain_core.tools import Tool
|
||||
from loguru import logger
|
||||
|
||||
from langflow.base.langchain_utilities.model import LCToolComponent
|
||||
from langflow.inputs import DropdownInput, MessageTextInput, MultiselectInput, SecretStrInput, StrInput
|
||||
|
||||
|
||||
class ComposioAPIComponent(LCToolComponent):
|
||||
display_name: str = "Composio Tools"
|
||||
description: str = "Use Composio toolset to run actions with your agent"
|
||||
name = "ComposioAPI"
|
||||
icon = "Composio"
|
||||
documentation: str = "https://docs.composio.dev"
|
||||
|
||||
inputs = [
|
||||
MessageTextInput(name="entity_id", display_name="Entity ID", value="default", advanced=True),
|
||||
SecretStrInput(
|
||||
name="api_key",
|
||||
display_name="Composio API Key",
|
||||
required=True,
|
||||
refresh_button=True,
|
||||
info="Refer to https://docs.composio.dev/introduction/foundations/howtos/get_api_key",
|
||||
),
|
||||
DropdownInput(
|
||||
name="app_names",
|
||||
display_name="App Name",
|
||||
options=[app_name for app_name in App.__annotations__],
|
||||
value="",
|
||||
info="The app name to use. Please refresh after selecting app name",
|
||||
refresh_button=True,
|
||||
),
|
||||
MultiselectInput(
|
||||
name="action_names",
|
||||
display_name="Actions to use",
|
||||
required=False,
|
||||
options=[],
|
||||
value=[],
|
||||
info="The actions to pass to agent to execute",
|
||||
),
|
||||
StrInput(
|
||||
name="auth_status_config",
|
||||
display_name="Auth status",
|
||||
value="",
|
||||
refresh_button=True,
|
||||
info="Open link or enter api key. Then refresh button",
|
||||
),
|
||||
]
|
||||
|
||||
def _check_for_authorization(self, app: str) -> str:
|
||||
"""
|
||||
Checks if the app is authorized.
|
||||
|
||||
Args:
|
||||
app (str): The app name to check authorization for.
|
||||
|
||||
Returns:
|
||||
str: The authorization status.
|
||||
"""
|
||||
toolset = self._build_wrapper()
|
||||
entity = toolset.client.get_entity(id=self.entity_id)
|
||||
try:
|
||||
entity.get_connection(app=app)
|
||||
return f"{app} CONNECTED"
|
||||
except Exception:
|
||||
return self._handle_authorization_failure(toolset, entity, app)
|
||||
|
||||
def _handle_authorization_failure(self, toolset: ComposioToolSet, entity: Any, app: str) -> str:
|
||||
"""
|
||||
Handles the authorization failure by attempting to process API key auth or initiate default connection.
|
||||
|
||||
Args:
|
||||
toolset (ComposioToolSet): The toolset instance.
|
||||
entity (Any): The entity instance.
|
||||
app (str): The app name.
|
||||
|
||||
Returns:
|
||||
str: The result of the authorization failure message.
|
||||
"""
|
||||
try:
|
||||
auth_schemes = toolset.client.apps.get(app).auth_schemes
|
||||
if auth_schemes[0].auth_mode == "API_KEY":
|
||||
return self._process_api_key_auth(entity, app)
|
||||
else:
|
||||
return self._initiate_default_connection(entity, app)
|
||||
except Exception as exc:
|
||||
logger.error(f"Authorization error: {str(exc)}")
|
||||
return "Error"
|
||||
|
||||
def _process_api_key_auth(self, entity: Any, app: str) -> str:
|
||||
"""
|
||||
Processes the API key authentication.
|
||||
|
||||
Args:
|
||||
entity (Any): The entity instance.
|
||||
app (str): The app name.
|
||||
|
||||
Returns:
|
||||
str: The status of the API key authentication.
|
||||
"""
|
||||
auth_status_config = self.auth_status_config
|
||||
is_url = "http" in auth_status_config or "https" in auth_status_config
|
||||
is_different_app = "CONNECTED" in auth_status_config and app not in auth_status_config
|
||||
is_default_api_key_message = "API Key" in auth_status_config
|
||||
|
||||
if is_different_app or is_url or is_default_api_key_message:
|
||||
return "Enter API Key"
|
||||
else:
|
||||
if not is_default_api_key_message:
|
||||
entity.initiate_connection(
|
||||
app_name=app,
|
||||
auth_mode="API_KEY",
|
||||
auth_config={"api_key": self.auth_status_config},
|
||||
use_composio_auth=False,
|
||||
force_new_integration=True,
|
||||
)
|
||||
return f"{app} CONNECTED"
|
||||
else:
|
||||
return "Enter API Key"
|
||||
|
||||
def _initiate_default_connection(self, entity: Any, app: str) -> str:
|
||||
connection = entity.initiate_connection(app_name=app, use_composio_auth=True, force_new_integration=True)
|
||||
return connection.redirectUrl
|
||||
|
||||
def _get_connected_app_names_for_entity(self) -> list[str]:
|
||||
toolset = self._build_wrapper()
|
||||
connections = toolset.client.get_entity(id=self.entity_id).get_connections()
|
||||
return list(set(connection.appUniqueId for connection in connections))
|
||||
|
||||
def _update_app_names_with_connected_status(self, build_config: dict) -> dict:
|
||||
connected_app_names = self._get_connected_app_names_for_entity()
|
||||
|
||||
app_names = [
|
||||
f"{app_name}_CONNECTED" for app_name in App.__annotations__ if app_name.lower() in connected_app_names
|
||||
]
|
||||
non_connected_app_names = [
|
||||
app_name for app_name in App.__annotations__ if app_name.lower() not in connected_app_names
|
||||
]
|
||||
build_config["app_names"]["options"] = app_names + non_connected_app_names
|
||||
build_config["app_names"]["value"] = app_names[0] if app_names else ""
|
||||
return build_config
|
||||
|
||||
def _get_normalized_app_name(self) -> str:
|
||||
return self.app_names.replace("_CONNECTED", "").replace("_connected", "")
|
||||
|
||||
def update_build_config(self, build_config: dict, field_value: Any, field_name: str | None = None) -> dict:
|
||||
if field_name == "api_key":
|
||||
build_config = self._update_app_names_with_connected_status(build_config)
|
||||
return build_config
|
||||
|
||||
if field_name in {"app_names", "auth_status_config"}:
|
||||
build_config["auth_status_config"]["value"] = self._check_for_authorization(self._get_normalized_app_name())
|
||||
|
||||
all_action_names = [action_name for action_name in Action.__annotations__]
|
||||
app_action_names = [
|
||||
action_name
|
||||
for action_name in all_action_names
|
||||
if action_name.lower().startswith(self._get_normalized_app_name().lower() + "_")
|
||||
]
|
||||
build_config["action_names"]["options"] = app_action_names
|
||||
build_config["action_names"]["value"] = [app_action_names[0]] if app_action_names else [""]
|
||||
return build_config
|
||||
|
||||
def build_tool(self) -> Sequence[Tool]:
|
||||
composio_toolset = self._build_wrapper()
|
||||
composio_tools = composio_toolset.get_actions(actions=self.action_names)
|
||||
return composio_tools
|
||||
|
||||
def _build_wrapper(self) -> ComposioToolSet:
|
||||
return ComposioToolSet(api_key=self.api_key)
|
||||
|
|
@ -1,7 +1,9 @@
|
|||
from .Metaphor import MetaphorToolkit
|
||||
from .VectorStoreInfo import VectorStoreInfoComponent
|
||||
from .ComposioAPI import ComposioAPIComponent
|
||||
|
||||
__all__ = [
|
||||
"MetaphorToolkit",
|
||||
"VectorStoreInfoComponent",
|
||||
"ComposioAPIComponent",
|
||||
]
|
||||
|
|
|
|||
68
src/frontend/src/icons/Composio/Composio.svg
Normal file
68
src/frontend/src/icons/Composio/Composio.svg
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="60"
|
||||
height="63"
|
||||
fill="none"
|
||||
>
|
||||
<path
|
||||
fill="#9A4DFF"
|
||||
d="M36.232 5H23.989C12.397 5 3 14.766 3 26.813v12.724C3 51.584 12.397 61.35 23.99 61.35h12.242c11.589 0 20.986-9.766 20.986-21.813V26.813C57.218 14.766 47.821 5 36.232 5"
|
||||
/>
|
||||
<path
|
||||
fill="url(#a)"
|
||||
d="M36.232 5H23.989C12.397 5 3 14.766 3 26.813v12.724C3 51.584 12.397 61.35 23.99 61.35h12.242c11.589 0 20.986-9.766 20.986-21.813V26.813C57.218 14.766 47.821 5 36.232 5"
|
||||
/>
|
||||
<path
|
||||
stroke="url(#b)"
|
||||
strokeWidth="1.44"
|
||||
d="M37.98 5H22.238C11.615 5 3 13.953 3 24.996v16.358C3 52.397 11.612 61.35 22.238 61.35H37.98c10.623 0 19.238-8.953 19.238-19.996V24.996C57.218 13.953 48.606 5 37.98 5Z"
|
||||
/>
|
||||
<mask
|
||||
id="c"
|
||||
width="28"
|
||||
height="28"
|
||||
x="16"
|
||||
y="19"
|
||||
maskUnits="userSpaceOnUse"
|
||||
style="maskType: luminance;"
|
||||
>
|
||||
<path fill="#fff" d="M16.994 19.542H43.23v27.266H16.994z" />
|
||||
</mask>
|
||||
<g mask="url(#c)">
|
||||
<path
|
||||
fill="#fff"
|
||||
fill-rule="evenodd"
|
||||
d="M32.952 20.899a.983.983 0 0 1 .444 1.095l-2.47 9.416h9.012a.9.9 0 0 1 .511.16.95.95 0 0 1 .344.423.99.99 0 0 1-.175 1.044L27.596 47.541a.899.899 0 0 1-1.138.19.95.95 0 0 1-.405-.474 1 1 0 0 1-.036-.633l2.472-9.412h-9.018a.9.9 0 0 1-.507-.159.95.95 0 0 1-.345-.425.99.99 0 0 1 .175-1.044l13.022-14.501a.899.899 0 0 1 1.132-.187"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<linearGradient
|
||||
id="a"
|
||||
x1="30.109"
|
||||
x2="30.109"
|
||||
y1="6.818"
|
||||
y2="59.532"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stop-color="#9A4DFF" />
|
||||
<stop offset="0.31" stop-color="#8017F7" />
|
||||
<stop offset="0.425" stop-color="#7A20E1" />
|
||||
<stop offset="0.495" stop-color="#7A20E1" />
|
||||
<stop offset="0.665" stop-color="#7C16F8" />
|
||||
<stop offset="1" stop-color="#8222FF" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="b"
|
||||
x1="30.109"
|
||||
x2="30.109"
|
||||
y1="6.817"
|
||||
y2="59.533"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stop-color="#6F00FF" stop-opacity="0.18" />
|
||||
<stop offset="1" stop-color="#600ED1" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
||||
|
After Width: | Height: | Size: 2.1 KiB |
74
src/frontend/src/icons/Composio/composio.jsx
Normal file
74
src/frontend/src/icons/Composio/composio.jsx
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
const Icon = (props) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={32}
|
||||
height={32}
|
||||
viewBox="0 0 60 63"
|
||||
fill="none"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
fill="#9A4DFF"
|
||||
d="M36.232 5H23.989C12.397 5 3 14.766 3 26.813v12.724C3 51.584 12.397 61.35 23.99 61.35h12.242c11.589 0 20.986-9.766 20.986-21.813V26.813C57.218 14.766 47.821 5 36.232 5"
|
||||
/>
|
||||
<path
|
||||
fill="url(#a)"
|
||||
d="M36.232 5H23.989C12.397 5 3 14.766 3 26.813v12.724C3 51.584 12.397 61.35 23.99 61.35h12.242c11.589 0 20.986-9.766 20.986-21.813V26.813C57.218 14.766 47.821 5 36.232 5"
|
||||
/>
|
||||
<path
|
||||
stroke="url(#b)"
|
||||
strokeWidth={1.44}
|
||||
d="M37.98 5H22.238C11.615 5 3 13.953 3 24.996v16.358C3 52.397 11.612 61.35 22.238 61.35H37.98c10.623 0 19.238-8.953 19.238-19.996V24.996C57.218 13.953 48.606 5 37.98 5Z"
|
||||
/>
|
||||
<mask
|
||||
id="c"
|
||||
width={28}
|
||||
height={28}
|
||||
x={16}
|
||||
y={19}
|
||||
maskUnits="userSpaceOnUse"
|
||||
style={{
|
||||
maskType: "luminance",
|
||||
}}
|
||||
>
|
||||
<path fill="#fff" d="M16.994 19.542H43.23v27.266H16.994z" />
|
||||
</mask>
|
||||
<g mask="url(#c)">
|
||||
<path
|
||||
fill="#fff"
|
||||
fillRule="evenodd"
|
||||
d="M32.952 20.899a.983.983 0 0 1 .444 1.095l-2.47 9.416h9.012a.9.9 0 0 1 .511.16.95.95 0 0 1 .344.423.99.99 0 0 1-.175 1.044L27.596 47.541a.899.899 0 0 1-1.138.19.95.95 0 0 1-.405-.474 1 1 0 0 1-.036-.633l2.472-9.412h-9.018a.9.9 0 0 1-.507-.159.95.95 0 0 1-.345-.425.99.99 0 0 1 .175-1.044l13.022-14.501a.899.899 0 0 1 1.132-.187"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<linearGradient
|
||||
id="a"
|
||||
x1={30.109}
|
||||
x2={30.109}
|
||||
y1={6.818}
|
||||
y2={59.532}
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#9A4DFF" />
|
||||
<stop offset={0.31} stopColor="#8017F7" />
|
||||
<stop offset={0.425} stopColor="#7A20E1" />
|
||||
<stop offset={0.495} stopColor="#7A20E1" />
|
||||
<stop offset={0.665} stopColor="#7C16F8" />
|
||||
<stop offset={1} stopColor="#8222FF" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="b"
|
||||
x1={30.109}
|
||||
x2={30.109}
|
||||
y1={6.817}
|
||||
y2={59.533}
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#6F00FF" stopOpacity={0.18} />
|
||||
<stop offset={1} stopColor="#600ED1" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
);
|
||||
export default Icon;
|
||||
9
src/frontend/src/icons/Composio/index.tsx
Normal file
9
src/frontend/src/icons/Composio/index.tsx
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import React, { forwardRef } from "react";
|
||||
import ComposioIconSVG from "./composio";
|
||||
|
||||
export const ComposioIcon = forwardRef<
|
||||
SVGSVGElement,
|
||||
React.PropsWithChildren<{}>
|
||||
>((props, ref) => {
|
||||
return <ComposioIconSVG ref={ref} {...props} />;
|
||||
});
|
||||
|
|
@ -172,6 +172,7 @@ import { BotMessageSquareIcon } from "../icons/BotMessageSquare";
|
|||
import { CassandraIcon } from "../icons/Cassandra";
|
||||
import { ChromaIcon } from "../icons/ChromaIcon";
|
||||
import { CohereIcon } from "../icons/Cohere";
|
||||
import { ComposioIcon } from "../icons/Composio";
|
||||
import { ConfluenceIcon } from "../icons/Confluence";
|
||||
import { CouchbaseIcon } from "../icons/Couchbase";
|
||||
import { CrewAiIcon } from "../icons/CrewAI";
|
||||
|
|
@ -392,6 +393,7 @@ export const nodeIconsLucide: iconsType = {
|
|||
HuggingFaceEmbeddings: HuggingFaceIcon,
|
||||
IFixitLoader: IFixIcon,
|
||||
CrewAI: CrewAiIcon,
|
||||
Composio: ComposioIcon,
|
||||
Meta: MetaIcon,
|
||||
CheckCheck,
|
||||
Midjorney: MidjourneyIcon,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue