mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-10-26 13:16:24 +00:00
fix(workflows): align sequential Agent handoff size limits (#7231)
Allow ordinary Agent tasks the same 16,000 JSON bytes already accepted for file-reference tasks, keeping the 12,000-byte output cap and bounded rejection. Cover ASCII, escaped Unicode, maximum output and pre-admission rejection. Co-authored-by: Totoro-qaq <279883115+Totoro-qaq@users.noreply.github.com> Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
This commit is contained in:
parent
4b7f2454fe
commit
f6fe1e9c9e
@ -209,6 +209,43 @@ async def test_sequential_parallel_branch_loop_structured_results(plugin):
|
||||
assert final["logs"] == ["Checks finished"]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize("material", ["x" * 9000, "材料" * 750, "x" * 11998], ids=["ascii-report", "chinese-report", "maximum-output"])
|
||||
async def test_step_output_can_be_handed_to_next_agent_without_truncation(plugin, material):
|
||||
runs = plugin[3]
|
||||
original_answer = runs.answer
|
||||
|
||||
async def answer(run, input):
|
||||
await original_answer(run, input)
|
||||
task = json.loads(input["messages"][0]["content"])
|
||||
content = material if task == "prepare" else {"received": True}
|
||||
runs.threads[run.thread_id]["values"]["messages"][-1]["content"] = json.dumps(content)
|
||||
|
||||
runs.answer = answer
|
||||
launched = await launch(
|
||||
plugin,
|
||||
"""
|
||||
const first = await agent({stepId:'prepare',agentRef:'lead_agent',task:'prepare'});
|
||||
const second = await agent({stepId:'produce',agentRef:'lead_agent',task:{material:first.output,instruction:'Use the supplied material'}});
|
||||
return second.output;
|
||||
""",
|
||||
)
|
||||
final = await done(plugin, launched["run_id"])
|
||||
assert final["status"] == "completed", final
|
||||
assert final["result"] == {"received": True}
|
||||
assert len(runs.starts) == 2
|
||||
assert json.loads(runs.starts[1][1]["messages"][0]["content"]) == {"material": material, "instruction": "Use the supplied material"}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_oversized_agent_task_is_rejected_before_child_admission(plugin):
|
||||
launched = await launch(plugin, "return await agent({stepId:'large',agentRef:'lead_agent',task:'x'.repeat(16001)});")
|
||||
final = await done(plugin, launched["run_id"])
|
||||
assert final["status"] == "failed"
|
||||
assert not plugin[3].starts
|
||||
assert not plugin[2].processes
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_background_return_idempotency_and_owner_scope(plugin):
|
||||
script = "return await agent({stepId:'a',agentRef:'lead_agent',task:'blocked'});"
|
||||
|
||||
@ -8,6 +8,9 @@ Generated script runs only in worker.py's isolated, capability-free JS context.
|
||||
Never pass host callables, capability handles, credentials or ambient environment
|
||||
into it. Keep every IPC and stored/public payload bounded. Parent lifecycle must
|
||||
kill and reap the worker and explicitly observe child cancellation.
|
||||
Agent tasks allow 16,000 serialized JSON bytes and outputs 12,000; preserve enough
|
||||
input room for a retained output plus task wrapping. Test sequential handoff with
|
||||
both ASCII and escaped Unicode, and reject oversized inputs before admission.
|
||||
The worker turn cap counts worker frames only; while the worker is idle (its job
|
||||
queue drained) and Agent calls are pending, wait locally with bounded cancellation,
|
||||
fault and worker-exit polls instead of round-tripping empty frames, so wall time stays the only clock bound.
|
||||
|
||||
@ -115,6 +115,11 @@ API; validate verdicts in the script before branching. Every Agent call must be
|
||||
awaited, including fan-out calls. Returning while child promises are outstanding
|
||||
fails the workflow and interrupts tracked children.
|
||||
|
||||
Agent task inputs allow 16,000 serialized JSON bytes, including wrapping fields
|
||||
and file references; step outputs allow 12,000. This leaves room to pass a saved
|
||||
output to the next Agent with a short task instruction. JSON escaping counts
|
||||
toward these limits. Larger material must use the file workflow described below.
|
||||
|
||||
`parallel()` takes lazy thunks and preserves order. The service, independently of
|
||||
JavaScript promises, bounds admitted children per workflow and globally. A rejected
|
||||
call rejects its promise; failures are not silently converted to null. Scripts may
|
||||
|
||||
@ -23,6 +23,8 @@ from .store import TERMINAL, Store
|
||||
NAMESPACE = "community.workflows"
|
||||
FRAME = 48 * 1024
|
||||
TURN_LIMIT = 10000
|
||||
AGENT_TASK_BYTES = 16000
|
||||
AGENT_OUTPUT_BYTES = 12000
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@ -418,7 +420,7 @@ class Workflows(Recovery, Lifecycle, Artifacts):
|
||||
{
|
||||
"role": "user",
|
||||
"id": message_id,
|
||||
"content": encoded(task, 16000 if references else 8000),
|
||||
"content": encoded(task, AGENT_TASK_BYTES),
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -539,7 +541,7 @@ class Workflows(Recovery, Lifecycle, Artifacts):
|
||||
output = content
|
||||
else:
|
||||
output = content
|
||||
encoded(output, 12000)
|
||||
encoded(output, AGENT_OUTPUT_BYTES)
|
||||
# Evidence comes from the run's completed host assembly, never a
|
||||
# fresh read of mutable configuration after the Agent finishes.
|
||||
async with asyncio.timeout(5):
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user