diffusers-workflow 0.4.0__py3-none-any.whl
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.
- diffusers_workflow-0.4.0.dist-info/METADATA +318 -0
- diffusers_workflow-0.4.0.dist-info/RECORD +260 -0
- diffusers_workflow-0.4.0.dist-info/WHEEL +5 -0
- diffusers_workflow-0.4.0.dist-info/entry_points.txt +7 -0
- diffusers_workflow-0.4.0.dist-info/licenses/LICENSE +201 -0
- diffusers_workflow-0.4.0.dist-info/top_level.txt +2 -0
- dw/__init__.py +440 -0
- dw/adapter_compatibility.py +226 -0
- dw/arguments.py +1231 -0
- dw/assessment_rules.py +159 -0
- dw/assets.py +130 -0
- dw/cache_blocks.json +16 -0
- dw/cache_blocks.py +146 -0
- dw/community_pipelines/pipeline_flux_rf_inversion.py +1184 -0
- dw/content_types.py +150 -0
- dw/dissolve_frame_errors.py +121 -0
- dw/docs/ACCELERATION.md +352 -0
- dw/docs/AGENT_LOOP.md +95 -0
- dw/docs/DEPENDENCIES.md +91 -0
- dw/docs/IP_ADAPTER.md +109 -0
- dw/docs/LORAS.md +131 -0
- dw/docs/MCP.md +517 -0
- dw/docs/PROMPT_WEIGHTING.md +78 -0
- dw/docs/QUANTIZATION.md +230 -0
- dw/docs/RECIPES_24GB.md +201 -0
- dw/docs/RELEASING.md +195 -0
- dw/docs/REMOTE.md +140 -0
- dw/docs/REPL_COMMANDS.md +121 -0
- dw/docs/REPL_WORKER_GUIDE.md +51 -0
- dw/docs/SECURITY.md +272 -0
- dw/docs/SECURITY_QUICKREF.md +112 -0
- dw/docs/SERVER.md +679 -0
- dw/docs/TASKS.md +1741 -0
- dw/docs/TESTING.md +71 -0
- dw/docs/WORKFLOW_GUIDE.md +2038 -0
- dw/docs/WORKSPACES.md +316 -0
- dw/download_watch.py +335 -0
- dw/elision.py +306 -0
- dw/events.py +275 -0
- dw/for_each.py +409 -0
- dw/host_memory.py +258 -0
- dw/host_memory_projection.py +230 -0
- dw/hub_cache.py +432 -0
- dw/introspection.py +1228 -0
- dw/kernel_availability.py +208 -0
- dw/locations.py +599 -0
- dw/log_setup.py +45 -0
- dw/loudness.py +82 -0
- dw/media_audio.py +217 -0
- dw/media_frames.py +367 -0
- dw/media_info.py +297 -0
- dw/pipeline_processors/chain.py +821 -0
- dw/pipeline_processors/config_objects.py +237 -0
- dw/pipeline_processors/pipeline.py +2297 -0
- dw/pipeline_processors/remote.py +46 -0
- dw/plan.py +920 -0
- dw/previous_results.py +411 -0
- dw/probe_paths.py +59 -0
- dw/prompt_schema.json +48 -0
- dw/prompt_weighting.py +378 -0
- dw/prompts.py +159 -0
- dw/realize.py +250 -0
- dw/reference_limits.py +215 -0
- dw/reference_names.py +125 -0
- dw/repl.py +338 -0
- dw/repl_commands.py +836 -0
- dw/repl_worker.py +159 -0
- dw/result.py +1720 -0
- dw/result_fps.py +82 -0
- dw/run.py +162 -0
- dw/runs.py +768 -0
- dw/scalar_result_validation.py +97 -0
- dw/schema.py +283 -0
- dw/security.py +1038 -0
- dw/select_validation.py +115 -0
- dw/serve.py +277 -0
- dw/server/__init__.py +2 -0
- dw/server/app.py +4586 -0
- dw/server/assess.py +132 -0
- dw/server/catalog_shape.py +487 -0
- dw/server/enhancers.py +129 -0
- dw/server/exports.py +480 -0
- dw/server/guides.py +257 -0
- dw/server/jobs.py +1561 -0
- dw/server/mcp_mount.py +95 -0
- dw/server/netinfo.py +124 -0
- dw/server/observed_cost.py +379 -0
- dw/server/sysinfo.py +71 -0
- dw/server/ui/assets/abap-08VXUWAP.js +1 -0
- dw/server/ui/assets/apex-BWPQTe0t.js +1 -0
- dw/server/ui/assets/azcli-Bc_sGQ0U.js +1 -0
- dw/server/ui/assets/bat-i0X4ZdIN.js +1 -0
- dw/server/ui/assets/bicep-B5-_aFwp.js +2 -0
- dw/server/ui/assets/cameligo-DMUM7wLl.js +1 -0
- dw/server/ui/assets/clojure-Cm7r79vr.js +1 -0
- dw/server/ui/assets/codicon-Brq4_Ui5.ttf +0 -0
- dw/server/ui/assets/coffee-Ba7i2nA0.js +1 -0
- dw/server/ui/assets/cpp-C7h46wYY.js +1 -0
- dw/server/ui/assets/csharp-BKxtCVv1.js +1 -0
- dw/server/ui/assets/csp-bTuwJoIa.js +1 -0
- dw/server/ui/assets/css-DIMkf-bt.js +3 -0
- dw/server/ui/assets/css.worker-B3ciXF_0.js +93 -0
- dw/server/ui/assets/cssMode-CPznxfY8.js +1 -0
- dw/server/ui/assets/cypher-CVaqCwHa.js +1 -0
- dw/server/ui/assets/dart-onAF5SnQ.js +1 -0
- dw/server/ui/assets/dockerfile-DZFCIeNp.js +1 -0
- dw/server/ui/assets/ecl-D05T4iGw.js +1 -0
- dw/server/ui/assets/editor-jjEx9u7D.css +1 -0
- dw/server/ui/assets/editor.api-CpWcotrd.js +847 -0
- dw/server/ui/assets/editor.worker-q-txB4vs.js +30 -0
- dw/server/ui/assets/elixir-6RTg0lbw.js +1 -0
- dw/server/ui/assets/flow9-C5_-GSwl.js +1 -0
- dw/server/ui/assets/freemarker2-CXtRM8N4.js +3 -0
- dw/server/ui/assets/fsharp-C8Ef5oNN.js +1 -0
- dw/server/ui/assets/go-C-y9NEjX.js +1 -0
- dw/server/ui/assets/graphql-fmXr3nnJ.js +1 -0
- dw/server/ui/assets/handlebars-N7x-6NMY.js +1 -0
- dw/server/ui/assets/hcl-CpzslTdj.js +1 -0
- dw/server/ui/assets/html-PhsdjHSr.js +1 -0
- dw/server/ui/assets/html.worker-C93Ht9o9.js +506 -0
- dw/server/ui/assets/htmlMode-Dgj0SEok.js +1 -0
- dw/server/ui/assets/index-3Vw6WAPW.css +1 -0
- dw/server/ui/assets/index-DgrYhQd9.js +43 -0
- dw/server/ui/assets/ini-sBoK_t0W.js +1 -0
- dw/server/ui/assets/java-BEtHBSE6.js +1 -0
- dw/server/ui/assets/javascript-BJqN9Qhv.js +1 -0
- dw/server/ui/assets/json.worker-B2V3pomh.js +62 -0
- dw/server/ui/assets/jsonMode-DbM4SWSv.js +7 -0
- dw/server/ui/assets/julia-Bri6UV-V.js +1 -0
- dw/server/ui/assets/kotlin-BOotOW0E.js +1 -0
- dw/server/ui/assets/less-B9JPFI3C.js +2 -0
- dw/server/ui/assets/lexon-CfSJPG6W.js +1 -0
- dw/server/ui/assets/liquid-BWr8lEc4.js +1 -0
- dw/server/ui/assets/lspLanguageFeatures-C1iGuDyZ.js +4 -0
- dw/server/ui/assets/lua-CsQS60Ue.js +1 -0
- dw/server/ui/assets/m3-D-oSqn_W.js +1 -0
- dw/server/ui/assets/markdown-Cimd5fb3.js +1 -0
- dw/server/ui/assets/mdx-DAdMi_0p.js +1 -0
- dw/server/ui/assets/mips-CIPQ_RoX.js +1 -0
- dw/server/ui/assets/monaco--ixms01u.css +1 -0
- dw/server/ui/assets/monaco-BGCeEqaw.js +56 -0
- dw/server/ui/assets/msdax-DauUninz.js +1 -0
- dw/server/ui/assets/mysql-SOo6toE5.js +1 -0
- dw/server/ui/assets/objective-c-FvmIjYaQ.js +1 -0
- dw/server/ui/assets/pascal-DrH0SRf2.js +1 -0
- dw/server/ui/assets/pascaligo-D-ptJ9y-.js +1 -0
- dw/server/ui/assets/perl-oz_6vUea.js +1 -0
- dw/server/ui/assets/pgsql-DTj74zXo.js +1 -0
- dw/server/ui/assets/php-nr791fC2.js +1 -0
- dw/server/ui/assets/pla-CopQ2nXW.js +1 -0
- dw/server/ui/assets/postiats-43DmfD33.js +1 -0
- dw/server/ui/assets/powerquery-D3hlyOfw.js +1 -0
- dw/server/ui/assets/powershell-DmHpPYUd.js +1 -0
- dw/server/ui/assets/protobuf-C531GsRP.js +2 -0
- dw/server/ui/assets/pug-Z5eAx3Zn.js +1 -0
- dw/server/ui/assets/python-Bcn70HdC.js +1 -0
- dw/server/ui/assets/qsharp-DkqhCAOL.js +1 -0
- dw/server/ui/assets/r-BwWrilGY.js +1 -0
- dw/server/ui/assets/razor-D1HmNnby.js +1 -0
- dw/server/ui/assets/redis-ClamHrr6.js +1 -0
- dw/server/ui/assets/redshift-DT7zqm-g.js +1 -0
- dw/server/ui/assets/restructuredtext-BYgofb2h.js +1 -0
- dw/server/ui/assets/ruby-DezsRK8O.js +1 -0
- dw/server/ui/assets/rust-DdL9SqIa.js +1 -0
- dw/server/ui/assets/sb-CcwsVR0C.js +1 -0
- dw/server/ui/assets/scala-DHpiXF5c.js +1 -0
- dw/server/ui/assets/scheme-BeGwcela.js +1 -0
- dw/server/ui/assets/scss-gp-XZpBa.js +3 -0
- dw/server/ui/assets/shell-CC2rA5mh.js +1 -0
- dw/server/ui/assets/solidity-BEEn4gHE.js +1 -0
- dw/server/ui/assets/sophia-CRfGWb83.js +1 -0
- dw/server/ui/assets/sparql-D_Lu-MrJ.js +1 -0
- dw/server/ui/assets/sql-NEE52Syq.js +1 -0
- dw/server/ui/assets/st-DbInun42.js +1 -0
- dw/server/ui/assets/swift-Bxkupp3x.js +1 -0
- dw/server/ui/assets/systemverilog-Bz4Y3fRF.js +1 -0
- dw/server/ui/assets/tcl-DISqw1ZD.js +1 -0
- dw/server/ui/assets/ts.worker-D7T1-Ig5.js +67738 -0
- dw/server/ui/assets/tsMode-D6u0XmOW.js +11 -0
- dw/server/ui/assets/twig-De2hgUGE.js +1 -0
- dw/server/ui/assets/typescript-BU6v-LMV.js +1 -0
- dw/server/ui/assets/typespec-B8J7ngcE.js +1 -0
- dw/server/ui/assets/vb-DV3o63ZY.js +1 -0
- dw/server/ui/assets/wgsl-DpFanUEy.js +298 -0
- dw/server/ui/assets/workers-Cn7cTUKr.js +1 -0
- dw/server/ui/assets/xml--0LP2Lwk.js +1 -0
- dw/server/ui/assets/yaml-mpBg9jnt.js +1 -0
- dw/server/ui/index.html +17 -0
- dw/server/updater.py +192 -0
- dw/settings.py +98 -0
- dw/shot_span_preflight.py +116 -0
- dw/shots.py +359 -0
- dw/slice_preflight.py +148 -0
- dw/step.py +187 -0
- dw/step_cache.py +442 -0
- dw/subfolders.py +107 -0
- dw/task_domains.py +307 -0
- dw/tasks/assess.py +826 -0
- dw/tasks/audio_transcription.py +88 -0
- dw/tasks/audio_utils.py +1862 -0
- dw/tasks/background_remover.py +43 -0
- dw/tasks/borders.py +113 -0
- dw/tasks/compose_text.py +74 -0
- dw/tasks/concat_videos.py +300 -0
- dw/tasks/depth_estimator.py +54 -0
- dw/tasks/diffusion_upscale.py +109 -0
- dw/tasks/dissolve_videos.py +342 -0
- dw/tasks/format_messages.py +24 -0
- dw/tasks/gather.py +173 -0
- dw/tasks/grade.py +97 -0
- dw/tasks/image_to_text.py +43 -0
- dw/tasks/image_utils.py +764 -0
- dw/tasks/interpolate_frames.py +252 -0
- dw/tasks/judge.py +68 -0
- dw/tasks/model_cache.py +55 -0
- dw/tasks/pair_audio.py +268 -0
- dw/tasks/qr_code.py +19 -0
- dw/tasks/restore_faces.py +175 -0
- dw/tasks/rife_model.py +192 -0
- dw/tasks/segment.py +121 -0
- dw/tasks/select.py +111 -0
- dw/tasks/speech_generation.py +228 -0
- dw/tasks/stabilize.py +129 -0
- dw/tasks/task.py +920 -0
- dw/tasks/tensor_image.py +57 -0
- dw/tasks/text_generation.py +169 -0
- dw/tasks/text_sections.py +80 -0
- dw/tasks/upscale.py +203 -0
- dw/tasks/video_utils.py +624 -0
- dw/tasks/zoe_depth.py +71 -0
- dw/teacache.py +381 -0
- dw/teacache_models.json +99 -0
- dw/test.py +29 -0
- dw/type_helpers.py +231 -0
- dw/validate.py +68 -0
- dw/variable_constraints.py +444 -0
- dw/variables.py +443 -0
- dw/video_extensions.py +141 -0
- dw/vram_estimate.py +116 -0
- dw/worker.py +764 -0
- dw/workflow.py +2007 -0
- dw/workflow_schema.json +1346 -0
- dw/workflow_sources.py +383 -0
- dw/workflows/h3_context_ir.json +57 -0
- dw/workflows/test.json +31 -0
- dw/workspace.py +730 -0
- dw_mcp/__init__.py +6 -0
- dw_mcp/__main__.py +133 -0
- dw_mcp/assets.py +336 -0
- dw_mcp/authoring.py +114 -0
- dw_mcp/catalog.py +360 -0
- dw_mcp/client.py +486 -0
- dw_mcp/diagnose.py +371 -0
- dw_mcp/exports.py +84 -0
- dw_mcp/guides.py +35 -0
- dw_mcp/media.py +638 -0
- dw_mcp/models.py +97 -0
- dw_mcp/prompts.py +104 -0
- dw_mcp/server.py +1343 -0
- dw_mcp/workspaces.py +212 -0
dw/previous_results.py
ADDED
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from itertools import product
|
|
3
|
+
|
|
4
|
+
from .arguments import (
|
|
5
|
+
FROM_PREVIOUS_RESULT_KEY,
|
|
6
|
+
PREVIOUS_RESULT_PREFIX,
|
|
7
|
+
build_objects,
|
|
8
|
+
)
|
|
9
|
+
from .for_each import MEMBER_SEPARATOR, render_path
|
|
10
|
+
from .step_cache import reference_resolves_to
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger("dw")
|
|
13
|
+
|
|
14
|
+
# Maximum number of iterations to prevent resource exhaustion
|
|
15
|
+
MAX_ITERATIONS = 10000
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def get_iterations(argument_template, previous_results):
|
|
19
|
+
"""Generate argument combinations using previous task results.
|
|
20
|
+
|
|
21
|
+
Takes a template of arguments and expands any references to previous results
|
|
22
|
+
into all possible combinations of those results.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
argument_template: Dict or list containing argument definitions
|
|
26
|
+
previous_results: Dict of results from previously executed steps
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
List of argument dictionaries, one for each possible combination
|
|
30
|
+
"""
|
|
31
|
+
# An 'inputs' list is already one iteration per entry. The static check
|
|
32
|
+
# (previous_result_reference_errors) accepts a reference inside one, so the
|
|
33
|
+
# run must resolve it too rather than hand the task the literal string:
|
|
34
|
+
# a reference entry is one iteration per result it names, and an object
|
|
35
|
+
# entry expands exactly as an 'arguments' template would
|
|
36
|
+
if isinstance(argument_template, list):
|
|
37
|
+
iterations = []
|
|
38
|
+
for entry in argument_template:
|
|
39
|
+
if isinstance(entry, str) and entry.startswith(PREVIOUS_RESULT_PREFIX):
|
|
40
|
+
iterations.extend(
|
|
41
|
+
get_previous_results(
|
|
42
|
+
previous_results, entry[len(PREVIOUS_RESULT_PREFIX) :]
|
|
43
|
+
)
|
|
44
|
+
)
|
|
45
|
+
elif isinstance(entry, dict):
|
|
46
|
+
iterations.extend(get_iterations(entry, previous_results))
|
|
47
|
+
else:
|
|
48
|
+
iterations.append(entry)
|
|
49
|
+
if len(iterations) > MAX_ITERATIONS:
|
|
50
|
+
raise ValueError(
|
|
51
|
+
f"Too many iterations generated: more than {MAX_ITERATIONS} "
|
|
52
|
+
f"from an 'inputs' list. Consider splitting it across steps."
|
|
53
|
+
)
|
|
54
|
+
return iterations
|
|
55
|
+
|
|
56
|
+
# Find any references to previous results in the template
|
|
57
|
+
# Returns dict of {arg_key: result_reference}
|
|
58
|
+
result_refs = find_previous_result_refs(argument_template)
|
|
59
|
+
|
|
60
|
+
# If no references found, return the template as-is
|
|
61
|
+
if not result_refs:
|
|
62
|
+
logger.debug("No result references found in template")
|
|
63
|
+
# Shallow copy: realize_args may have already loaded large media
|
|
64
|
+
# (PIL images, full video frame lists) into the template, so a deep
|
|
65
|
+
# copy would multiply memory use. Contract: iteration dicts may only
|
|
66
|
+
# be mutated at the top level (key pop/assign); nested values are
|
|
67
|
+
# shared across iterations and must never be mutated in place.
|
|
68
|
+
return [dict(argument_template)]
|
|
69
|
+
|
|
70
|
+
logger.debug(f"Found {len(result_refs)} result references: {result_refs}")
|
|
71
|
+
|
|
72
|
+
# Create a dictionary mapping each reference path to its possible values
|
|
73
|
+
# Example: {('image',): [img1, img2], ('prompt',): ['text1', 'text2']}
|
|
74
|
+
ref_results = {
|
|
75
|
+
ref_path: list(get_previous_results(previous_results, ref_value))
|
|
76
|
+
for ref_path, ref_value in result_refs.items()
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
# Generate all possible combinations of argument values
|
|
80
|
+
keys = list(ref_results.keys())
|
|
81
|
+
iterations = []
|
|
82
|
+
|
|
83
|
+
# Guard the cartesian product BEFORE building it - the product's size is
|
|
84
|
+
# known from the reference counts, and checking after the loop would only
|
|
85
|
+
# report the resource exhaustion it exists to prevent
|
|
86
|
+
combinations = 1
|
|
87
|
+
for key in keys:
|
|
88
|
+
combinations *= len(ref_results[key])
|
|
89
|
+
if combinations > MAX_ITERATIONS:
|
|
90
|
+
raise ValueError(
|
|
91
|
+
f"Too many iterations generated: {combinations} exceeds maximum of {MAX_ITERATIONS}. "
|
|
92
|
+
f"This usually indicates too many previous_result references creating a cartesian product. "
|
|
93
|
+
f"Consider reducing the number of multi-value results or splitting into multiple steps."
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
# Use itertools.product to create cartesian product of all possible values
|
|
97
|
+
# Example: if ref_results has 2 images and 2 prompts, creates 4 combinations
|
|
98
|
+
for values in product(*[ref_results[k] for k in keys]):
|
|
99
|
+
# Create fresh shallow copy of template for each combination.
|
|
100
|
+
# Nested values (e.g. loaded PIL images, video frame lists) are
|
|
101
|
+
# shared across iterations, not deep-copied, to avoid multiplying
|
|
102
|
+
# media memory usage by the iteration count. Contract: iteration
|
|
103
|
+
# dicts may only be mutated at the top level (key pop/assign);
|
|
104
|
+
# nested values must never be mutated in place.
|
|
105
|
+
arguments = dict(argument_template)
|
|
106
|
+
|
|
107
|
+
# Replace each reference with its actual value
|
|
108
|
+
for path, value in zip(keys, values):
|
|
109
|
+
# Handle nested dictionary properties
|
|
110
|
+
# If value is dict and contains the key we're looking for, use that property
|
|
111
|
+
key = path[-1]
|
|
112
|
+
arguments = substitute_at_path(
|
|
113
|
+
arguments,
|
|
114
|
+
path,
|
|
115
|
+
value[key] if isinstance(value, dict) and key in value else value,
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
# Now that the media exists, build the objects that were waiting for it -
|
|
119
|
+
# a reference constructed from a step's output rather than from a file
|
|
120
|
+
iterations.append(build_objects(arguments))
|
|
121
|
+
|
|
122
|
+
logger.debug(f"Generated {len(iterations)} argument combinations")
|
|
123
|
+
return iterations
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def get_previous_results(previous_results, previous_result_name):
|
|
127
|
+
"""Retrieve results or specific properties from previous tasks.
|
|
128
|
+
|
|
129
|
+
Args:
|
|
130
|
+
previous_results: Dict of results from previous steps
|
|
131
|
+
previous_result_name: String identifying the result, optionally with property
|
|
132
|
+
Format: "step_name" or "step_name.property_name"
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
List of results or specific properties from the referenced step
|
|
136
|
+
"""
|
|
137
|
+
# Step names are unrestricted strings and may themselves contain dots
|
|
138
|
+
# (e.g. "v1.0"), so resolve against the known step names rather than
|
|
139
|
+
# blindly splitting on the first/only ".".
|
|
140
|
+
|
|
141
|
+
# Exact match: the whole reference is a known step name, no property.
|
|
142
|
+
if previous_result_name in previous_results:
|
|
143
|
+
logger.debug(f"Getting all artifacts from result {previous_result_name}")
|
|
144
|
+
return previous_results[previous_result_name].get_artifacts()
|
|
145
|
+
|
|
146
|
+
if "." not in previous_result_name:
|
|
147
|
+
raise _not_found(previous_results, previous_result_name)
|
|
148
|
+
|
|
149
|
+
# Find the longest known step name the reference resolves to, and treat
|
|
150
|
+
# the remainder as the property name. The exact-match case returned
|
|
151
|
+
# above, so every name reaching here is a strict '<name>.' prefix.
|
|
152
|
+
result_name = max(
|
|
153
|
+
(
|
|
154
|
+
name
|
|
155
|
+
for name in previous_results
|
|
156
|
+
if reference_resolves_to(previous_result_name, name)
|
|
157
|
+
),
|
|
158
|
+
key=len,
|
|
159
|
+
default=None,
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
if result_name is None:
|
|
163
|
+
raise _not_found(previous_results, previous_result_name)
|
|
164
|
+
|
|
165
|
+
property_name = previous_result_name[len(result_name) + 1 :]
|
|
166
|
+
logger.debug(f"Getting property {property_name} from result {result_name}")
|
|
167
|
+
return previous_results[result_name].get_artifact_properties(property_name)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def resolve_chain_prompts(step_action, previous_results):
|
|
171
|
+
"""Resolve a pipeline chain's per-segment prompts against previous results.
|
|
172
|
+
|
|
173
|
+
A chain's "prompts" list is not part of the step's argument template, so the
|
|
174
|
+
cartesian pass that expands "previous_result:" everywhere else never reaches
|
|
175
|
+
it. That matters for a chain whose opening segment is written by a different
|
|
176
|
+
step from the ones that continue it - a continuation prompt declares a video
|
|
177
|
+
reference the first segment does not have.
|
|
178
|
+
|
|
179
|
+
Each entry resolves independently and yields one prompt, so this never
|
|
180
|
+
multiplies iterations the way an argument reference does; a reference that
|
|
181
|
+
produced several artifacts uses the first.
|
|
182
|
+
|
|
183
|
+
The resolved list is left on the step action for run_chain to pick up, and
|
|
184
|
+
nothing happens at all for a pipeline without chain prompts.
|
|
185
|
+
"""
|
|
186
|
+
definition = getattr(step_action, "pipeline_definition", None)
|
|
187
|
+
if not isinstance(definition, dict):
|
|
188
|
+
return
|
|
189
|
+
|
|
190
|
+
chain = definition.get("chain", None) or {}
|
|
191
|
+
prompts = chain.get("prompts", None)
|
|
192
|
+
if not prompts:
|
|
193
|
+
return
|
|
194
|
+
|
|
195
|
+
resolved = []
|
|
196
|
+
for entry in prompts:
|
|
197
|
+
if isinstance(entry, str) and entry.startswith("previous_result:"):
|
|
198
|
+
artifacts = get_previous_results(
|
|
199
|
+
previous_results, entry.removeprefix("previous_result:")
|
|
200
|
+
)
|
|
201
|
+
if not artifacts:
|
|
202
|
+
raise ValueError(f"Chain prompt reference '{entry}' produced no result")
|
|
203
|
+
if len(artifacts) > 1:
|
|
204
|
+
logger.warning(
|
|
205
|
+
f"Chain prompt reference '{entry}' produced {len(artifacts)} "
|
|
206
|
+
f"results - using the first"
|
|
207
|
+
)
|
|
208
|
+
entry = artifacts[0]
|
|
209
|
+
resolved.append(entry)
|
|
210
|
+
|
|
211
|
+
step_action.chain_prompts = resolved
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def find_previous_result_refs(arguments):
|
|
215
|
+
"""Find all values in an argument structure that reference previous results.
|
|
216
|
+
|
|
217
|
+
A reference is written either as a value with the "previous_result:" prefix, or
|
|
218
|
+
as the step name a 'from_previous_result' object description is built from. Both
|
|
219
|
+
are found at any depth: an argument that takes a constructed object holds it
|
|
220
|
+
inside a list - MiniMax-H3's 'references' - so the reference is nested rather
|
|
221
|
+
than sitting at the top of the arguments.
|
|
222
|
+
|
|
223
|
+
Args:
|
|
224
|
+
arguments: Dictionary of argument definitions
|
|
225
|
+
|
|
226
|
+
Returns:
|
|
227
|
+
Dict mapping the path of each reference to the result name it names. A path
|
|
228
|
+
is the tuple of keys and list indices that reaches the value, so a top-level
|
|
229
|
+
{'image': 'previous_result:step1'} comes back as {('image',): 'step1'}
|
|
230
|
+
"""
|
|
231
|
+
found = {}
|
|
232
|
+
_collect_refs(arguments, (), found)
|
|
233
|
+
return found
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def _collect_refs(value, path, found):
|
|
237
|
+
"""Walk an argument structure, collecting every reference by its path."""
|
|
238
|
+
if isinstance(value, dict):
|
|
239
|
+
for key, item in value.items():
|
|
240
|
+
# The object description names its step bare, the way it would name a
|
|
241
|
+
# file - the prefix would only repeat what the key already says
|
|
242
|
+
if key == FROM_PREVIOUS_RESULT_KEY and isinstance(item, str):
|
|
243
|
+
found[path + (key,)] = item
|
|
244
|
+
else:
|
|
245
|
+
_collect_refs(item, path + (key,), found)
|
|
246
|
+
|
|
247
|
+
elif isinstance(value, list):
|
|
248
|
+
for index, item in enumerate(value):
|
|
249
|
+
_collect_refs(item, path + (index,), found)
|
|
250
|
+
|
|
251
|
+
elif isinstance(value, str) and value.startswith(PREVIOUS_RESULT_PREFIX):
|
|
252
|
+
found[path] = value[len(PREVIOUS_RESULT_PREFIX) :]
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def substitute_at_path(container, path, value):
|
|
256
|
+
"""A copy of container with value placed at path.
|
|
257
|
+
|
|
258
|
+
Only the containers along the path are copied. Everything beside them stays
|
|
259
|
+
shared, which is the same contract the top-level copy keeps: iterations share
|
|
260
|
+
their nested values, so a substitution deep in one must not be visible in the
|
|
261
|
+
others.
|
|
262
|
+
|
|
263
|
+
Args:
|
|
264
|
+
container: The dict or list to substitute into
|
|
265
|
+
path: Tuple of keys and indices reaching the value to replace
|
|
266
|
+
value: What to put there
|
|
267
|
+
|
|
268
|
+
Returns:
|
|
269
|
+
The copied container
|
|
270
|
+
"""
|
|
271
|
+
key = path[0]
|
|
272
|
+
replacement = (
|
|
273
|
+
value if len(path) == 1 else substitute_at_path(container[key], path[1:], value)
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
if isinstance(container, list):
|
|
277
|
+
copied = list(container)
|
|
278
|
+
copied[key] = replacement
|
|
279
|
+
return copied
|
|
280
|
+
|
|
281
|
+
copied = dict(container)
|
|
282
|
+
copied[key] = replacement
|
|
283
|
+
return copied
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
class StepResults(dict):
|
|
287
|
+
"""The run's results, which also remembers every step that produced one.
|
|
288
|
+
|
|
289
|
+
`release_unreferenced_results` deletes a result the moment no remaining
|
|
290
|
+
step references it, so by the time a misspelled reference fails, the
|
|
291
|
+
steps that ran are gone from the dict and the error printed
|
|
292
|
+
'Available results: []' on a run where several steps had completed
|
|
293
|
+
(T015). Keeping the names - not the results, which is the whole point of
|
|
294
|
+
releasing them - costs nothing and is the one thing that diagnostic
|
|
295
|
+
needed.
|
|
296
|
+
"""
|
|
297
|
+
|
|
298
|
+
def __init__(self, *args, **kwargs):
|
|
299
|
+
super().__init__(*args, **kwargs)
|
|
300
|
+
self.completed_steps = list(self.keys())
|
|
301
|
+
|
|
302
|
+
def __setitem__(self, key, value):
|
|
303
|
+
if key not in self.completed_steps:
|
|
304
|
+
self.completed_steps.append(key)
|
|
305
|
+
super().__setitem__(key, value)
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
def _not_found(previous_results, previous_result_name):
|
|
309
|
+
"""The error a reference that names nothing raises.
|
|
310
|
+
|
|
311
|
+
Names what is there as well as what was asked for: the gap between them
|
|
312
|
+
is the fix, and on a long run it is the only thing standing between a
|
|
313
|
+
typo and another 40 minutes of GPU.
|
|
314
|
+
"""
|
|
315
|
+
message = (
|
|
316
|
+
f"Previous result '{previous_result_name}' not found. "
|
|
317
|
+
f"Available results: {list(previous_results.keys())}"
|
|
318
|
+
)
|
|
319
|
+
released = [
|
|
320
|
+
name
|
|
321
|
+
for name in getattr(previous_results, "completed_steps", ())
|
|
322
|
+
if name not in previous_results
|
|
323
|
+
]
|
|
324
|
+
if released:
|
|
325
|
+
message += (
|
|
326
|
+
f". Earlier steps that ran: {released} - their results were "
|
|
327
|
+
f"released because no remaining step references them"
|
|
328
|
+
)
|
|
329
|
+
return KeyError(message)
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
def previous_result_reference_errors(workflow_definition, source_indices=None):
|
|
333
|
+
"""Every 'previous_result:' reference that names no earlier step.
|
|
334
|
+
|
|
335
|
+
References resolve lazily, one step at a time, so a reference naming a
|
|
336
|
+
step that does not exist is only discovered when execution reaches it -
|
|
337
|
+
after everything before it has run. On a workflow whose steps are
|
|
338
|
+
generation steps that is 40 minutes of GPU spent to learn about a typo
|
|
339
|
+
a read of the file would have caught (T005). The names are all in the
|
|
340
|
+
definition, so this is answerable before anything runs.
|
|
341
|
+
|
|
342
|
+
The definition handed here has already been substituted and expanded,
|
|
343
|
+
so every reference in it is literal; a 'variable:' still spelled out is
|
|
344
|
+
one nothing resolved and is left alone.
|
|
345
|
+
|
|
346
|
+
`source_indices`, when given, is the source step index of each step -
|
|
347
|
+
expansion of a 'for_each' group turns one written step into several, and
|
|
348
|
+
the path an error carries has to be one the author can find in the file
|
|
349
|
+
they wrote.
|
|
350
|
+
"""
|
|
351
|
+
steps = workflow_definition.get("steps")
|
|
352
|
+
if not isinstance(steps, list):
|
|
353
|
+
return []
|
|
354
|
+
|
|
355
|
+
errors = []
|
|
356
|
+
seen = []
|
|
357
|
+
for index, step in enumerate(steps):
|
|
358
|
+
if not isinstance(step, dict):
|
|
359
|
+
continue
|
|
360
|
+
name = step.get("name")
|
|
361
|
+
found = {}
|
|
362
|
+
_collect_reference_paths(step, (), found)
|
|
363
|
+
for path, reference in sorted(found.items(), key=lambda item: str(item[0])):
|
|
364
|
+
if any(reference_resolves_to(reference, name) for name in seen):
|
|
365
|
+
continue
|
|
366
|
+
source = (
|
|
367
|
+
source_indices[index]
|
|
368
|
+
if source_indices is not None and index < len(source_indices)
|
|
369
|
+
else index
|
|
370
|
+
)
|
|
371
|
+
location = render_path(("steps", source) + path)
|
|
372
|
+
# Which expansion it was: the source path alone points at the one
|
|
373
|
+
# step the author wrote, and every member reports the same path
|
|
374
|
+
where = (
|
|
375
|
+
f" in member '{name}'"
|
|
376
|
+
if isinstance(name, str) and MEMBER_SEPARATOR in name
|
|
377
|
+
else ""
|
|
378
|
+
)
|
|
379
|
+
errors.append(
|
|
380
|
+
{
|
|
381
|
+
"path": location,
|
|
382
|
+
"message": (
|
|
383
|
+
f"previous_result '{reference}'{where} names no earlier "
|
|
384
|
+
f"step. Steps available here: {seen}"
|
|
385
|
+
),
|
|
386
|
+
}
|
|
387
|
+
)
|
|
388
|
+
if isinstance(name, str):
|
|
389
|
+
seen.append(name)
|
|
390
|
+
return errors
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
def _collect_reference_paths(value, path, found):
|
|
394
|
+
"""Every literal previous-result reference under `value`, by JSON path.
|
|
395
|
+
|
|
396
|
+
Both spellings: the 'previous_result:' prefix on a string, and the
|
|
397
|
+
'from_previous_result' key of a constructed object, which names a step
|
|
398
|
+
without the prefix.
|
|
399
|
+
"""
|
|
400
|
+
if isinstance(value, dict):
|
|
401
|
+
for key, item in value.items():
|
|
402
|
+
if key == FROM_PREVIOUS_RESULT_KEY and isinstance(item, str):
|
|
403
|
+
if not item.startswith("variable:"):
|
|
404
|
+
found[path + (key,)] = item
|
|
405
|
+
continue
|
|
406
|
+
_collect_reference_paths(item, path + (key,), found)
|
|
407
|
+
elif isinstance(value, list):
|
|
408
|
+
for index, item in enumerate(value):
|
|
409
|
+
_collect_reference_paths(item, path + (index,), found)
|
|
410
|
+
elif isinstance(value, str) and value.startswith(PREVIOUS_RESULT_PREFIX):
|
|
411
|
+
found[path] = value[len(PREVIOUS_RESULT_PREFIX) :]
|
dw/probe_paths.py
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""The local file a validate-time media probe may read, or None.
|
|
2
|
+
|
|
3
|
+
`dissolve_frame_errors` (#400) and `slice_preflight` (#402) each probe an
|
|
4
|
+
input's real length before the run, and each echoes what the probe found.
|
|
5
|
+
A free `validate_workflow` must not answer questions about files the run
|
|
6
|
+
itself would refuse to read, so a literal path goes through the same
|
|
7
|
+
containment policy the run applies (`validate_media_path`, lifted under
|
|
8
|
+
--trust-workflows) and is resolved against the workflow's directory; one it
|
|
9
|
+
refuses defers to the run, and `location_errors` reports the refusal.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import os
|
|
13
|
+
|
|
14
|
+
from .assets import fetch_asset, is_asset_reference
|
|
15
|
+
from .locations import is_http_url, validate_media_path
|
|
16
|
+
from .runs import fetch_output, is_output_reference
|
|
17
|
+
|
|
18
|
+
# Left to the run-time check: not yet resolved to a real file at the point
|
|
19
|
+
# validation walks the expanded definition.
|
|
20
|
+
UNRESOLVED_PREFIXES = ("previous_result:", "variable:", "item:", "gather:")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def resolve_probe_path(value, base_dir, what="a media argument"):
|
|
24
|
+
"""The local file `value` names, or None when it is not yet resolvable,
|
|
25
|
+
is not a local file, does not exist, or lies outside what this workflow
|
|
26
|
+
may read - any of which defers the check to the run.
|
|
27
|
+
|
|
28
|
+
An `asset:`/`output:` reference resolves through its own confined
|
|
29
|
+
resolver; a literal path is resolved against `base_dir` and must pass
|
|
30
|
+
`validate_media_path`.
|
|
31
|
+
"""
|
|
32
|
+
if not isinstance(value, str) or not value:
|
|
33
|
+
return None
|
|
34
|
+
if value.startswith(UNRESOLVED_PREFIXES) or is_http_url(value):
|
|
35
|
+
return None
|
|
36
|
+
if is_asset_reference(value) or is_output_reference(value):
|
|
37
|
+
try:
|
|
38
|
+
value = (
|
|
39
|
+
fetch_asset(value, base_dir=base_dir)
|
|
40
|
+
if is_asset_reference(value)
|
|
41
|
+
else fetch_output(value)
|
|
42
|
+
)
|
|
43
|
+
except Exception:
|
|
44
|
+
# Existence/traversal problems belong to reference_name_errors
|
|
45
|
+
# and reference resolution at run time, not to this check
|
|
46
|
+
return None
|
|
47
|
+
if not isinstance(value, str):
|
|
48
|
+
return None
|
|
49
|
+
else:
|
|
50
|
+
try:
|
|
51
|
+
value = validate_media_path(value, base_dir, what, require_exists=False)
|
|
52
|
+
except Exception:
|
|
53
|
+
# A refusal is location_errors' to report; echoing anything
|
|
54
|
+
# about the file here would answer what the run will not read
|
|
55
|
+
return None
|
|
56
|
+
return value if os.path.isfile(value) else None
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
__all__ = ["UNRESOLVED_PREFIXES", "resolve_probe_path"]
|
dw/prompt_schema.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "prompt_schema.json",
|
|
4
|
+
"title": "Stored prompt",
|
|
5
|
+
"description": "One prompt in the prompt library - the text a workflow loads with a 'prompt:' reference, plus the metadata the library pages show.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"required": ["text"],
|
|
8
|
+
"additionalProperties": false,
|
|
9
|
+
"properties": {
|
|
10
|
+
"text": {
|
|
11
|
+
"type": "string",
|
|
12
|
+
"minLength": 1,
|
|
13
|
+
"description": "The prompt text itself - what a 'prompt:' reference resolves to. May not begin with a reference prefix ('previous_result:', 'variable:', 'constant:', 'prompt:')."
|
|
14
|
+
},
|
|
15
|
+
"description": {
|
|
16
|
+
"type": "string",
|
|
17
|
+
"description": "Short summary shown on the prompt's library card."
|
|
18
|
+
},
|
|
19
|
+
"intended_model": {
|
|
20
|
+
"type": "string",
|
|
21
|
+
"description": "The model or model family the prompt was written for, such as 'minimax-h3'. Informational - the engine ignores it - but one spelling per family: 'list_prompts' narrows by an exact match, so a variant hides the prompt from its own family. The repo's library is swept by tests/test_prompt_library.py. Leave it out for text written for no particular model."
|
|
22
|
+
},
|
|
23
|
+
"negative_prompt": {
|
|
24
|
+
"type": "string",
|
|
25
|
+
"description": "A negative prompt paired with the text, for models that take one."
|
|
26
|
+
},
|
|
27
|
+
"tags": {
|
|
28
|
+
"type": "array",
|
|
29
|
+
"items": { "type": "string" },
|
|
30
|
+
"description": "Free-form labels for filtering the library."
|
|
31
|
+
},
|
|
32
|
+
"enhanced": {
|
|
33
|
+
"type": "object",
|
|
34
|
+
"additionalProperties": false,
|
|
35
|
+
"description": "Provenance when the text came from the AI enhancer - which model expanded which idea.",
|
|
36
|
+
"properties": {
|
|
37
|
+
"model": {
|
|
38
|
+
"type": "string",
|
|
39
|
+
"description": "The LLM that generated the text."
|
|
40
|
+
},
|
|
41
|
+
"idea": {
|
|
42
|
+
"type": "string",
|
|
43
|
+
"description": "The idea the text was expanded from."
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|