model.py 66 KB

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