Skip to content

fix: default empty input_schema for Anthropic function tools with nil params - #1872

Open
mvanhorn wants to merge 1 commit into
looplj:unstablefrom
mvanhorn:fix/1843-empty-input-schema
Open

fix: default empty input_schema for Anthropic function tools with nil params#1872
mvanhorn wants to merge 1 commit into
looplj:unstablefrom
mvanhorn:fix/1843-empty-input-schema

Conversation

@mvanhorn

Copy link
Copy Markdown
Contributor

Summary

  • When converting function-type tools for the Anthropic Messages API, the input_schema field was omitted entirely when a tool had nil or empty Parameters, due to json:",omitempty" on the field.
  • Anthropic now requires input_schema to be present (even if empty) for all function-type tools; omitting it causes HTTP 422.
  • This fix defaults InputSchema to {"type":"object","properties":{}} when tool.Function.Parameters is nil or zero-length.

Why this matters

Fixes #1843. After upgrading to v1.0.0-beta3, any Anthropic channel request containing a function tool with no parameters (e.g. a client sending a web_search function tool without a parameter schema) fails with HTTP 422 from the Anthropic API because input_schema is missing. The canonical empty JSON Schema object satisfies Anthropic's validation for no-argument tools.

Testing

  • Added TestConvertToolsAnthropic_NilParameters in outbound_convert_test.go covering:
    • nil Parameters produces valid {"type":"object","properties":{}} as InputSchema
    • empty Parameters (json.RawMessage{}) gets the same default
    • explicit Parameters with a schema are passed through unchanged
  • All 539 existing tests in the anthropic package continue to pass.
  • gofmt, go vet, and go build clean.

Fixes #1843

… params

