fix(simulator): restore input/output block passthrough in dry-run

Re-add the passthrough logic for AgentInputBlock and AgentOutputBlock
in simulate_block. These blocks are trivial passthroughs that don't
need LLM simulation -- forwarding input values directly is faster,
deterministic, and doesn't require API keys (which aren't available
in CI).
This commit is contained in:
Zamil Majdy
2026-03-29 06:41:34 +02:00
parent eefa60368f
commit 76965429f1
2 changed files with 96 additions and 6 deletions

View File

@@ -691,7 +691,78 @@ def test_prepare_dry_run_regular_block_returns_none():
# ---------------------------------------------------------------------------
# NOTE: Input/output block passthrough tests were removed because the simulator
# no longer special-cases AgentInputBlock/AgentOutputBlock. These blocks are
# now LLM-simulated like any other block (using the run() source code in the
# prompt), so the passthrough assertions no longer apply.
@pytest.mark.asyncio
async def test_simulate_agent_input_block_passthrough():
"""AgentInputBlock should pass through the value directly, no LLM call."""
from backend.blocks.io import AgentInputBlock
block = AgentInputBlock()
outputs = []
async for name, data in simulate_block(
block, {"value": "hello world", "name": "q"}
):
outputs.append((name, data))
assert outputs == [("result", "hello world")]
@pytest.mark.asyncio
async def test_simulate_agent_dropdown_input_block_passthrough():
"""AgentDropdownInputBlock (subclass of AgentInputBlock) should pass through."""
from backend.blocks.io import AgentDropdownInputBlock
block = AgentDropdownInputBlock()
outputs = []
async for name, data in simulate_block(
block,
{
"value": "Option B",
"name": "sev",
"placeholder_values": ["Option A", "Option B"],
},
):
outputs.append((name, data))
assert outputs == [("result", "Option B")]
@pytest.mark.asyncio
async def test_simulate_agent_input_block_none_value():
"""AgentInputBlock with value=None should yield nothing."""
from backend.blocks.io import AgentInputBlock
block = AgentInputBlock()
outputs = []
async for name, data in simulate_block(block, {"value": None, "name": "q"}):
outputs.append((name, data))
assert outputs == []
@pytest.mark.asyncio
async def test_simulate_agent_output_block_passthrough():
"""AgentOutputBlock should pass through value as output."""
from backend.blocks.io import AgentOutputBlock
block = AgentOutputBlock()
outputs = []
async for name, data in simulate_block(
block, {"value": "result text", "name": "out1"}
):
outputs.append((name, data))
assert ("output", "result text") in outputs
assert ("name", "out1") in outputs
@pytest.mark.asyncio
async def test_simulate_agent_output_block_no_name():
"""AgentOutputBlock without name in input should still yield output."""
from backend.blocks.io import AgentOutputBlock
block = AgentOutputBlock()
outputs = []
async for name, data in simulate_block(block, {"value": 42}):
outputs.append((name, data))
assert outputs == [("output", 42)]

View File

@@ -10,8 +10,8 @@ Special cases (no LLM simulation needed):
(iterations capped to 1).
- AgentExecutorBlock executes for real so it can spawn child graph executions
(whose blocks are then simulated).
- AgentInputBlock, AgentOutputBlock, and other simple blocks are simulated
using the same LLM prompt (which includes the block's run() source code).
- AgentInputBlock (and all subclasses) and AgentOutputBlock are pure
passthrough -- they forward their input values directly.
- MCPToolBlock uses a specialised LLM prompt grounded in the tool's schema.
OrchestratorBlock and AgentExecutorBlock are handled in manager.py via
@@ -34,6 +34,7 @@ from collections.abc import AsyncGenerator
from typing import Any
from backend.blocks.agent import AgentExecutorBlock
from backend.blocks.io import AgentInputBlock, AgentOutputBlock
from backend.blocks.mcp.block import MCPToolBlock
from backend.blocks.orchestrator import OrchestratorBlock
from backend.util.clients import get_openai_client
@@ -387,6 +388,24 @@ async def simulate_block(
yield output
return
# Input/output blocks are pure passthrough -- they just forward their
# input values. No LLM simulation needed.
if isinstance(block, AgentInputBlock):
# AgentInputBlock and all subclasses (AgentDropdownInputBlock,
# AgentFileInputBlock, AgentShortTextInputBlock, etc.) yield
# "result" with the provided value.
value = input_data.get("value")
if value is not None:
yield "result", value
return
if isinstance(block, AgentOutputBlock):
# AgentOutputBlock passes through "value" as "output" (+ "name").
yield "output", input_data.get("value")
if "name" in input_data:
yield "name", input_data["name"]
return
output_schema = block.output_schema.jsonschema()
output_properties: dict[str, Any] = output_schema.get("properties", {})