-
Notifications
You must be signed in to change notification settings - Fork 183
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
c016350
commit ded439b
Showing
9 changed files
with
532 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
9 changes: 9 additions & 0 deletions
9
docs/griptape-tools/official-tools/griptape-cloud-tool-tool.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
# Griptape Cloud Tool Tool | ||
|
||
The [GriptapeCloudToolTool](../../reference/griptape/tools/griptape_cloud_tool/tool.md) integrates with Griptape Cloud's hosted Tools. | ||
|
||
**Note:** This tool requires a [Tool](https://cloud.griptape.ai/tools) hosted in Griptape Cloud and an [API Key](https://cloud.griptape.ai/configuration/api-keys) for access. | ||
|
||
```python | ||
--8<-- "docs/griptape-tools/official-tools/src/griptape_cloud_tool_tool.py" | ||
``` |
13 changes: 13 additions & 0 deletions
13
docs/griptape-tools/official-tools/src/griptape_cloud_tool_tool.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import os | ||
|
||
from griptape.structures import Agent | ||
from griptape.tools.griptape_cloud_tool.tool import GriptapeCloudToolTool | ||
|
||
agent = Agent( | ||
tools=[ | ||
GriptapeCloudToolTool( # Tool is configured as a random number generator | ||
tool_id=os.environ["GT_CLOUD_TOOL_ID"], | ||
) | ||
] | ||
) | ||
agent.run("Generate a number between 1 and 10") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
from __future__ import annotations | ||
|
||
from types import MethodType | ||
from typing import Any, Callable | ||
from urllib.parse import urljoin | ||
|
||
import requests | ||
from attrs import define, field | ||
from schema import Literal, Optional, Schema | ||
|
||
from griptape.artifacts import BaseArtifact, TextArtifact | ||
from griptape.tools.base_griptape_cloud_tool import BaseGriptapeCloudTool | ||
from griptape.utils.decorators import activity | ||
|
||
|
||
@define() | ||
class GriptapeCloudToolTool(BaseGriptapeCloudTool): | ||
"""Runs a Griptape Cloud hosted Tool. | ||
Attributes: | ||
tool_id: The ID of the tool to run. | ||
""" | ||
|
||
tool_id: str = field(kw_only=True) | ||
|
||
def __attrs_post_init__(self) -> None: | ||
self._init_activities() | ||
|
||
def _init_activities(self) -> None: | ||
schema = self._get_schema() | ||
tool_name, activity_schemas = self._parse_schema(schema) | ||
|
||
if self.name == self.__class__.__name__: | ||
self.name = tool_name | ||
|
||
for activity_name, (description, activity_schema) in activity_schemas.items(): | ||
activity_handler = self._create_activity_handler(activity_name, description, activity_schema) | ||
|
||
setattr(self, activity_name, MethodType(activity_handler, self)) | ||
|
||
def _get_schema(self) -> dict: | ||
response = requests.get(urljoin(self.base_url, f"/api/tools/{self.tool_id}/openapi"), headers=self.headers) | ||
|
||
response.raise_for_status() | ||
|
||
return response.json() | ||
|
||
def _parse_schema(self, schema: dict) -> tuple[str, dict[str, tuple[str, Schema]]]: | ||
"""Parses an openapi schema into a dictionary of activity names and their respective descriptions + schemas.""" | ||
activities = {} | ||
|
||
name = schema.get("info", {}).get("title") | ||
|
||
for path, path_info in schema.get("paths", {}).items(): | ||
if not path.startswith("/activities"): | ||
continue | ||
for method, method_info in path_info.items(): | ||
if "post" in method.lower(): | ||
activity_name = method_info["operationId"] | ||
description = method_info.get("description", "") | ||
|
||
activity_schema = self.__extract_schema_from_ref( | ||
schema, | ||
method_info.get("requestBody", {}) | ||
.get("content", {}) | ||
.get("application/json", {}) | ||
.get("schema", {}), | ||
) | ||
|
||
activities[activity_name] = (description, activity_schema) | ||
|
||
return name, activities | ||
|
||
def __extract_schema_from_ref(self, schema: dict, schema_ref: dict) -> Schema: | ||
"""Extracts a schema from a $ref if present, resolving it into native schema properties.""" | ||
if "$ref" in schema_ref: | ||
# Resolve the reference and retrieve the schema data | ||
ref_path = schema_ref["$ref"].split("/")[-1] | ||
schema_data = schema["components"]["schemas"].get(ref_path, {}) | ||
else: | ||
# Use the provided schema directly if no $ref is found | ||
schema_data = schema_ref | ||
|
||
# Convert the schema_data dictionary into a Schema with its properties | ||
properties = {} | ||
for prop, prop_info in schema_data.get("properties", {}).items(): | ||
prop_type = prop_info.get("type", "string") | ||
prop_description = prop_info.get("description", "") | ||
schema_prop = Literal(prop, description=prop_description) | ||
is_optional = prop not in schema_data.get("required", []) | ||
|
||
if is_optional: | ||
schema_prop = Optional(schema_prop) | ||
|
||
properties[schema_prop] = self._map_openapi_type_to_python(prop_type) | ||
|
||
return Schema(properties) | ||
|
||
def _map_openapi_type_to_python(self, openapi_type: str) -> type: | ||
"""Maps OpenAPI types to native Python types.""" | ||
type_mapping = {"string": str, "integer": int, "boolean": bool, "number": float, "array": list, "object": dict} | ||
|
||
return type_mapping.get(openapi_type, str) | ||
|
||
def _create_activity_handler(self, activity_name: str, description: str, activity_schema: Schema) -> Callable: | ||
"""Creates an activity handler method for the tool.""" | ||
|
||
@activity(config={"name": activity_name, "description": description, "schema": activity_schema}) | ||
def activity_handler(self: GriptapeCloudToolTool, values: dict) -> Any: | ||
return self._run_activity(activity_name, values) | ||
|
||
return activity_handler | ||
|
||
def _run_activity(self, activity_name: str, params: dict) -> BaseArtifact: | ||
"""Runs an activity on the tool with the provided parameters.""" | ||
url = urljoin(self.base_url, f"/api/tools/{self.tool_id}/activities/{activity_name}") | ||
|
||
response = requests.post(url, json=params, headers=self.headers) | ||
|
||
response.raise_for_status() | ||
|
||
try: | ||
return BaseArtifact.from_dict(response.json()) | ||
except ValueError: | ||
return TextArtifact(response.text) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.