model.py 58 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430
  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 from_account_name(self):
  533. if self.from_account_id:
  534. account = db.session.query(Account).filter(Account.id == self.from_account_id).first()
  535. if account:
  536. return account.name
  537. return None
  538. @property
  539. def in_debug_mode(self):
  540. return self.override_model_configs is not None
  541. class Message(db.Model):
  542. __tablename__ = 'messages'
  543. __table_args__ = (
  544. db.PrimaryKeyConstraint('id', name='message_pkey'),
  545. db.Index('message_app_id_idx', 'app_id', 'created_at'),
  546. db.Index('message_conversation_id_idx', 'conversation_id'),
  547. db.Index('message_end_user_idx', 'app_id', 'from_source', 'from_end_user_id'),
  548. db.Index('message_account_idx', 'app_id', 'from_source', 'from_account_id'),
  549. db.Index('message_workflow_run_id_idx', 'conversation_id', 'workflow_run_id')
  550. )
  551. id = db.Column(StringUUID, server_default=db.text('uuid_generate_v4()'))
  552. app_id = db.Column(StringUUID, nullable=False)
  553. model_provider = db.Column(db.String(255), nullable=True)
  554. model_id = db.Column(db.String(255), nullable=True)
  555. override_model_configs = db.Column(db.Text)
  556. conversation_id = db.Column(StringUUID, db.ForeignKey('conversations.id'), nullable=False)
  557. inputs = db.Column(db.JSON)
  558. query = db.Column(db.Text, nullable=False)
  559. message = db.Column(db.JSON, nullable=False)
  560. message_tokens = db.Column(db.Integer, nullable=False, server_default=db.text('0'))
  561. message_unit_price = db.Column(db.Numeric(10, 4), nullable=False)
  562. message_price_unit = db.Column(db.Numeric(10, 7), nullable=False, server_default=db.text('0.001'))
  563. answer = db.Column(db.Text, nullable=False)
  564. answer_tokens = db.Column(db.Integer, nullable=False, server_default=db.text('0'))
  565. answer_unit_price = db.Column(db.Numeric(10, 4), nullable=False)
  566. answer_price_unit = db.Column(db.Numeric(10, 7), nullable=False, server_default=db.text('0.001'))
  567. provider_response_latency = db.Column(db.Float, nullable=False, server_default=db.text('0'))
  568. total_price = db.Column(db.Numeric(10, 7))
  569. currency = db.Column(db.String(255), nullable=False)
  570. status = db.Column(db.String(255), nullable=False, server_default=db.text("'normal'::character varying"))
  571. error = db.Column(db.Text)
  572. message_metadata = db.Column(db.Text)
  573. invoke_from = db.Column(db.String(255), nullable=True)
  574. from_source = db.Column(db.String(255), nullable=False)
  575. from_end_user_id = db.Column(StringUUID)
  576. from_account_id = db.Column(StringUUID)
  577. created_at = db.Column(db.DateTime, nullable=False, server_default=db.text('CURRENT_TIMESTAMP(0)'))
  578. updated_at = db.Column(db.DateTime, nullable=False, server_default=db.text('CURRENT_TIMESTAMP(0)'))
  579. agent_based = db.Column(db.Boolean, nullable=False, server_default=db.text('false'))
  580. workflow_run_id = db.Column(StringUUID)
  581. @property
  582. def re_sign_file_url_answer(self) -> str:
  583. if not self.answer:
  584. return self.answer
  585. pattern = r'\[!?.*?\]\((((http|https):\/\/.+)?\/files\/(tools\/)?[\w-]+.*?timestamp=.*&nonce=.*&sign=.*)\)'
  586. matches = re.findall(pattern, self.answer)
  587. if not matches:
  588. return self.answer
  589. urls = [match[0] for match in matches]
  590. # remove duplicate urls
  591. urls = list(set(urls))
  592. if not urls:
  593. return self.answer
  594. re_sign_file_url_answer = self.answer
  595. for url in urls:
  596. if 'files/tools' in url:
  597. # get tool file id
  598. tool_file_id_pattern = r'\/files\/tools\/([\.\w-]+)?\?timestamp='
  599. result = re.search(tool_file_id_pattern, url)
  600. if not result:
  601. continue
  602. tool_file_id = result.group(1)
  603. # get extension
  604. if '.' in tool_file_id:
  605. split_result = tool_file_id.split('.')
  606. extension = f'.{split_result[-1]}'
  607. if len(extension) > 10:
  608. extension = '.bin'
  609. tool_file_id = split_result[0]
  610. else:
  611. extension = '.bin'
  612. if not tool_file_id:
  613. continue
  614. sign_url = ToolFileParser.get_tool_file_manager().sign_file(
  615. tool_file_id=tool_file_id,
  616. extension=extension
  617. )
  618. else:
  619. # get upload file id
  620. upload_file_id_pattern = r'\/files\/([\w-]+)\/image-preview?\?timestamp='
  621. result = re.search(upload_file_id_pattern, url)
  622. if not result:
  623. continue
  624. upload_file_id = result.group(1)
  625. if not upload_file_id:
  626. continue
  627. sign_url = UploadFileParser.get_signed_temp_image_url(upload_file_id)
  628. re_sign_file_url_answer = re_sign_file_url_answer.replace(url, sign_url)
  629. return re_sign_file_url_answer
  630. @property
  631. def user_feedback(self):
  632. feedback = db.session.query(MessageFeedback).filter(MessageFeedback.message_id == self.id,
  633. MessageFeedback.from_source == 'user').first()
  634. return feedback
  635. @property
  636. def admin_feedback(self):
  637. feedback = db.session.query(MessageFeedback).filter(MessageFeedback.message_id == self.id,
  638. MessageFeedback.from_source == 'admin').first()
  639. return feedback
  640. @property
  641. def feedbacks(self):
  642. feedbacks = db.session.query(MessageFeedback).filter(MessageFeedback.message_id == self.id).all()
  643. return feedbacks
  644. @property
  645. def annotation(self):
  646. annotation = db.session.query(MessageAnnotation).filter(MessageAnnotation.message_id == self.id).first()
  647. return annotation
  648. @property
  649. def annotation_hit_history(self):
  650. annotation_history = (db.session.query(AppAnnotationHitHistory)
  651. .filter(AppAnnotationHitHistory.message_id == self.id).first())
  652. if annotation_history:
  653. annotation = (db.session.query(MessageAnnotation).
  654. filter(MessageAnnotation.id == annotation_history.annotation_id).first())
  655. return annotation
  656. return None
  657. @property
  658. def app_model_config(self):
  659. conversation = db.session.query(Conversation).filter(Conversation.id == self.conversation_id).first()
  660. if conversation:
  661. return db.session.query(AppModelConfig).filter(
  662. AppModelConfig.id == conversation.app_model_config_id).first()
  663. return None
  664. @property
  665. def in_debug_mode(self):
  666. return self.override_model_configs is not None
  667. @property
  668. def message_metadata_dict(self) -> dict:
  669. return json.loads(self.message_metadata) if self.message_metadata else {}
  670. @property
  671. def agent_thoughts(self):
  672. return db.session.query(MessageAgentThought).filter(MessageAgentThought.message_id == self.id) \
  673. .order_by(MessageAgentThought.position.asc()).all()
  674. @property
  675. def retriever_resources(self):
  676. return db.session.query(DatasetRetrieverResource).filter(DatasetRetrieverResource.message_id == self.id) \
  677. .order_by(DatasetRetrieverResource.position.asc()).all()
  678. @property
  679. def message_files(self):
  680. return db.session.query(MessageFile).filter(MessageFile.message_id == self.id).all()
  681. @property
  682. def files(self):
  683. message_files = self.message_files
  684. files = []
  685. for message_file in message_files:
  686. url = message_file.url
  687. if message_file.type == 'image':
  688. if message_file.transfer_method == 'local_file':
  689. upload_file = (db.session.query(UploadFile)
  690. .filter(
  691. UploadFile.id == message_file.upload_file_id
  692. ).first())
  693. url = UploadFileParser.get_image_data(
  694. upload_file=upload_file,
  695. force_url=True
  696. )
  697. if message_file.transfer_method == 'tool_file':
  698. # get tool file id
  699. tool_file_id = message_file.url.split('/')[-1]
  700. # trim extension
  701. tool_file_id = tool_file_id.split('.')[0]
  702. # get extension
  703. if '.' in message_file.url:
  704. extension = f'.{message_file.url.split(".")[-1]}'
  705. if len(extension) > 10:
  706. extension = '.bin'
  707. else:
  708. extension = '.bin'
  709. # add sign url
  710. url = ToolFileParser.get_tool_file_manager().sign_file(tool_file_id=tool_file_id, extension=extension)
  711. files.append({
  712. 'id': message_file.id,
  713. 'type': message_file.type,
  714. 'url': url,
  715. 'belongs_to': message_file.belongs_to if message_file.belongs_to else 'user'
  716. })
  717. return files
  718. @property
  719. def workflow_run(self):
  720. if self.workflow_run_id:
  721. from .workflow import WorkflowRun
  722. return db.session.query(WorkflowRun).filter(WorkflowRun.id == self.workflow_run_id).first()
  723. return None
  724. def to_dict(self) -> dict:
  725. return {
  726. 'id': self.id,
  727. 'app_id': self.app_id,
  728. 'conversation_id': self.conversation_id,
  729. 'inputs': self.inputs,
  730. 'query': self.query,
  731. 'message': self.message,
  732. 'answer': self.answer,
  733. 'status': self.status,
  734. 'error': self.error,
  735. 'message_metadata': self.message_metadata_dict,
  736. 'from_source': self.from_source,
  737. 'from_end_user_id': self.from_end_user_id,
  738. 'from_account_id': self.from_account_id,
  739. 'created_at': self.created_at.isoformat(),
  740. 'updated_at': self.updated_at.isoformat(),
  741. 'agent_based': self.agent_based,
  742. 'workflow_run_id': self.workflow_run_id
  743. }
  744. @classmethod
  745. def from_dict(cls, data: dict):
  746. return cls(
  747. id=data['id'],
  748. app_id=data['app_id'],
  749. conversation_id=data['conversation_id'],
  750. inputs=data['inputs'],
  751. query=data['query'],
  752. message=data['message'],
  753. answer=data['answer'],
  754. status=data['status'],
  755. error=data['error'],
  756. message_metadata=json.dumps(data['message_metadata']),
  757. from_source=data['from_source'],
  758. from_end_user_id=data['from_end_user_id'],
  759. from_account_id=data['from_account_id'],
  760. created_at=data['created_at'],
  761. updated_at=data['updated_at'],
  762. agent_based=data['agent_based'],
  763. workflow_run_id=data['workflow_run_id']
  764. )
  765. class MessageFeedback(db.Model):
  766. __tablename__ = 'message_feedbacks'
  767. __table_args__ = (
  768. db.PrimaryKeyConstraint('id', name='message_feedback_pkey'),
  769. db.Index('message_feedback_app_idx', 'app_id'),
  770. db.Index('message_feedback_message_idx', 'message_id', 'from_source'),
  771. db.Index('message_feedback_conversation_idx', 'conversation_id', 'from_source', 'rating')
  772. )
  773. id = db.Column(StringUUID, server_default=db.text('uuid_generate_v4()'))
  774. app_id = db.Column(StringUUID, nullable=False)
  775. conversation_id = db.Column(StringUUID, nullable=False)
  776. message_id = db.Column(StringUUID, nullable=False)
  777. rating = db.Column(db.String(255), nullable=False)
  778. content = db.Column(db.Text)
  779. from_source = db.Column(db.String(255), nullable=False)
  780. from_end_user_id = db.Column(StringUUID)
  781. from_account_id = db.Column(StringUUID)
  782. created_at = db.Column(db.DateTime, nullable=False, server_default=db.text('CURRENT_TIMESTAMP(0)'))
  783. updated_at = db.Column(db.DateTime, nullable=False, server_default=db.text('CURRENT_TIMESTAMP(0)'))
  784. @property
  785. def from_account(self):
  786. account = db.session.query(Account).filter(Account.id == self.from_account_id).first()
  787. return account
  788. class MessageFile(db.Model):
  789. __tablename__ = 'message_files'
  790. __table_args__ = (
  791. db.PrimaryKeyConstraint('id', name='message_file_pkey'),
  792. db.Index('message_file_message_idx', 'message_id'),
  793. db.Index('message_file_created_by_idx', 'created_by')
  794. )
  795. id = db.Column(StringUUID, server_default=db.text('uuid_generate_v4()'))
  796. message_id = db.Column(StringUUID, nullable=False)
  797. type = db.Column(db.String(255), nullable=False)
  798. transfer_method = db.Column(db.String(255), nullable=False)
  799. url = db.Column(db.Text, nullable=True)
  800. belongs_to = db.Column(db.String(255), nullable=True)
  801. upload_file_id = db.Column(StringUUID, nullable=True)
  802. created_by_role = db.Column(db.String(255), nullable=False)
  803. created_by = db.Column(StringUUID, nullable=False)
  804. created_at = db.Column(db.DateTime, nullable=False, server_default=db.text('CURRENT_TIMESTAMP(0)'))
  805. class MessageAnnotation(db.Model):
  806. __tablename__ = 'message_annotations'
  807. __table_args__ = (
  808. db.PrimaryKeyConstraint('id', name='message_annotation_pkey'),
  809. db.Index('message_annotation_app_idx', 'app_id'),
  810. db.Index('message_annotation_conversation_idx', 'conversation_id'),
  811. db.Index('message_annotation_message_idx', 'message_id')
  812. )
  813. id = db.Column(StringUUID, server_default=db.text('uuid_generate_v4()'))
  814. app_id = db.Column(StringUUID, nullable=False)
  815. conversation_id = db.Column(StringUUID, db.ForeignKey('conversations.id'), nullable=True)
  816. message_id = db.Column(StringUUID, nullable=True)
  817. question = db.Column(db.Text, nullable=True)
  818. content = db.Column(db.Text, nullable=False)
  819. hit_count = db.Column(db.Integer, nullable=False, server_default=db.text('0'))
  820. account_id = db.Column(StringUUID, nullable=False)
  821. created_at = db.Column(db.DateTime, nullable=False, server_default=db.text('CURRENT_TIMESTAMP(0)'))
  822. updated_at = db.Column(db.DateTime, nullable=False, server_default=db.text('CURRENT_TIMESTAMP(0)'))
  823. @property
  824. def account(self):
  825. account = db.session.query(Account).filter(Account.id == self.account_id).first()
  826. return account
  827. @property
  828. def annotation_create_account(self):
  829. account = db.session.query(Account).filter(Account.id == self.account_id).first()
  830. return account
  831. class AppAnnotationHitHistory(db.Model):
  832. __tablename__ = 'app_annotation_hit_histories'
  833. __table_args__ = (
  834. db.PrimaryKeyConstraint('id', name='app_annotation_hit_histories_pkey'),
  835. db.Index('app_annotation_hit_histories_app_idx', 'app_id'),
  836. db.Index('app_annotation_hit_histories_account_idx', 'account_id'),
  837. db.Index('app_annotation_hit_histories_annotation_idx', 'annotation_id'),
  838. db.Index('app_annotation_hit_histories_message_idx', 'message_id'),
  839. )
  840. id = db.Column(StringUUID, server_default=db.text('uuid_generate_v4()'))
  841. app_id = db.Column(StringUUID, nullable=False)
  842. annotation_id = db.Column(StringUUID, nullable=False)
  843. source = db.Column(db.Text, nullable=False)
  844. question = db.Column(db.Text, nullable=False)
  845. account_id = db.Column(StringUUID, nullable=False)
  846. created_at = db.Column(db.DateTime, nullable=False, server_default=db.text('CURRENT_TIMESTAMP(0)'))
  847. score = db.Column(Float, nullable=False, server_default=db.text('0'))
  848. message_id = db.Column(StringUUID, nullable=False)
  849. annotation_question = db.Column(db.Text, nullable=False)
  850. annotation_content = db.Column(db.Text, nullable=False)
  851. @property
  852. def account(self):
  853. account = (db.session.query(Account)
  854. .join(MessageAnnotation, MessageAnnotation.account_id == Account.id)
  855. .filter(MessageAnnotation.id == self.annotation_id).first())
  856. return account
  857. @property
  858. def annotation_create_account(self):
  859. account = db.session.query(Account).filter(Account.id == self.account_id).first()
  860. return account
  861. class AppAnnotationSetting(db.Model):
  862. __tablename__ = 'app_annotation_settings'
  863. __table_args__ = (
  864. db.PrimaryKeyConstraint('id', name='app_annotation_settings_pkey'),
  865. db.Index('app_annotation_settings_app_idx', 'app_id')
  866. )
  867. id = db.Column(StringUUID, server_default=db.text('uuid_generate_v4()'))
  868. app_id = db.Column(StringUUID, nullable=False)
  869. score_threshold = db.Column(Float, nullable=False, server_default=db.text('0'))
  870. collection_binding_id = db.Column(StringUUID, nullable=False)
  871. created_user_id = db.Column(StringUUID, nullable=False)
  872. created_at = db.Column(db.DateTime, nullable=False, server_default=db.text('CURRENT_TIMESTAMP(0)'))
  873. updated_user_id = db.Column(StringUUID, nullable=False)
  874. updated_at = db.Column(db.DateTime, nullable=False, server_default=db.text('CURRENT_TIMESTAMP(0)'))
  875. @property
  876. def created_account(self):
  877. account = (db.session.query(Account)
  878. .join(AppAnnotationSetting, AppAnnotationSetting.created_user_id == Account.id)
  879. .filter(AppAnnotationSetting.id == self.annotation_id).first())
  880. return account
  881. @property
  882. def updated_account(self):
  883. account = (db.session.query(Account)
  884. .join(AppAnnotationSetting, AppAnnotationSetting.updated_user_id == Account.id)
  885. .filter(AppAnnotationSetting.id == self.annotation_id).first())
  886. return account
  887. @property
  888. def collection_binding_detail(self):
  889. from .dataset import DatasetCollectionBinding
  890. collection_binding_detail = (db.session.query(DatasetCollectionBinding)
  891. .filter(DatasetCollectionBinding.id == self.collection_binding_id).first())
  892. return collection_binding_detail
  893. class OperationLog(db.Model):
  894. __tablename__ = 'operation_logs'
  895. __table_args__ = (
  896. db.PrimaryKeyConstraint('id', name='operation_log_pkey'),
  897. db.Index('operation_log_account_action_idx', 'tenant_id', 'account_id', 'action')
  898. )
  899. id = db.Column(StringUUID, server_default=db.text('uuid_generate_v4()'))
  900. tenant_id = db.Column(StringUUID, nullable=False)
  901. account_id = db.Column(StringUUID, nullable=False)
  902. action = db.Column(db.String(255), nullable=False)
  903. content = db.Column(db.JSON)
  904. created_at = db.Column(db.DateTime, nullable=False, server_default=db.text('CURRENT_TIMESTAMP(0)'))
  905. created_ip = db.Column(db.String(255), nullable=False)
  906. updated_at = db.Column(db.DateTime, nullable=False, server_default=db.text('CURRENT_TIMESTAMP(0)'))
  907. class EndUser(UserMixin, db.Model):
  908. __tablename__ = 'end_users'
  909. __table_args__ = (
  910. db.PrimaryKeyConstraint('id', name='end_user_pkey'),
  911. db.Index('end_user_session_id_idx', 'session_id', 'type'),
  912. db.Index('end_user_tenant_session_id_idx', 'tenant_id', 'session_id', 'type'),
  913. )
  914. id = db.Column(StringUUID, server_default=db.text('uuid_generate_v4()'))
  915. tenant_id = db.Column(StringUUID, nullable=False)
  916. app_id = db.Column(StringUUID, nullable=True)
  917. type = db.Column(db.String(255), nullable=False)
  918. external_user_id = db.Column(db.String(255), nullable=True)
  919. name = db.Column(db.String(255))
  920. is_anonymous = db.Column(db.Boolean, nullable=False, server_default=db.text('true'))
  921. session_id = db.Column(db.String(255), nullable=False)
  922. created_at = db.Column(db.DateTime, nullable=False, server_default=db.text('CURRENT_TIMESTAMP(0)'))
  923. updated_at = db.Column(db.DateTime, nullable=False, server_default=db.text('CURRENT_TIMESTAMP(0)'))
  924. class Site(db.Model):
  925. __tablename__ = 'sites'
  926. __table_args__ = (
  927. db.PrimaryKeyConstraint('id', name='site_pkey'),
  928. db.Index('site_app_id_idx', 'app_id'),
  929. db.Index('site_code_idx', 'code', 'status')
  930. )
  931. id = db.Column(StringUUID, server_default=db.text('uuid_generate_v4()'))
  932. app_id = db.Column(StringUUID, nullable=False)
  933. title = db.Column(db.String(255), nullable=False)
  934. icon_type = db.Column(db.String(255), nullable=True)
  935. icon = db.Column(db.String(255))
  936. icon_background = db.Column(db.String(255))
  937. description = db.Column(db.Text)
  938. default_language = db.Column(db.String(255), nullable=False)
  939. chat_color_theme = db.Column(db.String(255))
  940. chat_color_theme_inverted = db.Column(db.Boolean, nullable=False, server_default=db.text('false'))
  941. copyright = db.Column(db.String(255))
  942. privacy_policy = db.Column(db.String(255))
  943. show_workflow_steps = db.Column(db.Boolean, nullable=False, server_default=db.text('true'))
  944. custom_disclaimer = db.Column(db.String(255), nullable=True)
  945. customize_domain = db.Column(db.String(255))
  946. customize_token_strategy = db.Column(db.String(255), nullable=False)
  947. prompt_public = db.Column(db.Boolean, nullable=False, server_default=db.text('false'))
  948. status = db.Column(db.String(255), nullable=False, server_default=db.text("'normal'::character varying"))
  949. created_at = db.Column(db.DateTime, nullable=False, server_default=db.text('CURRENT_TIMESTAMP(0)'))
  950. updated_at = db.Column(db.DateTime, nullable=False, server_default=db.text('CURRENT_TIMESTAMP(0)'))
  951. code = db.Column(db.String(255))
  952. @staticmethod
  953. def generate_code(n):
  954. while True:
  955. result = generate_string(n)
  956. while db.session.query(Site).filter(Site.code == result).count() > 0:
  957. result = generate_string(n)
  958. return result
  959. @property
  960. def app_base_url(self):
  961. return (
  962. dify_config.APP_WEB_URL if dify_config.APP_WEB_URL else request.url_root.rstrip('/'))
  963. class ApiToken(db.Model):
  964. __tablename__ = 'api_tokens'
  965. __table_args__ = (
  966. db.PrimaryKeyConstraint('id', name='api_token_pkey'),
  967. db.Index('api_token_app_id_type_idx', 'app_id', 'type'),
  968. db.Index('api_token_token_idx', 'token', 'type'),
  969. db.Index('api_token_tenant_idx', 'tenant_id', 'type')
  970. )
  971. id = db.Column(StringUUID, server_default=db.text('uuid_generate_v4()'))
  972. app_id = db.Column(StringUUID, nullable=True)
  973. tenant_id = db.Column(StringUUID, nullable=True)
  974. type = db.Column(db.String(16), nullable=False)
  975. token = db.Column(db.String(255), nullable=False)
  976. last_used_at = db.Column(db.DateTime, nullable=True)
  977. created_at = db.Column(db.DateTime, nullable=False, server_default=db.text('CURRENT_TIMESTAMP(0)'))
  978. @staticmethod
  979. def generate_api_key(prefix, n):
  980. while True:
  981. result = prefix + generate_string(n)
  982. while db.session.query(ApiToken).filter(ApiToken.token == result).count() > 0:
  983. result = prefix + generate_string(n)
  984. return result
  985. class UploadFile(db.Model):
  986. __tablename__ = 'upload_files'
  987. __table_args__ = (
  988. db.PrimaryKeyConstraint('id', name='upload_file_pkey'),
  989. db.Index('upload_file_tenant_idx', 'tenant_id')
  990. )
  991. id = db.Column(StringUUID, server_default=db.text('uuid_generate_v4()'))
  992. tenant_id = db.Column(StringUUID, nullable=False)
  993. storage_type = db.Column(db.String(255), nullable=False)
  994. key = db.Column(db.String(255), nullable=False)
  995. name = db.Column(db.String(255), nullable=False)
  996. size = db.Column(db.Integer, nullable=False)
  997. extension = db.Column(db.String(255), nullable=False)
  998. mime_type = db.Column(db.String(255), nullable=True)
  999. created_by_role = db.Column(db.String(255), nullable=False, server_default=db.text("'account'::character varying"))
  1000. created_by = db.Column(StringUUID, nullable=False)
  1001. created_at = db.Column(db.DateTime, nullable=False, server_default=db.text('CURRENT_TIMESTAMP(0)'))
  1002. used = db.Column(db.Boolean, nullable=False, server_default=db.text('false'))
  1003. used_by = db.Column(StringUUID, nullable=True)
  1004. used_at = db.Column(db.DateTime, nullable=True)
  1005. hash = db.Column(db.String(255), nullable=True)
  1006. class ApiRequest(db.Model):
  1007. __tablename__ = 'api_requests'
  1008. __table_args__ = (
  1009. db.PrimaryKeyConstraint('id', name='api_request_pkey'),
  1010. db.Index('api_request_token_idx', 'tenant_id', 'api_token_id')
  1011. )
  1012. id = db.Column(StringUUID, nullable=False, server_default=db.text('uuid_generate_v4()'))
  1013. tenant_id = db.Column(StringUUID, nullable=False)
  1014. api_token_id = db.Column(StringUUID, nullable=False)
  1015. path = db.Column(db.String(255), nullable=False)
  1016. request = db.Column(db.Text, nullable=True)
  1017. response = db.Column(db.Text, nullable=True)
  1018. ip = db.Column(db.String(255), nullable=False)
  1019. created_at = db.Column(db.DateTime, nullable=False, server_default=db.text('CURRENT_TIMESTAMP(0)'))
  1020. class MessageChain(db.Model):
  1021. __tablename__ = 'message_chains'
  1022. __table_args__ = (
  1023. db.PrimaryKeyConstraint('id', name='message_chain_pkey'),
  1024. db.Index('message_chain_message_id_idx', 'message_id')
  1025. )
  1026. id = db.Column(StringUUID, nullable=False, server_default=db.text('uuid_generate_v4()'))
  1027. message_id = db.Column(StringUUID, nullable=False)
  1028. type = db.Column(db.String(255), nullable=False)
  1029. input = db.Column(db.Text, nullable=True)
  1030. output = db.Column(db.Text, nullable=True)
  1031. created_at = db.Column(db.DateTime, nullable=False, server_default=db.func.current_timestamp())
  1032. class MessageAgentThought(db.Model):
  1033. __tablename__ = 'message_agent_thoughts'
  1034. __table_args__ = (
  1035. db.PrimaryKeyConstraint('id', name='message_agent_thought_pkey'),
  1036. db.Index('message_agent_thought_message_id_idx', 'message_id'),
  1037. db.Index('message_agent_thought_message_chain_id_idx', 'message_chain_id'),
  1038. )
  1039. id = db.Column(StringUUID, nullable=False, server_default=db.text('uuid_generate_v4()'))
  1040. message_id = db.Column(StringUUID, nullable=False)
  1041. message_chain_id = db.Column(StringUUID, nullable=True)
  1042. position = db.Column(db.Integer, nullable=False)
  1043. thought = db.Column(db.Text, nullable=True)
  1044. tool = db.Column(db.Text, nullable=True)
  1045. tool_labels_str = db.Column(db.Text, nullable=False, server_default=db.text("'{}'::text"))
  1046. tool_meta_str = db.Column(db.Text, nullable=False, server_default=db.text("'{}'::text"))
  1047. tool_input = db.Column(db.Text, nullable=True)
  1048. observation = db.Column(db.Text, nullable=True)
  1049. # plugin_id = db.Column(StringUUID, nullable=True) ## for future design
  1050. tool_process_data = db.Column(db.Text, nullable=True)
  1051. message = db.Column(db.Text, nullable=True)
  1052. message_token = db.Column(db.Integer, nullable=True)
  1053. message_unit_price = db.Column(db.Numeric, nullable=True)
  1054. message_price_unit = db.Column(db.Numeric(10, 7), nullable=False, server_default=db.text('0.001'))
  1055. message_files = db.Column(db.Text, nullable=True)
  1056. answer = db.Column(db.Text, nullable=True)
  1057. answer_token = db.Column(db.Integer, nullable=True)
  1058. answer_unit_price = db.Column(db.Numeric, nullable=True)
  1059. answer_price_unit = db.Column(db.Numeric(10, 7), nullable=False, server_default=db.text('0.001'))
  1060. tokens = db.Column(db.Integer, nullable=True)
  1061. total_price = db.Column(db.Numeric, nullable=True)
  1062. currency = db.Column(db.String, nullable=True)
  1063. latency = db.Column(db.Float, nullable=True)
  1064. created_by_role = db.Column(db.String, nullable=False)
  1065. created_by = db.Column(StringUUID, nullable=False)
  1066. created_at = db.Column(db.DateTime, nullable=False, server_default=db.func.current_timestamp())
  1067. @property
  1068. def files(self) -> list:
  1069. if self.message_files:
  1070. return json.loads(self.message_files)
  1071. else:
  1072. return []
  1073. @property
  1074. def tools(self) -> list[str]:
  1075. return self.tool.split(";") if self.tool else []
  1076. @property
  1077. def tool_labels(self) -> dict:
  1078. try:
  1079. if self.tool_labels_str:
  1080. return json.loads(self.tool_labels_str)
  1081. else:
  1082. return {}
  1083. except Exception as e:
  1084. return {}
  1085. @property
  1086. def tool_meta(self) -> dict:
  1087. try:
  1088. if self.tool_meta_str:
  1089. return json.loads(self.tool_meta_str)
  1090. else:
  1091. return {}
  1092. except Exception as e:
  1093. return {}
  1094. @property
  1095. def tool_inputs_dict(self) -> dict:
  1096. tools = self.tools
  1097. try:
  1098. if self.tool_input:
  1099. data = json.loads(self.tool_input)
  1100. result = {}
  1101. for tool in tools:
  1102. if tool in data:
  1103. result[tool] = data[tool]
  1104. else:
  1105. if len(tools) == 1:
  1106. result[tool] = data
  1107. else:
  1108. result[tool] = {}
  1109. return result
  1110. else:
  1111. return {
  1112. tool: {} for tool in tools
  1113. }
  1114. except Exception as e:
  1115. return {}
  1116. @property
  1117. def tool_outputs_dict(self) -> dict:
  1118. tools = self.tools
  1119. try:
  1120. if self.observation:
  1121. data = json.loads(self.observation)
  1122. result = {}
  1123. for tool in tools:
  1124. if tool in data:
  1125. result[tool] = data[tool]
  1126. else:
  1127. if len(tools) == 1:
  1128. result[tool] = data
  1129. else:
  1130. result[tool] = {}
  1131. return result
  1132. else:
  1133. return {
  1134. tool: {} for tool in tools
  1135. }
  1136. except Exception as e:
  1137. if self.observation:
  1138. return dict.fromkeys(tools, self.observation)
  1139. class DatasetRetrieverResource(db.Model):
  1140. __tablename__ = 'dataset_retriever_resources'
  1141. __table_args__ = (
  1142. db.PrimaryKeyConstraint('id', name='dataset_retriever_resource_pkey'),
  1143. db.Index('dataset_retriever_resource_message_id_idx', 'message_id'),
  1144. )
  1145. id = db.Column(StringUUID, nullable=False, server_default=db.text('uuid_generate_v4()'))
  1146. message_id = db.Column(StringUUID, nullable=False)
  1147. position = db.Column(db.Integer, nullable=False)
  1148. dataset_id = db.Column(StringUUID, nullable=False)
  1149. dataset_name = db.Column(db.Text, nullable=False)
  1150. document_id = db.Column(StringUUID, nullable=False)
  1151. document_name = db.Column(db.Text, nullable=False)
  1152. data_source_type = db.Column(db.Text, nullable=False)
  1153. segment_id = db.Column(StringUUID, nullable=False)
  1154. score = db.Column(db.Float, nullable=True)
  1155. content = db.Column(db.Text, nullable=False)
  1156. hit_count = db.Column(db.Integer, nullable=True)
  1157. word_count = db.Column(db.Integer, nullable=True)
  1158. segment_position = db.Column(db.Integer, nullable=True)
  1159. index_node_hash = db.Column(db.Text, nullable=True)
  1160. retriever_from = db.Column(db.Text, nullable=False)
  1161. created_by = db.Column(StringUUID, nullable=False)
  1162. created_at = db.Column(db.DateTime, nullable=False, server_default=db.func.current_timestamp())
  1163. class Tag(db.Model):
  1164. __tablename__ = 'tags'
  1165. __table_args__ = (
  1166. db.PrimaryKeyConstraint('id', name='tag_pkey'),
  1167. db.Index('tag_type_idx', 'type'),
  1168. db.Index('tag_name_idx', 'name'),
  1169. )
  1170. TAG_TYPE_LIST = ['knowledge', 'app']
  1171. id = db.Column(StringUUID, server_default=db.text('uuid_generate_v4()'))
  1172. tenant_id = db.Column(StringUUID, nullable=True)
  1173. type = db.Column(db.String(16), nullable=False)
  1174. name = db.Column(db.String(255), nullable=False)
  1175. created_by = db.Column(StringUUID, nullable=False)
  1176. created_at = db.Column(db.DateTime, nullable=False, server_default=db.text('CURRENT_TIMESTAMP(0)'))
  1177. class TagBinding(db.Model):
  1178. __tablename__ = 'tag_bindings'
  1179. __table_args__ = (
  1180. db.PrimaryKeyConstraint('id', name='tag_binding_pkey'),
  1181. db.Index('tag_bind_target_id_idx', 'target_id'),
  1182. db.Index('tag_bind_tag_id_idx', 'tag_id'),
  1183. )
  1184. id = db.Column(StringUUID, server_default=db.text('uuid_generate_v4()'))
  1185. tenant_id = db.Column(StringUUID, nullable=True)
  1186. tag_id = db.Column(StringUUID, nullable=True)
  1187. target_id = db.Column(StringUUID, nullable=True)
  1188. created_by = db.Column(StringUUID, nullable=False)
  1189. created_at = db.Column(db.DateTime, nullable=False, server_default=db.text('CURRENT_TIMESTAMP(0)'))
  1190. class TraceAppConfig(db.Model):
  1191. __tablename__ = 'trace_app_config'
  1192. __table_args__ = (
  1193. db.PrimaryKeyConstraint('id', name='tracing_app_config_pkey'),
  1194. db.Index('trace_app_config_app_id_idx', 'app_id'),
  1195. )
  1196. id = db.Column(StringUUID, server_default=db.text('uuid_generate_v4()'))
  1197. app_id = db.Column(StringUUID, nullable=False)
  1198. tracing_provider = db.Column(db.String(255), nullable=True)
  1199. tracing_config = db.Column(db.JSON, nullable=True)
  1200. created_at = db.Column(db.DateTime, nullable=False, server_default=func.now())
  1201. updated_at = db.Column(db.DateTime, nullable=False, server_default=func.now(), onupdate=func.now())
  1202. is_active = db.Column(db.Boolean, nullable=False, server_default=db.text('true'))
  1203. @property
  1204. def tracing_config_dict(self):
  1205. return self.tracing_config if self.tracing_config else {}
  1206. @property
  1207. def tracing_config_str(self):
  1208. return json.dumps(self.tracing_config_dict)
  1209. def to_dict(self):
  1210. return {
  1211. 'id': self.id,
  1212. 'app_id': self.app_id,
  1213. 'tracing_provider': self.tracing_provider,
  1214. 'tracing_config': self.tracing_config_dict,
  1215. "is_active": self.is_active,
  1216. "created_at": self.created_at.__str__() if self.created_at else None,
  1217. 'updated_at': self.updated_at.__str__() if self.updated_at else None,
  1218. }