ToAnthropic
Go · module github.com/muthuishere/toolnexus/golang · SPEC §4 · golang/adapters.go
func ToAnthropic(tools []Tool) []anyTurns a []Tool into the tools array an Anthropic Messages request expects. Anthropic’s shape is
the flattest of the three: no type wrapper and no function nesting — each entry is just a name,
a description and an input_schema.
When to use it
Section titled “When to use it”When you drive the Anthropic Messages API yourself — the official SDK, a raw POST /v1/messages,
Bedrock or Vertex — and need the schema block for the request body. Reach for it whenever you have
tools from toolnexus and a Claude-shaped endpoint on the other end.
Why this and not the alternative
Section titled “Why this and not the alternative”tk.ToAnthropic() on a Toolkit is this same function applied to that
toolkit’s tools. Use the method when you hold a toolkit, the free function when you hold a slice.
Examples
Section titled “Examples”1. One tool to Anthropic schema
Section titled “1. One tool to Anthropic schema”Note the key: input_schema, snake-cased, at the top level of each entry.
package main
import ( "context" "encoding/json" "fmt" "log"
toolnexus "github.com/muthuishere/toolnexus/golang")
func main() { weather := toolnexus.NativeTool( "get_weather", "Current weather for a city", toolnexus.JSONSchema{ "type": "object", "properties": map[string]any{"city": map[string]any{"type": "string"}}, "required": []string{"city"}, }, func(ctx context.Context, args map[string]any) (string, error) { return fmt.Sprintf("sunny in %v", args["city"]), nil }, )
schema := toolnexus.ToAnthropic([]toolnexus.Tool{weather}) if len(schema) != 1 { log.Fatalf("expected 1 entry, got %d", len(schema)) }
b, _ := json.Marshal(schema[0]) var got map[string]any _ = json.Unmarshal(b, &got)
// Flat: no {"type":"function","function":{...}} wrapper. if _, wrapped := got["function"]; wrapped { log.Fatalf("anthropic entries are flat, got %v", got) } if got["name"] != "get_weather" || got["description"] != "Current weather for a city" { log.Fatalf("unexpected entry: %v", got) } if _, ok := got["input_schema"].(map[string]any); !ok { log.Fatalf("expected an input_schema object, got %v", got["input_schema"]) }
fmt.Println("ok:", got["name"])}2. Feeding it straight into a Messages request body
Section titled “2. Feeding it straight into a Messages request body”package main
import ( "context" "encoding/json" "fmt" "log"
toolnexus "github.com/muthuishere/toolnexus/golang")
func main() { mk := func(name, desc string) toolnexus.Tool { return toolnexus.NativeTool(name, desc, toolnexus.JSONSchema{"type": "object", "properties": map[string]any{}}, func(ctx context.Context, args map[string]any) (string, error) { return name, nil }, ) }
tools := []toolnexus.Tool{mk("search", "Search the docs"), mk("ping", "Health check")}
body := map[string]any{ "model": "claude-sonnet-4-5", "max_tokens": 1024, "messages": []any{map[string]any{"role": "user", "content": "search for adapters"}}, "tools": toolnexus.ToAnthropic(tools), }
// The whole body marshals with encoding/json — no custom marshaller needed. b, err := json.Marshal(body) if err != nil { log.Fatal(err) }
var got map[string]any _ = json.Unmarshal(b, &got) entries := got["tools"].([]any) if len(entries) != 2 { log.Fatalf("expected 2 tools, got %d", len(entries)) }
// Order is preserved. var names []string for _, e := range entries { names = append(names, e.(map[string]any)["name"].(string)) } if names[0] != "search" || names[1] != "ping" { log.Fatalf("order not preserved: %v", names) }
fmt.Println("ok:", names)}3. Round-tripping a tool_use block back to the tool
Section titled “3. Round-tripping a tool_use block back to the tool”Schema out, tool_use in. Anthropic hands input back as a JSON object, not a string — the one
real difference from OpenAI at the call site.
package main
import ( "context" "encoding/json" "fmt" "log"
toolnexus "github.com/muthuishere/toolnexus/golang")
func main() { weather := toolnexus.NativeTool( "get_weather", "Current weather for a city", toolnexus.JSONSchema{ "type": "object", "properties": map[string]any{"city": map[string]any{"type": "string"}}, "required": []string{"city"}, }, func(ctx context.Context, args map[string]any) (string, error) { return fmt.Sprintf("sunny in %v", args["city"]), nil }, ) tools := []toolnexus.Tool{weather}
// A content block as Claude would return it. raw := `{"type":"tool_use","id":"toolu_01","name":"get_weather","input":{"city":"Chennai"}}` var block struct { ID string `json:"id"` Name string `json:"name"` Input map[string]any `json:"input"` } if err := json.Unmarshal([]byte(raw), &block); err != nil { log.Fatal(err) }
var called *toolnexus.Tool for i := range tools { if tools[i].Name == block.Name { called = &tools[i] break } } if called == nil { log.Fatal("the advertised name should resolve back to the tool") }
res, err := called.Execute(block.Input, nil) if err != nil || res.Output != "sunny in Chennai" || res.IsError { log.Fatalf("unexpected: %+v %v", res, err) }
// The result goes back as a tool_result block keyed by tool_use_id. reply := map[string]any{ "type": "tool_result", "tool_use_id": block.ID, "content": res.Output, "is_error": res.IsError, } if reply["tool_use_id"] != "toolu_01" { log.Fatalf("unexpected reply: %v", reply) }
// An empty tool list is valid — it just means "no tools this turn". if len(toolnexus.ToAnthropic(nil)) != 0 { log.Fatal("expected an empty slice for no tools") }
fmt.Println("ok:", block.Name, "->", res.Output)}| Path | From | Notes |
|---|---|---|
[].name |
Tool.Name |
What the tool_use block calls back with. |
[].description |
Tool.Description |
|
[].input_schema |
Tool.InputSchema |
Renamed — inputSchema → input_schema. |
Each entry is the exported struct AnthropicTool, so you can type-assert it if you prefer that to
marshalling. There is no type field and no nesting.
See also
Section titled “See also”ToOpenAI·ToGeminiCreateToolkit—tk.ToAnthropic()is this, applied to the toolkitCreateClient— calls the adapter for youTool— the input type