workflow.py 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754
  1. import json
  2. from collections.abc import Mapping, Sequence
  3. from enum import Enum
  4. from typing import Any, Optional, Union
  5. from sqlalchemy import func
  6. from sqlalchemy.orm import Mapped
  7. import contexts
  8. from constants import HIDDEN_VALUE
  9. from core.app.segments import SecretVariable, Variable, factory
  10. from core.helper import encrypter
  11. from extensions.ext_database import db
  12. from libs import helper
  13. from .account import Account
  14. from .types import StringUUID
  15. class CreatedByRole(Enum):
  16. """
  17. Created By Role Enum
  18. """
  19. ACCOUNT = 'account'
  20. END_USER = 'end_user'
  21. @classmethod
  22. def value_of(cls, value: str) -> 'CreatedByRole':
  23. """
  24. Get value of given mode.
  25. :param value: mode value
  26. :return: mode
  27. """
  28. for mode in cls:
  29. if mode.value == value:
  30. return mode
  31. raise ValueError(f'invalid created by role value {value}')
  32. class WorkflowType(Enum):
  33. """
  34. Workflow Type Enum
  35. """
  36. WORKFLOW = 'workflow'
  37. CHAT = 'chat'
  38. @classmethod
  39. def value_of(cls, value: str) -> 'WorkflowType':
  40. """
  41. Get value of given mode.
  42. :param value: mode value
  43. :return: mode
  44. """
  45. for mode in cls:
  46. if mode.value == value:
  47. return mode
  48. raise ValueError(f'invalid workflow type value {value}')
  49. @classmethod
  50. def from_app_mode(cls, app_mode: Union[str, 'AppMode']) -> 'WorkflowType':
  51. """
  52. Get workflow type from app mode.
  53. :param app_mode: app mode
  54. :return: workflow type
  55. """
  56. from models.model import AppMode
  57. app_mode = app_mode if isinstance(app_mode, AppMode) else AppMode.value_of(app_mode)
  58. return cls.WORKFLOW if app_mode == AppMode.WORKFLOW else cls.CHAT
  59. class Workflow(db.Model):
  60. """
  61. Workflow, for `Workflow App` and `Chat App workflow mode`.
  62. Attributes:
  63. - id (uuid) Workflow ID, pk
  64. - tenant_id (uuid) Workspace ID
  65. - app_id (uuid) App ID
  66. - type (string) Workflow type
  67. `workflow` for `Workflow App`
  68. `chat` for `Chat App workflow mode`
  69. - version (string) Version
  70. `draft` for draft version (only one for each app), other for version number (redundant)
  71. - graph (text) Workflow canvas configuration (JSON)
  72. The entire canvas configuration JSON, including Node, Edge, and other configurations
  73. - nodes (array[object]) Node list, see Node Schema
  74. - edges (array[object]) Edge list, see Edge Schema
  75. - created_by (uuid) Creator ID
  76. - created_at (timestamp) Creation time
  77. - updated_by (uuid) `optional` Last updater ID
  78. - updated_at (timestamp) `optional` Last update time
  79. """
  80. __tablename__ = 'workflows'
  81. __table_args__ = (
  82. db.PrimaryKeyConstraint('id', name='workflow_pkey'),
  83. db.Index('workflow_version_idx', 'tenant_id', 'app_id', 'version'),
  84. )
  85. id = db.Column(StringUUID, server_default=db.text('uuid_generate_v4()'))
  86. tenant_id = db.Column(StringUUID, nullable=False)
  87. app_id = db.Column(StringUUID, nullable=False)
  88. type = db.Column(db.String(255), nullable=False)
  89. version = db.Column(db.String(255), nullable=False)
  90. graph = db.Column(db.Text)
  91. features = db.Column(db.Text)
  92. created_by = db.Column(StringUUID, nullable=False)
  93. created_at = db.Column(db.DateTime, nullable=False, server_default=db.text('CURRENT_TIMESTAMP(0)'))
  94. updated_by = db.Column(StringUUID)
  95. updated_at = db.Column(db.DateTime)
  96. _environment_variables = db.Column('environment_variables', db.Text, nullable=False, server_default='{}')
  97. _conversation_variables = db.Column('conversation_variables', db.Text, nullable=False, server_default='{}')
  98. @property
  99. def created_by_account(self):
  100. return db.session.get(Account, self.created_by)
  101. @property
  102. def updated_by_account(self):
  103. return db.session.get(Account, self.updated_by) if self.updated_by else None
  104. @property
  105. def graph_dict(self) -> Mapping[str, Any]:
  106. return json.loads(self.graph) if self.graph else {}
  107. @property
  108. def features_dict(self) -> Mapping[str, Any]:
  109. return json.loads(self.features) if self.features else {}
  110. def user_input_form(self, to_old_structure: bool = False) -> list:
  111. # get start node from graph
  112. if not self.graph:
  113. return []
  114. graph_dict = self.graph_dict
  115. if 'nodes' not in graph_dict:
  116. return []
  117. start_node = next((node for node in graph_dict['nodes'] if node['data']['type'] == 'start'), None)
  118. if not start_node:
  119. return []
  120. # get user_input_form from start node
  121. variables = start_node.get('data', {}).get('variables', [])
  122. if to_old_structure:
  123. old_structure_variables = []
  124. for variable in variables:
  125. old_structure_variables.append({
  126. variable['type']: variable
  127. })
  128. return old_structure_variables
  129. return variables
  130. @property
  131. def unique_hash(self) -> str:
  132. """
  133. Get hash of workflow.
  134. :return: hash
  135. """
  136. entity = {
  137. 'graph': self.graph_dict,
  138. 'features': self.features_dict
  139. }
  140. return helper.generate_text_hash(json.dumps(entity, sort_keys=True))
  141. @property
  142. def tool_published(self) -> bool:
  143. from models.tools import WorkflowToolProvider
  144. return db.session.query(WorkflowToolProvider).filter(
  145. WorkflowToolProvider.app_id == self.app_id
  146. ).first() is not None
  147. @property
  148. def environment_variables(self) -> Sequence[Variable]:
  149. # TODO: find some way to init `self._environment_variables` when instance created.
  150. if self._environment_variables is None:
  151. self._environment_variables = '{}'
  152. tenant_id = contexts.tenant_id.get()
  153. environment_variables_dict: dict[str, Any] = json.loads(self._environment_variables)
  154. results = [factory.build_variable_from_mapping(v) for v in environment_variables_dict.values()]
  155. # decrypt secret variables value
  156. decrypt_func = (
  157. lambda var: var.model_copy(
  158. update={'value': encrypter.decrypt_token(tenant_id=tenant_id, token=var.value)}
  159. )
  160. if isinstance(var, SecretVariable)
  161. else var
  162. )
  163. results = list(map(decrypt_func, results))
  164. return results
  165. @environment_variables.setter
  166. def environment_variables(self, value: Sequence[Variable]):
  167. tenant_id = contexts.tenant_id.get()
  168. value = list(value)
  169. if any(var for var in value if not var.id):
  170. raise ValueError('environment variable require a unique id')
  171. # Compare inputs and origin variables, if the value is HIDDEN_VALUE, use the origin variable value (only update `name`).
  172. origin_variables_dictionary = {var.id: var for var in self.environment_variables}
  173. for i, variable in enumerate(value):
  174. if variable.id in origin_variables_dictionary and variable.value == HIDDEN_VALUE:
  175. value[i] = origin_variables_dictionary[variable.id].model_copy(update={'name': variable.name})
  176. # encrypt secret variables value
  177. encrypt_func = (
  178. lambda var: var.model_copy(
  179. update={'value': encrypter.encrypt_token(tenant_id=tenant_id, token=var.value)}
  180. )
  181. if isinstance(var, SecretVariable)
  182. else var
  183. )
  184. encrypted_vars = list(map(encrypt_func, value))
  185. environment_variables_json = json.dumps(
  186. {var.name: var.model_dump() for var in encrypted_vars},
  187. ensure_ascii=False,
  188. )
  189. self._environment_variables = environment_variables_json
  190. def to_dict(self, *, include_secret: bool = False) -> Mapping[str, Any]:
  191. environment_variables = list(self.environment_variables)
  192. environment_variables = [
  193. v if not isinstance(v, SecretVariable) or include_secret else v.model_copy(update={'value': ''})
  194. for v in environment_variables
  195. ]
  196. result = {
  197. 'graph': self.graph_dict,
  198. 'features': self.features_dict,
  199. 'environment_variables': [var.model_dump(mode='json') for var in environment_variables],
  200. 'conversation_variables': [var.model_dump(mode='json') for var in self.conversation_variables],
  201. }
  202. return result
  203. @property
  204. def conversation_variables(self) -> Sequence[Variable]:
  205. # TODO: find some way to init `self._conversation_variables` when instance created.
  206. if self._conversation_variables is None:
  207. self._conversation_variables = '{}'
  208. variables_dict: dict[str, Any] = json.loads(self._conversation_variables)
  209. results = [factory.build_variable_from_mapping(v) for v in variables_dict.values()]
  210. return results
  211. @conversation_variables.setter
  212. def conversation_variables(self, value: Sequence[Variable]) -> None:
  213. self._conversation_variables = json.dumps(
  214. {var.name: var.model_dump() for var in value},
  215. ensure_ascii=False,
  216. )
  217. class WorkflowRunTriggeredFrom(Enum):
  218. """
  219. Workflow Run Triggered From Enum
  220. """
  221. DEBUGGING = 'debugging'
  222. APP_RUN = 'app-run'
  223. @classmethod
  224. def value_of(cls, value: str) -> 'WorkflowRunTriggeredFrom':
  225. """
  226. Get value of given mode.
  227. :param value: mode value
  228. :return: mode
  229. """
  230. for mode in cls:
  231. if mode.value == value:
  232. return mode
  233. raise ValueError(f'invalid workflow run triggered from value {value}')
  234. class WorkflowRunStatus(Enum):
  235. """
  236. Workflow Run Status Enum
  237. """
  238. RUNNING = 'running'
  239. SUCCEEDED = 'succeeded'
  240. FAILED = 'failed'
  241. STOPPED = 'stopped'
  242. @classmethod
  243. def value_of(cls, value: str) -> 'WorkflowRunStatus':
  244. """
  245. Get value of given mode.
  246. :param value: mode value
  247. :return: mode
  248. """
  249. for mode in cls:
  250. if mode.value == value:
  251. return mode
  252. raise ValueError(f'invalid workflow run status value {value}')
  253. class WorkflowRun(db.Model):
  254. """
  255. Workflow Run
  256. Attributes:
  257. - id (uuid) Run ID
  258. - tenant_id (uuid) Workspace ID
  259. - app_id (uuid) App ID
  260. - sequence_number (int) Auto-increment sequence number, incremented within the App, starting from 1
  261. - workflow_id (uuid) Workflow ID
  262. - type (string) Workflow type
  263. - triggered_from (string) Trigger source
  264. `debugging` for canvas debugging
  265. `app-run` for (published) app execution
  266. - version (string) Version
  267. - graph (text) Workflow canvas configuration (JSON)
  268. - inputs (text) Input parameters
  269. - status (string) Execution status, `running` / `succeeded` / `failed` / `stopped`
  270. - outputs (text) `optional` Output content
  271. - error (string) `optional` Error reason
  272. - elapsed_time (float) `optional` Time consumption (s)
  273. - total_tokens (int) `optional` Total tokens used
  274. - total_steps (int) Total steps (redundant), default 0
  275. - created_by_role (string) Creator role
  276. - `account` Console account
  277. - `end_user` End user
  278. - created_by (uuid) Runner ID
  279. - created_at (timestamp) Run time
  280. - finished_at (timestamp) End time
  281. """
  282. __tablename__ = 'workflow_runs'
  283. __table_args__ = (
  284. db.PrimaryKeyConstraint('id', name='workflow_run_pkey'),
  285. db.Index('workflow_run_triggerd_from_idx', 'tenant_id', 'app_id', 'triggered_from'),
  286. db.Index('workflow_run_tenant_app_sequence_idx', 'tenant_id', 'app_id', 'sequence_number'),
  287. )
  288. id = db.Column(StringUUID, server_default=db.text('uuid_generate_v4()'))
  289. tenant_id = db.Column(StringUUID, nullable=False)
  290. app_id = db.Column(StringUUID, nullable=False)
  291. sequence_number = db.Column(db.Integer, nullable=False)
  292. workflow_id = db.Column(StringUUID, nullable=False)
  293. type = db.Column(db.String(255), nullable=False)
  294. triggered_from = db.Column(db.String(255), nullable=False)
  295. version = db.Column(db.String(255), nullable=False)
  296. graph = db.Column(db.Text)
  297. inputs = db.Column(db.Text)
  298. status = db.Column(db.String(255), nullable=False)
  299. outputs = db.Column(db.Text)
  300. error = db.Column(db.Text)
  301. elapsed_time = db.Column(db.Float, nullable=False, server_default=db.text('0'))
  302. total_tokens = db.Column(db.Integer, nullable=False, server_default=db.text('0'))
  303. total_steps = db.Column(db.Integer, server_default=db.text('0'))
  304. created_by_role = db.Column(db.String(255), nullable=False)
  305. created_by = db.Column(StringUUID, nullable=False)
  306. created_at = db.Column(db.DateTime, nullable=False, server_default=db.text('CURRENT_TIMESTAMP(0)'))
  307. finished_at = db.Column(db.DateTime)
  308. @property
  309. def created_by_account(self):
  310. created_by_role = CreatedByRole.value_of(self.created_by_role)
  311. return db.session.get(Account, self.created_by) \
  312. if created_by_role == CreatedByRole.ACCOUNT else None
  313. @property
  314. def created_by_end_user(self):
  315. from models.model import EndUser
  316. created_by_role = CreatedByRole.value_of(self.created_by_role)
  317. return db.session.get(EndUser, self.created_by) \
  318. if created_by_role == CreatedByRole.END_USER else None
  319. @property
  320. def graph_dict(self):
  321. return json.loads(self.graph) if self.graph else None
  322. @property
  323. def inputs_dict(self):
  324. return json.loads(self.inputs) if self.inputs else None
  325. @property
  326. def outputs_dict(self):
  327. return json.loads(self.outputs) if self.outputs else None
  328. @property
  329. def message(self) -> Optional['Message']:
  330. from models.model import Message
  331. return db.session.query(Message).filter(
  332. Message.app_id == self.app_id,
  333. Message.workflow_run_id == self.id
  334. ).first()
  335. @property
  336. def workflow(self):
  337. return db.session.query(Workflow).filter(Workflow.id == self.workflow_id).first()
  338. def to_dict(self):
  339. return {
  340. 'id': self.id,
  341. 'tenant_id': self.tenant_id,
  342. 'app_id': self.app_id,
  343. 'sequence_number': self.sequence_number,
  344. 'workflow_id': self.workflow_id,
  345. 'type': self.type,
  346. 'triggered_from': self.triggered_from,
  347. 'version': self.version,
  348. 'graph': self.graph_dict,
  349. 'inputs': self.inputs_dict,
  350. 'status': self.status,
  351. 'outputs': self.outputs_dict,
  352. 'error': self.error,
  353. 'elapsed_time': self.elapsed_time,
  354. 'total_tokens': self.total_tokens,
  355. 'total_steps': self.total_steps,
  356. 'created_by_role': self.created_by_role,
  357. 'created_by': self.created_by,
  358. 'created_at': self.created_at,
  359. 'finished_at': self.finished_at,
  360. }
  361. @classmethod
  362. def from_dict(cls, data: dict) -> 'WorkflowRun':
  363. return cls(
  364. id=data.get('id'),
  365. tenant_id=data.get('tenant_id'),
  366. app_id=data.get('app_id'),
  367. sequence_number=data.get('sequence_number'),
  368. workflow_id=data.get('workflow_id'),
  369. type=data.get('type'),
  370. triggered_from=data.get('triggered_from'),
  371. version=data.get('version'),
  372. graph=json.dumps(data.get('graph')),
  373. inputs=json.dumps(data.get('inputs')),
  374. status=data.get('status'),
  375. outputs=json.dumps(data.get('outputs')),
  376. error=data.get('error'),
  377. elapsed_time=data.get('elapsed_time'),
  378. total_tokens=data.get('total_tokens'),
  379. total_steps=data.get('total_steps'),
  380. created_by_role=data.get('created_by_role'),
  381. created_by=data.get('created_by'),
  382. created_at=data.get('created_at'),
  383. finished_at=data.get('finished_at'),
  384. )
  385. class WorkflowNodeExecutionTriggeredFrom(Enum):
  386. """
  387. Workflow Node Execution Triggered From Enum
  388. """
  389. SINGLE_STEP = 'single-step'
  390. WORKFLOW_RUN = 'workflow-run'
  391. @classmethod
  392. def value_of(cls, value: str) -> 'WorkflowNodeExecutionTriggeredFrom':
  393. """
  394. Get value of given mode.
  395. :param value: mode value
  396. :return: mode
  397. """
  398. for mode in cls:
  399. if mode.value == value:
  400. return mode
  401. raise ValueError(f'invalid workflow node execution triggered from value {value}')
  402. class WorkflowNodeExecutionStatus(Enum):
  403. """
  404. Workflow Node Execution Status Enum
  405. """
  406. RUNNING = 'running'
  407. SUCCEEDED = 'succeeded'
  408. FAILED = 'failed'
  409. @classmethod
  410. def value_of(cls, value: str) -> 'WorkflowNodeExecutionStatus':
  411. """
  412. Get value of given mode.
  413. :param value: mode value
  414. :return: mode
  415. """
  416. for mode in cls:
  417. if mode.value == value:
  418. return mode
  419. raise ValueError(f'invalid workflow node execution status value {value}')
  420. class WorkflowNodeExecution(db.Model):
  421. """
  422. Workflow Node Execution
  423. - id (uuid) Execution ID
  424. - tenant_id (uuid) Workspace ID
  425. - app_id (uuid) App ID
  426. - workflow_id (uuid) Workflow ID
  427. - triggered_from (string) Trigger source
  428. `single-step` for single-step debugging
  429. `workflow-run` for workflow execution (debugging / user execution)
  430. - workflow_run_id (uuid) `optional` Workflow run ID
  431. Null for single-step debugging.
  432. - index (int) Execution sequence number, used for displaying Tracing Node order
  433. - predecessor_node_id (string) `optional` Predecessor node ID, used for displaying execution path
  434. - node_id (string) Node ID
  435. - node_type (string) Node type, such as `start`
  436. - title (string) Node title
  437. - inputs (json) All predecessor node variable content used in the node
  438. - process_data (json) Node process data
  439. - outputs (json) `optional` Node output variables
  440. - status (string) Execution status, `running` / `succeeded` / `failed`
  441. - error (string) `optional` Error reason
  442. - elapsed_time (float) `optional` Time consumption (s)
  443. - execution_metadata (text) Metadata
  444. - total_tokens (int) `optional` Total tokens used
  445. - total_price (decimal) `optional` Total cost
  446. - currency (string) `optional` Currency, such as USD / RMB
  447. - created_at (timestamp) Run time
  448. - created_by_role (string) Creator role
  449. - `account` Console account
  450. - `end_user` End user
  451. - created_by (uuid) Runner ID
  452. - finished_at (timestamp) End time
  453. """
  454. __tablename__ = 'workflow_node_executions'
  455. __table_args__ = (
  456. db.PrimaryKeyConstraint('id', name='workflow_node_execution_pkey'),
  457. db.Index('workflow_node_execution_workflow_run_idx', 'tenant_id', 'app_id', 'workflow_id',
  458. 'triggered_from', 'workflow_run_id'),
  459. db.Index('workflow_node_execution_node_run_idx', 'tenant_id', 'app_id', 'workflow_id',
  460. 'triggered_from', 'node_id'),
  461. )
  462. id = db.Column(StringUUID, server_default=db.text('uuid_generate_v4()'))
  463. tenant_id = db.Column(StringUUID, nullable=False)
  464. app_id = db.Column(StringUUID, nullable=False)
  465. workflow_id = db.Column(StringUUID, nullable=False)
  466. triggered_from = db.Column(db.String(255), nullable=False)
  467. workflow_run_id = db.Column(StringUUID)
  468. index = db.Column(db.Integer, nullable=False)
  469. predecessor_node_id = db.Column(db.String(255))
  470. node_id = db.Column(db.String(255), nullable=False)
  471. node_type = db.Column(db.String(255), nullable=False)
  472. title = db.Column(db.String(255), nullable=False)
  473. inputs = db.Column(db.Text)
  474. process_data = db.Column(db.Text)
  475. outputs = db.Column(db.Text)
  476. status = db.Column(db.String(255), nullable=False)
  477. error = db.Column(db.Text)
  478. elapsed_time = db.Column(db.Float, nullable=False, server_default=db.text('0'))
  479. execution_metadata = db.Column(db.Text)
  480. created_at = db.Column(db.DateTime, nullable=False, server_default=db.text('CURRENT_TIMESTAMP(0)'))
  481. created_by_role = db.Column(db.String(255), nullable=False)
  482. created_by = db.Column(StringUUID, nullable=False)
  483. finished_at = db.Column(db.DateTime)
  484. @property
  485. def created_by_account(self):
  486. created_by_role = CreatedByRole.value_of(self.created_by_role)
  487. return db.session.get(Account, self.created_by) \
  488. if created_by_role == CreatedByRole.ACCOUNT else None
  489. @property
  490. def created_by_end_user(self):
  491. from models.model import EndUser
  492. created_by_role = CreatedByRole.value_of(self.created_by_role)
  493. return db.session.get(EndUser, self.created_by) \
  494. if created_by_role == CreatedByRole.END_USER else None
  495. @property
  496. def inputs_dict(self):
  497. return json.loads(self.inputs) if self.inputs else None
  498. @property
  499. def outputs_dict(self):
  500. return json.loads(self.outputs) if self.outputs else None
  501. @property
  502. def process_data_dict(self):
  503. return json.loads(self.process_data) if self.process_data else None
  504. @property
  505. def execution_metadata_dict(self):
  506. return json.loads(self.execution_metadata) if self.execution_metadata else None
  507. @property
  508. def extras(self):
  509. from core.tools.tool_manager import ToolManager
  510. extras = {}
  511. if self.execution_metadata_dict:
  512. from core.workflow.entities.node_entities import NodeType
  513. if self.node_type == NodeType.TOOL.value and 'tool_info' in self.execution_metadata_dict:
  514. tool_info = self.execution_metadata_dict['tool_info']
  515. extras['icon'] = ToolManager.get_tool_icon(
  516. tenant_id=self.tenant_id,
  517. provider_type=tool_info['provider_type'],
  518. provider_id=tool_info['provider_id']
  519. )
  520. return extras
  521. class WorkflowAppLogCreatedFrom(Enum):
  522. """
  523. Workflow App Log Created From Enum
  524. """
  525. SERVICE_API = 'service-api'
  526. WEB_APP = 'web-app'
  527. INSTALLED_APP = 'installed-app'
  528. @classmethod
  529. def value_of(cls, value: str) -> 'WorkflowAppLogCreatedFrom':
  530. """
  531. Get value of given mode.
  532. :param value: mode value
  533. :return: mode
  534. """
  535. for mode in cls:
  536. if mode.value == value:
  537. return mode
  538. raise ValueError(f'invalid workflow app log created from value {value}')
  539. class WorkflowAppLog(db.Model):
  540. """
  541. Workflow App execution log, excluding workflow debugging records.
  542. Attributes:
  543. - id (uuid) run ID
  544. - tenant_id (uuid) Workspace ID
  545. - app_id (uuid) App ID
  546. - workflow_id (uuid) Associated Workflow ID
  547. - workflow_run_id (uuid) Associated Workflow Run ID
  548. - created_from (string) Creation source
  549. `service-api` App Execution OpenAPI
  550. `web-app` WebApp
  551. `installed-app` Installed App
  552. - created_by_role (string) Creator role
  553. - `account` Console account
  554. - `end_user` End user
  555. - created_by (uuid) Creator ID, depends on the user table according to created_by_role
  556. - created_at (timestamp) Creation time
  557. """
  558. __tablename__ = 'workflow_app_logs'
  559. __table_args__ = (
  560. db.PrimaryKeyConstraint('id', name='workflow_app_log_pkey'),
  561. db.Index('workflow_app_log_app_idx', 'tenant_id', 'app_id'),
  562. )
  563. id = db.Column(StringUUID, server_default=db.text('uuid_generate_v4()'))
  564. tenant_id = db.Column(StringUUID, nullable=False)
  565. app_id = db.Column(StringUUID, nullable=False)
  566. workflow_id = db.Column(StringUUID, nullable=False)
  567. workflow_run_id = db.Column(StringUUID, nullable=False)
  568. created_from = db.Column(db.String(255), nullable=False)
  569. created_by_role = db.Column(db.String(255), nullable=False)
  570. created_by = db.Column(StringUUID, nullable=False)
  571. created_at = db.Column(db.DateTime, nullable=False, server_default=db.text('CURRENT_TIMESTAMP(0)'))
  572. @property
  573. def workflow_run(self):
  574. return db.session.get(WorkflowRun, self.workflow_run_id)
  575. @property
  576. def created_by_account(self):
  577. created_by_role = CreatedByRole.value_of(self.created_by_role)
  578. return db.session.get(Account, self.created_by) \
  579. if created_by_role == CreatedByRole.ACCOUNT else None
  580. @property
  581. def created_by_end_user(self):
  582. from models.model import EndUser
  583. created_by_role = CreatedByRole.value_of(self.created_by_role)
  584. return db.session.get(EndUser, self.created_by) \
  585. if created_by_role == CreatedByRole.END_USER else None
  586. class ConversationVariable(db.Model):
  587. __tablename__ = 'workflow__conversation_variables'
  588. id: Mapped[str] = db.Column(StringUUID, primary_key=True)
  589. conversation_id: Mapped[str] = db.Column(StringUUID, nullable=False, primary_key=True)
  590. app_id: Mapped[str] = db.Column(StringUUID, nullable=False, index=True)
  591. data = db.Column(db.Text, nullable=False)
  592. created_at = db.Column(db.DateTime, nullable=False, index=True, server_default=db.text('CURRENT_TIMESTAMP(0)'))
  593. updated_at = db.Column(db.DateTime, nullable=False, server_default=func.current_timestamp(), onupdate=func.current_timestamp())
  594. def __init__(self, *, id: str, app_id: str, conversation_id: str, data: str) -> None:
  595. self.id = id
  596. self.app_id = app_id
  597. self.conversation_id = conversation_id
  598. self.data = data
  599. @classmethod
  600. def from_variable(cls, *, app_id: str, conversation_id: str, variable: Variable) -> 'ConversationVariable':
  601. obj = cls(
  602. id=variable.id,
  603. app_id=app_id,
  604. conversation_id=conversation_id,
  605. data=variable.model_dump_json(),
  606. )
  607. return obj
  608. def to_variable(self) -> Variable:
  609. mapping = json.loads(self.data)
  610. return factory.build_variable_from_mapping(mapping)