model.py 60 KB

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