workflow.py 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. from datetime import datetime
  2. from typing import TYPE_CHECKING
  3. from pydantic import BaseModel, Field
  4. from core_domain import WorkflowVersionContract
  5. from core_shared import JSONValue
  6. from app.application.designer import (
  7. DiagnosticSeverity,
  8. WorkflowDebugPlan,
  9. WorkflowDiagnostic,
  10. WorkflowEdgeInspection,
  11. WorkflowInspection,
  12. WorkflowNodeInspection,
  13. )
  14. if TYPE_CHECKING:
  15. from app.db.models import WorkflowDefinitionModel, WorkflowVersion
  16. class WorkflowCreateRequest(BaseModel):
  17. tenant_id: str
  18. app_id: str
  19. code: str
  20. name: str
  21. workflow_type: str = "main"
  22. class WorkflowDefinitionResponse(BaseModel):
  23. id: str
  24. tenant_id: str
  25. app_id: str
  26. code: str
  27. name: str
  28. workflow_type: str
  29. latest_version_no: int
  30. created_time: datetime
  31. @classmethod
  32. def from_entity(cls, entity: "WorkflowDefinitionModel") -> "WorkflowDefinitionResponse":
  33. return cls.model_validate(entity, from_attributes=True)
  34. class WorkflowVersionCreateRequest(BaseModel):
  35. tenant_id: str
  36. workflow_id: str
  37. dsl_json: dict[str, JSONValue] | None = None
  38. compiled_plan_json: dict[str, JSONValue] | None = None
  39. schema_version: str | None = None
  40. checksum: str | None = None
  41. status: str = "draft"
  42. class WorkflowVersionResponse(WorkflowVersionContract):
  43. @classmethod
  44. def from_entity(cls, entity: "WorkflowVersion") -> "WorkflowVersionResponse":
  45. return cls.model_validate(entity, from_attributes=True)
  46. class WorkflowDesignerValidateRequest(BaseModel):
  47. dsl_json: dict[str, JSONValue]
  48. class WorkflowDesignerDiagnosticResponse(BaseModel):
  49. severity: DiagnosticSeverity
  50. code: str
  51. message: str
  52. node_id: str | None = None
  53. edge_index: int | None = None
  54. @classmethod
  55. def from_inspection(cls, item: WorkflowDiagnostic) -> "WorkflowDesignerDiagnosticResponse":
  56. return cls(
  57. severity=item.severity,
  58. code=item.code,
  59. message=item.message,
  60. node_id=item.node_id,
  61. edge_index=item.edge_index,
  62. )
  63. class WorkflowDesignerNodeResponse(BaseModel):
  64. id: str
  65. type: str
  66. name: str | None = None
  67. incoming_count: int
  68. outgoing_count: int
  69. reachable: bool
  70. @classmethod
  71. def from_inspection(cls, item: WorkflowNodeInspection) -> "WorkflowDesignerNodeResponse":
  72. return cls(
  73. id=item.id,
  74. type=item.type,
  75. name=item.name,
  76. incoming_count=item.incoming_count,
  77. outgoing_count=item.outgoing_count,
  78. reachable=item.reachable,
  79. )
  80. class WorkflowDesignerEdgeResponse(BaseModel):
  81. source: str
  82. target: str
  83. condition: str | None = None
  84. valid_source: bool
  85. valid_target: bool
  86. @classmethod
  87. def from_inspection(cls, item: WorkflowEdgeInspection) -> "WorkflowDesignerEdgeResponse":
  88. return cls(
  89. source=item.source,
  90. target=item.target,
  91. condition=item.condition,
  92. valid_source=item.valid_source,
  93. valid_target=item.valid_target,
  94. )
  95. class WorkflowDesignerValidateResponse(BaseModel):
  96. valid: bool
  97. diagnostics: list[WorkflowDesignerDiagnosticResponse]
  98. node_count: int
  99. edge_count: int
  100. nodes: list[WorkflowDesignerNodeResponse]
  101. edges: list[WorkflowDesignerEdgeResponse]
  102. entry_node_ids: list[str]
  103. terminal_node_ids: list[str]
  104. isolated_node_ids: list[str]
  105. unreachable_node_ids: list[str]
  106. cycle_detected: bool
  107. normalized_dsl_json: dict[str, JSONValue] | None = None
  108. @classmethod
  109. def from_inspection(cls, inspection: WorkflowInspection) -> "WorkflowDesignerValidateResponse":
  110. normalized_dsl_json: dict[str, JSONValue] | None = None
  111. if inspection.workflow is not None:
  112. normalized_dsl_json = inspection.workflow.model_dump(mode="json")
  113. return cls(
  114. valid=inspection.valid,
  115. diagnostics=[
  116. WorkflowDesignerDiagnosticResponse.from_inspection(item)
  117. for item in inspection.diagnostics
  118. ],
  119. node_count=len(inspection.nodes),
  120. edge_count=len(inspection.edges),
  121. nodes=[WorkflowDesignerNodeResponse.from_inspection(item) for item in inspection.nodes],
  122. edges=[WorkflowDesignerEdgeResponse.from_inspection(item) for item in inspection.edges],
  123. entry_node_ids=inspection.entry_node_ids,
  124. terminal_node_ids=inspection.terminal_node_ids,
  125. isolated_node_ids=inspection.isolated_node_ids,
  126. unreachable_node_ids=inspection.unreachable_node_ids,
  127. cycle_detected=inspection.cycle_detected,
  128. normalized_dsl_json=normalized_dsl_json,
  129. )
  130. class WorkflowDebuggerPlanRequest(BaseModel):
  131. dsl_json: dict[str, JSONValue]
  132. max_preview_steps: int = Field(default=50, ge=1, le=500)
  133. class WorkflowDebuggerStepResponse(BaseModel):
  134. step_index: int
  135. node_id: str
  136. node_type: str
  137. name: str | None = None
  138. next_node_ids: list[str]
  139. class WorkflowDebuggerPlanResponse(WorkflowDesignerValidateResponse):
  140. execution_preview: list[WorkflowDebuggerStepResponse]
  141. max_preview_steps: int
  142. truncated: bool
  143. @classmethod
  144. def from_plan(cls, plan: WorkflowDebugPlan) -> "WorkflowDebuggerPlanResponse":
  145. base = WorkflowDesignerValidateResponse.from_inspection(plan.inspection)
  146. return cls(
  147. **base.model_dump(),
  148. execution_preview=[
  149. WorkflowDebuggerStepResponse(
  150. step_index=item.step_index,
  151. node_id=item.node_id,
  152. node_type=item.node_type,
  153. name=item.name,
  154. next_node_ids=item.next_node_ids,
  155. )
  156. for item in plan.execution_preview
  157. ],
  158. max_preview_steps=plan.max_preview_steps,
  159. truncated=plan.truncated,
  160. )