Merge main into feat/plugin

This commit is contained in:
Yeuoly
2024-08-29 13:09:13 +08:00
1405 changed files with 48109 additions and 23346 deletions

View File

@ -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
"""
```

View File

@ -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"

View File

@ -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

View File

@ -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

View File

@ -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

View 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))

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View 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

View 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))

View 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

View 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)

View File

@ -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

View 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]

View 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

View File

@ -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

View File

@ -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

View 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

View 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))

View 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

View 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)

View 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)

View 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

View 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)

View 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

View File

@ -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

View File

@ -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='',

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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))

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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: 在文档结尾添加内容

View File

@ -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

View File

@ -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))

View File

@ -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

View File

@ -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)

View File

@ -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: 消息接收者的 IDID 类型应与查询参数 receive_id_type 对应。
llm_description: 消息接收者的 IDID 类型应与查询参数 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

View File

@ -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)

View File

@ -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

View File

@ -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

View 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))

View 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

View 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

View File

@ -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

View 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

View File

@ -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

View 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())

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

View 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.')

View 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请填写此项

View File

@ -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}'
}
)

View File

@ -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

View File

@ -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}'
}
)

View File

@ -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

View 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

View 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))

View 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

View 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)}')

View File

@ -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

File diff suppressed because it is too large Load Diff

View 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

View File

@ -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:

View File

@ -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

View File

@ -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]

View File

@ -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

View 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

View 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))

View 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

View 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
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)

View File

@ -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

View File

@ -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

View 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"
)

View 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

View 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

View 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

View File

@ -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

View File

@ -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

View File

@ -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))

View File

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Some files were not shown because too many files have changed in this diff Show More