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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480
  1. import json
  2. from datetime import datetime
  3. from typing import Any, cast
  4. from urllib.parse import urlparse
  5. import sqlalchemy as sa
  6. from deprecated import deprecated
  7. from sqlalchemy import ForeignKey, String, func
  8. from sqlalchemy.orm import Mapped, mapped_column
  9. from core.file import helpers as file_helpers
  10. from core.helper import encrypter
  11. from core.mcp.types import Tool
  12. from core.tools.entities.common_entities import I18nObject
  13. from core.tools.entities.tool_bundle import ApiToolBundle
  14. from core.tools.entities.tool_entities import ApiProviderSchemaType, WorkflowToolParameterConfiguration
  15. from models.base import Base
  16. from .engine import db
  17. from .model import Account, App, Tenant
  18. from .types import StringUUID
  19. # system level tool oauth client params (client_id, client_secret, etc.)
  20. class ToolOAuthSystemClient(Base):
  21. __tablename__ = "tool_oauth_system_clients"
  22. __table_args__ = (
  23. sa.PrimaryKeyConstraint("id", name="tool_oauth_system_client_pkey"),
  24. sa.UniqueConstraint("plugin_id", "provider", name="tool_oauth_system_client_plugin_id_provider_idx"),
  25. )
  26. id: Mapped[str] = mapped_column(StringUUID, server_default=sa.text("uuid_generate_v4()"))
  27. plugin_id = mapped_column(String(512), nullable=False)
  28. provider: Mapped[str] = mapped_column(String(255), nullable=False)
  29. # oauth params of the tool provider
  30. encrypted_oauth_params: Mapped[str] = mapped_column(sa.Text, nullable=False)
  31. # tenant level tool oauth client params (client_id, client_secret, etc.)
  32. class ToolOAuthTenantClient(Base):
  33. __tablename__ = "tool_oauth_tenant_clients"
  34. __table_args__ = (
  35. sa.PrimaryKeyConstraint("id", name="tool_oauth_tenant_client_pkey"),
  36. sa.UniqueConstraint("tenant_id", "plugin_id", "provider", name="unique_tool_oauth_tenant_client"),
  37. )
  38. id: Mapped[str] = mapped_column(StringUUID, server_default=sa.text("uuid_generate_v4()"))
  39. # tenant id
  40. tenant_id: Mapped[str] = mapped_column(StringUUID, nullable=False)
  41. plugin_id: Mapped[str] = mapped_column(String(512), nullable=False)
  42. provider: Mapped[str] = mapped_column(String(255), nullable=False)
  43. enabled: Mapped[bool] = mapped_column(sa.Boolean, nullable=False, server_default=sa.text("true"))
  44. # oauth params of the tool provider
  45. encrypted_oauth_params: Mapped[str] = mapped_column(sa.Text, nullable=False)
  46. @property
  47. def oauth_params(self) -> dict:
  48. return cast(dict, json.loads(self.encrypted_oauth_params or "{}"))
  49. class BuiltinToolProvider(Base):
  50. """
  51. This table stores the tool provider information for built-in tools for each tenant.
  52. """
  53. __tablename__ = "tool_builtin_providers"
  54. __table_args__ = (
  55. sa.PrimaryKeyConstraint("id", name="tool_builtin_provider_pkey"),
  56. sa.UniqueConstraint("tenant_id", "provider", "name", name="unique_builtin_tool_provider"),
  57. )
  58. # id of the tool provider
  59. id: Mapped[str] = mapped_column(StringUUID, server_default=sa.text("uuid_generate_v4()"))
  60. name: Mapped[str] = mapped_column(
  61. String(256), nullable=False, server_default=sa.text("'API KEY 1'::character varying")
  62. )
  63. # id of the tenant
  64. tenant_id: Mapped[str] = mapped_column(StringUUID, nullable=True)
  65. # who created this tool provider
  66. user_id: Mapped[str] = mapped_column(StringUUID, nullable=False)
  67. # name of the tool provider
  68. provider: Mapped[str] = mapped_column(String(256), nullable=False)
  69. # credential of the tool provider
  70. encrypted_credentials: Mapped[str] = mapped_column(sa.Text, nullable=True)
  71. created_at: Mapped[datetime] = mapped_column(
  72. sa.DateTime, nullable=False, server_default=sa.text("CURRENT_TIMESTAMP(0)")
  73. )
  74. updated_at: Mapped[datetime] = mapped_column(
  75. sa.DateTime, nullable=False, server_default=sa.text("CURRENT_TIMESTAMP(0)")
  76. )
  77. is_default: Mapped[bool] = mapped_column(sa.Boolean, nullable=False, server_default=sa.text("false"))
  78. # credential type, e.g., "api-key", "oauth2"
  79. credential_type: Mapped[str] = mapped_column(
  80. String(32), nullable=False, server_default=sa.text("'api-key'::character varying")
  81. )
  82. expires_at: Mapped[int] = mapped_column(sa.BigInteger, nullable=False, server_default=sa.text("-1"))
  83. @property
  84. def credentials(self) -> dict:
  85. return cast(dict, json.loads(self.encrypted_credentials))
  86. class ApiToolProvider(Base):
  87. """
  88. The table stores the api providers.
  89. """
  90. __tablename__ = "tool_api_providers"
  91. __table_args__ = (
  92. sa.PrimaryKeyConstraint("id", name="tool_api_provider_pkey"),
  93. sa.UniqueConstraint("name", "tenant_id", name="unique_api_tool_provider"),
  94. )
  95. id = mapped_column(StringUUID, server_default=sa.text("uuid_generate_v4()"))
  96. # name of the api provider
  97. name = mapped_column(String(255), nullable=False, server_default=sa.text("'API KEY 1'::character varying"))
  98. # icon
  99. icon: Mapped[str] = mapped_column(String(255), nullable=False)
  100. # original schema
  101. schema = mapped_column(sa.Text, nullable=False)
  102. schema_type_str: Mapped[str] = mapped_column(String(40), nullable=False)
  103. # who created this tool
  104. user_id = mapped_column(StringUUID, nullable=False)
  105. # tenant id
  106. tenant_id = mapped_column(StringUUID, nullable=False)
  107. # description of the provider
  108. description = mapped_column(sa.Text, nullable=False)
  109. # json format tools
  110. tools_str = mapped_column(sa.Text, nullable=False)
  111. # json format credentials
  112. credentials_str = mapped_column(sa.Text, nullable=False)
  113. # privacy policy
  114. privacy_policy = mapped_column(String(255), nullable=True)
  115. # custom_disclaimer
  116. custom_disclaimer: Mapped[str] = mapped_column(sa.TEXT, default="")
  117. created_at: Mapped[datetime] = mapped_column(sa.DateTime, nullable=False, server_default=func.current_timestamp())
  118. updated_at: Mapped[datetime] = mapped_column(sa.DateTime, nullable=False, server_default=func.current_timestamp())
  119. @property
  120. def schema_type(self) -> ApiProviderSchemaType:
  121. return ApiProviderSchemaType.value_of(self.schema_type_str)
  122. @property
  123. def tools(self) -> list[ApiToolBundle]:
  124. return [ApiToolBundle(**tool) for tool in json.loads(self.tools_str)]
  125. @property
  126. def credentials(self) -> dict:
  127. return dict(json.loads(self.credentials_str))
  128. @property
  129. def user(self) -> Account | None:
  130. if not self.user_id:
  131. return None
  132. return db.session.query(Account).where(Account.id == self.user_id).first()
  133. @property
  134. def tenant(self) -> Tenant | None:
  135. return db.session.query(Tenant).where(Tenant.id == self.tenant_id).first()
  136. class ToolLabelBinding(Base):
  137. """
  138. The table stores the labels for tools.
  139. """
  140. __tablename__ = "tool_label_bindings"
  141. __table_args__ = (
  142. sa.PrimaryKeyConstraint("id", name="tool_label_bind_pkey"),
  143. sa.UniqueConstraint("tool_id", "label_name", name="unique_tool_label_bind"),
  144. )
  145. id: Mapped[str] = mapped_column(StringUUID, server_default=sa.text("uuid_generate_v4()"))
  146. # tool id
  147. tool_id: Mapped[str] = mapped_column(String(64), nullable=False)
  148. # tool type
  149. tool_type: Mapped[str] = mapped_column(String(40), nullable=False)
  150. # label name
  151. label_name: Mapped[str] = mapped_column(String(40), nullable=False)
  152. class WorkflowToolProvider(Base):
  153. """
  154. The table stores the workflow providers.
  155. """
  156. __tablename__ = "tool_workflow_providers"
  157. __table_args__ = (
  158. sa.PrimaryKeyConstraint("id", name="tool_workflow_provider_pkey"),
  159. sa.UniqueConstraint("name", "tenant_id", name="unique_workflow_tool_provider"),
  160. sa.UniqueConstraint("tenant_id", "app_id", name="unique_workflow_tool_provider_app_id"),
  161. )
  162. id: Mapped[str] = mapped_column(StringUUID, server_default=sa.text("uuid_generate_v4()"))
  163. # name of the workflow provider
  164. name: Mapped[str] = mapped_column(String(255), nullable=False)
  165. # label of the workflow provider
  166. label: Mapped[str] = mapped_column(String(255), nullable=False, server_default="")
  167. # icon
  168. icon: Mapped[str] = mapped_column(String(255), nullable=False)
  169. # app id of the workflow provider
  170. app_id: Mapped[str] = mapped_column(StringUUID, nullable=False)
  171. # version of the workflow provider
  172. version: Mapped[str] = mapped_column(String(255), nullable=False, server_default="")
  173. # who created this tool
  174. user_id: Mapped[str] = mapped_column(StringUUID, nullable=False)
  175. # tenant id
  176. tenant_id: Mapped[str] = mapped_column(StringUUID, nullable=False)
  177. # description of the provider
  178. description: Mapped[str] = mapped_column(sa.Text, nullable=False)
  179. # parameter configuration
  180. parameter_configuration: Mapped[str] = mapped_column(sa.Text, nullable=False, server_default="[]")
  181. # privacy policy
  182. privacy_policy: Mapped[str] = mapped_column(String(255), nullable=True, server_default="")
  183. created_at: Mapped[datetime] = mapped_column(
  184. sa.DateTime, nullable=False, server_default=sa.text("CURRENT_TIMESTAMP(0)")
  185. )
  186. updated_at: Mapped[datetime] = mapped_column(
  187. sa.DateTime, nullable=False, server_default=sa.text("CURRENT_TIMESTAMP(0)")
  188. )
  189. @property
  190. def user(self) -> Account | None:
  191. return db.session.query(Account).where(Account.id == self.user_id).first()
  192. @property
  193. def tenant(self) -> Tenant | None:
  194. return db.session.query(Tenant).where(Tenant.id == self.tenant_id).first()
  195. @property
  196. def parameter_configurations(self) -> list[WorkflowToolParameterConfiguration]:
  197. return [WorkflowToolParameterConfiguration(**config) for config in json.loads(self.parameter_configuration)]
  198. @property
  199. def app(self) -> App | None:
  200. return db.session.query(App).where(App.id == self.app_id).first()
  201. class MCPToolProvider(Base):
  202. """
  203. The table stores the mcp providers.
  204. """
  205. __tablename__ = "tool_mcp_providers"
  206. __table_args__ = (
  207. sa.PrimaryKeyConstraint("id", name="tool_mcp_provider_pkey"),
  208. sa.UniqueConstraint("tenant_id", "server_url_hash", name="unique_mcp_provider_server_url"),
  209. sa.UniqueConstraint("tenant_id", "name", name="unique_mcp_provider_name"),
  210. sa.UniqueConstraint("tenant_id", "server_identifier", name="unique_mcp_provider_server_identifier"),
  211. )
  212. id: Mapped[str] = mapped_column(StringUUID, server_default=sa.text("uuid_generate_v4()"))
  213. # name of the mcp provider
  214. name: Mapped[str] = mapped_column(String(40), nullable=False)
  215. # server identifier of the mcp provider
  216. server_identifier: Mapped[str] = mapped_column(String(64), nullable=False)
  217. # encrypted url of the mcp provider
  218. server_url: Mapped[str] = mapped_column(sa.Text, nullable=False)
  219. # hash of server_url for uniqueness check
  220. server_url_hash: Mapped[str] = mapped_column(String(64), nullable=False)
  221. # icon of the mcp provider
  222. icon: Mapped[str] = mapped_column(String(255), nullable=True)
  223. # tenant id
  224. tenant_id: Mapped[str] = mapped_column(StringUUID, nullable=False)
  225. # who created this tool
  226. user_id: Mapped[str] = mapped_column(StringUUID, nullable=False)
  227. # encrypted credentials
  228. encrypted_credentials: Mapped[str] = mapped_column(sa.Text, nullable=True)
  229. # authed
  230. authed: Mapped[bool] = mapped_column(sa.Boolean, nullable=False, default=False)
  231. # tools
  232. tools: Mapped[str] = mapped_column(sa.Text, nullable=False, default="[]")
  233. created_at: Mapped[datetime] = mapped_column(
  234. sa.DateTime, nullable=False, server_default=sa.text("CURRENT_TIMESTAMP(0)")
  235. )
  236. updated_at: Mapped[datetime] = mapped_column(
  237. sa.DateTime, nullable=False, server_default=sa.text("CURRENT_TIMESTAMP(0)")
  238. )
  239. timeout: Mapped[float] = mapped_column(sa.Float, nullable=False, server_default=sa.text("30"))
  240. sse_read_timeout: Mapped[float] = mapped_column(sa.Float, nullable=False, server_default=sa.text("300"))
  241. def load_user(self) -> Account | None:
  242. return db.session.query(Account).where(Account.id == self.user_id).first()
  243. @property
  244. def tenant(self) -> Tenant | None:
  245. return db.session.query(Tenant).where(Tenant.id == self.tenant_id).first()
  246. @property
  247. def credentials(self) -> dict:
  248. try:
  249. return cast(dict, json.loads(self.encrypted_credentials)) or {}
  250. except Exception:
  251. return {}
  252. @property
  253. def mcp_tools(self) -> list[Tool]:
  254. return [Tool(**tool) for tool in json.loads(self.tools)]
  255. @property
  256. def provider_icon(self) -> dict[str, str] | str:
  257. try:
  258. return cast(dict[str, str], json.loads(self.icon))
  259. except json.JSONDecodeError:
  260. return file_helpers.get_signed_file_url(self.icon)
  261. @property
  262. def decrypted_server_url(self) -> str:
  263. return cast(str, encrypter.decrypt_token(self.tenant_id, self.server_url))
  264. @property
  265. def masked_server_url(self) -> str:
  266. def mask_url(url: str, mask_char: str = "*") -> str:
  267. """
  268. mask the url to a simple string
  269. """
  270. parsed = urlparse(url)
  271. base_url = f"{parsed.scheme}://{parsed.netloc}"
  272. if parsed.path and parsed.path != "/":
  273. return f"{base_url}/{mask_char * 6}"
  274. else:
  275. return base_url
  276. return mask_url(self.decrypted_server_url)
  277. @property
  278. def decrypted_credentials(self) -> dict:
  279. from core.helper.provider_cache import NoOpProviderCredentialCache
  280. from core.tools.mcp_tool.provider import MCPToolProviderController
  281. from core.tools.utils.encryption import create_provider_encrypter
  282. provider_controller = MCPToolProviderController._from_db(self)
  283. encrypter, _ = create_provider_encrypter(
  284. tenant_id=self.tenant_id,
  285. config=[x.to_basic_provider_config() for x in provider_controller.get_credentials_schema()],
  286. cache=NoOpProviderCredentialCache(),
  287. )
  288. return encrypter.decrypt(self.credentials) # type: ignore
  289. class ToolModelInvoke(Base):
  290. """
  291. store the invoke logs from tool invoke
  292. """
  293. __tablename__ = "tool_model_invokes"
  294. __table_args__ = (sa.PrimaryKeyConstraint("id", name="tool_model_invoke_pkey"),)
  295. id = mapped_column(StringUUID, server_default=sa.text("uuid_generate_v4()"))
  296. # who invoke this tool
  297. user_id = mapped_column(StringUUID, nullable=False)
  298. # tenant id
  299. tenant_id = mapped_column(StringUUID, nullable=False)
  300. # provider
  301. provider: Mapped[str] = mapped_column(String(255), nullable=False)
  302. # type
  303. tool_type = mapped_column(String(40), nullable=False)
  304. # tool name
  305. tool_name = mapped_column(String(128), nullable=False)
  306. # invoke parameters
  307. model_parameters = mapped_column(sa.Text, nullable=False)
  308. # prompt messages
  309. prompt_messages = mapped_column(sa.Text, nullable=False)
  310. # invoke response
  311. model_response = mapped_column(sa.Text, nullable=False)
  312. prompt_tokens: Mapped[int] = mapped_column(sa.Integer, nullable=False, server_default=sa.text("0"))
  313. answer_tokens: Mapped[int] = mapped_column(sa.Integer, nullable=False, server_default=sa.text("0"))
  314. answer_unit_price = mapped_column(sa.Numeric(10, 4), nullable=False)
  315. answer_price_unit = mapped_column(sa.Numeric(10, 7), nullable=False, server_default=sa.text("0.001"))
  316. provider_response_latency = mapped_column(sa.Float, nullable=False, server_default=sa.text("0"))
  317. total_price = mapped_column(sa.Numeric(10, 7))
  318. currency: Mapped[str] = mapped_column(String(255), nullable=False)
  319. created_at = mapped_column(sa.DateTime, nullable=False, server_default=func.current_timestamp())
  320. updated_at = mapped_column(sa.DateTime, nullable=False, server_default=func.current_timestamp())
  321. @deprecated
  322. class ToolConversationVariables(Base):
  323. """
  324. store the conversation variables from tool invoke
  325. """
  326. __tablename__ = "tool_conversation_variables"
  327. __table_args__ = (
  328. sa.PrimaryKeyConstraint("id", name="tool_conversation_variables_pkey"),
  329. # add index for user_id and conversation_id
  330. sa.Index("user_id_idx", "user_id"),
  331. sa.Index("conversation_id_idx", "conversation_id"),
  332. )
  333. id = mapped_column(StringUUID, server_default=sa.text("uuid_generate_v4()"))
  334. # conversation user id
  335. user_id = mapped_column(StringUUID, nullable=False)
  336. # tenant id
  337. tenant_id = mapped_column(StringUUID, nullable=False)
  338. # conversation id
  339. conversation_id = mapped_column(StringUUID, nullable=False)
  340. # variables pool
  341. variables_str = mapped_column(sa.Text, nullable=False)
  342. created_at = mapped_column(sa.DateTime, nullable=False, server_default=func.current_timestamp())
  343. updated_at = mapped_column(sa.DateTime, nullable=False, server_default=func.current_timestamp())
  344. @property
  345. def variables(self) -> Any:
  346. return json.loads(self.variables_str)
  347. class ToolFile(Base):
  348. """This table stores file metadata generated in workflows,
  349. not only files created by agent.
  350. """
  351. __tablename__ = "tool_files"
  352. __table_args__ = (
  353. sa.PrimaryKeyConstraint("id", name="tool_file_pkey"),
  354. sa.Index("tool_file_conversation_id_idx", "conversation_id"),
  355. )
  356. id: Mapped[str] = mapped_column(StringUUID, server_default=sa.text("uuid_generate_v4()"))
  357. # conversation user id
  358. user_id: Mapped[str] = mapped_column(StringUUID)
  359. # tenant id
  360. tenant_id: Mapped[str] = mapped_column(StringUUID)
  361. # conversation id
  362. conversation_id: Mapped[str] = mapped_column(StringUUID, nullable=True)
  363. # file key
  364. file_key: Mapped[str] = mapped_column(String(255), nullable=False)
  365. # mime type
  366. mimetype: Mapped[str] = mapped_column(String(255), nullable=False)
  367. # original url
  368. original_url: Mapped[str] = mapped_column(String(2048), nullable=True)
  369. # name
  370. name: Mapped[str] = mapped_column(default="")
  371. # size
  372. size: Mapped[int] = mapped_column(default=-1)
  373. @deprecated
  374. class DeprecatedPublishedAppTool(Base):
  375. """
  376. The table stores the apps published as a tool for each person.
  377. """
  378. __tablename__ = "tool_published_apps"
  379. __table_args__ = (
  380. sa.PrimaryKeyConstraint("id", name="published_app_tool_pkey"),
  381. sa.UniqueConstraint("app_id", "user_id", name="unique_published_app_tool"),
  382. )
  383. id = mapped_column(StringUUID, server_default=sa.text("uuid_generate_v4()"))
  384. # id of the app
  385. app_id = mapped_column(StringUUID, ForeignKey("apps.id"), nullable=False)
  386. user_id: Mapped[str] = mapped_column(StringUUID, nullable=False)
  387. # who published this tool
  388. description = mapped_column(sa.Text, nullable=False)
  389. # llm_description of the tool, for LLM
  390. llm_description = mapped_column(sa.Text, nullable=False)
  391. # query description, query will be seem as a parameter of the tool,
  392. # to describe this parameter to llm, we need this field
  393. query_description = mapped_column(sa.Text, nullable=False)
  394. # query name, the name of the query parameter
  395. query_name = mapped_column(String(40), nullable=False)
  396. # name of the tool provider
  397. tool_name = mapped_column(String(40), nullable=False)
  398. # author
  399. author = mapped_column(String(40), nullable=False)
  400. created_at = mapped_column(sa.DateTime, nullable=False, server_default=sa.text("CURRENT_TIMESTAMP(0)"))
  401. updated_at = mapped_column(sa.DateTime, nullable=False, server_default=sa.text("CURRENT_TIMESTAMP(0)"))
  402. @property
  403. def description_i18n(self) -> I18nObject:
  404. return I18nObject(**json.loads(self.description))