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

model.py 75KB

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