dify-player 0.3.2__tar.gz → 0.3.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {dify_player-0.3.2 → dify_player-0.3.4}/PKG-INFO +1 -1
- {dify_player-0.3.2 → dify_player-0.3.4}/dify_player/__init__.py +1 -1
- dify_player-0.3.4/dify_player/dify_importer/node_converters/start.py +49 -0
- {dify_player-0.3.2 → dify_player-0.3.4}/dify_player/dify_importer/workflow_loader.py +1 -1
- {dify_player-0.3.2 → dify_player-0.3.4}/dify_player/nodes/llm_groq_chat.py +63 -7
- {dify_player-0.3.2 → dify_player-0.3.4}/dify_player/plan_loader.py +32 -0
- {dify_player-0.3.2 → dify_player-0.3.4}/dify_player/runtime.py +18 -1
- {dify_player-0.3.2 → dify_player-0.3.4}/dify_player.egg-info/PKG-INFO +1 -1
- {dify_player-0.3.2 → dify_player-0.3.4}/pyproject.toml +1 -1
- dify_player-0.3.2/dify_player/dify_importer/node_converters/start.py +0 -7
- {dify_player-0.3.2 → dify_player-0.3.4}/MANIFEST.in +0 -0
- {dify_player-0.3.2 → dify_player-0.3.4}/README.md +0 -0
- {dify_player-0.3.2 → dify_player-0.3.4}/dify_player/__main__.py +0 -0
- {dify_player-0.3.2 → dify_player-0.3.4}/dify_player/cli.py +0 -0
- {dify_player-0.3.2 → dify_player-0.3.4}/dify_player/dify_importer/__init__.py +0 -0
- {dify_player-0.3.2 → dify_player-0.3.4}/dify_player/dify_importer/graph_parser.py +0 -0
- {dify_player-0.3.2 → dify_player-0.3.4}/dify_player/dify_importer/http_body_converter.py +0 -0
- {dify_player-0.3.2 → dify_player-0.3.4}/dify_player/dify_importer/node_converters/__init__.py +0 -0
- {dify_player-0.3.2 → dify_player-0.3.4}/dify_player/dify_importer/node_converters/assigner.py +0 -0
- {dify_player-0.3.2 → dify_player-0.3.4}/dify_player/dify_importer/node_converters/code.py +0 -0
- {dify_player-0.3.2 → dify_player-0.3.4}/dify_player/dify_importer/node_converters/end.py +0 -0
- {dify_player-0.3.2 → dify_player-0.3.4}/dify_player/dify_importer/node_converters/http_request.py +0 -0
- {dify_player-0.3.2 → dify_player-0.3.4}/dify_player/dify_importer/node_converters/if_else.py +0 -0
- {dify_player-0.3.2 → dify_player-0.3.4}/dify_player/dify_importer/node_converters/llm.py +0 -0
- {dify_player-0.3.2 → dify_player-0.3.4}/dify_player/dify_importer/node_converters/loop.py +0 -0
- {dify_player-0.3.2 → dify_player-0.3.4}/dify_player/dify_importer/node_converters/template_transform.py +0 -0
- {dify_player-0.3.2 → dify_player-0.3.4}/dify_player/dify_importer/node_converters/variable_aggregator.py +0 -0
- {dify_player-0.3.2 → dify_player-0.3.4}/dify_player/dify_importer/plan_serializer.py +0 -0
- {dify_player-0.3.2 → dify_player-0.3.4}/dify_player/dify_importer/reference_converter.py +0 -0
- {dify_player-0.3.2 → dify_player-0.3.4}/dify_player/dify_importer/workflow_normalizer.py +0 -0
- {dify_player-0.3.2 → dify_player-0.3.4}/dify_player/dify_workflow_importer.py +0 -0
- {dify_player-0.3.2 → dify_player-0.3.4}/dify_player/event_logger.py +0 -0
- {dify_player-0.3.2 → dify_player-0.3.4}/dify_player/exceptions.py +0 -0
- {dify_player-0.3.2 → dify_player-0.3.4}/dify_player/input_resolver.py +0 -0
- {dify_player-0.3.2 → dify_player-0.3.4}/dify_player/llm_cache.py +0 -0
- {dify_player-0.3.2 → dify_player-0.3.4}/dify_player/models.py +0 -0
- {dify_player-0.3.2 → dify_player-0.3.4}/dify_player/nodes/__init__.py +0 -0
- {dify_player-0.3.2 → dify_player-0.3.4}/dify_player/nodes/assigner.py +0 -0
- {dify_player-0.3.2 → dify_player-0.3.4}/dify_player/nodes/code.py +0 -0
- {dify_player-0.3.2 → dify_player-0.3.4}/dify_player/nodes/end.py +0 -0
- {dify_player-0.3.2 → dify_player-0.3.4}/dify_player/nodes/http.py +0 -0
- {dify_player-0.3.2 → dify_player-0.3.4}/dify_player/nodes/if_else.py +0 -0
- {dify_player-0.3.2 → dify_player-0.3.4}/dify_player/nodes/llm_azure_chat.py +0 -0
- {dify_player-0.3.2 → dify_player-0.3.4}/dify_player/nodes/llm_xai_chat.py +0 -0
- {dify_player-0.3.2 → dify_player-0.3.4}/dify_player/nodes/start.py +0 -0
- {dify_player-0.3.2 → dify_player-0.3.4}/dify_player/nodes/template.py +0 -0
- {dify_player-0.3.2 → dify_player-0.3.4}/dify_player/nodes/variable_aggregator.py +0 -0
- {dify_player-0.3.2 → dify_player-0.3.4}/dify_player/value_renderer.py +0 -0
- {dify_player-0.3.2 → dify_player-0.3.4}/dify_player/workflow_engine.py +0 -0
- {dify_player-0.3.2 → dify_player-0.3.4}/dify_player/workflow_executor.py +0 -0
- {dify_player-0.3.2 → dify_player-0.3.4}/dify_player.egg-info/SOURCES.txt +0 -0
- {dify_player-0.3.2 → dify_player-0.3.4}/dify_player.egg-info/dependency_links.txt +0 -0
- {dify_player-0.3.2 → dify_player-0.3.4}/dify_player.egg-info/requires.txt +0 -0
- {dify_player-0.3.2 → dify_player-0.3.4}/dify_player.egg-info/top_level.txt +0 -0
- {dify_player-0.3.2 → dify_player-0.3.4}/setup.cfg +0 -0
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from dify_player.exceptions import PlanValidationError
|
|
6
|
+
from dify_player.models import Node
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def convert_start_node(*, node_id: str, node_name: str | None, data: dict[str, Any] | None = None) -> Node:
|
|
10
|
+
input_schema = _convert_input_schema(node_id=node_id, data=data or {})
|
|
11
|
+
config = {"input_schema": input_schema} if input_schema else {}
|
|
12
|
+
return Node(id=node_id, kind="start", name=node_name, config=config)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _convert_input_schema(*, node_id: str, data: dict[str, Any]) -> dict[str, dict[str, Any]]:
|
|
16
|
+
raw_variables = data.get("variables", [])
|
|
17
|
+
if raw_variables is None:
|
|
18
|
+
raw_variables = []
|
|
19
|
+
if not isinstance(raw_variables, list):
|
|
20
|
+
raise PlanValidationError(f"Dify start node {node_id!r} variables must be an array")
|
|
21
|
+
|
|
22
|
+
input_schema: dict[str, dict[str, Any]] = {}
|
|
23
|
+
for index, raw_variable in enumerate(raw_variables):
|
|
24
|
+
if not isinstance(raw_variable, dict):
|
|
25
|
+
raise PlanValidationError(f"Dify start node {node_id!r} variables[{index}] must be an object")
|
|
26
|
+
variable = raw_variable.get("variable")
|
|
27
|
+
if not isinstance(variable, str) or not variable:
|
|
28
|
+
raise PlanValidationError(f"Dify start node {node_id!r} variables[{index}].variable must be a non-empty string")
|
|
29
|
+
if variable in input_schema:
|
|
30
|
+
raise PlanValidationError(f"Dify start node {node_id!r} defines duplicate variable {variable!r}")
|
|
31
|
+
|
|
32
|
+
required = raw_variable.get("required", True)
|
|
33
|
+
if not isinstance(required, bool):
|
|
34
|
+
raise PlanValidationError(f"Dify start node {node_id!r} variables[{index}].required must be a boolean when provided")
|
|
35
|
+
|
|
36
|
+
field_schema: dict[str, Any] = {"required": required}
|
|
37
|
+
if _has_meaningful_default(raw_variable=raw_variable, required=required):
|
|
38
|
+
field_schema["default"] = raw_variable["default"]
|
|
39
|
+
input_schema[variable] = field_schema
|
|
40
|
+
return input_schema
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _has_meaningful_default(*, raw_variable: dict[str, Any], required: bool) -> bool:
|
|
44
|
+
if "default" not in raw_variable:
|
|
45
|
+
return False
|
|
46
|
+
default = raw_variable["default"]
|
|
47
|
+
if required and default == "":
|
|
48
|
+
return False
|
|
49
|
+
return True
|
|
@@ -42,7 +42,7 @@ def _convert_node(*, node_id: str, data: dict[str, Any], parsed_graph: ParsedDif
|
|
|
42
42
|
node_type = data["type"]
|
|
43
43
|
node_name = _extract_node_name(node_id=node_id, data=data)
|
|
44
44
|
if node_type == "start":
|
|
45
|
-
return convert_start_node(node_id=node_id, node_name=node_name)
|
|
45
|
+
return convert_start_node(node_id=node_id, node_name=node_name, data=data)
|
|
46
46
|
if node_type == "http-request":
|
|
47
47
|
return convert_http_request_node(node_id=node_id, node_name=node_name, data=data, node_specs=parsed_graph.node_specs)
|
|
48
48
|
if node_type == "llm":
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import json
|
|
3
4
|
import os
|
|
4
5
|
from typing import Any
|
|
5
6
|
|
|
@@ -158,13 +159,38 @@ async def _run_structured_output_with_repair(
|
|
|
158
159
|
latest_metadata = {"usage": _zero_usage(), "finish_reason": None}
|
|
159
160
|
|
|
160
161
|
for attempt_index in range(_STRUCTURED_OUTPUT_REPAIR_RETRIES + 1):
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
162
|
+
try:
|
|
163
|
+
raw_text, metadata = await _request_raw_text(
|
|
164
|
+
node=node,
|
|
165
|
+
base_url=base_url,
|
|
166
|
+
api_key=api_key,
|
|
167
|
+
messages=current_messages,
|
|
168
|
+
http_client=http_client,
|
|
169
|
+
)
|
|
170
|
+
except GroqLLMBadStatusError as exc:
|
|
171
|
+
failed_generation = _extract_groq_failed_generation(exc)
|
|
172
|
+
if failed_generation is None:
|
|
173
|
+
raise
|
|
174
|
+
|
|
175
|
+
validation_error, raw_text = failed_generation
|
|
176
|
+
attempts.append(
|
|
177
|
+
{
|
|
178
|
+
"attempt": str(attempt_index + 1),
|
|
179
|
+
"error": validation_error,
|
|
180
|
+
"raw_text": raw_text,
|
|
181
|
+
"provider_status_code": str(exc.status_code),
|
|
182
|
+
}
|
|
183
|
+
)
|
|
184
|
+
if attempt_index >= _STRUCTURED_OUTPUT_REPAIR_RETRIES:
|
|
185
|
+
raise GroqLLMStructuredOutputError(
|
|
186
|
+
f"{validation_error} after {attempt_index + 1} attempts",
|
|
187
|
+
attempts=attempts,
|
|
188
|
+
) from exc
|
|
189
|
+
current_messages = current_messages + [
|
|
190
|
+
_build_repair_message(validation_error=validation_error, previous_output=raw_text)
|
|
191
|
+
]
|
|
192
|
+
continue
|
|
193
|
+
|
|
168
194
|
aggregated_usage = _merge_usage(aggregated_usage, metadata["usage"])
|
|
169
195
|
latest_metadata = metadata
|
|
170
196
|
try:
|
|
@@ -195,6 +221,36 @@ async def _run_structured_output_with_repair(
|
|
|
195
221
|
raise AssertionError("structured output repair loop must return or raise")
|
|
196
222
|
|
|
197
223
|
|
|
224
|
+
# Groq may return json_validate_failed as HTTP 400 even when the failure is the
|
|
225
|
+
# model's generated JSON, not a permanent request/schema error. Preserve that
|
|
226
|
+
# failed output so the existing structured-output repair prompt can fix it.
|
|
227
|
+
def _extract_groq_failed_generation(exc: GroqLLMBadStatusError) -> tuple[str, str] | None:
|
|
228
|
+
if exc.status_code != 400 or exc.response_body is None:
|
|
229
|
+
return None
|
|
230
|
+
|
|
231
|
+
try:
|
|
232
|
+
payload = json.loads(exc.response_body)
|
|
233
|
+
except json.JSONDecodeError:
|
|
234
|
+
return None
|
|
235
|
+
if not isinstance(payload, dict):
|
|
236
|
+
return None
|
|
237
|
+
|
|
238
|
+
error = payload.get("error")
|
|
239
|
+
if not isinstance(error, dict):
|
|
240
|
+
return None
|
|
241
|
+
if error.get("code") != "json_validate_failed":
|
|
242
|
+
return None
|
|
243
|
+
|
|
244
|
+
failed_generation = error.get("failed_generation")
|
|
245
|
+
if not isinstance(failed_generation, str) or not failed_generation.strip():
|
|
246
|
+
return None
|
|
247
|
+
|
|
248
|
+
message = error.get("message")
|
|
249
|
+
if not isinstance(message, str) or not message:
|
|
250
|
+
message = "Groq json_validate_failed"
|
|
251
|
+
return message, failed_generation
|
|
252
|
+
|
|
253
|
+
|
|
198
254
|
def _build_llm_output(
|
|
199
255
|
*,
|
|
200
256
|
text: str,
|
|
@@ -78,6 +78,8 @@ def parse_plan(data: Any) -> Plan:
|
|
|
78
78
|
if not isinstance(config, dict):
|
|
79
79
|
raise PlanValidationError(f"node.config must be an object for node {node_label!r}")
|
|
80
80
|
normalized_config = dict(config)
|
|
81
|
+
if kind == "start":
|
|
82
|
+
_validate_start_config(node_id=node_id, node_name=name, config=normalized_config)
|
|
81
83
|
if kind == "template" and not isinstance(normalized_config.get("template"), str):
|
|
82
84
|
raise PlanValidationError(f"template node {node_label!r} must define config.template as a string")
|
|
83
85
|
if kind == "http":
|
|
@@ -305,6 +307,36 @@ def _collect_roots(value: Any) -> set[str]:
|
|
|
305
307
|
return set()
|
|
306
308
|
|
|
307
309
|
|
|
310
|
+
def _validate_start_config(*, node_id: str, node_name: str | None, config: dict[str, Any]) -> None:
|
|
311
|
+
node_label = format_node_label(node_id, node_name)
|
|
312
|
+
allowed_keys = {"input_schema"}
|
|
313
|
+
extra_keys = sorted(set(config) - allowed_keys)
|
|
314
|
+
if extra_keys:
|
|
315
|
+
raise PlanValidationError(f"start node {node_label!r} config has unsupported keys: {', '.join(extra_keys)}")
|
|
316
|
+
if "input_schema" not in config:
|
|
317
|
+
return
|
|
318
|
+
|
|
319
|
+
input_schema = config["input_schema"]
|
|
320
|
+
if not isinstance(input_schema, dict):
|
|
321
|
+
raise PlanValidationError(f"start node {node_label!r} config.input_schema must be an object")
|
|
322
|
+
|
|
323
|
+
normalized_schema: dict[str, dict[str, Any]] = {}
|
|
324
|
+
for variable, field_schema in input_schema.items():
|
|
325
|
+
if not isinstance(variable, str) or not variable:
|
|
326
|
+
raise PlanValidationError(f"start node {node_label!r} config.input_schema keys must be non-empty strings")
|
|
327
|
+
if not isinstance(field_schema, dict):
|
|
328
|
+
raise PlanValidationError(f"start node {node_label!r} config.input_schema.{variable} must be an object")
|
|
329
|
+
required = field_schema.get("required", True)
|
|
330
|
+
if not isinstance(required, bool):
|
|
331
|
+
raise PlanValidationError(
|
|
332
|
+
f"start node {node_label!r} config.input_schema.{variable}.required must be a boolean when provided"
|
|
333
|
+
)
|
|
334
|
+
normalized_field_schema = dict(field_schema)
|
|
335
|
+
normalized_field_schema["required"] = required
|
|
336
|
+
normalized_schema[variable] = normalized_field_schema
|
|
337
|
+
config["input_schema"] = normalized_schema
|
|
338
|
+
|
|
339
|
+
|
|
308
340
|
def _validate_http_config(*, node_id: str, node_name: str | None, config: dict[str, Any]) -> None:
|
|
309
341
|
node_label = format_node_label(node_id, node_name)
|
|
310
342
|
allowed_keys = {"method", "timeout_sec"}
|
|
@@ -52,7 +52,7 @@ class WorkflowRuntime:
|
|
|
52
52
|
) -> RunResult:
|
|
53
53
|
effective_started_at = started_at or _utc_now()
|
|
54
54
|
effective_started_monotonic = started_monotonic if started_monotonic is not None else time.monotonic()
|
|
55
|
-
state = RunState(run_id=self.logger.run_id, inputs=inputs)
|
|
55
|
+
state = RunState(run_id=self.logger.run_id, inputs=_normalize_workflow_inputs(compiled_plan=compiled_plan, inputs=inputs))
|
|
56
56
|
effective_http_client = http_client if http_client is not None else httpx.AsyncClient()
|
|
57
57
|
owns_http_client = http_client is None
|
|
58
58
|
try:
|
|
@@ -755,6 +755,23 @@ def _utc_now() -> str:
|
|
|
755
755
|
return datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")
|
|
756
756
|
|
|
757
757
|
|
|
758
|
+
def _normalize_workflow_inputs(*, compiled_plan: CompiledPlan, inputs: dict[str, Any]) -> dict[str, Any]:
|
|
759
|
+
normalized_inputs = dict(inputs)
|
|
760
|
+
start_node = compiled_plan.node_by_id[compiled_plan.start_node_id]
|
|
761
|
+
input_schema = start_node.config.get("input_schema", {})
|
|
762
|
+
if not isinstance(input_schema, dict):
|
|
763
|
+
return normalized_inputs
|
|
764
|
+
|
|
765
|
+
for variable, field_schema in input_schema.items():
|
|
766
|
+
if variable in normalized_inputs or not isinstance(field_schema, dict):
|
|
767
|
+
continue
|
|
768
|
+
if "default" in field_schema:
|
|
769
|
+
normalized_inputs[variable] = deepcopy(field_schema["default"])
|
|
770
|
+
elif field_schema.get("required", True) is False:
|
|
771
|
+
normalized_inputs[variable] = ""
|
|
772
|
+
return normalized_inputs
|
|
773
|
+
|
|
774
|
+
|
|
758
775
|
class _LoopRuntimeError(ValueError):
|
|
759
776
|
def __init__(self, error: WorkflowError) -> None:
|
|
760
777
|
super().__init__(error.message)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dify_player-0.3.2 → dify_player-0.3.4}/dify_player/dify_importer/node_converters/__init__.py
RENAMED
|
File without changes
|
{dify_player-0.3.2 → dify_player-0.3.4}/dify_player/dify_importer/node_converters/assigner.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dify_player-0.3.2 → dify_player-0.3.4}/dify_player/dify_importer/node_converters/http_request.py
RENAMED
|
File without changes
|
{dify_player-0.3.2 → dify_player-0.3.4}/dify_player/dify_importer/node_converters/if_else.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|