浏览代码

fix(workflow_run): sequence_number race. (#21228)

Signed-off-by: -LAN- <laipz8200@outlook.com>
tags/1.5.0
-LAN- 4 个月前
父节点
当前提交
6b1ad634f1
没有帐户链接到提交者的电子邮件

+ 2
- 15
api/core/repositories/sqlalchemy_workflow_execution_repository.py 查看文件

import logging import logging
from typing import Optional, Union from typing import Optional, Union


from sqlalchemy import func, select
from sqlalchemy import select
from sqlalchemy.engine import Engine from sqlalchemy.engine import Engine
from sqlalchemy.orm import sessionmaker from sqlalchemy.orm import sessionmaker


db_model.workflow_id = domain_model.workflow_id db_model.workflow_id = domain_model.workflow_id
db_model.triggered_from = self._triggered_from db_model.triggered_from = self._triggered_from


# Check if this is a new record
with self._session_factory() as session:
existing = session.scalar(select(WorkflowRun).where(WorkflowRun.id == domain_model.id_))
if not existing:
# For new records, get the next sequence number
stmt = select(func.max(WorkflowRun.sequence_number)).where(
WorkflowRun.app_id == self._app_id,
WorkflowRun.tenant_id == self._tenant_id,
)
max_sequence = session.scalar(stmt)
db_model.sequence_number = (max_sequence or 0) + 1
else:
# For updates, keep the existing sequence number
db_model.sequence_number = existing.sequence_number
# No sequence number generation needed anymore


db_model.type = domain_model.workflow_type db_model.type = domain_model.workflow_type
db_model.version = domain_model.workflow_version db_model.version = domain_model.workflow_version

+ 0
- 3
api/fields/workflow_run_fields.py 查看文件



workflow_run_for_list_fields = { workflow_run_for_list_fields = {
"id": fields.String, "id": fields.String,
"sequence_number": fields.Integer,
"version": fields.String, "version": fields.String,
"status": fields.String, "status": fields.String,
"elapsed_time": fields.Float, "elapsed_time": fields.Float,
"id": fields.String, "id": fields.String,
"conversation_id": fields.String, "conversation_id": fields.String,
"message_id": fields.String, "message_id": fields.String,
"sequence_number": fields.Integer,
"version": fields.String, "version": fields.String,
"status": fields.String, "status": fields.String,
"elapsed_time": fields.Float, "elapsed_time": fields.Float,


workflow_run_detail_fields = { workflow_run_detail_fields = {
"id": fields.String, "id": fields.String,
"sequence_number": fields.Integer,
"version": fields.String, "version": fields.String,
"graph": fields.Raw(attribute="graph_dict"), "graph": fields.Raw(attribute="graph_dict"),
"inputs": fields.Raw(attribute="inputs_dict"), "inputs": fields.Raw(attribute="inputs_dict"),

+ 66
- 0
api/migrations/versions/2025_06_19_1633-0ab65e1cc7fa_remove_sequence_number_from_workflow_.py 查看文件

"""remove sequence_number from workflow_runs

Revision ID: 0ab65e1cc7fa
Revises: 4474872b0ee6
Create Date: 2025-06-19 16:33:13.377215

"""
from alembic import op
import models as models
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision = '0ab65e1cc7fa'
down_revision = '4474872b0ee6'
branch_labels = None
depends_on = None


def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('workflow_runs', schema=None) as batch_op:
batch_op.drop_index(batch_op.f('workflow_run_tenant_app_sequence_idx'))
batch_op.drop_column('sequence_number')

# ### end Alembic commands ###


def downgrade():
# ### commands auto generated by Alembic - please adjust! ###

# WARNING: This downgrade CANNOT recover the original sequence_number values!
# The original sequence numbers are permanently lost after the upgrade.
# This downgrade will regenerate sequence numbers based on created_at order,
# which may result in different values than the original sequence numbers.
#
# If you need to preserve original sequence numbers, use the alternative
# migration approach that creates a backup table before removal.

# Step 1: Add sequence_number column as nullable first
with op.batch_alter_table('workflow_runs', schema=None) as batch_op:
batch_op.add_column(sa.Column('sequence_number', sa.INTEGER(), autoincrement=False, nullable=True))

# Step 2: Populate sequence_number values based on created_at order within each app
# NOTE: This recreates sequence numbering logic but values will be different
# from the original sequence numbers that were removed in the upgrade
connection = op.get_bind()
connection.execute(sa.text("""
UPDATE workflow_runs
SET sequence_number = subquery.row_num
FROM (
SELECT id, ROW_NUMBER() OVER (
PARTITION BY tenant_id, app_id
ORDER BY created_at, id
) as row_num
FROM workflow_runs
) subquery
WHERE workflow_runs.id = subquery.id
"""))

# Step 3: Make the column NOT NULL and add the index
with op.batch_alter_table('workflow_runs', schema=None) as batch_op:
batch_op.alter_column('sequence_number', nullable=False)
batch_op.create_index(batch_op.f('workflow_run_tenant_app_sequence_idx'), ['tenant_id', 'app_id', 'sequence_number'], unique=False)

# ### end Alembic commands ###

+ 2
- 5
api/models/workflow.py 查看文件

- id (uuid) Run ID - id (uuid) Run ID
- tenant_id (uuid) Workspace ID - tenant_id (uuid) Workspace ID
- app_id (uuid) App ID - app_id (uuid) App ID
- sequence_number (int) Auto-increment sequence number, incremented within the App, starting from 1
- workflow_id (uuid) Workflow ID - workflow_id (uuid) Workflow ID
- type (string) Workflow type - type (string) Workflow type
- triggered_from (string) Trigger source - triggered_from (string) Trigger source
__table_args__ = ( __table_args__ = (
db.PrimaryKeyConstraint("id", name="workflow_run_pkey"), db.PrimaryKeyConstraint("id", name="workflow_run_pkey"),
db.Index("workflow_run_triggerd_from_idx", "tenant_id", "app_id", "triggered_from"), db.Index("workflow_run_triggerd_from_idx", "tenant_id", "app_id", "triggered_from"),
db.Index("workflow_run_tenant_app_sequence_idx", "tenant_id", "app_id", "sequence_number"),
) )


id: Mapped[str] = mapped_column(StringUUID, server_default=db.text("uuid_generate_v4()")) id: Mapped[str] = mapped_column(StringUUID, server_default=db.text("uuid_generate_v4()"))
tenant_id: Mapped[str] = mapped_column(StringUUID) tenant_id: Mapped[str] = mapped_column(StringUUID)
app_id: Mapped[str] = mapped_column(StringUUID) app_id: Mapped[str] = mapped_column(StringUUID)
sequence_number: Mapped[int] = mapped_column()
workflow_id: Mapped[str] = mapped_column(StringUUID) workflow_id: Mapped[str] = mapped_column(StringUUID)
type: Mapped[str] = mapped_column(db.String(255)) type: Mapped[str] = mapped_column(db.String(255))
triggered_from: Mapped[str] = mapped_column(db.String(255)) triggered_from: Mapped[str] = mapped_column(db.String(255))
"id": self.id, "id": self.id,
"tenant_id": self.tenant_id, "tenant_id": self.tenant_id,
"app_id": self.app_id, "app_id": self.app_id,
"sequence_number": self.sequence_number,
"workflow_id": self.workflow_id, "workflow_id": self.workflow_id,
"type": self.type, "type": self.type,
"triggered_from": self.triggered_from, "triggered_from": self.triggered_from,
id=data.get("id"), id=data.get("id"),
tenant_id=data.get("tenant_id"), tenant_id=data.get("tenant_id"),
app_id=data.get("app_id"), app_id=data.get("app_id"),
sequence_number=data.get("sequence_number"),
workflow_id=data.get("workflow_id"), workflow_id=data.get("workflow_id"),
type=data.get("type"), type=data.get("type"),
triggered_from=data.get("triggered_from"), triggered_from=data.get("triggered_from"),

+ 0
- 1
api/tests/unit_tests/core/workflow/test_workflow_cycle_manager.py 查看文件

workflow_run.tenant_id = "test-tenant-id" workflow_run.tenant_id = "test-tenant-id"
workflow_run.app_id = "test-app-id" workflow_run.app_id = "test-app-id"
workflow_run.workflow_id = "test-workflow-id" workflow_run.workflow_id = "test-workflow-id"
workflow_run.sequence_number = 1
workflow_run.type = "chat" workflow_run.type = "chat"
workflow_run.triggered_from = "app-run" workflow_run.triggered_from = "app-run"
workflow_run.version = "1.0" workflow_run.version = "1.0"

+ 1
- 2
web/app/components/develop/template/template_advanced_chat.en.mdx 查看文件

- `data` (object) detail - `data` (object) detail
- `id` (string) Unique ID of workflow execution - `id` (string) Unique ID of workflow execution
- `workflow_id` (string) ID of related workflow - `workflow_id` (string) ID of related workflow
- `sequence_number` (int) Self-increasing serial number, self-increasing in the App, starting from 1
- `created_at` (timestamp) Creation timestamp, e.g., 1705395332 - `created_at` (timestamp) Creation timestamp, e.g., 1705395332
- `event: node_started` node execution started - `event: node_started` node execution started
- `task_id` (string) Task ID, used for request tracking and the below Stop Generate API - `task_id` (string) Task ID, used for request tracking and the below Stop Generate API
### Streaming Mode ### Streaming Mode
<CodeGroup title="Response"> <CodeGroup title="Response">
```streaming {{ title: 'Response' }} ```streaming {{ title: 'Response' }}
data: {"event": "workflow_started", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "workflow_id": "dfjasklfjdslag", "sequence_number": 1, "created_at": 1679586595}}
data: {"event": "workflow_started", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "workflow_id": "dfjasklfjdslag", "created_at": 1679586595}}
data: {"event": "node_started", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "node_id": "dfjasklfjdslag", "node_type": "start", "title": "Start", "index": 0, "predecessor_node_id": "fdljewklfklgejlglsd", "inputs": {}, "created_at": 1679586595}} data: {"event": "node_started", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "node_id": "dfjasklfjdslag", "node_type": "start", "title": "Start", "index": 0, "predecessor_node_id": "fdljewklfklgejlglsd", "inputs": {}, "created_at": 1679586595}}
data: {"event": "node_finished", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "node_id": "dfjasklfjdslag", "node_type": "start", "title": "Start", "index": 0, "predecessor_node_id": "fdljewklfklgejlglsd", "inputs": {}, "outputs": {}, "status": "succeeded", "elapsed_time": 0.324, "execution_metadata": {"total_tokens": 63127864, "total_price": 2.378, "currency": "USD"}, "created_at": 1679586595}} data: {"event": "node_finished", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "node_id": "dfjasklfjdslag", "node_type": "start", "title": "Start", "index": 0, "predecessor_node_id": "fdljewklfklgejlglsd", "inputs": {}, "outputs": {}, "status": "succeeded", "elapsed_time": 0.324, "execution_metadata": {"total_tokens": 63127864, "total_price": 2.378, "currency": "USD"}, "created_at": 1679586595}}
data: {"event": "workflow_finished", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "workflow_id": "dfjasklfjdslag", "outputs": {}, "status": "succeeded", "elapsed_time": 0.324, "total_tokens": 63127864, "total_steps": "1", "created_at": 1679586595, "finished_at": 1679976595}} data: {"event": "workflow_finished", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "workflow_id": "dfjasklfjdslag", "outputs": {}, "status": "succeeded", "elapsed_time": 0.324, "total_tokens": 63127864, "total_steps": "1", "created_at": 1679586595, "finished_at": 1679976595}}

