Du kannst nicht mehr als 25 Themen auswählen Themen müssen mit entweder einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

model.py 80KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912
  1. import json
  2. import re
  3. import uuid
  4. from collections.abc import Mapping
  5. from datetime import datetime
  6. from enum import Enum, StrEnum
  7. from typing import TYPE_CHECKING, Any, Literal, Optional, cast
  8. import sqlalchemy as sa
  9. from flask import request
  10. from flask_login import UserMixin # type: ignore[import-untyped]
  11. from sqlalchemy import Float, Index, PrimaryKeyConstraint, String, exists, func, select, text
  12. from sqlalchemy.orm import Mapped, Session, mapped_column
  13. from configs import dify_config
  14. from constants import DEFAULT_FILE_NUMBER_LIMITS
  15. from core.file import FILE_MODEL_IDENTITY, File, FileTransferMethod, FileType
  16. from core.file import helpers as file_helpers
  17. from core.tools.signature import sign_tool_file
  18. from core.workflow.enums import WorkflowExecutionStatus
  19. from libs.helper import generate_string # type: ignore[import-not-found]
  20. from .account import Account, Tenant
  21. from .base import Base
  22. from .engine import db
  23. from .enums import CreatorUserRole
  24. from .provider_ids import GenericProviderID
  25. from .types import StringUUID
  26. if TYPE_CHECKING:
  27. from models.workflow import Workflow
  28. class DifySetup(Base):
  29. __tablename__ = "dify_setups"
  30. __table_args__ = (sa.PrimaryKeyConstraint("version", name="dify_setup_pkey"),)
  31. version: Mapped[str] = mapped_column(String(255), nullable=False)
  32. setup_at = mapped_column(sa.DateTime, nullable=False, server_default=func.current_timestamp())
  33. class AppMode(StrEnum):
  34. COMPLETION = "completion"
  35. WORKFLOW = "workflow"
  36. CHAT = "chat"
  37. ADVANCED_CHAT = "advanced-chat"
  38. AGENT_CHAT = "agent-chat"
  39. @classmethod
  40. def value_of(cls, value: str) -> "AppMode":
  41. """
  42. Get value of given mode.
  43. :param value: mode value
  44. :return: mode
  45. """
  46. for mode in cls:
  47. if mode.value == value:
  48. return mode
  49. raise ValueError(f"invalid mode value {value}")
  50. class IconType(Enum):
  51. IMAGE = "image"
  52. EMOJI = "emoji"
  53. class App(Base):
  54. __tablename__ = "apps"
  55. __table_args__ = (sa.PrimaryKeyConstraint("id", name="app_pkey"), sa.Index("app_tenant_id_idx", "tenant_id"))
  56. id: Mapped[str] = mapped_column(StringUUID, server_default=sa.text("uuid_generate_v4()"))
  57. tenant_id: Mapped[str] = mapped_column(StringUUID)
  58. name: Mapped[str] = mapped_column(String(255))
  59. description: Mapped[str] = mapped_column(sa.Text, server_default=sa.text("''::character varying"))
  60. mode: Mapped[str] = mapped_column(String(255))
  61. icon_type: Mapped[Optional[str]] = mapped_column(String(255)) # image, emoji
  62. icon = mapped_column(String(255))
  63. icon_background: Mapped[Optional[str]] = mapped_column(String(255))
  64. app_model_config_id = mapped_column(StringUUID, nullable=True)
  65. workflow_id = mapped_column(StringUUID, nullable=True)
  66. status: Mapped[str] = mapped_column(String(255), server_default=sa.text("'normal'::character varying"))
  67. enable_site: Mapped[bool] = mapped_column(sa.Boolean)
  68. enable_api: Mapped[bool] = mapped_column(sa.Boolean)
  69. api_rpm: Mapped[int] = mapped_column(sa.Integer, server_default=sa.text("0"))
  70. api_rph: Mapped[int] = mapped_column(sa.Integer, server_default=sa.text("0"))
  71. is_demo: Mapped[bool] = mapped_column(sa.Boolean, server_default=sa.text("false"))
  72. is_public: Mapped[bool] = mapped_column(sa.Boolean, server_default=sa.text("false"))
  73. is_universal: Mapped[bool] = mapped_column(sa.Boolean, server_default=sa.text("false"))
  74. tracing = mapped_column(sa.Text, nullable=True)
  75. max_active_requests: Mapped[Optional[int]]
  76. created_by = mapped_column(StringUUID, nullable=True)
  77. created_at = mapped_column(sa.DateTime, nullable=False, server_default=func.current_timestamp())
  78. updated_by = mapped_column(StringUUID, nullable=True)
  79. updated_at: Mapped[datetime] = mapped_column(sa.DateTime, nullable=False, server_default=func.current_timestamp())
  80. use_icon_as_answer_icon: Mapped[bool] = mapped_column(sa.Boolean, nullable=False, server_default=sa.text("false"))
  81. @property
  82. def desc_or_prompt(self) -> str:
  83. if self.description:
  84. return self.description
  85. else:
  86. app_model_config = self.app_model_config
  87. if app_model_config:
  88. return app_model_config.pre_prompt
  89. else:
  90. return ""
  91. @property
  92. def site(self) -> Optional["Site"]:
  93. site = db.session.query(Site).where(Site.app_id == self.id).first()
  94. return site
  95. @property
  96. def app_model_config(self) -> Optional["AppModelConfig"]:
  97. if self.app_model_config_id:
  98. return db.session.query(AppModelConfig).where(AppModelConfig.id == self.app_model_config_id).first()
  99. return None
  100. @property
  101. def workflow(self) -> Optional["Workflow"]:
  102. if self.workflow_id:
  103. from .workflow import Workflow
  104. return db.session.query(Workflow).where(Workflow.id == self.workflow_id).first()
  105. return None
  106. @property
  107. def api_base_url(self) -> str:
  108. return (dify_config.SERVICE_API_URL or request.host_url.rstrip("/")) + "/v1"
  109. @property
  110. def tenant(self) -> Optional[Tenant]:
  111. tenant = db.session.query(Tenant).where(Tenant.id == self.tenant_id).first()
  112. return tenant
  113. @property
  114. def is_agent(self) -> bool:
  115. app_model_config = self.app_model_config
  116. if not app_model_config:
  117. return False
  118. if not app_model_config.agent_mode:
  119. return False
  120. if app_model_config.agent_mode_dict.get("enabled", False) and app_model_config.agent_mode_dict.get(
  121. "strategy", ""
  122. ) in {"function_call", "react"}:
  123. self.mode = AppMode.AGENT_CHAT.value
  124. db.session.commit()
  125. return True
  126. return False
  127. @property
  128. def mode_compatible_with_agent(self) -> str:
  129. if self.mode == AppMode.CHAT.value and self.is_agent:
  130. return AppMode.AGENT_CHAT.value
  131. return str(self.mode)
  132. @property
  133. def deleted_tools(self) -> list[dict[str, str]]:
  134. from core.tools.tool_manager import ToolManager, ToolProviderType
  135. from services.plugin.plugin_service import PluginService
  136. # get agent mode tools
  137. app_model_config = self.app_model_config
  138. if not app_model_config:
  139. return []
  140. if not app_model_config.agent_mode:
  141. return []
  142. agent_mode = app_model_config.agent_mode_dict
  143. tools = agent_mode.get("tools", [])
  144. api_provider_ids: list[str] = []
  145. builtin_provider_ids: list[GenericProviderID] = []
  146. for tool in tools:
  147. keys = list(tool.keys())
  148. if len(keys) >= 4:
  149. provider_type = tool.get("provider_type", "")
  150. provider_id = tool.get("provider_id", "")
  151. if provider_type == ToolProviderType.API.value:
  152. try:
  153. uuid.UUID(provider_id)
  154. except Exception:
  155. continue
  156. api_provider_ids.append(provider_id)
  157. if provider_type == ToolProviderType.BUILT_IN.value:
  158. try:
  159. # check if it's hardcoded
  160. try:
  161. ToolManager.get_hardcoded_provider(provider_id)
  162. is_hardcoded = True
  163. except Exception:
  164. is_hardcoded = False
  165. provider_id = GenericProviderID(provider_id, is_hardcoded)
  166. except Exception:
  167. continue
  168. builtin_provider_ids.append(provider_id)
  169. if not api_provider_ids and not builtin_provider_ids:
  170. return []
  171. with Session(db.engine) as session:
  172. if api_provider_ids:
  173. existing_api_providers = [
  174. api_provider.id
  175. for api_provider in session.execute(
  176. text("SELECT id FROM tool_api_providers WHERE id IN :provider_ids"),
  177. {"provider_ids": tuple(api_provider_ids)},
  178. ).fetchall()
  179. ]
  180. else:
  181. existing_api_providers = []
  182. if builtin_provider_ids:
  183. # get the non-hardcoded builtin providers
  184. non_hardcoded_builtin_providers = [
  185. provider_id for provider_id in builtin_provider_ids if not provider_id.is_hardcoded
  186. ]
  187. if non_hardcoded_builtin_providers:
  188. existence = list(PluginService.check_tools_existence(self.tenant_id, non_hardcoded_builtin_providers))
  189. else:
  190. existence = []
  191. # add the hardcoded builtin providers
  192. existence.extend([True] * (len(builtin_provider_ids) - len(non_hardcoded_builtin_providers)))
  193. builtin_provider_ids = non_hardcoded_builtin_providers + [
  194. provider_id for provider_id in builtin_provider_ids if provider_id.is_hardcoded
  195. ]
  196. else:
  197. existence = []
  198. existing_builtin_providers = {
  199. provider_id.provider_name: existence[i] for i, provider_id in enumerate(builtin_provider_ids)
  200. }
  201. deleted_tools: list[dict[str, str]] = []
  202. for tool in tools:
  203. keys = list(tool.keys())
  204. if len(keys) >= 4:
  205. provider_type = tool.get("provider_type", "")
  206. provider_id = tool.get("provider_id", "")
  207. if provider_type == ToolProviderType.API.value:
  208. if uuid.UUID(provider_id) not in existing_api_providers:
  209. deleted_tools.append(
  210. {
  211. "type": ToolProviderType.API.value,
  212. "tool_name": tool["tool_name"],
  213. "provider_id": provider_id,
  214. }
  215. )
  216. if provider_type == ToolProviderType.BUILT_IN.value:
  217. generic_provider_id = GenericProviderID(provider_id)
  218. if not existing_builtin_providers[generic_provider_id.provider_name]:
  219. deleted_tools.append(
  220. {
  221. "type": ToolProviderType.BUILT_IN.value,
  222. "tool_name": tool["tool_name"],
  223. "provider_id": provider_id, # use the original one
  224. }
  225. )
  226. return deleted_tools
  227. @property
  228. def tags(self) -> list["Tag"]:
  229. tags = (
  230. db.session.query(Tag)
  231. .join(TagBinding, Tag.id == TagBinding.tag_id)
  232. .where(
  233. TagBinding.target_id == self.id,
  234. TagBinding.tenant_id == self.tenant_id,
  235. Tag.tenant_id == self.tenant_id,
  236. Tag.type == "app",
  237. )
  238. .all()
  239. )
  240. return tags or []
  241. @property
  242. def author_name(self) -> Optional[str]:
  243. if self.created_by:
  244. account = db.session.query(Account).where(Account.id == self.created_by).first()
  245. if account:
  246. return account.name
  247. return None
  248. class AppModelConfig(Base):
  249. __tablename__ = "app_model_configs"
  250. __table_args__ = (sa.PrimaryKeyConstraint("id", name="app_model_config_pkey"), sa.Index("app_app_id_idx", "app_id"))
  251. id = mapped_column(StringUUID, server_default=sa.text("uuid_generate_v4()"))
  252. app_id = mapped_column(StringUUID, nullable=False)
  253. provider = mapped_column(String(255), nullable=True)
  254. model_id = mapped_column(String(255), nullable=True)
  255. configs = mapped_column(sa.JSON, nullable=True)
  256. created_by = mapped_column(StringUUID, nullable=True)
  257. created_at = mapped_column(sa.DateTime, nullable=False, server_default=func.current_timestamp())
  258. updated_by = mapped_column(StringUUID, nullable=True)
  259. updated_at = mapped_column(sa.DateTime, nullable=False, server_default=func.current_timestamp())
  260. opening_statement = mapped_column(sa.Text)
  261. suggested_questions = mapped_column(sa.Text)
  262. suggested_questions_after_answer = mapped_column(sa.Text)
  263. speech_to_text = mapped_column(sa.Text)
  264. text_to_speech = mapped_column(sa.Text)
  265. more_like_this = mapped_column(sa.Text)
  266. model = mapped_column(sa.Text)
  267. user_input_form = mapped_column(sa.Text)
  268. dataset_query_variable = mapped_column(String(255))
  269. pre_prompt = mapped_column(sa.Text)
  270. agent_mode = mapped_column(sa.Text)
  271. sensitive_word_avoidance = mapped_column(sa.Text)
  272. retriever_resource = mapped_column(sa.Text)
  273. prompt_type = mapped_column(String(255), nullable=False, server_default=sa.text("'simple'::character varying"))
  274. chat_prompt_config = mapped_column(sa.Text)
  275. completion_prompt_config = mapped_column(sa.Text)
  276. dataset_configs = mapped_column(sa.Text)
  277. external_data_tools = mapped_column(sa.Text)
  278. file_upload = mapped_column(sa.Text)
  279. @property
  280. def app(self) -> Optional[App]:
  281. app = db.session.query(App).where(App.id == self.app_id).first()
  282. return app
  283. @property
  284. def model_dict(self) -> dict[str, Any]:
  285. return json.loads(self.model) if self.model else {}
  286. @property
  287. def suggested_questions_list(self) -> list[str]:
  288. return json.loads(self.suggested_questions) if self.suggested_questions else []
  289. @property
  290. def suggested_questions_after_answer_dict(self) -> dict[str, Any]:
  291. return (
  292. json.loads(self.suggested_questions_after_answer)
  293. if self.suggested_questions_after_answer
  294. else {"enabled": False}
  295. )
  296. @property
  297. def speech_to_text_dict(self) -> dict[str, Any]:
  298. return json.loads(self.speech_to_text) if self.speech_to_text else {"enabled": False}
  299. @property
  300. def text_to_speech_dict(self) -> dict[str, Any]:
  301. return json.loads(self.text_to_speech) if self.text_to_speech else {"enabled": False}
  302. @property
  303. def retriever_resource_dict(self) -> dict[str, Any]:
  304. return json.loads(self.retriever_resource) if self.retriever_resource else {"enabled": True}
  305. @property
  306. def annotation_reply_dict(self) -> dict[str, Any]:
  307. annotation_setting = (
  308. db.session.query(AppAnnotationSetting).where(AppAnnotationSetting.app_id == self.app_id).first()
  309. )
  310. if annotation_setting:
  311. collection_binding_detail = annotation_setting.collection_binding_detail
  312. if not collection_binding_detail:
  313. raise ValueError("Collection binding detail not found")
  314. return {
  315. "id": annotation_setting.id,
  316. "enabled": True,
  317. "score_threshold": annotation_setting.score_threshold,
  318. "embedding_model": {
  319. "embedding_provider_name": collection_binding_detail.provider_name,
  320. "embedding_model_name": collection_binding_detail.model_name,
  321. },
  322. }
  323. else:
  324. return {"enabled": False}
  325. @property
  326. def more_like_this_dict(self) -> dict[str, Any]:
  327. return json.loads(self.more_like_this) if self.more_like_this else {"enabled": False}
  328. @property
  329. def sensitive_word_avoidance_dict(self) -> dict[str, Any]:
  330. return (
  331. json.loads(self.sensitive_word_avoidance)
  332. if self.sensitive_word_avoidance
  333. else {"enabled": False, "type": "", "configs": []}
  334. )
  335. @property
  336. def external_data_tools_list(self) -> list[dict[str, Any]]:
  337. return json.loads(self.external_data_tools) if self.external_data_tools else []
  338. @property
  339. def user_input_form_list(self) -> list[dict[str, Any]]:
  340. return json.loads(self.user_input_form) if self.user_input_form else []
  341. @property
  342. def agent_mode_dict(self) -> dict[str, Any]:
  343. return (
  344. json.loads(self.agent_mode)
  345. if self.agent_mode
  346. else {"enabled": False, "strategy": None, "tools": [], "prompt": None}
  347. )
  348. @property
  349. def chat_prompt_config_dict(self) -> dict[str, Any]:
  350. return json.loads(self.chat_prompt_config) if self.chat_prompt_config else {}
  351. @property
  352. def completion_prompt_config_dict(self) -> dict[str, Any]:
  353. return json.loads(self.completion_prompt_config) if self.completion_prompt_config else {}
  354. @property
  355. def dataset_configs_dict(self) -> dict[str, Any]:
  356. if self.dataset_configs:
  357. dataset_configs: dict[str, Any] = json.loads(self.dataset_configs)
  358. if "retrieval_model" not in dataset_configs:
  359. return {"retrieval_model": "single"}
  360. else:
  361. return dataset_configs
  362. return {
  363. "retrieval_model": "multiple",
  364. }
  365. @property
  366. def file_upload_dict(self) -> dict[str, Any]:
  367. return (
  368. json.loads(self.file_upload)
  369. if self.file_upload
  370. else {
  371. "image": {
  372. "enabled": False,
  373. "number_limits": DEFAULT_FILE_NUMBER_LIMITS,
  374. "detail": "high",
  375. "transfer_methods": ["remote_url", "local_file"],
  376. }
  377. }
  378. )
  379. def to_dict(self) -> dict[str, Any]:
  380. return {
  381. "opening_statement": self.opening_statement,
  382. "suggested_questions": self.suggested_questions_list,
  383. "suggested_questions_after_answer": self.suggested_questions_after_answer_dict,
  384. "speech_to_text": self.speech_to_text_dict,
  385. "text_to_speech": self.text_to_speech_dict,
  386. "retriever_resource": self.retriever_resource_dict,
  387. "annotation_reply": self.annotation_reply_dict,
  388. "more_like_this": self.more_like_this_dict,
  389. "sensitive_word_avoidance": self.sensitive_word_avoidance_dict,
  390. "external_data_tools": self.external_data_tools_list,
  391. "model": self.model_dict,
  392. "user_input_form": self.user_input_form_list,
  393. "dataset_query_variable": self.dataset_query_variable,
  394. "pre_prompt": self.pre_prompt,
  395. "agent_mode": self.agent_mode_dict,
  396. "prompt_type": self.prompt_type,
  397. "chat_prompt_config": self.chat_prompt_config_dict,
  398. "completion_prompt_config": self.completion_prompt_config_dict,
  399. "dataset_configs": self.dataset_configs_dict,
  400. "file_upload": self.file_upload_dict,
  401. }
  402. def from_model_config_dict(self, model_config: Mapping[str, Any]):
  403. self.opening_statement = model_config.get("opening_statement")
  404. self.suggested_questions = (
  405. json.dumps(model_config["suggested_questions"]) if model_config.get("suggested_questions") else None
  406. )
  407. self.suggested_questions_after_answer = (
  408. json.dumps(model_config["suggested_questions_after_answer"])
  409. if model_config.get("suggested_questions_after_answer")
  410. else None
  411. )
  412. self.speech_to_text = json.dumps(model_config["speech_to_text"]) if model_config.get("speech_to_text") else None
  413. self.text_to_speech = json.dumps(model_config["text_to_speech"]) if model_config.get("text_to_speech") else None
  414. self.more_like_this = json.dumps(model_config["more_like_this"]) if model_config.get("more_like_this") else None
  415. self.sensitive_word_avoidance = (
  416. json.dumps(model_config["sensitive_word_avoidance"])
  417. if model_config.get("sensitive_word_avoidance")
  418. else None
  419. )
  420. self.external_data_tools = (
  421. json.dumps(model_config["external_data_tools"]) if model_config.get("external_data_tools") else None
  422. )
  423. self.model = json.dumps(model_config["model"]) if model_config.get("model") else None
  424. self.user_input_form = (
  425. json.dumps(model_config["user_input_form"]) if model_config.get("user_input_form") else None
  426. )
  427. self.dataset_query_variable = model_config.get("dataset_query_variable")
  428. self.pre_prompt = model_config["pre_prompt"]
  429. self.agent_mode = json.dumps(model_config["agent_mode"]) if model_config.get("agent_mode") else None
  430. self.retriever_resource = (
  431. json.dumps(model_config["retriever_resource"]) if model_config.get("retriever_resource") else None
  432. )
  433. self.prompt_type = model_config.get("prompt_type", "simple")
  434. self.chat_prompt_config = (
  435. json.dumps(model_config.get("chat_prompt_config")) if model_config.get("chat_prompt_config") else None
  436. )
  437. self.completion_prompt_config = (
  438. json.dumps(model_config.get("completion_prompt_config"))
  439. if model_config.get("completion_prompt_config")
  440. else None
  441. )
  442. self.dataset_configs = (
  443. json.dumps(model_config.get("dataset_configs")) if model_config.get("dataset_configs") else None
  444. )
  445. self.file_upload = json.dumps(model_config.get("file_upload")) if model_config.get("file_upload") else None
  446. return self
  447. class RecommendedApp(Base):
  448. __tablename__ = "recommended_apps"
  449. __table_args__ = (
  450. sa.PrimaryKeyConstraint("id", name="recommended_app_pkey"),
  451. sa.Index("recommended_app_app_id_idx", "app_id"),
  452. sa.Index("recommended_app_is_listed_idx", "is_listed", "language"),
  453. )
  454. id = mapped_column(StringUUID, primary_key=True, server_default=sa.text("uuid_generate_v4()"))
  455. app_id = mapped_column(StringUUID, nullable=False)
  456. description = mapped_column(sa.JSON, nullable=False)
  457. copyright: Mapped[str] = mapped_column(String(255), nullable=False)
  458. privacy_policy: Mapped[str] = mapped_column(String(255), nullable=False)
  459. custom_disclaimer: Mapped[str] = mapped_column(sa.TEXT, default="")
  460. category: Mapped[str] = mapped_column(String(255), nullable=False)
  461. position: Mapped[int] = mapped_column(sa.Integer, nullable=False, default=0)
  462. is_listed: Mapped[bool] = mapped_column(sa.Boolean, nullable=False, default=True)
  463. install_count: Mapped[int] = mapped_column(sa.Integer, nullable=False, default=0)
  464. language = mapped_column(String(255), nullable=False, server_default=sa.text("'en-US'::character varying"))
  465. created_at = mapped_column(sa.DateTime, nullable=False, server_default=func.current_timestamp())
  466. updated_at = mapped_column(sa.DateTime, nullable=False, server_default=func.current_timestamp())
  467. @property
  468. def app(self) -> Optional[App]:
  469. app = db.session.query(App).where(App.id == self.app_id).first()
  470. return app
  471. class InstalledApp(Base):
  472. __tablename__ = "installed_apps"
  473. __table_args__ = (
  474. sa.PrimaryKeyConstraint("id", name="installed_app_pkey"),
  475. sa.Index("installed_app_tenant_id_idx", "tenant_id"),
  476. sa.Index("installed_app_app_id_idx", "app_id"),
  477. sa.UniqueConstraint("tenant_id", "app_id", name="unique_tenant_app"),
  478. )
  479. id = mapped_column(StringUUID, server_default=sa.text("uuid_generate_v4()"))
  480. tenant_id = mapped_column(StringUUID, nullable=False)
  481. app_id = mapped_column(StringUUID, nullable=False)
  482. app_owner_tenant_id = mapped_column(StringUUID, nullable=False)
  483. position: Mapped[int] = mapped_column(sa.Integer, nullable=False, default=0)
  484. is_pinned: Mapped[bool] = mapped_column(sa.Boolean, nullable=False, server_default=sa.text("false"))
  485. last_used_at = mapped_column(sa.DateTime, nullable=True)
  486. created_at = mapped_column(sa.DateTime, nullable=False, server_default=func.current_timestamp())
  487. @property
  488. def app(self) -> Optional[App]:
  489. app = db.session.query(App).where(App.id == self.app_id).first()
  490. return app
  491. @property
  492. def tenant(self) -> Optional[Tenant]:
  493. tenant = db.session.query(Tenant).where(Tenant.id == self.tenant_id).first()
  494. return tenant
  495. class OAuthProviderApp(Base):
  496. """
  497. Globally shared OAuth provider app information.
  498. Only for Dify Cloud.
  499. """
  500. __tablename__ = "oauth_provider_apps"
  501. __table_args__ = (
  502. sa.PrimaryKeyConstraint("id", name="oauth_provider_app_pkey"),
  503. sa.Index("oauth_provider_app_client_id_idx", "client_id"),
  504. )
  505. id = mapped_column(StringUUID, server_default=sa.text("uuidv7()"))
  506. app_icon = mapped_column(String(255), nullable=False)
  507. app_label = mapped_column(sa.JSON, nullable=False, server_default="{}")
  508. client_id = mapped_column(String(255), nullable=False)
  509. client_secret = mapped_column(String(255), nullable=False)
  510. redirect_uris = mapped_column(sa.JSON, nullable=False, server_default="[]")
  511. scope = mapped_column(
  512. String(255),
  513. nullable=False,
  514. server_default=sa.text("'read:name read:email read:avatar read:interface_language read:timezone'"),
  515. )
  516. created_at = mapped_column(sa.DateTime, nullable=False, server_default=sa.text("CURRENT_TIMESTAMP(0)"))
  517. class Conversation(Base):
  518. __tablename__ = "conversations"
  519. __table_args__ = (
  520. sa.PrimaryKeyConstraint("id", name="conversation_pkey"),
  521. sa.Index("conversation_app_from_user_idx", "app_id", "from_source", "from_end_user_id"),
  522. )
  523. id: Mapped[str] = mapped_column(StringUUID, server_default=sa.text("uuid_generate_v4()"))
  524. app_id = mapped_column(StringUUID, nullable=False)
  525. app_model_config_id = mapped_column(StringUUID, nullable=True)
  526. model_provider = mapped_column(String(255), nullable=True)
  527. override_model_configs = mapped_column(sa.Text)
  528. model_id = mapped_column(String(255), nullable=True)
  529. mode: Mapped[str] = mapped_column(String(255))
  530. name: Mapped[str] = mapped_column(String(255), nullable=False)
  531. summary = mapped_column(sa.Text)
  532. _inputs: Mapped[dict[str, Any]] = mapped_column("inputs", sa.JSON)
  533. introduction = mapped_column(sa.Text)
  534. system_instruction = mapped_column(sa.Text)
  535. system_instruction_tokens: Mapped[int] = mapped_column(sa.Integer, nullable=False, server_default=sa.text("0"))
  536. status: Mapped[str] = mapped_column(String(255), nullable=False)
  537. # The `invoke_from` records how the conversation is created.
  538. #
  539. # Its value corresponds to the members of `InvokeFrom`.
  540. # (api/core/app/entities/app_invoke_entities.py)
  541. invoke_from = mapped_column(String(255), nullable=True)
  542. # ref: ConversationSource.
  543. from_source: Mapped[str] = mapped_column(String(255), nullable=False)
  544. from_end_user_id = mapped_column(StringUUID)
  545. from_account_id = mapped_column(StringUUID)
  546. read_at = mapped_column(sa.DateTime)
  547. read_account_id = mapped_column(StringUUID)
  548. dialogue_count: Mapped[int] = mapped_column(default=0)
  549. created_at = mapped_column(sa.DateTime, nullable=False, server_default=func.current_timestamp())
  550. updated_at = mapped_column(sa.DateTime, nullable=False, server_default=func.current_timestamp())
  551. messages = db.relationship("Message", backref="conversation", lazy="select", passive_deletes="all")
  552. message_annotations = db.relationship(
  553. "MessageAnnotation", backref="conversation", lazy="select", passive_deletes="all"
  554. )
  555. is_deleted: Mapped[bool] = mapped_column(sa.Boolean, nullable=False, server_default=sa.text("false"))
  556. @property
  557. def inputs(self) -> dict[str, Any]:
  558. inputs = self._inputs.copy()
  559. # Convert file mapping to File object
  560. for key, value in inputs.items():
  561. # NOTE: It's not the best way to implement this, but it's the only way to avoid circular import for now.
  562. from factories import file_factory
  563. if (
  564. isinstance(value, dict)
  565. and cast(dict[str, Any], value).get("dify_model_identity") == FILE_MODEL_IDENTITY
  566. ):
  567. value_dict = cast(dict[str, Any], value)
  568. if value_dict["transfer_method"] == FileTransferMethod.TOOL_FILE:
  569. value_dict["tool_file_id"] = value_dict["related_id"]
  570. elif value_dict["transfer_method"] in [FileTransferMethod.LOCAL_FILE, FileTransferMethod.REMOTE_URL]:
  571. value_dict["upload_file_id"] = value_dict["related_id"]
  572. tenant_id = cast(str, value_dict.get("tenant_id", ""))
  573. inputs[key] = file_factory.build_from_mapping(mapping=value_dict, tenant_id=tenant_id)
  574. elif isinstance(value, list):
  575. value_list = cast(list[Any], value)
  576. if all(
  577. isinstance(item, dict)
  578. and cast(dict[str, Any], item).get("dify_model_identity") == FILE_MODEL_IDENTITY
  579. for item in value_list
  580. ):
  581. file_list: list[File] = []
  582. for item in value_list:
  583. if not isinstance(item, dict):
  584. continue
  585. item_dict = cast(dict[str, Any], item)
  586. if item_dict["transfer_method"] == FileTransferMethod.TOOL_FILE:
  587. item_dict["tool_file_id"] = item_dict["related_id"]
  588. elif item_dict["transfer_method"] in [
  589. FileTransferMethod.LOCAL_FILE,
  590. FileTransferMethod.REMOTE_URL,
  591. ]:
  592. item_dict["upload_file_id"] = item_dict["related_id"]
  593. tenant_id = cast(str, item_dict.get("tenant_id", ""))
  594. file_list.append(file_factory.build_from_mapping(mapping=item_dict, tenant_id=tenant_id))
  595. inputs[key] = file_list
  596. return inputs
  597. @inputs.setter
  598. def inputs(self, value: Mapping[str, Any]):
  599. inputs = dict(value)
  600. for k, v in inputs.items():
  601. if isinstance(v, File):
  602. inputs[k] = v.model_dump()
  603. elif isinstance(v, list):
  604. v_list = cast(list[Any], v)
  605. if all(isinstance(item, File) for item in v_list):
  606. inputs[k] = [item.model_dump() for item in v_list if isinstance(item, File)]
  607. self._inputs = inputs
  608. @property
  609. def model_config(self):
  610. model_config = {}
  611. app_model_config: Optional[AppModelConfig] = None
  612. if self.mode == AppMode.ADVANCED_CHAT.value:
  613. if self.override_model_configs:
  614. override_model_configs = json.loads(self.override_model_configs)
  615. model_config = override_model_configs
  616. else:
  617. if self.override_model_configs:
  618. override_model_configs = json.loads(self.override_model_configs)
  619. if "model" in override_model_configs:
  620. app_model_config = AppModelConfig()
  621. app_model_config = app_model_config.from_model_config_dict(override_model_configs)
  622. model_config = app_model_config.to_dict()
  623. else:
  624. model_config["configs"] = override_model_configs
  625. else:
  626. app_model_config = (
  627. db.session.query(AppModelConfig).where(AppModelConfig.id == self.app_model_config_id).first()
  628. )
  629. if app_model_config:
  630. model_config = app_model_config.to_dict()
  631. model_config["model_id"] = self.model_id
  632. model_config["provider"] = self.model_provider
  633. return model_config
  634. @property
  635. def summary_or_query(self):
  636. if self.summary:
  637. return self.summary
  638. else:
  639. first_message = self.first_message
  640. if first_message:
  641. return first_message.query
  642. else:
  643. return ""
  644. @property
  645. def annotated(self):
  646. return db.session.query(MessageAnnotation).where(MessageAnnotation.conversation_id == self.id).count() > 0
  647. @property
  648. def annotation(self):
  649. return db.session.query(MessageAnnotation).where(MessageAnnotation.conversation_id == self.id).first()
  650. @property
  651. def message_count(self):
  652. return db.session.query(Message).where(Message.conversation_id == self.id).count()
  653. @property
  654. def user_feedback_stats(self):
  655. like = (
  656. db.session.query(MessageFeedback)
  657. .where(
  658. MessageFeedback.conversation_id == self.id,
  659. MessageFeedback.from_source == "user",
  660. MessageFeedback.rating == "like",
  661. )
  662. .count()
  663. )
  664. dislike = (
  665. db.session.query(MessageFeedback)
  666. .where(
  667. MessageFeedback.conversation_id == self.id,
  668. MessageFeedback.from_source == "user",
  669. MessageFeedback.rating == "dislike",
  670. )
  671. .count()
  672. )
  673. return {"like": like, "dislike": dislike}
  674. @property
  675. def admin_feedback_stats(self):
  676. like = (
  677. db.session.query(MessageFeedback)
  678. .where(
  679. MessageFeedback.conversation_id == self.id,
  680. MessageFeedback.from_source == "admin",
  681. MessageFeedback.rating == "like",
  682. )
  683. .count()
  684. )
  685. dislike = (
  686. db.session.query(MessageFeedback)
  687. .where(
  688. MessageFeedback.conversation_id == self.id,
  689. MessageFeedback.from_source == "admin",
  690. MessageFeedback.rating == "dislike",
  691. )
  692. .count()
  693. )
  694. return {"like": like, "dislike": dislike}
  695. @property
  696. def status_count(self):
  697. messages = db.session.query(Message).where(Message.conversation_id == self.id).all()
  698. status_counts = {
  699. WorkflowExecutionStatus.RUNNING: 0,
  700. WorkflowExecutionStatus.SUCCEEDED: 0,
  701. WorkflowExecutionStatus.FAILED: 0,
  702. WorkflowExecutionStatus.STOPPED: 0,
  703. WorkflowExecutionStatus.PARTIAL_SUCCEEDED: 0,
  704. }
  705. for message in messages:
  706. if message.workflow_run:
  707. status_counts[WorkflowExecutionStatus(message.workflow_run.status)] += 1
  708. return (
  709. {
  710. "success": status_counts[WorkflowExecutionStatus.SUCCEEDED],
  711. "failed": status_counts[WorkflowExecutionStatus.FAILED],
  712. "partial_success": status_counts[WorkflowExecutionStatus.PARTIAL_SUCCEEDED],
  713. }
  714. if messages
  715. else None
  716. )
  717. @property
  718. def first_message(self):
  719. return (
  720. db.session.query(Message)
  721. .where(Message.conversation_id == self.id)
  722. .order_by(Message.created_at.asc())
  723. .first()
  724. )
  725. @property
  726. def app(self) -> Optional[App]:
  727. with Session(db.engine, expire_on_commit=False) as session:
  728. return session.query(App).where(App.id == self.app_id).first()
  729. @property
  730. def from_end_user_session_id(self):
  731. if self.from_end_user_id:
  732. end_user = db.session.query(EndUser).where(EndUser.id == self.from_end_user_id).first()
  733. if end_user:
  734. return end_user.session_id
  735. return None
  736. @property
  737. def from_account_name(self) -> Optional[str]:
  738. if self.from_account_id:
  739. account = db.session.query(Account).where(Account.id == self.from_account_id).first()
  740. if account:
  741. return account.name
  742. return None
  743. @property
  744. def in_debug_mode(self) -> bool:
  745. return self.override_model_configs is not None
  746. def to_dict(self) -> dict[str, Any]:
  747. return {
  748. "id": self.id,
  749. "app_id": self.app_id,
  750. "app_model_config_id": self.app_model_config_id,
  751. "model_provider": self.model_provider,
  752. "override_model_configs": self.override_model_configs,
  753. "model_id": self.model_id,
  754. "mode": self.mode,
  755. "name": self.name,
  756. "summary": self.summary,
  757. "inputs": self.inputs,
  758. "introduction": self.introduction,
  759. "system_instruction": self.system_instruction,
  760. "system_instruction_tokens": self.system_instruction_tokens,
  761. "status": self.status,
  762. "invoke_from": self.invoke_from,
  763. "from_source": self.from_source,
  764. "from_end_user_id": self.from_end_user_id,
  765. "from_account_id": self.from_account_id,
  766. "read_at": self.read_at,
  767. "read_account_id": self.read_account_id,
  768. "dialogue_count": self.dialogue_count,
  769. "created_at": self.created_at,
  770. "updated_at": self.updated_at,
  771. }
  772. class Message(Base):
  773. __tablename__ = "messages"
  774. __table_args__ = (
  775. PrimaryKeyConstraint("id", name="message_pkey"),
  776. Index("message_app_id_idx", "app_id", "created_at"),
  777. Index("message_conversation_id_idx", "conversation_id"),
  778. Index("message_end_user_idx", "app_id", "from_source", "from_end_user_id"),
  779. Index("message_account_idx", "app_id", "from_source", "from_account_id"),
  780. Index("message_workflow_run_id_idx", "conversation_id", "workflow_run_id"),
  781. Index("message_created_at_idx", "created_at"),
  782. )
  783. id: Mapped[str] = mapped_column(StringUUID, server_default=sa.text("uuid_generate_v4()"))
  784. app_id = mapped_column(StringUUID, nullable=False)
  785. model_provider = mapped_column(String(255), nullable=True)
  786. model_id = mapped_column(String(255), nullable=True)
  787. override_model_configs = mapped_column(sa.Text)
  788. conversation_id = mapped_column(StringUUID, sa.ForeignKey("conversations.id"), nullable=False)
  789. _inputs: Mapped[dict[str, Any]] = mapped_column("inputs", sa.JSON)
  790. query: Mapped[str] = mapped_column(sa.Text, nullable=False)
  791. message = mapped_column(sa.JSON, nullable=False)
  792. message_tokens: Mapped[int] = mapped_column(sa.Integer, nullable=False, server_default=sa.text("0"))
  793. message_unit_price = mapped_column(sa.Numeric(10, 4), nullable=False)
  794. message_price_unit = mapped_column(sa.Numeric(10, 7), nullable=False, server_default=sa.text("0.001"))
  795. answer: Mapped[str] = mapped_column(sa.Text, nullable=False)
  796. answer_tokens: Mapped[int] = mapped_column(sa.Integer, nullable=False, server_default=sa.text("0"))
  797. answer_unit_price = mapped_column(sa.Numeric(10, 4), nullable=False)
  798. answer_price_unit = mapped_column(sa.Numeric(10, 7), nullable=False, server_default=sa.text("0.001"))
  799. parent_message_id = mapped_column(StringUUID, nullable=True)
  800. provider_response_latency = mapped_column(sa.Float, nullable=False, server_default=sa.text("0"))
  801. total_price = mapped_column(sa.Numeric(10, 7))
  802. currency: Mapped[str] = mapped_column(String(255), nullable=False)
  803. status = mapped_column(String(255), nullable=False, server_default=sa.text("'normal'::character varying"))
  804. error = mapped_column(sa.Text)
  805. message_metadata = mapped_column(sa.Text)
  806. invoke_from: Mapped[Optional[str]] = mapped_column(String(255), nullable=True)
  807. from_source: Mapped[str] = mapped_column(String(255), nullable=False)
  808. from_end_user_id: Mapped[Optional[str]] = mapped_column(StringUUID)
  809. from_account_id: Mapped[Optional[str]] = mapped_column(StringUUID)
  810. created_at: Mapped[datetime] = mapped_column(sa.DateTime, server_default=func.current_timestamp())
  811. updated_at = mapped_column(sa.DateTime, nullable=False, server_default=func.current_timestamp())
  812. agent_based: Mapped[bool] = mapped_column(sa.Boolean, nullable=False, server_default=sa.text("false"))
  813. workflow_run_id: Mapped[Optional[str]] = mapped_column(StringUUID)
  814. @property
  815. def inputs(self) -> dict[str, Any]:
  816. inputs = self._inputs.copy()
  817. for key, value in inputs.items():
  818. # NOTE: It's not the best way to implement this, but it's the only way to avoid circular import for now.
  819. from factories import file_factory
  820. if (
  821. isinstance(value, dict)
  822. and cast(dict[str, Any], value).get("dify_model_identity") == FILE_MODEL_IDENTITY
  823. ):
  824. value_dict = cast(dict[str, Any], value)
  825. if value_dict["transfer_method"] == FileTransferMethod.TOOL_FILE:
  826. value_dict["tool_file_id"] = value_dict["related_id"]
  827. elif value_dict["transfer_method"] in [FileTransferMethod.LOCAL_FILE, FileTransferMethod.REMOTE_URL]:
  828. value_dict["upload_file_id"] = value_dict["related_id"]
  829. tenant_id = cast(str, value_dict.get("tenant_id", ""))
  830. inputs[key] = file_factory.build_from_mapping(mapping=value_dict, tenant_id=tenant_id)
  831. elif isinstance(value, list):
  832. value_list = cast(list[Any], value)
  833. if all(
  834. isinstance(item, dict)
  835. and cast(dict[str, Any], item).get("dify_model_identity") == FILE_MODEL_IDENTITY
  836. for item in value_list
  837. ):
  838. file_list: list[File] = []
  839. for item in value_list:
  840. if not isinstance(item, dict):
  841. continue
  842. item_dict = cast(dict[str, Any], item)
  843. if item_dict["transfer_method"] == FileTransferMethod.TOOL_FILE:
  844. item_dict["tool_file_id"] = item_dict["related_id"]
  845. elif item_dict["transfer_method"] in [
  846. FileTransferMethod.LOCAL_FILE,
  847. FileTransferMethod.REMOTE_URL,
  848. ]:
  849. item_dict["upload_file_id"] = item_dict["related_id"]
  850. tenant_id = cast(str, item_dict.get("tenant_id", ""))
  851. file_list.append(file_factory.build_from_mapping(mapping=item_dict, tenant_id=tenant_id))
  852. inputs[key] = file_list
  853. return inputs
  854. @inputs.setter
  855. def inputs(self, value: Mapping[str, Any]):
  856. inputs = dict(value)
  857. for k, v in inputs.items():
  858. if isinstance(v, File):
  859. inputs[k] = v.model_dump()
  860. elif isinstance(v, list):
  861. v_list = cast(list[Any], v)
  862. if all(isinstance(item, File) for item in v_list):
  863. inputs[k] = [item.model_dump() for item in v_list if isinstance(item, File)]
  864. self._inputs = inputs
  865. @property
  866. def re_sign_file_url_answer(self) -> str:
  867. if not self.answer:
  868. return self.answer
  869. pattern = r"\[!?.*?\]\((((http|https):\/\/.+)?\/files\/(tools\/)?[\w-]+.*?timestamp=.*&nonce=.*&sign=.*)\)"
  870. matches = re.findall(pattern, self.answer)
  871. if not matches:
  872. return self.answer
  873. urls = [match[0] for match in matches]
  874. # remove duplicate urls
  875. urls = list(set(urls))
  876. if not urls:
  877. return self.answer
  878. re_sign_file_url_answer = self.answer
  879. for url in urls:
  880. if "files/tools" in url:
  881. # get tool file id
  882. tool_file_id_pattern = r"\/files\/tools\/([\.\w-]+)?\?timestamp="
  883. result = re.search(tool_file_id_pattern, url)
  884. if not result:
  885. continue
  886. tool_file_id = result.group(1)
  887. # get extension
  888. if "." in tool_file_id:
  889. split_result = tool_file_id.split(".")
  890. extension = f".{split_result[-1]}"
  891. if len(extension) > 10:
  892. extension = ".bin"
  893. tool_file_id = split_result[0]
  894. else:
  895. extension = ".bin"
  896. if not tool_file_id:
  897. continue
  898. sign_url = sign_tool_file(tool_file_id=tool_file_id, extension=extension)
  899. elif "file-preview" in url:
  900. # get upload file id
  901. upload_file_id_pattern = r"\/files\/([\w-]+)\/file-preview?\?timestamp="
  902. result = re.search(upload_file_id_pattern, url)
  903. if not result:
  904. continue
  905. upload_file_id = result.group(1)
  906. if not upload_file_id:
  907. continue
  908. sign_url = file_helpers.get_signed_file_url(upload_file_id)
  909. elif "image-preview" in url:
  910. # image-preview is deprecated, use file-preview instead
  911. upload_file_id_pattern = r"\/files\/([\w-]+)\/image-preview?\?timestamp="
  912. result = re.search(upload_file_id_pattern, url)
  913. if not result:
  914. continue
  915. upload_file_id = result.group(1)
  916. if not upload_file_id:
  917. continue
  918. sign_url = file_helpers.get_signed_file_url(upload_file_id)
  919. else:
  920. continue
  921. # if as_attachment is in the url, add it to the sign_url.
  922. if "as_attachment" in url:
  923. sign_url += "&as_attachment=true"
  924. re_sign_file_url_answer = re_sign_file_url_answer.replace(url, sign_url)
  925. return re_sign_file_url_answer
  926. @property
  927. def user_feedback(self):
  928. feedback = (
  929. db.session.query(MessageFeedback)
  930. .where(MessageFeedback.message_id == self.id, MessageFeedback.from_source == "user")
  931. .first()
  932. )
  933. return feedback
  934. @property
  935. def admin_feedback(self):
  936. feedback = (
  937. db.session.query(MessageFeedback)
  938. .where(MessageFeedback.message_id == self.id, MessageFeedback.from_source == "admin")
  939. .first()
  940. )
  941. return feedback
  942. @property
  943. def feedbacks(self):
  944. feedbacks = db.session.query(MessageFeedback).where(MessageFeedback.message_id == self.id).all()
  945. return feedbacks
  946. @property
  947. def annotation(self):
  948. annotation = db.session.query(MessageAnnotation).where(MessageAnnotation.message_id == self.id).first()
  949. return annotation
  950. @property
  951. def annotation_hit_history(self):
  952. annotation_history = (
  953. db.session.query(AppAnnotationHitHistory).where(AppAnnotationHitHistory.message_id == self.id).first()
  954. )
  955. if annotation_history:
  956. annotation = (
  957. db.session.query(MessageAnnotation)
  958. .where(MessageAnnotation.id == annotation_history.annotation_id)
  959. .first()
  960. )
  961. return annotation
  962. return None
  963. @property
  964. def app_model_config(self):
  965. conversation = db.session.query(Conversation).where(Conversation.id == self.conversation_id).first()
  966. if conversation:
  967. return db.session.query(AppModelConfig).where(AppModelConfig.id == conversation.app_model_config_id).first()
  968. return None
  969. @property
  970. def in_debug_mode(self) -> bool:
  971. return self.override_model_configs is not None
  972. @property
  973. def message_metadata_dict(self) -> dict[str, Any]:
  974. return json.loads(self.message_metadata) if self.message_metadata else {}
  975. @property
  976. def agent_thoughts(self) -> list["MessageAgentThought"]:
  977. return (
  978. db.session.query(MessageAgentThought)
  979. .where(MessageAgentThought.message_id == self.id)
  980. .order_by(MessageAgentThought.position.asc())
  981. .all()
  982. )
  983. @property
  984. def retriever_resources(self) -> Any | list[Any]:
  985. return self.message_metadata_dict.get("retriever_resources") if self.message_metadata else []
  986. @property
  987. def message_files(self) -> list[dict[str, Any]]:
  988. from factories import file_factory
  989. message_files = db.session.query(MessageFile).where(MessageFile.message_id == self.id).all()
  990. current_app = db.session.query(App).where(App.id == self.app_id).first()
  991. if not current_app:
  992. raise ValueError(f"App {self.app_id} not found")
  993. files: list[File] = []
  994. for message_file in message_files:
  995. if message_file.transfer_method == FileTransferMethod.LOCAL_FILE.value:
  996. if message_file.upload_file_id is None:
  997. raise ValueError(f"MessageFile {message_file.id} is a local file but has no upload_file_id")
  998. file = file_factory.build_from_mapping(
  999. mapping={
  1000. "id": message_file.id,
  1001. "type": message_file.type,
  1002. "transfer_method": message_file.transfer_method,
  1003. "upload_file_id": message_file.upload_file_id,
  1004. },
  1005. tenant_id=current_app.tenant_id,
  1006. )
  1007. elif message_file.transfer_method == FileTransferMethod.REMOTE_URL.value:
  1008. if message_file.url is None:
  1009. raise ValueError(f"MessageFile {message_file.id} is a remote url but has no url")
  1010. file = file_factory.build_from_mapping(
  1011. mapping={
  1012. "id": message_file.id,
  1013. "type": message_file.type,
  1014. "transfer_method": message_file.transfer_method,
  1015. "upload_file_id": message_file.upload_file_id,
  1016. "url": message_file.url,
  1017. },
  1018. tenant_id=current_app.tenant_id,
  1019. )
  1020. elif message_file.transfer_method == FileTransferMethod.TOOL_FILE.value:
  1021. if message_file.upload_file_id is None:
  1022. assert message_file.url is not None
  1023. message_file.upload_file_id = message_file.url.split("/")[-1].split(".")[0]
  1024. mapping = {
  1025. "id": message_file.id,
  1026. "type": message_file.type,
  1027. "transfer_method": message_file.transfer_method,
  1028. "tool_file_id": message_file.upload_file_id,
  1029. }
  1030. file = file_factory.build_from_mapping(
  1031. mapping=mapping,
  1032. tenant_id=current_app.tenant_id,
  1033. )
  1034. else:
  1035. raise ValueError(
  1036. f"MessageFile {message_file.id} has an invalid transfer_method {message_file.transfer_method}"
  1037. )
  1038. files.append(file)
  1039. result: list[dict[str, Any]] = [
  1040. {"belongs_to": message_file.belongs_to, "upload_file_id": message_file.upload_file_id, **file.to_dict()}
  1041. for (file, message_file) in zip(files, message_files)
  1042. ]
  1043. db.session.commit()
  1044. return result
  1045. @property
  1046. def workflow_run(self):
  1047. if self.workflow_run_id:
  1048. from .workflow import WorkflowRun
  1049. return db.session.query(WorkflowRun).where(WorkflowRun.id == self.workflow_run_id).first()
  1050. return None
  1051. def to_dict(self) -> dict[str, Any]:
  1052. return {
  1053. "id": self.id,
  1054. "app_id": self.app_id,
  1055. "conversation_id": self.conversation_id,
  1056. "model_id": self.model_id,
  1057. "inputs": self.inputs,
  1058. "query": self.query,
  1059. "total_price": self.total_price,
  1060. "message": self.message,
  1061. "answer": self.answer,
  1062. "status": self.status,
  1063. "error": self.error,
  1064. "message_metadata": self.message_metadata_dict,
  1065. "from_source": self.from_source,
  1066. "from_end_user_id": self.from_end_user_id,
  1067. "from_account_id": self.from_account_id,
  1068. "created_at": self.created_at.isoformat(),
  1069. "updated_at": self.updated_at.isoformat(),
  1070. "agent_based": self.agent_based,
  1071. "workflow_run_id": self.workflow_run_id,
  1072. }
  1073. @classmethod
  1074. def from_dict(cls, data: dict[str, Any]) -> "Message":
  1075. return cls(
  1076. id=data["id"],
  1077. app_id=data["app_id"],
  1078. conversation_id=data["conversation_id"],
  1079. model_id=data["model_id"],
  1080. inputs=data["inputs"],
  1081. total_price=data["total_price"],
  1082. query=data["query"],
  1083. message=data["message"],
  1084. answer=data["answer"],
  1085. status=data["status"],
  1086. error=data["error"],
  1087. message_metadata=json.dumps(data["message_metadata"]),
  1088. from_source=data["from_source"],
  1089. from_end_user_id=data["from_end_user_id"],
  1090. from_account_id=data["from_account_id"],
  1091. created_at=data["created_at"],
  1092. updated_at=data["updated_at"],
  1093. agent_based=data["agent_based"],
  1094. workflow_run_id=data["workflow_run_id"],
  1095. )
  1096. class MessageFeedback(Base):
  1097. __tablename__ = "message_feedbacks"
  1098. __table_args__ = (
  1099. sa.PrimaryKeyConstraint("id", name="message_feedback_pkey"),
  1100. sa.Index("message_feedback_app_idx", "app_id"),
  1101. sa.Index("message_feedback_message_idx", "message_id", "from_source"),
  1102. sa.Index("message_feedback_conversation_idx", "conversation_id", "from_source", "rating"),
  1103. )
  1104. id = mapped_column(StringUUID, server_default=sa.text("uuid_generate_v4()"))
  1105. app_id = mapped_column(StringUUID, nullable=False)
  1106. conversation_id = mapped_column(StringUUID, nullable=False)
  1107. message_id = mapped_column(StringUUID, nullable=False)
  1108. rating: Mapped[str] = mapped_column(String(255), nullable=False)
  1109. content = mapped_column(sa.Text)
  1110. from_source: Mapped[str] = mapped_column(String(255), nullable=False)
  1111. from_end_user_id = mapped_column(StringUUID)
  1112. from_account_id = mapped_column(StringUUID)
  1113. created_at = mapped_column(sa.DateTime, nullable=False, server_default=func.current_timestamp())
  1114. updated_at = mapped_column(sa.DateTime, nullable=False, server_default=func.current_timestamp())
  1115. @property
  1116. def from_account(self):
  1117. account = db.session.query(Account).where(Account.id == self.from_account_id).first()
  1118. return account
  1119. def to_dict(self) -> dict[str, Any]:
  1120. return {
  1121. "id": str(self.id),
  1122. "app_id": str(self.app_id),
  1123. "conversation_id": str(self.conversation_id),
  1124. "message_id": str(self.message_id),
  1125. "rating": self.rating,
  1126. "content": self.content,
  1127. "from_source": self.from_source,
  1128. "from_end_user_id": str(self.from_end_user_id) if self.from_end_user_id else None,
  1129. "from_account_id": str(self.from_account_id) if self.from_account_id else None,
  1130. "created_at": self.created_at.isoformat(),
  1131. "updated_at": self.updated_at.isoformat(),
  1132. }
  1133. class MessageFile(Base):
  1134. __tablename__ = "message_files"
  1135. __table_args__ = (
  1136. sa.PrimaryKeyConstraint("id", name="message_file_pkey"),
  1137. sa.Index("message_file_message_idx", "message_id"),
  1138. sa.Index("message_file_created_by_idx", "created_by"),
  1139. )
  1140. def __init__(
  1141. self,
  1142. *,
  1143. message_id: str,
  1144. type: FileType,
  1145. transfer_method: FileTransferMethod,
  1146. url: str | None = None,
  1147. belongs_to: Literal["user", "assistant"] | None = None,
  1148. upload_file_id: str | None = None,
  1149. created_by_role: CreatorUserRole,
  1150. created_by: str,
  1151. ):
  1152. self.message_id = message_id
  1153. self.type = type
  1154. self.transfer_method = transfer_method
  1155. self.url = url
  1156. self.belongs_to = belongs_to
  1157. self.upload_file_id = upload_file_id
  1158. self.created_by_role = created_by_role.value
  1159. self.created_by = created_by
  1160. id: Mapped[str] = mapped_column(StringUUID, server_default=sa.text("uuid_generate_v4()"))
  1161. message_id: Mapped[str] = mapped_column(StringUUID, nullable=False)
  1162. type: Mapped[str] = mapped_column(String(255), nullable=False)
  1163. transfer_method: Mapped[str] = mapped_column(String(255), nullable=False)
  1164. url: Mapped[Optional[str]] = mapped_column(sa.Text, nullable=True)
  1165. belongs_to: Mapped[Optional[str]] = mapped_column(String(255), nullable=True)
  1166. upload_file_id: Mapped[Optional[str]] = mapped_column(StringUUID, nullable=True)
  1167. created_by_role: Mapped[str] = mapped_column(String(255), nullable=False)
  1168. created_by: Mapped[str] = mapped_column(StringUUID, nullable=False)
  1169. created_at: Mapped[datetime] = mapped_column(sa.DateTime, nullable=False, server_default=func.current_timestamp())
  1170. class MessageAnnotation(Base):
  1171. __tablename__ = "message_annotations"
  1172. __table_args__ = (
  1173. sa.PrimaryKeyConstraint("id", name="message_annotation_pkey"),
  1174. sa.Index("message_annotation_app_idx", "app_id"),
  1175. sa.Index("message_annotation_conversation_idx", "conversation_id"),
  1176. sa.Index("message_annotation_message_idx", "message_id"),
  1177. )
  1178. id: Mapped[str] = mapped_column(StringUUID, server_default=sa.text("uuid_generate_v4()"))
  1179. app_id: Mapped[str] = mapped_column(StringUUID)
  1180. conversation_id: Mapped[Optional[str]] = mapped_column(StringUUID, sa.ForeignKey("conversations.id"))
  1181. message_id: Mapped[Optional[str]] = mapped_column(StringUUID)
  1182. question = mapped_column(sa.Text, nullable=True)
  1183. content = mapped_column(sa.Text, nullable=False)
  1184. hit_count: Mapped[int] = mapped_column(sa.Integer, nullable=False, server_default=sa.text("0"))
  1185. account_id = mapped_column(StringUUID, nullable=False)
  1186. created_at = mapped_column(sa.DateTime, nullable=False, server_default=func.current_timestamp())
  1187. updated_at = mapped_column(sa.DateTime, nullable=False, server_default=func.current_timestamp())
  1188. @property
  1189. def account(self):
  1190. account = db.session.query(Account).where(Account.id == self.account_id).first()
  1191. return account
  1192. @property
  1193. def annotation_create_account(self):
  1194. account = db.session.query(Account).where(Account.id == self.account_id).first()
  1195. return account
  1196. class AppAnnotationHitHistory(Base):
  1197. __tablename__ = "app_annotation_hit_histories"
  1198. __table_args__ = (
  1199. sa.PrimaryKeyConstraint("id", name="app_annotation_hit_histories_pkey"),
  1200. sa.Index("app_annotation_hit_histories_app_idx", "app_id"),
  1201. sa.Index("app_annotation_hit_histories_account_idx", "account_id"),
  1202. sa.Index("app_annotation_hit_histories_annotation_idx", "annotation_id"),
  1203. sa.Index("app_annotation_hit_histories_message_idx", "message_id"),
  1204. )
  1205. id = mapped_column(StringUUID, server_default=sa.text("uuid_generate_v4()"))
  1206. app_id = mapped_column(StringUUID, nullable=False)
  1207. annotation_id: Mapped[str] = mapped_column(StringUUID, nullable=False)
  1208. source = mapped_column(sa.Text, nullable=False)
  1209. question = mapped_column(sa.Text, nullable=False)
  1210. account_id = mapped_column(StringUUID, nullable=False)
  1211. created_at = mapped_column(sa.DateTime, nullable=False, server_default=func.current_timestamp())
  1212. score = mapped_column(Float, nullable=False, server_default=sa.text("0"))
  1213. message_id = mapped_column(StringUUID, nullable=False)
  1214. annotation_question = mapped_column(sa.Text, nullable=False)
  1215. annotation_content = mapped_column(sa.Text, nullable=False)
  1216. @property
  1217. def account(self):
  1218. account = (
  1219. db.session.query(Account)
  1220. .join(MessageAnnotation, MessageAnnotation.account_id == Account.id)
  1221. .where(MessageAnnotation.id == self.annotation_id)
  1222. .first()
  1223. )
  1224. return account
  1225. @property
  1226. def annotation_create_account(self):
  1227. account = db.session.query(Account).where(Account.id == self.account_id).first()
  1228. return account
  1229. class AppAnnotationSetting(Base):
  1230. __tablename__ = "app_annotation_settings"
  1231. __table_args__ = (
  1232. sa.PrimaryKeyConstraint("id", name="app_annotation_settings_pkey"),
  1233. sa.Index("app_annotation_settings_app_idx", "app_id"),
  1234. )
  1235. id = mapped_column(StringUUID, server_default=sa.text("uuid_generate_v4()"))
  1236. app_id = mapped_column(StringUUID, nullable=False)
  1237. score_threshold = mapped_column(Float, nullable=False, server_default=sa.text("0"))
  1238. collection_binding_id = mapped_column(StringUUID, nullable=False)
  1239. created_user_id = mapped_column(StringUUID, nullable=False)
  1240. created_at = mapped_column(sa.DateTime, nullable=False, server_default=func.current_timestamp())
  1241. updated_user_id = mapped_column(StringUUID, nullable=False)
  1242. updated_at = mapped_column(sa.DateTime, nullable=False, server_default=func.current_timestamp())
  1243. @property
  1244. def collection_binding_detail(self):
  1245. from .dataset import DatasetCollectionBinding
  1246. collection_binding_detail = (
  1247. db.session.query(DatasetCollectionBinding)
  1248. .where(DatasetCollectionBinding.id == self.collection_binding_id)
  1249. .first()
  1250. )
  1251. return collection_binding_detail
  1252. class OperationLog(Base):
  1253. __tablename__ = "operation_logs"
  1254. __table_args__ = (
  1255. sa.PrimaryKeyConstraint("id", name="operation_log_pkey"),
  1256. sa.Index("operation_log_account_action_idx", "tenant_id", "account_id", "action"),
  1257. )
  1258. id = mapped_column(StringUUID, server_default=sa.text("uuid_generate_v4()"))
  1259. tenant_id = mapped_column(StringUUID, nullable=False)
  1260. account_id = mapped_column(StringUUID, nullable=False)
  1261. action: Mapped[str] = mapped_column(String(255), nullable=False)
  1262. content = mapped_column(sa.JSON)
  1263. created_at = mapped_column(sa.DateTime, nullable=False, server_default=func.current_timestamp())
  1264. created_ip: Mapped[str] = mapped_column(String(255), nullable=False)
  1265. updated_at = mapped_column(sa.DateTime, nullable=False, server_default=func.current_timestamp())
  1266. class EndUser(Base, UserMixin):
  1267. __tablename__ = "end_users"
  1268. __table_args__ = (
  1269. sa.PrimaryKeyConstraint("id", name="end_user_pkey"),
  1270. sa.Index("end_user_session_id_idx", "session_id", "type"),
  1271. sa.Index("end_user_tenant_session_id_idx", "tenant_id", "session_id", "type"),
  1272. )
  1273. id = mapped_column(StringUUID, server_default=sa.text("uuid_generate_v4()"))
  1274. tenant_id: Mapped[str] = mapped_column(StringUUID, nullable=False)
  1275. app_id = mapped_column(StringUUID, nullable=True)
  1276. type: Mapped[str] = mapped_column(String(255), nullable=False)
  1277. external_user_id = mapped_column(String(255), nullable=True)
  1278. name = mapped_column(String(255))
  1279. _is_anonymous: Mapped[bool] = mapped_column(
  1280. "is_anonymous", sa.Boolean, nullable=False, server_default=sa.text("true")
  1281. )
  1282. @property
  1283. def is_anonymous(self) -> Literal[False]:
  1284. return False
  1285. @is_anonymous.setter
  1286. def is_anonymous(self, value: bool) -> None:
  1287. self._is_anonymous = value
  1288. session_id: Mapped[str] = mapped_column()
  1289. created_at = mapped_column(sa.DateTime, nullable=False, server_default=func.current_timestamp())
  1290. updated_at = mapped_column(sa.DateTime, nullable=False, server_default=func.current_timestamp())
  1291. class AppMCPServer(Base):
  1292. __tablename__ = "app_mcp_servers"
  1293. __table_args__ = (
  1294. sa.PrimaryKeyConstraint("id", name="app_mcp_server_pkey"),
  1295. sa.UniqueConstraint("tenant_id", "app_id", name="unique_app_mcp_server_tenant_app_id"),
  1296. sa.UniqueConstraint("server_code", name="unique_app_mcp_server_server_code"),
  1297. )
  1298. id = mapped_column(StringUUID, server_default=sa.text("uuid_generate_v4()"))
  1299. tenant_id = mapped_column(StringUUID, nullable=False)
  1300. app_id = mapped_column(StringUUID, nullable=False)
  1301. name: Mapped[str] = mapped_column(String(255), nullable=False)
  1302. description: Mapped[str] = mapped_column(String(255), nullable=False)
  1303. server_code: Mapped[str] = mapped_column(String(255), nullable=False)
  1304. status = mapped_column(String(255), nullable=False, server_default=sa.text("'normal'::character varying"))
  1305. parameters = mapped_column(sa.Text, nullable=False)
  1306. created_at = mapped_column(sa.DateTime, nullable=False, server_default=func.current_timestamp())
  1307. updated_at = mapped_column(sa.DateTime, nullable=False, server_default=func.current_timestamp())
  1308. @staticmethod
  1309. def generate_server_code(n: int) -> str:
  1310. while True:
  1311. result = generate_string(n)
  1312. while db.session.query(AppMCPServer).where(AppMCPServer.server_code == result).count() > 0:
  1313. result = generate_string(n)
  1314. return result
  1315. @property
  1316. def parameters_dict(self) -> dict[str, Any]:
  1317. return cast(dict[str, Any], json.loads(self.parameters))
  1318. class Site(Base):
  1319. __tablename__ = "sites"
  1320. __table_args__ = (
  1321. sa.PrimaryKeyConstraint("id", name="site_pkey"),
  1322. sa.Index("site_app_id_idx", "app_id"),
  1323. sa.Index("site_code_idx", "code", "status"),
  1324. )
  1325. id = mapped_column(StringUUID, server_default=sa.text("uuid_generate_v4()"))
  1326. app_id = mapped_column(StringUUID, nullable=False)
  1327. title: Mapped[str] = mapped_column(String(255), nullable=False)
  1328. icon_type = mapped_column(String(255), nullable=True)
  1329. icon = mapped_column(String(255))
  1330. icon_background = mapped_column(String(255))
  1331. description = mapped_column(sa.Text)
  1332. default_language: Mapped[str] = mapped_column(String(255), nullable=False)
  1333. chat_color_theme = mapped_column(String(255))
  1334. chat_color_theme_inverted: Mapped[bool] = mapped_column(sa.Boolean, nullable=False, server_default=sa.text("false"))
  1335. copyright = mapped_column(String(255))
  1336. privacy_policy = mapped_column(String(255))
  1337. show_workflow_steps: Mapped[bool] = mapped_column(sa.Boolean, nullable=False, server_default=sa.text("true"))
  1338. use_icon_as_answer_icon: Mapped[bool] = mapped_column(sa.Boolean, nullable=False, server_default=sa.text("false"))
  1339. _custom_disclaimer: Mapped[str] = mapped_column("custom_disclaimer", sa.TEXT, default="")
  1340. customize_domain = mapped_column(String(255))
  1341. customize_token_strategy: Mapped[str] = mapped_column(String(255), nullable=False)
  1342. prompt_public: Mapped[bool] = mapped_column(sa.Boolean, nullable=False, server_default=sa.text("false"))
  1343. status = mapped_column(String(255), nullable=False, server_default=sa.text("'normal'::character varying"))
  1344. created_by = mapped_column(StringUUID, nullable=True)
  1345. created_at = mapped_column(sa.DateTime, nullable=False, server_default=func.current_timestamp())
  1346. updated_by = mapped_column(StringUUID, nullable=True)
  1347. updated_at = mapped_column(sa.DateTime, nullable=False, server_default=func.current_timestamp())
  1348. code = mapped_column(String(255))
  1349. @property
  1350. def custom_disclaimer(self):
  1351. return self._custom_disclaimer
  1352. @custom_disclaimer.setter
  1353. def custom_disclaimer(self, value: str):
  1354. if len(value) > 512:
  1355. raise ValueError("Custom disclaimer cannot exceed 512 characters.")
  1356. self._custom_disclaimer = value
  1357. @staticmethod
  1358. def generate_code(n: int) -> str:
  1359. while True:
  1360. result = generate_string(n)
  1361. while db.session.query(Site).where(Site.code == result).count() > 0:
  1362. result = generate_string(n)
  1363. return result
  1364. @property
  1365. def app_base_url(self):
  1366. return dify_config.APP_WEB_URL or request.url_root.rstrip("/")
  1367. class ApiToken(Base):
  1368. __tablename__ = "api_tokens"
  1369. __table_args__ = (
  1370. sa.PrimaryKeyConstraint("id", name="api_token_pkey"),
  1371. sa.Index("api_token_app_id_type_idx", "app_id", "type"),
  1372. sa.Index("api_token_token_idx", "token", "type"),
  1373. sa.Index("api_token_tenant_idx", "tenant_id", "type"),
  1374. )
  1375. id = mapped_column(StringUUID, server_default=sa.text("uuid_generate_v4()"))
  1376. app_id = mapped_column(StringUUID, nullable=True)
  1377. tenant_id = mapped_column(StringUUID, nullable=True)
  1378. type = mapped_column(String(16), nullable=False)
  1379. token: Mapped[str] = mapped_column(String(255), nullable=False)
  1380. last_used_at = mapped_column(sa.DateTime, nullable=True)
  1381. created_at = mapped_column(sa.DateTime, nullable=False, server_default=func.current_timestamp())
  1382. @staticmethod
  1383. def generate_api_key(prefix: str, n: int) -> str:
  1384. while True:
  1385. result = prefix + generate_string(n)
  1386. if db.session.scalar(select(exists().where(ApiToken.token == result))):
  1387. continue
  1388. return result
  1389. class UploadFile(Base):
  1390. __tablename__ = "upload_files"
  1391. __table_args__ = (
  1392. sa.PrimaryKeyConstraint("id", name="upload_file_pkey"),
  1393. sa.Index("upload_file_tenant_idx", "tenant_id"),
  1394. )
  1395. id: Mapped[str] = mapped_column(StringUUID, server_default=sa.text("uuid_generate_v4()"))
  1396. tenant_id: Mapped[str] = mapped_column(StringUUID, nullable=False)
  1397. storage_type: Mapped[str] = mapped_column(String(255), nullable=False)
  1398. key: Mapped[str] = mapped_column(String(255), nullable=False)
  1399. name: Mapped[str] = mapped_column(String(255), nullable=False)
  1400. size: Mapped[int] = mapped_column(sa.Integer, nullable=False)
  1401. extension: Mapped[str] = mapped_column(String(255), nullable=False)
  1402. mime_type: Mapped[str] = mapped_column(String(255), nullable=True)
  1403. created_by_role: Mapped[str] = mapped_column(
  1404. String(255), nullable=False, server_default=sa.text("'account'::character varying")
  1405. )
  1406. created_by: Mapped[str] = mapped_column(StringUUID, nullable=False)
  1407. created_at: Mapped[datetime] = mapped_column(sa.DateTime, nullable=False, server_default=func.current_timestamp())
  1408. used: Mapped[bool] = mapped_column(sa.Boolean, nullable=False, server_default=sa.text("false"))
  1409. used_by: Mapped[str | None] = mapped_column(StringUUID, nullable=True)
  1410. used_at: Mapped[datetime | None] = mapped_column(sa.DateTime, nullable=True)
  1411. hash: Mapped[str | None] = mapped_column(String(255), nullable=True)
  1412. source_url: Mapped[str] = mapped_column(sa.TEXT, default="")
  1413. def __init__(
  1414. self,
  1415. *,
  1416. tenant_id: str,
  1417. storage_type: str,
  1418. key: str,
  1419. name: str,
  1420. size: int,
  1421. extension: str,
  1422. mime_type: str,
  1423. created_by_role: CreatorUserRole,
  1424. created_by: str,
  1425. created_at: datetime,
  1426. used: bool,
  1427. used_by: str | None = None,
  1428. used_at: datetime | None = None,
  1429. hash: str | None = None,
  1430. source_url: str = "",
  1431. ):
  1432. self.tenant_id = tenant_id
  1433. self.storage_type = storage_type
  1434. self.key = key
  1435. self.name = name
  1436. self.size = size
  1437. self.extension = extension
  1438. self.mime_type = mime_type
  1439. self.created_by_role = created_by_role.value
  1440. self.created_by = created_by
  1441. self.created_at = created_at
  1442. self.used = used
  1443. self.used_by = used_by
  1444. self.used_at = used_at
  1445. self.hash = hash
  1446. self.source_url = source_url
  1447. class ApiRequest(Base):
  1448. __tablename__ = "api_requests"
  1449. __table_args__ = (
  1450. sa.PrimaryKeyConstraint("id", name="api_request_pkey"),
  1451. sa.Index("api_request_token_idx", "tenant_id", "api_token_id"),
  1452. )
  1453. id = mapped_column(StringUUID, nullable=False, server_default=sa.text("uuid_generate_v4()"))
  1454. tenant_id = mapped_column(StringUUID, nullable=False)
  1455. api_token_id = mapped_column(StringUUID, nullable=False)
  1456. path: Mapped[str] = mapped_column(String(255), nullable=False)
  1457. request = mapped_column(sa.Text, nullable=True)
  1458. response = mapped_column(sa.Text, nullable=True)
  1459. ip: Mapped[str] = mapped_column(String(255), nullable=False)
  1460. created_at = mapped_column(sa.DateTime, nullable=False, server_default=func.current_timestamp())
  1461. class MessageChain(Base):
  1462. __tablename__ = "message_chains"
  1463. __table_args__ = (
  1464. sa.PrimaryKeyConstraint("id", name="message_chain_pkey"),
  1465. sa.Index("message_chain_message_id_idx", "message_id"),
  1466. )
  1467. id = mapped_column(StringUUID, nullable=False, server_default=sa.text("uuid_generate_v4()"))
  1468. message_id = mapped_column(StringUUID, nullable=False)
  1469. type: Mapped[str] = mapped_column(String(255), nullable=False)
  1470. input = mapped_column(sa.Text, nullable=True)
  1471. output = mapped_column(sa.Text, nullable=True)
  1472. created_at = mapped_column(sa.DateTime, nullable=False, server_default=db.func.current_timestamp())
  1473. class MessageAgentThought(Base):
  1474. __tablename__ = "message_agent_thoughts"
  1475. __table_args__ = (
  1476. sa.PrimaryKeyConstraint("id", name="message_agent_thought_pkey"),
  1477. sa.Index("message_agent_thought_message_id_idx", "message_id"),
  1478. sa.Index("message_agent_thought_message_chain_id_idx", "message_chain_id"),
  1479. )
  1480. id = mapped_column(StringUUID, nullable=False, server_default=sa.text("uuid_generate_v4()"))
  1481. message_id = mapped_column(StringUUID, nullable=False)
  1482. message_chain_id = mapped_column(StringUUID, nullable=True)
  1483. position: Mapped[int] = mapped_column(sa.Integer, nullable=False)
  1484. thought = mapped_column(sa.Text, nullable=True)
  1485. tool = mapped_column(sa.Text, nullable=True)
  1486. tool_labels_str = mapped_column(sa.Text, nullable=False, server_default=sa.text("'{}'::text"))
  1487. tool_meta_str = mapped_column(sa.Text, nullable=False, server_default=sa.text("'{}'::text"))
  1488. tool_input = mapped_column(sa.Text, nullable=True)
  1489. observation = mapped_column(sa.Text, nullable=True)
  1490. # plugin_id = mapped_column(StringUUID, nullable=True) ## for future design
  1491. tool_process_data = mapped_column(sa.Text, nullable=True)
  1492. message = mapped_column(sa.Text, nullable=True)
  1493. message_token: Mapped[Optional[int]] = mapped_column(sa.Integer, nullable=True)
  1494. message_unit_price = mapped_column(sa.Numeric, nullable=True)
  1495. message_price_unit = mapped_column(sa.Numeric(10, 7), nullable=False, server_default=sa.text("0.001"))
  1496. message_files = mapped_column(sa.Text, nullable=True)
  1497. answer = mapped_column(sa.Text, nullable=True)
  1498. answer_token: Mapped[Optional[int]] = mapped_column(sa.Integer, nullable=True)
  1499. answer_unit_price = mapped_column(sa.Numeric, nullable=True)
  1500. answer_price_unit = mapped_column(sa.Numeric(10, 7), nullable=False, server_default=sa.text("0.001"))
  1501. tokens: Mapped[Optional[int]] = mapped_column(sa.Integer, nullable=True)
  1502. total_price = mapped_column(sa.Numeric, nullable=True)
  1503. currency = mapped_column(String, nullable=True)
  1504. latency: Mapped[Optional[float]] = mapped_column(sa.Float, nullable=True)
  1505. created_by_role = mapped_column(String, nullable=False)
  1506. created_by = mapped_column(StringUUID, nullable=False)
  1507. created_at = mapped_column(sa.DateTime, nullable=False, server_default=db.func.current_timestamp())
  1508. @property
  1509. def files(self) -> list[Any]:
  1510. if self.message_files:
  1511. return cast(list[Any], json.loads(self.message_files))
  1512. else:
  1513. return []
  1514. @property
  1515. def tools(self) -> list[str]:
  1516. return self.tool.split(";") if self.tool else []
  1517. @property
  1518. def tool_labels(self) -> dict[str, Any]:
  1519. try:
  1520. if self.tool_labels_str:
  1521. return cast(dict[str, Any], json.loads(self.tool_labels_str))
  1522. else:
  1523. return {}
  1524. except Exception:
  1525. return {}
  1526. @property
  1527. def tool_meta(self) -> dict[str, Any]:
  1528. try:
  1529. if self.tool_meta_str:
  1530. return cast(dict[str, Any], json.loads(self.tool_meta_str))
  1531. else:
  1532. return {}
  1533. except Exception:
  1534. return {}
  1535. @property
  1536. def tool_inputs_dict(self) -> dict[str, Any]:
  1537. tools = self.tools
  1538. try:
  1539. if self.tool_input:
  1540. data = json.loads(self.tool_input)
  1541. result: dict[str, Any] = {}
  1542. for tool in tools:
  1543. if tool in data:
  1544. result[tool] = data[tool]
  1545. else:
  1546. if len(tools) == 1:
  1547. result[tool] = data
  1548. else:
  1549. result[tool] = {}
  1550. return result
  1551. else:
  1552. return {tool: {} for tool in tools}
  1553. except Exception:
  1554. return {}
  1555. @property
  1556. def tool_outputs_dict(self) -> dict[str, Any]:
  1557. tools = self.tools
  1558. try:
  1559. if self.observation:
  1560. data = json.loads(self.observation)
  1561. result: dict[str, Any] = {}
  1562. for tool in tools:
  1563. if tool in data:
  1564. result[tool] = data[tool]
  1565. else:
  1566. if len(tools) == 1:
  1567. result[tool] = data
  1568. else:
  1569. result[tool] = {}
  1570. return result
  1571. else:
  1572. return {tool: {} for tool in tools}
  1573. except Exception:
  1574. if self.observation:
  1575. return dict.fromkeys(tools, self.observation)
  1576. else:
  1577. return {}
  1578. class DatasetRetrieverResource(Base):
  1579. __tablename__ = "dataset_retriever_resources"
  1580. __table_args__ = (
  1581. sa.PrimaryKeyConstraint("id", name="dataset_retriever_resource_pkey"),
  1582. sa.Index("dataset_retriever_resource_message_id_idx", "message_id"),
  1583. )
  1584. id = mapped_column(StringUUID, nullable=False, server_default=sa.text("uuid_generate_v4()"))
  1585. message_id = mapped_column(StringUUID, nullable=False)
  1586. position: Mapped[int] = mapped_column(sa.Integer, nullable=False)
  1587. dataset_id = mapped_column(StringUUID, nullable=False)
  1588. dataset_name = mapped_column(sa.Text, nullable=False)
  1589. document_id = mapped_column(StringUUID, nullable=True)
  1590. document_name = mapped_column(sa.Text, nullable=False)
  1591. data_source_type = mapped_column(sa.Text, nullable=True)
  1592. segment_id = mapped_column(StringUUID, nullable=True)
  1593. score: Mapped[Optional[float]] = mapped_column(sa.Float, nullable=True)
  1594. content = mapped_column(sa.Text, nullable=False)
  1595. hit_count: Mapped[Optional[int]] = mapped_column(sa.Integer, nullable=True)
  1596. word_count: Mapped[Optional[int]] = mapped_column(sa.Integer, nullable=True)
  1597. segment_position: Mapped[Optional[int]] = mapped_column(sa.Integer, nullable=True)
  1598. index_node_hash = mapped_column(sa.Text, nullable=True)
  1599. retriever_from = mapped_column(sa.Text, nullable=False)
  1600. created_by = mapped_column(StringUUID, nullable=False)
  1601. created_at = mapped_column(sa.DateTime, nullable=False, server_default=db.func.current_timestamp())
  1602. class Tag(Base):
  1603. __tablename__ = "tags"
  1604. __table_args__ = (
  1605. sa.PrimaryKeyConstraint("id", name="tag_pkey"),
  1606. sa.Index("tag_type_idx", "type"),
  1607. sa.Index("tag_name_idx", "name"),
  1608. )
  1609. TAG_TYPE_LIST = ["knowledge", "app"]
  1610. id = mapped_column(StringUUID, server_default=sa.text("uuid_generate_v4()"))
  1611. tenant_id = mapped_column(StringUUID, nullable=True)
  1612. type = mapped_column(String(16), nullable=False)
  1613. name: Mapped[str] = mapped_column(String(255), nullable=False)
  1614. created_by = mapped_column(StringUUID, nullable=False)
  1615. created_at = mapped_column(sa.DateTime, nullable=False, server_default=func.current_timestamp())
  1616. class TagBinding(Base):
  1617. __tablename__ = "tag_bindings"
  1618. __table_args__ = (
  1619. sa.PrimaryKeyConstraint("id", name="tag_binding_pkey"),
  1620. sa.Index("tag_bind_target_id_idx", "target_id"),
  1621. sa.Index("tag_bind_tag_id_idx", "tag_id"),
  1622. )
  1623. id = mapped_column(StringUUID, server_default=sa.text("uuid_generate_v4()"))
  1624. tenant_id = mapped_column(StringUUID, nullable=True)
  1625. tag_id = mapped_column(StringUUID, nullable=True)
  1626. target_id = mapped_column(StringUUID, nullable=True)
  1627. created_by = mapped_column(StringUUID, nullable=False)
  1628. created_at = mapped_column(sa.DateTime, nullable=False, server_default=func.current_timestamp())
  1629. class TraceAppConfig(Base):
  1630. __tablename__ = "trace_app_config"
  1631. __table_args__ = (
  1632. sa.PrimaryKeyConstraint("id", name="tracing_app_config_pkey"),
  1633. sa.Index("trace_app_config_app_id_idx", "app_id"),
  1634. )
  1635. id = mapped_column(StringUUID, server_default=sa.text("uuid_generate_v4()"))
  1636. app_id = mapped_column(StringUUID, nullable=False)
  1637. tracing_provider = mapped_column(String(255), nullable=True)
  1638. tracing_config = mapped_column(sa.JSON, nullable=True)
  1639. created_at = mapped_column(sa.DateTime, nullable=False, server_default=func.current_timestamp())
  1640. updated_at = mapped_column(
  1641. sa.DateTime, nullable=False, server_default=func.current_timestamp(), onupdate=func.current_timestamp()
  1642. )
  1643. is_active: Mapped[bool] = mapped_column(sa.Boolean, nullable=False, server_default=sa.text("true"))
  1644. @property
  1645. def tracing_config_dict(self) -> dict[str, Any]:
  1646. return self.tracing_config or {}
  1647. @property
  1648. def tracing_config_str(self) -> str:
  1649. return json.dumps(self.tracing_config_dict)
  1650. def to_dict(self) -> dict[str, Any]:
  1651. return {
  1652. "id": self.id,
  1653. "app_id": self.app_id,
  1654. "tracing_provider": self.tracing_provider,
  1655. "tracing_config": self.tracing_config_dict,
  1656. "is_active": self.is_active,
  1657. "created_at": str(self.created_at) if self.created_at else None,
  1658. "updated_at": str(self.updated_at) if self.updated_at else None,
  1659. }