model.py 69 KB

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