| model_type: ModelType | model_type: ModelType | ||||
| enabled: bool = True | enabled: bool = True | ||||
| load_balancing_configs: list[ModelLoadBalancingConfiguration] = [] | load_balancing_configs: list[ModelLoadBalancingConfiguration] = [] | ||||
| # pydantic configs | |||||
| model_config = ConfigDict(protected_namespaces=()) |
| icon: str | icon: str | ||||
| label: I18nObject # label | label: I18nObject # label | ||||
| type: ToolProviderType | type: ToolProviderType | ||||
| masked_credentials: dict = None | |||||
| original_credentials: dict = None | |||||
| masked_credentials: Optional[dict] = None | |||||
| original_credentials: Optional[dict] = None | |||||
| is_team_authorization: bool = False | is_team_authorization: bool = False | ||||
| allow_delete: bool = True | allow_delete: bool = True | ||||
| tools: list[UserTool] = None | tools: list[UserTool] = None |
| colors, styles = self.get_styles(user_id='__dify_system__') | colors, styles = self.get_styles(user_id='__dify_system__') | ||||
| except Exception as e: | except Exception as e: | ||||
| colors, styles = [ | colors, styles = [ | ||||
| {'id': -1, 'name': '__default__', 'en_name': '__default__'} | |||||
| {'id': '-1', 'name': '__default__', 'en_name': '__default__'} | |||||
| ], [ | ], [ | ||||
| {'id': -1, 'name': '__default__', 'en_name': '__default__'} | |||||
| {'id': '-1', 'name': '__default__', 'en_name': '__default__'} | |||||
| ] | ] | ||||
| return [ | return [ |
| en_US: New Zealand | en_US: New Zealand | ||||
| zh_Hans: 新西兰 | zh_Hans: 新西兰 | ||||
| pt_BR: New Zealand | pt_BR: New Zealand | ||||
| - value: NO | |||||
| - value: 'NO' | |||||
| label: | label: | ||||
| en_US: Norway | en_US: Norway | ||||
| zh_Hans: 挪威 | zh_Hans: 挪威 |
| <<<<<<< HEAD | |||||
| ======= | |||||
| from typing import Any, Optional | |||||
| from pydantic import BaseModel, Field | |||||
| from core.tools.entities.tool_entities import ToolInvokeMessage | |||||
| from core.tools.tool.builtin_tool import BuiltinTool | |||||
| class DuckDuckGoSearchAPIWrapper(BaseModel): | |||||
| """Wrapper for DuckDuckGo Search API. | |||||
| Free and does not require any setup. | |||||
| """ | |||||
| region: Optional[str] = "wt-wt" | |||||
| safesearch: str = "moderate" | |||||
| time: Optional[str] = "y" | |||||
| max_results: int = 5 | |||||
| def get_snippets(self, query: str) -> list[str]: | |||||
| """Run query through DuckDuckGo and return concatenated results.""" | |||||
| from duckduckgo_search import DDGS | |||||
| with DDGS() as ddgs: | |||||
| results = ddgs.text( | |||||
| query, | |||||
| region=self.region, | |||||
| safesearch=self.safesearch, | |||||
| timelimit=self.time, | |||||
| ) | |||||
| if results is None: | |||||
| return ["No good DuckDuckGo Search Result was found"] | |||||
| snippets = [] | |||||
| for i, res in enumerate(results, 1): | |||||
| if res is not None: | |||||
| snippets.append(res["body"]) | |||||
| if len(snippets) == self.max_results: | |||||
| break | |||||
| return snippets | |||||
| def run(self, query: str) -> str: | |||||
| snippets = self.get_snippets(query) | |||||
| return " ".join(snippets) | |||||
| def results( | |||||
| self, query: str, num_results: int, backend: str = "api" | |||||
| ) -> list[dict[str, str]]: | |||||
| """Run query through DuckDuckGo and return metadata. | |||||
| Args: | |||||
| query: The query to search for. | |||||
| num_results: The number of results to return. | |||||
| Returns: | |||||
| A list of dictionaries with the following keys: | |||||
| snippet - The description of the result. | |||||
| title - The title of the result. | |||||
| link - The link to the result. | |||||
| """ | |||||
| from duckduckgo_search import DDGS | |||||
| with DDGS() as ddgs: | |||||
| results = ddgs.text( | |||||
| query, | |||||
| region=self.region, | |||||
| safesearch=self.safesearch, | |||||
| timelimit=self.time, | |||||
| backend=backend, | |||||
| ) | |||||
| if results is None: | |||||
| return [{"Result": "No good DuckDuckGo Search Result was found"}] | |||||
| def to_metadata(result: dict) -> dict[str, str]: | |||||
| if backend == "news": | |||||
| return { | |||||
| "date": result["date"], | |||||
| "title": result["title"], | |||||
| "snippet": result["body"], | |||||
| "source": result["source"], | |||||
| "link": result["url"], | |||||
| } | |||||
| return { | |||||
| "snippet": result["body"], | |||||
| "title": result["title"], | |||||
| "link": result["href"], | |||||
| } | |||||
| formatted_results = [] | |||||
| for i, res in enumerate(results, 1): | |||||
| if res is not None: | |||||
| formatted_results.append(to_metadata(res)) | |||||
| if len(formatted_results) == num_results: | |||||
| break | |||||
| return formatted_results | |||||
| class DuckDuckGoSearchRun(BaseModel): | |||||
| """Tool that queries the DuckDuckGo search API.""" | |||||
| name: str = "duckduckgo_search" | |||||
| description: str = ( | |||||
| "A wrapper around DuckDuckGo Search. " | |||||
| "Useful for when you need to answer questions about current events. " | |||||
| "Input should be a search query." | |||||
| ) | |||||
| api_wrapper: DuckDuckGoSearchAPIWrapper = Field( | |||||
| default_factory=DuckDuckGoSearchAPIWrapper | |||||
| ) | |||||
| def _run( | |||||
| self, | |||||
| query: str, | |||||
| ) -> str: | |||||
| """Use the tool.""" | |||||
| return self.api_wrapper.run(query) | |||||
| class DuckDuckGoSearchResults(BaseModel): | |||||
| """Tool that queries the DuckDuckGo search API and gets back json.""" | |||||
| name: str = "DuckDuckGo Results JSON" | |||||
| description: str = ( | |||||
| "A wrapper around Duck Duck Go Search. " | |||||
| "Useful for when you need to answer questions about current events. " | |||||
| "Input should be a search query. Output is a JSON array of the query results" | |||||
| ) | |||||
| num_results: int = 4 | |||||
| api_wrapper: DuckDuckGoSearchAPIWrapper = Field( | |||||
| default_factory=DuckDuckGoSearchAPIWrapper | |||||
| ) | |||||
| backend: str = "api" | |||||
| def _run( | |||||
| self, | |||||
| query: str, | |||||
| ) -> str: | |||||
| """Use the tool.""" | |||||
| res = self.api_wrapper.results(query, self.num_results, backend=self.backend) | |||||
| res_strs = [", ".join([f"{k}: {v}" for k, v in d.items()]) for d in res] | |||||
| return ", ".join([f"[{rs}]" for rs in res_strs]) | |||||
| class DuckDuckGoInput(BaseModel): | |||||
| query: str = Field(..., description="Search query.") | |||||
| class DuckDuckGoSearchTool(BuiltinTool): | |||||
| """ | |||||
| Tool for performing a search using DuckDuckGo search engine. | |||||
| """ | |||||
| def _invoke(self, user_id: str, tool_parameters: dict[str, Any]) -> ToolInvokeMessage | list[ToolInvokeMessage]: | |||||
| """ | |||||
| Invoke the DuckDuckGo search tool. | |||||
| Args: | |||||
| user_id (str): The ID of the user invoking the tool. | |||||
| tool_parameters (dict[str, Any]): The parameters for the tool invocation. | |||||
| Returns: | |||||
| ToolInvokeMessage | list[ToolInvokeMessage]: The result of the tool invocation. | |||||
| """ | |||||
| query = tool_parameters.get('query', '') | |||||
| if not query: | |||||
| return self.create_text_message('Please input query') | |||||
| tool = DuckDuckGoSearchRun(args_schema=DuckDuckGoInput) | |||||
| result = tool._run(query) | |||||
| return self.create_text_message(self.summary(user_id=user_id, content=result)) | |||||
| >>>>>>> 4c2ba442b (missing type in DuckDuckGoSearchAPIWrapper) |
| zh_Hans: 如果启用,爬虫将仅返回页面的主要内容,不包括标题、导航、页脚等。 | zh_Hans: 如果启用,爬虫将仅返回页面的主要内容,不包括标题、导航、页脚等。 | ||||
| form: form | form: form | ||||
| options: | options: | ||||
| - value: true | |||||
| - value: 'true' | |||||
| label: | label: | ||||
| en_US: 'Yes' | en_US: 'Yes' | ||||
| zh_Hans: 是 | zh_Hans: 是 | ||||
| - value: false | |||||
| - value: 'false' | |||||
| label: | label: | ||||
| en_US: 'No' | en_US: 'No' | ||||
| zh_Hans: 否 | zh_Hans: 否 | ||||
| default: false | |||||
| default: 'false' |
| en_US: New Zealand | en_US: New Zealand | ||||
| zh_Hans: 新西兰 | zh_Hans: 新西兰 | ||||
| pt_BR: New Zealand | pt_BR: New Zealand | ||||
| - value: NO | |||||
| - value: 'NO' | |||||
| label: | label: | ||||
| en_US: Norway | en_US: Norway | ||||
| zh_Hans: 挪威 | zh_Hans: 挪威 |
| en_US: New Zealand | en_US: New Zealand | ||||
| zh_Hans: 新西兰 | zh_Hans: 新西兰 | ||||
| pt_BR: New Zealand | pt_BR: New Zealand | ||||
| - value: NO | |||||
| - value: 'NO' | |||||
| label: | label: | ||||
| en_US: Norway | en_US: Norway | ||||
| zh_Hans: 挪威 | zh_Hans: 挪威 | ||||
| required: false | required: false | ||||
| form: form | form: form | ||||
| options: | options: | ||||
| - value: true | |||||
| - value: 'true' | |||||
| label: | label: | ||||
| en_US: "true" | en_US: "true" | ||||
| zh_Hans: "true" | zh_Hans: "true" | ||||
| - value: false | |||||
| - value: 'false' | |||||
| label: | label: | ||||
| en_US: "false" | en_US: "false" | ||||
| zh_Hans: "false" | zh_Hans: "false" |
| en_US: New Zealand | en_US: New Zealand | ||||
| zh_Hans: 新西兰 | zh_Hans: 新西兰 | ||||
| pt_BR: New Zealand | pt_BR: New Zealand | ||||
| - value: NO | |||||
| - value: 'NO' | |||||
| label: | label: | ||||
| en_US: Norway | en_US: Norway | ||||
| zh_Hans: 挪威 | zh_Hans: 挪威 |
| zh_Hans: Stack Exchange | zh_Hans: Stack Exchange | ||||
| description: | description: | ||||
| en_US: Access questions and answers from the Stack Exchange and its sub-sites. | en_US: Access questions and answers from the Stack Exchange and its sub-sites. | ||||
| zh_Hans: 从Stack Exchange和其子论坛获取问题和答案。 | |||||
| zh_Hans: 从 Stack Exchange 和其子论坛获取问题和答案。 | |||||
| icon: icon.svg | icon: icon.svg | ||||
| tags: | tags: | ||||
| - search | - search |
| zh_Hans: 是否限制为只有已接受答案的问题。 | zh_Hans: 是否限制为只有已接受答案的问题。 | ||||
| form: form | form: form | ||||
| options: | options: | ||||
| - value: true | |||||
| - value: 'true' | |||||
| label: | label: | ||||
| en_US: 'Yes' | en_US: 'Yes' | ||||
| zh_Hans: 是 | zh_Hans: 是 | ||||
| - value: false | |||||
| - value: 'false' | |||||
| label: | label: | ||||
| en_US: 'No' | en_US: 'No' | ||||
| zh_Hans: 否 | zh_Hans: 否 | ||||
| default: true | |||||
| default: 'true' | |||||
| - name: pagesize | - name: pagesize | ||||
| type: number | type: number | ||||
| required: true | required: true |
| pt_BR: Include images in the search results | pt_BR: Include images in the search results | ||||
| form: form | form: form | ||||
| options: | options: | ||||
| - value: true | |||||
| - value: 'true' | |||||
| label: | label: | ||||
| en_US: 'Yes' | en_US: 'Yes' | ||||
| zh_Hans: 是 | zh_Hans: 是 | ||||
| pt_BR: 'Yes' | pt_BR: 'Yes' | ||||
| - value: false | |||||
| - value: 'false' | |||||
| label: | label: | ||||
| en_US: 'No' | en_US: 'No' | ||||
| zh_Hans: 否 | zh_Hans: 否 | ||||
| pt_BR: 'No' | pt_BR: 'No' | ||||
| default: false | |||||
| default: 'false' | |||||
| - name: include_answer | - name: include_answer | ||||
| type: boolean | type: boolean | ||||
| required: false | required: false | ||||
| pt_BR: Include answers in the search results | pt_BR: Include answers in the search results | ||||
| form: form | form: form | ||||
| options: | options: | ||||
| - value: true | |||||
| - value: 'true' | |||||
| label: | label: | ||||
| en_US: 'Yes' | en_US: 'Yes' | ||||
| zh_Hans: 是 | zh_Hans: 是 | ||||
| pt_BR: 'Yes' | pt_BR: 'Yes' | ||||
| - value: false | |||||
| - value: 'false' | |||||
| label: | label: | ||||
| en_US: 'No' | en_US: 'No' | ||||
| zh_Hans: 否 | zh_Hans: 否 | ||||
| pt_BR: 'No' | pt_BR: 'No' | ||||
| default: false | |||||
| default: 'false' | |||||
| - name: include_raw_content | - name: include_raw_content | ||||
| type: boolean | type: boolean | ||||
| required: false | required: false | ||||
| pt_BR: Include raw content in the search results | pt_BR: Include raw content in the search results | ||||
| form: form | form: form | ||||
| options: | options: | ||||
| - value: true | |||||
| - value: 'true' | |||||
| label: | label: | ||||
| en_US: 'Yes' | en_US: 'Yes' | ||||
| zh_Hans: 是 | zh_Hans: 是 | ||||
| pt_BR: 'Yes' | pt_BR: 'Yes' | ||||
| - value: false | |||||
| - value: 'false' | |||||
| label: | label: | ||||
| en_US: 'No' | en_US: 'No' | ||||
| zh_Hans: 否 | zh_Hans: 否 | ||||
| pt_BR: 'No' | pt_BR: 'No' | ||||
| default: false | |||||
| default: 'false' | |||||
| - name: max_results | - name: max_results | ||||
| type: number | type: number | ||||
| required: false | required: false |
| zh_Hans: 如果启用,爬虫将仅返回页面摘要内容。 | zh_Hans: 如果启用,爬虫将仅返回页面摘要内容。 | ||||
| form: form | form: form | ||||
| options: | options: | ||||
| - value: true | |||||
| - value: 'true' | |||||
| label: | label: | ||||
| en_US: 'Yes' | en_US: 'Yes' | ||||
| zh_Hans: 是 | zh_Hans: 是 | ||||
| - value: false | |||||
| - value: 'false' | |||||
| label: | label: | ||||
| en_US: 'No' | en_US: 'No' | ||||
| zh_Hans: 否 | zh_Hans: 否 | ||||
| default: false | |||||
| default: 'false' |
| class Tool(BaseModel, ABC): | class Tool(BaseModel, ABC): | ||||
| identity: ToolIdentity = None | |||||
| identity: Optional[ToolIdentity] = None | |||||
| parameters: Optional[list[ToolParameter]] = None | parameters: Optional[list[ToolParameter]] = None | ||||
| description: ToolDescription = None | |||||
| description: Optional[ToolDescription] = None | |||||
| is_team_authorization: bool = False | is_team_authorization: bool = False | ||||
| # pydantic configs | # pydantic configs | ||||
| if not self.runtime_parameters: | if not self.runtime_parameters: | ||||
| self.runtime_parameters = {} | self.runtime_parameters = {} | ||||
| tenant_id: str = None | |||||
| tool_id: str = None | |||||
| invoke_from: InvokeFrom = None | |||||
| tool_invoke_from: ToolInvokeFrom = None | |||||
| credentials: dict[str, Any] = None | |||||
| runtime_parameters: dict[str, Any] = None | |||||
| tenant_id: Optional[str] = None | |||||
| tool_id: Optional[str] = None | |||||
| invoke_from: Optional[InvokeFrom] = None | |||||
| tool_invoke_from: Optional[ToolInvokeFrom] = None | |||||
| credentials: Optional[dict[str, Any]] = None | |||||
| runtime_parameters: Optional[dict[str, Any]] = None | |||||
| runtime: Runtime = None | |||||
| variables: ToolRuntimeVariablePool = None | |||||
| runtime: Optional[Runtime] = None | |||||
| variables: Optional[ToolRuntimeVariablePool] = None | |||||
| def __init__(self, **data: Any): | def __init__(self, **data: Any): | ||||
| super().__init__(**data) | super().__init__(**data) |
| result: list[UserToolProvider] = [] | result: list[UserToolProvider] = [] | ||||
| for provider_controller in provider_controllers: | for provider_controller in provider_controllers: | ||||
| # convert provider controller to user provider | |||||
| user_builtin_provider = ToolTransformService.builtin_provider_to_user_provider( | |||||
| provider_controller=provider_controller, | |||||
| db_provider=find_provider(provider_controller.identity.name), | |||||
| decrypt_credentials=True | |||||
| ) | |||||
| # add icon | |||||
| ToolTransformService.repack_provider(user_builtin_provider) | |||||
| tools = provider_controller.get_tools() | |||||
| for tool in tools: | |||||
| user_builtin_provider.tools.append(ToolTransformService.tool_to_user_tool( | |||||
| tenant_id=tenant_id, | |||||
| tool=tool, | |||||
| credentials=user_builtin_provider.original_credentials, | |||||
| labels=ToolLabelManager.get_tool_labels(provider_controller) | |||||
| )) | |||||
| result.append(user_builtin_provider) | |||||
| try: | |||||
| # convert provider controller to user provider | |||||
| user_builtin_provider = ToolTransformService.builtin_provider_to_user_provider( | |||||
| provider_controller=provider_controller, | |||||
| db_provider=find_provider(provider_controller.identity.name), | |||||
| decrypt_credentials=True | |||||
| ) | |||||
| # add icon | |||||
| ToolTransformService.repack_provider(user_builtin_provider) | |||||
| tools = provider_controller.get_tools() | |||||
| for tool in tools: | |||||
| user_builtin_provider.tools.append(ToolTransformService.tool_to_user_tool( | |||||
| tenant_id=tenant_id, | |||||
| tool=tool, | |||||
| credentials=user_builtin_provider.original_credentials, | |||||
| labels=ToolLabelManager.get_tool_labels(provider_controller) | |||||
| )) | |||||
| result.append(user_builtin_provider) | |||||
| except Exception as e: | |||||
| raise e | |||||
| return BuiltinToolProviderSort.sort(result) | return BuiltinToolProviderSort.sort(result) | ||||
| from flask import current_app | from flask import current_app | ||||
| from core.model_runtime.entities.common_entities import I18nObject | |||||
| from core.tools.entities.api_entities import UserTool, UserToolProvider | from core.tools.entities.api_entities import UserTool, UserToolProvider | ||||
| from core.tools.entities.common_entities import I18nObject | |||||
| from core.tools.entities.tool_bundle import ApiToolBundle | from core.tools.entities.tool_bundle import ApiToolBundle | ||||
| from core.tools.entities.tool_entities import ( | from core.tools.entities.tool_entities import ( | ||||
| ApiProviderAuthType, | ApiProviderAuthType, | ||||
| description=I18nObject( | description=I18nObject( | ||||
| en_US=provider_controller.identity.description.en_US, | en_US=provider_controller.identity.description.en_US, | ||||
| zh_Hans=provider_controller.identity.description.zh_Hans, | zh_Hans=provider_controller.identity.description.zh_Hans, | ||||
| pt_BR=provider_controller.identity.description.pt_BR, | |||||
| ), | ), | ||||
| icon=provider_controller.identity.icon, | icon=provider_controller.identity.icon, | ||||
| label=I18nObject( | label=I18nObject( | ||||
| en_US=provider_controller.identity.label.en_US, | en_US=provider_controller.identity.label.en_US, | ||||
| zh_Hans=provider_controller.identity.label.zh_Hans, | zh_Hans=provider_controller.identity.label.zh_Hans, | ||||
| pt_BR=provider_controller.identity.label.pt_BR, | |||||
| ), | ), | ||||
| type=ToolProviderType.BUILT_IN, | type=ToolProviderType.BUILT_IN, | ||||
| masked_credentials={}, | masked_credentials={}, |