+ 1
- 2
web/app/components/develop/template/template_advanced_chat.ja.mdx 查看文件

- `data` (object) 詳細 - `data` (object) 詳細
- `id` (string) ワークフロー実行の一意ID - `id` (string) ワークフロー実行の一意ID
- `workflow_id` (string) 関連ワークフローのID - `workflow_id` (string) 関連ワークフローのID
- `sequence_number` (int) 自己増加シリアル番号、アプリ内で自己増加し、1から始まります
- `created_at` (timestamp) 作成タイムスタンプ、例:1705395332 - `created_at` (timestamp) 作成タイムスタンプ、例:1705395332
- `event: node_started` ノード実行が開始 - `event: node_started` ノード実行が開始
- `task_id` (string) タスクID、リクエスト追跡と以下のStop Generate APIに使用 - `task_id` (string) タスクID、リクエスト追跡と以下のStop Generate APIに使用
### ストリーミングモード ### ストリーミングモード
<CodeGroup title="応答"> <CodeGroup title="応答">
```streaming {{ title: '応答' }} ```streaming {{ title: '応答' }}
data: {"event": "workflow_started", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "workflow_id": "dfjasklfjdslag", "sequence_number": 1, "created_at": 1679586595}}
data: {"event": "workflow_started", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "workflow_id": "dfjasklfjdslag", "created_at": 1679586595}}
data: {"event": "node_started", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "node_id": "dfjasklfjdslag", "node_type": "start", "title": "Start", "index": 0, "predecessor_node_id": "fdljewklfklgejlglsd", "inputs": {}, "created_at": 1679586595}} data: {"event": "node_started", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "node_id": "dfjasklfjdslag", "node_type": "start", "title": "Start", "index": 0, "predecessor_node_id": "fdljewklfklgejlglsd", "inputs": {}, "created_at": 1679586595}}
data: {"event": "node_finished", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "node_id": "dfjasklfjdslag", "node_type": "start", "title": "Start", "index": 0, "predecessor_node_id": "fdljewklfklgejlglsd", "inputs": {}, "outputs": {}, "status": "succeeded", "elapsed_time": 0.324, "execution_metadata": {"total_tokens": 63127864, "total_price": 2.378, "currency": "USD"}, "created_at": 1679586595}} data: {"event": "node_finished", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "node_id": "dfjasklfjdslag", "node_type": "start", "title": "Start", "index": 0, "predecessor_node_id": "fdljewklfklgejlglsd", "inputs": {}, "outputs": {}, "status": "succeeded", "elapsed_time": 0.324, "execution_metadata": {"total_tokens": 63127864, "total_price": 2.378, "currency": "USD"}, "created_at": 1679586595}}
data: {"event": "workflow_finished", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "workflow_id": "dfjasklfjdslag", "outputs": {}, "status": "succeeded", "elapsed_time": 0.324, "total_tokens": 63127864, "total_steps": "1", "created_at": 1679586595, "finished_at": 1679976595}} data: {"event": "workflow_finished", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "workflow_id": "dfjasklfjdslag", "outputs": {}, "status": "succeeded", "elapsed_time": 0.324, "total_tokens": 63127864, "total_steps": "1", "created_at": 1679586595, "finished_at": 1679976595}}

