mirror of
https://github.com/langgenius/dify.git
synced 2026-09-24 23:22:26 +08:00
chore: Improve workflow generator accessibility and API contracts (#38838)
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
8e0d10bf5e
commit
7865ffd429
@@ -3,7 +3,7 @@ from collections.abc import Generator, Sequence
|
||||
from typing import Any, Literal
|
||||
|
||||
from flask_restx import Resource
|
||||
from pydantic import BaseModel, Field, RootModel
|
||||
from pydantic import BaseModel, ConfigDict, Field, RootModel
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
@@ -26,9 +26,10 @@ from core.helper.code_executor.python3.python3_code_provider import Python3CodeP
|
||||
from core.llm_generator.entities import RuleCodeGeneratePayload, RuleGeneratePayload, RuleStructuredOutputPayload
|
||||
from core.llm_generator.llm_generator import LLMGenerator
|
||||
from core.workflow.generator.types import WorkflowGenerateErrorCode
|
||||
from fields.base import ResponseModel
|
||||
from graphon.model_runtime.entities.llm_entities import LLMMode
|
||||
from graphon.model_runtime.errors.invoke import InvokeError
|
||||
from libs.helper import compact_generate_response
|
||||
from libs.helper import compact_generate_response, dump_response
|
||||
from libs.login import login_required
|
||||
from models import App
|
||||
from services.workflow_generator_service import WorkflowGeneratorService
|
||||
@@ -60,6 +61,48 @@ class InstructionTemplatePayload(BaseModel):
|
||||
_MAX_INSTRUCTION_LENGTH = 10_000
|
||||
|
||||
|
||||
class WorkflowGraphPosition(BaseModel):
|
||||
x: float
|
||||
y: float
|
||||
|
||||
|
||||
class WorkflowGraphViewport(WorkflowGraphPosition):
|
||||
zoom: float
|
||||
|
||||
|
||||
class WorkflowGraphNode(BaseModel):
|
||||
"""React Flow node shape accepted and returned by the generator.
|
||||
|
||||
Node-specific configuration lives under ``data`` and wrapper metadata
|
||||
differs for container children, so unknown wrapper fields must survive
|
||||
request validation and response serialization.
|
||||
"""
|
||||
|
||||
model_config = ConfigDict(extra="allow", populate_by_name=True)
|
||||
|
||||
id: str
|
||||
type: str
|
||||
position: WorkflowGraphPosition
|
||||
data: dict[str, Any]
|
||||
|
||||
|
||||
class WorkflowGraphEdge(BaseModel):
|
||||
"""React Flow edge shape with extensible renderer metadata."""
|
||||
|
||||
model_config = ConfigDict(extra="allow", populate_by_name=True)
|
||||
|
||||
id: str
|
||||
source: str
|
||||
target: str
|
||||
type: str
|
||||
|
||||
|
||||
class WorkflowGraph(BaseModel):
|
||||
nodes: list[WorkflowGraphNode]
|
||||
edges: list[WorkflowGraphEdge]
|
||||
viewport: WorkflowGraphViewport
|
||||
|
||||
|
||||
class WorkflowGeneratePayload(BaseModel):
|
||||
"""Payload for the cmd+k `/create` and `/refine` workflow generator endpoint.
|
||||
|
||||
@@ -79,7 +122,7 @@ class WorkflowGeneratePayload(BaseModel):
|
||||
alias="model_config",
|
||||
description="Model configuration",
|
||||
)
|
||||
current_graph: dict | None = Field(
|
||||
current_graph: WorkflowGraph | None = Field(
|
||||
default=None,
|
||||
description="Existing draft graph to refine (cmd+k `/refine`); omit for create-from-scratch",
|
||||
)
|
||||
@@ -98,6 +141,59 @@ class WorkflowInstructionSuggestionsPayload(BaseModel):
|
||||
count: int = Field(default=4, ge=1, le=6, description="Number of suggestions to return (1-6)")
|
||||
|
||||
|
||||
class WorkflowGenerateErrorResponse(ResponseModel):
|
||||
code: WorkflowGenerateErrorCode
|
||||
detail: str
|
||||
node_id: str | None = None
|
||||
|
||||
|
||||
class WorkflowGenerateResponse(ResponseModel):
|
||||
graph: WorkflowGraph
|
||||
message: str = ""
|
||||
app_name: str = ""
|
||||
icon: str = ""
|
||||
error: str = ""
|
||||
errors: list[WorkflowGenerateErrorResponse] = Field(default_factory=list)
|
||||
mode: Literal["workflow", "advanced-chat"] | None = None
|
||||
|
||||
|
||||
class WorkflowPlanNodeResponse(ResponseModel):
|
||||
label: str
|
||||
node_type: str
|
||||
purpose: str = ""
|
||||
|
||||
|
||||
class WorkflowPlanStartInputResponse(ResponseModel):
|
||||
variable: str
|
||||
label: str = ""
|
||||
type: str = ""
|
||||
|
||||
|
||||
class WorkflowGeneratePlanEventResponse(ResponseModel):
|
||||
event: Literal["plan"] = "plan"
|
||||
title: str = ""
|
||||
description: str = ""
|
||||
app_name: str = ""
|
||||
icon: str = ""
|
||||
mode: Literal["workflow", "advanced-chat"]
|
||||
nodes: list[WorkflowPlanNodeResponse]
|
||||
start_inputs: list[WorkflowPlanStartInputResponse] = Field(default_factory=list)
|
||||
|
||||
|
||||
class WorkflowGenerateResultEventResponse(WorkflowGenerateResponse):
|
||||
event: Literal["result"] = "result"
|
||||
|
||||
|
||||
class WorkflowGenerateStreamEventResponse(
|
||||
RootModel[WorkflowGeneratePlanEventResponse | WorkflowGenerateResultEventResponse]
|
||||
):
|
||||
"""Schema for each JSON object carried by an SSE ``data:`` frame."""
|
||||
|
||||
|
||||
class WorkflowInstructionSuggestionsResponse(ResponseModel):
|
||||
suggestions: list[str]
|
||||
|
||||
|
||||
class GeneratorResponse(RootModel[Any]):
|
||||
root: Any
|
||||
|
||||
@@ -114,7 +210,16 @@ register_schema_models(
|
||||
WorkflowInstructionSuggestionsPayload,
|
||||
ModelConfig,
|
||||
)
|
||||
register_response_schema_models(console_ns, GeneratorResponse, SimpleDataResponse)
|
||||
register_response_schema_models(
|
||||
console_ns,
|
||||
GeneratorResponse,
|
||||
SimpleDataResponse,
|
||||
WorkflowGenerateResponse,
|
||||
WorkflowGeneratePlanEventResponse,
|
||||
WorkflowGenerateResultEventResponse,
|
||||
WorkflowGenerateStreamEventResponse,
|
||||
WorkflowInstructionSuggestionsResponse,
|
||||
)
|
||||
|
||||
|
||||
@console_ns.route("/rule-generate")
|
||||
@@ -376,7 +481,11 @@ class WorkflowGenerateApi(Resource):
|
||||
@console_ns.doc("generate_workflow_graph")
|
||||
@console_ns.doc(description="Generate a Dify workflow graph from natural language")
|
||||
@console_ns.expect(console_ns.models[WorkflowGeneratePayload.__name__])
|
||||
@console_ns.response(200, "Workflow graph generated successfully", console_ns.models[GeneratorResponse.__name__])
|
||||
@console_ns.response(
|
||||
200,
|
||||
"Workflow graph generated successfully",
|
||||
console_ns.models[WorkflowGenerateResponse.__name__],
|
||||
)
|
||||
@console_ns.response(400, "Invalid request parameters")
|
||||
@console_ns.response(402, "Provider quota exceeded")
|
||||
@setup_required
|
||||
@@ -399,7 +508,9 @@ class WorkflowGenerateApi(Resource):
|
||||
instruction=args.instruction,
|
||||
model_config=args.model_config_data,
|
||||
ideal_output=args.ideal_output,
|
||||
current_graph=args.current_graph,
|
||||
current_graph=args.current_graph.model_dump(by_alias=True, exclude_none=True)
|
||||
if args.current_graph
|
||||
else None,
|
||||
)
|
||||
except ProviderTokenNotInitError as ex:
|
||||
raise ProviderNotInitializeError(ex.description)
|
||||
@@ -410,7 +521,7 @@ class WorkflowGenerateApi(Resource):
|
||||
except InvokeError as e:
|
||||
raise CompletionRequestError(e.description)
|
||||
|
||||
return result
|
||||
return dump_response(WorkflowGenerateResponse, result)
|
||||
|
||||
|
||||
@console_ns.route("/workflow-generate/suggestions")
|
||||
@@ -426,7 +537,11 @@ class WorkflowInstructionSuggestionsApi(Resource):
|
||||
@console_ns.doc("generate_workflow_instruction_suggestions")
|
||||
@console_ns.doc(description="Suggest example workflow-generator instructions for the tenant")
|
||||
@console_ns.expect(console_ns.models[WorkflowInstructionSuggestionsPayload.__name__])
|
||||
@console_ns.response(200, "Suggestions generated successfully", console_ns.models[GeneratorResponse.__name__])
|
||||
@console_ns.response(
|
||||
200,
|
||||
"Suggestions generated successfully",
|
||||
console_ns.models[WorkflowInstructionSuggestionsResponse.__name__],
|
||||
)
|
||||
@console_ns.response(400, "Invalid request parameters")
|
||||
@setup_required
|
||||
@login_required
|
||||
@@ -440,7 +555,7 @@ class WorkflowInstructionSuggestionsApi(Resource):
|
||||
language=args.language,
|
||||
count=args.count,
|
||||
)
|
||||
return {"suggestions": suggestions}
|
||||
return dump_response(WorkflowInstructionSuggestionsResponse, {"suggestions": suggestions})
|
||||
|
||||
|
||||
@console_ns.route("/workflow-generate/stream")
|
||||
@@ -458,7 +573,11 @@ class WorkflowGenerateStreamApi(Resource):
|
||||
@console_ns.doc("generate_workflow_graph_stream")
|
||||
@console_ns.doc(description="Stream a Dify workflow graph (plan then result) via SSE")
|
||||
@console_ns.expect(console_ns.models[WorkflowGeneratePayload.__name__])
|
||||
@console_ns.response(200, "Server-Sent Events stream of plan/result events")
|
||||
@console_ns.response(
|
||||
200,
|
||||
"Server-Sent Events stream; each data frame matches this plan/result event schema",
|
||||
console_ns.models[WorkflowGenerateStreamEventResponse.__name__],
|
||||
)
|
||||
@console_ns.response(400, "Invalid request parameters")
|
||||
@setup_required
|
||||
@login_required
|
||||
@@ -481,10 +600,17 @@ class WorkflowGenerateStreamApi(Resource):
|
||||
instruction=args.instruction,
|
||||
model_config=args.model_config_data,
|
||||
ideal_output=args.ideal_output,
|
||||
current_graph=args.current_graph,
|
||||
current_graph=(
|
||||
args.current_graph.model_dump(by_alias=True, exclude_none=True) if args.current_graph else None
|
||||
),
|
||||
):
|
||||
body = {"event": event_name, **payload}
|
||||
yield f"data: {json.dumps(body)}\n\n"
|
||||
if event_name == "plan":
|
||||
plan_event = WorkflowGeneratePlanEventResponse.model_validate(body)
|
||||
yield f"data: {json.dumps(plan_event.model_dump(mode='json'))}\n\n"
|
||||
else:
|
||||
result_event = WorkflowGenerateResultEventResponse.model_validate(body)
|
||||
yield f"data: {json.dumps(result_event.model_dump(mode='json'))}\n\n"
|
||||
except (ProviderTokenNotInitError, QuotaExceededError, ModelCurrentlyNotSupportError, InvokeError) as e:
|
||||
# The model instance is resolved inside the service (lazily, on
|
||||
# first iteration), so a provider / init error surfaces here.
|
||||
@@ -498,6 +624,7 @@ class WorkflowGenerateStreamApi(Resource):
|
||||
"error": detail,
|
||||
"errors": [{"code": WorkflowGenerateErrorCode.MODEL_ERROR, "detail": detail}],
|
||||
}
|
||||
yield f"data: {json.dumps(error_body)}\n\n"
|
||||
error_event = WorkflowGenerateResultEventResponse.model_validate(error_body)
|
||||
yield f"data: {json.dumps(error_event.model_dump(mode='json'))}\n\n"
|
||||
|
||||
return compact_generate_response(generate())
|
||||
|
||||
Reference in New Issue
Block a user