github.com/anthropics/anthropic-sdk-go@v1.70.1/toolrunner/tool.go (about) 1 package toolrunner 2 3 import ( 4 "context" 5 "encoding/json" 6 "fmt" 7 8 anthropic "github.com/anthropics/anthropic-sdk-go" 9 "github.com/invopop/jsonschema" 10 ) 11 12 // betaTool is the internal generic implementation of anthropic.BetaTool. 13 // Users never see this type directly - they work with the BetaTool interface. 14 // The generic type parameter T is used internally for type-safe JSON unmarshaling. 15 type betaTool[T any] struct { 16 name string 17 description string 18 schema anthropic.BetaToolInputSchemaParam 19 handler func(context.Context, T) (anthropic.BetaToolResultBlockParamContentUnion, error) 20 } 21 22 func (t *betaTool[T]) Name() string { return t.name } 23 func (t *betaTool[T]) Description() string { return t.description } 24 func (t *betaTool[T]) InputSchema() anthropic.BetaToolInputSchemaParam { return t.schema } 25 26 func (t *betaTool[T]) Execute(ctx context.Context, input json.RawMessage) ([]anthropic.BetaToolResultBlockParamContentUnion, error) { 27 parsed, err := t.parse(input) 28 if err != nil { 29 return nil, fmt.Errorf("failed to parse tool input: %w", err) 30 } 31 result, err := t.handler(ctx, parsed) 32 if err != nil { 33 return nil, err 34 } 35 return []anthropic.BetaToolResultBlockParamContentUnion{result}, nil 36 } 37 38 // parse validates and parses the input according to the tool's schema. 39 // This method handles special cases for json.RawMessage and []byte type parameters. 40 func (t *betaTool[T]) parse(input json.RawMessage) (T, error) { 41 var parsed T 42 43 switch any(parsed).(type) { 44 case json.RawMessage: 45 // If T is json.RawMessage, return the input as is 46 if result, ok := any(input).(T); ok { 47 return result, nil 48 } 49 return parsed, fmt.Errorf("type assertion failed for json.RawMessage") 50 case []byte: 51 // If T is []byte, return the raw JSON input as bytes 52 if result, ok := any([]byte(input)).(T); ok { 53 return result, nil 54 } 55 return parsed, fmt.Errorf("type assertion failed for []byte") 56 default: 57 // For all other types (structs), unmarshal the input 58 if err := json.Unmarshal(input, &parsed); err != nil { 59 return parsed, err 60 } 61 return parsed, nil 62 } 63 } 64 65 func parseSchemaMap(s map[string]any) (anthropic.BetaToolInputSchemaParam, error) { 66 bytes, err := json.Marshal(s) 67 if err != nil { 68 return anthropic.BetaToolInputSchemaParam{}, fmt.Errorf("failed to marshal schema: %w", err) 69 } 70 71 var schema anthropic.BetaToolInputSchemaParam 72 if err := json.Unmarshal(bytes, &schema); err != nil { 73 return anthropic.BetaToolInputSchemaParam{}, fmt.Errorf("failed to unmarshal schema: %w", err) 74 } 75 76 return schema, nil 77 } 78 79 // NewBetaToolFromBytes creates a BetaTool from JSON schema bytes. 80 func NewBetaToolFromBytes[T any]( 81 name, description string, 82 schemaJSON []byte, 83 handler func(context.Context, T) (anthropic.BetaToolResultBlockParamContentUnion, error), 84 ) (anthropic.BetaTool, error) { 85 var schema anthropic.BetaToolInputSchemaParam 86 if err := schema.UnmarshalJSON(schemaJSON); err != nil { 87 return nil, fmt.Errorf("failed to unmarshal schema: %w", err) 88 } 89 return NewBetaTool(name, description, schema, handler), nil 90 } 91 92 // NewBetaToolFromJSONSchema creates a BetaTool by inferring the schema from struct type T using reflection. 93 // The struct should use jsonschema tags to define the schema (e.g., `jsonschema:"required,description=..."`). 94 func NewBetaToolFromJSONSchema[T any]( 95 name, description string, 96 handler func(context.Context, T) (anthropic.BetaToolResultBlockParamContentUnion, error), 97 ) (anthropic.BetaTool, error) { 98 var zeroValue T 99 reflector := jsonschema.Reflector{ 100 AllowAdditionalProperties: false, 101 RequiredFromJSONSchemaTags: true, 102 DoNotReference: true, 103 } 104 105 schema := reflector.Reflect(zeroValue) 106 107 schemaBytes, err := json.Marshal(schema) 108 if err != nil { 109 return nil, err 110 } 111 var schemaMap map[string]any 112 if err := json.Unmarshal(schemaBytes, &schemaMap); err != nil { 113 return nil, err 114 } 115 116 inputSchema, err := parseSchemaMap(schemaMap) 117 if err != nil { 118 return nil, err 119 } 120 121 return NewBetaTool(name, description, inputSchema, handler), nil 122 } 123 124 // NewBetaTool creates a BetaTool with a BetaToolInputSchemaParam directly. 125 func NewBetaTool[T any]( 126 name, description string, 127 schema anthropic.BetaToolInputSchemaParam, 128 handler func(context.Context, T) (anthropic.BetaToolResultBlockParamContentUnion, error), 129 ) anthropic.BetaTool { 130 return &betaTool[T]{ 131 name: name, 132 description: description, 133 schema: schema, 134 handler: handler, 135 } 136 }