您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

tool_entities.py 16KB

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