model.py 66 KB

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