Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

tool_entities.py 16KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511
  1. import base64
  2. import contextlib
  3. import enum
  4. from collections.abc import Mapping
  5. from enum import Enum
  6. from typing import Any, Optional, Union
  7. from pydantic import BaseModel, ConfigDict, Field, ValidationInfo, field_serializer, field_validator, model_validator
  8. from core.entities.provider_entities import ProviderConfig
  9. from core.plugin.entities.parameters import (
  10. MCPServerParameterType,
  11. PluginParameter,
  12. PluginParameterOption,
  13. PluginParameterType,
  14. as_normal_type,
  15. cast_parameter_value,
  16. init_frontend_parameter,
  17. )
  18. from core.rag.entities.citation_metadata import RetrievalSourceMetadata
  19. from core.tools.entities.common_entities import I18nObject
  20. from core.tools.entities.constants import TOOL_SELECTOR_MODEL_IDENTITY
  21. class ToolLabelEnum(Enum):
  22. SEARCH = "search"
  23. IMAGE = "image"
  24. VIDEOS = "videos"
  25. WEATHER = "weather"
  26. FINANCE = "finance"
  27. DESIGN = "design"
  28. TRAVEL = "travel"
  29. SOCIAL = "social"
  30. NEWS = "news"
  31. MEDICAL = "medical"
  32. PRODUCTIVITY = "productivity"
  33. EDUCATION = "education"
  34. BUSINESS = "business"
  35. ENTERTAINMENT = "entertainment"
  36. UTILITIES = "utilities"
  37. OTHER = "other"
  38. class ToolProviderType(enum.StrEnum):
  39. """
  40. Enum class for tool provider
  41. """
  42. PLUGIN = "plugin"
  43. BUILT_IN = "builtin"
  44. WORKFLOW = "workflow"
  45. API = "api"
  46. APP = "app"
  47. DATASET_RETRIEVAL = "dataset-retrieval"
  48. MCP = "mcp"
  49. @classmethod
  50. def value_of(cls, value: str) -> "ToolProviderType":
  51. """
  52. Get value of given mode.
  53. :param value: mode value
  54. :return: mode
  55. """
  56. for mode in cls:
  57. if mode.value == value:
  58. return mode
  59. raise ValueError(f"invalid mode value {value}")
  60. class ApiProviderSchemaType(Enum):
  61. """
  62. Enum class for api provider schema type.
  63. """
  64. OPENAPI = "openapi"
  65. SWAGGER = "swagger"
  66. OPENAI_PLUGIN = "openai_plugin"
  67. OPENAI_ACTIONS = "openai_actions"
  68. @classmethod
  69. def value_of(cls, value: str) -> "ApiProviderSchemaType":
  70. """
  71. Get value of given mode.
  72. :param value: mode value
  73. :return: mode
  74. """
  75. for mode in cls:
  76. if mode.value == value:
  77. return mode
  78. raise ValueError(f"invalid mode value {value}")
  79. class ApiProviderAuthType(Enum):
  80. """
  81. Enum class for api provider auth type.
  82. """
  83. NONE = "none"
  84. API_KEY_HEADER = "api_key_header"
  85. API_KEY_QUERY = "api_key_query"
  86. @classmethod
  87. def value_of(cls, value: str) -> "ApiProviderAuthType":
  88. """
  89. Get value of given mode.
  90. :param value: mode value
  91. :return: mode
  92. """
  93. # 'api_key' deprecated in PR #21656
  94. # normalize & tiny alias for backward compatibility
  95. v = (value or "").strip().lower()
  96. if v == "api_key":
  97. v = cls.API_KEY_HEADER.value
  98. for mode in cls:
  99. if mode.value == v:
  100. return mode
  101. valid = ", ".join(m.value for m in cls)
  102. raise ValueError(f"invalid mode value '{value}', expected one of: {valid}")
  103. class ToolInvokeMessage(BaseModel):
  104. class TextMessage(BaseModel):
  105. text: str
  106. class JsonMessage(BaseModel):
  107. json_object: dict
  108. class BlobMessage(BaseModel):
  109. blob: bytes
  110. class BlobChunkMessage(BaseModel):
  111. id: str = Field(..., description="The id of the blob")
  112. sequence: int = Field(..., description="The sequence of the chunk")
  113. total_length: int = Field(..., description="The total length of the blob")
  114. blob: bytes = Field(..., description="The blob data of the chunk")
  115. end: bool = Field(..., description="Whether the chunk is the last chunk")
  116. class FileMessage(BaseModel):
  117. pass
  118. class VariableMessage(BaseModel):
  119. variable_name: str = Field(..., description="The name of the variable")
  120. variable_value: Any = Field(..., description="The value of the variable")
  121. stream: bool = Field(default=False, description="Whether the variable is streamed")
  122. @model_validator(mode="before")
  123. @classmethod
  124. def transform_variable_value(cls, values) -> Any:
  125. """
  126. Only basic types and lists are allowed.
  127. """
  128. value = values.get("variable_value")
  129. if not isinstance(value, dict | list | str | int | float | bool):
  130. raise ValueError("Only basic types and lists are allowed.")
  131. # if stream is true, the value must be a string
  132. if values.get("stream"):
  133. if not isinstance(value, str):
  134. raise ValueError("When 'stream' is True, 'variable_value' must be a string.")
  135. return values
  136. @field_validator("variable_name", mode="before")
  137. @classmethod
  138. def transform_variable_name(cls, value: str) -> str:
  139. """
  140. The variable name must be a string.
  141. """
  142. if value in {"json", "text", "files"}:
  143. raise ValueError(f"The variable name '{value}' is reserved.")
  144. return value
  145. class LogMessage(BaseModel):
  146. class LogStatus(Enum):
  147. START = "start"
  148. ERROR = "error"
  149. SUCCESS = "success"
  150. id: str
  151. label: str = Field(..., description="The label of the log")
  152. parent_id: Optional[str] = Field(default=None, description="Leave empty for root log")
  153. error: Optional[str] = Field(default=None, description="The error message")
  154. status: LogStatus = Field(..., description="The status of the log")
  155. data: Mapping[str, Any] = Field(..., description="Detailed log data")
  156. metadata: Optional[Mapping[str, Any]] = Field(default=None, description="The metadata of the log")
  157. class RetrieverResourceMessage(BaseModel):
  158. retriever_resources: list[RetrievalSourceMetadata] = Field(..., description="retriever resources")
  159. context: str = Field(..., description="context")
  160. class MessageType(Enum):
  161. TEXT = "text"
  162. IMAGE = "image"
  163. LINK = "link"
  164. BLOB = "blob"
  165. JSON = "json"
  166. IMAGE_LINK = "image_link"
  167. BINARY_LINK = "binary_link"
  168. VARIABLE = "variable"
  169. FILE = "file"
  170. LOG = "log"
  171. BLOB_CHUNK = "blob_chunk"
  172. RETRIEVER_RESOURCES = "retriever_resources"
  173. type: MessageType = MessageType.TEXT
  174. """
  175. plain text, image url or link url
  176. """
  177. message: (
  178. JsonMessage
  179. | TextMessage
  180. | BlobChunkMessage
  181. | BlobMessage
  182. | LogMessage
  183. | FileMessage
  184. | None
  185. | VariableMessage
  186. | RetrieverResourceMessage
  187. )
  188. meta: dict[str, Any] | None = None
  189. @field_validator("message", mode="before")
  190. @classmethod
  191. def decode_blob_message(cls, v):
  192. if isinstance(v, dict) and "blob" in v:
  193. with contextlib.suppress(Exception):
  194. v["blob"] = base64.b64decode(v["blob"])
  195. return v
  196. @field_serializer("message")
  197. def serialize_message(self, v):
  198. if isinstance(v, self.BlobMessage):
  199. return {"blob": base64.b64encode(v.blob).decode("utf-8")}
  200. return v
  201. class ToolInvokeMessageBinary(BaseModel):
  202. mimetype: str = Field(..., description="The mimetype of the binary")
  203. url: str = Field(..., description="The url of the binary")
  204. file_var: Optional[dict[str, Any]] = None
  205. class ToolParameter(PluginParameter):
  206. """
  207. Overrides type
  208. """
  209. class ToolParameterType(enum.StrEnum):
  210. """
  211. removes TOOLS_SELECTOR from PluginParameterType
  212. """
  213. STRING = PluginParameterType.STRING.value
  214. NUMBER = PluginParameterType.NUMBER.value
  215. BOOLEAN = PluginParameterType.BOOLEAN.value
  216. SELECT = PluginParameterType.SELECT.value
  217. SECRET_INPUT = PluginParameterType.SECRET_INPUT.value
  218. FILE = PluginParameterType.FILE.value
  219. FILES = PluginParameterType.FILES.value
  220. APP_SELECTOR = PluginParameterType.APP_SELECTOR.value
  221. MODEL_SELECTOR = PluginParameterType.MODEL_SELECTOR.value
  222. ANY = PluginParameterType.ANY.value
  223. DYNAMIC_SELECT = PluginParameterType.DYNAMIC_SELECT.value
  224. # MCP object and array type parameters
  225. ARRAY = MCPServerParameterType.ARRAY.value
  226. OBJECT = MCPServerParameterType.OBJECT.value
  227. # deprecated, should not use.
  228. SYSTEM_FILES = PluginParameterType.SYSTEM_FILES.value
  229. def as_normal_type(self):
  230. return as_normal_type(self)
  231. def cast_value(self, value: Any):
  232. return cast_parameter_value(self, value)
  233. class ToolParameterForm(Enum):
  234. SCHEMA = "schema" # should be set while adding tool
  235. FORM = "form" # should be set before invoking tool
  236. LLM = "llm" # will be set by LLM
  237. type: ToolParameterType = Field(..., description="The type of the parameter")
  238. human_description: Optional[I18nObject] = Field(default=None, description="The description presented to the user")
  239. form: ToolParameterForm = Field(..., description="The form of the parameter, schema/form/llm")
  240. llm_description: Optional[str] = None
  241. # MCP object and array type parameters use this field to store the schema
  242. input_schema: Optional[dict] = None
  243. @classmethod
  244. def get_simple_instance(
  245. cls,
  246. name: str,
  247. llm_description: str,
  248. typ: ToolParameterType,
  249. required: bool,
  250. options: Optional[list[str]] = None,
  251. ) -> "ToolParameter":
  252. """
  253. get a simple tool parameter
  254. :param name: the name of the parameter
  255. :param llm_description: the description presented to the LLM
  256. :param typ: the type of the parameter
  257. :param required: if the parameter is required
  258. :param options: the options of the parameter
  259. """
  260. # convert options to ToolParameterOption
  261. if options:
  262. option_objs = [
  263. PluginParameterOption(value=option, label=I18nObject(en_US=option, zh_Hans=option))
  264. for option in options
  265. ]
  266. else:
  267. option_objs = []
  268. return cls(
  269. name=name,
  270. label=I18nObject(en_US="", zh_Hans=""),
  271. placeholder=None,
  272. human_description=I18nObject(en_US="", zh_Hans=""),
  273. type=typ,
  274. form=cls.ToolParameterForm.LLM,
  275. llm_description=llm_description,
  276. required=required,
  277. options=option_objs,
  278. )
  279. def init_frontend_parameter(self, value: Any):
  280. return init_frontend_parameter(self, self.type, value)
  281. class ToolProviderIdentity(BaseModel):
  282. author: str = Field(..., description="The author of the tool")
  283. name: str = Field(..., description="The name of the tool")
  284. description: I18nObject = Field(..., description="The description of the tool")
  285. icon: str = Field(..., description="The icon of the tool")
  286. icon_dark: Optional[str] = Field(default=None, description="The dark icon of the tool")
  287. label: I18nObject = Field(..., description="The label of the tool")
  288. tags: Optional[list[ToolLabelEnum]] = Field(
  289. default=[],
  290. description="The tags of the tool",
  291. )
  292. class ToolIdentity(BaseModel):
  293. author: str = Field(..., description="The author of the tool")
  294. name: str = Field(..., description="The name of the tool")
  295. label: I18nObject = Field(..., description="The label of the tool")
  296. provider: str = Field(..., description="The provider of the tool")
  297. icon: Optional[str] = None
  298. class ToolDescription(BaseModel):
  299. human: I18nObject = Field(..., description="The description presented to the user")
  300. llm: str = Field(..., description="The description presented to the LLM")
  301. class ToolEntity(BaseModel):
  302. identity: ToolIdentity
  303. parameters: list[ToolParameter] = Field(default_factory=list)
  304. description: Optional[ToolDescription] = None
  305. output_schema: Optional[dict] = None
  306. has_runtime_parameters: bool = Field(default=False, description="Whether the tool has runtime parameters")
  307. # pydantic configs
  308. model_config = ConfigDict(protected_namespaces=())
  309. @field_validator("parameters", mode="before")
  310. @classmethod
  311. def set_parameters(cls, v, validation_info: ValidationInfo) -> list[ToolParameter]:
  312. return v or []
  313. class OAuthSchema(BaseModel):
  314. client_schema: list[ProviderConfig] = Field(default_factory=list, description="The schema of the OAuth client")
  315. credentials_schema: list[ProviderConfig] = Field(
  316. default_factory=list, description="The schema of the OAuth credentials"
  317. )
  318. class ToolProviderEntity(BaseModel):
  319. identity: ToolProviderIdentity
  320. plugin_id: Optional[str] = None
  321. credentials_schema: list[ProviderConfig] = Field(default_factory=list)
  322. oauth_schema: Optional[OAuthSchema] = None
  323. class ToolProviderEntityWithPlugin(ToolProviderEntity):
  324. tools: list[ToolEntity] = Field(default_factory=list)
  325. class WorkflowToolParameterConfiguration(BaseModel):
  326. """
  327. Workflow tool configuration
  328. """
  329. name: str = Field(..., description="The name of the parameter")
  330. description: str = Field(..., description="The description of the parameter")
  331. form: ToolParameter.ToolParameterForm = Field(..., description="The form of the parameter")
  332. class ToolInvokeMeta(BaseModel):
  333. """
  334. Tool invoke meta
  335. """
  336. time_cost: float = Field(..., description="The time cost of the tool invoke")
  337. error: Optional[str] = None
  338. tool_config: Optional[dict] = None
  339. @classmethod
  340. def empty(cls) -> "ToolInvokeMeta":
  341. """
  342. Get an empty instance of ToolInvokeMeta
  343. """
  344. return cls(time_cost=0.0, error=None, tool_config={})
  345. @classmethod
  346. def error_instance(cls, error: str) -> "ToolInvokeMeta":
  347. """
  348. Get an instance of ToolInvokeMeta with error
  349. """
  350. return cls(time_cost=0.0, error=error, tool_config={})
  351. def to_dict(self) -> dict:
  352. return {
  353. "time_cost": self.time_cost,
  354. "error": self.error,
  355. "tool_config": self.tool_config,
  356. }
  357. class ToolLabel(BaseModel):
  358. """
  359. Tool label
  360. """
  361. name: str = Field(..., description="The name of the tool")
  362. label: I18nObject = Field(..., description="The label of the tool")
  363. icon: str = Field(..., description="The icon of the tool")
  364. class ToolInvokeFrom(Enum):
  365. """
  366. Enum class for tool invoke
  367. """
  368. WORKFLOW = "workflow"
  369. AGENT = "agent"
  370. PLUGIN = "plugin"
  371. class ToolSelector(BaseModel):
  372. dify_model_identity: str = TOOL_SELECTOR_MODEL_IDENTITY
  373. class Parameter(BaseModel):
  374. name: str = Field(..., description="The name of the parameter")
  375. type: ToolParameter.ToolParameterType = Field(..., description="The type of the parameter")
  376. required: bool = Field(..., description="Whether the parameter is required")
  377. description: str = Field(..., description="The description of the parameter")
  378. default: Optional[Union[int, float, str]] = None
  379. options: Optional[list[PluginParameterOption]] = None
  380. provider_id: str = Field(..., description="The id of the provider")
  381. credential_id: Optional[str] = Field(default=None, description="The id of the credential")
  382. tool_name: str = Field(..., description="The name of the tool")
  383. tool_description: str = Field(..., description="The description of the tool")
  384. tool_configuration: Mapping[str, Any] = Field(..., description="Configuration, type form")
  385. tool_parameters: Mapping[str, Parameter] = Field(..., description="Parameters, type llm")
  386. def to_plugin_parameter(self) -> dict[str, Any]:
  387. return self.model_dump()
  388. class CredentialType(enum.StrEnum):
  389. API_KEY = "api-key"
  390. OAUTH2 = "oauth2"
  391. def get_name(self):
  392. if self == CredentialType.API_KEY:
  393. return "API KEY"
  394. elif self == CredentialType.OAUTH2:
  395. return "AUTH"
  396. else:
  397. return self.value.replace("-", " ").upper()
  398. def is_editable(self):
  399. return self == CredentialType.API_KEY
  400. def is_validate_allowed(self):
  401. return self == CredentialType.API_KEY
  402. @classmethod
  403. def values(cls):
  404. return [item.value for item in cls]
  405. @classmethod
  406. def of(cls, credential_type: str) -> "CredentialType":
  407. type_name = credential_type.lower()
  408. if type_name == "api-key":
  409. return cls.API_KEY
  410. elif type_name == "oauth2":
  411. return cls.OAUTH2
  412. else:
  413. raise ValueError(f"Invalid credential type: {credential_type}")