Du kannst nicht mehr als 25 Themen auswählen Themen müssen mit entweder einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

model.py 73KB

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