+ 1
- 2
web/app/components/develop/template/template_advanced_chat.zh.mdx 查看文件

- `data` (object) 详细内容 - `data` (object) 详细内容
- `id` (string) workflow 执行 ID - `id` (string) workflow 执行 ID
- `workflow_id` (string) 关联 Workflow ID - `workflow_id` (string) 关联 Workflow ID
- `sequence_number` (int) 自增序号,App 内自增,从 1 开始
- `created_at` (timestamp) 开始时间 - `created_at` (timestamp) 开始时间
- `event: node_started` node 开始执行 - `event: node_started` node 开始执行
- `task_id` (string) 任务 ID,用于请求跟踪和下方的停止响应接口 - `task_id` (string) 任务 ID,用于请求跟踪和下方的停止响应接口
### 流式模式 ### 流式模式
<CodeGroup title="Response"> <CodeGroup title="Response">
```streaming {{ title: 'Response' }} ```streaming {{ title: 'Response' }}
data: {"event": "workflow_started", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "workflow_id": "dfjasklfjdslag", "sequence_number": 1, "created_at": 1679586595}}
data: {"event": "workflow_started", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "workflow_id": "dfjasklfjdslag", "created_at": 1679586595}}
data: {"event": "node_started", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "node_id": "dfjasklfjdslag", "node_type": "start", "title": "Start", "index": 0, "predecessor_node_id": "fdljewklfklgejlglsd", "inputs": {}, "created_at": 1679586595}} data: {"event": "node_started", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "node_id": "dfjasklfjdslag", "node_type": "start", "title": "Start", "index": 0, "predecessor_node_id": "fdljewklfklgejlglsd", "inputs": {}, "created_at": 1679586595}}
data: {"event": "node_finished", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "node_id": "dfjasklfjdslag", "node_type": "start", "title": "Start", "index": 0, "predecessor_node_id": "fdljewklfklgejlglsd", "inputs": {}, "outputs": {}, "status": "succeeded", "elapsed_time": 0.324, "execution_metadata": {"total_tokens": 63127864, "total_price": 2.378, "currency": "USD"}, "created_at": 1679586595}} data: {"event": "node_finished", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "node_id": "dfjasklfjdslag", "node_type": "start", "title": "Start", "index": 0, "predecessor_node_id": "fdljewklfklgejlglsd", "inputs": {}, "outputs": {}, "status": "succeeded", "elapsed_time": 0.324, "execution_metadata": {"total_tokens": 63127864, "total_price": 2.378, "currency": "USD"}, "created_at": 1679586595}}
data: {"event": "workflow_finished", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "workflow_id": "dfjasklfjdslag", "outputs": {}, "status": "succeeded", "elapsed_time": 0.324, "total_tokens": 63127864, "total_steps": "1", "created_at": 1679586595, "finished_at": 1679976595}} data: {"event": "workflow_finished", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "workflow_id": "dfjasklfjdslag", "outputs": {}, "status": "succeeded", "elapsed_time": 0.324, "total_tokens": 63127864, "total_steps": "1", "created_at": 1679586595, "finished_at": 1679976595}}

