Skip to content

think: surface output / experimental_output in TurnConfig (AI SDK structured-output passthrough) #1383

Description

@gabrielbryk

Summary

When building a ThinkAgent that wants both server-side tool calls and structured (JSON-schema-validated) output on the final turn, today the Vercel AI SDK's experimental_output / output option is unreachable because Think's TurnConfig doesn't forward it into the wrapped streamText call.

Why this matters

Vercel AI SDK supports the pattern:

Call tools during intermediate turns; on the final turn, switch to structured-output mode so the response is guaranteed to match a schema.

This is the canonical fix for the well-known "model emits prose around JSON" failure on tool-calling agents. On Workers AI (via workers-ai-provider), responseFormat: { type: "json", schema } works but forcibly strips tools — so it can't be enabled at model-construction time for a tool-using agent. The AI SDK's per-call experimental_output avoids this by only applying structured output on the non-tool-calling terminal turn.

Think's TurnConfig (packages/think v0.2.4 surface) exposes model, system, messages, tools, activeTools, toolChoice, maxSteps, providerOptions — but no output / experimental_output. As a result, agents authored on Think fall back to prompt-level nudges ("respond with raw JSON only, no markdown fences") which fail ~10–20% of the time even on well-behaved models.

We hit this in production on the GridLink flapping-charger diagnostic agent: Kimi K2.5 frequently wraps its final answer in a ​```json fence or a short apology, which trips our outputSchema validation downstream. We currently work around it with a second extractor call (no tools, Output.object()), but the right place for the fix is Think.

Proposed change

Add output (and experimental_output for legacy parity) to the TurnConfig interface:

interface TurnConfig {
  model?: LanguageModel;
  system?: string;
  messages?: ModelMessage[];
  tools?: ToolSet;
  activeTools?: string[];
  toolChoice?: Parameters<typeof streamText>[0]['toolChoice'];
  maxSteps?: number;
  providerOptions?: Record<string, unknown>;
  // NEW
  output?: Parameters<typeof streamText>[0]['output'];
  experimental_output?: Parameters<typeof streamText>[0]['experimental_output'];
}

…and forward them in the streamText(...) call inside chat(). Both fields are already strongly typed by the AI SDK; Think can passthrough without needing its own validation layer.

Backward compatibility: both fields are optional, so existing callers see no change.

Reference

Happy to open a PR if the direction looks right.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions