model.py 58 KB

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