Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.

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