+ 1
- 2
web/app/components/develop/template/template_workflow.en.mdx 查看文件

- `data` (object) detail - `data` (object) detail
- `id` (string) Unique ID of workflow execution - `id` (string) Unique ID of workflow execution
- `workflow_id` (string) ID of related workflow - `workflow_id` (string) ID of related workflow
- `sequence_number` (int) Self-increasing serial number, self-increasing in the App, starting from 1
- `created_at` (timestamp) Creation timestamp, e.g., 1705395332 - `created_at` (timestamp) Creation timestamp, e.g., 1705395332
- `event: node_started` node execution started - `event: node_started` node execution started
- `task_id` (string) Task ID, used for request tracking and the below Stop Generate API - `task_id` (string) Task ID, used for request tracking and the below Stop Generate API
### Streaming Mode ### Streaming Mode
<CodeGroup title="Response"> <CodeGroup title="Response">
```streaming {{ title: 'Response' }} ```streaming {{ title: 'Response' }}
data: {"event": "workflow_started", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "workflow_id": "dfjasklfjdslag", "sequence_number": 1, "created_at": 1679586595}}
data: {"event": "workflow_started", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "workflow_id": "dfjasklfjdslag", "created_at": 1679586595}}
data: {"event": "node_started", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "node_id": "dfjasklfjdslag", "node_type": "start", "title": "Start", "index": 0, "predecessor_node_id": "fdljewklfklgejlglsd", "inputs": {}, "created_at": 1679586595}} data: {"event": "node_started", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "node_id": "dfjasklfjdslag", "node_type": "start", "title": "Start", "index": 0, "predecessor_node_id": "fdljewklfklgejlglsd", "inputs": {}, "created_at": 1679586595}}
data: {"event": "node_finished", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "node_id": "dfjasklfjdslag", "node_type": "start", "title": "Start", "index": 0, "predecessor_node_id": "fdljewklfklgejlglsd", "inputs": {}, "outputs": {}, "status": "succeeded", "elapsed_time": 0.324, "execution_metadata": {"total_tokens": 63127864, "total_price": 2.378, "currency": "USD"}, "created_at": 1679586595}} data: {"event": "node_finished", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "node_id": "dfjasklfjdslag", "node_type": "start", "title": "Start", "index": 0, "predecessor_node_id": "fdljewklfklgejlglsd", "inputs": {}, "outputs": {}, "status": "succeeded", "elapsed_time": 0.324, "execution_metadata": {"total_tokens": 63127864, "total_price": 2.378, "currency": "USD"}, "created_at": 1679586595}}
data: {"event": "workflow_finished", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "workflow_id": "dfjasklfjdslag", "outputs": {}, "status": "succeeded", "elapsed_time": 0.324, "total_tokens": 63127864, "total_steps": "1", "created_at": 1679586595, "finished_at": 1679976595}} data: {"event": "workflow_finished", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "workflow_id": "dfjasklfjdslag", "outputs": {}, "status": "succeeded", "elapsed_time": 0.324, "total_tokens": 63127864, "total_steps": "1", "created_at": 1679586595, "finished_at": 1679976595}}

