forked from charmbracelet/fantasy
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtool.go
More file actions
175 lines (153 loc) · 4.96 KB
/
tool.go
File metadata and controls
175 lines (153 loc) · 4.96 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
package fantasy
import (
"context"
"encoding/json"
"fmt"
"reflect"
"charm.land/fantasy/schema"
)
// Schema represents a JSON schema for tool input validation.
type Schema = schema.Schema
// ToolInfo represents tool metadata, matching the existing pattern.
type ToolInfo struct {
Name string `json:"name"`
Description string `json:"description"`
Parameters map[string]any `json:"parameters"`
Required []string `json:"required"`
Parallel bool `json:"parallel"` // Whether this tool can run in parallel with other tools
}
// ToolCall represents a tool invocation, matching the existing pattern.
type ToolCall struct {
ID string `json:"id"`
Name string `json:"name"`
Input string `json:"input"`
}
// ToolResponse represents the response from a tool execution, matching the existing pattern.
type ToolResponse struct {
Type string `json:"type"`
Content string `json:"content"`
// Data contains binary data for image/media responses (e.g., image bytes, audio data).
Data []byte `json:"data,omitempty"`
// MediaType specifies the MIME type of the media (e.g., "image/png", "audio/wav").
MediaType string `json:"media_type,omitempty"`
Metadata string `json:"metadata,omitempty"`
IsError bool `json:"is_error"`
}
// NewTextResponse creates a text response.
func NewTextResponse(content string) ToolResponse {
return ToolResponse{
Type: "text",
Content: content,
}
}
// NewTextErrorResponse creates an error response.
func NewTextErrorResponse(content string) ToolResponse {
return ToolResponse{
Type: "text",
Content: content,
IsError: true,
}
}
// NewImageResponse creates an image response with binary data.
func NewImageResponse(data []byte, mediaType string) ToolResponse {
return ToolResponse{
Type: "image",
Data: data,
MediaType: mediaType,
}
}
// NewMediaResponse creates a media response with binary data (e.g., audio, video).
func NewMediaResponse(data []byte, mediaType string) ToolResponse {
return ToolResponse{
Type: "media",
Data: data,
MediaType: mediaType,
}
}
// WithResponseMetadata adds metadata to a response.
func WithResponseMetadata(response ToolResponse, metadata any) ToolResponse {
if metadata != nil {
metadataBytes, err := json.Marshal(metadata)
if err != nil {
return response
}
response.Metadata = string(metadataBytes)
}
return response
}
// AgentTool represents a tool that can be called by a language model.
// This matches the existing BaseTool interface pattern.
type AgentTool interface {
Info() ToolInfo
Run(ctx context.Context, params ToolCall) (ToolResponse, error)
ProviderOptions() ProviderOptions
SetProviderOptions(opts ProviderOptions)
}
// NewAgentTool creates a typed tool from a function with automatic schema generation.
// This is the recommended way to create tools.
func NewAgentTool[TInput any](
name string,
description string,
fn func(ctx context.Context, input TInput, call ToolCall) (ToolResponse, error),
) AgentTool {
var input TInput
schema := schema.Generate(reflect.TypeOf(input))
return &funcToolWrapper[TInput]{
name: name,
description: description,
fn: fn,
schema: schema,
parallel: false, // Default to sequential execution
}
}
// NewParallelAgentTool creates a typed tool from a function with automatic schema generation.
// This also marks a tool as safe to run in parallel with other tools.
func NewParallelAgentTool[TInput any](
name string,
description string,
fn func(ctx context.Context, input TInput, call ToolCall) (ToolResponse, error),
) AgentTool {
tool := NewAgentTool(name, description, fn)
// Try to use the SetParallel method if available
if setter, ok := tool.(interface{ SetParallel(bool) }); ok {
setter.SetParallel(true)
}
return tool
}
// funcToolWrapper wraps a function to implement the AgentTool interface.
type funcToolWrapper[TInput any] struct {
name string
description string
fn func(ctx context.Context, input TInput, call ToolCall) (ToolResponse, error)
schema Schema
providerOptions ProviderOptions
parallel bool
}
func (w *funcToolWrapper[TInput]) SetProviderOptions(opts ProviderOptions) {
w.providerOptions = opts
}
func (w *funcToolWrapper[TInput]) ProviderOptions() ProviderOptions {
return w.providerOptions
}
func (w *funcToolWrapper[TInput]) SetParallel(parallel bool) {
w.parallel = parallel
}
func (w *funcToolWrapper[TInput]) Info() ToolInfo {
if w.schema.Required == nil {
w.schema.Required = []string{}
}
return ToolInfo{
Name: w.name,
Description: w.description,
Parameters: schema.ToParameters(w.schema),
Required: w.schema.Required,
Parallel: w.parallel,
}
}
func (w *funcToolWrapper[TInput]) Run(ctx context.Context, params ToolCall) (ToolResponse, error) {
var input TInput
if err := json.Unmarshal([]byte(params.Input), &input); err != nil {
return NewTextErrorResponse(fmt.Sprintf("invalid parameters: %s", err)), nil
}
return w.fn(ctx, input, params)
}