model.py 74 KB

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