Skip to content

feat: avoid forced tool choice for structured output when unsupported - #28621

Closed
StarpTech wants to merge 4 commits into
devfrom
dustin/tool-choice-capability-support
Closed

StarpTech wants to merge 4 commits into
devfrom
dustin/tool-choice-capability-support

Conversation

@StarpTech

@StarpTech StarpTech commented May 21, 2026 •

Copy link
Copy Markdown
Contributor

Issue for this PR

None

What does this PR do?

This fixes SDK structured response support for DeepSeek v4 reasoning models. Those models do not support forcing tool calls with tool_choice: "required".

Previously, OpenCode always used forced tool calling for JSON schema output. That worked for many models, but DeepSeek reasoning models reject this parameter with an error like:

deepseek-reasoner does not support this tool_choice

Type of change

  • Bug fix
  • New feature
  • Refactor / code improvement
  • Documentation

Open Question

Since we changed the semantics from required to auto, should we detect and return an error when a structured response wasn't generated?

UPDATE: We already do

if (format.type === "json_schema") {

Naming tool_choice_required might be a bit confusing, however, if you read it as "capability" like tool_call, it could be fine.

What Changed

  • Added a new model capability: tool_choice_required.
  • Kept tool_call separate from forced tool choice support.
  • Updated structured output logic:
    • Use toolChoice: "required" when the model supports forced tool choice.
    • Use toolChoice: "auto" when tool_choice_required: false.
    • Added config support so users can override model metadata.
    • Added models.dev schema support for tool_choice_required.
    • Added SDK types for tool_choice_required.
    • Added tests proving tools are still sent while forced tool choice is avoided.

Why

Some models have this capability shape:

{
  "tool_call": true,
  "tool_choice_required": false
}

That means:

  • The model supports tools.
  • The model can choose to call a tool.
  • The model does not support being forced to call a tool.

Structured output uses a StructuredOutput tool. For these models, we now send the tool but use tool_choice: "auto" instead of tool_choice: "required".

Example

This SDK script proves the fix by using DeepSeek V4 Flash Free / Pro with JSON schema output:

import { createOpencode } from "@opencode-ai/sdk"
const { client } = await createOpencode({
  port: 14096,
  config: {
    model: "opencode/deepseek-v4-flash-free",
    provider: {
      opencode: {
        models: {
          "deepseek-v4-flash-free": {
            tool_call: true,
            tool_choice_required: false, // Toggle to make it fail
          },
        },
      },
    },
  },
})
const session = await client.session.create({
  body: { title: "Test 1" },
})
const result = await client.session.prompt({
  path: { id: session.data.id },
  body: {
    parts: [{ type: "text", text: "Say hello in one sentence." }],
    format: {
      type: "json_schema",
      schema: {
        type: "object",
        properties: {
          answer: { type: "string" },
        },
        required: ["answer"],
      },
    },
  },
})
console.log(JSON.stringify(result.data, null, 2))

Before this fix, the request could fail because OpenCode forced:
tool_choice: "required"

After this fix, the request uses:
tool_choice: "auto"

while still sending the StructuredOutput tool.

Before release

The models.dev metadata must be updated for affected DeepSeek reasoning models:

{
  "tool_call": true,
  "tool_choice_required": false
}

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

If you do not follow this template your PR will be automatically rejected.

@github-actions github-actions Bot added needs:compliance This means the issue will auto-close after 2 hours. contributor labels May 21, 2026
@github-actions github-actions Bot removed the needs:compliance This means the issue will auto-close after 2 hours. label May 21, 2026
@github-actions github-actions Bot closed this May 21, 2026
@StarpTech StarpTech reopened this May 21, 2026
@anomalyco anomalyco deleted a comment from github-actions Bot May 21, 2026
@github-actions github-actions Bot added the needs:compliance This means the issue will auto-close after 2 hours. label May 21, 2026
@anomalyco anomalyco deleted a comment from github-actions Bot May 21, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Automated PR Cleanup

Thank you for contributing to opencode.

Due to the high volume of PRs from users and AI agents, we periodically close older PRs using automated criteria so maintainers can focus review time on the most active and community-supported contributions.

This PR was closed because it matched the following cleanup criteria:

  • The PR was created more than 1 month ago
  • The PR had fewer than 2 positive reactions
  • Positive reactions are counted as thumbs-up, heart, celebration, or rocket reactions on the PR

PRs created within the last month are not affected by this cleanup.

If you believe this PR was closed incorrectly, or if you are still actively working on it, please leave a comment explaining why it should be reopened. A maintainer can review and reopen it if appropriate.

Thanks again for taking the time to contribute.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

automated-pr-cleanup contributor needs:compliance This means the issue will auto-close after 2 hours.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant