mirror of
https://github.com/langgenius/dify.git
synced 2026-05-12 15:58:19 +08:00
144 lines
4.5 KiB
Python
144 lines
4.5 KiB
Python
"""Server-side JSON Schema derivation from Dify `user_input_form`."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Any, cast
|
|
|
|
from controllers.service_api.app.error import AppUnavailableError
|
|
from models import App
|
|
from models.model import AppMode
|
|
|
|
JSON_SCHEMA_DRAFT = "https://json-schema.org/draft/2020-12/schema"
|
|
|
|
EMPTY_INPUT_SCHEMA: dict[str, Any] = {
|
|
"$schema": JSON_SCHEMA_DRAFT,
|
|
"type": "object",
|
|
"properties": {},
|
|
"required": [],
|
|
}
|
|
|
|
_CHAT_FAMILY = frozenset({AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT})
|
|
|
|
|
|
def _file_object_shape() -> dict[str, Any]:
|
|
"""Single-file value shape. Forward-compat placeholder; refine when file-API contract pins."""
|
|
return {
|
|
"type": "object",
|
|
"properties": {
|
|
"type": {"type": "string"},
|
|
"transfer_method": {"type": "string"},
|
|
"url": {"type": "string"},
|
|
"upload_file_id": {"type": "string"},
|
|
},
|
|
"additionalProperties": True,
|
|
}
|
|
|
|
|
|
def _row_to_schema(row_type: str, row: dict[str, Any]) -> dict[str, Any] | None:
|
|
label = row.get("label") or row.get("variable", "")
|
|
base: dict[str, Any] = {"title": label} if label else {}
|
|
|
|
if row_type in ("text-input", "paragraph"):
|
|
out = {"type": "string"} | base
|
|
max_length = row.get("max_length")
|
|
if isinstance(max_length, int) and max_length > 0:
|
|
out["maxLength"] = max_length
|
|
return out
|
|
|
|
if row_type == "select":
|
|
return {"type": "string"} | base | {"enum": list(row.get("options") or [])}
|
|
|
|
if row_type == "number":
|
|
return {"type": "number"} | base
|
|
|
|
if row_type == "file":
|
|
return _file_object_shape() | base
|
|
|
|
if row_type == "file-list":
|
|
return {
|
|
"type": "array",
|
|
"items": _file_object_shape(),
|
|
} | base
|
|
|
|
return None
|
|
|
|
|
|
def _form_to_jsonschema(form: list[dict[str, Any]]) -> tuple[dict[str, Any], list[str]]:
|
|
"""Translate a user_input_form row list into (properties, required-list).
|
|
|
|
Each row is a single-key dict: `{"text-input": {variable, label, required, ...}}`.
|
|
Unknown variable types are skipped (forward-compat).
|
|
"""
|
|
properties: dict[str, Any] = {}
|
|
required: list[str] = []
|
|
for row in form:
|
|
if not isinstance(row, dict) or len(row) != 1:
|
|
continue
|
|
((row_type, row_body),) = row.items()
|
|
if not isinstance(row_body, dict):
|
|
continue
|
|
variable = row_body.get("variable")
|
|
if not variable:
|
|
continue
|
|
schema = _row_to_schema(row_type, row_body)
|
|
if schema is None:
|
|
continue
|
|
properties[variable] = schema
|
|
if row_body.get("required"):
|
|
required.append(variable)
|
|
return properties, required
|
|
|
|
|
|
def resolve_app_config(app: App) -> tuple[dict[str, Any], list[dict[str, Any]]]:
|
|
"""Resolve `(features_dict, user_input_form)` for parameters / schema derivation.
|
|
|
|
Raises `AppUnavailableError` on misconfigured apps.
|
|
"""
|
|
if app.mode in {AppMode.ADVANCED_CHAT, AppMode.WORKFLOW}:
|
|
workflow = app.workflow
|
|
if workflow is None:
|
|
raise AppUnavailableError()
|
|
return (
|
|
workflow.features_dict,
|
|
cast(list[dict[str, Any]], workflow.user_input_form(to_old_structure=True)),
|
|
)
|
|
|
|
app_model_config = app.app_model_config
|
|
if app_model_config is None:
|
|
raise AppUnavailableError()
|
|
features_dict = cast(dict[str, Any], app_model_config.to_dict())
|
|
return features_dict, cast(list[dict[str, Any]], features_dict.get("user_input_form", []))
|
|
|
|
|
|
def build_input_schema(app: App) -> dict[str, Any]:
|
|
"""Derive Draft 2020-12 JSON Schema from `user_input_form` + app mode.
|
|
|
|
chat / agent-chat / advanced-chat: top-level `query` (required, minLength=1) + `inputs` object.
|
|
completion / workflow: `inputs` object only.
|
|
Raises `AppUnavailableError` on misconfigured apps.
|
|
"""
|
|
_, user_input_form = resolve_app_config(app)
|
|
inputs_props, inputs_required = _form_to_jsonschema(user_input_form)
|
|
|
|
properties: dict[str, Any] = {}
|
|
required: list[str] = []
|
|
|
|
if app.mode in _CHAT_FAMILY:
|
|
properties["query"] = {"type": "string", "minLength": 1}
|
|
required.append("query")
|
|
|
|
properties["inputs"] = {
|
|
"type": "object",
|
|
"properties": inputs_props,
|
|
"required": inputs_required,
|
|
"additionalProperties": False,
|
|
}
|
|
required.append("inputs")
|
|
|
|
return {
|
|
"$schema": JSON_SCHEMA_DRAFT,
|
|
"type": "object",
|
|
"properties": properties,
|
|
"required": required,
|
|
}
|