842 lines
31 KiB
Python
842 lines
31 KiB
Python
"""Remote component payload construction and proxy prompt rewriting."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import copy
|
|
import logging
|
|
from dataclasses import dataclass
|
|
from typing import Any, Mapping
|
|
|
|
if __package__:
|
|
from .component_planning import _proxy_boundary_output_is_list
|
|
from .modal_executor_node import (
|
|
MODAL_ARTIFACT_FINALIZER_MAX_COMPONENTS,
|
|
MODAL_ARTIFACT_FINALIZER_NODE_ID,
|
|
MODAL_COMPONENT_COMPLETION_OUTPUT_NAME,
|
|
MODAL_LOCAL_BRIDGE_MATERIALIZER_NODE_ID,
|
|
ensure_modal_artifact_finalizer_registered,
|
|
ensure_modal_component_proxy_node_registered,
|
|
ensure_modal_local_bridge_materializer_registered,
|
|
register_cache_friendly_proxy_payload,
|
|
register_modal_map_input_warmup_context,
|
|
)
|
|
from .prompt_payload_building import _build_component_payload
|
|
from .prompt_affinity_planning import (
|
|
_SPECULATIVE_PREWARM_PAYLOAD_FIELDS,
|
|
_SPECULATIVE_PREWARM_TARGET_KEY,
|
|
_component_descendant_distances,
|
|
_configure_local_gap_keepalive_payloads,
|
|
_configure_speculative_affinity_prewarm_payloads,
|
|
_nearest_downstream_remote_component_ids,
|
|
_parallelize_non_returning_local_branches,
|
|
_remote_proxy_dependency_edges,
|
|
_remote_proxy_payload,
|
|
_speculative_prewarm_target_payload,
|
|
)
|
|
from .remote_graph_analysis import (
|
|
_is_link,
|
|
_iter_payload_input_strings,
|
|
_normalize_output_metadata,
|
|
)
|
|
from .remote_plan_types import (
|
|
BoundaryOutputSpec,
|
|
LinkedOutputRef,
|
|
ModalPromptValidationError,
|
|
RemoteComponentPlan,
|
|
)
|
|
from .sync_engine import AssetSyncRequestCache, ModalAssetSyncEngine, SyncedAsset
|
|
else: # pragma: no cover - flat import inside the Modal container.
|
|
from component_planning import _proxy_boundary_output_is_list
|
|
from modal_executor_node import (
|
|
MODAL_ARTIFACT_FINALIZER_MAX_COMPONENTS,
|
|
MODAL_ARTIFACT_FINALIZER_NODE_ID,
|
|
MODAL_COMPONENT_COMPLETION_OUTPUT_NAME,
|
|
MODAL_LOCAL_BRIDGE_MATERIALIZER_NODE_ID,
|
|
ensure_modal_artifact_finalizer_registered,
|
|
ensure_modal_component_proxy_node_registered,
|
|
ensure_modal_local_bridge_materializer_registered,
|
|
register_cache_friendly_proxy_payload,
|
|
register_modal_map_input_warmup_context,
|
|
)
|
|
from prompt_payload_building import _build_component_payload
|
|
from prompt_affinity_planning import (
|
|
_SPECULATIVE_PREWARM_PAYLOAD_FIELDS,
|
|
_SPECULATIVE_PREWARM_TARGET_KEY,
|
|
_component_descendant_distances,
|
|
_configure_local_gap_keepalive_payloads,
|
|
_configure_speculative_affinity_prewarm_payloads,
|
|
_nearest_downstream_remote_component_ids,
|
|
_parallelize_non_returning_local_branches,
|
|
_remote_proxy_dependency_edges,
|
|
_remote_proxy_payload,
|
|
_speculative_prewarm_target_payload,
|
|
)
|
|
from remote_graph_analysis import (
|
|
_is_link,
|
|
_iter_payload_input_strings,
|
|
_normalize_output_metadata,
|
|
)
|
|
from remote_plan_types import (
|
|
BoundaryOutputSpec,
|
|
LinkedOutputRef,
|
|
ModalPromptValidationError,
|
|
RemoteComponentPlan,
|
|
)
|
|
from sync_engine import AssetSyncRequestCache, ModalAssetSyncEngine, SyncedAsset
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
def _sync_component_prompt_inputs(
|
|
component: RemoteComponentPlan,
|
|
rewritten_prompt: dict[str, Any],
|
|
sync_engine: ModalAssetSyncEngine,
|
|
request_cache: AssetSyncRequestCache,
|
|
status_callback: Any | None = None,
|
|
) -> tuple[dict[str, Any], list[SyncedAsset]]:
|
|
"""Build a synced prompt payload for one remote component."""
|
|
component_prompt: dict[str, Any] = {}
|
|
synced_assets: list[SyncedAsset] = []
|
|
logger.info(
|
|
"Syncing prompt inputs for remote component %s with %d nodes.",
|
|
component.representative_node_id,
|
|
len(component.node_ids),
|
|
)
|
|
for node_id in component.node_ids:
|
|
prompt_node = rewritten_prompt[node_id]
|
|
synced_inputs, node_assets = sync_engine.sync_prompt_inputs(
|
|
copy.deepcopy(prompt_node.get("inputs", {})),
|
|
status_callback=status_callback,
|
|
request_cache=request_cache,
|
|
)
|
|
synced_assets.extend(node_assets)
|
|
component_prompt[node_id] = {
|
|
"class_type": str(prompt_node["class_type"]),
|
|
"inputs": synced_inputs,
|
|
"_meta": copy.deepcopy(prompt_node.get("_meta", {})),
|
|
}
|
|
logger.info(
|
|
"Prepared remote node %s (%s) with %d synced assets.",
|
|
node_id,
|
|
component_prompt[node_id]["class_type"],
|
|
len(node_assets),
|
|
)
|
|
logger.info(
|
|
"Finished syncing remote component %s with %d total synced assets.",
|
|
component.representative_node_id,
|
|
len(synced_assets),
|
|
)
|
|
return component_prompt, synced_assets
|
|
|
|
|
|
def _deduplicate_synced_assets(synced_assets: list[SyncedAsset]) -> list[SyncedAsset]:
|
|
"""Return one request summary record per content-addressed remote path."""
|
|
unique_assets_by_remote_path: dict[str, SyncedAsset] = {}
|
|
for synced_asset in synced_assets:
|
|
unique_assets_by_remote_path.setdefault(synced_asset.remote_path, synced_asset)
|
|
return list(unique_assets_by_remote_path.values())
|
|
|
|
|
|
def _component_uploaded_volume_paths(
|
|
*,
|
|
component_prompt: dict[str, Any],
|
|
synced_assets: list[SyncedAsset],
|
|
custom_nodes_bundle: SyncedAsset | None,
|
|
) -> list[str]:
|
|
"""Return newly uploaded mounted-volume paths that this component can actually reference."""
|
|
referenced_paths: set[str] = set()
|
|
|
|
for prompt_node in component_prompt.values():
|
|
if not isinstance(prompt_node, dict):
|
|
continue
|
|
inputs = prompt_node.get("inputs", {})
|
|
if not isinstance(inputs, dict):
|
|
continue
|
|
for input_value in inputs.values():
|
|
for candidate_path in _iter_payload_input_strings(input_value):
|
|
if isinstance(candidate_path, str) and candidate_path.startswith("/"):
|
|
referenced_paths.add(candidate_path)
|
|
|
|
uploaded_paths = {
|
|
asset.remote_path
|
|
for asset in synced_assets
|
|
if asset.uploaded and asset.remote_path in referenced_paths
|
|
}
|
|
if custom_nodes_bundle is not None and custom_nodes_bundle.uploaded:
|
|
uploaded_paths.add(custom_nodes_bundle.remote_path)
|
|
return sorted(uploaded_paths)
|
|
|
|
|
|
def _contains_output_node(
|
|
node_ids: list[str],
|
|
*,
|
|
rewritten_prompt: dict[str, Any],
|
|
nodes_module: Any,
|
|
) -> bool:
|
|
"""Return whether one node subset contains an output node."""
|
|
for node_id in node_ids:
|
|
prompt_node = rewritten_prompt.get(node_id)
|
|
if prompt_node is None:
|
|
continue
|
|
node_class = nodes_module.NODE_CLASS_MAPPINGS.get(
|
|
str(prompt_node["class_type"])
|
|
)
|
|
if node_class is not None and getattr(node_class, "OUTPUT_NODE", False):
|
|
return True
|
|
return False
|
|
|
|
|
|
def _proxy_inputs_for_boundary_inputs(
|
|
boundary_inputs: list[dict[str, Any]],
|
|
*,
|
|
rewritten_prompt: dict[str, Any],
|
|
) -> dict[str, Any]:
|
|
"""Resolve one proxy input mapping from the current prompt graph."""
|
|
proxy_inputs: dict[str, Any] = {}
|
|
for boundary_input in boundary_inputs:
|
|
current_input_value: Any = None
|
|
for target in boundary_input.get("targets", []):
|
|
target_prompt_node = rewritten_prompt.get(str(target["node_id"]))
|
|
if target_prompt_node is None:
|
|
continue
|
|
target_input_value = (target_prompt_node.get("inputs") or {}).get(
|
|
str(target["input_name"])
|
|
)
|
|
if _is_link(target_input_value):
|
|
current_input_value = list(target_input_value)
|
|
break
|
|
if current_input_value is None:
|
|
raise ModalPromptValidationError(
|
|
"Unable to resolve proxy boundary input wiring while rewriting split Modal proxies."
|
|
)
|
|
proxy_inputs[str(boundary_input["proxy_input_name"])] = current_input_value
|
|
return proxy_inputs
|
|
|
|
|
|
def _register_proxy_node(
|
|
*,
|
|
prompt_node_id: str,
|
|
payload_mapping: dict[str, Any],
|
|
proxy_inputs: dict[str, Any],
|
|
meta: dict[str, Any],
|
|
is_output_node: bool,
|
|
rewritten_prompt: dict[str, Any],
|
|
nodes_module: Any,
|
|
) -> None:
|
|
"""Insert one dynamic proxy node into the rewritten prompt."""
|
|
boundary_outputs = list(payload_mapping.get("boundary_outputs", []))
|
|
proxy_node_id = ensure_modal_component_proxy_node_registered(
|
|
output_types=tuple(str(output["io_type"]) for output in boundary_outputs),
|
|
output_names=tuple(
|
|
str(output["proxy_output_name"]) for output in boundary_outputs
|
|
),
|
|
output_is_list=tuple(
|
|
_proxy_boundary_output_is_list(output) for output in boundary_outputs
|
|
),
|
|
nodes_module=nodes_module,
|
|
is_output_node=is_output_node,
|
|
include_completion_output=True,
|
|
)
|
|
proxy_inputs["original_node_data"] = register_cache_friendly_proxy_payload(
|
|
prompt_node_id,
|
|
payload_mapping,
|
|
)
|
|
rewritten_prompt[prompt_node_id] = {
|
|
"class_type": proxy_node_id,
|
|
"inputs": proxy_inputs,
|
|
"_meta": copy.deepcopy(meta),
|
|
}
|
|
|
|
|
|
@dataclass
|
|
class _ProxyReplacementMaps:
|
|
"""Collect consumer-specific and default proxy output rewrites."""
|
|
|
|
materializer_by_consumer: dict[tuple[str, LinkedOutputRef], list[Any]]
|
|
session_by_consumer: dict[tuple[str, LinkedOutputRef], list[Any]]
|
|
default_by_source: dict[LinkedOutputRef, list[Any]]
|
|
|
|
|
|
@dataclass
|
|
class _OrderedProxyRewriteState:
|
|
"""Collect intermediate state while preparing ordered phase proxies."""
|
|
|
|
phase_payloads: list[dict[str, Any]]
|
|
proxy_node_ids: list[str]
|
|
proxy_inputs_by_node_id: dict[str, dict[str, Any]]
|
|
proxy_meta_by_node_id: dict[str, dict[str, Any]]
|
|
replacements: _ProxyReplacementMaps
|
|
materializer_proxy_outputs: dict[str, list[Any]]
|
|
|
|
|
|
def _empty_proxy_replacement_maps() -> _ProxyReplacementMaps:
|
|
"""Return empty output replacement maps for one component rewrite."""
|
|
return _ProxyReplacementMaps(
|
|
materializer_by_consumer={},
|
|
session_by_consumer={},
|
|
default_by_source={},
|
|
)
|
|
|
|
|
|
def _linked_output_ref(payload: Mapping[str, Any]) -> LinkedOutputRef:
|
|
"""Return the typed source reference encoded in one boundary payload."""
|
|
return LinkedOutputRef(
|
|
node_id=str(payload["node_id"]),
|
|
output_index=int(payload["output_index"]),
|
|
)
|
|
|
|
|
|
def _unique_proxy_node_id(
|
|
candidate: str,
|
|
*,
|
|
rewritten_prompt: dict[str, Any],
|
|
component_node_ids: set[str],
|
|
) -> str:
|
|
"""Return a proxy id that does not overwrite an unrelated prompt node."""
|
|
while candidate in rewritten_prompt and candidate not in component_node_ids:
|
|
candidate = f"{candidate}_proxy"
|
|
return candidate
|
|
|
|
|
|
def _install_local_bridge_materializer(
|
|
rewritten_prompt: dict[str, Any],
|
|
materializer_node_id: str,
|
|
proxy_output: list[Any],
|
|
) -> None:
|
|
"""Insert one local bridge materializer wired to a proxy output."""
|
|
rewritten_prompt[materializer_node_id] = {
|
|
"class_type": MODAL_LOCAL_BRIDGE_MATERIALIZER_NODE_ID,
|
|
"inputs": {"bridge_ref": list(proxy_output)},
|
|
"_meta": {"title": "Modal Local Bridge Materializer"},
|
|
}
|
|
|
|
|
|
def _replacement_output(
|
|
replacements: _ProxyReplacementMaps,
|
|
*,
|
|
consumer_node_id: str,
|
|
source: LinkedOutputRef,
|
|
) -> list[Any] | None:
|
|
"""Resolve the most specific proxy output for one surviving consumer."""
|
|
replacement = replacements.materializer_by_consumer.get(
|
|
(consumer_node_id, source)
|
|
)
|
|
if replacement is None:
|
|
replacement = replacements.session_by_consumer.get(
|
|
(consumer_node_id, source)
|
|
)
|
|
if replacement is None:
|
|
replacement = replacements.default_by_source.get(source)
|
|
return replacement
|
|
|
|
|
|
def _rewire_proxy_consumers(
|
|
rewritten_prompt: dict[str, Any],
|
|
*,
|
|
skip_node_ids: set[str],
|
|
replacements: _ProxyReplacementMaps,
|
|
) -> None:
|
|
"""Rewrite surviving prompt links to the selected proxy outputs."""
|
|
for node_id, prompt_node in list(rewritten_prompt.items()):
|
|
if node_id in skip_node_ids:
|
|
continue
|
|
for input_name, input_value in list((prompt_node.get("inputs") or {}).items()):
|
|
if not _is_link(input_value):
|
|
continue
|
|
source = LinkedOutputRef(
|
|
node_id=str(input_value[0]), output_index=int(input_value[1])
|
|
)
|
|
replacement = _replacement_output(
|
|
replacements,
|
|
consumer_node_id=node_id,
|
|
source=source,
|
|
)
|
|
if replacement is not None:
|
|
prompt_node["inputs"][input_name] = list(replacement)
|
|
|
|
|
|
def _record_boundary_output_routes(
|
|
spec: BoundaryOutputSpec,
|
|
proxy_output: list[Any],
|
|
replacements: _ProxyReplacementMaps,
|
|
materializer_proxy_outputs: dict[str, list[Any]],
|
|
) -> None:
|
|
"""Record session and local-materializer routes for one boundary output."""
|
|
for consumer_node_id in spec.session_consumer_node_ids:
|
|
replacements.session_by_consumer[(consumer_node_id, spec.source)] = (
|
|
proxy_output
|
|
)
|
|
materializer_node_id = spec.local_materializer_node_id
|
|
if materializer_node_id is None:
|
|
return
|
|
materializer_proxy_outputs[materializer_node_id] = proxy_output
|
|
for consumer_node_id in spec.local_materializer_consumer_node_ids:
|
|
replacements.materializer_by_consumer[(consumer_node_id, spec.source)] = [
|
|
materializer_node_id,
|
|
0,
|
|
]
|
|
|
|
|
|
def _assign_ordered_phase_proxy_ids(
|
|
state: _OrderedProxyRewriteState,
|
|
*,
|
|
rewritten_prompt: dict[str, Any],
|
|
component_node_ids: set[str],
|
|
) -> None:
|
|
"""Assign collision-free proxy ids to every ordered phase payload."""
|
|
for phase_payload in state.phase_payloads:
|
|
proxy_node_id = _unique_proxy_node_id(
|
|
str(phase_payload["component_id"]),
|
|
rewritten_prompt=rewritten_prompt,
|
|
component_node_ids=component_node_ids,
|
|
)
|
|
phase_payload["component_id"] = proxy_node_id
|
|
state.proxy_node_ids.append(proxy_node_id)
|
|
|
|
|
|
def _prepare_ordered_phase_proxy(
|
|
state: _OrderedProxyRewriteState,
|
|
phase_payload: dict[str, Any],
|
|
*,
|
|
rewritten_prompt: dict[str, Any],
|
|
boundary_specs_by_source: dict[LinkedOutputRef, BoundaryOutputSpec],
|
|
produced_outputs_by_name: dict[str, list[Any]],
|
|
) -> None:
|
|
"""Prepare inputs, metadata, and output routes for one ordered phase."""
|
|
proxy_node_id = str(phase_payload["component_id"])
|
|
proxy_inputs = _proxy_inputs_for_boundary_inputs(
|
|
list(phase_payload["boundary_inputs"]),
|
|
rewritten_prompt=rewritten_prompt,
|
|
)
|
|
for boundary_input in phase_payload.get("boundary_inputs", []):
|
|
input_name = str(boundary_input["proxy_input_name"])
|
|
produced_output = produced_outputs_by_name.get(input_name)
|
|
if produced_output is not None:
|
|
proxy_inputs[input_name] = list(produced_output)
|
|
first_node_id = str(phase_payload["component_node_ids"][0])
|
|
state.proxy_inputs_by_node_id[proxy_node_id] = proxy_inputs
|
|
state.proxy_meta_by_node_id[proxy_node_id] = copy.deepcopy(
|
|
rewritten_prompt[first_node_id].get("_meta", {})
|
|
)
|
|
for output_index, boundary_output in enumerate(
|
|
phase_payload.get("boundary_outputs", [])
|
|
):
|
|
proxy_output = [proxy_node_id, output_index]
|
|
produced_outputs_by_name[
|
|
str(boundary_output["proxy_output_name"])
|
|
] = proxy_output
|
|
source = _linked_output_ref(boundary_output)
|
|
spec = boundary_specs_by_source.get(source)
|
|
if spec is not None:
|
|
_record_boundary_output_routes(
|
|
spec,
|
|
proxy_output,
|
|
state.replacements,
|
|
state.materializer_proxy_outputs,
|
|
)
|
|
if not bool(boundary_output.get("session_output")):
|
|
state.replacements.default_by_source[source] = proxy_output
|
|
|
|
|
|
def _register_ordered_phase_proxies(
|
|
state: _OrderedProxyRewriteState,
|
|
*,
|
|
rewritten_prompt: dict[str, Any],
|
|
nodes_module: Any,
|
|
) -> None:
|
|
"""Register every prepared phase proxy and local bridge materializer."""
|
|
for phase_payload in state.phase_payloads:
|
|
proxy_node_id = str(phase_payload["component_id"])
|
|
_register_proxy_node(
|
|
prompt_node_id=proxy_node_id,
|
|
payload_mapping=phase_payload,
|
|
proxy_inputs=state.proxy_inputs_by_node_id[proxy_node_id],
|
|
meta=state.proxy_meta_by_node_id[proxy_node_id],
|
|
is_output_node=_contains_output_node(
|
|
list(phase_payload["component_node_ids"]),
|
|
rewritten_prompt=rewritten_prompt,
|
|
nodes_module=nodes_module,
|
|
),
|
|
rewritten_prompt=rewritten_prompt,
|
|
nodes_module=nodes_module,
|
|
)
|
|
for materializer_node_id, proxy_output in state.materializer_proxy_outputs.items():
|
|
_install_local_bridge_materializer(
|
|
rewritten_prompt, materializer_node_id, proxy_output
|
|
)
|
|
|
|
|
|
def _register_ordered_mapped_warmup(
|
|
component: RemoteComponentPlan,
|
|
phase_payloads: list[dict[str, Any]],
|
|
) -> None:
|
|
"""Register mapped-input warmup against the phase that owns mapped nodes."""
|
|
if component.mapped_boundary_source_node_id is None:
|
|
return
|
|
mapped_node_ids = set(component.mapped_node_ids)
|
|
for phase_payload in phase_payloads:
|
|
phase_node_ids = {
|
|
str(node_id) for node_id in phase_payload.get("component_node_ids", [])
|
|
}
|
|
if not (phase_node_ids & mapped_node_ids):
|
|
continue
|
|
register_modal_map_input_warmup_context(
|
|
component.mapped_boundary_source_node_id,
|
|
phase_payload,
|
|
str(component.mapped_boundary_input_io_type or "*"),
|
|
)
|
|
return
|
|
|
|
|
|
def _rewrite_ordered_phase_proxies(
|
|
component: RemoteComponentPlan,
|
|
rewritten_prompt: dict[str, Any],
|
|
split_payloads: list[Any],
|
|
nodes_module: Any,
|
|
) -> list[str]:
|
|
"""Replace one component with its ordered sequence of phase proxies."""
|
|
state = _OrderedProxyRewriteState(
|
|
phase_payloads=[dict(phase_payload) for phase_payload in split_payloads],
|
|
proxy_node_ids=[],
|
|
proxy_inputs_by_node_id={},
|
|
proxy_meta_by_node_id={},
|
|
replacements=_empty_proxy_replacement_maps(),
|
|
materializer_proxy_outputs={},
|
|
)
|
|
if any(spec.local_materializer_node_id for spec in component.boundary_outputs):
|
|
ensure_modal_local_bridge_materializer_registered(nodes_module)
|
|
_assign_ordered_phase_proxy_ids(
|
|
state,
|
|
rewritten_prompt=rewritten_prompt,
|
|
component_node_ids=set(component.node_ids),
|
|
)
|
|
boundary_specs = {spec.source: spec for spec in component.boundary_outputs}
|
|
produced_outputs: dict[str, list[Any]] = {}
|
|
for phase_payload in state.phase_payloads:
|
|
_prepare_ordered_phase_proxy(
|
|
state,
|
|
phase_payload,
|
|
rewritten_prompt=rewritten_prompt,
|
|
boundary_specs_by_source=boundary_specs,
|
|
produced_outputs_by_name=produced_outputs,
|
|
)
|
|
for node_id in component.node_ids:
|
|
rewritten_prompt.pop(node_id, None)
|
|
_register_ordered_phase_proxies(
|
|
state, rewritten_prompt=rewritten_prompt, nodes_module=nodes_module
|
|
)
|
|
_register_ordered_mapped_warmup(component, state.phase_payloads)
|
|
_rewire_proxy_consumers(
|
|
rewritten_prompt,
|
|
skip_node_ids=set(state.proxy_node_ids),
|
|
replacements=state.replacements,
|
|
)
|
|
logger.info(
|
|
"Rewrote remote component %s into ordered proxies %s.",
|
|
component.representative_node_id,
|
|
state.proxy_node_ids,
|
|
)
|
|
return state.proxy_node_ids
|
|
|
|
|
|
def _hybrid_proxy_inputs(
|
|
*,
|
|
static_payload: dict[str, Any],
|
|
mapped_payload: dict[str, Any],
|
|
static_proxy_node_id: str,
|
|
rewritten_prompt: dict[str, Any],
|
|
) -> tuple[dict[str, Any], dict[str, Any]]:
|
|
"""Resolve static and mapped proxy inputs, including session bridge links."""
|
|
static_inputs = _proxy_inputs_for_boundary_inputs(
|
|
list(static_payload["boundary_inputs"]), rewritten_prompt=rewritten_prompt
|
|
)
|
|
mapped_inputs = _proxy_inputs_for_boundary_inputs(
|
|
list(mapped_payload["boundary_inputs"]), rewritten_prompt=rewritten_prompt
|
|
)
|
|
mapped_static_phase = mapped_payload.get("static_phase")
|
|
if isinstance(mapped_static_phase, dict):
|
|
static_phase_inputs = _proxy_inputs_for_boundary_inputs(
|
|
list(mapped_static_phase.get("boundary_inputs", [])),
|
|
rewritten_prompt=rewritten_prompt,
|
|
)
|
|
for input_name, input_value in static_phase_inputs.items():
|
|
mapped_inputs.setdefault(input_name, input_value)
|
|
bridge_indices = {
|
|
str(output["proxy_output_name"]): index
|
|
for index, output in enumerate(static_payload["boundary_outputs"])
|
|
if bool(output.get("session_output"))
|
|
}
|
|
for boundary_input in mapped_payload.get("boundary_inputs", []):
|
|
input_name = str(boundary_input["proxy_input_name"])
|
|
if input_name in bridge_indices:
|
|
mapped_inputs[input_name] = [
|
|
static_proxy_node_id,
|
|
bridge_indices[input_name],
|
|
]
|
|
return static_inputs, mapped_inputs
|
|
|
|
|
|
def _hybrid_output_replacements(
|
|
static_payload: dict[str, Any],
|
|
mapped_payload: dict[str, Any],
|
|
*,
|
|
static_proxy_node_id: str,
|
|
mapped_proxy_node_id: str,
|
|
) -> _ProxyReplacementMaps:
|
|
"""Build default output rewrites for paired static and mapped proxies."""
|
|
replacements = _empty_proxy_replacement_maps()
|
|
for output_index, boundary_output in enumerate(static_payload["boundary_outputs"]):
|
|
if not bool(boundary_output.get("session_output")):
|
|
replacements.default_by_source[_linked_output_ref(boundary_output)] = [
|
|
static_proxy_node_id,
|
|
output_index,
|
|
]
|
|
for output_index, boundary_output in enumerate(
|
|
mapped_payload.get("boundary_outputs", [])
|
|
):
|
|
replacements.default_by_source[_linked_output_ref(boundary_output)] = [
|
|
mapped_proxy_node_id,
|
|
output_index,
|
|
]
|
|
return replacements
|
|
|
|
|
|
def _rewrite_hybrid_split_proxies(
|
|
component: RemoteComponentPlan,
|
|
rewritten_prompt: dict[str, Any],
|
|
split_payloads: dict[str, Any],
|
|
nodes_module: Any,
|
|
) -> list[str]:
|
|
"""Replace one hybrid component with paired static and mapped proxies."""
|
|
static_payload = dict(split_payloads["static"])
|
|
mapped_payload = dict(split_payloads["mapped"])
|
|
static_proxy_node_id = str(static_payload["component_id"])
|
|
mapped_proxy_node_id = _unique_proxy_node_id(
|
|
str(mapped_payload["component_id"]),
|
|
rewritten_prompt=rewritten_prompt,
|
|
component_node_ids=set(component.node_ids),
|
|
)
|
|
mapped_payload["component_id"] = mapped_proxy_node_id
|
|
static_inputs, mapped_inputs = _hybrid_proxy_inputs(
|
|
static_payload=static_payload,
|
|
mapped_payload=mapped_payload,
|
|
static_proxy_node_id=static_proxy_node_id,
|
|
rewritten_prompt=rewritten_prompt,
|
|
)
|
|
static_meta = copy.deepcopy(rewritten_prompt[static_proxy_node_id].get("_meta", {}))
|
|
mapped_meta = copy.deepcopy(
|
|
rewritten_prompt[component.mapped_node_ids[0]].get("_meta", {})
|
|
)
|
|
replacements = _hybrid_output_replacements(
|
|
static_payload,
|
|
mapped_payload,
|
|
static_proxy_node_id=static_proxy_node_id,
|
|
mapped_proxy_node_id=mapped_proxy_node_id,
|
|
)
|
|
for node_id in component.node_ids:
|
|
rewritten_prompt.pop(node_id, None)
|
|
for proxy_node_id, proxy_payload, proxy_inputs, meta, node_ids in (
|
|
(static_proxy_node_id, static_payload, static_inputs, static_meta, component.static_node_ids),
|
|
(mapped_proxy_node_id, mapped_payload, mapped_inputs, mapped_meta, component.mapped_node_ids),
|
|
):
|
|
_register_proxy_node(
|
|
prompt_node_id=proxy_node_id,
|
|
payload_mapping=proxy_payload,
|
|
proxy_inputs=proxy_inputs,
|
|
meta=meta,
|
|
is_output_node=_contains_output_node(
|
|
node_ids, rewritten_prompt=rewritten_prompt, nodes_module=nodes_module
|
|
),
|
|
rewritten_prompt=rewritten_prompt,
|
|
nodes_module=nodes_module,
|
|
)
|
|
if component.mapped_boundary_source_node_id is not None:
|
|
register_modal_map_input_warmup_context(
|
|
component.mapped_boundary_source_node_id,
|
|
mapped_payload,
|
|
str(component.mapped_boundary_input_io_type or "*"),
|
|
)
|
|
proxy_node_ids = [static_proxy_node_id, mapped_proxy_node_id]
|
|
_rewire_proxy_consumers(
|
|
rewritten_prompt,
|
|
skip_node_ids=set(proxy_node_ids),
|
|
replacements=replacements,
|
|
)
|
|
logger.info(
|
|
"Rewrote hybrid remote component %s into static proxy %s and mapped proxy %s.",
|
|
component.representative_node_id,
|
|
static_proxy_node_id,
|
|
mapped_proxy_node_id,
|
|
)
|
|
return proxy_node_ids
|
|
|
|
|
|
def _single_proxy_replacement_maps(
|
|
component: RemoteComponentPlan,
|
|
rewritten_prompt: dict[str, Any],
|
|
*,
|
|
representative_node_id: str,
|
|
) -> _ProxyReplacementMaps:
|
|
"""Build output rewrites and materializer nodes for one ordinary proxy."""
|
|
replacements = _empty_proxy_replacement_maps()
|
|
materializer_proxy_outputs: dict[str, list[Any]] = {}
|
|
for output_index, spec in enumerate(component.boundary_outputs):
|
|
proxy_output = [representative_node_id, output_index]
|
|
replacements.default_by_source.setdefault(spec.source, proxy_output)
|
|
_record_boundary_output_routes(
|
|
spec, proxy_output, replacements, materializer_proxy_outputs
|
|
)
|
|
for materializer_node_id, proxy_output in materializer_proxy_outputs.items():
|
|
_install_local_bridge_materializer(
|
|
rewritten_prompt, materializer_node_id, proxy_output
|
|
)
|
|
return replacements
|
|
|
|
|
|
def _rewrite_single_component_proxy(
|
|
component: RemoteComponentPlan,
|
|
rewritten_prompt: dict[str, Any],
|
|
payload: dict[str, Any],
|
|
nodes_module: Any,
|
|
) -> list[str]:
|
|
"""Replace one ordinary remote component with its representative proxy."""
|
|
representative_node_id = component.representative_node_id
|
|
proxy_inputs = _proxy_inputs_for_boundary_inputs(
|
|
list(payload.get("boundary_inputs", [])), rewritten_prompt=rewritten_prompt
|
|
)
|
|
representative_meta = copy.deepcopy(
|
|
rewritten_prompt[representative_node_id].get("_meta", {})
|
|
)
|
|
_register_proxy_node(
|
|
prompt_node_id=representative_node_id,
|
|
payload_mapping=payload,
|
|
proxy_inputs=proxy_inputs,
|
|
meta=representative_meta,
|
|
is_output_node=component.contains_output_node,
|
|
rewritten_prompt=rewritten_prompt,
|
|
nodes_module=nodes_module,
|
|
)
|
|
if component.mapped_boundary_source_node_id is not None:
|
|
register_modal_map_input_warmup_context(
|
|
component.mapped_boundary_source_node_id,
|
|
payload,
|
|
str(component.mapped_boundary_input_io_type or "*"),
|
|
)
|
|
if any(spec.local_materializer_node_id for spec in component.boundary_outputs):
|
|
ensure_modal_local_bridge_materializer_registered(nodes_module)
|
|
replacements = _single_proxy_replacement_maps(
|
|
component,
|
|
rewritten_prompt,
|
|
representative_node_id=representative_node_id,
|
|
)
|
|
for node_id in component.node_ids:
|
|
if node_id != representative_node_id:
|
|
rewritten_prompt.pop(node_id, None)
|
|
_rewire_proxy_consumers(
|
|
rewritten_prompt,
|
|
skip_node_ids={representative_node_id},
|
|
replacements=replacements,
|
|
)
|
|
logger.info(
|
|
"Rewrote remote component %s with %d nodes to its representative proxy.",
|
|
representative_node_id,
|
|
len(component.node_ids),
|
|
)
|
|
return [representative_node_id]
|
|
|
|
|
|
def _rewrite_component_into_proxy(
|
|
component: RemoteComponentPlan,
|
|
rewritten_prompt: dict[str, Any],
|
|
payload: dict[str, Any],
|
|
nodes_module: Any,
|
|
) -> list[str]:
|
|
"""Replace one remote component with the payload's selected proxy shape."""
|
|
split_payloads = payload.get("split_proxy_payloads")
|
|
if isinstance(split_payloads, list):
|
|
return _rewrite_ordered_phase_proxies(
|
|
component, rewritten_prompt, split_payloads, nodes_module
|
|
)
|
|
if isinstance(split_payloads, dict):
|
|
return _rewrite_hybrid_split_proxies(
|
|
component, rewritten_prompt, split_payloads, nodes_module
|
|
)
|
|
return _rewrite_single_component_proxy(
|
|
component, rewritten_prompt, payload, nodes_module
|
|
)
|
|
|
|
|
|
def _modal_component_completion_output_index(
|
|
*,
|
|
proxy_node_id: str,
|
|
rewritten_prompt: dict[str, Any],
|
|
nodes_module: Any,
|
|
) -> int:
|
|
"""Return the synthetic completion-token output index for one Modal proxy."""
|
|
prompt_node = rewritten_prompt.get(proxy_node_id)
|
|
if prompt_node is None:
|
|
raise ModalPromptValidationError(
|
|
f"Modal proxy node {proxy_node_id!r} is missing before artifact finalization."
|
|
)
|
|
class_type = str(prompt_node.get("class_type"))
|
|
node_class = nodes_module.NODE_CLASS_MAPPINGS.get(class_type)
|
|
if node_class is None:
|
|
raise ModalPromptValidationError(
|
|
f"Modal proxy class {class_type!r} is not registered before artifact finalization."
|
|
)
|
|
_output_types, output_names, _output_is_list = _normalize_output_metadata(
|
|
node_class
|
|
)
|
|
try:
|
|
return output_names.index(MODAL_COMPONENT_COMPLETION_OUTPUT_NAME)
|
|
except ValueError as exc:
|
|
raise ModalPromptValidationError(
|
|
f"Modal proxy {proxy_node_id!r} does not expose its completion token."
|
|
) from exc
|
|
|
|
|
|
def _attach_modal_artifact_finalizer(
|
|
*,
|
|
rewritten_prompt: dict[str, Any],
|
|
remote_component_ids: list[str],
|
|
nodes_module: Any,
|
|
) -> str:
|
|
"""Attach one internal output sink to every rewritten Modal component."""
|
|
if not remote_component_ids:
|
|
raise ModalPromptValidationError(
|
|
"Modal artifact finalization requires at least one remote component."
|
|
)
|
|
if len(remote_component_ids) > MODAL_ARTIFACT_FINALIZER_MAX_COMPONENTS:
|
|
raise ModalPromptValidationError(
|
|
"Modal artifact finalization supports at most "
|
|
f"{MODAL_ARTIFACT_FINALIZER_MAX_COMPONENTS} remote components per prompt."
|
|
)
|
|
|
|
ensure_modal_artifact_finalizer_registered(nodes_module)
|
|
finalizer_node_id = f"__{MODAL_ARTIFACT_FINALIZER_NODE_ID}__"
|
|
while finalizer_node_id in rewritten_prompt:
|
|
finalizer_node_id = f"{finalizer_node_id}_proxy"
|
|
|
|
finalizer_inputs = {
|
|
f"components.component_{component_index}": [
|
|
proxy_node_id,
|
|
_modal_component_completion_output_index(
|
|
proxy_node_id=proxy_node_id,
|
|
rewritten_prompt=rewritten_prompt,
|
|
nodes_module=nodes_module,
|
|
),
|
|
]
|
|
for component_index, proxy_node_id in enumerate(remote_component_ids)
|
|
}
|
|
rewritten_prompt[finalizer_node_id] = {
|
|
"class_type": MODAL_ARTIFACT_FINALIZER_NODE_ID,
|
|
"inputs": finalizer_inputs,
|
|
"_meta": {"title": "Modal Artifact Finalizer"},
|
|
}
|
|
logger.info(
|
|
"Attached Modal artifact finalizer %s to remote components %s.",
|
|
finalizer_node_id,
|
|
remote_component_ids,
|
|
)
|
|
return finalizer_node_id
|