+ 1
- 2
web/app/components/develop/template/template_workflow.ja.mdx 查看文件

- `data` (object) 詳細 - `data` (object) 詳細
- `id` (string) ワークフロー実行の一意の ID - `id` (string) ワークフロー実行の一意の ID
- `workflow_id` (string) 関連するワークフローの ID - `workflow_id` (string) 関連するワークフローの ID
- `sequence_number` (int) 自己増加シリアル番号、アプリ内で自己増加し、1 から始まります
- `created_at` (timestamp) 作成タイムスタンプ、例:1705395332 - `created_at` (timestamp) 作成タイムスタンプ、例:1705395332
- `event: node_started` ノード実行開始 - `event: node_started` ノード実行開始
- `task_id` (string) タスク ID、リクエスト追跡と以下の Stop Generate API に使用 - `task_id` (string) タスク ID、リクエスト追跡と以下の Stop Generate API に使用
### ストリーミングモード ### ストリーミングモード
<CodeGroup title="応答"> <CodeGroup title="応答">
```streaming {{ title: '応答' }} ```streaming {{ title: '応答' }}
data: {"event": "workflow_started", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "workflow_id": "dfjasklfjdslag", "sequence_number": 1, "created_at": 1679586595}}
data: {"event": "workflow_started", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "workflow_id": "dfjasklfjdslag", "created_at": 1679586595}}
data: {"event": "node_started", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "node_id": "dfjasklfjdslag", "node_type": "start", "title": "Start", "index": 0, "predecessor_node_id": "fdljewklfklgejlglsd", "inputs": {}, "created_at": 1679586595}} data: {"event": "node_started", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "node_id": "dfjasklfjdslag", "node_type": "start", "title": "Start", "index": 0, "predecessor_node_id": "fdljewklfklgejlglsd", "inputs": {}, "created_at": 1679586595}}
data: {"event": "node_finished", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "node_id": "dfjasklfjdslag", "node_type": "start", "title": "Start", "index": 0, "predecessor_node_id": "fdljewklfklgejlglsd", "inputs": {}, "outputs": {}, "status": "succeeded", "elapsed_time": 0.324, "execution_metadata": {"total_tokens": 63127864, "total_price": 2.378, "currency": "USD"}, "created_at": 1679586595}} data: {"event": "node_finished", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "node_id": "dfjasklfjdslag", "node_type": "start", "title": "Start", "index": 0, "predecessor_node_id": "fdljewklfklgejlglsd", "inputs": {}, "outputs": {}, "status": "succeeded", "elapsed_time": 0.324, "execution_metadata": {"total_tokens": 63127864, "total_price": 2.378, "currency": "USD"}, "created_at": 1679586595}}
data: {"event": "workflow_finished", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "workflow_id": "dfjasklfjdslag", "outputs": {}, "status": "succeeded", "elapsed_time": 0.324, "total_tokens": 63127864, "total_steps": "1", "created_at": 1679586595, "finished_at": 1679976595}} data: {"event": "workflow_finished", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "workflow_id": "dfjasklfjdslag", "outputs": {}, "status": "succeeded", "elapsed_time": 0.324, "total_tokens": 63127864, "total_steps": "1", "created_at": 1679586595, "finished_at": 1679976595}}

