model.py 66 KB

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