model.py 69 KB

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