model.py 66 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655
  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. mapping = {
  846. "id": message_file.id,
  847. "type": message_file.type,
  848. "transfer_method": message_file.transfer_method,
  849. "tool_file_id": message_file.upload_file_id,
  850. }
  851. file = file_factory.build_from_mapping(
  852. mapping=mapping,
  853. tenant_id=current_app.tenant_id,
  854. user_id=self.from_account_id or self.from_end_user_id or "",
  855. role=CreatedByRole(message_file.created_by_role),
  856. config=FileExtraConfig(),
  857. )
  858. else:
  859. raise ValueError(
  860. f"MessageFile {message_file.id} has an invalid transfer_method {message_file.transfer_method}"
  861. )
  862. files.append(file)
  863. result = [
  864. {"belongs_to": message_file.belongs_to, **file.to_dict()}
  865. for (file, message_file) in zip(files, message_files)
  866. ]
  867. return result
  868. @property
  869. def workflow_run(self):
  870. if self.workflow_run_id:
  871. from .workflow import WorkflowRun
  872. return db.session.query(WorkflowRun).filter(WorkflowRun.id == self.workflow_run_id).first()
  873. return None
  874. def to_dict(self) -> dict:
  875. return {
  876. "id": self.id,
  877. "app_id": self.app_id,
  878. "conversation_id": self.conversation_id,
  879. "inputs": self.inputs,
  880. "query": self.query,
  881. "message": self.message,
  882. "answer": self.answer,
  883. "status": self.status,
  884. "error": self.error,
  885. "message_metadata": self.message_metadata_dict,
  886. "from_source": self.from_source,
  887. "from_end_user_id": self.from_end_user_id,
  888. "from_account_id": self.from_account_id,
  889. "created_at": self.created_at.isoformat(),
  890. "updated_at": self.updated_at.isoformat(),
  891. "agent_based": self.agent_based,
  892. "workflow_run_id": self.workflow_run_id,
  893. }
  894. @classmethod
  895. def from_dict(cls, data: dict):
  896. return cls(
  897. id=data["id"],
  898. app_id=data["app_id"],
  899. conversation_id=data["conversation_id"],
  900. inputs=data["inputs"],
  901. query=data["query"],
  902. message=data["message"],
  903. answer=data["answer"],
  904. status=data["status"],
  905. error=data["error"],
  906. message_metadata=json.dumps(data["message_metadata"]),
  907. from_source=data["from_source"],
  908. from_end_user_id=data["from_end_user_id"],
  909. from_account_id=data["from_account_id"],
  910. created_at=data["created_at"],
  911. updated_at=data["updated_at"],
  912. agent_based=data["agent_based"],
  913. workflow_run_id=data["workflow_run_id"],
  914. )
  915. class MessageFeedback(db.Model):
  916. __tablename__ = "message_feedbacks"
  917. __table_args__ = (
  918. db.PrimaryKeyConstraint("id", name="message_feedback_pkey"),
  919. db.Index("message_feedback_app_idx", "app_id"),
  920. db.Index("message_feedback_message_idx", "message_id", "from_source"),
  921. db.Index("message_feedback_conversation_idx", "conversation_id", "from_source", "rating"),
  922. )
  923. id = db.Column(StringUUID, server_default=db.text("uuid_generate_v4()"))
  924. app_id = db.Column(StringUUID, nullable=False)
  925. conversation_id = db.Column(StringUUID, nullable=False)
  926. message_id = db.Column(StringUUID, nullable=False)
  927. rating = db.Column(db.String(255), nullable=False)
  928. content = db.Column(db.Text)
  929. from_source = db.Column(db.String(255), nullable=False)
  930. from_end_user_id = db.Column(StringUUID)
  931. from_account_id = db.Column(StringUUID)
  932. created_at = db.Column(db.DateTime, nullable=False, server_default=db.text("CURRENT_TIMESTAMP(0)"))
  933. updated_at = db.Column(db.DateTime, nullable=False, server_default=db.text("CURRENT_TIMESTAMP(0)"))
  934. @property
  935. def from_account(self):
  936. account = db.session.query(Account).filter(Account.id == self.from_account_id).first()
  937. return account
  938. class MessageFile(db.Model):
  939. __tablename__ = "message_files"
  940. __table_args__ = (
  941. db.PrimaryKeyConstraint("id", name="message_file_pkey"),
  942. db.Index("message_file_message_idx", "message_id"),
  943. db.Index("message_file_created_by_idx", "created_by"),
  944. )
  945. def __init__(
  946. self,
  947. *,
  948. message_id: str,
  949. type: FileType,
  950. transfer_method: FileTransferMethod,
  951. url: str | None = None,
  952. belongs_to: Literal["user", "assistant"] | None = None,
  953. upload_file_id: str | None = None,
  954. created_by_role: CreatedByRole,
  955. created_by: str,
  956. ):
  957. self.message_id = message_id
  958. self.type = type
  959. self.transfer_method = transfer_method
  960. self.url = url
  961. self.belongs_to = belongs_to
  962. self.upload_file_id = upload_file_id
  963. self.created_by_role = created_by_role
  964. self.created_by = created_by
  965. id: Mapped[str] = db.Column(StringUUID, server_default=db.text("uuid_generate_v4()"))
  966. message_id: Mapped[str] = db.Column(StringUUID, nullable=False)
  967. type: Mapped[str] = db.Column(db.String(255), nullable=False)
  968. transfer_method: Mapped[str] = db.Column(db.String(255), nullable=False)
  969. url: Mapped[Optional[str]] = db.Column(db.Text, nullable=True)
  970. belongs_to: Mapped[Optional[str]] = db.Column(db.String(255), nullable=True)
  971. upload_file_id: Mapped[Optional[str]] = db.Column(StringUUID, nullable=True)
  972. created_by_role: Mapped[str] = db.Column(db.String(255), nullable=False)
  973. created_by: Mapped[str] = db.Column(StringUUID, nullable=False)
  974. created_at: Mapped[datetime] = db.Column(
  975. db.DateTime, nullable=False, server_default=db.text("CURRENT_TIMESTAMP(0)")
  976. )
  977. class MessageAnnotation(db.Model):
  978. __tablename__ = "message_annotations"
  979. __table_args__ = (
  980. db.PrimaryKeyConstraint("id", name="message_annotation_pkey"),
  981. db.Index("message_annotation_app_idx", "app_id"),
  982. db.Index("message_annotation_conversation_idx", "conversation_id"),
  983. db.Index("message_annotation_message_idx", "message_id"),
  984. )
  985. id = db.Column(StringUUID, server_default=db.text("uuid_generate_v4()"))
  986. app_id = db.Column(StringUUID, nullable=False)
  987. conversation_id = db.Column(StringUUID, db.ForeignKey("conversations.id"), nullable=True)
  988. message_id = db.Column(StringUUID, nullable=True)
  989. question = db.Column(db.Text, nullable=True)
  990. content = db.Column(db.Text, nullable=False)
  991. hit_count = db.Column(db.Integer, nullable=False, server_default=db.text("0"))
  992. account_id = db.Column(StringUUID, nullable=False)
  993. created_at = db.Column(db.DateTime, nullable=False, server_default=db.text("CURRENT_TIMESTAMP(0)"))
  994. updated_at = db.Column(db.DateTime, nullable=False, server_default=db.text("CURRENT_TIMESTAMP(0)"))
  995. @property
  996. def account(self):
  997. account = db.session.query(Account).filter(Account.id == self.account_id).first()
  998. return account
  999. @property
  1000. def annotation_create_account(self):
  1001. account = db.session.query(Account).filter(Account.id == self.account_id).first()
  1002. return account
  1003. class AppAnnotationHitHistory(db.Model):
  1004. __tablename__ = "app_annotation_hit_histories"
  1005. __table_args__ = (
  1006. db.PrimaryKeyConstraint("id", name="app_annotation_hit_histories_pkey"),
  1007. db.Index("app_annotation_hit_histories_app_idx", "app_id"),
  1008. db.Index("app_annotation_hit_histories_account_idx", "account_id"),
  1009. db.Index("app_annotation_hit_histories_annotation_idx", "annotation_id"),
  1010. db.Index("app_annotation_hit_histories_message_idx", "message_id"),
  1011. )
  1012. id = db.Column(StringUUID, server_default=db.text("uuid_generate_v4()"))
  1013. app_id = db.Column(StringUUID, nullable=False)
  1014. annotation_id = db.Column(StringUUID, nullable=False)
  1015. source = db.Column(db.Text, nullable=False)
  1016. question = db.Column(db.Text, nullable=False)
  1017. account_id = db.Column(StringUUID, nullable=False)
  1018. created_at = db.Column(db.DateTime, nullable=False, server_default=db.text("CURRENT_TIMESTAMP(0)"))
  1019. score = db.Column(Float, nullable=False, server_default=db.text("0"))
  1020. message_id = db.Column(StringUUID, nullable=False)
  1021. annotation_question = db.Column(db.Text, nullable=False)
  1022. annotation_content = db.Column(db.Text, nullable=False)
  1023. @property
  1024. def account(self):
  1025. account = (
  1026. db.session.query(Account)
  1027. .join(MessageAnnotation, MessageAnnotation.account_id == Account.id)
  1028. .filter(MessageAnnotation.id == self.annotation_id)
  1029. .first()
  1030. )
  1031. return account
  1032. @property
  1033. def annotation_create_account(self):
  1034. account = db.session.query(Account).filter(Account.id == self.account_id).first()
  1035. return account
  1036. class AppAnnotationSetting(db.Model):
  1037. __tablename__ = "app_annotation_settings"
  1038. __table_args__ = (
  1039. db.PrimaryKeyConstraint("id", name="app_annotation_settings_pkey"),
  1040. db.Index("app_annotation_settings_app_idx", "app_id"),
  1041. )
  1042. id = db.Column(StringUUID, server_default=db.text("uuid_generate_v4()"))
  1043. app_id = db.Column(StringUUID, nullable=False)
  1044. score_threshold = db.Column(Float, nullable=False, server_default=db.text("0"))
  1045. collection_binding_id = db.Column(StringUUID, nullable=False)
  1046. created_user_id = db.Column(StringUUID, nullable=False)
  1047. created_at = db.Column(db.DateTime, nullable=False, server_default=db.text("CURRENT_TIMESTAMP(0)"))
  1048. updated_user_id = db.Column(StringUUID, nullable=False)
  1049. updated_at = db.Column(db.DateTime, nullable=False, server_default=db.text("CURRENT_TIMESTAMP(0)"))
  1050. @property
  1051. def created_account(self):
  1052. account = (
  1053. db.session.query(Account)
  1054. .join(AppAnnotationSetting, AppAnnotationSetting.created_user_id == Account.id)
  1055. .filter(AppAnnotationSetting.id == self.annotation_id)
  1056. .first()
  1057. )
  1058. return account
  1059. @property
  1060. def updated_account(self):
  1061. account = (
  1062. db.session.query(Account)
  1063. .join(AppAnnotationSetting, AppAnnotationSetting.updated_user_id == Account.id)
  1064. .filter(AppAnnotationSetting.id == self.annotation_id)
  1065. .first()
  1066. )
  1067. return account
  1068. @property
  1069. def collection_binding_detail(self):
  1070. from .dataset import DatasetCollectionBinding
  1071. collection_binding_detail = (
  1072. db.session.query(DatasetCollectionBinding)
  1073. .filter(DatasetCollectionBinding.id == self.collection_binding_id)
  1074. .first()
  1075. )
  1076. return collection_binding_detail
  1077. class OperationLog(db.Model):
  1078. __tablename__ = "operation_logs"
  1079. __table_args__ = (
  1080. db.PrimaryKeyConstraint("id", name="operation_log_pkey"),
  1081. db.Index("operation_log_account_action_idx", "tenant_id", "account_id", "action"),
  1082. )
  1083. id = db.Column(StringUUID, server_default=db.text("uuid_generate_v4()"))
  1084. tenant_id = db.Column(StringUUID, nullable=False)
  1085. account_id = db.Column(StringUUID, nullable=False)
  1086. action = db.Column(db.String(255), nullable=False)
  1087. content = db.Column(db.JSON)
  1088. created_at = db.Column(db.DateTime, nullable=False, server_default=db.text("CURRENT_TIMESTAMP(0)"))
  1089. created_ip = db.Column(db.String(255), nullable=False)
  1090. updated_at = db.Column(db.DateTime, nullable=False, server_default=db.text("CURRENT_TIMESTAMP(0)"))
  1091. class EndUser(UserMixin, db.Model):
  1092. __tablename__ = "end_users"
  1093. __table_args__ = (
  1094. db.PrimaryKeyConstraint("id", name="end_user_pkey"),
  1095. db.Index("end_user_session_id_idx", "session_id", "type"),
  1096. db.Index("end_user_tenant_session_id_idx", "tenant_id", "session_id", "type"),
  1097. )
  1098. id = db.Column(StringUUID, server_default=db.text("uuid_generate_v4()"))
  1099. tenant_id = db.Column(StringUUID, nullable=False)
  1100. app_id = db.Column(StringUUID, nullable=True)
  1101. type = db.Column(db.String(255), nullable=False)
  1102. external_user_id = db.Column(db.String(255), nullable=True)
  1103. name = db.Column(db.String(255))
  1104. is_anonymous = db.Column(db.Boolean, nullable=False, server_default=db.text("true"))
  1105. session_id = db.Column(db.String(255), nullable=False)
  1106. created_at = db.Column(db.DateTime, nullable=False, server_default=db.text("CURRENT_TIMESTAMP(0)"))
  1107. updated_at = db.Column(db.DateTime, nullable=False, server_default=db.text("CURRENT_TIMESTAMP(0)"))
  1108. class Site(db.Model):
  1109. __tablename__ = "sites"
  1110. __table_args__ = (
  1111. db.PrimaryKeyConstraint("id", name="site_pkey"),
  1112. db.Index("site_app_id_idx", "app_id"),
  1113. db.Index("site_code_idx", "code", "status"),
  1114. )
  1115. id = db.Column(StringUUID, server_default=db.text("uuid_generate_v4()"))
  1116. app_id = db.Column(StringUUID, nullable=False)
  1117. title = db.Column(db.String(255), nullable=False)
  1118. icon_type = db.Column(db.String(255), nullable=True)
  1119. icon = db.Column(db.String(255))
  1120. icon_background = db.Column(db.String(255))
  1121. description = db.Column(db.Text)
  1122. default_language = db.Column(db.String(255), nullable=False)
  1123. chat_color_theme = db.Column(db.String(255))
  1124. chat_color_theme_inverted = db.Column(db.Boolean, nullable=False, server_default=db.text("false"))
  1125. copyright = db.Column(db.String(255))
  1126. privacy_policy = db.Column(db.String(255))
  1127. show_workflow_steps = db.Column(db.Boolean, nullable=False, server_default=db.text("true"))
  1128. use_icon_as_answer_icon = db.Column(db.Boolean, nullable=False, server_default=db.text("false"))
  1129. custom_disclaimer = db.Column(db.String(255), nullable=True)
  1130. customize_domain = db.Column(db.String(255))
  1131. customize_token_strategy = db.Column(db.String(255), nullable=False)
  1132. prompt_public = db.Column(db.Boolean, nullable=False, server_default=db.text("false"))
  1133. status = db.Column(db.String(255), nullable=False, server_default=db.text("'normal'::character varying"))
  1134. created_by = db.Column(StringUUID, nullable=True)
  1135. created_at = db.Column(db.DateTime, nullable=False, server_default=db.text("CURRENT_TIMESTAMP(0)"))
  1136. updated_by = db.Column(StringUUID, nullable=True)
  1137. updated_at = db.Column(db.DateTime, nullable=False, server_default=db.text("CURRENT_TIMESTAMP(0)"))
  1138. code = db.Column(db.String(255))
  1139. @staticmethod
  1140. def generate_code(n):
  1141. while True:
  1142. result = generate_string(n)
  1143. while db.session.query(Site).filter(Site.code == result).count() > 0:
  1144. result = generate_string(n)
  1145. return result
  1146. @property
  1147. def app_base_url(self):
  1148. return dify_config.APP_WEB_URL or request.url_root.rstrip("/")
  1149. class ApiToken(db.Model):
  1150. __tablename__ = "api_tokens"
  1151. __table_args__ = (
  1152. db.PrimaryKeyConstraint("id", name="api_token_pkey"),
  1153. db.Index("api_token_app_id_type_idx", "app_id", "type"),
  1154. db.Index("api_token_token_idx", "token", "type"),
  1155. db.Index("api_token_tenant_idx", "tenant_id", "type"),
  1156. )
  1157. id = db.Column(StringUUID, server_default=db.text("uuid_generate_v4()"))
  1158. app_id = db.Column(StringUUID, nullable=True)
  1159. tenant_id = db.Column(StringUUID, nullable=True)
  1160. type = db.Column(db.String(16), nullable=False)
  1161. token = db.Column(db.String(255), nullable=False)
  1162. last_used_at = db.Column(db.DateTime, nullable=True)
  1163. created_at = db.Column(db.DateTime, nullable=False, server_default=db.text("CURRENT_TIMESTAMP(0)"))
  1164. @staticmethod
  1165. def generate_api_key(prefix, n):
  1166. while True:
  1167. result = prefix + generate_string(n)
  1168. while db.session.query(ApiToken).filter(ApiToken.token == result).count() > 0:
  1169. result = prefix + generate_string(n)
  1170. return result
  1171. class UploadFile(db.Model):
  1172. __tablename__ = "upload_files"
  1173. __table_args__ = (
  1174. db.PrimaryKeyConstraint("id", name="upload_file_pkey"),
  1175. db.Index("upload_file_tenant_idx", "tenant_id"),
  1176. )
  1177. id: Mapped[str] = db.Column(StringUUID, server_default=db.text("uuid_generate_v4()"))
  1178. tenant_id: Mapped[str] = db.Column(StringUUID, nullable=False)
  1179. storage_type: Mapped[str] = db.Column(db.String(255), nullable=False)
  1180. key: Mapped[str] = db.Column(db.String(255), nullable=False)
  1181. name: Mapped[str] = db.Column(db.String(255), nullable=False)
  1182. size: Mapped[int] = db.Column(db.Integer, nullable=False)
  1183. extension: Mapped[str] = db.Column(db.String(255), nullable=False)
  1184. mime_type: Mapped[str] = db.Column(db.String(255), nullable=True)
  1185. created_by_role: Mapped[str] = db.Column(
  1186. db.String(255), nullable=False, server_default=db.text("'account'::character varying")
  1187. )
  1188. created_by: Mapped[str] = db.Column(StringUUID, nullable=False)
  1189. created_at: Mapped[datetime] = db.Column(
  1190. db.DateTime, nullable=False, server_default=db.text("CURRENT_TIMESTAMP(0)")
  1191. )
  1192. used: Mapped[bool] = db.Column(db.Boolean, nullable=False, server_default=db.text("false"))
  1193. used_by: Mapped[str | None] = db.Column(StringUUID, nullable=True)
  1194. used_at: Mapped[datetime | None] = db.Column(db.DateTime, nullable=True)
  1195. hash: Mapped[str | None] = db.Column(db.String(255), nullable=True)
  1196. def __init__(
  1197. self,
  1198. *,
  1199. tenant_id: str,
  1200. storage_type: str,
  1201. key: str,
  1202. name: str,
  1203. size: int,
  1204. extension: str,
  1205. mime_type: str,
  1206. created_by_role: CreatedByRole,
  1207. created_by: str,
  1208. created_at: datetime,
  1209. used: bool,
  1210. used_by: str | None = None,
  1211. used_at: datetime | None = None,
  1212. hash: str | None = None,
  1213. ) -> None:
  1214. self.tenant_id = tenant_id
  1215. self.storage_type = storage_type
  1216. self.key = key
  1217. self.name = name
  1218. self.size = size
  1219. self.extension = extension
  1220. self.mime_type = mime_type
  1221. self.created_by_role = created_by_role.value
  1222. self.created_by = created_by
  1223. self.created_at = created_at
  1224. self.used = used
  1225. self.used_by = used_by
  1226. self.used_at = used_at
  1227. self.hash = hash
  1228. class ApiRequest(db.Model):
  1229. __tablename__ = "api_requests"
  1230. __table_args__ = (
  1231. db.PrimaryKeyConstraint("id", name="api_request_pkey"),
  1232. db.Index("api_request_token_idx", "tenant_id", "api_token_id"),
  1233. )
  1234. id = db.Column(StringUUID, nullable=False, server_default=db.text("uuid_generate_v4()"))
  1235. tenant_id = db.Column(StringUUID, nullable=False)
  1236. api_token_id = db.Column(StringUUID, nullable=False)
  1237. path = db.Column(db.String(255), nullable=False)
  1238. request = db.Column(db.Text, nullable=True)
  1239. response = db.Column(db.Text, nullable=True)
  1240. ip = db.Column(db.String(255), nullable=False)
  1241. created_at = db.Column(db.DateTime, nullable=False, server_default=db.text("CURRENT_TIMESTAMP(0)"))
  1242. class MessageChain(db.Model):
  1243. __tablename__ = "message_chains"
  1244. __table_args__ = (
  1245. db.PrimaryKeyConstraint("id", name="message_chain_pkey"),
  1246. db.Index("message_chain_message_id_idx", "message_id"),
  1247. )
  1248. id = db.Column(StringUUID, nullable=False, server_default=db.text("uuid_generate_v4()"))
  1249. message_id = db.Column(StringUUID, nullable=False)
  1250. type = db.Column(db.String(255), nullable=False)
  1251. input = db.Column(db.Text, nullable=True)
  1252. output = db.Column(db.Text, nullable=True)
  1253. created_at = db.Column(db.DateTime, nullable=False, server_default=db.func.current_timestamp())
  1254. class MessageAgentThought(db.Model):
  1255. __tablename__ = "message_agent_thoughts"
  1256. __table_args__ = (
  1257. db.PrimaryKeyConstraint("id", name="message_agent_thought_pkey"),
  1258. db.Index("message_agent_thought_message_id_idx", "message_id"),
  1259. db.Index("message_agent_thought_message_chain_id_idx", "message_chain_id"),
  1260. )
  1261. id = db.Column(StringUUID, nullable=False, server_default=db.text("uuid_generate_v4()"))
  1262. message_id = db.Column(StringUUID, nullable=False)
  1263. message_chain_id = db.Column(StringUUID, nullable=True)
  1264. position = db.Column(db.Integer, nullable=False)
  1265. thought = db.Column(db.Text, nullable=True)
  1266. tool = db.Column(db.Text, nullable=True)
  1267. tool_labels_str = db.Column(db.Text, nullable=False, server_default=db.text("'{}'::text"))
  1268. tool_meta_str = db.Column(db.Text, nullable=False, server_default=db.text("'{}'::text"))
  1269. tool_input = db.Column(db.Text, nullable=True)
  1270. observation = db.Column(db.Text, nullable=True)
  1271. # plugin_id = db.Column(StringUUID, nullable=True) ## for future design
  1272. tool_process_data = db.Column(db.Text, nullable=True)
  1273. message = db.Column(db.Text, nullable=True)
  1274. message_token = db.Column(db.Integer, nullable=True)
  1275. message_unit_price = db.Column(db.Numeric, nullable=True)
  1276. message_price_unit = db.Column(db.Numeric(10, 7), nullable=False, server_default=db.text("0.001"))
  1277. message_files = db.Column(db.Text, nullable=True)
  1278. answer = db.Column(db.Text, nullable=True)
  1279. answer_token = db.Column(db.Integer, nullable=True)
  1280. answer_unit_price = db.Column(db.Numeric, nullable=True)
  1281. answer_price_unit = db.Column(db.Numeric(10, 7), nullable=False, server_default=db.text("0.001"))
  1282. tokens = db.Column(db.Integer, nullable=True)
  1283. total_price = db.Column(db.Numeric, nullable=True)
  1284. currency = db.Column(db.String, nullable=True)
  1285. latency = db.Column(db.Float, nullable=True)
  1286. created_by_role = db.Column(db.String, nullable=False)
  1287. created_by = db.Column(StringUUID, nullable=False)
  1288. created_at = db.Column(db.DateTime, nullable=False, server_default=db.func.current_timestamp())
  1289. @property
  1290. def files(self) -> list:
  1291. if self.message_files:
  1292. return json.loads(self.message_files)
  1293. else:
  1294. return []
  1295. @property
  1296. def tools(self) -> list[str]:
  1297. return self.tool.split(";") if self.tool else []
  1298. @property
  1299. def tool_labels(self) -> dict:
  1300. try:
  1301. if self.tool_labels_str:
  1302. return json.loads(self.tool_labels_str)
  1303. else:
  1304. return {}
  1305. except Exception as e:
  1306. return {}
  1307. @property
  1308. def tool_meta(self) -> dict:
  1309. try:
  1310. if self.tool_meta_str:
  1311. return json.loads(self.tool_meta_str)
  1312. else:
  1313. return {}
  1314. except Exception as e:
  1315. return {}
  1316. @property
  1317. def tool_inputs_dict(self) -> dict:
  1318. tools = self.tools
  1319. try:
  1320. if self.tool_input:
  1321. data = json.loads(self.tool_input)
  1322. result = {}
  1323. for tool in tools:
  1324. if tool in data:
  1325. result[tool] = data[tool]
  1326. else:
  1327. if len(tools) == 1:
  1328. result[tool] = data
  1329. else:
  1330. result[tool] = {}
  1331. return result
  1332. else:
  1333. return {tool: {} for tool in tools}
  1334. except Exception as e:
  1335. return {}
  1336. @property
  1337. def tool_outputs_dict(self) -> dict:
  1338. tools = self.tools
  1339. try:
  1340. if self.observation:
  1341. data = json.loads(self.observation)
  1342. result = {}
  1343. for tool in tools:
  1344. if tool in data:
  1345. result[tool] = data[tool]
  1346. else:
  1347. if len(tools) == 1:
  1348. result[tool] = data
  1349. else:
  1350. result[tool] = {}
  1351. return result
  1352. else:
  1353. return {tool: {} for tool in tools}
  1354. except Exception as e:
  1355. if self.observation:
  1356. return dict.fromkeys(tools, self.observation)
  1357. class DatasetRetrieverResource(db.Model):
  1358. __tablename__ = "dataset_retriever_resources"
  1359. __table_args__ = (
  1360. db.PrimaryKeyConstraint("id", name="dataset_retriever_resource_pkey"),
  1361. db.Index("dataset_retriever_resource_message_id_idx", "message_id"),
  1362. )
  1363. id = db.Column(StringUUID, nullable=False, server_default=db.text("uuid_generate_v4()"))
  1364. message_id = db.Column(StringUUID, nullable=False)
  1365. position = db.Column(db.Integer, nullable=False)
  1366. dataset_id = db.Column(StringUUID, nullable=False)
  1367. dataset_name = db.Column(db.Text, nullable=False)
  1368. document_id = db.Column(StringUUID, nullable=True)
  1369. document_name = db.Column(db.Text, nullable=False)
  1370. data_source_type = db.Column(db.Text, nullable=True)
  1371. segment_id = db.Column(StringUUID, nullable=True)
  1372. score = db.Column(db.Float, nullable=True)
  1373. content = db.Column(db.Text, nullable=False)
  1374. hit_count = db.Column(db.Integer, nullable=True)
  1375. word_count = db.Column(db.Integer, nullable=True)
  1376. segment_position = db.Column(db.Integer, nullable=True)
  1377. index_node_hash = db.Column(db.Text, nullable=True)
  1378. retriever_from = db.Column(db.Text, nullable=False)
  1379. created_by = db.Column(StringUUID, nullable=False)
  1380. created_at = db.Column(db.DateTime, nullable=False, server_default=db.func.current_timestamp())
  1381. class Tag(db.Model):
  1382. __tablename__ = "tags"
  1383. __table_args__ = (
  1384. db.PrimaryKeyConstraint("id", name="tag_pkey"),
  1385. db.Index("tag_type_idx", "type"),
  1386. db.Index("tag_name_idx", "name"),
  1387. )
  1388. TAG_TYPE_LIST = ["knowledge", "app"]
  1389. id = db.Column(StringUUID, server_default=db.text("uuid_generate_v4()"))
  1390. tenant_id = db.Column(StringUUID, nullable=True)
  1391. type = db.Column(db.String(16), nullable=False)
  1392. name = db.Column(db.String(255), nullable=False)
  1393. created_by = db.Column(StringUUID, nullable=False)
  1394. created_at = db.Column(db.DateTime, nullable=False, server_default=db.text("CURRENT_TIMESTAMP(0)"))
  1395. class TagBinding(db.Model):
  1396. __tablename__ = "tag_bindings"
  1397. __table_args__ = (
  1398. db.PrimaryKeyConstraint("id", name="tag_binding_pkey"),
  1399. db.Index("tag_bind_target_id_idx", "target_id"),
  1400. db.Index("tag_bind_tag_id_idx", "tag_id"),
  1401. )
  1402. id = db.Column(StringUUID, server_default=db.text("uuid_generate_v4()"))
  1403. tenant_id = db.Column(StringUUID, nullable=True)
  1404. tag_id = db.Column(StringUUID, nullable=True)
  1405. target_id = db.Column(StringUUID, nullable=True)
  1406. created_by = db.Column(StringUUID, nullable=False)
  1407. created_at = db.Column(db.DateTime, nullable=False, server_default=db.text("CURRENT_TIMESTAMP(0)"))
  1408. class TraceAppConfig(db.Model):
  1409. __tablename__ = "trace_app_config"
  1410. __table_args__ = (
  1411. db.PrimaryKeyConstraint("id", name="tracing_app_config_pkey"),
  1412. db.Index("trace_app_config_app_id_idx", "app_id"),
  1413. )
  1414. id = db.Column(StringUUID, server_default=db.text("uuid_generate_v4()"))
  1415. app_id = db.Column(StringUUID, nullable=False)
  1416. tracing_provider = db.Column(db.String(255), nullable=True)
  1417. tracing_config = db.Column(db.JSON, nullable=True)
  1418. created_at = db.Column(db.DateTime, nullable=False, server_default=func.now())
  1419. updated_at = db.Column(db.DateTime, nullable=False, server_default=func.now(), onupdate=func.now())
  1420. is_active = db.Column(db.Boolean, nullable=False, server_default=db.text("true"))
  1421. @property
  1422. def tracing_config_dict(self):
  1423. return self.tracing_config or {}
  1424. @property
  1425. def tracing_config_str(self):
  1426. return json.dumps(self.tracing_config_dict)
  1427. def to_dict(self):
  1428. return {
  1429. "id": self.id,
  1430. "app_id": self.app_id,
  1431. "tracing_provider": self.tracing_provider,
  1432. "tracing_config": self.tracing_config_dict,
  1433. "is_active": self.is_active,
  1434. "created_at": str(self.created_at) if self.created_at else None,
  1435. "updated_at": str(self.updated_at) if self.updated_at else None,
  1436. }