model.py 68 KB

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