model.py 66 KB

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