+ 1
- 2
web/app/components/develop/template/template_workflow.zh.mdx 查看文件

- `data` (object) 详细内容 - `data` (object) 详细内容
- `id` (string) workflow 执行 ID - `id` (string) workflow 执行 ID
- `workflow_id` (string) 关联 Workflow ID - `workflow_id` (string) 关联 Workflow ID
- `sequence_number` (int) 自增序号,App 内自增,从 1 开始
- `created_at` (timestamp) 开始时间 - `created_at` (timestamp) 开始时间
- `event: node_started` node 开始执行 - `event: node_started` node 开始执行
- `task_id` (string) 任务 ID,用于请求跟踪和下方的停止响应接口 - `task_id` (string) 任务 ID,用于请求跟踪和下方的停止响应接口
### Streaming Mode ### Streaming Mode
<CodeGroup title="Response"> <CodeGroup title="Response">
```streaming {{ title: 'Response' }} ```streaming {{ title: 'Response' }}
data: {"event": "workflow_started", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "workflow_id": "dfjasklfjdslag", "sequence_number": 1, "created_at": 1679586595}}
data: {"event": "workflow_started", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "workflow_id": "dfjasklfjdslag", "created_at": 1679586595}}
data: {"event": "node_started", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "node_id": "dfjasklfjdslag", "node_type": "start", "title": "Start", "index": 0, "predecessor_node_id": "fdljewklfklgejlglsd", "inputs": {}, "created_at": 1679586595}} data: {"event": "node_started", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "node_id": "dfjasklfjdslag", "node_type": "start", "title": "Start", "index": 0, "predecessor_node_id": "fdljewklfklgejlglsd", "inputs": {}, "created_at": 1679586595}}
data: {"event": "node_finished", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "node_id": "dfjasklfjdslag", "node_type": "start", "title": "Start", "index": 0, "predecessor_node_id": "fdljewklfklgejlglsd", "inputs": {}, "outputs": {}, "status": "succeeded", "elapsed_time": 0.324, "execution_metadata": {"total_tokens": 63127864, "total_price": 2.378, "currency": "USD"}, "created_at": 1679586595}} data: {"event": "node_finished", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "node_id": "dfjasklfjdslag", "node_type": "start", "title": "Start", "index": 0, "predecessor_node_id": "fdljewklfklgejlglsd", "inputs": {}, "outputs": {}, "status": "succeeded", "elapsed_time": 0.324, "execution_metadata": {"total_tokens": 63127864, "total_price": 2.378, "currency": "USD"}, "created_at": 1679586595}}
data: {"event": "workflow_finished", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "workflow_id": "dfjasklfjdslag", "outputs": {}, "status": "succeeded", "elapsed_time": 0.324, "total_tokens": 63127864, "total_steps": "1", "created_at": 1679586595, "finished_at": 1679976595}} data: {"event": "workflow_finished", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "workflow_id": "dfjasklfjdslag", "outputs": {}, "status": "succeeded", "elapsed_time": 0.324, "total_tokens": 63127864, "total_steps": "1", "created_at": 1679586595, "finished_at": 1679976595}}

