model.py 66 KB

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