model.py 73 KB

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