Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.

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