Package: @ai-sdk/workflow (packages/workflow in this repo)
Versions affected: @ai-sdk/workflow 1.0.4–1.0.11, with ai 7.0.x. Confirmed on 1.0.11 + ai@7.0.11; the bug has been present since at least 1.0.4 based on reading the history of do-stream-step.ts.
Symptom
WorkflowAgent.stream() called with output: Output.object({ schema }) never actually enforces structured output. The model call that goes out has responseFormat: null regardless of the output spec passed to the agent, and there is no schema-in-prompt fallback either. In practice, with real providers this surfaces as AI_NoObjectGeneratedError: could not parse the response once the model returns prose or a shape that doesn't match the schema, because the model was never told what schema to produce.
For comparison, the same output/schema config used through the non-workflow Experimental_Agent / ToolLoopAgent path in ai correctly receives responseFormat on the model call — so this looks specific to the WorkflowAgent step-boundary plumbing, not a schema-conversion bug.
Minimal repro
import { WorkflowAgent, Output } from '@ai-sdk/workflow';
import { MockLanguageModelV4 } from 'ai/test';
import { z } from 'zod';
const model = new MockLanguageModelV4({
doStream: async () => ({
stream: new ReadableStream({
start(c) {
c.enqueue({ type: 'text-start', id: '1' });
c.enqueue({ type: 'text-delta', id: '1', delta: '{"ok":true}' });
c.enqueue({ type: 'text-end', id: '1' });
c.enqueue({ type: 'finish', finishReason: 'stop', usage: { inputTokens: 0, outputTokens: 0, totalTokens: 0 } });
c.close();
},
}),
}),
});
const agent = new WorkflowAgent({
model,
output: Output.object({ schema: z.object({ ok: z.boolean() }) }),
});
await agent.stream({ messages: [{ role: 'user', content: 'go' }] });
console.log(model.doStreamCalls[0]?.responseFormat);
// Actual: null
// Expected: { type: 'json', schema: <json-schema for z.object({ ok: z.boolean() })> }
Root cause
responseFormat is computed correctly and threaded most of the way through, then dropped at the last hop:
WorkflowAgent's stream path computes it from the output spec: packages/workflow/src/workflow-agent.ts:2093
responseFormat: await (options.output ?? this.output)?.responseFormat,
- It's forwarded correctly into the options passed to
doStreamStep: packages/workflow/src/stream-text-iterator.ts:358 (inside the options object built for the doStreamStep(...) call at line 348).
doStreamStep's own option type even declares it: packages/workflow/src/do-stream-step.ts:59
responseFormat?: LanguageModelV4CallOptions['responseFormat'];
- But the
streamModelCall({...}) call inside doStreamStep (packages/workflow/src/do-stream-step.ts:125-148) never includes it. toolChoice, headers, and reasoning are all forwarded from options, responseFormat is not:
const { stream: modelStream } = await streamModelCall({
model,
messages: conversationPrompt as unknown as ModelMessage[],
allowSystemInMessages: true,
tools,
toolChoice: options?.toolChoice, // forwarded
includeRawChunks: options?.includeRawChunks,
providerOptions: options?.providerOptions,
abortSignal: options?.abortSignal,
headers: options?.headers, // forwarded
reasoning: options?.reasoning, // forwarded
maxOutputTokens: options?.maxOutputTokens,
...
// responseFormat: options?.responseFormat, <-- missing
});
Notably, reasoning forwarding on this exact call was added in 1.0.6 for what looks like the same class of gap (changelog: "Forward reasoning generation settings from WorkflowAgent to model calls"). responseFormat needs the same one-line fix.
Suggested fix
In packages/workflow/src/do-stream-step.ts, add responseFormat: options?.responseFormat, to the streamModelCall({...}) call around line 137-138, alongside the existing toolChoice/headers/reasoning forwarding.
Context: found while integrating WorkflowAgent in a production app. Happy to send a one-line PR if useful.
Package:
@ai-sdk/workflow(packages/workflowin this repo)Versions affected:
@ai-sdk/workflow1.0.4–1.0.11, withai7.0.x. Confirmed on 1.0.11 + ai@7.0.11; the bug has been present since at least 1.0.4 based on reading the history ofdo-stream-step.ts.Symptom
WorkflowAgent.stream()called withoutput: Output.object({ schema })never actually enforces structured output. The model call that goes out hasresponseFormat: nullregardless of theoutputspec passed to the agent, and there is no schema-in-prompt fallback either. In practice, with real providers this surfaces asAI_NoObjectGeneratedError: could not parse the responseonce the model returns prose or a shape that doesn't match the schema, because the model was never told what schema to produce.For comparison, the same
output/schema config used through the non-workflowExperimental_Agent/ToolLoopAgentpath inaicorrectly receivesresponseFormaton the model call — so this looks specific to theWorkflowAgentstep-boundary plumbing, not a schema-conversion bug.Minimal repro
Root cause
responseFormatis computed correctly and threaded most of the way through, then dropped at the last hop:WorkflowAgent's stream path computes it from the output spec:packages/workflow/src/workflow-agent.ts:2093doStreamStep:packages/workflow/src/stream-text-iterator.ts:358(inside the options object built for thedoStreamStep(...)call at line 348).doStreamStep's own option type even declares it:packages/workflow/src/do-stream-step.ts:59streamModelCall({...})call insidedoStreamStep(packages/workflow/src/do-stream-step.ts:125-148) never includes it.toolChoice,headers, andreasoningare all forwarded fromoptions,responseFormatis not:Notably,
reasoningforwarding on this exact call was added in 1.0.6 for what looks like the same class of gap (changelog: "Forwardreasoninggeneration settings fromWorkflowAgentto model calls").responseFormatneeds the same one-line fix.Suggested fix
In
packages/workflow/src/do-stream-step.ts, addresponseFormat: options?.responseFormat,to thestreamModelCall({...})call around line 137-138, alongside the existingtoolChoice/headers/reasoningforwarding.Context: found while integrating
WorkflowAgentin a production app. Happy to send a one-line PR if useful.