-
Notifications
You must be signed in to change notification settings - Fork 8.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(azure_blob_storage): add ephemeral file share tool for uploading…
… files and generating shared access URLs
- Loading branch information
Showing
2 changed files
with
214 additions
and
0 deletions.
There are no files selected for viewing
145 changes: 145 additions & 0 deletions
145
api/core/tools/provider/builtin/azure_blob_storage/tools/ephemeral_share_file.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,145 @@ | ||
import io | ||
import uuid | ||
from datetime import UTC, datetime, timedelta | ||
from typing import Any, Union | ||
|
||
from azure.core.exceptions import ResourceExistsError | ||
from azure.storage.blob import BlobSasPermissions, BlobServiceClient, ContentSettings, generate_blob_sas | ||
|
||
from core.file.file_manager import download | ||
from core.tools.entities.tool_entities import ToolInvokeMessage | ||
from core.tools.errors import ToolProviderCredentialValidationError | ||
from core.tools.tool.builtin_tool import BuiltinTool | ||
|
||
|
||
class EphemeralFireShareTool(BuiltinTool): | ||
""" | ||
Tool to upload a file to Azure Blob Storage and generate a Shared Access URL for the file. | ||
""" | ||
|
||
def _invoke( | ||
self, user_id: str, tool_parameters: dict[str, Any] | ||
) -> Union[ToolInvokeMessage, list[ToolInvokeMessage]]: | ||
# Ensure runtime and credentials | ||
if not self.runtime or not self.runtime.credentials: | ||
raise ToolProviderCredentialValidationError("Tool runtime or credentials are missing") | ||
|
||
# Get account name | ||
account_name = self.runtime.credentials.get("azure_blob_storage_account_name") | ||
if not account_name: | ||
raise ValueError("Azure Blob Storage connection string is required") | ||
|
||
# Get API key | ||
api_key = self.runtime.credentials.get("azure_blob_storage_api_key") | ||
if not api_key: | ||
raise ValueError("Azure Blob Storage API Key is required") | ||
|
||
# get container name | ||
container_name = tool_parameters.get("container_name") | ||
if not container_name: | ||
raise ValueError("Container name is required") | ||
|
||
# get file prefix | ||
file_prefix = tool_parameters.get("file_prefix") | ||
# create a blob client using the connection string | ||
blob_service_client = BlobServiceClient( | ||
account_url=f"https://{account_name}.blob.core.windows.net", credential=api_key | ||
) | ||
|
||
# get duration | ||
overwrite = tool_parameters.get("duration", 5) | ||
|
||
# get stop_on_error flag | ||
stop_on_error = tool_parameters.get("stop_on_error", True) | ||
|
||
# create container | ||
try: | ||
blob_service_client.create_container(name=container_name) | ||
except ResourceExistsError: | ||
pass | ||
except Exception as exc: | ||
if stop_on_error: | ||
raise ValueError("Failed to create container") from exc | ||
else: | ||
return [ | ||
self.create_text_message("Failed to create container"), | ||
self.create_json_message({"error": "Failed to create container"}), | ||
] | ||
|
||
# Get file | ||
file = tool_parameters.get("file") | ||
if not file: | ||
raise ValueError("File is required") | ||
|
||
file_id = uuid.uuid4().hex | ||
filename = file.filename or None | ||
mime_type = file.mime_type or None | ||
file_binary = io.BytesIO(download(file)) | ||
|
||
# blob name is masked with fileid. | ||
# If file_prefix is provided, then it will be used as a prefix | ||
blob = f"{file_prefix}{file_id}" if file_prefix else file_id | ||
|
||
# content settings including content type and content disposition | ||
content_disposition: str = f'attachment; filename="{filename}"' if filename else "attachment" | ||
content_settings = ContentSettings(content_type=mime_type, content_disposition=content_disposition) | ||
|
||
# upload file to blob storage | ||
blob_client = blob_service_client.get_blob_client(container=container_name, blob=blob) | ||
try: | ||
blob_client.upload_blob(file_binary, content_settings=content_settings, overwrite=overwrite) | ||
except Exception as exc: | ||
if stop_on_error: | ||
raise ValueError("Failed to upload file") from exc | ||
else: | ||
return [ | ||
self.create_text_message(f'Failed to upload file "{filename}" to container "{container_name}"'), | ||
self.create_json_message( | ||
{"error": f'Failed to upload file "{filename}" to container "{container_name}"'} | ||
), | ||
] | ||
|
||
# get blob properties for response | ||
blob_properties = blob_client.get_blob_properties() | ||
result_content_settings = blob_properties.content_settings or None | ||
|
||
# create SAS token for the uploaded file | ||
try: | ||
expiry = datetime.now(UTC) + timedelta(minutes=overwrite) | ||
sas_token = generate_blob_sas( | ||
account_name=account_name, | ||
container_name=container_name, | ||
blob_name=blob, | ||
account_key=api_key, | ||
permission=BlobSasPermissions(read=True, write=False, delete=False, list=False), | ||
expiry=expiry, | ||
) | ||
shared_access_url = f"https://{account_name}.blob.core.windows.net/{container_name}/{blob}?{sas_token}" | ||
except Exception as exc: | ||
if stop_on_error: | ||
raise ValueError("Failed to generate SAS token") from exc | ||
else: | ||
return [ | ||
self.create_text_message(f'Failed to generate SAS token for file "{filename}"'), | ||
self.create_json_message({"error": f'Failed to generate SAS token for file "{filename}"'}), | ||
] | ||
|
||
result = { | ||
"name": blob_properties.name or None, | ||
"container": blob_properties.container or None, | ||
"size": blob_properties.size or None, | ||
"creation_time": blob_properties.creation_time.isoformat() or None, | ||
"last_modified": blob_properties.last_modified.isoformat() or None, | ||
"content_type": result_content_settings.content_type if result_content_settings else None, | ||
"content_disposition": result_content_settings.content_disposition if result_content_settings else None, | ||
"expiry": expiry.isoformat(), | ||
"sas_token": sas_token, | ||
"shared_access_url": shared_access_url, | ||
} | ||
|
||
return [ | ||
self.create_text_message( | ||
f"Shared Access URL is {shared_access_url} ", f"This URL will expire in {overwrite} minutes." | ||
), | ||
self.create_json_message(result), | ||
] |
69 changes: 69 additions & 0 deletions
69
api/core/tools/provider/builtin/azure_blob_storage/tools/ephemeral_share_file.yaml
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,69 @@ | ||
identity: | ||
name: ephemeral_share_file | ||
author: Hiroshi Fujita | ||
label: | ||
en_US: Ephemeral File Share | ||
ja_JP: 一時ファイル共有 | ||
description: | ||
human: | ||
en_US: Upload the file to Azure Blob Storage and create a ephemeral sharing link. | ||
ja_JP: ファイルをAzure Blob Storageにアップロードし、一時的な共有リンクを作成します。 | ||
llm: This tool upload the file to Azure Blob Storage and create a ephemeral sharing link. | ||
parameters: | ||
- name: container_name | ||
type: string | ||
required: true | ||
label: | ||
en_US: Container name | ||
ja_JP: コンテナ名 | ||
human_description: | ||
en_US: Name of the container to upload to | ||
ja_JP: アップロード先のコンテナの名称 | ||
llm_description: Name of the container to upload to | ||
form: llm | ||
- name: file_prefix | ||
type: string | ||
required: false | ||
label: | ||
en_US: Prefix | ||
ja_JP: プレフィックス | ||
human_description: | ||
en_US: The prefix is a string added to the beginning of the Blob file name during upload. It allows dynamic control of the Blob's file name and virtual folder structure. For example, if prefix="folder1/", the Blob will be saved as folder1/filename. This helps avoid file name conflicts and organizes files into virtual directories. | ||
ja_JP: プレフィックスはアップロード時にBlobファイル名の先頭に追加される文字列です。これにより、Blobのファイル名や仮想フォルダ構造を動的に制御できます。例えば、prefix="folder1/" と指定すると、Blobは folder1/filename の形式で保存されます。これにより、ファイル名の重複を回避し、ファイルを仮想フォルダに整理できます。 | ||
llm_description: The prefix is a string added to the beginning of the Blob file name during upload. It allows dynamic control of the Blob's file name and virtual folder structure. For example, if prefix="folder1/", the Blob will be saved as folder1/filename. This helps avoid file name conflicts and organizes files into virtual directories. | ||
form: llm | ||
- name: file | ||
type: file | ||
required: true | ||
label: | ||
en_US: File to upload | ||
ja_JP: アップロードするファイル | ||
human_description: | ||
en_US: Select the file to upload. | ||
ja_JP: アップロードするファイルを選択します。 | ||
llm_description: Select the file to upload. | ||
form: llm | ||
- name: duration | ||
type: number | ||
required: true | ||
label: | ||
en_US: Sharing Duration (min) | ||
ja_JP: 共有期間(分) | ||
human_description: | ||
en_US: Set the period for which the share will be valid, after which the share will become invalid. | ||
ja_JP: 共有が有効な期間を設定します。設定した期間が過ぎると共有は無効になります。 | ||
llm_description: Set the period for which the share will be valid, after which the share will become invalid. | ||
form: form | ||
default: 5 | ||
- name: stop_on_error | ||
type: boolean | ||
required: true | ||
label: | ||
en_US: Stop on Error | ||
ja_JP: エラー時に停止する | ||
human_description: | ||
en_US: Set whether to stop the flow if the file upload fails. | ||
ja_JP: ファイルのアップロードが失敗した場合に、フローを止めるかを設定します。 | ||
llm_description: Set whether to stop the flow if the file upload fails. | ||
form: form | ||
default: true |