mirror of
https://github.com/langgenius/dify.git
synced 2026-09-24 23:22:26 +08:00
fix: preserve and display workflow retry details (#38854)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
co-authored by
autofix-ci[bot]
parent
7dd33929c7
commit
7dc3126ff4
@@ -15,6 +15,7 @@ from datetime import datetime
|
||||
from typing import Any, Union, override
|
||||
|
||||
from core.app.entities.app_invoke_entities import AdvancedChatAppGenerateEntity, WorkflowAppGenerateEntity
|
||||
from core.app.workflow.retry_history import RETRY_HISTORY_PROCESS_DATA_KEY, WorkflowNodeRetryAttempt
|
||||
from core.helper.trace_id_helper import ParentTraceContext
|
||||
from core.ops.entities.trace_entity import TraceTaskName
|
||||
from core.ops.ops_trace_manager import TraceQueueManager, TraceTask
|
||||
@@ -257,6 +258,7 @@ class WorkflowPersistenceLayer(GraphEngineLayer):
|
||||
domain_execution = self._get_node_execution(event.id)
|
||||
domain_execution.status = WorkflowNodeExecutionStatus.RETRY
|
||||
domain_execution.error = event.error
|
||||
self._append_retry_history(domain_execution, event)
|
||||
self._workflow_node_execution_repository.save(domain_execution)
|
||||
self._workflow_node_execution_repository.save_execution_data(domain_execution)
|
||||
_inspector_publish_node_changed(
|
||||
@@ -356,6 +358,46 @@ class WorkflowPersistenceLayer(GraphEngineLayer):
|
||||
self._node_sequence += 1
|
||||
return self._node_sequence
|
||||
|
||||
def _append_retry_history(self, execution: WorkflowNodeExecution, event: NodeRunRetryEvent) -> None:
|
||||
"""Append a validated full attempt before repository truncation or offload."""
|
||||
finished_at = naive_utc_now()
|
||||
process_data = dict(execution.process_data or {})
|
||||
raw_history = process_data.get(RETRY_HISTORY_PROCESS_DATA_KEY)
|
||||
history = list(raw_history) if isinstance(raw_history, list) else []
|
||||
projected_outputs = project_node_outputs_for_workflow_run(
|
||||
node_type=execution.node_type,
|
||||
inputs=event.node_run_result.inputs,
|
||||
outputs=event.node_run_result.outputs,
|
||||
)
|
||||
attempt = WorkflowNodeRetryAttempt(
|
||||
retry_index=event.retry_index,
|
||||
inputs=event.node_run_result.inputs,
|
||||
process_data=event.node_run_result.process_data,
|
||||
outputs=projected_outputs,
|
||||
error=event.node_run_result.error or event.error,
|
||||
elapsed_time=max((finished_at - event.start_at).total_seconds(), 0.0),
|
||||
execution_metadata={key.value: value for key, value in event.node_run_result.metadata.items()},
|
||||
created_at=int(event.start_at.timestamp()),
|
||||
finished_at=int(finished_at.timestamp()),
|
||||
)
|
||||
history.append(attempt.model_dump(mode="json"))
|
||||
process_data[RETRY_HISTORY_PROCESS_DATA_KEY] = history
|
||||
execution.process_data = process_data
|
||||
|
||||
@staticmethod
|
||||
def _merge_retry_history(
|
||||
existing_process_data: Mapping[str, Any] | None,
|
||||
next_process_data: Mapping[str, Any] | None,
|
||||
) -> Mapping[str, Any] | None:
|
||||
"""Keep internal retry history while replacing node-specific Process Data."""
|
||||
raw_history = (existing_process_data or {}).get(RETRY_HISTORY_PROCESS_DATA_KEY)
|
||||
if not isinstance(raw_history, list) or not raw_history:
|
||||
return next_process_data
|
||||
|
||||
merged_process_data = dict(next_process_data or {})
|
||||
merged_process_data[RETRY_HISTORY_PROCESS_DATA_KEY] = raw_history
|
||||
return merged_process_data
|
||||
|
||||
def _populate_completion_statistics(self, execution: WorkflowExecution, *, update_finished: bool = True) -> None:
|
||||
if update_finished:
|
||||
execution.finished_at = naive_utc_now()
|
||||
@@ -391,9 +433,10 @@ class WorkflowPersistenceLayer(GraphEngineLayer):
|
||||
inputs=node_result.inputs,
|
||||
outputs=node_result.outputs,
|
||||
)
|
||||
process_data = self._merge_retry_history(domain_execution.process_data, node_result.process_data)
|
||||
domain_execution.update_from_mapping(
|
||||
inputs=node_result.inputs,
|
||||
process_data=node_result.process_data,
|
||||
process_data=process_data,
|
||||
outputs=projected_outputs,
|
||||
metadata=node_result.metadata,
|
||||
)
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
"""Validated internal payloads for persisted workflow node retry history."""
|
||||
|
||||
from collections.abc import Mapping
|
||||
from typing import Any
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
|
||||
RETRY_HISTORY_PROCESS_DATA_KEY = "__dify_retry_history"
|
||||
|
||||
|
||||
class WorkflowNodeRetryAttempt(BaseModel):
|
||||
"""Complete data captured for one failed node attempt before a retry."""
|
||||
|
||||
retry_index: int = Field(gt=0)
|
||||
inputs: Mapping[str, Any]
|
||||
process_data: Mapping[str, Any]
|
||||
outputs: Mapping[str, Any]
|
||||
error: str
|
||||
elapsed_time: float = Field(ge=0)
|
||||
execution_metadata: Mapping[str, Any]
|
||||
created_at: int
|
||||
finished_at: int
|
||||
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
@@ -200,7 +200,7 @@ def make_request(method: str, url: str, max_retries: int = SSRF_DEFAULT_MAX_RETR
|
||||
f"The URL may point to a private or local network address. "
|
||||
)
|
||||
|
||||
if response.status_code not in STATUS_FORCELIST:
|
||||
if response.status_code not in STATUS_FORCELIST or max_retries == 0:
|
||||
return response
|
||||
else:
|
||||
logger.warning(
|
||||
|
||||
Reference in New Issue
Block a user