You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

model.py 75KB

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