+ 2
- 1
web/app/components/workflow/header/running-title.tsx 查看文件

import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useIsChatMode } from '../hooks' import { useIsChatMode } from '../hooks'
import { useStore } from '../store' import { useStore } from '../store'
import { formatWorkflowRunIdentifier } from '../utils'
import { ClockPlay } from '@/app/components/base/icons/src/vender/line/time' import { ClockPlay } from '@/app/components/base/icons/src/vender/line/time'


const RunningTitle = () => { const RunningTitle = () => {
return ( return (
<div className='flex h-[18px] items-center text-xs text-gray-500'> <div className='flex h-[18px] items-center text-xs text-gray-500'>
<ClockPlay className='mr-1 h-3 w-3 text-gray-500' /> <ClockPlay className='mr-1 h-3 w-3 text-gray-500' />
<span>{isChatMode ? `Test Chat#${historyWorkflowData?.sequence_number}` : `Test Run#${historyWorkflowData?.sequence_number}`}</span>
<span>{isChatMode ? `Test Chat${formatWorkflowRunIdentifier(historyWorkflowData?.finished_at)}` : `Test Run${formatWorkflowRunIdentifier(historyWorkflowData?.finished_at)}`}</span>
<span className='mx-1'>·</span> <span className='mx-1'>·</span>
<span className='ml-1 flex h-[18px] items-center rounded-[5px] border border-indigo-300 bg-white/[0.48] px-1 text-[10px] font-semibold uppercase text-indigo-600'> <span className='ml-1 flex h-[18px] items-center rounded-[5px] border border-indigo-300 bg-white/[0.48] px-1 text-[10px] font-semibold uppercase text-indigo-600'>
{t('workflow.common.viewOnly')} {t('workflow.common.viewOnly')}

+ 2
- 1
web/app/components/workflow/header/view-history.tsx 查看文件

useWorkflowRun, useWorkflowRun,
} from '../hooks' } from '../hooks'
import { ControlMode, WorkflowRunningStatus } from '../types' import { ControlMode, WorkflowRunningStatus } from '../types'
import { formatWorkflowRunIdentifier } from '../utils'
import cn from '@/utils/classnames' import cn from '@/utils/classnames'
import { import {
PortalToFollowElem, PortalToFollowElem,
item.id === historyWorkflowData?.id && 'text-text-accent', item.id === historyWorkflowData?.id && 'text-text-accent',
)} )}
> >
{`Test ${isChatMode ? 'Chat' : 'Run'} #${item.sequence_number}`}
{`Test ${isChatMode ? 'Chat' : 'Run'}${formatWorkflowRunIdentifier(item.finished_at)}`}
</div> </div>
<div className='flex items-center text-xs leading-[18px] text-text-tertiary'> <div className='flex items-center text-xs leading-[18px] text-text-tertiary'>
{item.created_by_account?.name} · {formatTimeFromNow((item.finished_at || item.created_at) * 1000)} {item.created_by_account?.name} · {formatTimeFromNow((item.finished_at || item.created_at) * 1000)}

+ 2
- 1
web/app/components/workflow/panel/chat-record/index.tsx 查看文件

