model.py 67 KB

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