workflow.py 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  1. from datetime import datetime
  2. from typing import TYPE_CHECKING
  3. from core_domain import WorkflowConfigContract
  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, WorkflowConfig
  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. created_time: datetime
  28. @classmethod
  29. def from_entity(cls, entity: "WorkflowDefinitionModel") -> "WorkflowDefinitionResponse":
  30. return cls.model_validate(entity, from_attributes=True)
  31. class WorkflowListRequest(BaseModel):
  32. app_id: str | None = None
  33. class WorkflowConfigCreateRequest(BaseModel):
  34. workflow_id: str
  35. dsl_json: dict[str, JSONValue] | None = None
  36. compiled_plan_json: dict[str, JSONValue] | None = None
  37. checksum: str | None = None
  38. class WorkflowConfigResponse(WorkflowConfigContract):
  39. @classmethod
  40. def from_entity(cls, entity: "WorkflowConfig") -> "WorkflowConfigResponse":
  41. return cls.model_validate(entity, from_attributes=True)
  42. class WorkflowConfigListRequest(BaseModel):
  43. workflow_id: str
  44. class WorkflowConfigDetailRequest(BaseModel):
  45. workflow_config_id: str
  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. class WorkflowDesignerNodeResponse(BaseModel):
  63. id: str
  64. type: str
  65. name: str | None = None
  66. incoming_count: int
  67. outgoing_count: int
  68. reachable: bool
  69. @classmethod
  70. def from_inspection(cls, item: WorkflowNodeInspection) -> "WorkflowDesignerNodeResponse":
  71. return cls(
  72. id=item.id,
  73. type=item.type,
  74. name=item.name,
  75. incoming_count=item.incoming_count,
  76. outgoing_count=item.outgoing_count,
  77. reachable=item.reachable)
  78. class WorkflowDesignerEdgeResponse(BaseModel):
  79. source: str
  80. target: str
  81. condition: str | None = None
  82. valid_source: bool
  83. valid_target: bool
  84. @classmethod
  85. def from_inspection(cls, item: WorkflowEdgeInspection) -> "WorkflowDesignerEdgeResponse":
  86. return cls(
  87. source=item.source,
  88. target=item.target,
  89. condition=item.condition,
  90. valid_source=item.valid_source,
  91. valid_target=item.valid_target)
  92. class WorkflowDesignerValidateResponse(BaseModel):
  93. valid: bool
  94. diagnostics: list[WorkflowDesignerDiagnosticResponse]
  95. node_count: int
  96. edge_count: int
  97. nodes: list[WorkflowDesignerNodeResponse]
  98. edges: list[WorkflowDesignerEdgeResponse]
  99. entry_node_ids: list[str]
  100. terminal_node_ids: list[str]
  101. isolated_node_ids: list[str]
  102. unreachable_node_ids: list[str]
  103. cycle_detected: bool
  104. normalized_dsl_json: dict[str, JSONValue] | None = None
  105. @classmethod
  106. def from_inspection(cls, inspection: WorkflowInspection) -> "WorkflowDesignerValidateResponse":
  107. normalized_dsl_json: dict[str, JSONValue] | None = None
  108. if inspection.workflow is not None:
  109. normalized_dsl_json = inspection.workflow.model_dump(mode="json")
  110. return cls(
  111. valid=inspection.valid,
  112. diagnostics=[
  113. WorkflowDesignerDiagnosticResponse.from_inspection(item)
  114. for item in inspection.diagnostics
  115. ],
  116. node_count=len(inspection.nodes),
  117. edge_count=len(inspection.edges),
  118. nodes=[WorkflowDesignerNodeResponse.from_inspection(item) for item in inspection.nodes],
  119. edges=[WorkflowDesignerEdgeResponse.from_inspection(item) for item in inspection.edges],
  120. entry_node_ids=inspection.entry_node_ids,
  121. terminal_node_ids=inspection.terminal_node_ids,
  122. isolated_node_ids=inspection.isolated_node_ids,
  123. unreachable_node_ids=inspection.unreachable_node_ids,
  124. cycle_detected=inspection.cycle_detected,
  125. normalized_dsl_json=normalized_dsl_json)
  126. class WorkflowDebuggerPlanRequest(BaseModel):
  127. dsl_json: dict[str, JSONValue]
  128. max_preview_steps: int = Field(default=50, ge=1, le=500)
  129. class WorkflowConfigDebuggerPlanRequest(BaseModel):
  130. workflow_config_id: str
  131. max_preview_steps: int = Field(default=50, ge=1, le=500)
  132. class WorkflowDebuggerStepResponse(BaseModel):
  133. step_index: int
  134. node_id: str
  135. node_type: str
  136. name: str | None = None
  137. next_node_ids: list[str]
  138. class WorkflowDebuggerPlanResponse(WorkflowDesignerValidateResponse):
  139. execution_preview: list[WorkflowDebuggerStepResponse]
  140. max_preview_steps: int
  141. truncated: bool
  142. @classmethod
  143. def from_plan(cls, plan: WorkflowDebugPlan) -> "WorkflowDebuggerPlanResponse":
  144. base = WorkflowDesignerValidateResponse.from_inspection(plan.inspection)
  145. return cls(
  146. **base.model_dump(),
  147. execution_preview=[
  148. WorkflowDebuggerStepResponse(
  149. step_index=item.step_index,
  150. node_id=item.node_id,
  151. node_type=item.node_type,
  152. name=item.name,
  153. next_node_ids=item.next_node_ids)
  154. for item in plan.execution_preview
  155. ],
  156. max_preview_steps=plan.max_preview_steps,
  157. truncated=plan.truncated)