Skip to content

Commit

Permalink
Merge branch 'main' of https://github.com/langgenius/dify into add_pr…
Browse files Browse the repository at this point in the history
…ovider
  • Loading branch information
Walter-jin committed Aug 29, 2024
2 parents 440bd20 + 0e0a703 commit 69a1f73
Show file tree
Hide file tree
Showing 21 changed files with 209 additions and 65 deletions.
2 changes: 1 addition & 1 deletion api/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ RUN apt-get update \
&& echo "deb http://deb.debian.org/debian testing main" > /etc/apt/sources.list \
&& apt-get update \
# For Security
&& apt-get install -y --no-install-recommends zlib1g=1:1.3.dfsg+really1.3.1-1 expat=2.6.2-1 libldap-2.5-0=2.5.18+dfsg-2 perl=5.38.2-5 libsqlite3-0=3.46.0-1 \
&& apt-get install -y --no-install-recommends zlib1g=1:1.3.dfsg+really1.3.1-1 expat=2.6.2-1 libldap-2.5-0=2.5.18+dfsg-3 perl=5.38.2-5 libsqlite3-0=3.46.0-1 \
&& apt-get autoremove -y \
&& rm -rf /var/lib/apt/lists/*

Expand Down
2 changes: 1 addition & 1 deletion api/controllers/console/datasets/file.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ def get(self):
@login_required
@account_initialization_required
@marshal_with(file_fields)
@cloud_edition_billing_resource_check(resource="documents")
@cloud_edition_billing_resource_check("documents")
def post(self):
# get file from request
file = request.files["file"]
Expand Down
26 changes: 12 additions & 14 deletions api/controllers/console/wraps.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,7 @@ def decorated(*args, **kwargs):
return decorated


def cloud_edition_billing_resource_check(
resource: str, error_msg: str = "You have reached the limit of your subscription."
):
def cloud_edition_billing_resource_check(resource: str):
def interceptor(view):
@wraps(view)
def decorated(*args, **kwargs):
Expand All @@ -60,22 +58,22 @@ def decorated(*args, **kwargs):
documents_upload_quota = features.documents_upload_quota
annotation_quota_limit = features.annotation_quota_limit
if resource == "members" and 0 < members.limit <= members.size:
abort(403, error_msg)
abort(403, "The number of members has reached the limit of your subscription.")
elif resource == "apps" and 0 < apps.limit <= apps.size:
abort(403, error_msg)
abort(403, "The number of apps has reached the limit of your subscription.")
elif resource == "vector_space" and 0 < vector_space.limit <= vector_space.size:
abort(403, error_msg)
abort(403, "The capacity of the vector space has reached the limit of your subscription.")
elif resource == "documents" and 0 < documents_upload_quota.limit <= documents_upload_quota.size:
# The api of file upload is used in the multiple places, so we need to check the source of the request from datasets
source = request.args.get("source")
if source == "datasets":
abort(403, error_msg)
abort(403, "The number of documents has reached the limit of your subscription.")
else:
return view(*args, **kwargs)
elif resource == "workspace_custom" and not features.can_replace_logo:
abort(403, error_msg)
abort(403, "The workspace custom feature has reached the limit of your subscription.")
elif resource == "annotation" and 0 < annotation_quota_limit.limit < annotation_quota_limit.size:
abort(403, error_msg)
abort(403, "The annotation quota has reached the limit of your subscription.")
else:
return view(*args, **kwargs)

Expand All @@ -86,18 +84,18 @@ def decorated(*args, **kwargs):
return interceptor


def cloud_edition_billing_knowledge_limit_check(
resource: str,
error_msg: str = "To unlock this feature and elevate your Dify experience, please upgrade to a paid plan.",
):
def cloud_edition_billing_knowledge_limit_check(resource: str):
def interceptor(view):
@wraps(view)
def decorated(*args, **kwargs):
features = FeatureService.get_features(current_user.current_tenant_id)
if features.billing.enabled:
if resource == "add_segment":
if features.billing.subscription.plan == "sandbox":
abort(403, error_msg)
abort(
403,
"To unlock this feature and elevate your Dify experience, please upgrade to a paid plan.",
)
else:
return view(*args, **kwargs)

Expand Down
22 changes: 9 additions & 13 deletions api/controllers/service_api/wraps.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,7 @@ def decorated_view(*args, **kwargs):
return decorator(view)


def cloud_edition_billing_resource_check(
resource: str, api_token_type: str, error_msg: str = "You have reached the limit of your subscription."
):
def cloud_edition_billing_resource_check(resource: str, api_token_type: str):
def interceptor(view):
def decorated(*args, **kwargs):
api_token = validate_and_get_api_token(api_token_type)
Expand All @@ -98,13 +96,13 @@ def decorated(*args, **kwargs):
documents_upload_quota = features.documents_upload_quota

if resource == "members" and 0 < members.limit <= members.size:
raise Forbidden(error_msg)
raise Forbidden("The number of members has reached the limit of your subscription.")
elif resource == "apps" and 0 < apps.limit <= apps.size:
raise Forbidden(error_msg)
raise Forbidden("The number of apps has reached the limit of your subscription.")
elif resource == "vector_space" and 0 < vector_space.limit <= vector_space.size:
raise Forbidden(error_msg)
raise Forbidden("The capacity of the vector space has reached the limit of your subscription.")
elif resource == "documents" and 0 < documents_upload_quota.limit <= documents_upload_quota.size:
raise Forbidden(error_msg)
raise Forbidden("The number of documents has reached the limit of your subscription.")
else:
return view(*args, **kwargs)

Expand All @@ -115,11 +113,7 @@ def decorated(*args, **kwargs):
return interceptor


def cloud_edition_billing_knowledge_limit_check(
resource: str,
api_token_type: str,
error_msg: str = "To unlock this feature and elevate your Dify experience, please upgrade to a paid plan.",
):
def cloud_edition_billing_knowledge_limit_check(resource: str, api_token_type: str):
def interceptor(view):
@wraps(view)
def decorated(*args, **kwargs):
Expand All @@ -128,7 +122,9 @@ def decorated(*args, **kwargs):
if features.billing.enabled:
if resource == "add_segment":
if features.billing.subscription.plan == "sandbox":
raise Forbidden(error_msg)
raise Forbidden(
"To unlock this feature and elevate your Dify experience, please upgrade to a paid plan."
)
else:
return view(*args, **kwargs)

Expand Down
21 changes: 16 additions & 5 deletions api/core/model_runtime/model_providers/volcengine_maas/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@
UserPromptMessage,
)

DEFAULT_V2_ENDPOINT = "maas-api.ml-platform-cn-beijing.volces.com"
DEFAULT_V3_ENDPOINT = "https://ark.cn-beijing.volces.com/api/v3"


class ArkClientV3:
endpoint_id: Optional[str] = None
Expand All @@ -43,16 +46,24 @@ def __init__(self, *args, **kwargs):

@staticmethod
def is_legacy(credentials: dict) -> bool:
# match default v2 endpoint
if ArkClientV3.is_compatible_with_legacy(credentials):
return False
sdk_version = credentials.get("sdk_version", "v2")
return sdk_version != "v3"
# match default v3 endpoint
if credentials.get("api_endpoint_host") == DEFAULT_V3_ENDPOINT:
return False
# only v3 support api_key
if credentials.get("auth_method") == "api_key":
return False
# these cases are considered as sdk v2
# - modified default v2 endpoint
# - modified default v3 endpoint and auth without api_key
return True

@staticmethod
def is_compatible_with_legacy(credentials: dict) -> bool:
sdk_version = credentials.get("sdk_version")
endpoint = credentials.get("api_endpoint_host")
return sdk_version is None and endpoint == "maas-api.ml-platform-cn-beijing.volces.com"
return endpoint == DEFAULT_V2_ENDPOINT

@classmethod
def from_credentials(cls, credentials):
Expand All @@ -64,7 +75,7 @@ def from_credentials(cls, credentials):
"sk": credentials['volc_secret_access_key'],
}
if cls.is_compatible_with_legacy(credentials):
args["base_url"] = "https://ark.cn-beijing.volces.com/api/v3"
args["base_url"] = DEFAULT_V3_ENDPOINT

client = ArkClientV3(
**args
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ model_credential_schema:
en_US: API Endpoint Host
zh_Hans: API Endpoint Host
type: text-input
default: maas-api.ml-platform-cn-beijing.volces.com
default: https://ark.cn-beijing.volces.com/api/v3
placeholder:
en_US: Enter your API Endpoint Host
zh_Hans: 输入 API Endpoint Host
Expand Down
1 change: 1 addition & 0 deletions api/core/tools/provider/_position.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
- wikipedia
- nominatim
- yahoo
- alphavantage
- arxiv
- pubmed
- stablediffusion
Expand Down
7 changes: 7 additions & 0 deletions api/core/tools/provider/builtin/alphavantage/_assets/icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
22 changes: 22 additions & 0 deletions api/core/tools/provider/builtin/alphavantage/alphavantage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from typing import Any

from core.tools.errors import ToolProviderCredentialValidationError
from core.tools.provider.builtin.alphavantage.tools.query_stock import QueryStockTool
from core.tools.provider.builtin_tool_provider import BuiltinToolProviderController


class AlphaVantageProvider(BuiltinToolProviderController):
def _validate_credentials(self, credentials: dict[str, Any]) -> None:
try:
QueryStockTool().fork_tool_runtime(
runtime={
"credentials": credentials,
}
).invoke(
user_id='',
tool_parameters={
"code": "AAPL", # Apple Inc.
},
)
except Exception as e:
raise ToolProviderCredentialValidationError(str(e))
31 changes: 31 additions & 0 deletions api/core/tools/provider/builtin/alphavantage/alphavantage.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
identity:
author: zhuhao
name: alphavantage
label:
en_US: AlphaVantage
zh_Hans: AlphaVantage
pt_BR: AlphaVantage
description:
en_US: AlphaVantage is an online platform that provides financial market data and APIs, making it convenient for individual investors and developers to access stock quotes, technical indicators, and stock analysis.
zh_Hans: AlphaVantage是一个在线平台,它提供金融市场数据和API,便于个人投资者和开发者获取股票报价、技术指标和股票分析。
pt_BR: AlphaVantage is an online platform that provides financial market data and APIs, making it convenient for individual investors and developers to access stock quotes, technical indicators, and stock analysis.
icon: icon.svg
tags:
- finance
credentials_for_provider:
api_key:
type: secret-input
required: true
label:
en_US: AlphaVantage API key
zh_Hans: AlphaVantage API key
pt_BR: AlphaVantage API key
placeholder:
en_US: Please input your AlphaVantage API key
zh_Hans: 请输入你的 AlphaVantage API key
pt_BR: Please input your AlphaVantage API key
help:
en_US: Get your AlphaVantage API key from AlphaVantage
zh_Hans: 从 AlphaVantage 获取您的 AlphaVantage API key
pt_BR: Get your AlphaVantage API key from AlphaVantage
url: https://www.alphavantage.co/support/#api-key
49 changes: 49 additions & 0 deletions api/core/tools/provider/builtin/alphavantage/tools/query_stock.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
from typing import Any, Union

import requests

from core.tools.entities.tool_entities import ToolInvokeMessage
from core.tools.tool.builtin_tool import BuiltinTool

ALPHAVANTAGE_API_URL = "https://www.alphavantage.co/query"


class QueryStockTool(BuiltinTool):

def _invoke(self,
user_id: str,
tool_parameters: dict[str, Any],
) -> Union[ToolInvokeMessage, list[ToolInvokeMessage]]:

stock_code = tool_parameters.get('code', '')
if not stock_code:
return self.create_text_message('Please tell me your stock code')

if 'api_key' not in self.runtime.credentials or not self.runtime.credentials.get('api_key'):
return self.create_text_message("Alpha Vantage API key is required.")

params = {
"function": "TIME_SERIES_DAILY",
"symbol": stock_code,
"outputsize": "compact",
"datatype": "json",
"apikey": self.runtime.credentials['api_key']
}
response = requests.get(url=ALPHAVANTAGE_API_URL, params=params)
response.raise_for_status()
result = self._handle_response(response.json())
return self.create_json_message(result)

def _handle_response(self, response: dict[str, Any]) -> dict[str, Any]:
result = response.get('Time Series (Daily)', {})
if not result:
return {}
stock_result = {}
for k, v in result.items():
stock_result[k] = {}
stock_result[k]['open'] = v.get('1. open')
stock_result[k]['high'] = v.get('2. high')
stock_result[k]['low'] = v.get('3. low')
stock_result[k]['close'] = v.get('4. close')
stock_result[k]['volume'] = v.get('5. volume')
return stock_result
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
identity:
name: query_stock
author: zhuhao
label:
en_US: query_stock
zh_Hans: query_stock
pt_BR: query_stock
description:
human:
en_US: Retrieve information such as daily opening price, daily highest price, daily lowest price, daily closing price, and daily trading volume for a specified stock symbol.
zh_Hans: 获取指定股票代码的每日开盘价、每日最高价、每日最低价、每日收盘价和每日交易量等信息。
pt_BR: Retrieve information such as daily opening price, daily highest price, daily lowest price, daily closing price, and daily trading volume for a specified stock symbol
llm: Retrieve information such as daily opening price, daily highest price, daily lowest price, daily closing price, and daily trading volume for a specified stock symbol
parameters:
- name: code
type: string
required: true
label:
en_US: stock code
zh_Hans: 股票代码
pt_BR: stock code
human_description:
en_US: stock code
zh_Hans: 股票代码
pt_BR: stock code
llm_description: stock code for query from alphavantage
form: llm
10 changes: 3 additions & 7 deletions api/core/workflow/nodes/http_request/entities.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,6 @@
from configs import dify_config
from core.workflow.entities.base_node_data_entities import BaseNodeData

MAX_CONNECT_TIMEOUT = dify_config.HTTP_REQUEST_MAX_CONNECT_TIMEOUT
MAX_READ_TIMEOUT = dify_config.HTTP_REQUEST_MAX_READ_TIMEOUT
MAX_WRITE_TIMEOUT = dify_config.HTTP_REQUEST_MAX_WRITE_TIMEOUT


class HttpRequestNodeAuthorizationConfig(BaseModel):
type: Literal[None, 'basic', 'bearer', 'custom']
Expand Down Expand Up @@ -41,9 +37,9 @@ class HttpRequestNodeBody(BaseModel):


class HttpRequestNodeTimeout(BaseModel):
connect: int = MAX_CONNECT_TIMEOUT
read: int = MAX_READ_TIMEOUT
write: int = MAX_WRITE_TIMEOUT
connect: int = dify_config.HTTP_REQUEST_MAX_CONNECT_TIMEOUT
read: int = dify_config.HTTP_REQUEST_MAX_READ_TIMEOUT
write: int = dify_config.HTTP_REQUEST_MAX_WRITE_TIMEOUT


class HttpRequestNodeData(BaseNodeData):
Expand Down
Loading

0 comments on commit 69a1f73

Please sign in to comment.