Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add Azure Blob Storage provier and tools #11081

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
from typing import Any

from azure.storage.blob import BlobServiceClient

from core.tools.errors import ToolProviderCredentialValidationError
from core.tools.provider.builtin_tool_provider import BuiltinToolProviderController


class AzureBlobStorage(BuiltinToolProviderController):
"""
Azure Blob Storage provider
"""

def _validate_credentials(self, credentials: dict[str, Any]) -> None:
account_name = credentials.get("azure_blob_storage_account_name")
api_key = credentials.get("azure_blob_storage_api_key")
# connection_string = credentials.get("azure_blob_storage_connection_string")

# ensure account name and api key are provided
if not account_name:
raise ToolProviderCredentialValidationError("Azure Blob Storage Account Name is required")
if not api_key:
raise ToolProviderCredentialValidationError("Azure Blob Storage API Key is required")

# validate connection string
try:
blob_service_client = BlobServiceClient(
account_url=f"https://{account_name}.blob.core.windows.net", credential=api_key
)
containers = blob_service_client.list_containers()
containers_count = len(list(containers))
except Exception:
raise ToolProviderCredentialValidationError("Invalid Azure Blob Storage connection string")
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
identity:
author: Hiroshi Fujita
name: azure_blob_storage
label:
en_US: Azure Blob Storage
description:
en_US: Storing and hosting files.
ja_JP: ファイルの保存とホスティング
icon: 10780-icon-service-Blob-Block.svg
tags:
- utilities
credentials_for_provider:
azure_blob_storage_account_name:
type: text-input
required: true
label:
en_US: Storage Account Name
ja_JP: ストレージアカウント名
placeholder:
en_US: Enter Storage Account Name
ja_JP: ストレージアカウント名を入力
url: https://portal.azure.com
azure_blob_storage_api_key:
type: secret-input
required: true
label:
en_US: API Key
ja_JP: APIキー
placeholder:
en_US: Enter API Key
ja_JP: APIキーを入力
url: https://portal.azure.com
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),
]
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
Loading
Loading