useWorkflowStore, useWorkflowStore,
} from '../../store' } from '../../store'
import { useWorkflowRun } from '../../hooks' import { useWorkflowRun } from '../../hooks'
import { formatWorkflowRunIdentifier } from '../../utils'
import UserInput from './user-input' import UserInput from './user-input'
import Chat from '@/app/components/base/chat/chat' import Chat from '@/app/components/base/chat/chat'
import type { ChatItem, ChatItemInTree } from '@/app/components/base/chat/types' import type { ChatItem, ChatItemInTree } from '@/app/components/base/chat/types'
{fetched && ( {fetched && (
<> <>
<div className='flex shrink-0 items-center justify-between p-4 pb-1 text-base font-semibold text-text-primary'> <div className='flex shrink-0 items-center justify-between p-4 pb-1 text-base font-semibold text-text-primary'>
{`TEST CHAT#${historyWorkflowData?.sequence_number}`}
{`TEST CHAT${formatWorkflowRunIdentifier(historyWorkflowData?.finished_at)}`}
<div <div
className='flex h-6 w-6 cursor-pointer items-center justify-center' className='flex h-6 w-6 cursor-pointer items-center justify-center'
onClick={() => { onClick={() => {

+ 2
- 1
web/app/components/workflow/panel/record.tsx 查看文件

import Run from '../run' import Run from '../run'
import { useStore } from '../store' import { useStore } from '../store'
import { useWorkflowUpdate } from '../hooks' import { useWorkflowUpdate } from '../hooks'
import { formatWorkflowRunIdentifier } from '../utils'


const Record = () => { const Record = () => {
const historyWorkflowData = useStore(s => s.historyWorkflowData) const historyWorkflowData = useStore(s => s.historyWorkflowData)
return ( return (
<div className='flex h-full w-[400px] flex-col rounded-l-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-xl'> <div className='flex h-full w-[400px] flex-col rounded-l-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-xl'>
<div className='system-xl-semibold flex items-center justify-between p-4 pb-0 text-text-primary'> <div className='system-xl-semibold flex items-center justify-between p-4 pb-0 text-text-primary'>
{`Test Run#${historyWorkflowData?.sequence_number}`}
{`Test Run${formatWorkflowRunIdentifier(historyWorkflowData?.finished_at)}`}
</div> </div>
<Run <Run
runID={historyWorkflowData?.id || ''} runID={historyWorkflowData?.id || ''}

+ 2
- 1
web/app/components/workflow/panel/workflow-preview.tsx 查看文件

import { import {
WorkflowRunningStatus, WorkflowRunningStatus,
} from '../types' } from '../types'
import { formatWorkflowRunIdentifier } from '../utils'
import Toast from '../../base/toast' import Toast from '../../base/toast'
import InputsPanel from './inputs-panel' import InputsPanel from './inputs-panel'
import cn from '@/utils/classnames' import cn from '@/utils/classnames'
onMouseDown={startResizing} onMouseDown={startResizing}
/> />
<div className='flex items-center justify-between p-4 pb-1 text-base font-semibold text-text-primary'> <div className='flex items-center justify-between p-4 pb-1 text-base font-semibold text-text-primary'>
{`Test Run${!workflowRunningData?.result.sequence_number ? '' : `#${workflowRunningData?.result.sequence_number}`}`}
{`Test Run${formatWorkflowRunIdentifier(workflowRunningData?.result.finished_at)}`}
<div className='cursor-pointer p-1' onClick={() => handleCancelDebugAndPreviewPanel()}> <div className='cursor-pointer p-1' onClick={() => handleCancelDebugAndPreviewPanel()}>
<RiCloseLine className='h-4 w-4 text-text-tertiary' /> <RiCloseLine className='h-4 w-4 text-text-tertiary' />
</div> </div>

+ 1
- 2
web/app/components/workflow/types.ts 查看文件

message_id?: string message_id?: string
conversation_id?: string conversation_id?: string
result: { result: {
sequence_number?: number
workflow_id?: string workflow_id?: string
inputs?: string inputs?: string
process_data?: string process_data?: string


export type HistoryWorkflowData = { export type HistoryWorkflowData = {
id: string id: string
sequence_number: number
status: string status: string
conversation_id?: string conversation_id?: string
finished_at?: number
} }


export enum ChangeType { export enum ChangeType {

+ 19
- 0
web/app/components/workflow/utils/common.ts 查看文件

if (target.contentEditable === 'true') if (target.contentEditable === 'true')
return true return true
} }

/**
* Format workflow run identifier using finished_at timestamp
* @param finishedAt - Unix timestamp in seconds
* @param fallbackText - Text to show when finishedAt is not available (default: 'Running')
* @returns Formatted string like " (14:30:25)" or " (Running)"
*/
export const formatWorkflowRunIdentifier = (finishedAt?: number, fallbackText = 'Running'): string => {
if (!finishedAt)
return ` (${fallbackText})`

const date = new Date(finishedAt * 1000)
const timeStr = date.toLocaleTimeString([], {
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
})
return ` (${timeStr})`
}

+ 0
- 1
web/models/log.ts 查看文件



export type WorkflowRunDetailResponse = { export type WorkflowRunDetailResponse = {
id: string id: string
sequence_number: number
version: string version: string
graph: { graph: {
nodes: Node[] nodes: Node[]

+ 0
- 2
web/types/workflow.ts 查看文件

data: { data: {
id: string id: string
workflow_id: string workflow_id: string
sequence_number: number
created_at: number created_at: number
} }
} }


export type WorkflowRunHistory = { export type WorkflowRunHistory = {
id: string id: string
sequence_number: number
version: string version: string
conversation_id?: string conversation_id?: string
message_id?: string message_id?: string

正在加载...
取消
保存