model.py 58 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421
  1. import json
  2. import re
  3. import uuid
  4. from enum import Enum
  5. from typing import Optional
  6. from flask import request
  7. from flask_login import UserMixin
  8. from sqlalchemy import Float, func, text
  9. from sqlalchemy.orm import Mapped, mapped_column
  10. from configs import dify_config
  11. from core.file.tool_file_parser import ToolFileParser
  12. from core.file.upload_file_parser import UploadFileParser
  13. from extensions.ext_database import db
  14. from libs.helper import generate_string
  15. from .account import Account, Tenant
  16. from .types import StringUUID
  17. class DifySetup(db.Model):
  18. __tablename__ = 'dify_setups'
  19. __table_args__ = (
  20. db.PrimaryKeyConstraint('version', name='dify_setup_pkey'),
  21. )
  22. version = db.Column(db.String(255), nullable=False)
  23. setup_at = db.Column(db.DateTime, nullable=False, server_default=db.text('CURRENT_TIMESTAMP(0)'))
  24. class AppMode(Enum):
  25. COMPLETION = 'completion'
  26. WORKFLOW = 'workflow'
  27. CHAT = 'chat'
  28. ADVANCED_CHAT = 'advanced-chat'
  29. AGENT_CHAT = 'agent-chat'
  30. CHANNEL = 'channel'
  31. @classmethod
  32. def value_of(cls, value: str) -> 'AppMode':
  33. """
  34. Get value of given mode.
  35. :param value: mode value
  36. :return: mode
  37. """
  38. for mode in cls:
  39. if mode.value == value:
  40. return mode
  41. raise ValueError(f'invalid mode value {value}')
  42. class IconType(Enum):
  43. IMAGE = "image"
  44. EMOJI = "emoji"
  45. class App(db.Model):
  46. __tablename__ = 'apps'
  47. __table_args__ = (
  48. db.PrimaryKeyConstraint('id', name='app_pkey'),
  49. db.Index('app_tenant_id_idx', 'tenant_id')
  50. )
  51. id = db.Column(StringUUID, server_default=db.text('uuid_generate_v4()'))
  52. tenant_id = db.Column(StringUUID, nullable=False)
  53. name = db.Column(db.String(255), nullable=False)
  54. description = db.Column(db.Text, nullable=False, server_default=db.text("''::character varying"))
  55. mode = db.Column(db.String(255), nullable=False)
  56. icon_type = db.Column(db.String(255), nullable=True)
  57. icon = db.Column(db.String(255))
  58. icon_background = db.Column(db.String(255))
  59. app_model_config_id = db.Column(StringUUID, nullable=True)
  60. workflow_id = db.Column(StringUUID, nullable=True)
  61. status = db.Column(db.String(255), nullable=False, server_default=db.text("'normal'::character varying"))
  62. enable_site = db.Column(db.Boolean, nullable=False)
  63. enable_api = db.Column(db.Boolean, nullable=False)
  64. api_rpm = db.Column(db.Integer, nullable=False, server_default=db.text('0'))
  65. api_rph = db.Column(db.Integer, nullable=False, server_default=db.text('0'))
  66. is_demo = db.Column(db.Boolean, nullable=False, server_default=db.text('false'))
  67. is_public = db.Column(db.Boolean, nullable=False, server_default=db.text('false'))
  68. is_universal = db.Column(db.Boolean, nullable=False, server_default=db.text('false'))
  69. tracing = db.Column(db.Text, nullable=True)
  70. max_active_requests = db.Column(db.Integer, nullable=True)
  71. created_at = db.Column(db.DateTime, nullable=False, server_default=db.text('CURRENT_TIMESTAMP(0)'))
  72. updated_at = db.Column(db.DateTime, nullable=False, server_default=db.text('CURRENT_TIMESTAMP(0)'))
  73. @property
  74. def desc_or_prompt(self):
  75. if self.description:
  76. return self.description
  77. else:
  78. app_model_config = self.app_model_config
  79. if app_model_config:
  80. return app_model_config.pre_prompt
  81. else:
  82. return ''
  83. @property
  84. def site(self):
  85. site = db.session.query(Site).filter(Site.app_id == self.id).first()
  86. return site
  87. @property
  88. def app_model_config(self) -> Optional['AppModelConfig']:
  89. if self.app_model_config_id:
  90. return db.session.query(AppModelConfig).filter(AppModelConfig.id == self.app_model_config_id).first()
  91. return None
  92. @property
  93. def workflow(self) -> Optional['Workflow']:
  94. if self.workflow_id:
  95. from .workflow import Workflow
  96. return db.session.query(Workflow).filter(Workflow.id == self.workflow_id).first()
  97. return None
  98. @property
  99. def api_base_url(self):
  100. return (dify_config.SERVICE_API_URL if dify_config.SERVICE_API_URL
  101. else request.host_url.rstrip('/')) + '/v1'
  102. @property
  103. def tenant(self):
  104. tenant = db.session.query(Tenant).filter(Tenant.id == self.tenant_id).first()
  105. return tenant
  106. @property
  107. def is_agent(self) -> bool:
  108. app_model_config = self.app_model_config
  109. if not app_model_config:
  110. return False
  111. if not app_model_config.agent_mode:
  112. return False
  113. if self.app_model_config.agent_mode_dict.get('enabled', False) \
  114. and self.app_model_config.agent_mode_dict.get('strategy', '') in ['function_call', 'react']:
  115. self.mode = AppMode.AGENT_CHAT.value
  116. db.session.commit()
  117. return True
  118. return False
  119. @property
  120. def mode_compatible_with_agent(self) -> str:
  121. if self.mode == AppMode.CHAT.value and self.is_agent:
  122. return AppMode.AGENT_CHAT.value
  123. return self.mode
  124. @property
  125. def deleted_tools(self) -> list:
  126. # get agent mode tools
  127. app_model_config = self.app_model_config
  128. if not app_model_config:
  129. return []
  130. if not app_model_config.agent_mode:
  131. return []
  132. agent_mode = app_model_config.agent_mode_dict
  133. tools = agent_mode.get('tools', [])
  134. provider_ids = []
  135. for tool in tools:
  136. keys = list(tool.keys())
  137. if len(keys) >= 4:
  138. provider_type = tool.get('provider_type', '')
  139. provider_id = tool.get('provider_id', '')
  140. if provider_type == 'api':
  141. # check if provider id is a uuid string, if not, skip
  142. try:
  143. uuid.UUID(provider_id)
  144. except Exception:
  145. continue
  146. provider_ids.append(provider_id)
  147. if not provider_ids:
  148. return []
  149. api_providers = db.session.execute(
  150. text('SELECT id FROM tool_api_providers WHERE id IN :provider_ids'),
  151. {'provider_ids': tuple(provider_ids)}
  152. ).fetchall()
  153. deleted_tools = []
  154. current_api_provider_ids = [str(api_provider.id) for api_provider in api_providers]
  155. for tool in tools:
  156. keys = list(tool.keys())
  157. if len(keys) >= 4:
  158. provider_type = tool.get('provider_type', '')
  159. provider_id = tool.get('provider_id', '')
  160. if provider_type == 'api' and provider_id not in current_api_provider_ids:
  161. deleted_tools.append(tool['tool_name'])
  162. return deleted_tools
  163. @property
  164. def tags(self):
  165. tags = db.session.query(Tag).join(
  166. TagBinding,
  167. Tag.id == TagBinding.tag_id
  168. ).filter(
  169. TagBinding.target_id == self.id,
  170. TagBinding.tenant_id == self.tenant_id,
  171. Tag.tenant_id == self.tenant_id,
  172. Tag.type == 'app'
  173. ).all()
  174. return tags if tags else []
  175. class AppModelConfig(db.Model):
  176. __tablename__ = 'app_model_configs'
  177. __table_args__ = (
  178. db.PrimaryKeyConstraint('id', name='app_model_config_pkey'),
  179. db.Index('app_app_id_idx', 'app_id')
  180. )
  181. id = db.Column(StringUUID, server_default=db.text('uuid_generate_v4()'))
  182. app_id = db.Column(StringUUID, nullable=False)
  183. provider = db.Column(db.String(255), nullable=True)
  184. model_id = db.Column(db.String(255), nullable=True)
  185. configs = db.Column(db.JSON, nullable=True)
  186. created_at = db.Column(db.DateTime, nullable=False, server_default=db.text('CURRENT_TIMESTAMP(0)'))
  187. updated_at = db.Column(db.DateTime, nullable=False, server_default=db.text('CURRENT_TIMESTAMP(0)'))
  188. opening_statement = db.Column(db.Text)
  189. suggested_questions = db.Column(db.Text)
  190. suggested_questions_after_answer = db.Column(db.Text)
  191. speech_to_text = db.Column(db.Text)
  192. text_to_speech = db.Column(db.Text)
  193. more_like_this = db.Column(db.Text)
  194. model = db.Column(db.Text)
  195. user_input_form = db.Column(db.Text)
  196. dataset_query_variable = db.Column(db.String(255))
  197. pre_prompt = db.Column(db.Text)
  198. agent_mode = db.Column(db.Text)
  199. sensitive_word_avoidance = db.Column(db.Text)
  200. retriever_resource = db.Column(db.Text)
  201. prompt_type = db.Column(db.String(255), nullable=False, server_default=db.text("'simple'::character varying"))
  202. chat_prompt_config = db.Column(db.Text)
  203. completion_prompt_config = db.Column(db.Text)
  204. dataset_configs = db.Column(db.Text)
  205. external_data_tools = db.Column(db.Text)
  206. file_upload = db.Column(db.Text)
  207. @property
  208. def app(self):
  209. app = db.session.query(App).filter(App.id == self.app_id).first()
  210. return app
  211. @property
  212. def model_dict(self) -> dict:
  213. return json.loads(self.model) if self.model else None
  214. @property
  215. def suggested_questions_list(self) -> list:
  216. return json.loads(self.suggested_questions) if self.suggested_questions else []
  217. @property
  218. def suggested_questions_after_answer_dict(self) -> dict:
  219. return json.loads(self.suggested_questions_after_answer) if self.suggested_questions_after_answer \
  220. else {"enabled": False}
  221. @property
  222. def speech_to_text_dict(self) -> dict:
  223. return json.loads(self.speech_to_text) if self.speech_to_text \
  224. else {"enabled": False}
  225. @property
  226. def text_to_speech_dict(self) -> dict:
  227. return json.loads(self.text_to_speech) if self.text_to_speech \
  228. else {"enabled": False}
  229. @property
  230. def retriever_resource_dict(self) -> dict:
  231. return json.loads(self.retriever_resource) if self.retriever_resource \
  232. else {"enabled": True}
  233. @property
  234. def annotation_reply_dict(self) -> dict:
  235. annotation_setting = db.session.query(AppAnnotationSetting).filter(
  236. AppAnnotationSetting.app_id == self.app_id).first()
  237. if annotation_setting:
  238. collection_binding_detail = annotation_setting.collection_binding_detail
  239. return {
  240. "id": annotation_setting.id,
  241. "enabled": True,
  242. "score_threshold": annotation_setting.score_threshold,
  243. "embedding_model": {
  244. "embedding_provider_name": collection_binding_detail.provider_name,
  245. "embedding_model_name": collection_binding_detail.model_name
  246. }
  247. }
  248. else:
  249. return {"enabled": False}
  250. @property
  251. def more_like_this_dict(self) -> dict:
  252. return json.loads(self.more_like_this) if self.more_like_this else {"enabled": False}
  253. @property
  254. def sensitive_word_avoidance_dict(self) -> dict:
  255. return json.loads(self.sensitive_word_avoidance) if self.sensitive_word_avoidance \
  256. else {"enabled": False, "type": "", "configs": []}
  257. @property
  258. def external_data_tools_list(self) -> list[dict]:
  259. return json.loads(self.external_data_tools) if self.external_data_tools \
  260. else []
  261. @property
  262. def user_input_form_list(self) -> dict:
  263. return json.loads(self.user_input_form) if self.user_input_form else []
  264. @property
  265. def agent_mode_dict(self) -> dict:
  266. return json.loads(self.agent_mode) if self.agent_mode else {"enabled": False, "strategy": None, "tools": [],
  267. "prompt": None}
  268. @property
  269. def chat_prompt_config_dict(self) -> dict:
  270. return json.loads(self.chat_prompt_config) if self.chat_prompt_config else {}
  271. @property
  272. def completion_prompt_config_dict(self) -> dict:
  273. return json.loads(self.completion_prompt_config) if self.completion_prompt_config else {}
  274. @property
  275. def dataset_configs_dict(self) -> dict:
  276. if self.dataset_configs:
  277. dataset_configs = json.loads(self.dataset_configs)
  278. if 'retrieval_model' not in dataset_configs:
  279. return {'retrieval_model': 'single'}
  280. else:
  281. return dataset_configs
  282. return {
  283. 'retrieval_model': 'multiple',
  284. }
  285. @property
  286. def file_upload_dict(self) -> dict:
  287. return json.loads(self.file_upload) if self.file_upload else {
  288. "image": {"enabled": False, "number_limits": 3, "detail": "high",
  289. "transfer_methods": ["remote_url", "local_file"]}}
  290. def to_dict(self) -> dict:
  291. return {
  292. "opening_statement": self.opening_statement,
  293. "suggested_questions": self.suggested_questions_list,
  294. "suggested_questions_after_answer": self.suggested_questions_after_answer_dict,
  295. "speech_to_text": self.speech_to_text_dict,
  296. "text_to_speech": self.text_to_speech_dict,
  297. "retriever_resource": self.retriever_resource_dict,
  298. "annotation_reply": self.annotation_reply_dict,
  299. "more_like_this": self.more_like_this_dict,
  300. "sensitive_word_avoidance": self.sensitive_word_avoidance_dict,
  301. "external_data_tools": self.external_data_tools_list,
  302. "model": self.model_dict,
  303. "user_input_form": self.user_input_form_list,
  304. "dataset_query_variable": self.dataset_query_variable,
  305. "pre_prompt": self.pre_prompt,
  306. "agent_mode": self.agent_mode_dict,
  307. "prompt_type": self.prompt_type,
  308. "chat_prompt_config": self.chat_prompt_config_dict,
  309. "completion_prompt_config": self.completion_prompt_config_dict,
  310. "dataset_configs": self.dataset_configs_dict,
  311. "file_upload": self.file_upload_dict
  312. }
  313. def from_model_config_dict(self, model_config: dict):
  314. self.opening_statement = model_config.get('opening_statement')
  315. self.suggested_questions = json.dumps(model_config['suggested_questions']) \
  316. if model_config.get('suggested_questions') else None
  317. self.suggested_questions_after_answer = json.dumps(model_config['suggested_questions_after_answer']) \
  318. if model_config.get('suggested_questions_after_answer') else None
  319. self.speech_to_text = json.dumps(model_config['speech_to_text']) \
  320. if model_config.get('speech_to_text') else None
  321. self.text_to_speech = json.dumps(model_config['text_to_speech']) \
  322. if model_config.get('text_to_speech') else None
  323. self.more_like_this = json.dumps(model_config['more_like_this']) \
  324. if model_config.get('more_like_this') else None
  325. self.sensitive_word_avoidance = json.dumps(model_config['sensitive_word_avoidance']) \
  326. if model_config.get('sensitive_word_avoidance') else None
  327. self.external_data_tools = json.dumps(model_config['external_data_tools']) \
  328. if model_config.get('external_data_tools') else None
  329. self.model = json.dumps(model_config['model']) \
  330. if model_config.get('model') else None
  331. self.user_input_form = json.dumps(model_config['user_input_form']) \
  332. if model_config.get('user_input_form') else None
  333. self.dataset_query_variable = model_config.get('dataset_query_variable')
  334. self.pre_prompt = model_config['pre_prompt']
  335. self.agent_mode = json.dumps(model_config['agent_mode']) \
  336. if model_config.get('agent_mode') else None
  337. self.retriever_resource = json.dumps(model_config['retriever_resource']) \
  338. if model_config.get('retriever_resource') else None
  339. self.prompt_type = model_config.get('prompt_type', 'simple')
  340. self.chat_prompt_config = json.dumps(model_config.get('chat_prompt_config')) \
  341. if model_config.get('chat_prompt_config') else None
  342. self.completion_prompt_config = json.dumps(model_config.get('completion_prompt_config')) \
  343. if model_config.get('completion_prompt_config') else None
  344. self.dataset_configs = json.dumps(model_config.get('dataset_configs')) \
  345. if model_config.get('dataset_configs') else None
  346. self.file_upload = json.dumps(model_config.get('file_upload')) \
  347. if model_config.get('file_upload') else None
  348. return self
  349. def copy(self):
  350. new_app_model_config = AppModelConfig(
  351. id=self.id,
  352. app_id=self.app_id,
  353. opening_statement=self.opening_statement,
  354. suggested_questions=self.suggested_questions,
  355. suggested_questions_after_answer=self.suggested_questions_after_answer,
  356. speech_to_text=self.speech_to_text,
  357. text_to_speech=self.text_to_speech,
  358. more_like_this=self.more_like_this,
  359. sensitive_word_avoidance=self.sensitive_word_avoidance,
  360. external_data_tools=self.external_data_tools,
  361. model=self.model,
  362. user_input_form=self.user_input_form,
  363. dataset_query_variable=self.dataset_query_variable,
  364. pre_prompt=self.pre_prompt,
  365. agent_mode=self.agent_mode,
  366. retriever_resource=self.retriever_resource,
  367. prompt_type=self.prompt_type,
  368. chat_prompt_config=self.chat_prompt_config,
  369. completion_prompt_config=self.completion_prompt_config,
  370. dataset_configs=self.dataset_configs,
  371. file_upload=self.file_upload
  372. )
  373. return new_app_model_config
  374. class RecommendedApp(db.Model):
  375. __tablename__ = 'recommended_apps'
  376. __table_args__ = (
  377. db.PrimaryKeyConstraint('id', name='recommended_app_pkey'),
  378. db.Index('recommended_app_app_id_idx', 'app_id'),
  379. db.Index('recommended_app_is_listed_idx', 'is_listed', 'language')
  380. )
  381. id = db.Column(StringUUID, primary_key=True, server_default=db.text('uuid_generate_v4()'))
  382. app_id = db.Column(StringUUID, nullable=False)
  383. description = db.Column(db.JSON, nullable=False)
  384. copyright = db.Column(db.String(255), nullable=False)
  385. privacy_policy = db.Column(db.String(255), nullable=False)
  386. custom_disclaimer = db.Column(db.String(255), nullable=True)
  387. category = db.Column(db.String(255), nullable=False)
  388. position = db.Column(db.Integer, nullable=False, default=0)
  389. is_listed = db.Column(db.Boolean, nullable=False, default=True)
  390. install_count = db.Column(db.Integer, nullable=False, default=0)
  391. language = db.Column(db.String(255), nullable=False, server_default=db.text("'en-US'::character varying"))
  392. created_at = db.Column(db.DateTime, nullable=False, server_default=db.text('CURRENT_TIMESTAMP(0)'))
  393. updated_at = db.Column(db.DateTime, nullable=False, server_default=db.text('CURRENT_TIMESTAMP(0)'))
  394. @property
  395. def app(self):
  396. app = db.session.query(App).filter(App.id == self.app_id).first()
  397. return app
  398. class InstalledApp(db.Model):
  399. __tablename__ = 'installed_apps'
  400. __table_args__ = (
  401. db.PrimaryKeyConstraint('id', name='installed_app_pkey'),
  402. db.Index('installed_app_tenant_id_idx', 'tenant_id'),
  403. db.Index('installed_app_app_id_idx', 'app_id'),
  404. db.UniqueConstraint('tenant_id', 'app_id', name='unique_tenant_app')
  405. )
  406. id = db.Column(StringUUID, server_default=db.text('uuid_generate_v4()'))
  407. tenant_id = db.Column(StringUUID, nullable=False)
  408. app_id = db.Column(StringUUID, nullable=False)
  409. app_owner_tenant_id = db.Column(StringUUID, nullable=False)
  410. position = db.Column(db.Integer, nullable=False, default=0)
  411. is_pinned = db.Column(db.Boolean, nullable=False, server_default=db.text('false'))
  412. last_used_at = db.Column(db.DateTime, nullable=True)
  413. created_at = db.Column(db.DateTime, nullable=False, server_default=db.text('CURRENT_TIMESTAMP(0)'))
  414. @property
  415. def app(self):
  416. app = db.session.query(App).filter(App.id == self.app_id).first()
  417. return app
  418. @property
  419. def tenant(self):
  420. tenant = db.session.query(Tenant).filter(Tenant.id == self.tenant_id).first()
  421. return tenant
  422. class Conversation(db.Model):
  423. __tablename__ = 'conversations'
  424. __table_args__ = (
  425. db.PrimaryKeyConstraint('id', name='conversation_pkey'),
  426. db.Index('conversation_app_from_user_idx', 'app_id', 'from_source', 'from_end_user_id')
  427. )
  428. id = db.Column(StringUUID, server_default=db.text('uuid_generate_v4()'))
  429. app_id = db.Column(StringUUID, nullable=False)
  430. app_model_config_id = db.Column(StringUUID, nullable=True)
  431. model_provider = db.Column(db.String(255), nullable=True)
  432. override_model_configs = db.Column(db.Text)
  433. model_id = db.Column(db.String(255), nullable=True)
  434. mode = db.Column(db.String(255), nullable=False)
  435. name = db.Column(db.String(255), nullable=False)
  436. summary = db.Column(db.Text)
  437. inputs = db.Column(db.JSON)
  438. introduction = db.Column(db.Text)
  439. system_instruction = db.Column(db.Text)
  440. system_instruction_tokens = db.Column(db.Integer, nullable=False, server_default=db.text('0'))
  441. status = db.Column(db.String(255), nullable=False)
  442. invoke_from = db.Column(db.String(255), nullable=True)
  443. from_source = db.Column(db.String(255), nullable=False)
  444. from_end_user_id = db.Column(StringUUID)
  445. from_account_id = db.Column(StringUUID)
  446. read_at = db.Column(db.DateTime)
  447. read_account_id = db.Column(StringUUID)
  448. dialogue_count: Mapped[int] = mapped_column(default=0)
  449. created_at = db.Column(db.DateTime, nullable=False, server_default=db.text('CURRENT_TIMESTAMP(0)'))
  450. updated_at = db.Column(db.DateTime, nullable=False, server_default=db.text('CURRENT_TIMESTAMP(0)'))
  451. messages = db.relationship("Message", backref="conversation", lazy='select', passive_deletes="all")
  452. message_annotations = db.relationship("MessageAnnotation", backref="conversation", lazy='select', passive_deletes="all")
  453. is_deleted = db.Column(db.Boolean, nullable=False, server_default=db.text('false'))
  454. @property
  455. def model_config(self):
  456. model_config = {}
  457. if self.mode == AppMode.ADVANCED_CHAT.value:
  458. if self.override_model_configs:
  459. override_model_configs = json.loads(self.override_model_configs)
  460. model_config = override_model_configs
  461. else:
  462. if self.override_model_configs:
  463. override_model_configs = json.loads(self.override_model_configs)
  464. if 'model' in override_model_configs:
  465. app_model_config = AppModelConfig()
  466. app_model_config = app_model_config.from_model_config_dict(override_model_configs)
  467. model_config = app_model_config.to_dict()
  468. else:
  469. model_config['configs'] = override_model_configs
  470. else:
  471. app_model_config = db.session.query(AppModelConfig).filter(
  472. AppModelConfig.id == self.app_model_config_id).first()
  473. model_config = app_model_config.to_dict()
  474. model_config['model_id'] = self.model_id
  475. model_config['provider'] = self.model_provider
  476. return model_config
  477. @property
  478. def summary_or_query(self):
  479. if self.summary:
  480. return self.summary
  481. else:
  482. first_message = self.first_message
  483. if first_message:
  484. return first_message.query
  485. else:
  486. return ''
  487. @property
  488. def annotated(self):
  489. return db.session.query(MessageAnnotation).filter(MessageAnnotation.conversation_id == self.id).count() > 0
  490. @property
  491. def annotation(self):
  492. return db.session.query(MessageAnnotation).filter(MessageAnnotation.conversation_id == self.id).first()
  493. @property
  494. def message_count(self):
  495. return db.session.query(Message).filter(Message.conversation_id == self.id).count()
  496. @property
  497. def user_feedback_stats(self):
  498. like = db.session.query(MessageFeedback) \
  499. .filter(MessageFeedback.conversation_id == self.id,
  500. MessageFeedback.from_source == 'user',
  501. MessageFeedback.rating == 'like').count()
  502. dislike = db.session.query(MessageFeedback) \
  503. .filter(MessageFeedback.conversation_id == self.id,
  504. MessageFeedback.from_source == 'user',
  505. MessageFeedback.rating == 'dislike').count()
  506. return {'like': like, 'dislike': dislike}
  507. @property
  508. def admin_feedback_stats(self):
  509. like = db.session.query(MessageFeedback) \
  510. .filter(MessageFeedback.conversation_id == self.id,
  511. MessageFeedback.from_source == 'admin',
  512. MessageFeedback.rating == 'like').count()
  513. dislike = db.session.query(MessageFeedback) \
  514. .filter(MessageFeedback.conversation_id == self.id,
  515. MessageFeedback.from_source == 'admin',
  516. MessageFeedback.rating == 'dislike').count()
  517. return {'like': like, 'dislike': dislike}
  518. @property
  519. def first_message(self):
  520. return db.session.query(Message).filter(Message.conversation_id == self.id).first()
  521. @property
  522. def app(self):
  523. return db.session.query(App).filter(App.id == self.app_id).first()
  524. @property
  525. def from_end_user_session_id(self):
  526. if self.from_end_user_id:
  527. end_user = db.session.query(EndUser).filter(EndUser.id == self.from_end_user_id).first()
  528. if end_user:
  529. return end_user.session_id
  530. return None
  531. @property
  532. def in_debug_mode(self):
  533. return self.override_model_configs is not None
  534. class Message(db.Model):
  535. __tablename__ = 'messages'
  536. __table_args__ = (
  537. db.PrimaryKeyConstraint('id', name='message_pkey'),
  538. db.Index('message_app_id_idx', 'app_id', 'created_at'),
  539. db.Index('message_conversation_id_idx', 'conversation_id'),
  540. db.Index('message_end_user_idx', 'app_id', 'from_source', 'from_end_user_id'),
  541. db.Index('message_account_idx', 'app_id', 'from_source', 'from_account_id'),
  542. db.Index('message_workflow_run_id_idx', 'conversation_id', 'workflow_run_id')
  543. )
  544. id = db.Column(StringUUID, server_default=db.text('uuid_generate_v4()'))
  545. app_id = db.Column(StringUUID, nullable=False)
  546. model_provider = db.Column(db.String(255), nullable=True)
  547. model_id = db.Column(db.String(255), nullable=True)
  548. override_model_configs = db.Column(db.Text)
  549. conversation_id = db.Column(StringUUID, db.ForeignKey('conversations.id'), nullable=False)
  550. inputs = db.Column(db.JSON)
  551. query = db.Column(db.Text, nullable=False)
  552. message = db.Column(db.JSON, nullable=False)
  553. message_tokens = db.Column(db.Integer, nullable=False, server_default=db.text('0'))
  554. message_unit_price = db.Column(db.Numeric(10, 4), nullable=False)
  555. message_price_unit = db.Column(db.Numeric(10, 7), nullable=False, server_default=db.text('0.001'))
  556. answer = db.Column(db.Text, nullable=False)
  557. answer_tokens = db.Column(db.Integer, nullable=False, server_default=db.text('0'))
  558. answer_unit_price = db.Column(db.Numeric(10, 4), nullable=False)
  559. answer_price_unit = db.Column(db.Numeric(10, 7), nullable=False, server_default=db.text('0.001'))
  560. provider_response_latency = db.Column(db.Float, nullable=False, server_default=db.text('0'))
  561. total_price = db.Column(db.Numeric(10, 7))
  562. currency = db.Column(db.String(255), nullable=False)
  563. status = db.Column(db.String(255), nullable=False, server_default=db.text("'normal'::character varying"))
  564. error = db.Column(db.Text)
  565. message_metadata = db.Column(db.Text)
  566. invoke_from = db.Column(db.String(255), nullable=True)
  567. from_source = db.Column(db.String(255), nullable=False)
  568. from_end_user_id = db.Column(StringUUID)
  569. from_account_id = db.Column(StringUUID)
  570. created_at = db.Column(db.DateTime, nullable=False, server_default=db.text('CURRENT_TIMESTAMP(0)'))
  571. updated_at = db.Column(db.DateTime, nullable=False, server_default=db.text('CURRENT_TIMESTAMP(0)'))
  572. agent_based = db.Column(db.Boolean, nullable=False, server_default=db.text('false'))
  573. workflow_run_id = db.Column(StringUUID)
  574. @property
  575. def re_sign_file_url_answer(self) -> str:
  576. if not self.answer:
  577. return self.answer
  578. pattern = r'\[!?.*?\]\((((http|https):\/\/.+)?\/files\/(tools\/)?[\w-]+.*?timestamp=.*&nonce=.*&sign=.*)\)'
  579. matches = re.findall(pattern, self.answer)
  580. if not matches:
  581. return self.answer
  582. urls = [match[0] for match in matches]
  583. # remove duplicate urls
  584. urls = list(set(urls))
  585. if not urls:
  586. return self.answer
  587. re_sign_file_url_answer = self.answer
  588. for url in urls:
  589. if 'files/tools' in url:
  590. # get tool file id
  591. tool_file_id_pattern = r'\/files\/tools\/([\.\w-]+)?\?timestamp='
  592. result = re.search(tool_file_id_pattern, url)
  593. if not result:
  594. continue
  595. tool_file_id = result.group(1)
  596. # get extension
  597. if '.' in tool_file_id:
  598. split_result = tool_file_id.split('.')
  599. extension = f'.{split_result[-1]}'
  600. if len(extension) > 10:
  601. extension = '.bin'
  602. tool_file_id = split_result[0]
  603. else:
  604. extension = '.bin'
  605. if not tool_file_id:
  606. continue
  607. sign_url = ToolFileParser.get_tool_file_manager().sign_file(
  608. tool_file_id=tool_file_id,
  609. extension=extension
  610. )
  611. else:
  612. # get upload file id
  613. upload_file_id_pattern = r'\/files\/([\w-]+)\/image-preview?\?timestamp='
  614. result = re.search(upload_file_id_pattern, url)
  615. if not result:
  616. continue
  617. upload_file_id = result.group(1)
  618. if not upload_file_id:
  619. continue
  620. sign_url = UploadFileParser.get_signed_temp_image_url(upload_file_id)
  621. re_sign_file_url_answer = re_sign_file_url_answer.replace(url, sign_url)
  622. return re_sign_file_url_answer
  623. @property
  624. def user_feedback(self):
  625. feedback = db.session.query(MessageFeedback).filter(MessageFeedback.message_id == self.id,
  626. MessageFeedback.from_source == 'user').first()
  627. return feedback
  628. @property
  629. def admin_feedback(self):
  630. feedback = db.session.query(MessageFeedback).filter(MessageFeedback.message_id == self.id,
  631. MessageFeedback.from_source == 'admin').first()
  632. return feedback
  633. @property
  634. def feedbacks(self):
  635. feedbacks = db.session.query(MessageFeedback).filter(MessageFeedback.message_id == self.id).all()
  636. return feedbacks
  637. @property
  638. def annotation(self):
  639. annotation = db.session.query(MessageAnnotation).filter(MessageAnnotation.message_id == self.id).first()
  640. return annotation
  641. @property
  642. def annotation_hit_history(self):
  643. annotation_history = (db.session.query(AppAnnotationHitHistory)
  644. .filter(AppAnnotationHitHistory.message_id == self.id).first())
  645. if annotation_history:
  646. annotation = (db.session.query(MessageAnnotation).
  647. filter(MessageAnnotation.id == annotation_history.annotation_id).first())
  648. return annotation
  649. return None
  650. @property
  651. def app_model_config(self):
  652. conversation = db.session.query(Conversation).filter(Conversation.id == self.conversation_id).first()
  653. if conversation:
  654. return db.session.query(AppModelConfig).filter(
  655. AppModelConfig.id == conversation.app_model_config_id).first()
  656. return None
  657. @property
  658. def in_debug_mode(self):
  659. return self.override_model_configs is not None
  660. @property
  661. def message_metadata_dict(self) -> dict:
  662. return json.loads(self.message_metadata) if self.message_metadata else {}
  663. @property
  664. def agent_thoughts(self):
  665. return db.session.query(MessageAgentThought).filter(MessageAgentThought.message_id == self.id) \
  666. .order_by(MessageAgentThought.position.asc()).all()
  667. @property
  668. def retriever_resources(self):
  669. return db.session.query(DatasetRetrieverResource).filter(DatasetRetrieverResource.message_id == self.id) \
  670. .order_by(DatasetRetrieverResource.position.asc()).all()
  671. @property
  672. def message_files(self):
  673. return db.session.query(MessageFile).filter(MessageFile.message_id == self.id).all()
  674. @property
  675. def files(self):
  676. message_files = self.message_files
  677. files = []
  678. for message_file in message_files:
  679. url = message_file.url
  680. if message_file.type == 'image':
  681. if message_file.transfer_method == 'local_file':
  682. upload_file = (db.session.query(UploadFile)
  683. .filter(
  684. UploadFile.id == message_file.upload_file_id
  685. ).first())
  686. url = UploadFileParser.get_image_data(
  687. upload_file=upload_file,
  688. force_url=True
  689. )
  690. if message_file.transfer_method == 'tool_file':
  691. # get tool file id
  692. tool_file_id = message_file.url.split('/')[-1]
  693. # trim extension
  694. tool_file_id = tool_file_id.split('.')[0]
  695. # get extension
  696. if '.' in message_file.url:
  697. extension = f'.{message_file.url.split(".")[-1]}'
  698. if len(extension) > 10:
  699. extension = '.bin'
  700. else:
  701. extension = '.bin'
  702. # add sign url
  703. url = ToolFileParser.get_tool_file_manager().sign_file(tool_file_id=tool_file_id, extension=extension)
  704. files.append({
  705. 'id': message_file.id,
  706. 'type': message_file.type,
  707. 'url': url,
  708. 'belongs_to': message_file.belongs_to if message_file.belongs_to else 'user'
  709. })
  710. return files
  711. @property
  712. def workflow_run(self):
  713. if self.workflow_run_id:
  714. from .workflow import WorkflowRun
  715. return db.session.query(WorkflowRun).filter(WorkflowRun.id == self.workflow_run_id).first()
  716. return None
  717. def to_dict(self) -> dict:
  718. return {
  719. 'id': self.id,
  720. 'app_id': self.app_id,
  721. 'conversation_id': self.conversation_id,
  722. 'inputs': self.inputs,
  723. 'query': self.query,
  724. 'message': self.message,
  725. 'answer': self.answer,
  726. 'status': self.status,
  727. 'error': self.error,
  728. 'message_metadata': self.message_metadata_dict,
  729. 'from_source': self.from_source,
  730. 'from_end_user_id': self.from_end_user_id,
  731. 'from_account_id': self.from_account_id,
  732. 'created_at': self.created_at.isoformat(),
  733. 'updated_at': self.updated_at.isoformat(),
  734. 'agent_based': self.agent_based,
  735. 'workflow_run_id': self.workflow_run_id
  736. }
  737. @classmethod
  738. def from_dict(cls, data: dict):
  739. return cls(
  740. id=data['id'],
  741. app_id=data['app_id'],
  742. conversation_id=data['conversation_id'],
  743. inputs=data['inputs'],
  744. query=data['query'],
  745. message=data['message'],
  746. answer=data['answer'],
  747. status=data['status'],
  748. error=data['error'],
  749. message_metadata=json.dumps(data['message_metadata']),
  750. from_source=data['from_source'],
  751. from_end_user_id=data['from_end_user_id'],
  752. from_account_id=data['from_account_id'],
  753. created_at=data['created_at'],
  754. updated_at=data['updated_at'],
  755. agent_based=data['agent_based'],
  756. workflow_run_id=data['workflow_run_id']
  757. )
  758. class MessageFeedback(db.Model):
  759. __tablename__ = 'message_feedbacks'
  760. __table_args__ = (
  761. db.PrimaryKeyConstraint('id', name='message_feedback_pkey'),
  762. db.Index('message_feedback_app_idx', 'app_id'),
  763. db.Index('message_feedback_message_idx', 'message_id', 'from_source'),
  764. db.Index('message_feedback_conversation_idx', 'conversation_id', 'from_source', 'rating')
  765. )
  766. id = db.Column(StringUUID, server_default=db.text('uuid_generate_v4()'))
  767. app_id = db.Column(StringUUID, nullable=False)
  768. conversation_id = db.Column(StringUUID, nullable=False)
  769. message_id = db.Column(StringUUID, nullable=False)
  770. rating = db.Column(db.String(255), nullable=False)
  771. content = db.Column(db.Text)
  772. from_source = db.Column(db.String(255), nullable=False)
  773. from_end_user_id = db.Column(StringUUID)
  774. from_account_id = db.Column(StringUUID)
  775. created_at = db.Column(db.DateTime, nullable=False, server_default=db.text('CURRENT_TIMESTAMP(0)'))
  776. updated_at = db.Column(db.DateTime, nullable=False, server_default=db.text('CURRENT_TIMESTAMP(0)'))
  777. @property
  778. def from_account(self):
  779. account = db.session.query(Account).filter(Account.id == self.from_account_id).first()
  780. return account
  781. class MessageFile(db.Model):
  782. __tablename__ = 'message_files'
  783. __table_args__ = (
  784. db.PrimaryKeyConstraint('id', name='message_file_pkey'),
  785. db.Index('message_file_message_idx', 'message_id'),
  786. db.Index('message_file_created_by_idx', 'created_by')
  787. )
  788. id = db.Column(StringUUID, server_default=db.text('uuid_generate_v4()'))
  789. message_id = db.Column(StringUUID, nullable=False)
  790. type = db.Column(db.String(255), nullable=False)
  791. transfer_method = db.Column(db.String(255), nullable=False)
  792. url = db.Column(db.Text, nullable=True)
  793. belongs_to = db.Column(db.String(255), nullable=True)
  794. upload_file_id = db.Column(StringUUID, nullable=True)
  795. created_by_role = db.Column(db.String(255), nullable=False)
  796. created_by = db.Column(StringUUID, nullable=False)
  797. created_at = db.Column(db.DateTime, nullable=False, server_default=db.text('CURRENT_TIMESTAMP(0)'))
  798. class MessageAnnotation(db.Model):
  799. __tablename__ = 'message_annotations'
  800. __table_args__ = (
  801. db.PrimaryKeyConstraint('id', name='message_annotation_pkey'),
  802. db.Index('message_annotation_app_idx', 'app_id'),
  803. db.Index('message_annotation_conversation_idx', 'conversation_id'),
  804. db.Index('message_annotation_message_idx', 'message_id')
  805. )
  806. id = db.Column(StringUUID, server_default=db.text('uuid_generate_v4()'))
  807. app_id = db.Column(StringUUID, nullable=False)
  808. conversation_id = db.Column(StringUUID, db.ForeignKey('conversations.id'), nullable=True)
  809. message_id = db.Column(StringUUID, nullable=True)
  810. question = db.Column(db.Text, nullable=True)
  811. content = db.Column(db.Text, nullable=False)
  812. hit_count = db.Column(db.Integer, nullable=False, server_default=db.text('0'))
  813. account_id = db.Column(StringUUID, nullable=False)
  814. created_at = db.Column(db.DateTime, nullable=False, server_default=db.text('CURRENT_TIMESTAMP(0)'))
  815. updated_at = db.Column(db.DateTime, nullable=False, server_default=db.text('CURRENT_TIMESTAMP(0)'))
  816. @property
  817. def account(self):
  818. account = db.session.query(Account).filter(Account.id == self.account_id).first()
  819. return account
  820. @property
  821. def annotation_create_account(self):
  822. account = db.session.query(Account).filter(Account.id == self.account_id).first()
  823. return account
  824. class AppAnnotationHitHistory(db.Model):
  825. __tablename__ = 'app_annotation_hit_histories'
  826. __table_args__ = (
  827. db.PrimaryKeyConstraint('id', name='app_annotation_hit_histories_pkey'),
  828. db.Index('app_annotation_hit_histories_app_idx', 'app_id'),
  829. db.Index('app_annotation_hit_histories_account_idx', 'account_id'),
  830. db.Index('app_annotation_hit_histories_annotation_idx', 'annotation_id'),
  831. db.Index('app_annotation_hit_histories_message_idx', 'message_id'),
  832. )
  833. id = db.Column(StringUUID, server_default=db.text('uuid_generate_v4()'))
  834. app_id = db.Column(StringUUID, nullable=False)
  835. annotation_id = db.Column(StringUUID, nullable=False)
  836. source = db.Column(db.Text, nullable=False)
  837. question = db.Column(db.Text, nullable=False)
  838. account_id = db.Column(StringUUID, nullable=False)
  839. created_at = db.Column(db.DateTime, nullable=False, server_default=db.text('CURRENT_TIMESTAMP(0)'))
  840. score = db.Column(Float, nullable=False, server_default=db.text('0'))
  841. message_id = db.Column(StringUUID, nullable=False)
  842. annotation_question = db.Column(db.Text, nullable=False)
  843. annotation_content = db.Column(db.Text, nullable=False)
  844. @property
  845. def account(self):
  846. account = (db.session.query(Account)
  847. .join(MessageAnnotation, MessageAnnotation.account_id == Account.id)
  848. .filter(MessageAnnotation.id == self.annotation_id).first())
  849. return account
  850. @property
  851. def annotation_create_account(self):
  852. account = db.session.query(Account).filter(Account.id == self.account_id).first()
  853. return account
  854. class AppAnnotationSetting(db.Model):
  855. __tablename__ = 'app_annotation_settings'
  856. __table_args__ = (
  857. db.PrimaryKeyConstraint('id', name='app_annotation_settings_pkey'),
  858. db.Index('app_annotation_settings_app_idx', 'app_id')
  859. )
  860. id = db.Column(StringUUID, server_default=db.text('uuid_generate_v4()'))
  861. app_id = db.Column(StringUUID, nullable=False)
  862. score_threshold = db.Column(Float, nullable=False, server_default=db.text('0'))
  863. collection_binding_id = db.Column(StringUUID, nullable=False)
  864. created_user_id = db.Column(StringUUID, nullable=False)
  865. created_at = db.Column(db.DateTime, nullable=False, server_default=db.text('CURRENT_TIMESTAMP(0)'))
  866. updated_user_id = db.Column(StringUUID, nullable=False)
  867. updated_at = db.Column(db.DateTime, nullable=False, server_default=db.text('CURRENT_TIMESTAMP(0)'))
  868. @property
  869. def created_account(self):
  870. account = (db.session.query(Account)
  871. .join(AppAnnotationSetting, AppAnnotationSetting.created_user_id == Account.id)
  872. .filter(AppAnnotationSetting.id == self.annotation_id).first())
  873. return account
  874. @property
  875. def updated_account(self):
  876. account = (db.session.query(Account)
  877. .join(AppAnnotationSetting, AppAnnotationSetting.updated_user_id == Account.id)
  878. .filter(AppAnnotationSetting.id == self.annotation_id).first())
  879. return account
  880. @property
  881. def collection_binding_detail(self):
  882. from .dataset import DatasetCollectionBinding
  883. collection_binding_detail = (db.session.query(DatasetCollectionBinding)
  884. .filter(DatasetCollectionBinding.id == self.collection_binding_id).first())
  885. return collection_binding_detail
  886. class OperationLog(db.Model):
  887. __tablename__ = 'operation_logs'
  888. __table_args__ = (
  889. db.PrimaryKeyConstraint('id', name='operation_log_pkey'),
  890. db.Index('operation_log_account_action_idx', 'tenant_id', 'account_id', 'action')
  891. )
  892. id = db.Column(StringUUID, server_default=db.text('uuid_generate_v4()'))
  893. tenant_id = db.Column(StringUUID, nullable=False)
  894. account_id = db.Column(StringUUID, nullable=False)
  895. action = db.Column(db.String(255), nullable=False)
  896. content = db.Column(db.JSON)
  897. created_at = db.Column(db.DateTime, nullable=False, server_default=db.text('CURRENT_TIMESTAMP(0)'))
  898. created_ip = db.Column(db.String(255), nullable=False)
  899. updated_at = db.Column(db.DateTime, nullable=False, server_default=db.text('CURRENT_TIMESTAMP(0)'))
  900. class EndUser(UserMixin, db.Model):
  901. __tablename__ = 'end_users'
  902. __table_args__ = (
  903. db.PrimaryKeyConstraint('id', name='end_user_pkey'),
  904. db.Index('end_user_session_id_idx', 'session_id', 'type'),
  905. db.Index('end_user_tenant_session_id_idx', 'tenant_id', 'session_id', 'type'),
  906. )
  907. id = db.Column(StringUUID, server_default=db.text('uuid_generate_v4()'))
  908. tenant_id = db.Column(StringUUID, nullable=False)
  909. app_id = db.Column(StringUUID, nullable=True)
  910. type = db.Column(db.String(255), nullable=False)
  911. external_user_id = db.Column(db.String(255), nullable=True)
  912. name = db.Column(db.String(255))
  913. is_anonymous = db.Column(db.Boolean, nullable=False, server_default=db.text('true'))
  914. session_id = db.Column(db.String(255), nullable=False)
  915. created_at = db.Column(db.DateTime, nullable=False, server_default=db.text('CURRENT_TIMESTAMP(0)'))
  916. updated_at = db.Column(db.DateTime, nullable=False, server_default=db.text('CURRENT_TIMESTAMP(0)'))
  917. class Site(db.Model):
  918. __tablename__ = 'sites'
  919. __table_args__ = (
  920. db.PrimaryKeyConstraint('id', name='site_pkey'),
  921. db.Index('site_app_id_idx', 'app_id'),
  922. db.Index('site_code_idx', 'code', 'status')
  923. )
  924. id = db.Column(StringUUID, server_default=db.text('uuid_generate_v4()'))
  925. app_id = db.Column(StringUUID, nullable=False)
  926. title = db.Column(db.String(255), nullable=False)
  927. icon_type = db.Column(db.String(255), nullable=True)
  928. icon = db.Column(db.String(255))
  929. icon_background = db.Column(db.String(255))
  930. description = db.Column(db.Text)
  931. default_language = db.Column(db.String(255), nullable=False)
  932. chat_color_theme = db.Column(db.String(255))
  933. chat_color_theme_inverted = db.Column(db.Boolean, nullable=False, server_default=db.text('false'))
  934. copyright = db.Column(db.String(255))
  935. privacy_policy = db.Column(db.String(255))
  936. show_workflow_steps = db.Column(db.Boolean, nullable=False, server_default=db.text('true'))
  937. custom_disclaimer = db.Column(db.String(255), nullable=True)
  938. customize_domain = db.Column(db.String(255))
  939. customize_token_strategy = db.Column(db.String(255), nullable=False)
  940. prompt_public = db.Column(db.Boolean, nullable=False, server_default=db.text('false'))
  941. status = db.Column(db.String(255), nullable=False, server_default=db.text("'normal'::character varying"))
  942. created_at = db.Column(db.DateTime, nullable=False, server_default=db.text('CURRENT_TIMESTAMP(0)'))
  943. updated_at = db.Column(db.DateTime, nullable=False, server_default=db.text('CURRENT_TIMESTAMP(0)'))
  944. code = db.Column(db.String(255))
  945. @staticmethod
  946. def generate_code(n):
  947. while True:
  948. result = generate_string(n)
  949. while db.session.query(Site).filter(Site.code == result).count() > 0:
  950. result = generate_string(n)
  951. return result
  952. @property
  953. def app_base_url(self):
  954. return (
  955. dify_config.APP_WEB_URL if dify_config.APP_WEB_URL else request.url_root.rstrip('/'))
  956. class ApiToken(db.Model):
  957. __tablename__ = 'api_tokens'
  958. __table_args__ = (
  959. db.PrimaryKeyConstraint('id', name='api_token_pkey'),
  960. db.Index('api_token_app_id_type_idx', 'app_id', 'type'),
  961. db.Index('api_token_token_idx', 'token', 'type'),
  962. db.Index('api_token_tenant_idx', 'tenant_id', 'type')
  963. )
  964. id = db.Column(StringUUID, server_default=db.text('uuid_generate_v4()'))
  965. app_id = db.Column(StringUUID, nullable=True)
  966. tenant_id = db.Column(StringUUID, nullable=True)
  967. type = db.Column(db.String(16), nullable=False)
  968. token = db.Column(db.String(255), nullable=False)
  969. last_used_at = db.Column(db.DateTime, nullable=True)
  970. created_at = db.Column(db.DateTime, nullable=False, server_default=db.text('CURRENT_TIMESTAMP(0)'))
  971. @staticmethod
  972. def generate_api_key(prefix, n):
  973. while True:
  974. result = prefix + generate_string(n)
  975. while db.session.query(ApiToken).filter(ApiToken.token == result).count() > 0:
  976. result = prefix + generate_string(n)
  977. return result
  978. class UploadFile(db.Model):
  979. __tablename__ = 'upload_files'
  980. __table_args__ = (
  981. db.PrimaryKeyConstraint('id', name='upload_file_pkey'),
  982. db.Index('upload_file_tenant_idx', 'tenant_id')
  983. )
  984. id = db.Column(StringUUID, server_default=db.text('uuid_generate_v4()'))
  985. tenant_id = db.Column(StringUUID, nullable=False)
  986. storage_type = db.Column(db.String(255), nullable=False)
  987. key = db.Column(db.String(255), nullable=False)
  988. name = db.Column(db.String(255), nullable=False)
  989. size = db.Column(db.Integer, nullable=False)
  990. extension = db.Column(db.String(255), nullable=False)
  991. mime_type = db.Column(db.String(255), nullable=True)
  992. created_by_role = db.Column(db.String(255), nullable=False, server_default=db.text("'account'::character varying"))
  993. created_by = db.Column(StringUUID, nullable=False)
  994. created_at = db.Column(db.DateTime, nullable=False, server_default=db.text('CURRENT_TIMESTAMP(0)'))
  995. used = db.Column(db.Boolean, nullable=False, server_default=db.text('false'))
  996. used_by = db.Column(StringUUID, nullable=True)
  997. used_at = db.Column(db.DateTime, nullable=True)
  998. hash = db.Column(db.String(255), nullable=True)
  999. class ApiRequest(db.Model):
  1000. __tablename__ = 'api_requests'
  1001. __table_args__ = (
  1002. db.PrimaryKeyConstraint('id', name='api_request_pkey'),
  1003. db.Index('api_request_token_idx', 'tenant_id', 'api_token_id')
  1004. )
  1005. id = db.Column(StringUUID, nullable=False, server_default=db.text('uuid_generate_v4()'))
  1006. tenant_id = db.Column(StringUUID, nullable=False)
  1007. api_token_id = db.Column(StringUUID, nullable=False)
  1008. path = db.Column(db.String(255), nullable=False)
  1009. request = db.Column(db.Text, nullable=True)
  1010. response = db.Column(db.Text, nullable=True)
  1011. ip = db.Column(db.String(255), nullable=False)
  1012. created_at = db.Column(db.DateTime, nullable=False, server_default=db.text('CURRENT_TIMESTAMP(0)'))
  1013. class MessageChain(db.Model):
  1014. __tablename__ = 'message_chains'
  1015. __table_args__ = (
  1016. db.PrimaryKeyConstraint('id', name='message_chain_pkey'),
  1017. db.Index('message_chain_message_id_idx', 'message_id')
  1018. )
  1019. id = db.Column(StringUUID, nullable=False, server_default=db.text('uuid_generate_v4()'))
  1020. message_id = db.Column(StringUUID, nullable=False)
  1021. type = db.Column(db.String(255), nullable=False)
  1022. input = db.Column(db.Text, nullable=True)
  1023. output = db.Column(db.Text, nullable=True)
  1024. created_at = db.Column(db.DateTime, nullable=False, server_default=db.func.current_timestamp())
  1025. class MessageAgentThought(db.Model):
  1026. __tablename__ = 'message_agent_thoughts'
  1027. __table_args__ = (
  1028. db.PrimaryKeyConstraint('id', name='message_agent_thought_pkey'),
  1029. db.Index('message_agent_thought_message_id_idx', 'message_id'),
  1030. db.Index('message_agent_thought_message_chain_id_idx', 'message_chain_id'),
  1031. )
  1032. id = db.Column(StringUUID, nullable=False, server_default=db.text('uuid_generate_v4()'))
  1033. message_id = db.Column(StringUUID, nullable=False)
  1034. message_chain_id = db.Column(StringUUID, nullable=True)
  1035. position = db.Column(db.Integer, nullable=False)
  1036. thought = db.Column(db.Text, nullable=True)
  1037. tool = db.Column(db.Text, nullable=True)
  1038. tool_labels_str = db.Column(db.Text, nullable=False, server_default=db.text("'{}'::text"))
  1039. tool_meta_str = db.Column(db.Text, nullable=False, server_default=db.text("'{}'::text"))
  1040. tool_input = db.Column(db.Text, nullable=True)
  1041. observation = db.Column(db.Text, nullable=True)
  1042. # plugin_id = db.Column(StringUUID, nullable=True) ## for future design
  1043. tool_process_data = db.Column(db.Text, nullable=True)
  1044. message = db.Column(db.Text, nullable=True)
  1045. message_token = db.Column(db.Integer, nullable=True)
  1046. message_unit_price = db.Column(db.Numeric, nullable=True)
  1047. message_price_unit = db.Column(db.Numeric(10, 7), nullable=False, server_default=db.text('0.001'))
  1048. message_files = db.Column(db.Text, nullable=True)
  1049. answer = db.Column(db.Text, nullable=True)
  1050. answer_token = db.Column(db.Integer, nullable=True)
  1051. answer_unit_price = db.Column(db.Numeric, nullable=True)
  1052. answer_price_unit = db.Column(db.Numeric(10, 7), nullable=False, server_default=db.text('0.001'))
  1053. tokens = db.Column(db.Integer, nullable=True)
  1054. total_price = db.Column(db.Numeric, nullable=True)
  1055. currency = db.Column(db.String, nullable=True)
  1056. latency = db.Column(db.Float, nullable=True)
  1057. created_by_role = db.Column(db.String, nullable=False)
  1058. created_by = db.Column(StringUUID, nullable=False)
  1059. created_at = db.Column(db.DateTime, nullable=False, server_default=db.func.current_timestamp())
  1060. @property
  1061. def files(self) -> list:
  1062. if self.message_files:
  1063. return json.loads(self.message_files)
  1064. else:
  1065. return []
  1066. @property
  1067. def tools(self) -> list[str]:
  1068. return self.tool.split(";") if self.tool else []
  1069. @property
  1070. def tool_labels(self) -> dict:
  1071. try:
  1072. if self.tool_labels_str:
  1073. return json.loads(self.tool_labels_str)
  1074. else:
  1075. return {}
  1076. except Exception as e:
  1077. return {}
  1078. @property
  1079. def tool_meta(self) -> dict:
  1080. try:
  1081. if self.tool_meta_str:
  1082. return json.loads(self.tool_meta_str)
  1083. else:
  1084. return {}
  1085. except Exception as e:
  1086. return {}
  1087. @property
  1088. def tool_inputs_dict(self) -> dict:
  1089. tools = self.tools
  1090. try:
  1091. if self.tool_input:
  1092. data = json.loads(self.tool_input)
  1093. result = {}
  1094. for tool in tools:
  1095. if tool in data:
  1096. result[tool] = data[tool]
  1097. else:
  1098. if len(tools) == 1:
  1099. result[tool] = data
  1100. else:
  1101. result[tool] = {}
  1102. return result
  1103. else:
  1104. return {
  1105. tool: {} for tool in tools
  1106. }
  1107. except Exception as e:
  1108. return {}
  1109. @property
  1110. def tool_outputs_dict(self) -> dict:
  1111. tools = self.tools
  1112. try:
  1113. if self.observation:
  1114. data = json.loads(self.observation)
  1115. result = {}
  1116. for tool in tools:
  1117. if tool in data:
  1118. result[tool] = data[tool]
  1119. else:
  1120. if len(tools) == 1:
  1121. result[tool] = data
  1122. else:
  1123. result[tool] = {}
  1124. return result
  1125. else:
  1126. return {
  1127. tool: {} for tool in tools
  1128. }
  1129. except Exception as e:
  1130. if self.observation:
  1131. return dict.fromkeys(tools, self.observation)
  1132. class DatasetRetrieverResource(db.Model):
  1133. __tablename__ = 'dataset_retriever_resources'
  1134. __table_args__ = (
  1135. db.PrimaryKeyConstraint('id', name='dataset_retriever_resource_pkey'),
  1136. db.Index('dataset_retriever_resource_message_id_idx', 'message_id'),
  1137. )
  1138. id = db.Column(StringUUID, nullable=False, server_default=db.text('uuid_generate_v4()'))
  1139. message_id = db.Column(StringUUID, nullable=False)
  1140. position = db.Column(db.Integer, nullable=False)
  1141. dataset_id = db.Column(StringUUID, nullable=False)
  1142. dataset_name = db.Column(db.Text, nullable=False)
  1143. document_id = db.Column(StringUUID, nullable=False)
  1144. document_name = db.Column(db.Text, nullable=False)
  1145. data_source_type = db.Column(db.Text, nullable=False)
  1146. segment_id = db.Column(StringUUID, nullable=False)
  1147. score = db.Column(db.Float, nullable=True)
  1148. content = db.Column(db.Text, nullable=False)
  1149. hit_count = db.Column(db.Integer, nullable=True)
  1150. word_count = db.Column(db.Integer, nullable=True)
  1151. segment_position = db.Column(db.Integer, nullable=True)
  1152. index_node_hash = db.Column(db.Text, nullable=True)
  1153. retriever_from = db.Column(db.Text, nullable=False)
  1154. created_by = db.Column(StringUUID, nullable=False)
  1155. created_at = db.Column(db.DateTime, nullable=False, server_default=db.func.current_timestamp())
  1156. class Tag(db.Model):
  1157. __tablename__ = 'tags'
  1158. __table_args__ = (
  1159. db.PrimaryKeyConstraint('id', name='tag_pkey'),
  1160. db.Index('tag_type_idx', 'type'),
  1161. db.Index('tag_name_idx', 'name'),
  1162. )
  1163. TAG_TYPE_LIST = ['knowledge', 'app']
  1164. id = db.Column(StringUUID, server_default=db.text('uuid_generate_v4()'))
  1165. tenant_id = db.Column(StringUUID, nullable=True)
  1166. type = db.Column(db.String(16), nullable=False)
  1167. name = db.Column(db.String(255), nullable=False)
  1168. created_by = db.Column(StringUUID, nullable=False)
  1169. created_at = db.Column(db.DateTime, nullable=False, server_default=db.text('CURRENT_TIMESTAMP(0)'))
  1170. class TagBinding(db.Model):
  1171. __tablename__ = 'tag_bindings'
  1172. __table_args__ = (
  1173. db.PrimaryKeyConstraint('id', name='tag_binding_pkey'),
  1174. db.Index('tag_bind_target_id_idx', 'target_id'),
  1175. db.Index('tag_bind_tag_id_idx', 'tag_id'),
  1176. )
  1177. id = db.Column(StringUUID, server_default=db.text('uuid_generate_v4()'))
  1178. tenant_id = db.Column(StringUUID, nullable=True)
  1179. tag_id = db.Column(StringUUID, nullable=True)
  1180. target_id = db.Column(StringUUID, nullable=True)
  1181. created_by = db.Column(StringUUID, nullable=False)
  1182. created_at = db.Column(db.DateTime, nullable=False, server_default=db.text('CURRENT_TIMESTAMP(0)'))
  1183. class TraceAppConfig(db.Model):
  1184. __tablename__ = 'trace_app_config'
  1185. __table_args__ = (
  1186. db.PrimaryKeyConstraint('id', name='tracing_app_config_pkey'),
  1187. db.Index('trace_app_config_app_id_idx', 'app_id'),
  1188. )
  1189. id = db.Column(StringUUID, server_default=db.text('uuid_generate_v4()'))
  1190. app_id = db.Column(StringUUID, nullable=False)
  1191. tracing_provider = db.Column(db.String(255), nullable=True)
  1192. tracing_config = db.Column(db.JSON, nullable=True)
  1193. created_at = db.Column(db.DateTime, nullable=False, server_default=func.now())
  1194. updated_at = db.Column(db.DateTime, nullable=False, server_default=func.now(), onupdate=func.now())
  1195. is_active = db.Column(db.Boolean, nullable=False, server_default=db.text('true'))
  1196. @property
  1197. def tracing_config_dict(self):
  1198. return self.tracing_config if self.tracing_config else {}
  1199. @property
  1200. def tracing_config_str(self):
  1201. return json.dumps(self.tracing_config_dict)
  1202. def to_dict(self):
  1203. return {
  1204. 'id': self.id,
  1205. 'app_id': self.app_id,
  1206. 'tracing_provider': self.tracing_provider,
  1207. 'tracing_config': self.tracing_config_dict,
  1208. "is_active": self.is_active,
  1209. "created_at": self.created_at.__str__() if self.created_at else None,
  1210. 'updated_at': self.updated_at.__str__() if self.updated_at else None,
  1211. }