When a function tool has nil or empty Parameters, the Anthropic API now
requires input_schema to be present. Default to {"type":"object","properties":{}}
to prevent HTTP 422 errors for no-argument function tools (fixes looplj#1843).
@greptile-apps

greptile-apps Bot commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a regression introduced in v1.0.0-beta3 where function-type tools with no parameters caused HTTP 422 errors from the Anthropic Messages API because the input_schema field was omitted from the serialised request (due to json:\",omitempty\" on the struct field). The fix defaults InputSchema to the canonical empty JSON Schema object {\"type\":\"object\",\"properties\":{}} in convertToolsAnthropic whenever Parameters is nil or zero-length.

  • outbound_convert.go: adds a two-line guard in the ToolTypeFunction branch that substitutes the empty-schema default before constructing the Tool struct.
  • outbound_convert_test.go: adds TestConvertToolsAnthropic_NilParameters with three sub-cases (nil, empty, and explicit parameters) that together give full branch coverage of the new guard.
  • Remaining file changes are whitespace/alignment-only formatting fixes with no logic impact.

Confidence Score: 4/5

Safe to merge; the change is minimal and directly fixes the HTTP 422 regression for no-parameter function tools.

The fix is a targeted two-line guard with clear test coverage for all three parameter states. The only residual concern is that Tool.InputSchema in model.go still has omitempty, meaning the same class of failure could resurface if a Tool is ever built outside convertToolsAnthropic. That is a hardening item rather than a current defect.

llm/transformer/anthropic/model.go — the omitempty tag on Tool.InputSchema was not addressed and could silently reintroduce the bug for any direct Tool construction that bypasses the conversion helper.

Important Files Changed

Filename Overview
llm/transformer/anthropic/outbound_convert.go Core fix: defaults InputSchema to {"type":"object","properties":{}} when Parameters is nil or empty, preventing HTTP 422 from Anthropic's API.
llm/transformer/anthropic/outbound_convert_test.go Adds TestConvertToolsAnthropic_NilParameters covering nil, empty, and explicit Parameters cases. All assertions are correct and well-scoped.
llm/pipeline/empty_response_test.go Whitespace/alignment-only formatting change; no logic affected.
llm/pipeline/streaming_integration_test.go Removes a stray blank line between two test functions; no logic affected.
llm/transformer/anthropic/outbound_stream_server_tool_use_test.go Alignment-only formatting change in a test map literal; no logic affected.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant Client
    participant convertToolsAnthropic
    participant AnthropicAPI

    Note over Client,AnthropicAPI: After fix
    Client->>convertToolsAnthropic: "Tool{Type: function, Parameters: nil}"
    Note over convertToolsAnthropic: len(inputSchema) == 0
    Note over convertToolsAnthropic: default to {type:object,properties:{}}
    convertToolsAnthropic->>AnthropicAPI: "input_schema: {type:object,properties:{}}"
    AnthropicAPI-->>Client: 200 OK

    Note over Client,AnthropicAPI: Before fix
    Client->>convertToolsAnthropic: "Tool{Type: function, Parameters: nil}"
    Note over convertToolsAnthropic: omitempty drops input_schema
    convertToolsAnthropic->>AnthropicAPI: (input_schema omitted)
    AnthropicAPI-->>Client: 422 Unprocessable Entity
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant Client
    participant convertToolsAnthropic
    participant AnthropicAPI

    Note over Client,AnthropicAPI: After fix
    Client->>convertToolsAnthropic: "Tool{Type: function, Parameters: nil}"
    Note over convertToolsAnthropic: len(inputSchema) == 0
    Note over convertToolsAnthropic: default to {type:object,properties:{}}
    convertToolsAnthropic->>AnthropicAPI: "input_schema: {type:object,properties:{}}"
    AnthropicAPI-->>Client: 200 OK

    Note over Client,AnthropicAPI: Before fix
    Client->>convertToolsAnthropic: "Tool{Type: function, Parameters: nil}"
    Note over convertToolsAnthropic: omitempty drops input_schema
    convertToolsAnthropic->>AnthropicAPI: (input_schema omitted)
    AnthropicAPI-->>Client: 422 Unprocessable Entity
Loading

Reviews (1): Last reviewed commit: "fix: default empty input_schema for Anth..." | Re-trigger Greptile

Comment on lines 229 to +233
case llm.ToolTypeFunction:
inputSchema := tool.Function.Parameters
if len(inputSchema) == 0 {
inputSchema = json.RawMessage(`{"type":"object","properties":{}}`)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 omitempty on InputSchema still latent in model.go

The fix correctly supplies a default value before constructing the Tool struct, so the immediate bug is resolved. However, Tool.InputSchema in model.go still carries json:"input_schema,omitempty". The guard in convertToolsAnthropic is the only thing standing between a future caller that builds a Tool directly (bypassing the helper) and another HTTP 422. Removing omitempty from the struct tag in model.go would make the invariant hold at the serialisation layer for all code paths, not just this one.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@looplj

looplj commented Jun 20, 2026

Copy link
Copy Markdown
Owner

https://platform.claude.com/docs/en/api/go/messages/create#web_search_tool_20260209

The web search tool is not function type, and no input schema field.

@mvanhorn

Copy link
Copy Markdown
Contributor Author

Agreed that web_search is not a function type and must not carry an input_schema, and this PR keeps that invariant.

The default only fires inside the case llm.ToolTypeFunction: branch of convertToolsAnthropic:

case llm.ToolTypeFunction:
    inputSchema := tool.Function.Parameters
    if len(inputSchema) == 0 {
        inputSchema = json.RawMessage(`{"type":"object","properties":{}}`)
    }
    // ...
case llm.ToolTypeWebSearch:
    // builds Tool{Type: ToolTypeWebSearch20250305, Name: ...} with no InputSchema

The web_search branch is untouched: it sets only Type and Name, and InputSchema keeps its json:"input_schema,omitempty" tag in model.go, so native tools serialize without an input_schema field. I intentionally did not take Greptile's suggestion to drop omitempty, for exactly this reason: removing it would force input_schema: null onto web_search and the other native tools.

So a no-arg function tool gets {"type":"object","properties":{}} and a web_search tool gets nothing, which I believe is the behavior you're describing.

If you're seeing a specific tool reaching the function branch when it shouldn't (e.g. a server-side tool arriving with Type=function), that would be a classification issue upstream of this conversion, and I'm happy to handle it separately if you can point me at the request shape that triggers it.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug/错误]: 升级至最新版本后 anthropic 接口转 codex 接口报错

2 participants