workflow.py 5.7 KB

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