Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

model.py 77KB

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