Merge main into feat/plugin
@ -21,6 +21,7 @@ Dify支持`文本` `链接` `图片` `文件BLOB` `JSON` 等多种消息类型
|
||||
create an image message
|
||||
|
||||
:param image: the url of the image
|
||||
:param save_as: save as
|
||||
:return: the image message
|
||||
"""
|
||||
```
|
||||
@ -34,6 +35,7 @@ Dify支持`文本` `链接` `图片` `文件BLOB` `JSON` 等多种消息类型
|
||||
create a link message
|
||||
|
||||
:param link: the url of the link
|
||||
:param save_as: save as
|
||||
:return: the link message
|
||||
"""
|
||||
```
|
||||
@ -47,6 +49,7 @@ Dify支持`文本` `链接` `图片` `文件BLOB` `JSON` 等多种消息类型
|
||||
create a text message
|
||||
|
||||
:param text: the text of the message
|
||||
:param save_as: save as
|
||||
:return: the text message
|
||||
"""
|
||||
```
|
||||
@ -63,6 +66,8 @@ Dify支持`文本` `链接` `图片` `文件BLOB` `JSON` 等多种消息类型
|
||||
create a blob message
|
||||
|
||||
:param blob: the blob
|
||||
:param meta: meta
|
||||
:param save_as: save as
|
||||
:return: the blob message
|
||||
"""
|
||||
```
|
||||
|
||||
@ -46,7 +46,7 @@ class ToolProviderType(Enum):
|
||||
if mode.value == value:
|
||||
return mode
|
||||
raise ValueError(f'invalid mode value {value}')
|
||||
|
||||
|
||||
class ApiProviderSchemaType(Enum):
|
||||
"""
|
||||
Enum class for api provider schema type.
|
||||
@ -68,7 +68,7 @@ class ApiProviderSchemaType(Enum):
|
||||
if mode.value == value:
|
||||
return mode
|
||||
raise ValueError(f'invalid mode value {value}')
|
||||
|
||||
|
||||
class ApiProviderAuthType(Enum):
|
||||
"""
|
||||
Enum class for api provider auth type.
|
||||
@ -109,8 +109,8 @@ class ToolInvokeMessage(BaseModel):
|
||||
"""
|
||||
plain text, image url or link url
|
||||
"""
|
||||
message: JsonMessage | TextMessage
|
||||
meta: Optional[dict[str, Any]] = None
|
||||
message: JsonMessage | TextMessage | None
|
||||
meta: dict[str, Any] | None = None
|
||||
save_as: str = ''
|
||||
|
||||
class ToolInvokeMessageBinary(BaseModel):
|
||||
@ -154,14 +154,14 @@ class ToolParameter(BaseModel):
|
||||
form: ToolParameterForm = Field(..., description="The form of the parameter, schema/form/llm")
|
||||
llm_description: Optional[str] = None
|
||||
required: Optional[bool] = False
|
||||
default: Optional[Union[int, str]] = None
|
||||
default: Optional[Union[float, int, str]] = None
|
||||
min: Optional[Union[float, int]] = None
|
||||
max: Optional[Union[float, int]] = None
|
||||
options: Optional[list[ToolParameterOption]] = None
|
||||
|
||||
@classmethod
|
||||
def get_simple_instance(cls,
|
||||
name: str, llm_description: str, type: ToolParameterType,
|
||||
def get_simple_instance(cls,
|
||||
name: str, llm_description: str, type: ToolParameterType,
|
||||
required: bool, options: Optional[list[str]] = None) -> 'ToolParameter':
|
||||
"""
|
||||
get a simple tool parameter
|
||||
@ -231,7 +231,7 @@ class ToolProviderCredentials(BaseModel):
|
||||
if mode.value == value:
|
||||
return mode
|
||||
raise ValueError(f'invalid mode value {value}')
|
||||
|
||||
|
||||
@staticmethod
|
||||
def default(value: str) -> str:
|
||||
return ""
|
||||
@ -299,7 +299,7 @@ class ToolRuntimeVariablePool(BaseModel):
|
||||
'tenant_id': self.tenant_id,
|
||||
'pool': [variable.model_dump() for variable in self.pool],
|
||||
}
|
||||
|
||||
|
||||
def set_text(self, tool_name: str, name: str, value: str) -> None:
|
||||
"""
|
||||
set a text variable
|
||||
@ -310,7 +310,7 @@ class ToolRuntimeVariablePool(BaseModel):
|
||||
variable = cast(ToolRuntimeTextVariable, variable)
|
||||
variable.value = value
|
||||
return
|
||||
|
||||
|
||||
variable = ToolRuntimeTextVariable(
|
||||
type=ToolRuntimeVariableType.TEXT,
|
||||
name=name,
|
||||
@ -343,7 +343,7 @@ class ToolRuntimeVariablePool(BaseModel):
|
||||
variable = cast(ToolRuntimeImageVariable, variable)
|
||||
variable.value = value
|
||||
return
|
||||
|
||||
|
||||
variable = ToolRuntimeImageVariable(
|
||||
type=ToolRuntimeVariableType.IMAGE,
|
||||
name=name,
|
||||
@ -397,21 +397,21 @@ class ToolInvokeMeta(BaseModel):
|
||||
Get an empty instance of ToolInvokeMeta
|
||||
"""
|
||||
return cls(time_cost=0.0, error=None, tool_config={})
|
||||
|
||||
|
||||
@classmethod
|
||||
def error_instance(cls, error: str) -> 'ToolInvokeMeta':
|
||||
"""
|
||||
Get an instance of ToolInvokeMeta with error
|
||||
"""
|
||||
return cls(time_cost=0.0, error=error, tool_config={})
|
||||
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
return {
|
||||
'time_cost': self.time_cost,
|
||||
'error': self.error,
|
||||
'tool_config': self.tool_config,
|
||||
}
|
||||
|
||||
|
||||
class ToolLabel(BaseModel):
|
||||
"""
|
||||
Tool label
|
||||
@ -425,4 +425,4 @@ class ToolInvokeFrom(Enum):
|
||||
Enum class for tool invoke
|
||||
"""
|
||||
WORKFLOW = "workflow"
|
||||
AGENT = "agent"
|
||||
AGENT = "agent"
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
- bing
|
||||
- duckduckgo
|
||||
- searchapi
|
||||
- serper
|
||||
- searxng
|
||||
- dalle
|
||||
- azuredalle
|
||||
@ -9,6 +10,7 @@
|
||||
- wikipedia
|
||||
- nominatim
|
||||
- yahoo
|
||||
- alphavantage
|
||||
- arxiv
|
||||
- pubmed
|
||||
- stablediffusion
|
||||
@ -29,5 +31,7 @@
|
||||
- dingtalk
|
||||
- feishu
|
||||
- feishu_base
|
||||
- feishu_document
|
||||
- feishu_message
|
||||
- slack
|
||||
- tianditu
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import os.path
|
||||
|
||||
from core.helper.position_helper import get_position_map, sort_by_position_map
|
||||
from core.helper.position_helper import get_tool_position_map, sort_by_position_map
|
||||
from core.tools.entities.api_entities import UserToolProvider
|
||||
|
||||
|
||||
@ -10,11 +10,11 @@ class BuiltinToolProviderSort:
|
||||
@classmethod
|
||||
def sort(cls, providers: list[UserToolProvider]) -> list[UserToolProvider]:
|
||||
if not cls._position:
|
||||
cls._position = get_position_map(os.path.join(os.path.dirname(__file__), '..'))
|
||||
cls._position = get_tool_position_map(os.path.join(os.path.dirname(__file__), '..'))
|
||||
|
||||
def name_func(provider: UserToolProvider) -> str:
|
||||
return provider.name
|
||||
|
||||
sorted_providers = sort_by_position_map(cls._position, providers, name_func)
|
||||
|
||||
return sorted_providers
|
||||
return sorted_providers
|
||||
|
||||
@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="56px" height="56px" viewBox="0 0 56 56" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>形状结合</title>
|
||||
<g id="设计规范" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<path d="M56,0 L56,56 L0,56 L0,0 L56,0 Z M31.6063018,12 L24.3936982,12 L24.1061064,12.7425499 L12.6071308,42.4324141 L12,44 L19.7849972,44 L20.0648488,43.2391815 L22.5196173,36.5567427 L33.4780427,36.5567427 L35.9351512,43.2391815 L36.2150028,44 L44,44 L43.3928692,42.4324141 L31.8938936,12.7425499 L31.6063018,12 Z M28.0163803,21.5755126 L31.1613993,30.2523823 L24.8432808,30.2523823 L28.0163803,21.5755126 Z" id="形状结合" fill="#2F4F4F"></path>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 780 B |
22
api/core/tools/provider/builtin/alphavantage/alphavantage.py
Normal file
@ -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))
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -25,7 +25,7 @@ parameters:
|
||||
pt_BR: Prompt
|
||||
human_description:
|
||||
en_US: Image prompt, you can check the official documentation of DallE 3
|
||||
zh_Hans: 图像提示词,您可以查看DallE 3 的官方文档
|
||||
zh_Hans: 图像提示词,您可以查看 DallE 3 的官方文档
|
||||
pt_BR: Imagem prompt, você pode verificar a documentação oficial do DallE 3
|
||||
llm_description: Image prompt of DallE 3, you should describe the image you want to generate as a list of words as possible as detailed
|
||||
form: llm
|
||||
|
||||
@ -25,7 +25,7 @@ parameters:
|
||||
pt_BR: Prompt
|
||||
human_description:
|
||||
en_US: Image prompt, you can check the official documentation of CogView 3
|
||||
zh_Hans: 图像提示词,您可以查看CogView 3 的官方文档
|
||||
zh_Hans: 图像提示词,您可以查看 CogView 3 的官方文档
|
||||
pt_BR: Image prompt, you can check the official documentation of CogView 3
|
||||
llm_description: Image prompt of CogView 3, you should describe the image you want to generate as a list of words as possible as detailed
|
||||
form: llm
|
||||
|
||||
49
api/core/tools/provider/builtin/crossref/_assets/icon.svg
Normal file
@ -0,0 +1,49 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 19.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 200 130.2" style="enable-background:new 0 0 200 130.2;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#3EB1C8;}
|
||||
.st1{fill:#D8D2C4;}
|
||||
.st2{fill:#4F5858;}
|
||||
.st3{fill:#FFC72C;}
|
||||
.st4{fill:#EF3340;}
|
||||
</style>
|
||||
<g>
|
||||
<polygon class="st0" points="111.8,95.5 111.8,66.8 135.4,59 177.2,73.3 "/>
|
||||
<polygon class="st1" points="153.6,36.8 111.8,51.2 135.4,59 177.2,44.6 "/>
|
||||
<polygon class="st2" points="135.4,59 177.2,44.6 177.2,73.3 "/>
|
||||
<polygon class="st3" points="177.2,0.3 177.2,29 153.6,36.8 111.8,22.5 "/>
|
||||
<polygon class="st4" points="153.6,36.8 111.8,51.2 111.8,22.5 "/>
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<path class="st2" d="M26.3,104.8c-0.5-3.7-4.1-6.5-8.1-6.5c-7.3,0-10.1,6.2-10.1,12.7c0,6.2,2.8,12.4,10.1,12.4
|
||||
c5,0,7.8-3.4,8.4-8.3h7.9c-0.8,9.2-7.2,15.2-16.3,15.2C6.8,130.2,0,121.7,0,111c0-11,6.8-19.6,18.2-19.6c8.2,0,15,4.8,16,13.3
|
||||
H26.3z"/>
|
||||
<path class="st2" d="M37.4,102.5h7v5h0.1c1.4-3.4,5-5.7,8.6-5.7c0.5,0,1.1,0.1,1.6,0.3v6.9c-0.7-0.2-1.8-0.3-2.6-0.3
|
||||
c-5.4,0-7.3,3.9-7.3,8.6v12.1h-7.4V102.5z"/>
|
||||
<path class="st2" d="M68.7,101.8c8.5,0,13.9,5.6,13.9,14.2c0,8.5-5.5,14.1-13.9,14.1c-8.4,0-13.9-5.6-13.9-14.1
|
||||
C54.9,107.4,60.3,101.8,68.7,101.8z M68.7,124.5c5,0,6.5-4.3,6.5-8.6c0-4.3-1.5-8.6-6.5-8.6c-5,0-6.5,4.3-6.5,8.6
|
||||
C62.2,120.2,63.8,124.5,68.7,124.5z"/>
|
||||
<path class="st2" d="M91.2,120.6c0.1,3.2,2.8,4.5,5.7,4.5c2.1,0,4.8-0.8,4.8-3.4c0-2.2-3.1-3-8.4-4.2c-4.3-0.9-8.5-2.4-8.5-7.2
|
||||
c0-6.9,5.9-8.6,11.7-8.6c5.9,0,11.3,2,11.8,8.6h-7c-0.2-2.9-2.4-3.6-5-3.6c-1.7,0-4.1,0.3-4.1,2.5c0,2.6,4.2,3,8.4,4
|
||||
c4.3,1,8.5,2.5,8.5,7.5c0,7.1-6.1,9.3-12.3,9.3c-6.2,0-12.3-2.3-12.6-9.5H91.2z"/>
|
||||
<path class="st2" d="M118.1,120.6c0.1,3.2,2.8,4.5,5.7,4.5c2.1,0,4.8-0.8,4.8-3.4c0-2.2-3.1-3-8.4-4.2
|
||||
c-4.3-0.9-8.5-2.4-8.5-7.2c0-6.9,5.9-8.6,11.7-8.6c5.9,0,11.3,2,11.8,8.6h-7c-0.2-2.9-2.4-3.6-5-3.6c-1.7,0-4.1,0.3-4.1,2.5
|
||||
c0,2.6,4.2,3,8.4,4c4.3,1,8.5,2.5,8.5,7.5c0,7.1-6.1,9.3-12.3,9.3c-6.2,0-12.3-2.3-12.6-9.5H118.1z"/>
|
||||
<path class="st2" d="M138.4,102.5h7v5h0.1c1.4-3.4,5-5.7,8.6-5.7c0.5,0,1.1,0.1,1.6,0.3v6.9c-0.7-0.2-1.8-0.3-2.6-0.3
|
||||
c-5.4,0-7.3,3.9-7.3,8.6v12.1h-7.4V102.5z"/>
|
||||
<path class="st2" d="M163.7,117.7c0.2,4.7,2.5,6.8,6.6,6.8c3,0,5.3-1.8,5.8-3.5h6.5c-2.1,6.3-6.5,9-12.6,9
|
||||
c-8.5,0-13.7-5.8-13.7-14.1c0-8,5.6-14.2,13.7-14.2c9.1,0,13.6,7.7,13,15.9H163.7z M175.7,113.1c-0.7-3.7-2.3-5.7-5.9-5.7
|
||||
c-4.7,0-6,3.6-6.1,5.7H175.7z"/>
|
||||
<path class="st2" d="M187.2,107.5h-4.4v-4.9h4.4v-2.1c0-4.7,3-8.2,9-8.2c1.3,0,2.6,0.2,3.9,0.2V98c-0.9-0.1-1.8-0.2-2.7-0.2
|
||||
c-2,0-2.8,0.8-2.8,3.1v1.6h5.1v4.9h-5.1v21.9h-7.4V107.5z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.0 KiB |
20
api/core/tools/provider/builtin/crossref/crossref.py
Normal file
@ -0,0 +1,20 @@
|
||||
from core.tools.errors import ToolProviderCredentialValidationError
|
||||
from core.tools.provider.builtin.crossref.tools.query_doi import CrossRefQueryDOITool
|
||||
from core.tools.provider.builtin_tool_provider import BuiltinToolProviderController
|
||||
|
||||
|
||||
class CrossRefProvider(BuiltinToolProviderController):
|
||||
def _validate_credentials(self, credentials: dict) -> None:
|
||||
try:
|
||||
CrossRefQueryDOITool().fork_tool_runtime(
|
||||
runtime={
|
||||
"credentials": credentials,
|
||||
}
|
||||
).invoke(
|
||||
user_id='',
|
||||
tool_parameters={
|
||||
"doi": '10.1007/s00894-022-05373-8',
|
||||
},
|
||||
)
|
||||
except Exception as e:
|
||||
raise ToolProviderCredentialValidationError(str(e))
|
||||
29
api/core/tools/provider/builtin/crossref/crossref.yaml
Normal file
@ -0,0 +1,29 @@
|
||||
identity:
|
||||
author: Sakura4036
|
||||
name: crossref
|
||||
label:
|
||||
en_US: CrossRef
|
||||
zh_Hans: CrossRef
|
||||
description:
|
||||
en_US: Crossref is a cross-publisher reference linking registration query system using DOI technology created in 2000. Crossref establishes cross-database links between the reference list and citation full text of papers, making it very convenient for readers to access the full text of papers.
|
||||
zh_Hans: Crossref是于2000年创建的使用DOI技术的跨出版商参考文献链接注册查询系统。Crossref建立了在论文的参考文献列表和引文全文之间的跨数据库链接,使得读者能够非常便捷地获取文献全文。
|
||||
icon: icon.svg
|
||||
tags:
|
||||
- search
|
||||
credentials_for_provider:
|
||||
mailto:
|
||||
type: text-input
|
||||
required: true
|
||||
label:
|
||||
en_US: email address
|
||||
zh_Hans: email地址
|
||||
pt_BR: email address
|
||||
placeholder:
|
||||
en_US: Please input your email address
|
||||
zh_Hans: 请输入你的email地址
|
||||
pt_BR: Please input your email address
|
||||
help:
|
||||
en_US: According to the requirements of Crossref, an email address is required
|
||||
zh_Hans: 根据Crossref的要求,需要提供一个邮箱地址
|
||||
pt_BR: According to the requirements of Crossref, an email address is required
|
||||
url: https://api.crossref.org/swagger-ui/index.html
|
||||
25
api/core/tools/provider/builtin/crossref/tools/query_doi.py
Normal file
@ -0,0 +1,25 @@
|
||||
from typing import Any, Union
|
||||
|
||||
import requests
|
||||
|
||||
from core.tools.entities.tool_entities import ToolInvokeMessage
|
||||
from core.tools.errors import ToolParameterValidationError
|
||||
from core.tools.tool.builtin_tool import BuiltinTool
|
||||
|
||||
|
||||
class CrossRefQueryDOITool(BuiltinTool):
|
||||
"""
|
||||
Tool for querying the metadata of a publication using its DOI.
|
||||
"""
|
||||
def _invoke(self, user_id: str, tool_parameters: dict[str, Any]) -> Union[ToolInvokeMessage, list[ToolInvokeMessage]]:
|
||||
doi = tool_parameters.get('doi')
|
||||
if not doi:
|
||||
raise ToolParameterValidationError('doi is required.')
|
||||
# doc: https://github.com/CrossRef/rest-api-doc
|
||||
url = f"https://api.crossref.org/works/{doi}"
|
||||
response = requests.get(url)
|
||||
response.raise_for_status()
|
||||
response = response.json()
|
||||
message = response.get('message', {})
|
||||
|
||||
return self.create_json_message(message)
|
||||
@ -0,0 +1,23 @@
|
||||
identity:
|
||||
name: crossref_query_doi
|
||||
author: Sakura4036
|
||||
label:
|
||||
en_US: CrossRef Query DOI
|
||||
zh_Hans: CrossRef DOI 查询
|
||||
pt_BR: CrossRef Query DOI
|
||||
description:
|
||||
human:
|
||||
en_US: A tool for searching literature information using CrossRef by DOI.
|
||||
zh_Hans: 一个使用CrossRef通过DOI获取文献信息的工具。
|
||||
pt_BR: A tool for searching literature information using CrossRef by DOI.
|
||||
llm: A tool for searching literature information using CrossRef by DOI.
|
||||
parameters:
|
||||
- name: doi
|
||||
type: string
|
||||
required: true
|
||||
label:
|
||||
en_US: DOI
|
||||
zh_Hans: DOI
|
||||
pt_BR: DOI
|
||||
llm_description: DOI for searching in CrossRef
|
||||
form: llm
|
||||
120
api/core/tools/provider/builtin/crossref/tools/query_title.py
Normal file
@ -0,0 +1,120 @@
|
||||
import time
|
||||
from typing import Any, Union
|
||||
|
||||
import requests
|
||||
|
||||
from core.tools.entities.tool_entities import ToolInvokeMessage
|
||||
from core.tools.tool.builtin_tool import BuiltinTool
|
||||
|
||||
|
||||
def convert_time_str_to_seconds(time_str: str) -> int:
|
||||
"""
|
||||
Convert a time string to seconds.
|
||||
example: 1s -> 1, 1m30s -> 90, 1h30m -> 5400, 1h30m30s -> 5430
|
||||
"""
|
||||
time_str = time_str.lower().strip().replace(' ', '')
|
||||
seconds = 0
|
||||
if 'h' in time_str:
|
||||
hours, time_str = time_str.split('h')
|
||||
seconds += int(hours) * 3600
|
||||
if 'm' in time_str:
|
||||
minutes, time_str = time_str.split('m')
|
||||
seconds += int(minutes) * 60
|
||||
if 's' in time_str:
|
||||
seconds += int(time_str.replace('s', ''))
|
||||
return seconds
|
||||
|
||||
|
||||
class CrossRefQueryTitleAPI:
|
||||
"""
|
||||
Tool for querying the metadata of a publication using its title.
|
||||
Crossref API doc: https://github.com/CrossRef/rest-api-doc
|
||||
"""
|
||||
query_url_template: str = "https://api.crossref.org/works?query.bibliographic={query}&rows={rows}&offset={offset}&sort={sort}&order={order}&mailto={mailto}"
|
||||
rate_limit: int = 50
|
||||
rate_interval: float = 1
|
||||
max_limit: int = 1000
|
||||
|
||||
def __init__(self, mailto: str):
|
||||
self.mailto = mailto
|
||||
|
||||
def _query(self, query: str, rows: int = 5, offset: int = 0, sort: str = 'relevance', order: str = 'desc', fuzzy_query: bool = False) -> list[dict]:
|
||||
"""
|
||||
Query the metadata of a publication using its title.
|
||||
:param query: the title of the publication
|
||||
:param rows: the number of results to return
|
||||
:param sort: the sort field
|
||||
:param order: the sort order
|
||||
:param fuzzy_query: whether to return all items that match the query
|
||||
"""
|
||||
url = self.query_url_template.format(query=query, rows=rows, offset=offset, sort=sort, order=order, mailto=self.mailto)
|
||||
response = requests.get(url)
|
||||
response.raise_for_status()
|
||||
rate_limit = int(response.headers['x-ratelimit-limit'])
|
||||
# convert time string to seconds
|
||||
rate_interval = convert_time_str_to_seconds(response.headers['x-ratelimit-interval'])
|
||||
|
||||
self.rate_limit = rate_limit
|
||||
self.rate_interval = rate_interval
|
||||
|
||||
response = response.json()
|
||||
if response['status'] != 'ok':
|
||||
return []
|
||||
|
||||
message = response['message']
|
||||
if fuzzy_query:
|
||||
# fuzzy query return all items
|
||||
return message['items']
|
||||
else:
|
||||
for paper in message['items']:
|
||||
title = paper['title'][0]
|
||||
if title.lower() != query.lower():
|
||||
continue
|
||||
return [paper]
|
||||
return []
|
||||
|
||||
def query(self, query: str, rows: int = 5, sort: str = 'relevance', order: str = 'desc', fuzzy_query: bool = False) -> list[dict]:
|
||||
"""
|
||||
Query the metadata of a publication using its title.
|
||||
:param query: the title of the publication
|
||||
:param rows: the number of results to return
|
||||
:param sort: the sort field
|
||||
:param order: the sort order
|
||||
:param fuzzy_query: whether to return all items that match the query
|
||||
"""
|
||||
rows = min(rows, self.max_limit)
|
||||
if rows > self.rate_limit:
|
||||
# query multiple times
|
||||
query_times = rows // self.rate_limit + 1
|
||||
results = []
|
||||
|
||||
for i in range(query_times):
|
||||
result = self._query(query, rows=self.rate_limit, offset=i * self.rate_limit, sort=sort, order=order, fuzzy_query=fuzzy_query)
|
||||
if fuzzy_query:
|
||||
results.extend(result)
|
||||
else:
|
||||
# fuzzy_query=False, only one result
|
||||
if result:
|
||||
return result
|
||||
time.sleep(self.rate_interval)
|
||||
return results
|
||||
else:
|
||||
# query once
|
||||
return self._query(query, rows, sort=sort, order=order, fuzzy_query=fuzzy_query)
|
||||
|
||||
|
||||
class CrossRefQueryTitleTool(BuiltinTool):
|
||||
"""
|
||||
Tool for querying the metadata of a publication using its title.
|
||||
"""
|
||||
def _invoke(self, user_id: str, tool_parameters: dict[str, Any]) -> Union[ToolInvokeMessage, list[ToolInvokeMessage]]:
|
||||
query = tool_parameters.get('query')
|
||||
fuzzy_query = tool_parameters.get('fuzzy_query', False)
|
||||
rows = tool_parameters.get('rows', 3)
|
||||
sort = tool_parameters.get('sort', 'relevance')
|
||||
order = tool_parameters.get('order', 'desc')
|
||||
mailto = self.runtime.credentials['mailto']
|
||||
|
||||
result = CrossRefQueryTitleAPI(mailto).query(query, rows, sort, order, fuzzy_query)
|
||||
|
||||
return [self.create_json_message(r) for r in result]
|
||||
105
api/core/tools/provider/builtin/crossref/tools/query_title.yaml
Normal file
@ -0,0 +1,105 @@
|
||||
identity:
|
||||
name: crossref_query_title
|
||||
author: Sakura4036
|
||||
label:
|
||||
en_US: CrossRef Title Query
|
||||
zh_Hans: CrossRef 标题查询
|
||||
pt_BR: CrossRef Title Query
|
||||
description:
|
||||
human:
|
||||
en_US: A tool for querying literature information using CrossRef by title.
|
||||
zh_Hans: 一个使用CrossRef通过标题搜索文献信息的工具。
|
||||
pt_BR: A tool for querying literature information using CrossRef by title.
|
||||
llm: A tool for querying literature information using CrossRef by title.
|
||||
parameters:
|
||||
- name: query
|
||||
type: string
|
||||
required: true
|
||||
label:
|
||||
en_US: 标题
|
||||
zh_Hans: 查询语句
|
||||
pt_BR: 标题
|
||||
human_description:
|
||||
en_US: Query bibliographic information, useful for citation look up. Includes titles, authors, ISSNs and publication years
|
||||
zh_Hans: 用于搜索文献信息,有助于查找引用。包括标题,作者,ISSN和出版年份
|
||||
pt_BR: Query bibliographic information, useful for citation look up. Includes titles, authors, ISSNs and publication years
|
||||
llm_description: key words for querying in Web of Science
|
||||
form: llm
|
||||
- name: fuzzy_query
|
||||
type: boolean
|
||||
default: false
|
||||
label:
|
||||
en_US: Whether to fuzzy search
|
||||
zh_Hans: 是否模糊搜索
|
||||
pt_BR: Whether to fuzzy search
|
||||
human_description:
|
||||
en_US: used for selecting the query type, fuzzy query returns more results, precise query returns 1 or none
|
||||
zh_Hans: 用于选择搜索类型,模糊搜索返回更多结果,精确搜索返回1条结果或无
|
||||
pt_BR: used for selecting the query type, fuzzy query returns more results, precise query returns 1 or none
|
||||
form: form
|
||||
- name: limit
|
||||
type: number
|
||||
required: false
|
||||
label:
|
||||
en_US: max query number
|
||||
zh_Hans: 最大搜索数
|
||||
pt_BR: max query number
|
||||
human_description:
|
||||
en_US: max query number(fuzzy search returns the maximum number of results or precise search the maximum number of matches)
|
||||
zh_Hans: 最大搜索数(模糊搜索返回的最大结果数或精确搜索最大匹配数)
|
||||
pt_BR: max query number(fuzzy search returns the maximum number of results or precise search the maximum number of matches)
|
||||
form: llm
|
||||
default: 50
|
||||
- name: sort
|
||||
type: select
|
||||
required: true
|
||||
options:
|
||||
- value: relevance
|
||||
label:
|
||||
en_US: relevance
|
||||
zh_Hans: 相关性
|
||||
pt_BR: relevance
|
||||
- value: published
|
||||
label:
|
||||
en_US: publication date
|
||||
zh_Hans: 出版日期
|
||||
pt_BR: publication date
|
||||
- value: references-count
|
||||
label:
|
||||
en_US: references-count
|
||||
zh_Hans: 引用次数
|
||||
pt_BR: references-count
|
||||
default: relevance
|
||||
label:
|
||||
en_US: sorting field
|
||||
zh_Hans: 排序字段
|
||||
pt_BR: sorting field
|
||||
human_description:
|
||||
en_US: Sorting of query results
|
||||
zh_Hans: 检索结果的排序字段
|
||||
pt_BR: Sorting of query results
|
||||
form: form
|
||||
- name: order
|
||||
type: select
|
||||
required: true
|
||||
options:
|
||||
- value: desc
|
||||
label:
|
||||
en_US: descending
|
||||
zh_Hans: 降序
|
||||
pt_BR: descending
|
||||
- value: asc
|
||||
label:
|
||||
en_US: ascending
|
||||
zh_Hans: 升序
|
||||
pt_BR: ascending
|
||||
default: desc
|
||||
label:
|
||||
en_US: Order
|
||||
zh_Hans: 排序
|
||||
pt_BR: Order
|
||||
human_description:
|
||||
en_US: Order of query results
|
||||
zh_Hans: 检索结果的排序方式
|
||||
pt_BR: Order of query results
|
||||
form: form
|
||||
@ -24,7 +24,7 @@ parameters:
|
||||
pt_BR: Prompt
|
||||
human_description:
|
||||
en_US: Image prompt, you can check the official documentation of DallE 2
|
||||
zh_Hans: 图像提示词,您可以查看DallE 2 的官方文档
|
||||
zh_Hans: 图像提示词,您可以查看 DallE 2 的官方文档
|
||||
pt_BR: Image prompt, you can check the official documentation of DallE 2
|
||||
llm_description: Image prompt of DallE 2, you should describe the image you want to generate as a list of words as possible as detailed
|
||||
form: llm
|
||||
|
||||
@ -25,7 +25,7 @@ parameters:
|
||||
pt_BR: Prompt
|
||||
human_description:
|
||||
en_US: Image prompt, you can check the official documentation of DallE 3
|
||||
zh_Hans: 图像提示词,您可以查看DallE 3 的官方文档
|
||||
zh_Hans: 图像提示词,您可以查看 DallE 3 的官方文档
|
||||
pt_BR: Image prompt, you can check the official documentation of DallE 3
|
||||
llm_description: Image prompt of DallE 3, you should describe the image you want to generate as a list of words as possible as detailed
|
||||
form: llm
|
||||
|
||||
14
api/core/tools/provider/builtin/did/_assets/icon.svg
Normal file
@ -0,0 +1,14 @@
|
||||
<svg viewBox="0 0 40 40" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="0" y="0" width="40" height="40" style="fill: #0d0a08;"></rect>
|
||||
<g clip-path="url(#clip0_269_13)" transform="matrix(0.429227, 0, 0, 0.429227, 6.326543, 9.593137)" style="background-color: f4f3f2">
|
||||
<path d="M6.05159 7.04111H0.5V44.0227H6.05159C13.5 44.0227 16.6023 42.1692 16.6023 34.1718V16.8831C16.6023 8.791 13.503 7.03223 6.05159 7.03223V7.03815V7.04111ZM11.9755 34.1718C11.9755 38.7019 10.5898 39.3948 6.09591 39.3948H5.12091V11.6601H6.09591C10.5839 11.6601 11.9755 12.353 11.9755 16.8831V34.1718Z" fill="white"></path>
|
||||
<path d="M18.9834 26.2188V29.9169H25.9207V26.2188H18.9834Z" fill="white"></path>
|
||||
<path d="M28.562 13.9783V44.0225H33.1888V13.9783H28.562Z" fill="white"></path>
|
||||
<path d="M41.3822 7.04111H35.8306V44.0227H41.3822C48.8306 44.0227 51.9358 42.1692 51.9358 34.1718V16.8831C51.9358 8.791 48.8365 7.03223 41.3822 7.03223V7.03815V7.04111ZM47.306 34.1718C47.306 38.7019 45.9203 39.3948 41.4265 39.3948H40.4515V11.6601H41.4265C45.9144 11.6601 47.306 12.353 47.306 16.8831V34.1718Z" fill="white"></path>
|
||||
<path d="M30.8758 11.2278C32.2775 11.2278 33.4138 10.0917 33.4138 8.69032C33.4138 7.2889 32.2775 6.15283 30.8758 6.15283C29.4742 6.15283 28.3379 7.2889 28.3379 8.69032C28.3379 10.0917 29.4742 11.2278 30.8758 11.2278Z" fill="#FF882E"></path>
|
||||
<path d="M36.191 4.02677C36.9621 4.02677 37.5885 3.40202 37.5885 2.62923C37.5885 1.85644 36.9621 1.23169 36.191 1.23169C35.4198 1.23169 34.7935 1.85644 34.7935 2.62923C34.7935 3.40202 35.4198 4.02677 36.191 4.02677Z" fill="#FF882E"></path>
|
||||
<path d="M42.1978 2.09631C42.7769 2.09631 43.2467 1.62553 43.2467 1.04816C43.2467 0.470782 42.7769 0 42.1978 0C41.6187 0 41.1489 0.470782 41.1489 1.04816C41.1489 1.62553 41.6187 2.09631 42.1978 2.09631Z" fill="#FF882E"></path>
|
||||
<path d="M47.8467 3.14734C48.4258 3.14734 48.8956 2.67656 48.8956 2.09918C48.8956 1.52181 48.4258 1.05103 47.8467 1.05103C47.2676 1.05103 46.7979 1.52181 46.7979 2.09918C46.7979 2.67656 47.2676 3.14734 47.8467 3.14734Z" fill="#FF882E"></path>
|
||||
<path d="M55.9065 53C54.7276 53 53.729 52.6239 53.0081 52.3515L52.7422 52.2538C51.5367 51.8156 50.3726 51.3774 49.2854 50.951C48.6826 50.7142 48.3842 50.0332 48.6206 49.4291C48.857 48.8251 49.5395 48.529 50.1422 48.7659C51.2117 49.1863 52.3581 49.6157 53.5488 50.048C53.6433 50.0835 53.7408 50.119 53.8383 50.1575C54.6449 50.4625 55.5608 50.8089 56.5654 50.5839C57.4635 50.3825 58.0219 50.0391 58.3144 49.5091C58.5892 49.0117 58.5035 48.6593 58.3144 48.0227C58.1549 47.4897 57.9599 46.8265 58.214 46.107C58.4976 45.3016 59.0738 44.9078 59.4963 44.6206C59.8833 44.3542 59.9631 44.2831 60.0074 44.0581C60.1049 43.5606 59.8272 43.3001 59.7297 43.2261C59.2895 43.0662 58.9763 42.6516 58.9585 42.1661C58.9379 41.5916 59.3338 41.0883 59.8951 40.9728C59.9956 40.9521 60.2999 40.8899 60.5451 40.6412C60.6722 40.5139 60.8908 40.2474 60.9115 39.8891C60.9292 39.5605 60.7549 39.291 60.4683 38.8913C60.1492 38.4501 59.5554 37.627 60.1049 36.7032C60.5392 35.9719 61.4581 35.5899 62.1967 35.282C62.3504 35.2168 62.4892 35.1606 62.5956 35.1103C63.0388 34.8911 63.2338 34.6484 63.1392 33.8696C63.1097 33.6357 63.0004 33.4492 62.9295 33.3485C61.9456 32.0813 61.0297 30.8081 60.1315 29.4579C59.3397 28.2617 58.8079 27.3823 58.601 26.1328C58.3913 24.8804 59.0472 22.5916 59.124 22.334C59.907 19.6692 59.9424 17.641 58.0367 13.321C56.5979 10.064 54.376 7.8345 52.7658 6.53762C52.2606 6.13198 52.1808 5.39176 52.5885 4.88841C52.9963 4.38209 53.7349 4.30215 54.2401 4.71075C56.8401 6.8041 58.8935 9.4541 60.1847 12.3735C62.1435 16.8119 62.4331 19.3938 61.3783 22.9943C61.1006 23.9388 60.8465 25.3008 60.9204 25.7479C61.0415 26.4792 61.3192 26.9915 62.0904 28.161C62.959 29.4668 63.8454 30.6985 64.7997 31.9243C64.8085 31.9362 64.8174 31.9451 64.8233 31.9569C65.0685 32.2944 65.3788 32.8511 65.4704 33.5824C65.7244 35.7084 64.6135 36.7299 63.6385 37.2125C63.4967 37.2835 63.3076 37.3635 63.1008 37.4494C62.9531 37.5115 62.7226 37.6063 62.5129 37.707C62.8645 38.2103 63.3195 38.9742 63.2604 40.0165C63.2131 40.8603 62.8408 41.6716 62.2115 42.2993C62.1613 42.3496 62.1081 42.4 62.052 42.4473C62.3622 43.0721 62.4567 43.7857 62.3179 44.5022C62.0845 45.6954 61.2956 46.2343 60.8258 46.5541C60.6249 46.6903 60.492 46.7851 60.4476 46.8561C60.4565 46.9597 60.5245 47.1818 60.5717 47.3476C60.7845 48.0612 61.139 49.2574 60.3767 50.6372C59.7533 51.7682 58.6454 52.5173 57.0883 52.8667C56.6806 52.9585 56.2876 52.997 55.9124 52.997L55.9065 53Z" fill="#FF882E"></path>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.4 KiB |
21
api/core/tools/provider/builtin/did/did.py
Normal file
@ -0,0 +1,21 @@
|
||||
from core.tools.errors import ToolProviderCredentialValidationError
|
||||
from core.tools.provider.builtin.did.tools.talks import TalksTool
|
||||
from core.tools.provider.builtin_tool_provider import BuiltinToolProviderController
|
||||
|
||||
|
||||
class DIDProvider(BuiltinToolProviderController):
|
||||
def _validate_credentials(self, credentials: dict) -> None:
|
||||
try:
|
||||
# Example validation using the D-ID talks tool
|
||||
TalksTool().fork_tool_runtime(
|
||||
runtime={"credentials": credentials}
|
||||
).invoke(
|
||||
user_id='',
|
||||
tool_parameters={
|
||||
"source_url": "https://www.d-id.com/wp-content/uploads/2023/11/Hero-image-1.png",
|
||||
"text_input": "Hello, welcome to use D-ID tool in Dify",
|
||||
}
|
||||
)
|
||||
except Exception as e:
|
||||
raise ToolProviderCredentialValidationError(str(e))
|
||||
|
||||
28
api/core/tools/provider/builtin/did/did.yaml
Normal file
@ -0,0 +1,28 @@
|
||||
identity:
|
||||
author: Matri Qi
|
||||
name: did
|
||||
label:
|
||||
en_US: D-ID
|
||||
description:
|
||||
en_US: D-ID is a tool enabling the creation of high-quality, custom videos of Digital Humans from a single image.
|
||||
icon: icon.svg
|
||||
tags:
|
||||
- videos
|
||||
credentials_for_provider:
|
||||
did_api_key:
|
||||
type: secret-input
|
||||
required: true
|
||||
label:
|
||||
en_US: D-ID API Key
|
||||
placeholder:
|
||||
en_US: Please input your D-ID API key
|
||||
help:
|
||||
en_US: Get your D-ID API key from your D-ID account settings.
|
||||
url: https://studio.d-id.com/account-settings
|
||||
base_url:
|
||||
type: text-input
|
||||
required: false
|
||||
label:
|
||||
en_US: D-ID server's Base URL
|
||||
placeholder:
|
||||
en_US: https://api.d-id.com
|
||||
87
api/core/tools/provider/builtin/did/did_appx.py
Normal file
@ -0,0 +1,87 @@
|
||||
import logging
|
||||
import time
|
||||
from collections.abc import Mapping
|
||||
from typing import Any
|
||||
|
||||
import requests
|
||||
from requests.exceptions import HTTPError
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class DIDApp:
|
||||
def __init__(self, api_key: str | None = None, base_url: str | None = None):
|
||||
self.api_key = api_key
|
||||
self.base_url = base_url or 'https://api.d-id.com'
|
||||
if not self.api_key:
|
||||
raise ValueError('API key is required')
|
||||
|
||||
def _prepare_headers(self, idempotency_key: str | None = None):
|
||||
headers = {'Content-Type': 'application/json', 'Authorization': f'Basic {self.api_key}'}
|
||||
if idempotency_key:
|
||||
headers['Idempotency-Key'] = idempotency_key
|
||||
return headers
|
||||
|
||||
def _request(
|
||||
self,
|
||||
method: str,
|
||||
url: str,
|
||||
data: Mapping[str, Any] | None = None,
|
||||
headers: Mapping[str, str] | None = None,
|
||||
retries: int = 3,
|
||||
backoff_factor: float = 0.3,
|
||||
) -> Mapping[str, Any] | None:
|
||||
for i in range(retries):
|
||||
try:
|
||||
response = requests.request(method, url, json=data, headers=headers)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
except requests.exceptions.RequestException as e:
|
||||
if i < retries - 1 and isinstance(e, HTTPError) and e.response.status_code >= 500:
|
||||
time.sleep(backoff_factor * (2**i))
|
||||
else:
|
||||
raise
|
||||
return None
|
||||
|
||||
def talks(self, wait: bool = True, poll_interval: int = 5, idempotency_key: str | None = None, **kwargs):
|
||||
endpoint = f'{self.base_url}/talks'
|
||||
headers = self._prepare_headers(idempotency_key)
|
||||
data = kwargs['params']
|
||||
logger.debug(f'Send request to {endpoint=} body={data}')
|
||||
response = self._request('POST', endpoint, data, headers)
|
||||
if response is None:
|
||||
raise HTTPError('Failed to initiate D-ID talks after multiple retries')
|
||||
id: str = response['id']
|
||||
if wait:
|
||||
return self._monitor_job_status(id=id, target='talks', poll_interval=poll_interval)
|
||||
return id
|
||||
|
||||
def animations(self, wait: bool = True, poll_interval: int = 5, idempotency_key: str | None = None, **kwargs):
|
||||
endpoint = f'{self.base_url}/animations'
|
||||
headers = self._prepare_headers(idempotency_key)
|
||||
data = kwargs['params']
|
||||
logger.debug(f'Send request to {endpoint=} body={data}')
|
||||
response = self._request('POST', endpoint, data, headers)
|
||||
if response is None:
|
||||
raise HTTPError('Failed to initiate D-ID talks after multiple retries')
|
||||
id: str = response['id']
|
||||
if wait:
|
||||
return self._monitor_job_status(target='animations', id=id, poll_interval=poll_interval)
|
||||
return id
|
||||
|
||||
def check_did_status(self, target: str, id: str):
|
||||
endpoint = f'{self.base_url}/{target}/{id}'
|
||||
headers = self._prepare_headers()
|
||||
response = self._request('GET', endpoint, headers=headers)
|
||||
if response is None:
|
||||
raise HTTPError(f'Failed to check status for talks {id} after multiple retries')
|
||||
return response
|
||||
|
||||
def _monitor_job_status(self, target: str, id: str, poll_interval: int):
|
||||
while True:
|
||||
status = self.check_did_status(target=target, id=id)
|
||||
if status['status'] == 'done':
|
||||
return status
|
||||
elif status['status'] == 'error' or status['status'] == 'rejected':
|
||||
raise HTTPError(f'Talks {id} failed: {status["status"]} {status.get("error",{}).get("description")}')
|
||||
time.sleep(poll_interval)
|
||||
49
api/core/tools/provider/builtin/did/tools/animations.py
Normal file
@ -0,0 +1,49 @@
|
||||
import json
|
||||
from typing import Any, Union
|
||||
|
||||
from core.tools.entities.tool_entities import ToolInvokeMessage
|
||||
from core.tools.provider.builtin.did.did_appx import DIDApp
|
||||
from core.tools.tool.builtin_tool import BuiltinTool
|
||||
|
||||
|
||||
class AnimationsTool(BuiltinTool):
|
||||
def _invoke(
|
||||
self, user_id: str, tool_parameters: dict[str, Any]
|
||||
) -> Union[ToolInvokeMessage, list[ToolInvokeMessage]]:
|
||||
app = DIDApp(api_key=self.runtime.credentials['did_api_key'], base_url=self.runtime.credentials['base_url'])
|
||||
|
||||
driver_expressions_str = tool_parameters.get('driver_expressions')
|
||||
driver_expressions = json.loads(driver_expressions_str) if driver_expressions_str else None
|
||||
|
||||
config = {
|
||||
'stitch': tool_parameters.get('stitch', True),
|
||||
'mute': tool_parameters.get('mute'),
|
||||
'result_format': tool_parameters.get('result_format') or 'mp4',
|
||||
}
|
||||
config = {k: v for k, v in config.items() if v is not None and v != ''}
|
||||
|
||||
options = {
|
||||
'source_url': tool_parameters['source_url'],
|
||||
'driver_url': tool_parameters.get('driver_url'),
|
||||
'config': config,
|
||||
}
|
||||
options = {k: v for k, v in options.items() if v is not None and v != ''}
|
||||
|
||||
if not options.get('source_url'):
|
||||
raise ValueError('Source URL is required')
|
||||
|
||||
if config.get('logo_url'):
|
||||
if not config.get('logo_x'):
|
||||
raise ValueError('Logo X position is required when logo URL is provided')
|
||||
if not config.get('logo_y'):
|
||||
raise ValueError('Logo Y position is required when logo URL is provided')
|
||||
|
||||
animations_result = app.animations(params=options, wait=True)
|
||||
|
||||
if not isinstance(animations_result, str):
|
||||
animations_result = json.dumps(animations_result, ensure_ascii=False, indent=4)
|
||||
|
||||
if not animations_result:
|
||||
return self.create_text_message('D-ID animations request failed.')
|
||||
|
||||
return self.create_text_message(animations_result)
|
||||
86
api/core/tools/provider/builtin/did/tools/animations.yaml
Normal file
@ -0,0 +1,86 @@
|
||||
identity:
|
||||
name: animations
|
||||
author: Matri Qi
|
||||
label:
|
||||
en_US: Animations
|
||||
description:
|
||||
human:
|
||||
en_US: Animations enables to create videos matching head movements, expressions, emotions, and voice from a driver video and image.
|
||||
llm: Animations enables to create videos matching head movements, expressions, emotions, and voice from a driver video and image.
|
||||
parameters:
|
||||
- name: source_url
|
||||
type: string
|
||||
required: true
|
||||
label:
|
||||
en_US: source url
|
||||
human_description:
|
||||
en_US: The URL of the source image to be animated by the driver video, or a selection from the list of provided studio actors.
|
||||
llm_description: The URL of the source image to be animated by the driver video, or a selection from the list of provided studio actors.
|
||||
form: llm
|
||||
- name: driver_url
|
||||
type: string
|
||||
required: false
|
||||
label:
|
||||
en_US: driver url
|
||||
human_description:
|
||||
en_US: The URL of the driver video to drive the animation, or a provided driver name from D-ID.
|
||||
form: form
|
||||
- name: mute
|
||||
type: boolean
|
||||
required: false
|
||||
label:
|
||||
en_US: mute
|
||||
human_description:
|
||||
en_US: Mutes the driver sound in the animated video result, defaults to true
|
||||
form: form
|
||||
- name: stitch
|
||||
type: boolean
|
||||
required: false
|
||||
label:
|
||||
en_US: stitch
|
||||
human_description:
|
||||
en_US: If enabled, the driver video will be stitched with the animationing head video.
|
||||
form: form
|
||||
- name: logo_url
|
||||
type: string
|
||||
required: false
|
||||
label:
|
||||
en_US: logo url
|
||||
human_description:
|
||||
en_US: The URL of the logo image to be added to the animation video.
|
||||
form: form
|
||||
- name: logo_x
|
||||
type: number
|
||||
required: false
|
||||
label:
|
||||
en_US: logo position x
|
||||
human_description:
|
||||
en_US: The x position of the logo image in the animation video. It's required when logo url is provided.
|
||||
form: form
|
||||
- name: logo_y
|
||||
type: number
|
||||
required: false
|
||||
label:
|
||||
en_US: logo position y
|
||||
human_description:
|
||||
en_US: The y position of the logo image in the animation video. It's required when logo url is provided.
|
||||
form: form
|
||||
- name: result_format
|
||||
type: string
|
||||
default: mp4
|
||||
required: false
|
||||
label:
|
||||
en_US: result format
|
||||
human_description:
|
||||
en_US: The format of the result video.
|
||||
form: form
|
||||
options:
|
||||
- value: mp4
|
||||
label:
|
||||
en_US: mp4
|
||||
- value: gif
|
||||
label:
|
||||
en_US: gif
|
||||
- value: mov
|
||||
label:
|
||||
en_US: mov
|
||||
65
api/core/tools/provider/builtin/did/tools/talks.py
Normal file
@ -0,0 +1,65 @@
|
||||
import json
|
||||
from typing import Any, Union
|
||||
|
||||
from core.tools.entities.tool_entities import ToolInvokeMessage
|
||||
from core.tools.provider.builtin.did.did_appx import DIDApp
|
||||
from core.tools.tool.builtin_tool import BuiltinTool
|
||||
|
||||
|
||||
class TalksTool(BuiltinTool):
|
||||
def _invoke(
|
||||
self, user_id: str, tool_parameters: dict[str, Any]
|
||||
) -> Union[ToolInvokeMessage, list[ToolInvokeMessage]]:
|
||||
app = DIDApp(api_key=self.runtime.credentials['did_api_key'], base_url=self.runtime.credentials['base_url'])
|
||||
|
||||
driver_expressions_str = tool_parameters.get('driver_expressions')
|
||||
driver_expressions = json.loads(driver_expressions_str) if driver_expressions_str else None
|
||||
|
||||
script = {
|
||||
'type': tool_parameters.get('script_type') or 'text',
|
||||
'input': tool_parameters.get('text_input'),
|
||||
'audio_url': tool_parameters.get('audio_url'),
|
||||
'reduce_noise': tool_parameters.get('audio_reduce_noise', False),
|
||||
}
|
||||
script = {k: v for k, v in script.items() if v is not None and v != ''}
|
||||
config = {
|
||||
'stitch': tool_parameters.get('stitch', True),
|
||||
'sharpen': tool_parameters.get('sharpen'),
|
||||
'fluent': tool_parameters.get('fluent'),
|
||||
'result_format': tool_parameters.get('result_format') or 'mp4',
|
||||
'pad_audio': tool_parameters.get('pad_audio'),
|
||||
'driver_expressions': driver_expressions,
|
||||
}
|
||||
config = {k: v for k, v in config.items() if v is not None and v != ''}
|
||||
|
||||
options = {
|
||||
'source_url': tool_parameters['source_url'],
|
||||
'driver_url': tool_parameters.get('driver_url'),
|
||||
'script': script,
|
||||
'config': config,
|
||||
}
|
||||
options = {k: v for k, v in options.items() if v is not None and v != ''}
|
||||
|
||||
if not options.get('source_url'):
|
||||
raise ValueError('Source URL is required')
|
||||
|
||||
if script.get('type') == 'audio':
|
||||
script.pop('input', None)
|
||||
if not script.get('audio_url'):
|
||||
raise ValueError('Audio URL is required for audio script type')
|
||||
|
||||
if script.get('type') == 'text':
|
||||
script.pop('audio_url', None)
|
||||
script.pop('reduce_noise', None)
|
||||
if not script.get('input'):
|
||||
raise ValueError('Text input is required for text script type')
|
||||
|
||||
talks_result = app.talks(params=options, wait=True)
|
||||
|
||||
if not isinstance(talks_result, str):
|
||||
talks_result = json.dumps(talks_result, ensure_ascii=False, indent=4)
|
||||
|
||||
if not talks_result:
|
||||
return self.create_text_message('D-ID talks request failed.')
|
||||
|
||||
return self.create_text_message(talks_result)
|
||||
126
api/core/tools/provider/builtin/did/tools/talks.yaml
Normal file
@ -0,0 +1,126 @@
|
||||
identity:
|
||||
name: talks
|
||||
author: Matri Qi
|
||||
label:
|
||||
en_US: Talks
|
||||
description:
|
||||
human:
|
||||
en_US: Talks enables the creation of realistic talking head videos from text or audio inputs.
|
||||
llm: Talks enables the creation of realistic talking head videos from text or audio inputs.
|
||||
parameters:
|
||||
- name: source_url
|
||||
type: string
|
||||
required: true
|
||||
label:
|
||||
en_US: source url
|
||||
human_description:
|
||||
en_US: The URL of the source image to be animated by the driver video, or a selection from the list of provided studio actors.
|
||||
llm_description: The URL of the source image to be animated by the driver video, or a selection from the list of provided studio actors.
|
||||
form: llm
|
||||
- name: driver_url
|
||||
type: string
|
||||
required: false
|
||||
label:
|
||||
en_US: driver url
|
||||
human_description:
|
||||
en_US: The URL of the driver video to drive the talk, or a provided driver name from D-ID.
|
||||
form: form
|
||||
- name: script_type
|
||||
type: string
|
||||
required: false
|
||||
label:
|
||||
en_US: script type
|
||||
human_description:
|
||||
en_US: The type of the script.
|
||||
form: form
|
||||
options:
|
||||
- value: text
|
||||
label:
|
||||
en_US: text
|
||||
- value: audio
|
||||
label:
|
||||
en_US: audio
|
||||
- name: text_input
|
||||
type: string
|
||||
required: false
|
||||
label:
|
||||
en_US: text input
|
||||
human_description:
|
||||
en_US: The text input to be spoken by the talking head. Required when script type is text.
|
||||
form: form
|
||||
- name: audio_url
|
||||
type: string
|
||||
required: false
|
||||
label:
|
||||
en_US: audio url
|
||||
human_description:
|
||||
en_US: The URL of the audio file to be spoken by the talking head. Required when script type is audio.
|
||||
form: form
|
||||
- name: audio_reduce_noise
|
||||
type: boolean
|
||||
required: false
|
||||
label:
|
||||
en_US: audio reduce noise
|
||||
human_description:
|
||||
en_US: If enabled, the audio will be processed to reduce noise before being spoken by the talking head. It only works when script type is audio.
|
||||
form: form
|
||||
- name: stitch
|
||||
type: boolean
|
||||
required: false
|
||||
label:
|
||||
en_US: stitch
|
||||
human_description:
|
||||
en_US: If enabled, the driver video will be stitched with the talking head video.
|
||||
form: form
|
||||
- name: sharpen
|
||||
type: boolean
|
||||
required: false
|
||||
label:
|
||||
en_US: sharpen
|
||||
human_description:
|
||||
en_US: If enabled, the talking head video will be sharpened.
|
||||
form: form
|
||||
- name: result_format
|
||||
type: string
|
||||
required: false
|
||||
label:
|
||||
en_US: result format
|
||||
human_description:
|
||||
en_US: The format of the result video.
|
||||
form: form
|
||||
options:
|
||||
- value: mp4
|
||||
label:
|
||||
en_US: mp4
|
||||
- value: gif
|
||||
label:
|
||||
en_US: gif
|
||||
- value: mov
|
||||
label:
|
||||
en_US: mov
|
||||
- name: fluent
|
||||
type: boolean
|
||||
required: false
|
||||
label:
|
||||
en_US: fluent
|
||||
human_description:
|
||||
en_US: Interpolate between the last & first frames of the driver video When used together with pad_audio can create a seamless transition between videos of the same driver
|
||||
form: form
|
||||
- name: pad_audio
|
||||
type: number
|
||||
required: false
|
||||
label:
|
||||
en_US: pad audio
|
||||
human_description:
|
||||
en_US: Pad the audio with silence at the end (given in seconds) Will increase the video duration & the credits it consumes
|
||||
form: form
|
||||
min: 1
|
||||
max: 60
|
||||
- name: driver_expressions
|
||||
type: string
|
||||
required: false
|
||||
label:
|
||||
en_US: driver expressions
|
||||
human_description:
|
||||
en_US: timed expressions for animation. It should be an JSON array style string. Take D-ID documentation(https://docs.d-id.com/reference/createtalk) for more information.
|
||||
form: form
|
||||
@ -25,9 +25,9 @@ parameters:
|
||||
type: select
|
||||
required: true
|
||||
options:
|
||||
- value: gpt-3.5
|
||||
- value: gpt-4o-mini
|
||||
label:
|
||||
en_US: GPT-3.5
|
||||
en_US: GPT-4o-mini
|
||||
- value: claude-3-haiku
|
||||
label:
|
||||
en_US: Claude 3
|
||||
|
||||
@ -2,6 +2,7 @@ from typing import Any
|
||||
|
||||
from duckduckgo_search import DDGS
|
||||
|
||||
from core.file.file_obj import FileTransferMethod
|
||||
from core.tools.entities.tool_entities import ToolInvokeMessage
|
||||
from core.tools.tool.builtin_tool import BuiltinTool
|
||||
|
||||
@ -21,6 +22,7 @@ class DuckDuckGoImageSearchTool(BuiltinTool):
|
||||
response = DDGS().images(**query_dict)
|
||||
result = []
|
||||
for res in response:
|
||||
res['transfer_method'] = FileTransferMethod.REMOTE_URL
|
||||
msg = ToolInvokeMessage(type=ToolInvokeMessage.MessageType.IMAGE_LINK,
|
||||
message=res.get('image'),
|
||||
save_as='',
|
||||
|
||||
@ -21,23 +21,16 @@ class DuckDuckGoSearchTool(BuiltinTool):
|
||||
"""
|
||||
Tool for performing a search using DuckDuckGo search engine.
|
||||
"""
|
||||
|
||||
def _invoke(self, user_id: str, tool_parameters: dict[str, Any]) -> ToolInvokeMessage:
|
||||
query = tool_parameters.get('query', '')
|
||||
result_type = tool_parameters.get('result_type', 'text')
|
||||
max_results = tool_parameters.get('max_results', 10)
|
||||
def _invoke(self, user_id: str, tool_parameters: dict[str, Any]) -> ToolInvokeMessage | list[ToolInvokeMessage]:
|
||||
query = tool_parameters.get('query')
|
||||
max_results = tool_parameters.get('max_results', 5)
|
||||
require_summary = tool_parameters.get('require_summary', False)
|
||||
response = DDGS().text(query, max_results=max_results)
|
||||
|
||||
if result_type == 'link':
|
||||
results = [f"[{res.get('title')}]({res.get('href')})" for res in response]
|
||||
results = "\n".join(results)
|
||||
return self.create_link_message(link=results)
|
||||
results = [res.get("body") for res in response]
|
||||
results = "\n".join(results)
|
||||
if require_summary:
|
||||
results = "\n".join([res.get("body") for res in response])
|
||||
results = self.summary_results(user_id=user_id, content=results, query=query)
|
||||
return self.create_text_message(text=results)
|
||||
return self.create_text_message(text=results)
|
||||
return [self.create_json_message(res) for res in response]
|
||||
|
||||
def summary_results(self, user_id: str, content: str, query: str) -> str:
|
||||
prompt = SUMMARY_PROMPT.format(query=query, content=content)
|
||||
|
||||
@ -28,29 +28,6 @@ parameters:
|
||||
label:
|
||||
en_US: Max results
|
||||
zh_Hans: 最大结果数量
|
||||
human_description:
|
||||
en_US: The max results.
|
||||
zh_Hans: 最大结果数量
|
||||
form: form
|
||||
- name: result_type
|
||||
type: select
|
||||
required: true
|
||||
options:
|
||||
- value: text
|
||||
label:
|
||||
en_US: text
|
||||
zh_Hans: 文本
|
||||
- value: link
|
||||
label:
|
||||
en_US: link
|
||||
zh_Hans: 链接
|
||||
default: text
|
||||
label:
|
||||
en_US: Result type
|
||||
zh_Hans: 结果类型
|
||||
human_description:
|
||||
en_US: used for selecting the result type, text or link
|
||||
zh_Hans: 用于选择结果类型,使用文本还是链接进行展示
|
||||
form: form
|
||||
- name: require_summary
|
||||
type: boolean
|
||||
|
||||
@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="64px" height="64px" style="shape-rendering:geometricPrecision; text-rendering:geometricPrecision; image-rendering:optimizeQuality; fill-rule:evenodd; clip-rule:evenodd" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g><path style="opacity:1" fill="#fefefe" d="M -0.5,-0.5 C 20.8333,-0.5 42.1667,-0.5 63.5,-0.5C 63.5,20.8333 63.5,42.1667 63.5,63.5C 42.1667,63.5 20.8333,63.5 -0.5,63.5C -0.5,42.1667 -0.5,20.8333 -0.5,-0.5 Z"/></g>
|
||||
<g><path style="opacity:1" fill="#346df3" d="M 47.5,33.5 C 43.3272,29.8779 38.9939,29.7112 34.5,33C 32.682,35.4897 30.3487,37.3231 27.5,38.5C 23.5003,43.5136 24.167,47.847 29.5,51.5C 24.1563,51.666 18.8229,51.4994 13.5,51C 13,50.5 12.5,50 12,49.5C 11.3333,36.8333 11.3333,24.1667 12,11.5C 12.5,11 13,10.5 13.5,10C 24.1667,9.33333 34.8333,9.33333 45.5,10C 46,10.5 46.5,11 47,11.5C 47.4997,18.8258 47.6663,26.1591 47.5,33.5 Z"/></g>
|
||||
<g><path style="opacity:1" fill="#f9fafe" d="M 20.5,19.5 C 25.1785,19.3342 29.8452,19.5008 34.5,20C 35.8333,21 35.8333,22 34.5,23C 29.8333,23.6667 25.1667,23.6667 20.5,23C 19.3157,21.8545 19.3157,20.6879 20.5,19.5 Z"/></g>
|
||||
<g><path style="opacity:1" fill="#f3f6fe" d="M 20.5,27.5 C 22.5273,27.3379 24.5273,27.5045 26.5,28C 27.8333,29 27.8333,30 26.5,31C 24.5,31.6667 22.5,31.6667 20.5,31C 19.3157,29.8545 19.3157,28.6879 20.5,27.5 Z"/></g>
|
||||
<g><path style="opacity:1" fill="#36d4c1" d="M 47.5,33.5 C 48.7298,35.2972 49.3964,37.2972 49.5,39.5C 51.3904,39.2965 52.8904,39.9632 54,41.5C 55.1825,45.2739 54.3492,48.4406 51.5,51C 44.1742,51.4997 36.8409,51.6663 29.5,51.5C 24.167,47.847 23.5003,43.5136 27.5,38.5C 30.3487,37.3231 32.682,35.4897 34.5,33C 38.9939,29.7112 43.3272,29.8779 47.5,33.5 Z"/></g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
@ -0,0 +1,15 @@
|
||||
from core.tools.errors import ToolProviderCredentialValidationError
|
||||
from core.tools.provider.builtin_tool_provider import BuiltinToolProviderController
|
||||
from core.tools.utils.feishu_api_utils import FeishuRequest
|
||||
|
||||
|
||||
class FeishuDocumentProvider(BuiltinToolProviderController):
|
||||
def _validate_credentials(self, credentials: dict) -> None:
|
||||
app_id = credentials.get('app_id')
|
||||
app_secret = credentials.get('app_secret')
|
||||
if not app_id or not app_secret:
|
||||
raise ToolProviderCredentialValidationError("app_id and app_secret is required")
|
||||
try:
|
||||
assert FeishuRequest(app_id, app_secret).tenant_access_token is not None
|
||||
except Exception as e:
|
||||
raise ToolProviderCredentialValidationError(str(e))
|
||||
@ -0,0 +1,34 @@
|
||||
identity:
|
||||
author: Doug Lea
|
||||
name: feishu_document
|
||||
label:
|
||||
en_US: Lark Cloud Document
|
||||
zh_Hans: 飞书云文档
|
||||
description:
|
||||
en_US: Lark Cloud Document
|
||||
zh_Hans: 飞书云文档
|
||||
icon: icon.svg
|
||||
tags:
|
||||
- social
|
||||
- productivity
|
||||
credentials_for_provider:
|
||||
app_id:
|
||||
type: text-input
|
||||
required: true
|
||||
label:
|
||||
en_US: APP ID
|
||||
placeholder:
|
||||
en_US: Please input your feishu app id
|
||||
zh_Hans: 请输入你的飞书 app id
|
||||
help:
|
||||
en_US: Get your app_id and app_secret from Feishu
|
||||
zh_Hans: 从飞书获取您的 app_id 和 app_secret
|
||||
url: https://open.feishu.cn
|
||||
app_secret:
|
||||
type: secret-input
|
||||
required: true
|
||||
label:
|
||||
en_US: APP Secret
|
||||
placeholder:
|
||||
en_US: Please input your app secret
|
||||
zh_Hans: 请输入你的飞书 app secret
|
||||
@ -0,0 +1,19 @@
|
||||
from typing import Any
|
||||
|
||||
from core.tools.entities.tool_entities import ToolInvokeMessage
|
||||
from core.tools.tool.builtin_tool import BuiltinTool
|
||||
from core.tools.utils.feishu_api_utils import FeishuRequest
|
||||
|
||||
|
||||
class CreateDocumentTool(BuiltinTool):
|
||||
def _invoke(self, user_id: str, tool_parameters: dict[str, Any]) -> ToolInvokeMessage:
|
||||
app_id = self.runtime.credentials.get('app_id')
|
||||
app_secret = self.runtime.credentials.get('app_secret')
|
||||
client = FeishuRequest(app_id, app_secret)
|
||||
|
||||
title = tool_parameters.get('title')
|
||||
content = tool_parameters.get('content')
|
||||
folder_token = tool_parameters.get('folder_token')
|
||||
|
||||
res = client.create_document(title, content, folder_token)
|
||||
return self.create_json_message(res)
|
||||
@ -0,0 +1,47 @@
|
||||
identity:
|
||||
name: create_document
|
||||
author: Doug Lea
|
||||
label:
|
||||
en_US: Create Lark document
|
||||
zh_Hans: 创建飞书文档
|
||||
description:
|
||||
human:
|
||||
en_US: Create Lark document
|
||||
zh_Hans: 创建飞书文档,支持创建空文档和带内容的文档,支持 markdown 语法创建。
|
||||
llm: A tool for creating Feishu documents.
|
||||
parameters:
|
||||
- name: title
|
||||
type: string
|
||||
required: false
|
||||
label:
|
||||
en_US: Document title
|
||||
zh_Hans: 文档标题
|
||||
human_description:
|
||||
en_US: Document title, only supports plain text content.
|
||||
zh_Hans: 文档标题,只支持纯文本内容。
|
||||
llm_description: 文档标题,只支持纯文本内容,可以为空。
|
||||
form: llm
|
||||
|
||||
- name: content
|
||||
type: string
|
||||
required: false
|
||||
label:
|
||||
en_US: Document content
|
||||
zh_Hans: 文档内容
|
||||
human_description:
|
||||
en_US: Document content, supports markdown syntax, can be empty.
|
||||
zh_Hans: 文档内容,支持 markdown 语法,可以为空。
|
||||
llm_description: 文档内容,支持 markdown 语法,可以为空。
|
||||
form: llm
|
||||
|
||||
- name: folder_token
|
||||
type: string
|
||||
required: false
|
||||
label:
|
||||
en_US: folder_token
|
||||
zh_Hans: 文档所在文件夹的 Token
|
||||
human_description:
|
||||
en_US: The token of the folder where the document is located. If it is not passed or is empty, it means the root directory.
|
||||
zh_Hans: 文档所在文件夹的 Token,不传或传空表示根目录。
|
||||
llm_description: 文档所在文件夹的 Token,不传或传空表示根目录。
|
||||
form: llm
|
||||
@ -0,0 +1,17 @@
|
||||
from typing import Any
|
||||
|
||||
from core.tools.entities.tool_entities import ToolInvokeMessage
|
||||
from core.tools.tool.builtin_tool import BuiltinTool
|
||||
from core.tools.utils.feishu_api_utils import FeishuRequest
|
||||
|
||||
|
||||
class GetDocumentRawContentTool(BuiltinTool):
|
||||
def _invoke(self, user_id: str, tool_parameters: dict[str, Any]) -> ToolInvokeMessage:
|
||||
app_id = self.runtime.credentials.get('app_id')
|
||||
app_secret = self.runtime.credentials.get('app_secret')
|
||||
client = FeishuRequest(app_id, app_secret)
|
||||
|
||||
document_id = tool_parameters.get('document_id')
|
||||
|
||||
res = client.get_document_raw_content(document_id)
|
||||
return self.create_json_message(res)
|
||||
@ -0,0 +1,23 @@
|
||||
identity:
|
||||
name: get_document_raw_content
|
||||
author: Doug Lea
|
||||
label:
|
||||
en_US: Get Document Raw Content
|
||||
zh_Hans: 获取文档纯文本内容
|
||||
description:
|
||||
human:
|
||||
en_US: Get document raw content
|
||||
zh_Hans: 获取文档纯文本内容
|
||||
llm: A tool for getting the plain text content of Feishu documents
|
||||
parameters:
|
||||
- name: document_id
|
||||
type: string
|
||||
required: true
|
||||
label:
|
||||
en_US: document_id
|
||||
zh_Hans: 飞书文档的唯一标识
|
||||
human_description:
|
||||
en_US: Unique ID of Feishu document document_id
|
||||
zh_Hans: 飞书文档的唯一标识 document_id
|
||||
llm_description: 飞书文档的唯一标识 document_id
|
||||
form: llm
|
||||
@ -0,0 +1,19 @@
|
||||
from typing import Any
|
||||
|
||||
from core.tools.entities.tool_entities import ToolInvokeMessage
|
||||
from core.tools.tool.builtin_tool import BuiltinTool
|
||||
from core.tools.utils.feishu_api_utils import FeishuRequest
|
||||
|
||||
|
||||
class ListDocumentBlockTool(BuiltinTool):
|
||||
def _invoke(self, user_id: str, tool_parameters: dict[str, Any]) -> ToolInvokeMessage:
|
||||
app_id = self.runtime.credentials.get('app_id')
|
||||
app_secret = self.runtime.credentials.get('app_secret')
|
||||
client = FeishuRequest(app_id, app_secret)
|
||||
|
||||
document_id = tool_parameters.get('document_id')
|
||||
page_size = tool_parameters.get('page_size', 500)
|
||||
page_token = tool_parameters.get('page_token', '')
|
||||
|
||||
res = client.list_document_block(document_id, page_token, page_size)
|
||||
return self.create_json_message(res)
|
||||
@ -0,0 +1,48 @@
|
||||
identity:
|
||||
name: list_document_block
|
||||
author: Doug Lea
|
||||
label:
|
||||
en_US: List Document Block
|
||||
zh_Hans: 获取飞书文档所有块
|
||||
description:
|
||||
human:
|
||||
en_US: List document block
|
||||
zh_Hans: 获取飞书文档所有块的富文本内容并分页返回。
|
||||
llm: A tool to get all blocks of Feishu documents
|
||||
parameters:
|
||||
- name: document_id
|
||||
type: string
|
||||
required: true
|
||||
label:
|
||||
en_US: document_id
|
||||
zh_Hans: 飞书文档的唯一标识
|
||||
human_description:
|
||||
en_US: Unique ID of Feishu document document_id
|
||||
zh_Hans: 飞书文档的唯一标识 document_id
|
||||
llm_description: 飞书文档的唯一标识 document_id
|
||||
form: llm
|
||||
|
||||
- name: page_size
|
||||
type: number
|
||||
required: false
|
||||
default: 500
|
||||
label:
|
||||
en_US: page_size
|
||||
zh_Hans: 分页大小
|
||||
human_description:
|
||||
en_US: Paging size, the default and maximum value is 500.
|
||||
zh_Hans: 分页大小, 默认值和最大值为 500。
|
||||
llm_description: 分页大小, 表示一次请求最多返回多少条数据,默认值和最大值为 500。
|
||||
form: llm
|
||||
|
||||
- name: page_token
|
||||
type: string
|
||||
required: false
|
||||
label:
|
||||
en_US: page_token
|
||||
zh_Hans: 分页标记
|
||||
human_description:
|
||||
en_US: Pagination tag, used to paginate query results so that more items can be obtained in the next traversal.
|
||||
zh_Hans: 分页标记,用于分页查询结果,以便下次遍历时获取更多项。
|
||||
llm_description: 分页标记,第一次请求不填,表示从头开始遍历;分页查询结果还有更多项时会同时返回新的 page_token,下次遍历可采用该 page_token 获取查询结果。
|
||||
form: llm
|
||||
@ -0,0 +1,19 @@
|
||||
from typing import Any
|
||||
|
||||
from core.tools.entities.tool_entities import ToolInvokeMessage
|
||||
from core.tools.tool.builtin_tool import BuiltinTool
|
||||
from core.tools.utils.feishu_api_utils import FeishuRequest
|
||||
|
||||
|
||||
class CreateDocumentTool(BuiltinTool):
|
||||
def _invoke(self, user_id: str, tool_parameters: dict[str, Any]) -> ToolInvokeMessage:
|
||||
app_id = self.runtime.credentials.get('app_id')
|
||||
app_secret = self.runtime.credentials.get('app_secret')
|
||||
client = FeishuRequest(app_id, app_secret)
|
||||
|
||||
document_id = tool_parameters.get('document_id')
|
||||
content = tool_parameters.get('content')
|
||||
position = tool_parameters.get('position')
|
||||
|
||||
res = client.write_document(document_id, content, position)
|
||||
return self.create_json_message(res)
|
||||
@ -0,0 +1,56 @@
|
||||
identity:
|
||||
name: write_document
|
||||
author: Doug Lea
|
||||
label:
|
||||
en_US: Write Document
|
||||
zh_Hans: 在飞书文档中新增内容
|
||||
description:
|
||||
human:
|
||||
en_US: Adding new content to Lark documents
|
||||
zh_Hans: 在飞书文档中新增内容
|
||||
llm: A tool for adding new content to Lark documents.
|
||||
parameters:
|
||||
- name: document_id
|
||||
type: string
|
||||
required: true
|
||||
label:
|
||||
en_US: document_id
|
||||
zh_Hans: 飞书文档的唯一标识
|
||||
human_description:
|
||||
en_US: Unique ID of Feishu document document_id
|
||||
zh_Hans: 飞书文档的唯一标识 document_id
|
||||
llm_description: 飞书文档的唯一标识 document_id
|
||||
form: llm
|
||||
|
||||
- name: content
|
||||
type: string
|
||||
required: true
|
||||
label:
|
||||
en_US: document content
|
||||
zh_Hans: 文档内容
|
||||
human_description:
|
||||
en_US: Document content, supports markdown syntax, can be empty.
|
||||
zh_Hans: 文档内容,支持 markdown 语法,可以为空。
|
||||
llm_description:
|
||||
form: llm
|
||||
|
||||
- name: position
|
||||
type: select
|
||||
required: true
|
||||
default: start
|
||||
label:
|
||||
en_US: Choose where to add content
|
||||
zh_Hans: 选择添加内容的位置
|
||||
human_description:
|
||||
en_US: Please fill in start or end to add content at the beginning or end of the document respectively.
|
||||
zh_Hans: 请填入 start 或 end, 分别表示在文档开头(start)或结尾(end)添加内容。
|
||||
form: llm
|
||||
options:
|
||||
- value: start
|
||||
label:
|
||||
en_US: start
|
||||
zh_Hans: 在文档开头添加内容
|
||||
- value: end
|
||||
label:
|
||||
en_US: end
|
||||
zh_Hans: 在文档结尾添加内容
|
||||
@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="64px" height="64px" viewBox="0 0 64 64" enable-background="new 0 0 64 64" xml:space="preserve"> <image id="image0" width="64" height="64" x="0" y="0"
|
||||
xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAMAAACdt4HsAAAAIGNIUk0AAHomAACAhAAA+gAAAIDo
|
||||
AAB1MAAA6mAAADqYAAAXcJy6UTwAAAC9UExURf///////+bs/vL2/qa/+n+j+E1/9TNt9FmI9nOa
|
||||
+Obt/sza/GaR97PI+9nk/aa/+5m2+oCk+Iyt+Yys+eXt/oCj+L/R+4yt+HOb+Ex/9TOA6jOi2jO8
|
||||
zTPJxzPWwDOa3eb69zN67X/l2DOb3TPPw0DZxLPv55nq4LPw6DOB6vL9+0B29TOo16bt4zPCynPj
|
||||
00zbyDN08WbgzzOH50DYxFmI9bLI+5nr34zn3OX699n384zo21ndyzTWwJnq37nAcdIAAAABdFJO
|
||||
U/4a4wd9AAAAAWJLR0QAiAUdSAAAAAlwSFlzAAAWJQAAFiUBSVIk8AAAAAd0SU1FB+gHEggfEk4D
|
||||
XiUAAAFOSURBVFjD7dVZU8IwFAXgpq2NtFFRUVTKtYC4gCvu6///WcCMI9Cc3CR2fLLn/XyT3KRp
|
||||
IComqIEa+GMgDMNfA1G8lsh51htx6g9kSi5HbfgBm6v1eZLUA9iSKE1nYFviqMgNMPVn44xcgB1p
|
||||
jnIAmpLLrhVoST6ZDdizAMoCZNKWjAdsC8BLWACRtS9lygH7DkDMAW0H4IADlANwyAEJUzzq5F2i
|
||||
bn5cMIC53svpJ/3CHxic0FKGp75Ah0o585uB1ic69zmFnt6nYQEBfA9yAFDf/SZeEMwIfgtjAFxi
|
||||
4AoBcA/XGLiBAHoPcJ9uISAaWv/OABAGWuOKgIgrbgHM0TDEiQnQHnavY0Tfwz0GCgMA/kweVxm/
|
||||
y2gJD4UJQJd5wE6gfIxlIXlsPz1rwIsRwNGFkR8gXicVASHe3j++u5+zfHlugU8N1MD/AQI2U2Cm
|
||||
Yux2lsz2AAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDI0LTA3LTE4VDA4OjMxOjE4KzAwOjAwPdC6HgAA
|
||||
ACV0RVh0ZGF0ZTptb2RpZnkAMjAyNC0wNy0xOFQwODozMToxOCswMDowMEyNAqIAAAAodEVYdGRh
|
||||
dGU6dGltZXN0YW1wADIwMjQtMDctMThUMDg6MzE6MTgrMDA6MDAbmCN9AAAAAElFTkSuQmCC" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
@ -0,0 +1,15 @@
|
||||
from core.tools.errors import ToolProviderCredentialValidationError
|
||||
from core.tools.provider.builtin_tool_provider import BuiltinToolProviderController
|
||||
from core.tools.utils.feishu_api_utils import FeishuRequest
|
||||
|
||||
|
||||
class FeishuMessageProvider(BuiltinToolProviderController):
|
||||
def _validate_credentials(self, credentials: dict) -> None:
|
||||
app_id = credentials.get('app_id')
|
||||
app_secret = credentials.get('app_secret')
|
||||
if not app_id or not app_secret:
|
||||
raise ToolProviderCredentialValidationError("app_id and app_secret is required")
|
||||
try:
|
||||
assert FeishuRequest(app_id, app_secret).tenant_access_token is not None
|
||||
except Exception as e:
|
||||
raise ToolProviderCredentialValidationError(str(e))
|
||||
@ -0,0 +1,34 @@
|
||||
identity:
|
||||
author: Doug Lea
|
||||
name: feishu_message
|
||||
label:
|
||||
en_US: Lark Message
|
||||
zh_Hans: 飞书消息
|
||||
description:
|
||||
en_US: Lark Message
|
||||
zh_Hans: 飞书消息
|
||||
icon: icon.svg
|
||||
tags:
|
||||
- social
|
||||
- productivity
|
||||
credentials_for_provider:
|
||||
app_id:
|
||||
type: text-input
|
||||
required: true
|
||||
label:
|
||||
en_US: APP ID
|
||||
placeholder:
|
||||
en_US: Please input your feishu app id
|
||||
zh_Hans: 请输入你的飞书 app id
|
||||
help:
|
||||
en_US: Get your app_id and app_secret from Feishu
|
||||
zh_Hans: 从飞书获取您的 app_id 和 app_secret
|
||||
url: https://open.feishu.cn
|
||||
app_secret:
|
||||
type: secret-input
|
||||
required: true
|
||||
label:
|
||||
en_US: APP Secret
|
||||
placeholder:
|
||||
en_US: Please input your app secret
|
||||
zh_Hans: 请输入你的飞书 app secret
|
||||
@ -0,0 +1,20 @@
|
||||
from typing import Any
|
||||
|
||||
from core.tools.entities.tool_entities import ToolInvokeMessage
|
||||
from core.tools.tool.builtin_tool import BuiltinTool
|
||||
from core.tools.utils.feishu_api_utils import FeishuRequest
|
||||
|
||||
|
||||
class SendBotMessageTool(BuiltinTool):
|
||||
def _invoke(self, user_id: str, tool_parameters: dict[str, Any]) -> ToolInvokeMessage:
|
||||
app_id = self.runtime.credentials.get('app_id')
|
||||
app_secret = self.runtime.credentials.get('app_secret')
|
||||
client = FeishuRequest(app_id, app_secret)
|
||||
|
||||
receive_id_type = tool_parameters.get('receive_id_type')
|
||||
receive_id = tool_parameters.get('receive_id')
|
||||
msg_type = tool_parameters.get('msg_type')
|
||||
content = tool_parameters.get('content')
|
||||
|
||||
res = client.send_bot_message(receive_id_type, receive_id, msg_type, content)
|
||||
return self.create_json_message(res)
|
||||
@ -0,0 +1,91 @@
|
||||
identity:
|
||||
name: send_bot_message
|
||||
author: Doug Lea
|
||||
label:
|
||||
en_US: Send Bot Message
|
||||
zh_Hans: 发送飞书应用消息
|
||||
description:
|
||||
human:
|
||||
en_US: Send bot message
|
||||
zh_Hans: 发送飞书应用消息
|
||||
llm: A tool for sending Feishu application messages.
|
||||
parameters:
|
||||
- name: receive_id_type
|
||||
type: select
|
||||
required: true
|
||||
options:
|
||||
- value: open_id
|
||||
label:
|
||||
en_US: open id
|
||||
zh_Hans: open id
|
||||
- value: union_id
|
||||
label:
|
||||
en_US: union id
|
||||
zh_Hans: union id
|
||||
- value: user_id
|
||||
label:
|
||||
en_US: user id
|
||||
zh_Hans: user id
|
||||
- value: email
|
||||
label:
|
||||
en_US: email
|
||||
zh_Hans: email
|
||||
- value: chat_id
|
||||
label:
|
||||
en_US: chat id
|
||||
zh_Hans: chat id
|
||||
label:
|
||||
en_US: User ID Type
|
||||
zh_Hans: 用户 ID 类型
|
||||
human_description:
|
||||
en_US: User ID Type
|
||||
zh_Hans: 用户 ID 类型,可选值有 open_id、union_id、user_id、email、chat_id。
|
||||
llm_description: 用户 ID 类型,可选值有 open_id、union_id、user_id、email、chat_id。
|
||||
form: llm
|
||||
|
||||
- name: receive_id
|
||||
type: string
|
||||
required: true
|
||||
label:
|
||||
en_US: Receive Id
|
||||
zh_Hans: 消息接收者的 ID
|
||||
human_description:
|
||||
en_US: The ID of the message receiver. The ID type should correspond to the query parameter receive_id_type.
|
||||
zh_Hans: 消息接收者的 ID,ID 类型应与查询参数 receive_id_type 对应。
|
||||
llm_description: 消息接收者的 ID,ID 类型应与查询参数 receive_id_type 对应。
|
||||
form: llm
|
||||
|
||||
- name: msg_type
|
||||
type: string
|
||||
required: true
|
||||
options:
|
||||
- value: text
|
||||
label:
|
||||
en_US: text
|
||||
zh_Hans: 文本
|
||||
- value: interactive
|
||||
label:
|
||||
en_US: message card
|
||||
zh_Hans: 消息卡片
|
||||
label:
|
||||
en_US: Message type
|
||||
zh_Hans: 消息类型
|
||||
human_description:
|
||||
en_US: Message type, optional values are, text (text), interactive (message card).
|
||||
zh_Hans: 消息类型,可选值有:text(文本)、interactive(消息卡片)。
|
||||
llm_description: 消息类型,可选值有:text(文本)、interactive(消息卡片)。
|
||||
form: llm
|
||||
|
||||
- name: content
|
||||
type: string
|
||||
required: true
|
||||
label:
|
||||
en_US: Message content
|
||||
zh_Hans: 消息内容
|
||||
human_description:
|
||||
en_US: Message content
|
||||
zh_Hans: |
|
||||
消息内容,JSON 结构序列化后的字符串。不同 msg_type 对应不同内容,
|
||||
具体格式说明参考:https://open.larkoffice.com/document/server-docs/im-v1/message-content-description/create_json
|
||||
llm_description: 消息内容,JSON 结构序列化后的字符串。不同 msg_type 对应不同内容。
|
||||
form: llm
|
||||
@ -0,0 +1,19 @@
|
||||
from typing import Any
|
||||
|
||||
from core.tools.entities.tool_entities import ToolInvokeMessage
|
||||
from core.tools.tool.builtin_tool import BuiltinTool
|
||||
from core.tools.utils.feishu_api_utils import FeishuRequest
|
||||
|
||||
|
||||
class SendWebhookMessageTool(BuiltinTool):
|
||||
def _invoke(self, user_id: str, tool_parameters: dict[str, Any]) ->ToolInvokeMessage:
|
||||
app_id = self.runtime.credentials.get('app_id')
|
||||
app_secret = self.runtime.credentials.get('app_secret')
|
||||
client = FeishuRequest(app_id, app_secret)
|
||||
|
||||
webhook = tool_parameters.get('webhook')
|
||||
msg_type = tool_parameters.get('msg_type')
|
||||
content = tool_parameters.get('content')
|
||||
|
||||
res = client.send_webhook_message(webhook, msg_type, content)
|
||||
return self.create_json_message(res)
|
||||
@ -0,0 +1,58 @@
|
||||
identity:
|
||||
name: send_webhook_message
|
||||
author: Doug Lea
|
||||
label:
|
||||
en_US: Send Webhook Message
|
||||
zh_Hans: 使用自定义机器人发送飞书消息
|
||||
description:
|
||||
human:
|
||||
en_US: Send webhook message
|
||||
zh_Hans: 使用自定义机器人发送飞书消息
|
||||
llm: A tool for sending Lark messages using a custom robot.
|
||||
parameters:
|
||||
- name: webhook
|
||||
type: string
|
||||
required: true
|
||||
label:
|
||||
en_US: webhook
|
||||
zh_Hans: webhook 的地址
|
||||
human_description:
|
||||
en_US: The address of the webhook
|
||||
zh_Hans: webhook 的地址
|
||||
llm_description: webhook 的地址
|
||||
form: llm
|
||||
|
||||
- name: msg_type
|
||||
type: string
|
||||
required: true
|
||||
options:
|
||||
- value: text
|
||||
label:
|
||||
en_US: text
|
||||
zh_Hans: 文本
|
||||
- value: interactive
|
||||
label:
|
||||
en_US: message card
|
||||
zh_Hans: 消息卡片
|
||||
label:
|
||||
en_US: Message type
|
||||
zh_Hans: 消息类型
|
||||
human_description:
|
||||
en_US: Message type, optional values are, text (text), interactive (message card).
|
||||
zh_Hans: 消息类型,可选值有:text(文本)、interactive(消息卡片)。
|
||||
llm_description: 消息类型,可选值有:text(文本)、interactive(消息卡片)。
|
||||
form: llm
|
||||
|
||||
- name: content
|
||||
type: string
|
||||
required: true
|
||||
label:
|
||||
en_US: Message content
|
||||
zh_Hans: 消息内容
|
||||
human_description:
|
||||
en_US: Message content
|
||||
zh_Hans: |
|
||||
消息内容,JSON 结构序列化后的字符串。不同 msg_type 对应不同内容,
|
||||
具体格式说明参考:https://open.larkoffice.com/document/server-docs/im-v1/message-content-description/create_json
|
||||
llm_description: 消息内容,JSON 结构序列化后的字符串。不同 msg_type 对应不同内容。
|
||||
form: llm
|
||||
@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg width="24" height="25" viewBox="0 0 24 25" xmlns="http://www.w3.org/2000/svg" fill="none"><path fill="#FC6D26" d="M14.975 8.904L14.19 6.55l-1.552-4.67a.268.268 0 00-.255-.18.268.268 0 00-.254.18l-1.552 4.667H5.422L3.87 1.879a.267.267 0 00-.254-.179.267.267 0 00-.254.18l-1.55 4.667-.784 2.357a.515.515 0 00.193.583l6.78 4.812 6.778-4.812a.516.516 0 00.196-.583z"/><path fill="#E24329" d="M8 14.296l2.578-7.75H5.423L8 14.296z"/><path fill="#FC6D26" d="M8 14.296l-2.579-7.75H1.813L8 14.296z"/><path fill="#FCA326" d="M1.81 6.549l-.784 2.354a.515.515 0 00.193.583L8 14.3 1.81 6.55z"/><path fill="#E24329" d="M1.812 6.549h3.612L3.87 1.882a.268.268 0 00-.254-.18.268.268 0 00-.255.18L1.812 6.549z"/><path fill="#FC6D26" d="M8 14.296l2.578-7.75h3.614L8 14.296z"/><path fill="#FCA326" d="M14.19 6.549l.783 2.354a.514.514 0 01-.193.583L8 14.296l6.188-7.747h.001z"/><path fill="#E24329" d="M14.19 6.549H10.58l1.551-4.667a.267.267 0 01.255-.18c.115 0 .217.073.254.18l1.552 4.667z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
34
api/core/tools/provider/builtin/gitlab/gitlab.py
Normal file
@ -0,0 +1,34 @@
|
||||
from typing import Any
|
||||
|
||||
import requests
|
||||
|
||||
from core.tools.errors import ToolProviderCredentialValidationError
|
||||
from core.tools.provider.builtin_tool_provider import BuiltinToolProviderController
|
||||
|
||||
|
||||
class GitlabProvider(BuiltinToolProviderController):
|
||||
def _validate_credentials(self, credentials: dict[str, Any]) -> None:
|
||||
try:
|
||||
if 'access_tokens' not in credentials or not credentials.get('access_tokens'):
|
||||
raise ToolProviderCredentialValidationError("Gitlab Access Tokens is required.")
|
||||
|
||||
if 'site_url' not in credentials or not credentials.get('site_url'):
|
||||
site_url = 'https://gitlab.com'
|
||||
else:
|
||||
site_url = credentials.get('site_url')
|
||||
|
||||
try:
|
||||
headers = {
|
||||
"Content-Type": "application/vnd.text+json",
|
||||
"Authorization": f"Bearer {credentials.get('access_tokens')}",
|
||||
}
|
||||
|
||||
response = requests.get(
|
||||
url= f"{site_url}/api/v4/user",
|
||||
headers=headers)
|
||||
if response.status_code != 200:
|
||||
raise ToolProviderCredentialValidationError((response.json()).get('message'))
|
||||
except Exception as e:
|
||||
raise ToolProviderCredentialValidationError("Gitlab Access Tokens is invalid. {}".format(e))
|
||||
except Exception as e:
|
||||
raise ToolProviderCredentialValidationError(str(e))
|
||||
38
api/core/tools/provider/builtin/gitlab/gitlab.yaml
Normal file
@ -0,0 +1,38 @@
|
||||
identity:
|
||||
author: Leo.Wang
|
||||
name: gitlab
|
||||
label:
|
||||
en_US: GitLab
|
||||
zh_Hans: GitLab
|
||||
description:
|
||||
en_US: GitLab plugin, API v4 only.
|
||||
zh_Hans: 用于获取GitLab内容的插件,目前仅支持 API v4。
|
||||
icon: gitlab.svg
|
||||
credentials_for_provider:
|
||||
access_tokens:
|
||||
type: secret-input
|
||||
required: true
|
||||
label:
|
||||
en_US: GitLab access token
|
||||
zh_Hans: GitLab access token
|
||||
placeholder:
|
||||
en_US: Please input your GitLab access token
|
||||
zh_Hans: 请输入你的 GitLab access token
|
||||
help:
|
||||
en_US: Get your GitLab access token from GitLab
|
||||
zh_Hans: 从 GitLab 获取您的 access token
|
||||
url: https://docs.gitlab.com/16.9/ee/api/oauth2.html
|
||||
site_url:
|
||||
type: text-input
|
||||
required: false
|
||||
default: 'https://gitlab.com'
|
||||
label:
|
||||
en_US: GitLab site url
|
||||
zh_Hans: GitLab site url
|
||||
placeholder:
|
||||
en_US: Please input your GitLab site url
|
||||
zh_Hans: 请输入你的 GitLab site url
|
||||
help:
|
||||
en_US: Find your GitLab url
|
||||
zh_Hans: 找到你的 GitLab url
|
||||
url: https://gitlab.com/help
|
||||
111
api/core/tools/provider/builtin/gitlab/tools/gitlab_commits.py
Normal file
@ -0,0 +1,111 @@
|
||||
import json
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Any, Union
|
||||
|
||||
import requests
|
||||
|
||||
from core.tools.entities.tool_entities import ToolInvokeMessage
|
||||
from core.tools.tool.builtin_tool import BuiltinTool
|
||||
|
||||
|
||||
class GitlabCommitsTool(BuiltinTool):
|
||||
def _invoke(self,
|
||||
user_id: str,
|
||||
tool_parameters: dict[str, Any]
|
||||
) -> Union[ToolInvokeMessage, list[ToolInvokeMessage]]:
|
||||
|
||||
project = tool_parameters.get('project', '')
|
||||
employee = tool_parameters.get('employee', '')
|
||||
start_time = tool_parameters.get('start_time', '')
|
||||
end_time = tool_parameters.get('end_time', '')
|
||||
change_type = tool_parameters.get('change_type', 'all')
|
||||
|
||||
if not project:
|
||||
return self.create_text_message('Project is required')
|
||||
|
||||
if not start_time:
|
||||
start_time = (datetime.utcnow() - timedelta(days=1)).isoformat()
|
||||
if not end_time:
|
||||
end_time = datetime.utcnow().isoformat()
|
||||
|
||||
access_token = self.runtime.credentials.get('access_tokens')
|
||||
site_url = self.runtime.credentials.get('site_url')
|
||||
|
||||
if 'access_tokens' not in self.runtime.credentials or not self.runtime.credentials.get('access_tokens'):
|
||||
return self.create_text_message("Gitlab API Access Tokens is required.")
|
||||
if 'site_url' not in self.runtime.credentials or not self.runtime.credentials.get('site_url'):
|
||||
site_url = 'https://gitlab.com'
|
||||
|
||||
# Get commit content
|
||||
result = self.fetch(user_id, site_url, access_token, project, employee, start_time, end_time, change_type)
|
||||
|
||||
return [self.create_json_message(item) for item in result]
|
||||
|
||||
def fetch(self,user_id: str, site_url: str, access_token: str, project: str, employee: str = None, start_time: str = '', end_time: str = '', change_type: str = '') -> list[dict[str, Any]]:
|
||||
domain = site_url
|
||||
headers = {"PRIVATE-TOKEN": access_token}
|
||||
results = []
|
||||
|
||||
try:
|
||||
# Get all of projects
|
||||
url = f"{domain}/api/v4/projects"
|
||||
response = requests.get(url, headers=headers)
|
||||
response.raise_for_status()
|
||||
projects = response.json()
|
||||
|
||||
filtered_projects = [p for p in projects if project == "*" or p['name'] == project]
|
||||
|
||||
for project in filtered_projects:
|
||||
project_id = project['id']
|
||||
project_name = project['name']
|
||||
print(f"Project: {project_name}")
|
||||
|
||||
# Get all of proejct commits
|
||||
commits_url = f"{domain}/api/v4/projects/{project_id}/repository/commits"
|
||||
params = {
|
||||
'since': start_time,
|
||||
'until': end_time
|
||||
}
|
||||
if employee:
|
||||
params['author'] = employee
|
||||
|
||||
commits_response = requests.get(commits_url, headers=headers, params=params)
|
||||
commits_response.raise_for_status()
|
||||
commits = commits_response.json()
|
||||
|
||||
for commit in commits:
|
||||
commit_sha = commit['id']
|
||||
author_name = commit['author_name']
|
||||
|
||||
diff_url = f"{domain}/api/v4/projects/{project_id}/repository/commits/{commit_sha}/diff"
|
||||
diff_response = requests.get(diff_url, headers=headers)
|
||||
diff_response.raise_for_status()
|
||||
diffs = diff_response.json()
|
||||
|
||||
for diff in diffs:
|
||||
# Caculate code lines of changed
|
||||
added_lines = diff['diff'].count('\n+')
|
||||
removed_lines = diff['diff'].count('\n-')
|
||||
total_changes = added_lines + removed_lines
|
||||
|
||||
if change_type == "new":
|
||||
if added_lines > 1:
|
||||
final_code = ''.join([line[1:] for line in diff['diff'].split('\n') if line.startswith('+') and not line.startswith('+++')])
|
||||
results.append({
|
||||
"commit_sha": commit_sha,
|
||||
"author_name": author_name,
|
||||
"diff": final_code
|
||||
})
|
||||
else:
|
||||
if total_changes > 1:
|
||||
final_code = ''.join([line[1:] for line in diff['diff'].split('\n') if (line.startswith('+') or line.startswith('-')) and not line.startswith('+++') and not line.startswith('---')])
|
||||
final_code_escaped = json.dumps(final_code)[1:-1] # Escape the final code
|
||||
results.append({
|
||||
"commit_sha": commit_sha,
|
||||
"author_name": author_name,
|
||||
"diff": final_code_escaped
|
||||
})
|
||||
except requests.RequestException as e:
|
||||
print(f"Error fetching data from GitLab: {e}")
|
||||
|
||||
return results
|
||||
@ -0,0 +1,77 @@
|
||||
identity:
|
||||
name: gitlab_commits
|
||||
author: Leo.Wang
|
||||
label:
|
||||
en_US: GitLab Commits
|
||||
zh_Hans: GitLab 提交内容查询
|
||||
description:
|
||||
human:
|
||||
en_US: A tool for query GitLab commits, Input should be a exists username or projec.
|
||||
zh_Hans: 一个用于查询 GitLab 代码提交内容的工具,输入的内容应该是一个已存在的用户名或者项目名。
|
||||
llm: A tool for query GitLab commits, Input should be a exists username or project.
|
||||
parameters:
|
||||
- name: username
|
||||
type: string
|
||||
required: false
|
||||
label:
|
||||
en_US: username
|
||||
zh_Hans: 员工用户名
|
||||
human_description:
|
||||
en_US: username
|
||||
zh_Hans: 员工用户名
|
||||
llm_description: User name for GitLab
|
||||
form: llm
|
||||
- name: project
|
||||
type: string
|
||||
required: true
|
||||
label:
|
||||
en_US: project
|
||||
zh_Hans: 项目名
|
||||
human_description:
|
||||
en_US: project
|
||||
zh_Hans: 项目名
|
||||
llm_description: project for GitLab
|
||||
form: llm
|
||||
- name: start_time
|
||||
type: string
|
||||
required: false
|
||||
label:
|
||||
en_US: start_time
|
||||
zh_Hans: 开始时间
|
||||
human_description:
|
||||
en_US: start_time
|
||||
zh_Hans: 开始时间
|
||||
llm_description: Start time for GitLab
|
||||
form: llm
|
||||
- name: end_time
|
||||
type: string
|
||||
required: false
|
||||
label:
|
||||
en_US: end_time
|
||||
zh_Hans: 结束时间
|
||||
human_description:
|
||||
en_US: end_time
|
||||
zh_Hans: 结束时间
|
||||
llm_description: End time for GitLab
|
||||
form: llm
|
||||
- name: change_type
|
||||
type: select
|
||||
required: false
|
||||
options:
|
||||
- value: all
|
||||
label:
|
||||
en_US: all
|
||||
zh_Hans: 所有
|
||||
- value: new
|
||||
label:
|
||||
en_US: new
|
||||
zh_Hans: 新增
|
||||
default: all
|
||||
label:
|
||||
en_US: change_type
|
||||
zh_Hans: 变更类型
|
||||
human_description:
|
||||
en_US: change_type
|
||||
zh_Hans: 变更类型
|
||||
llm_description: Content change type for GitLab
|
||||
form: llm
|
||||
95
api/core/tools/provider/builtin/gitlab/tools/gitlab_files.py
Normal file
@ -0,0 +1,95 @@
|
||||
from typing import Any, Union
|
||||
|
||||
import requests
|
||||
|
||||
from core.tools.entities.tool_entities import ToolInvokeMessage
|
||||
from core.tools.tool.builtin_tool import BuiltinTool
|
||||
|
||||
|
||||
class GitlabFilesTool(BuiltinTool):
|
||||
def _invoke(self,
|
||||
user_id: str,
|
||||
tool_parameters: dict[str, Any]
|
||||
) -> Union[ToolInvokeMessage, list[ToolInvokeMessage]]:
|
||||
|
||||
project = tool_parameters.get('project', '')
|
||||
branch = tool_parameters.get('branch', '')
|
||||
path = tool_parameters.get('path', '')
|
||||
|
||||
|
||||
if not project:
|
||||
return self.create_text_message('Project is required')
|
||||
if not branch:
|
||||
return self.create_text_message('Branch is required')
|
||||
|
||||
if not path:
|
||||
return self.create_text_message('Path is required')
|
||||
|
||||
access_token = self.runtime.credentials.get('access_tokens')
|
||||
site_url = self.runtime.credentials.get('site_url')
|
||||
|
||||
if 'access_tokens' not in self.runtime.credentials or not self.runtime.credentials.get('access_tokens'):
|
||||
return self.create_text_message("Gitlab API Access Tokens is required.")
|
||||
if 'site_url' not in self.runtime.credentials or not self.runtime.credentials.get('site_url'):
|
||||
site_url = 'https://gitlab.com'
|
||||
|
||||
# Get project ID from project name
|
||||
project_id = self.get_project_id(site_url, access_token, project)
|
||||
if not project_id:
|
||||
return self.create_text_message(f"Project '{project}' not found.")
|
||||
|
||||
# Get commit content
|
||||
result = self.fetch(user_id, project_id, site_url, access_token, branch, path)
|
||||
|
||||
return [self.create_json_message(item) for item in result]
|
||||
|
||||
def extract_project_name_and_path(self, path: str) -> tuple[str, str]:
|
||||
parts = path.split('/', 1)
|
||||
if len(parts) < 2:
|
||||
return None, None
|
||||
return parts[0], parts[1]
|
||||
|
||||
def get_project_id(self, site_url: str, access_token: str, project_name: str) -> Union[str, None]:
|
||||
headers = {"PRIVATE-TOKEN": access_token}
|
||||
try:
|
||||
url = f"{site_url}/api/v4/projects?search={project_name}"
|
||||
response = requests.get(url, headers=headers)
|
||||
response.raise_for_status()
|
||||
projects = response.json()
|
||||
for project in projects:
|
||||
if project['name'] == project_name:
|
||||
return project['id']
|
||||
except requests.RequestException as e:
|
||||
print(f"Error fetching project ID from GitLab: {e}")
|
||||
return None
|
||||
|
||||
def fetch(self,user_id: str, project_id: str, site_url: str, access_token: str, branch: str, path: str = None) -> list[dict[str, Any]]:
|
||||
domain = site_url
|
||||
headers = {"PRIVATE-TOKEN": access_token}
|
||||
results = []
|
||||
|
||||
try:
|
||||
# List files and directories in the given path
|
||||
url = f"{domain}/api/v4/projects/{project_id}/repository/tree?path={path}&ref={branch}"
|
||||
response = requests.get(url, headers=headers)
|
||||
response.raise_for_status()
|
||||
items = response.json()
|
||||
|
||||
for item in items:
|
||||
item_path = item['path']
|
||||
if item['type'] == 'tree': # It's a directory
|
||||
results.extend(self.fetch(project_id, site_url, access_token, branch, item_path))
|
||||
else: # It's a file
|
||||
file_url = f"{domain}/api/v4/projects/{project_id}/repository/files/{item_path}/raw?ref={branch}"
|
||||
file_response = requests.get(file_url, headers=headers)
|
||||
file_response.raise_for_status()
|
||||
file_content = file_response.text
|
||||
results.append({
|
||||
"path": item_path,
|
||||
"branch": branch,
|
||||
"content": file_content
|
||||
})
|
||||
except requests.RequestException as e:
|
||||
print(f"Error fetching data from GitLab: {e}")
|
||||
|
||||
return results
|
||||
@ -0,0 +1,45 @@
|
||||
identity:
|
||||
name: gitlab_files
|
||||
author: Leo.Wang
|
||||
label:
|
||||
en_US: GitLab Files
|
||||
zh_Hans: GitLab 文件获取
|
||||
description:
|
||||
human:
|
||||
en_US: A tool for query GitLab files, Input should be branch and a exists file or directory path.
|
||||
zh_Hans: 一个用于查询 GitLab 文件的工具,输入的内容应该是分支和一个已存在文件或者文件夹路径。
|
||||
llm: A tool for query GitLab files, Input should be a exists file or directory path.
|
||||
parameters:
|
||||
- name: project
|
||||
type: string
|
||||
required: true
|
||||
label:
|
||||
en_US: project
|
||||
zh_Hans: 项目
|
||||
human_description:
|
||||
en_US: project
|
||||
zh_Hans: 项目
|
||||
llm_description: Project for GitLab
|
||||
form: llm
|
||||
- name: branch
|
||||
type: string
|
||||
required: true
|
||||
label:
|
||||
en_US: branch
|
||||
zh_Hans: 分支
|
||||
human_description:
|
||||
en_US: branch
|
||||
zh_Hans: 分支
|
||||
llm_description: Branch for GitLab
|
||||
form: llm
|
||||
- name: path
|
||||
type: string
|
||||
required: true
|
||||
label:
|
||||
en_US: path
|
||||
zh_Hans: 文件路径
|
||||
human_description:
|
||||
en_US: path
|
||||
zh_Hans: 文件路径
|
||||
llm_description: File path for GitLab
|
||||
form: llm
|
||||
43
api/core/tools/provider/builtin/jina/tools/jina_tokenizer.py
Normal file
@ -0,0 +1,43 @@
|
||||
from typing import Any
|
||||
|
||||
from core.helper import ssrf_proxy
|
||||
from core.tools.entities.tool_entities import ToolInvokeMessage
|
||||
from core.tools.tool.builtin_tool import BuiltinTool
|
||||
|
||||
|
||||
class JinaTokenizerTool(BuiltinTool):
|
||||
_jina_tokenizer_endpoint = 'https://tokenize.jina.ai/'
|
||||
|
||||
def _invoke(
|
||||
self,
|
||||
user_id: str,
|
||||
tool_parameters: dict[str, Any],
|
||||
) -> ToolInvokeMessage:
|
||||
content = tool_parameters['content']
|
||||
body = {
|
||||
"content": content
|
||||
}
|
||||
|
||||
headers = {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
|
||||
if 'api_key' in self.runtime.credentials and self.runtime.credentials.get('api_key'):
|
||||
headers['Authorization'] = "Bearer " + self.runtime.credentials.get('api_key')
|
||||
|
||||
if tool_parameters.get('return_chunks', False):
|
||||
body['return_chunks'] = True
|
||||
|
||||
if tool_parameters.get('return_tokens', False):
|
||||
body['return_tokens'] = True
|
||||
|
||||
if tokenizer := tool_parameters.get('tokenizer'):
|
||||
body['tokenizer'] = tokenizer
|
||||
|
||||
response = ssrf_proxy.post(
|
||||
self._jina_tokenizer_endpoint,
|
||||
headers=headers,
|
||||
json=body,
|
||||
)
|
||||
|
||||
return self.create_json_message(response.json())
|
||||
@ -0,0 +1,70 @@
|
||||
identity:
|
||||
name: jina_tokenizer
|
||||
author: hjlarry
|
||||
label:
|
||||
en_US: JinaTokenizer
|
||||
description:
|
||||
human:
|
||||
en_US: Free API to tokenize text and segment long text into chunks.
|
||||
zh_Hans: 免费的API可以将文本tokenize,也可以将长文本分割成多个部分。
|
||||
llm: Free API to tokenize text and segment long text into chunks.
|
||||
parameters:
|
||||
- name: content
|
||||
type: string
|
||||
required: true
|
||||
label:
|
||||
en_US: Content
|
||||
zh_Hans: 内容
|
||||
llm_description: the content which need to tokenize or segment
|
||||
form: llm
|
||||
- name: return_tokens
|
||||
type: boolean
|
||||
required: false
|
||||
label:
|
||||
en_US: Return the tokens
|
||||
zh_Hans: 是否返回tokens
|
||||
human_description:
|
||||
en_US: Return the tokens and their corresponding ids in the response.
|
||||
zh_Hans: 返回tokens及其对应的ids。
|
||||
form: form
|
||||
- name: return_chunks
|
||||
type: boolean
|
||||
label:
|
||||
en_US: Return the chunks
|
||||
zh_Hans: 是否分块
|
||||
human_description:
|
||||
en_US: Chunking the input into semantically meaningful segments while handling a wide variety of text types and edge cases based on common structural cues.
|
||||
zh_Hans: 将输入分块为具有语义意义的片段,同时根据常见的结构线索处理各种文本类型和边缘情况。
|
||||
form: form
|
||||
- name: tokenizer
|
||||
type: select
|
||||
options:
|
||||
- value: cl100k_base
|
||||
label:
|
||||
en_US: cl100k_base
|
||||
- value: o200k_base
|
||||
label:
|
||||
en_US: o200k_base
|
||||
- value: p50k_base
|
||||
label:
|
||||
en_US: p50k_base
|
||||
- value: r50k_base
|
||||
label:
|
||||
en_US: r50k_base
|
||||
- value: p50k_edit
|
||||
label:
|
||||
en_US: p50k_edit
|
||||
- value: gpt2
|
||||
label:
|
||||
en_US: gpt2
|
||||
label:
|
||||
en_US: Tokenizer
|
||||
human_description:
|
||||
en_US: |
|
||||
· cl100k_base --- gpt-4, gpt-3.5-turbo, gpt-3.5
|
||||
· o200k_base --- gpt-4o, gpt-4o-mini
|
||||
· p50k_base --- text-davinci-003, text-davinci-002
|
||||
· r50k_base --- text-davinci-001, text-curie-001
|
||||
· p50k_edit --- text-davinci-edit-001, code-davinci-edit-001
|
||||
· gpt2 --- gpt-2
|
||||
form: form
|
||||
@ -36,21 +36,26 @@ class JSONParseTool(BuiltinTool):
|
||||
# get create path
|
||||
create_path = tool_parameters.get('create_path', False)
|
||||
|
||||
# get value decode.
|
||||
# if true, it will be decoded to an dict
|
||||
value_decode = tool_parameters.get('value_decode', False)
|
||||
|
||||
ensure_ascii = tool_parameters.get('ensure_ascii', True)
|
||||
try:
|
||||
result = self._insert(content, query, new_value, ensure_ascii, index, create_path)
|
||||
result = self._insert(content, query, new_value, ensure_ascii, value_decode, index, create_path)
|
||||
return self.create_text_message(str(result))
|
||||
except Exception:
|
||||
return self.create_text_message('Failed to insert JSON content')
|
||||
|
||||
def _insert(self, origin_json, query, new_value, ensure_ascii: bool, index=None, create_path=False):
|
||||
def _insert(self, origin_json, query, new_value, ensure_ascii: bool, value_decode: bool, index=None, create_path=False):
|
||||
try:
|
||||
input_data = json.loads(origin_json)
|
||||
expr = parse(query)
|
||||
try:
|
||||
new_value = json.loads(new_value)
|
||||
except json.JSONDecodeError:
|
||||
new_value = new_value
|
||||
if value_decode is True:
|
||||
try:
|
||||
new_value = json.loads(new_value)
|
||||
except json.JSONDecodeError:
|
||||
return "Cannot decode new value to json object"
|
||||
|
||||
matches = expr.find(input_data)
|
||||
|
||||
|
||||
@ -47,10 +47,22 @@ parameters:
|
||||
pt_BR: New Value
|
||||
human_description:
|
||||
en_US: New Value
|
||||
zh_Hans: 新值
|
||||
zh_Hans: 插入的新值
|
||||
pt_BR: New Value
|
||||
llm_description: New Value to insert
|
||||
form: llm
|
||||
- name: value_decode
|
||||
type: boolean
|
||||
default: false
|
||||
label:
|
||||
en_US: Decode Value
|
||||
zh_Hans: 解码值
|
||||
pt_BR: Decode Value
|
||||
human_description:
|
||||
en_US: Whether to decode the value to a JSON object
|
||||
zh_Hans: 是否将值解码为 JSON 对象
|
||||
pt_BR: Whether to decode the value to a JSON object
|
||||
form: form
|
||||
- name: create_path
|
||||
type: select
|
||||
required: true
|
||||
|
||||
@ -35,6 +35,10 @@ class JSONReplaceTool(BuiltinTool):
|
||||
if not replace_model:
|
||||
return self.create_text_message('Invalid parameter replace_model')
|
||||
|
||||
# get value decode.
|
||||
# if true, it will be decoded to an dict
|
||||
value_decode = tool_parameters.get('value_decode', False)
|
||||
|
||||
ensure_ascii = tool_parameters.get('ensure_ascii', True)
|
||||
try:
|
||||
if replace_model == 'pattern':
|
||||
@ -42,17 +46,17 @@ class JSONReplaceTool(BuiltinTool):
|
||||
replace_pattern = tool_parameters.get('replace_pattern', '')
|
||||
if not replace_pattern:
|
||||
return self.create_text_message('Invalid parameter replace_pattern')
|
||||
result = self._replace_pattern(content, query, replace_pattern, replace_value, ensure_ascii)
|
||||
result = self._replace_pattern(content, query, replace_pattern, replace_value, ensure_ascii, value_decode)
|
||||
elif replace_model == 'key':
|
||||
result = self._replace_key(content, query, replace_value, ensure_ascii)
|
||||
elif replace_model == 'value':
|
||||
result = self._replace_value(content, query, replace_value, ensure_ascii)
|
||||
result = self._replace_value(content, query, replace_value, ensure_ascii, value_decode)
|
||||
return self.create_text_message(str(result))
|
||||
except Exception:
|
||||
return self.create_text_message('Failed to replace JSON content')
|
||||
|
||||
# Replace pattern
|
||||
def _replace_pattern(self, content: str, query: str, replace_pattern: str, replace_value: str, ensure_ascii: bool) -> str:
|
||||
def _replace_pattern(self, content: str, query: str, replace_pattern: str, replace_value: str, ensure_ascii: bool, value_decode: bool) -> str:
|
||||
try:
|
||||
input_data = json.loads(content)
|
||||
expr = parse(query)
|
||||
@ -61,6 +65,12 @@ class JSONReplaceTool(BuiltinTool):
|
||||
|
||||
for match in matches:
|
||||
new_value = match.value.replace(replace_pattern, replace_value)
|
||||
if value_decode is True:
|
||||
try:
|
||||
new_value = json.loads(new_value)
|
||||
except json.JSONDecodeError:
|
||||
return "Cannot decode replace value to json object"
|
||||
|
||||
match.full_path.update(input_data, new_value)
|
||||
|
||||
return json.dumps(input_data, ensure_ascii=ensure_ascii)
|
||||
@ -92,10 +102,15 @@ class JSONReplaceTool(BuiltinTool):
|
||||
return str(e)
|
||||
|
||||
# Replace value
|
||||
def _replace_value(self, content: str, query: str, replace_value: str, ensure_ascii: bool) -> str:
|
||||
def _replace_value(self, content: str, query: str, replace_value: str, ensure_ascii: bool, value_decode: bool) -> str:
|
||||
try:
|
||||
input_data = json.loads(content)
|
||||
expr = parse(query)
|
||||
if value_decode is True:
|
||||
try:
|
||||
replace_value = json.loads(replace_value)
|
||||
except json.JSONDecodeError:
|
||||
return "Cannot decode replace value to json object"
|
||||
|
||||
matches = expr.find(input_data)
|
||||
|
||||
|
||||
@ -60,10 +60,22 @@ parameters:
|
||||
pt_BR: Replace Value
|
||||
human_description:
|
||||
en_US: New Value
|
||||
zh_Hans: New Value
|
||||
zh_Hans: 新值
|
||||
pt_BR: New Value
|
||||
llm_description: New Value to replace
|
||||
form: llm
|
||||
- name: value_decode
|
||||
type: boolean
|
||||
default: false
|
||||
label:
|
||||
en_US: Decode Value
|
||||
zh_Hans: 解码值
|
||||
pt_BR: Decode Value
|
||||
human_description:
|
||||
en_US: Whether to decode the value to a JSON object (Does not apply to replace key)
|
||||
zh_Hans: 是否将值解码为 JSON 对象 (不适用于键替换)
|
||||
pt_BR: Whether to decode the value to a JSON object (Does not apply to replace key)
|
||||
form: form
|
||||
- name: replace_model
|
||||
type: select
|
||||
required: true
|
||||
|
||||
@ -0,0 +1,73 @@
|
||||
from novita_client import (
|
||||
Txt2ImgV3Embedding,
|
||||
Txt2ImgV3HiresFix,
|
||||
Txt2ImgV3LoRA,
|
||||
Txt2ImgV3Refiner,
|
||||
V3TaskImage,
|
||||
)
|
||||
|
||||
|
||||
class NovitaAiToolBase:
|
||||
def _extract_loras(self, loras_str: str):
|
||||
if not loras_str:
|
||||
return []
|
||||
|
||||
loras_ori_list = lora_str.strip().split(';')
|
||||
result_list = []
|
||||
for lora_str in loras_ori_list:
|
||||
lora_info = lora_str.strip().split(',')
|
||||
lora = Txt2ImgV3LoRA(
|
||||
model_name=lora_info[0].strip(),
|
||||
strength=float(lora_info[1]),
|
||||
)
|
||||
result_list.append(lora)
|
||||
|
||||
return result_list
|
||||
|
||||
def _extract_embeddings(self, embeddings_str: str):
|
||||
if not embeddings_str:
|
||||
return []
|
||||
|
||||
embeddings_ori_list = embeddings_str.strip().split(';')
|
||||
result_list = []
|
||||
for embedding_str in embeddings_ori_list:
|
||||
embedding = Txt2ImgV3Embedding(
|
||||
model_name=embedding_str.strip()
|
||||
)
|
||||
result_list.append(embedding)
|
||||
|
||||
return result_list
|
||||
|
||||
def _extract_hires_fix(self, hires_fix_str: str):
|
||||
hires_fix_info = hires_fix_str.strip().split(',')
|
||||
if 'upscaler' in hires_fix_info:
|
||||
hires_fix = Txt2ImgV3HiresFix(
|
||||
target_width=int(hires_fix_info[0]),
|
||||
target_height=int(hires_fix_info[1]),
|
||||
strength=float(hires_fix_info[2]),
|
||||
upscaler=hires_fix_info[3].strip()
|
||||
)
|
||||
else:
|
||||
hires_fix = Txt2ImgV3HiresFix(
|
||||
target_width=int(hires_fix_info[0]),
|
||||
target_height=int(hires_fix_info[1]),
|
||||
strength=float(hires_fix_info[2])
|
||||
)
|
||||
|
||||
return hires_fix
|
||||
|
||||
def _extract_refiner(self, switch_at: str):
|
||||
refiner = Txt2ImgV3Refiner(
|
||||
switch_at=float(switch_at)
|
||||
)
|
||||
return refiner
|
||||
|
||||
def _is_hit_nsfw_detection(self, image: V3TaskImage, confidence_threshold: float) -> bool:
|
||||
"""
|
||||
is hit nsfw
|
||||
"""
|
||||
if image.nsfw_detection_result is None:
|
||||
return False
|
||||
if image.nsfw_detection_result.valid and image.nsfw_detection_result.confidence >= confidence_threshold:
|
||||
return True
|
||||
return False
|
||||
@ -4,19 +4,15 @@ from typing import Any, Union
|
||||
|
||||
from novita_client import (
|
||||
NovitaClient,
|
||||
Txt2ImgV3Embedding,
|
||||
Txt2ImgV3HiresFix,
|
||||
Txt2ImgV3LoRA,
|
||||
Txt2ImgV3Refiner,
|
||||
V3TaskImage,
|
||||
)
|
||||
|
||||
from core.tools.entities.tool_entities import ToolInvokeMessage
|
||||
from core.tools.errors import ToolProviderCredentialValidationError
|
||||
from core.tools.provider.builtin.novitaai._novita_tool_base import NovitaAiToolBase
|
||||
from core.tools.tool.builtin_tool import BuiltinTool
|
||||
|
||||
|
||||
class NovitaAiTxt2ImgTool(BuiltinTool):
|
||||
class NovitaAiTxt2ImgTool(BuiltinTool, NovitaAiToolBase):
|
||||
def _invoke(self,
|
||||
user_id: str,
|
||||
tool_parameters: dict[str, Any],
|
||||
@ -73,65 +69,19 @@ class NovitaAiTxt2ImgTool(BuiltinTool):
|
||||
|
||||
# process loras
|
||||
if 'loras' in res_parameters:
|
||||
loras_ori_list = res_parameters.get('loras').strip().split(';')
|
||||
locals_list = []
|
||||
for lora_str in loras_ori_list:
|
||||
lora_info = lora_str.strip().split(',')
|
||||
lora = Txt2ImgV3LoRA(
|
||||
model_name=lora_info[0].strip(),
|
||||
strength=float(lora_info[1]),
|
||||
)
|
||||
locals_list.append(lora)
|
||||
|
||||
res_parameters['loras'] = locals_list
|
||||
res_parameters['loras'] = self._extract_loras(res_parameters.get('loras'))
|
||||
|
||||
# process embeddings
|
||||
if 'embeddings' in res_parameters:
|
||||
embeddings_ori_list = res_parameters.get('embeddings').strip().split(';')
|
||||
locals_list = []
|
||||
for embedding_str in embeddings_ori_list:
|
||||
embedding = Txt2ImgV3Embedding(
|
||||
model_name=embedding_str.strip()
|
||||
)
|
||||
locals_list.append(embedding)
|
||||
|
||||
res_parameters['embeddings'] = locals_list
|
||||
res_parameters['embeddings'] = self._extract_embeddings(res_parameters.get('embeddings'))
|
||||
|
||||
# process hires_fix
|
||||
if 'hires_fix' in res_parameters:
|
||||
hires_fix_ori = res_parameters.get('hires_fix')
|
||||
hires_fix_info = hires_fix_ori.strip().split(',')
|
||||
if 'upscaler' in hires_fix_info:
|
||||
hires_fix = Txt2ImgV3HiresFix(
|
||||
target_width=int(hires_fix_info[0]),
|
||||
target_height=int(hires_fix_info[1]),
|
||||
strength=float(hires_fix_info[2]),
|
||||
upscaler=hires_fix_info[3].strip()
|
||||
)
|
||||
else:
|
||||
hires_fix = Txt2ImgV3HiresFix(
|
||||
target_width=int(hires_fix_info[0]),
|
||||
target_height=int(hires_fix_info[1]),
|
||||
strength=float(hires_fix_info[2])
|
||||
)
|
||||
res_parameters['hires_fix'] = self._extract_hires_fix(res_parameters.get('hires_fix'))
|
||||
|
||||
res_parameters['hires_fix'] = hires_fix
|
||||
|
||||
if 'refiner_switch_at' in res_parameters:
|
||||
refiner = Txt2ImgV3Refiner(
|
||||
switch_at=float(res_parameters.get('refiner_switch_at'))
|
||||
)
|
||||
del res_parameters['refiner_switch_at']
|
||||
res_parameters['refiner'] = refiner
|
||||
# process refiner
|
||||
if 'refiner_switch_at' in res_parameters:
|
||||
res_parameters['refiner'] = self._extract_refiner(res_parameters.get('refiner_switch_at'))
|
||||
del res_parameters['refiner_switch_at']
|
||||
|
||||
return res_parameters
|
||||
|
||||
def _is_hit_nsfw_detection(self, image: V3TaskImage, confidence_threshold: float) -> bool:
|
||||
"""
|
||||
is hit nsfw
|
||||
"""
|
||||
if image.nsfw_detection_result is None:
|
||||
return False
|
||||
if image.nsfw_detection_result.valid and image.nsfw_detection_result.confidence >= confidence_threshold:
|
||||
return True
|
||||
return False
|
||||
|
||||
BIN
api/core/tools/provider/builtin/onebot/_assets/icon.ico
Normal file
|
After Width: | Height: | Size: 37 KiB |
12
api/core/tools/provider/builtin/onebot/onebot.py
Normal file
@ -0,0 +1,12 @@
|
||||
from typing import Any
|
||||
|
||||
from core.tools.errors import ToolProviderCredentialValidationError
|
||||
from core.tools.provider.builtin_tool_provider import BuiltinToolProviderController
|
||||
|
||||
|
||||
class OneBotProvider(BuiltinToolProviderController):
|
||||
|
||||
def _validate_credentials(self, credentials: dict[str, Any]) -> None:
|
||||
|
||||
if not credentials.get("ob11_http_url"):
|
||||
raise ToolProviderCredentialValidationError('OneBot HTTP URL is required.')
|
||||
35
api/core/tools/provider/builtin/onebot/onebot.yaml
Normal file
@ -0,0 +1,35 @@
|
||||
identity:
|
||||
author: RockChinQ
|
||||
name: onebot
|
||||
label:
|
||||
en_US: OneBot v11 Protocol
|
||||
zh_Hans: OneBot v11 协议
|
||||
description:
|
||||
en_US: Unofficial OneBot v11 Protocol Tool
|
||||
zh_Hans: 非官方 OneBot v11 协议工具
|
||||
icon: icon.ico
|
||||
credentials_for_provider:
|
||||
ob11_http_url:
|
||||
type: text-input
|
||||
required: true
|
||||
label:
|
||||
en_US: HTTP URL
|
||||
zh_Hans: HTTP URL
|
||||
description:
|
||||
en_US: Forward HTTP URL of OneBot v11
|
||||
zh_Hans: OneBot v11 正向 HTTP URL
|
||||
help:
|
||||
en_US: Fill this with the HTTP URL of your OneBot server
|
||||
zh_Hans: 请在你的 OneBot 协议端开启 正向 HTTP 并填写其 URL
|
||||
access_token:
|
||||
type: secret-input
|
||||
required: false
|
||||
label:
|
||||
en_US: Access Token
|
||||
zh_Hans: 访问令牌
|
||||
description:
|
||||
en_US: Access Token for OneBot v11 Protocol
|
||||
zh_Hans: OneBot 协议访问令牌
|
||||
help:
|
||||
en_US: Fill this if you set a access token in your OneBot server
|
||||
zh_Hans: 如果你在 OneBot 服务器中设置了 access token,请填写此项
|
||||
@ -0,0 +1,64 @@
|
||||
from typing import Any, Union
|
||||
|
||||
import requests
|
||||
from yarl import URL
|
||||
|
||||
from core.tools.entities.tool_entities import ToolInvokeMessage
|
||||
from core.tools.tool.builtin_tool import BuiltinTool
|
||||
|
||||
|
||||
class SendGroupMsg(BuiltinTool):
|
||||
"""OneBot v11 Tool: Send Group Message"""
|
||||
|
||||
def _invoke(
|
||||
self,
|
||||
user_id: str,
|
||||
tool_parameters: dict[str, Any]
|
||||
) -> Union[ToolInvokeMessage, list[ToolInvokeMessage]]:
|
||||
|
||||
# Get parameters
|
||||
send_group_id = tool_parameters.get('group_id', '')
|
||||
|
||||
message = tool_parameters.get('message', '')
|
||||
if not message:
|
||||
return self.create_json_message(
|
||||
{
|
||||
'error': 'Message is empty.'
|
||||
}
|
||||
)
|
||||
|
||||
auto_escape = tool_parameters.get('auto_escape', False)
|
||||
|
||||
try:
|
||||
url = URL(self.runtime.credentials['ob11_http_url']) / 'send_group_msg'
|
||||
|
||||
resp = requests.post(
|
||||
url,
|
||||
json={
|
||||
'group_id': send_group_id,
|
||||
'message': message,
|
||||
'auto_escape': auto_escape
|
||||
},
|
||||
headers={
|
||||
'Authorization': 'Bearer ' + self.runtime.credentials['access_token']
|
||||
}
|
||||
)
|
||||
|
||||
if resp.status_code != 200:
|
||||
return self.create_json_message(
|
||||
{
|
||||
'error': f'Failed to send group message: {resp.text}'
|
||||
}
|
||||
)
|
||||
|
||||
return self.create_json_message(
|
||||
{
|
||||
'response': resp.json()
|
||||
}
|
||||
)
|
||||
except Exception as e:
|
||||
return self.create_json_message(
|
||||
{
|
||||
'error': f'Failed to send group message: {e}'
|
||||
}
|
||||
)
|
||||
@ -0,0 +1,46 @@
|
||||
identity:
|
||||
name: send_group_msg
|
||||
author: RockChinQ
|
||||
label:
|
||||
en_US: Send Group Message
|
||||
zh_Hans: 发送群消息
|
||||
description:
|
||||
human:
|
||||
en_US: Send a message to a group
|
||||
zh_Hans: 发送消息到群聊
|
||||
llm: A tool for sending a message segment to a group
|
||||
parameters:
|
||||
- name: group_id
|
||||
type: number
|
||||
required: true
|
||||
label:
|
||||
en_US: Target Group ID
|
||||
zh_Hans: 目标群 ID
|
||||
human_description:
|
||||
en_US: The group ID of the target group
|
||||
zh_Hans: 目标群的群 ID
|
||||
llm_description: The group ID of the target group
|
||||
form: llm
|
||||
- name: message
|
||||
type: string
|
||||
required: true
|
||||
label:
|
||||
en_US: Message
|
||||
zh_Hans: 消息
|
||||
human_description:
|
||||
en_US: The message to send
|
||||
zh_Hans: 要发送的消息。支持 CQ码(需要同时设置 auto_escape 为 true)
|
||||
llm_description: The message to send
|
||||
form: llm
|
||||
- name: auto_escape
|
||||
type: boolean
|
||||
required: false
|
||||
default: false
|
||||
label:
|
||||
en_US: Auto Escape
|
||||
zh_Hans: 自动转义
|
||||
human_description:
|
||||
en_US: If true, the message will be treated as a CQ code for parsing, otherwise it will be treated as plain text for direct sending. Since Dify currently does not support passing Object-format message chains, developers can send complex message components through CQ codes.
|
||||
zh_Hans: 若为 true 则会把 message 视为 CQ 码解析,否则视为 纯文本 直接发送。由于 Dify 目前不支持传入 Object格式 的消息,故开发者可以通过 CQ 码来发送复杂消息组件。
|
||||
llm_description: If true, the message will be treated as a CQ code for parsing, otherwise it will be treated as plain text for direct sending.
|
||||
form: form
|
||||
@ -0,0 +1,64 @@
|
||||
from typing import Any, Union
|
||||
|
||||
import requests
|
||||
from yarl import URL
|
||||
|
||||
from core.tools.entities.tool_entities import ToolInvokeMessage
|
||||
from core.tools.tool.builtin_tool import BuiltinTool
|
||||
|
||||
|
||||
class SendPrivateMsg(BuiltinTool):
|
||||
"""OneBot v11 Tool: Send Private Message"""
|
||||
|
||||
def _invoke(
|
||||
self,
|
||||
user_id: str,
|
||||
tool_parameters: dict[str, Any]
|
||||
) -> Union[ToolInvokeMessage, list[ToolInvokeMessage]]:
|
||||
|
||||
# Get parameters
|
||||
send_user_id = tool_parameters.get('user_id', '')
|
||||
|
||||
message = tool_parameters.get('message', '')
|
||||
if not message:
|
||||
return self.create_json_message(
|
||||
{
|
||||
'error': 'Message is empty.'
|
||||
}
|
||||
)
|
||||
|
||||
auto_escape = tool_parameters.get('auto_escape', False)
|
||||
|
||||
try:
|
||||
url = URL(self.runtime.credentials['ob11_http_url']) / 'send_private_msg'
|
||||
|
||||
resp = requests.post(
|
||||
url,
|
||||
json={
|
||||
'user_id': send_user_id,
|
||||
'message': message,
|
||||
'auto_escape': auto_escape
|
||||
},
|
||||
headers={
|
||||
'Authorization': 'Bearer ' + self.runtime.credentials['access_token']
|
||||
}
|
||||
)
|
||||
|
||||
if resp.status_code != 200:
|
||||
return self.create_json_message(
|
||||
{
|
||||
'error': f'Failed to send private message: {resp.text}'
|
||||
}
|
||||
)
|
||||
|
||||
return self.create_json_message(
|
||||
{
|
||||
'response': resp.json()
|
||||
}
|
||||
)
|
||||
except Exception as e:
|
||||
return self.create_json_message(
|
||||
{
|
||||
'error': f'Failed to send private message: {e}'
|
||||
}
|
||||
)
|
||||
@ -0,0 +1,46 @@
|
||||
identity:
|
||||
name: send_private_msg
|
||||
author: RockChinQ
|
||||
label:
|
||||
en_US: Send Private Message
|
||||
zh_Hans: 发送私聊消息
|
||||
description:
|
||||
human:
|
||||
en_US: Send a private message to a user
|
||||
zh_Hans: 发送私聊消息给用户
|
||||
llm: A tool for sending a message segment to a user in private chat
|
||||
parameters:
|
||||
- name: user_id
|
||||
type: number
|
||||
required: true
|
||||
label:
|
||||
en_US: Target User ID
|
||||
zh_Hans: 目标用户 ID
|
||||
human_description:
|
||||
en_US: The user ID of the target user
|
||||
zh_Hans: 目标用户的用户 ID
|
||||
llm_description: The user ID of the target user
|
||||
form: llm
|
||||
- name: message
|
||||
type: string
|
||||
required: true
|
||||
label:
|
||||
en_US: Message
|
||||
zh_Hans: 消息
|
||||
human_description:
|
||||
en_US: The message to send
|
||||
zh_Hans: 要发送的消息。支持 CQ码(需要同时设置 auto_escape 为 true)
|
||||
llm_description: The message to send
|
||||
form: llm
|
||||
- name: auto_escape
|
||||
type: boolean
|
||||
required: false
|
||||
default: false
|
||||
label:
|
||||
en_US: Auto Escape
|
||||
zh_Hans: 自动转义
|
||||
human_description:
|
||||
en_US: If true, the message will be treated as a CQ code for parsing, otherwise it will be treated as plain text for direct sending. Since Dify currently does not support passing Object-format message chains, developers can send complex message components through CQ codes.
|
||||
zh_Hans: 若为 true 则会把 message 视为 CQ 码解析,否则视为 纯文本 直接发送。由于 Dify 目前不支持传入 Object格式 的消息,故开发者可以通过 CQ 码来发送复杂消息组件。
|
||||
llm_description: If true, the message will be treated as a CQ code for parsing, otherwise it will be treated as plain text for direct sending.
|
||||
form: form
|
||||
1
api/core/tools/provider/builtin/regex/_assets/icon.svg
Normal file
@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1723087850395" class="icon" viewBox="0 0 1188 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="39503" xmlns:xlink="http://www.w3.org/1999/xlink" width="232.03125" height="200"><path d="M800.736395 0C906.556049 0 992.395062 92.513975 992.395062 206.569877V278.818765h-145.869432L865.975309 447.841975h-75.889778l-19.449679-169.035852H283.369877c13.520593-26.737778 21.162667-57.413531 21.162666-89.979259 0-48.159605-16.813827-79.492741-44.329086-112.829629h-22.338371c-21.168988 0-29.879309 2.932938-44.22795 19.658271-14.348642 16.731654-15.075556 32.085333-13.432099 54.771358l67.141531 793.34084h601.878123V1024H176.374519v-0.25284C78.07684 1018.424889 0 930.980346 0 823.763753L1.175704 745.876543h152.974222l-49.739852-590.127407C98.183901 71.863309 159.674469 0 237.871407 0zM160.079012 806.918321H58.228938l-0.126419 5.499259c0.12642 35.858963 13.482667 69.594074 37.692049 94.953877 20.751802 21.744198 47.344198 35.239506 76.092049 38.608592l-11.807605-139.061728zM1033.399309 524.641975c49.967407 0 71.667358 14.159012 82.267654 39.948642V529.698765H1188.345679v251.828149c0 74.840494-37.856395 122.374321-150.91358 122.374321-24.727704 0-55.517235-2.528395-77.722864-6.573828v-58.153086c21.699951 5.562469 48.45037 8.090864 72.173037 8.090864 59.050667 0 83.784691-16.687407 83.784691-68.772345v-14.664692c-11.611654 24.272593-33.311605 38.937284-82.267654 38.937284-88.329481 0-110.535111-56.13037-110.535111-139.061728 0-74.840494 22.20563-139.061728 110.535111-139.061729z m-284.703605 0c95.446914 0 117.279605 49.777778 117.279605 120.38321 0 15.739259-1.011358 30.473481-2.541037 38.090272l-176.677926 11.175506c2.534716 39.114272 25.385086 56.888889 74.119901 56.888889 33.513877 0 73.114864-9.645827 90.88316-19.297975v57.900246c-16.244938 9.652148-57.369284 19.304296-105.099061 19.304297C662.888296 809.08642 613.135802 778.100938 613.135802 666.864198S662.888296 524.641975 748.695704 524.641975zM424.005531 448.790123c95.560691 0 136.950519 30.599901 136.950518 112.210173 0 59.164444-20.947753 90.788346-66.43358 104.561778L594.17284 802.765432H502.701827l-83.297975-129.042963h-38.324148V802.765432H303.407407V448.790123z m631.258074 126.419754C1004.720988 575.209877 998.716049 615.847506 998.716049 660.037531v1.396938c0.132741 46.111605 7.003654 84.442074 56.547556 84.442074 54.044444 0 63.55121-31.49116 63.55121-85.839012 0-52.318815-9.506765-84.827654-63.55121-84.827654z m-303.470617 0c-45.814519 0-61.263012 19.879506-62.805334 63.209876l113.777778-8.666074c0-30.075259-7.205926-54.543802-50.966123-54.543802zM412.204247 512H379.259259v107.45679h30.694716C461.280395 619.45679 480.395062 609.121975 480.395062 563.661432 480.395062 520.786173 461.274074 512 409.953975 512z m372.577975-442.127802H361.370864c17.66084 33.842568 27.660642 73.007407 27.660642 114.725925 0 8.116148-0.423506 16.225975-1.169383 24.335803h517.12v-8.109827c0-34.999309-12.559802-67.90637-35.214222-92.589827-22.660741-24.683457-52.875062-38.362074-84.998321-38.362074z" fill="#000000" p-id="39504"></path></svg>
|
||||
|
After Width: | Height: | Size: 3.1 KiB |
19
api/core/tools/provider/builtin/regex/regex.py
Normal file
@ -0,0 +1,19 @@
|
||||
from typing import Any
|
||||
|
||||
from core.tools.errors import ToolProviderCredentialValidationError
|
||||
from core.tools.provider.builtin.regex.tools.regex_extract import RegexExpressionTool
|
||||
from core.tools.provider.builtin_tool_provider import BuiltinToolProviderController
|
||||
|
||||
|
||||
class RegexProvider(BuiltinToolProviderController):
|
||||
def _validate_credentials(self, credentials: dict[str, Any]) -> None:
|
||||
try:
|
||||
RegexExpressionTool().invoke(
|
||||
user_id='',
|
||||
tool_parameters={
|
||||
'content': '1+(2+3)*4',
|
||||
'expression': r'(\d+)',
|
||||
},
|
||||
)
|
||||
except Exception as e:
|
||||
raise ToolProviderCredentialValidationError(str(e))
|
||||
15
api/core/tools/provider/builtin/regex/regex.yaml
Normal file
@ -0,0 +1,15 @@
|
||||
identity:
|
||||
author: zhuhao
|
||||
name: regex
|
||||
label:
|
||||
en_US: Regex
|
||||
zh_Hans: 正则表达式提取
|
||||
pt_BR: Regex
|
||||
description:
|
||||
en_US: A tool for regex extraction.
|
||||
zh_Hans: 一个用于正则表达式内容提取的工具。
|
||||
pt_BR: A tool for regex extraction.
|
||||
icon: icon.svg
|
||||
tags:
|
||||
- utilities
|
||||
- productivity
|
||||
27
api/core/tools/provider/builtin/regex/tools/regex_extract.py
Normal file
@ -0,0 +1,27 @@
|
||||
import re
|
||||
from typing import Any, Union
|
||||
|
||||
from core.tools.entities.tool_entities import ToolInvokeMessage
|
||||
from core.tools.tool.builtin_tool import BuiltinTool
|
||||
|
||||
|
||||
class RegexExpressionTool(BuiltinTool):
|
||||
def _invoke(self,
|
||||
user_id: str,
|
||||
tool_parameters: dict[str, Any],
|
||||
) -> Union[ToolInvokeMessage, list[ToolInvokeMessage]]:
|
||||
"""
|
||||
invoke tools
|
||||
"""
|
||||
# get expression
|
||||
content = tool_parameters.get('content', '').strip()
|
||||
if not content:
|
||||
return self.create_text_message('Invalid content')
|
||||
expression = tool_parameters.get('expression', '').strip()
|
||||
if not expression:
|
||||
return self.create_text_message('Invalid expression')
|
||||
try:
|
||||
result = re.findall(expression, content)
|
||||
return self.create_text_message(str(result))
|
||||
except Exception as e:
|
||||
return self.create_text_message(f'Failed to extract result, error: {str(e)}')
|
||||
@ -0,0 +1,38 @@
|
||||
identity:
|
||||
name: regex_extract
|
||||
author: zhuhao
|
||||
label:
|
||||
en_US: Regex Extract
|
||||
zh_Hans: 正则表达式内容提取
|
||||
pt_BR: Regex Extract
|
||||
description:
|
||||
human:
|
||||
en_US: A tool for extracting matching content using regular expressions.
|
||||
zh_Hans: 一个用于利用正则表达式提取匹配内容结果的工具。
|
||||
pt_BR: A tool for extracting matching content using regular expressions.
|
||||
llm: A tool for extracting matching content using regular expressions.
|
||||
parameters:
|
||||
- name: content
|
||||
type: string
|
||||
required: true
|
||||
label:
|
||||
en_US: Content to be extracted
|
||||
zh_Hans: 内容
|
||||
pt_BR: Content to be extracted
|
||||
human_description:
|
||||
en_US: Content to be extracted
|
||||
zh_Hans: 内容
|
||||
pt_BR: Content to be extracted
|
||||
form: llm
|
||||
- name: expression
|
||||
type: string
|
||||
required: true
|
||||
label:
|
||||
en_US: Regular expression
|
||||
zh_Hans: 正则表达式
|
||||
pt_BR: Regular expression
|
||||
human_description:
|
||||
en_US: Regular expression
|
||||
zh_Hans: 正则表达式
|
||||
pt_BR: Regular expression
|
||||
form: llm
|
||||
2501
api/core/tools/provider/builtin/searxng/docker/settings.yml
Normal file
54
api/core/tools/provider/builtin/searxng/docker/uwsgi.ini
Normal file
@ -0,0 +1,54 @@
|
||||
[uwsgi]
|
||||
# Who will run the code
|
||||
uid = searxng
|
||||
gid = searxng
|
||||
|
||||
# Number of workers (usually CPU count)
|
||||
# default value: %k (= number of CPU core, see Dockerfile)
|
||||
workers = %k
|
||||
|
||||
# Number of threads per worker
|
||||
# default value: 4 (see Dockerfile)
|
||||
threads = 4
|
||||
|
||||
# The right granted on the created socket
|
||||
chmod-socket = 666
|
||||
|
||||
# Plugin to use and interpreter config
|
||||
single-interpreter = true
|
||||
master = true
|
||||
plugin = python3
|
||||
lazy-apps = true
|
||||
enable-threads = 4
|
||||
|
||||
# Module to import
|
||||
module = searx.webapp
|
||||
|
||||
# Virtualenv and python path
|
||||
pythonpath = /usr/local/searxng/
|
||||
chdir = /usr/local/searxng/searx/
|
||||
|
||||
# automatically set processes name to something meaningful
|
||||
auto-procname = true
|
||||
|
||||
# Disable request logging for privacy
|
||||
disable-logging = true
|
||||
log-5xx = true
|
||||
|
||||
# Set the max size of a request (request-body excluded)
|
||||
buffer-size = 8192
|
||||
|
||||
# No keep alive
|
||||
# See https://github.com/searx/searx-docker/issues/24
|
||||
add-header = Connection: close
|
||||
|
||||
# Follow SIGTERM convention
|
||||
# See https://github.com/searxng/searxng/issues/3427
|
||||
die-on-term
|
||||
|
||||
# uwsgi serves the static files
|
||||
static-map = /static=/usr/local/searxng/searx/static
|
||||
# expires set to one day
|
||||
static-expires = /* 86400
|
||||
static-gzip-all = True
|
||||
offload-threads = 4
|
||||
@ -17,8 +17,7 @@ class SearXNGProvider(BuiltinToolProviderController):
|
||||
tool_parameters={
|
||||
"query": "SearXNG",
|
||||
"limit": 1,
|
||||
"search_type": "page",
|
||||
"result_type": "link"
|
||||
"search_type": "general"
|
||||
},
|
||||
)
|
||||
except Exception as e:
|
||||
|
||||
@ -6,21 +6,18 @@ identity:
|
||||
zh_Hans: SearXNG
|
||||
description:
|
||||
en_US: A free internet metasearch engine.
|
||||
zh_Hans: 开源互联网元搜索引擎
|
||||
zh_Hans: 开源免费的互联网元搜索引擎
|
||||
icon: icon.svg
|
||||
tags:
|
||||
- search
|
||||
- productivity
|
||||
credentials_for_provider:
|
||||
searxng_base_url:
|
||||
type: secret-input
|
||||
type: text-input
|
||||
required: true
|
||||
label:
|
||||
en_US: SearXNG base URL
|
||||
zh_Hans: SearXNG base URL
|
||||
help:
|
||||
en_US: Please input your SearXNG base URL
|
||||
zh_Hans: 请输入您的 SearXNG base URL
|
||||
placeholder:
|
||||
en_US: Please input your SearXNG base URL
|
||||
zh_Hans: 请输入您的 SearXNG base URL
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
import json
|
||||
from typing import Any
|
||||
|
||||
import requests
|
||||
@ -7,90 +6,11 @@ from core.tools.entities.tool_entities import ToolInvokeMessage
|
||||
from core.tools.tool.builtin_tool import BuiltinTool
|
||||
|
||||
|
||||
class SearXNGSearchResults(dict):
|
||||
"""Wrapper for search results."""
|
||||
|
||||
def __init__(self, data: str):
|
||||
super().__init__(json.loads(data))
|
||||
self.__dict__ = self
|
||||
|
||||
@property
|
||||
def results(self) -> Any:
|
||||
return self.get("results", [])
|
||||
|
||||
|
||||
class SearXNGSearchTool(BuiltinTool):
|
||||
"""
|
||||
Tool for performing a search using SearXNG engine.
|
||||
"""
|
||||
|
||||
SEARCH_TYPE: dict[str, str] = {
|
||||
"page": "general",
|
||||
"news": "news",
|
||||
"image": "images",
|
||||
# "video": "videos",
|
||||
# "file": "files"
|
||||
}
|
||||
LINK_FILED: dict[str, str] = {
|
||||
"page": "url",
|
||||
"news": "url",
|
||||
"image": "img_src",
|
||||
# "video": "iframe_src",
|
||||
# "file": "magnetlink"
|
||||
}
|
||||
TEXT_FILED: dict[str, str] = {
|
||||
"page": "content",
|
||||
"news": "content",
|
||||
"image": "img_src",
|
||||
# "video": "iframe_src",
|
||||
# "file": "magnetlink"
|
||||
}
|
||||
|
||||
def _invoke_query(self, user_id: str, host: str, query: str, search_type: str, result_type: str, topK: int = 5) -> list[dict]:
|
||||
"""Run query and return the results."""
|
||||
|
||||
search_type = search_type.lower()
|
||||
if search_type not in self.SEARCH_TYPE.keys():
|
||||
search_type= "page"
|
||||
|
||||
response = requests.get(host, params={
|
||||
"q": query,
|
||||
"format": "json",
|
||||
"categories": self.SEARCH_TYPE[search_type]
|
||||
})
|
||||
|
||||
if response.status_code != 200:
|
||||
raise Exception(f'Error {response.status_code}: {response.text}')
|
||||
|
||||
search_results = SearXNGSearchResults(response.text).results[:topK]
|
||||
|
||||
if result_type == 'link':
|
||||
results = []
|
||||
if search_type == "page" or search_type == "news":
|
||||
for r in search_results:
|
||||
results.append(self.create_text_message(
|
||||
text=f'{r["title"]}: {r.get(self.LINK_FILED[search_type], "")}'
|
||||
))
|
||||
elif search_type == "image":
|
||||
for r in search_results:
|
||||
results.append(self.create_image_message(
|
||||
image=r.get(self.LINK_FILED[search_type], "")
|
||||
))
|
||||
else:
|
||||
for r in search_results:
|
||||
results.append(self.create_link_message(
|
||||
link=r.get(self.LINK_FILED[search_type], "")
|
||||
))
|
||||
|
||||
return results
|
||||
else:
|
||||
text = ''
|
||||
for i, r in enumerate(search_results):
|
||||
text += f'{i+1}: {r["title"]} - {r.get(self.TEXT_FILED[search_type], "")}\n'
|
||||
|
||||
return self.create_text_message(text=self.summary(user_id=user_id, content=text))
|
||||
|
||||
|
||||
def _invoke(self, user_id: str, tool_parameters: dict[str, Any]) -> ToolInvokeMessage | list[ToolInvokeMessage]:
|
||||
"""
|
||||
Invoke the SearXNG search tool.
|
||||
@ -103,23 +23,21 @@ class SearXNGSearchTool(BuiltinTool):
|
||||
ToolInvokeMessage | list[ToolInvokeMessage]: The result of the tool invocation.
|
||||
"""
|
||||
|
||||
host = self.runtime.credentials.get('searxng_base_url', None)
|
||||
host = self.runtime.credentials.get('searxng_base_url')
|
||||
if not host:
|
||||
raise Exception('SearXNG api is required')
|
||||
|
||||
query = tool_parameters.get('query')
|
||||
if not query:
|
||||
return self.create_text_message('Please input query')
|
||||
|
||||
num_results = min(tool_parameters.get('num_results', 5), 20)
|
||||
search_type = tool_parameters.get('search_type', 'page') or 'page'
|
||||
result_type = tool_parameters.get('result_type', 'text') or 'text'
|
||||
|
||||
return self._invoke_query(
|
||||
user_id=user_id,
|
||||
host=host,
|
||||
query=query,
|
||||
search_type=search_type,
|
||||
result_type=result_type,
|
||||
topK=num_results
|
||||
)
|
||||
response = requests.get(host, params={
|
||||
"q": tool_parameters.get('query'),
|
||||
"format": "json",
|
||||
"categories": tool_parameters.get('search_type', 'general')
|
||||
})
|
||||
|
||||
if response.status_code != 200:
|
||||
raise Exception(f'Error {response.status_code}: {response.text}')
|
||||
|
||||
res = response.json().get("results", [])
|
||||
if not res:
|
||||
return self.create_text_message(f"No results found, get response: {response.content}")
|
||||
|
||||
return [self.create_json_message(item) for item in res]
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
identity:
|
||||
name: searxng_search
|
||||
author: Tice
|
||||
author: Junytang
|
||||
label:
|
||||
en_US: SearXNG Search
|
||||
zh_Hans: SearXNG 搜索
|
||||
description:
|
||||
human:
|
||||
en_US: Perform searches on SearXNG and get results.
|
||||
zh_Hans: 在 SearXNG 上进行搜索并获取结果。
|
||||
en_US: SearXNG is a free internet metasearch engine which aggregates results from more than 70 search services.
|
||||
zh_Hans: SearXNG 是一个免费的互联网元搜索引擎,它从70多个不同的搜索服务中聚合搜索结果。
|
||||
llm: Perform searches on SearXNG and get results.
|
||||
parameters:
|
||||
- name: query
|
||||
@ -16,9 +16,6 @@ parameters:
|
||||
label:
|
||||
en_US: Query string
|
||||
zh_Hans: 查询语句
|
||||
human_description:
|
||||
en_US: The search query.
|
||||
zh_Hans: 搜索查询语句。
|
||||
llm_description: Key words for searching
|
||||
form: llm
|
||||
- name: search_type
|
||||
@ -27,63 +24,46 @@ parameters:
|
||||
label:
|
||||
en_US: search type
|
||||
zh_Hans: 搜索类型
|
||||
pt_BR: search type
|
||||
human_description:
|
||||
en_US: search type for page, news or image.
|
||||
zh_Hans: 选择搜索的类型:网页,新闻,图片。
|
||||
pt_BR: search type for page, news or image.
|
||||
default: Page
|
||||
default: general
|
||||
options:
|
||||
- value: Page
|
||||
- value: general
|
||||
label:
|
||||
en_US: Page
|
||||
zh_Hans: 网页
|
||||
pt_BR: Page
|
||||
- value: News
|
||||
en_US: General
|
||||
zh_Hans: 综合
|
||||
- value: images
|
||||
label:
|
||||
en_US: Images
|
||||
zh_Hans: 图片
|
||||
- value: videos
|
||||
label:
|
||||
en_US: Videos
|
||||
zh_Hans: 视频
|
||||
- value: news
|
||||
label:
|
||||
en_US: News
|
||||
zh_Hans: 新闻
|
||||
pt_BR: News
|
||||
- value: Image
|
||||
- value: map
|
||||
label:
|
||||
en_US: Image
|
||||
zh_Hans: 图片
|
||||
pt_BR: Image
|
||||
form: form
|
||||
- name: num_results
|
||||
type: number
|
||||
required: true
|
||||
label:
|
||||
en_US: Number of query results
|
||||
zh_Hans: 返回查询数量
|
||||
human_description:
|
||||
en_US: The number of query results.
|
||||
zh_Hans: 返回查询结果的数量。
|
||||
form: form
|
||||
default: 5
|
||||
min: 1
|
||||
max: 20
|
||||
- name: result_type
|
||||
type: select
|
||||
required: true
|
||||
label:
|
||||
en_US: result type
|
||||
zh_Hans: 结果类型
|
||||
pt_BR: result type
|
||||
human_description:
|
||||
en_US: return a list of links or texts.
|
||||
zh_Hans: 返回一个连接列表还是纯文本内容。
|
||||
pt_BR: return a list of links or texts.
|
||||
default: text
|
||||
options:
|
||||
- value: link
|
||||
en_US: Map
|
||||
zh_Hans: 地图
|
||||
- value: music
|
||||
label:
|
||||
en_US: Link
|
||||
zh_Hans: 链接
|
||||
pt_BR: Link
|
||||
- value: text
|
||||
en_US: Music
|
||||
zh_Hans: 音乐
|
||||
- value: it
|
||||
label:
|
||||
en_US: Text
|
||||
zh_Hans: 文本
|
||||
pt_BR: Text
|
||||
en_US: It
|
||||
zh_Hans: 信息技术
|
||||
- value: science
|
||||
label:
|
||||
en_US: Science
|
||||
zh_Hans: 科学
|
||||
- value: files
|
||||
label:
|
||||
en_US: Files
|
||||
zh_Hans: 文件
|
||||
- value: social_media
|
||||
label:
|
||||
en_US: Social Media
|
||||
zh_Hans: 社交媒体
|
||||
form: form
|
||||
|
||||
12
api/core/tools/provider/builtin/serper/_assets/icon.svg
Normal file
@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="128px" height="128px" viewBox="0 0 128 128" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>serper</title>
|
||||
<g id="ai-doc-check-2.0" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="画板" fill="#90CDF4" fill-rule="nonzero">
|
||||
<g id="serper">
|
||||
<path d="M65.2785371,32.0482743 C68.7012571,32.0482743 71.9303314,32.6286629 74.9661257,33.7892571 C78.0315429,34.9500343 80.7100343,36.5571657 83.0017829,38.6106514 C85.2935314,40.66432 87.0345143,43.0453029 88.2249143,45.7536 C88.4928,46.3488 88.6268343,46.9589943 88.6268343,47.584 C88.6268343,48.8042057 88.1802971,49.8607543 87.2875886,50.7536457 C86.42432,51.6167314 85.3827657,52.0482743 84.16256,52.0482743 C83.3589029,52.0482743 82.5554286,51.7952 81.7517714,51.2892343 C80.9482971,50.7536457 80.38272,50.1136457 80.0554057,49.3696 C78.9244343,46.8101486 77.0346057,44.7714743 74.3857371,43.2535771 C71.7666743,41.73568 68.73088,40.9768229 65.2785371,40.9768229 C63.5821714,40.9768229 61.7815771,41.1850971 59.8767543,41.6018286 C58.0017371,42.0183771 56.2309486,42.6583771 54.5642057,43.5214629 C52.8976457,44.3547429 51.5434057,45.4112914 50.5018514,46.6911086 C49.4601143,47.9707429 48.9393371,49.4590171 48.9393371,51.1553829 C48.9393371,52.5244343 49.3856914,53.7148343 50.2785829,54.7267657 C51.1714743,55.7088914 52.1684114,56.4827429 53.2695771,57.0481371 C55.8590171,58.3279543 58.71616,59.2208457 61.8410057,59.7268114 C64.99584,60.2327771 68.1802971,60.7237486 71.39456,61.2000914 C74.6090057,61.6762514 77.6,62.5095314 80.3679086,63.6999314 C82.1238857,64.4439771 83.80544,65.4709029 85.4125714,66.7803429 C87.0197029,68.0899657 88.3291429,69.6821029 89.3410743,71.55712 C90.3530057,73.4321371 90.8589714,75.6344686 90.8589714,78.1642971 C90.8589714,81.4381714 90.0703086,84.3101257 88.4928,86.7803429 C86.9154743,89.25056 84.8469943,91.3042286 82.2875429,92.9409829 C79.7279086,94.57792 76.9451886,95.81312 73.9392,96.6464 C70.9630171,97.4500571 68.0464457,97.8517943 65.1893029,97.8517943 C61.3202286,97.8517943 57.6892343,97.2416 54.2965029,96.0213943 C50.9035886,94.7713829 47.9422171,93.0154057 45.4125714,90.7536457 C42.8827429,88.4618971 40.9482971,85.7834057 39.6088686,82.7178057 C39.3409829,82.1226057 39.2071314,81.5274057 39.2071314,80.9322057 C39.2071314,79.7118171 39.6386743,78.6702629 40.50176,77.8071771 C41.3946514,76.9142857 42.4512,76.4679314 43.6714057,76.4679314 C44.5046857,76.4679314 45.3231543,76.7356343 46.1268114,77.2714057 C46.9302857,77.7773714 47.4808686,78.4171886 47.77856,79.19104 C49.0881829,82.1970286 51.3053257,84.5780114 54.4303543,86.3339886 C57.5553829,88.06016 61.1415771,88.9232457 65.1893029,88.9232457 C67.7191314,88.9232457 70.2637714,88.5511314 72.8232229,87.8070857 C75.3826743,87.0334171 77.5255771,85.8578286 79.2517486,84.28032 C81.0077257,82.6731886 81.8856229,80.6345143 81.8856229,78.1642971 C81.8856229,76.5869714 81.3500343,75.2923429 80.2784914,74.2804114 C79.2369371,73.2386743 78.0911543,72.4500114 76.8411429,71.91424 C74.0136229,70.6940343 70.9928229,69.8607543 67.77856,69.4142171 C64.5642971,68.9380571 61.3648457,68.4470857 58.1803886,67.94112 C54.9957486,67.4053486 52.0195657,66.4380343 49.25184,65.03936 C46.9006629,63.8487771 44.75776,62.1226057 42.8231314,59.8606629 C40.9184914,57.5690971 39.9659886,54.6673371 39.9659886,51.1553829 C39.9659886,48.06016 40.7100343,45.3368686 42.1981257,42.9856914 C43.7160229,40.6047086 45.71008,38.6106514 48.1802971,37.00352 C50.68032,35.3665829 53.4184229,34.1315657 56.3946057,33.2982857 C59.3707886,32.4648229 62.33216,32.0482743 65.2785371,32.0482743 Z" id="路径"></path>
|
||||
<path d="M64,0 C99.3462239,0 128,28.6537761 128,64 C128,99.3462239 99.3462239,128 64,128 C28.6537761,128 0,99.3462239 0,64 C0,28.6537761 28.6537761,0 64,0 Z M64,10.9714286 C34.7131288,10.9714286 10.9714286,34.7131288 10.9714286,64 C10.9714286,93.2868712 34.7131288,117.028571 64,117.028571 C93.2868712,117.028571 117.028571,93.2868712 117.028571,64 C117.028571,34.7131288 93.2868712,10.9714286 64,10.9714286 Z" id="椭圆形"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.1 KiB |
23
api/core/tools/provider/builtin/serper/serper.py
Normal file
@ -0,0 +1,23 @@
|
||||
from typing import Any
|
||||
|
||||
from core.tools.errors import ToolProviderCredentialValidationError
|
||||
from core.tools.provider.builtin.serper.tools.serper_search import SerperSearchTool
|
||||
from core.tools.provider.builtin_tool_provider import BuiltinToolProviderController
|
||||
|
||||
|
||||
class SerperProvider(BuiltinToolProviderController):
|
||||
def _validate_credentials(self, credentials: dict[str, Any]) -> None:
|
||||
try:
|
||||
SerperSearchTool().fork_tool_runtime(
|
||||
runtime={
|
||||
"credentials": credentials,
|
||||
}
|
||||
).invoke(
|
||||
user_id='',
|
||||
tool_parameters={
|
||||
"query": "test",
|
||||
"result_type": "link"
|
||||
},
|
||||
)
|
||||
except Exception as e:
|
||||
raise ToolProviderCredentialValidationError(str(e))
|
||||
31
api/core/tools/provider/builtin/serper/serper.yaml
Normal file
@ -0,0 +1,31 @@
|
||||
identity:
|
||||
author: zhuhao
|
||||
name: serper
|
||||
label:
|
||||
en_US: Serper
|
||||
zh_Hans: Serper
|
||||
pt_BR: Serper
|
||||
description:
|
||||
en_US: Serper is a powerful real-time search engine tool API that provides structured data from Google Search.
|
||||
zh_Hans: Serper 是一个强大的实时搜索引擎工具API,可提供来自 Google 搜索引擎搜索的结构化数据。
|
||||
pt_BR: Serper is a powerful real-time search engine tool API that provides structured data from Google Search.
|
||||
icon: icon.svg
|
||||
tags:
|
||||
- search
|
||||
credentials_for_provider:
|
||||
serperapi_api_key:
|
||||
type: secret-input
|
||||
required: true
|
||||
label:
|
||||
en_US: Serper API key
|
||||
zh_Hans: Serper API key
|
||||
pt_BR: Serper API key
|
||||
placeholder:
|
||||
en_US: Please input your Serper API key
|
||||
zh_Hans: 请输入你的 Serper API key
|
||||
pt_BR: Please input your Serper API key
|
||||
help:
|
||||
en_US: Get your Serper API key from Serper
|
||||
zh_Hans: 从 Serper 获取您的 Serper API key
|
||||
pt_BR: Get your Serper API key from Serper
|
||||
url: https://serper.dev/api-key
|
||||
@ -0,0 +1,44 @@
|
||||
from typing import Any, Union
|
||||
|
||||
import requests
|
||||
|
||||
from core.tools.entities.tool_entities import ToolInvokeMessage
|
||||
from core.tools.tool.builtin_tool import BuiltinTool
|
||||
|
||||
SERPER_API_URL = "https://google.serper.dev/search"
|
||||
|
||||
|
||||
class SerperSearchTool(BuiltinTool):
|
||||
|
||||
def _parse_response(self, response: dict) -> dict:
|
||||
result = {}
|
||||
if "knowledgeGraph" in response:
|
||||
result["title"] = response["knowledgeGraph"].get("title", "")
|
||||
result["description"] = response["knowledgeGraph"].get("description", "")
|
||||
if "organic" in response:
|
||||
result["organic"] = [
|
||||
{
|
||||
"title": item.get("title", ""),
|
||||
"link": item.get("link", ""),
|
||||
"snippet": item.get("snippet", "")
|
||||
}
|
||||
for item in response["organic"]
|
||||
]
|
||||
return result
|
||||
def _invoke(self,
|
||||
user_id: str,
|
||||
tool_parameters: dict[str, Any],
|
||||
) -> Union[ToolInvokeMessage, list[ToolInvokeMessage]]:
|
||||
params = {
|
||||
"q": tool_parameters['query'],
|
||||
"gl": "us",
|
||||
"hl": "en"
|
||||
}
|
||||
headers = {
|
||||
'X-API-KEY': self.runtime.credentials['serperapi_api_key'],
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
response = requests.get(url=SERPER_API_URL, params=params,headers=headers)
|
||||
response.raise_for_status()
|
||||
valuable_res = self._parse_response(response.json())
|
||||
return self.create_json_message(valuable_res)
|
||||
@ -0,0 +1,27 @@
|
||||
identity:
|
||||
name: serper
|
||||
author: zhuhao
|
||||
label:
|
||||
en_US: Serper
|
||||
zh_Hans: Serper
|
||||
pt_BR: Serper
|
||||
description:
|
||||
human:
|
||||
en_US: A tool for performing a Google search and extracting snippets and webpages.Input should be a search query.
|
||||
zh_Hans: 一个用于执行 Google 搜索并提取片段和网页的工具。输入应该是一个搜索查询。
|
||||
pt_BR: A tool for performing a Google search and extracting snippets and webpages.Input should be a search query.
|
||||
llm: A tool for performing a Google search and extracting snippets and webpages.Input should be a search query.
|
||||
parameters:
|
||||
- name: query
|
||||
type: string
|
||||
required: true
|
||||
label:
|
||||
en_US: Query string
|
||||
zh_Hans: 查询语句
|
||||
pt_BR: Query string
|
||||
human_description:
|
||||
en_US: used for searching
|
||||
zh_Hans: 用于搜索网页内容
|
||||
pt_BR: used for searching
|
||||
llm_description: key words for searching
|
||||
form: llm
|
||||
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="128" height="128" viewBox="0 0 128 128"><g><g style="opacity:0;"><rect x="0" y="0" width="128" height="128" rx="0" fill="#FFFFFF" fill-opacity="1"/></g><g><path d="M100.74,12L93.2335,12C69.21260000000001,12,55.3672,27.3468,55.3672,50.8672L55.3672,54.8988C52.6011,54.1056,49.7377,53.7031,46.8601,53.7031C29.816499999999998,53.7031,16,67.5196,16,84.5632C16,101.6069,29.816499999999998,115.423,46.8601,115.423C63.9037,115.423,77.72030000000001,101.6069,77.72030000000001,84.5632C77.72030000000001,82.4902,77.51140000000001,80.4223,77.0967,78.3911L77.2197,78.3911L100.74,78.3911C106.9654,78.3681,112,73.3151,112,67.08959999999999C112,60.8642,106.9654,55.8111,100.74,55.7882L100.7362,55.7882L100.6985,55.7879L100.6606,55.7882L77.2197,55.7882L77.2195,49.8663C77.2195,40.8584,83.7252,34.352900000000005,93.2335,34.352900000000005L100.5653,34.352900000000005L100.5733,34.352900000000005L100.5812,34.352900000000005L100.74,34.352900000000005L100.74,34.352900000000005C106.8469,34.2605,111.7497,29.284,111.7497,23.1764C111.7497,17.06889,106.8469,12.0923454,100.74,12L100.74,12ZM56.0347,84.5632C56.0347,79.4962,51.9271,75.3885,46.8601,75.3885C41.793099999999995,75.3885,37.6854,79.4962,37.6854,84.5632C37.6854,89.6303,41.793099999999995,93.7378,46.8601,93.7378C51.9271,93.7378,56.0347,89.6303,56.0347,84.5632Z" fill-rule="evenodd" fill="#8358F6" fill-opacity="1"/></g></g></svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
19
api/core/tools/provider/builtin/siliconflow/siliconflow.py
Normal file
@ -0,0 +1,19 @@
|
||||
import requests
|
||||
|
||||
from core.tools.errors import ToolProviderCredentialValidationError
|
||||
from core.tools.provider.builtin_tool_provider import BuiltinToolProviderController
|
||||
|
||||
|
||||
class SiliconflowProvider(BuiltinToolProviderController):
|
||||
def _validate_credentials(self, credentials: dict) -> None:
|
||||
url = "https://api.siliconflow.cn/v1/models"
|
||||
headers = {
|
||||
"accept": "application/json",
|
||||
"authorization": f"Bearer {credentials.get('siliconFlow_api_key')}",
|
||||
}
|
||||
|
||||
response = requests.get(url, headers=headers)
|
||||
if response.status_code != 200:
|
||||
raise ToolProviderCredentialValidationError(
|
||||
"SiliconFlow API key is invalid"
|
||||
)
|
||||
21
api/core/tools/provider/builtin/siliconflow/siliconflow.yaml
Normal file
@ -0,0 +1,21 @@
|
||||
identity:
|
||||
author: hjlarry
|
||||
name: siliconflow
|
||||
label:
|
||||
en_US: SiliconFlow
|
||||
zh_CN: 硅基流动
|
||||
description:
|
||||
en_US: The image generation API provided by SiliconFlow includes Flux and Stable Diffusion models.
|
||||
zh_CN: 硅基流动提供的图片生成 API,包含 Flux 和 Stable Diffusion 模型。
|
||||
icon: icon.svg
|
||||
tags:
|
||||
- image
|
||||
credentials_for_provider:
|
||||
siliconFlow_api_key:
|
||||
type: secret-input
|
||||
required: true
|
||||
label:
|
||||
en_US: SiliconFlow API Key
|
||||
placeholder:
|
||||
en_US: Please input your SiliconFlow API key
|
||||
url: https://cloud.siliconflow.cn/account/ak
|
||||
44
api/core/tools/provider/builtin/siliconflow/tools/flux.py
Normal file
@ -0,0 +1,44 @@
|
||||
from typing import Any, Union
|
||||
|
||||
import requests
|
||||
|
||||
from core.tools.entities.tool_entities import ToolInvokeMessage
|
||||
from core.tools.tool.builtin_tool import BuiltinTool
|
||||
|
||||
FLUX_URL = (
|
||||
"https://api.siliconflow.cn/v1/black-forest-labs/FLUX.1-schnell/text-to-image"
|
||||
)
|
||||
|
||||
|
||||
class FluxTool(BuiltinTool):
|
||||
|
||||
def _invoke(
|
||||
self, user_id: str, tool_parameters: dict[str, Any]
|
||||
) -> Union[ToolInvokeMessage, list[ToolInvokeMessage]]:
|
||||
|
||||
headers = {
|
||||
"accept": "application/json",
|
||||
"content-type": "application/json",
|
||||
"authorization": f"Bearer {self.runtime.credentials['siliconFlow_api_key']}",
|
||||
}
|
||||
|
||||
payload = {
|
||||
"prompt": tool_parameters.get("prompt"),
|
||||
"image_size": tool_parameters.get("image_size", "1024x1024"),
|
||||
"seed": tool_parameters.get("seed"),
|
||||
"num_inference_steps": tool_parameters.get("num_inference_steps", 20),
|
||||
}
|
||||
|
||||
response = requests.post(FLUX_URL, json=payload, headers=headers)
|
||||
if response.status_code != 200:
|
||||
return self.create_text_message(f"Got Error Response:{response.text}")
|
||||
|
||||
res = response.json()
|
||||
result = [self.create_json_message(res)]
|
||||
for image in res.get("images", []):
|
||||
result.append(
|
||||
self.create_image_message(
|
||||
image=image.get("url"), save_as=self.VARIABLE_KEY.IMAGE.value
|
||||
)
|
||||
)
|
||||
return result
|
||||
73
api/core/tools/provider/builtin/siliconflow/tools/flux.yaml
Normal file
@ -0,0 +1,73 @@
|
||||
identity:
|
||||
name: flux
|
||||
author: hjlarry
|
||||
label:
|
||||
en_US: Flux
|
||||
icon: icon.svg
|
||||
description:
|
||||
human:
|
||||
en_US: Generate image via SiliconFlow's flux schnell.
|
||||
llm: This tool is used to generate image from prompt via SiliconFlow's flux schnell model.
|
||||
parameters:
|
||||
- name: prompt
|
||||
type: string
|
||||
required: true
|
||||
label:
|
||||
en_US: prompt
|
||||
zh_Hans: 提示词
|
||||
human_description:
|
||||
en_US: The text prompt used to generate the image.
|
||||
zh_Hans: 用于生成图片的文字提示词
|
||||
llm_description: this prompt text will be used to generate image.
|
||||
form: llm
|
||||
- name: image_size
|
||||
type: select
|
||||
required: true
|
||||
options:
|
||||
- value: 1024x1024
|
||||
label:
|
||||
en_US: 1024x1024
|
||||
- value: 768x1024
|
||||
label:
|
||||
en_US: 768x1024
|
||||
- value: 576x1024
|
||||
label:
|
||||
en_US: 576x1024
|
||||
- value: 512x1024
|
||||
label:
|
||||
en_US: 512x1024
|
||||
- value: 1024x576
|
||||
label:
|
||||
en_US: 1024x576
|
||||
- value: 768x512
|
||||
label:
|
||||
en_US: 768x512
|
||||
default: 1024x1024
|
||||
label:
|
||||
en_US: Choose Image Size
|
||||
zh_Hans: 选择生成的图片大小
|
||||
form: form
|
||||
- name: num_inference_steps
|
||||
type: number
|
||||
required: true
|
||||
default: 20
|
||||
min: 1
|
||||
max: 100
|
||||
label:
|
||||
en_US: Num Inference Steps
|
||||
zh_Hans: 生成图片的步数
|
||||
form: form
|
||||
human_description:
|
||||
en_US: The number of inference steps to perform. More steps produce higher quality but take longer.
|
||||
zh_Hans: 执行的推理步骤数量。更多的步骤可以产生更高质量的结果,但需要更长的时间。
|
||||
- name: seed
|
||||
type: number
|
||||
min: 0
|
||||
max: 9999999999
|
||||
label:
|
||||
en_US: Seed
|
||||
zh_Hans: 种子
|
||||
human_description:
|
||||
en_US: The same seed and prompt can produce similar images.
|
||||
zh_Hans: 相同的种子和提示可以产生相似的图像。
|
||||
form: form
|
||||
@ -0,0 +1,51 @@
|
||||
from typing import Any, Union
|
||||
|
||||
import requests
|
||||
|
||||
from core.tools.entities.tool_entities import ToolInvokeMessage
|
||||
from core.tools.tool.builtin_tool import BuiltinTool
|
||||
|
||||
SDURL = {
|
||||
"sd_3": "https://api.siliconflow.cn/v1/stabilityai/stable-diffusion-3-medium/text-to-image",
|
||||
"sd_xl": "https://api.siliconflow.cn/v1/stabilityai/stable-diffusion-xl-base-1.0/text-to-image",
|
||||
}
|
||||
|
||||
|
||||
class StableDiffusionTool(BuiltinTool):
|
||||
|
||||
def _invoke(
|
||||
self, user_id: str, tool_parameters: dict[str, Any]
|
||||
) -> Union[ToolInvokeMessage, list[ToolInvokeMessage]]:
|
||||
|
||||
headers = {
|
||||
"accept": "application/json",
|
||||
"content-type": "application/json",
|
||||
"authorization": f"Bearer {self.runtime.credentials['siliconFlow_api_key']}",
|
||||
}
|
||||
|
||||
model = tool_parameters.get("model", "sd_3")
|
||||
url = SDURL.get(model)
|
||||
|
||||
payload = {
|
||||
"prompt": tool_parameters.get("prompt"),
|
||||
"negative_prompt": tool_parameters.get("negative_prompt", ""),
|
||||
"image_size": tool_parameters.get("image_size", "1024x1024"),
|
||||
"batch_size": tool_parameters.get("batch_size", 1),
|
||||
"seed": tool_parameters.get("seed"),
|
||||
"guidance_scale": tool_parameters.get("guidance_scale", 7.5),
|
||||
"num_inference_steps": tool_parameters.get("num_inference_steps", 20),
|
||||
}
|
||||
|
||||
response = requests.post(url, json=payload, headers=headers)
|
||||
if response.status_code != 200:
|
||||
return self.create_text_message(f"Got Error Response:{response.text}")
|
||||
|
||||
res = response.json()
|
||||
result = [self.create_json_message(res)]
|
||||
for image in res.get("images", []):
|
||||
result.append(
|
||||
self.create_image_message(
|
||||
image=image.get("url"), save_as=self.VARIABLE_KEY.IMAGE.value
|
||||
)
|
||||
)
|
||||
return result
|
||||
@ -0,0 +1,121 @@
|
||||
identity:
|
||||
name: stable_diffusion
|
||||
author: hjlarry
|
||||
label:
|
||||
en_US: Stable Diffusion
|
||||
icon: icon.svg
|
||||
description:
|
||||
human:
|
||||
en_US: Generate image via SiliconFlow's stable diffusion model.
|
||||
llm: This tool is used to generate image from prompt via SiliconFlow's stable diffusion model.
|
||||
parameters:
|
||||
- name: prompt
|
||||
type: string
|
||||
required: true
|
||||
label:
|
||||
en_US: prompt
|
||||
zh_Hans: 提示词
|
||||
human_description:
|
||||
en_US: The text prompt used to generate the image.
|
||||
zh_Hans: 用于生成图片的文字提示词
|
||||
llm_description: this prompt text will be used to generate image.
|
||||
form: llm
|
||||
- name: negative_prompt
|
||||
type: string
|
||||
label:
|
||||
en_US: negative prompt
|
||||
zh_Hans: 负面提示词
|
||||
human_description:
|
||||
en_US: Describe what you don't want included in the image.
|
||||
zh_Hans: 描述您不希望包含在图片中的内容。
|
||||
llm_description: Describe what you don't want included in the image.
|
||||
form: llm
|
||||
- name: model
|
||||
type: select
|
||||
required: true
|
||||
options:
|
||||
- value: sd_3
|
||||
label:
|
||||
en_US: Stable Diffusion 3
|
||||
- value: sd_xl
|
||||
label:
|
||||
en_US: Stable Diffusion XL
|
||||
default: sd_3
|
||||
label:
|
||||
en_US: Choose Image Model
|
||||
zh_Hans: 选择生成图片的模型
|
||||
form: form
|
||||
- name: image_size
|
||||
type: select
|
||||
required: true
|
||||
options:
|
||||
- value: 1024x1024
|
||||
label:
|
||||
en_US: 1024x1024
|
||||
- value: 1024x2048
|
||||
label:
|
||||
en_US: 1024x2048
|
||||
- value: 1152x2048
|
||||
label:
|
||||
en_US: 1152x2048
|
||||
- value: 1536x1024
|
||||
label:
|
||||
en_US: 1536x1024
|
||||
- value: 1536x2048
|
||||
label:
|
||||
en_US: 1536x2048
|
||||
- value: 2048x1152
|
||||
label:
|
||||
en_US: 2048x1152
|
||||
default: 1024x1024
|
||||
label:
|
||||
en_US: Choose Image Size
|
||||
zh_Hans: 选择生成图片的大小
|
||||
form: form
|
||||
- name: batch_size
|
||||
type: number
|
||||
required: true
|
||||
default: 1
|
||||
min: 1
|
||||
max: 4
|
||||
label:
|
||||
en_US: Number Images
|
||||
zh_Hans: 生成图片的数量
|
||||
form: form
|
||||
- name: guidance_scale
|
||||
type: number
|
||||
required: true
|
||||
default: 7.5
|
||||
min: 0
|
||||
max: 100
|
||||
label:
|
||||
en_US: Guidance Scale
|
||||
zh_Hans: 与提示词紧密性
|
||||
human_description:
|
||||
en_US: Classifier Free Guidance. How close you want the model to stick to your prompt when looking for a related image to show you.
|
||||
zh_Hans: 无分类器引导。您希望模型在寻找相关图片向您展示时,与您的提示保持多紧密的关联度。
|
||||
form: form
|
||||
- name: num_inference_steps
|
||||
type: number
|
||||
required: true
|
||||
default: 20
|
||||
min: 1
|
||||
max: 100
|
||||
label:
|
||||
en_US: Num Inference Steps
|
||||
zh_Hans: 生成图片的步数
|
||||
human_description:
|
||||
en_US: The number of inference steps to perform. More steps produce higher quality but take longer.
|
||||
zh_Hans: 执行的推理步骤数量。更多的步骤可以产生更高质量的结果,但需要更长的时间。
|
||||
form: form
|
||||
- name: seed
|
||||
type: number
|
||||
min: 0
|
||||
max: 9999999999
|
||||
label:
|
||||
en_US: Seed
|
||||
zh_Hans: 种子
|
||||
human_description:
|
||||
en_US: The same seed and prompt can produce similar images.
|
||||
zh_Hans: 相同的种子和提示可以产生相似的图像。
|
||||
form: form
|
||||
@ -8,7 +8,13 @@ from core.tools.provider.builtin_tool_provider import BuiltinToolProviderControl
|
||||
class SpiderProvider(BuiltinToolProviderController):
|
||||
def _validate_credentials(self, credentials: dict[str, Any]) -> None:
|
||||
try:
|
||||
app = Spider(api_key=credentials["spider_api_key"])
|
||||
app.scrape_url(url="https://spider.cloud")
|
||||
app = Spider(api_key=credentials['spider_api_key'])
|
||||
app.scrape_url(url='https://spider.cloud')
|
||||
except AttributeError as e:
|
||||
# Handle cases where NoneType is not iterable, which might indicate API issues
|
||||
if 'NoneType' in str(e) and 'not iterable' in str(e):
|
||||
raise ToolProviderCredentialValidationError('API is currently down, try again in 15 minutes', str(e))
|
||||
else:
|
||||
raise ToolProviderCredentialValidationError('An unexpected error occurred.', str(e))
|
||||
except Exception as e:
|
||||
raise ToolProviderCredentialValidationError(str(e))
|
||||
raise ToolProviderCredentialValidationError('An unexpected error occurred.', str(e))
|
||||
|
||||
@ -27,7 +27,7 @@ DRAW_TEXT_OPTIONS = {
|
||||
"seed_resize_from_w": -1,
|
||||
|
||||
# Samplers
|
||||
# "sampler_name": "DPM++ 2M",
|
||||
"sampler_name": "DPM++ 2M",
|
||||
# "scheduler": "",
|
||||
# "sampler_index": "Automatic",
|
||||
|
||||
@ -178,6 +178,23 @@ class StableDiffusionTool(BuiltinTool):
|
||||
return [d['model_name'] for d in response.json()]
|
||||
except Exception as e:
|
||||
return []
|
||||
|
||||
def get_sample_methods(self) -> list[str]:
|
||||
"""
|
||||
get sample method
|
||||
"""
|
||||
try:
|
||||
base_url = self.runtime.credentials.get('base_url', None)
|
||||
if not base_url:
|
||||
return []
|
||||
api_url = str(URL(base_url) / 'sdapi' / 'v1' / 'samplers')
|
||||
response = get(url=api_url, timeout=(2, 10))
|
||||
if response.status_code != 200:
|
||||
return []
|
||||
else:
|
||||
return [d['name'] for d in response.json()]
|
||||
except Exception as e:
|
||||
return []
|
||||
|
||||
def img2img(self, base_url: str, tool_parameters: dict[str, Any]) \
|
||||
-> Union[ToolInvokeMessage, list[ToolInvokeMessage]]:
|
||||
@ -339,7 +356,27 @@ class StableDiffusionTool(BuiltinTool):
|
||||
label=I18nObject(en_US=i, zh_Hans=i)
|
||||
) for i in models])
|
||||
)
|
||||
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
sample_methods = self.get_sample_methods()
|
||||
if len(sample_methods) != 0:
|
||||
parameters.append(
|
||||
ToolParameter(name='sampler_name',
|
||||
label=I18nObject(en_US='Sampling method', zh_Hans='Sampling method'),
|
||||
human_description=I18nObject(
|
||||
en_US='Sampling method of Stable Diffusion, you can check the official documentation of Stable Diffusion',
|
||||
zh_Hans='Stable Diffusion 的Sampling method,您可以查看 Stable Diffusion 的官方文档',
|
||||
),
|
||||
type=ToolParameter.ToolParameterType.SELECT,
|
||||
form=ToolParameter.ToolParameterForm.FORM,
|
||||
llm_description='Sampling method of Stable Diffusion, you can check the official documentation of Stable Diffusion',
|
||||
required=True,
|
||||
default=sample_methods[0],
|
||||
options=[ToolParameterOption(
|
||||
value=i,
|
||||
label=I18nObject(en_US=i, zh_Hans=i)
|
||||
) for i in sample_methods])
|
||||
)
|
||||
return parameters
|
||||
|
||||
0
api/core/tools/provider/builtin/stepfun/__init__.py
Normal file
BIN
api/core/tools/provider/builtin/stepfun/_assets/icon.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |