-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Expand file tree
/
Copy pathdynamictool.go
More file actions
91 lines (82 loc) · 2.65 KB
/
Copy pathdynamictool.go
File metadata and controls
91 lines (82 loc) · 2.65 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
package chatd
import (
"context"
"encoding/json"
"charm.land/fantasy"
"cdr.dev/slog/v3"
"github.com/coder/coder/v2/codersdk"
)
// dynamicTool wraps a codersdk.DynamicTool as a fantasy.AgentTool.
// These tools are presented to the LLM but never executed by the
// chatloop — when the LLM calls one, the chatloop exits with
// requires_action status and the client handles execution.
// The Run method should never be called; it returns an error if
// it is, as a safety net.
type dynamicTool struct {
name string
description string
parameters map[string]any
required []string
opts fantasy.ProviderOptions
}
// dynamicToolsFromSDK converts codersdk.DynamicTool definitions
// into fantasy.AgentTool implementations for inclusion in the LLM
// tool list.
func dynamicToolsFromSDK(logger slog.Logger, tools []codersdk.DynamicTool) []fantasy.AgentTool {
if len(tools) == 0 {
return nil
}
result := make([]fantasy.AgentTool, 0, len(tools))
for _, t := range tools {
dt := &dynamicTool{
name: t.Name,
description: t.Description,
}
// InputSchema is a full JSON Schema object stored as
// json.RawMessage. Extract the "properties" and
// "required" fields that fantasy.ToolInfo expects.
if len(t.InputSchema) > 0 {
var schema struct {
Properties map[string]any `json:"properties"`
Required []string `json:"required"`
}
if err := json.Unmarshal(t.InputSchema, &schema); err != nil {
// Defensive: present the tool with no parameter
// constraints rather than failing. The LLM may
// hallucinate argument shapes, but the tool will
// still appear in the tool list.
logger.Warn(context.Background(), "failed to parse dynamic tool input schema",
slog.F("tool_name", t.Name),
slog.Error(err))
} else {
dt.parameters = schema.Properties
dt.required = schema.Required
}
}
result = append(result, dt)
}
return result
}
func (t *dynamicTool) Info() fantasy.ToolInfo {
return fantasy.ToolInfo{
Name: t.name,
Description: t.description,
Parameters: t.parameters,
Required: t.required,
}
}
func (*dynamicTool) Run(_ context.Context, _ fantasy.ToolCall) (fantasy.ToolResponse, error) {
// Dynamic tools are never executed by the chatloop. If this
// method is called, it indicates a bug in the chatloop's
// dynamic tool detection logic.
return fantasy.NewTextErrorResponse(
"dynamic tool called in chatloop — this is a bug; " +
"dynamic tools should be handled by the client",
), nil
}
func (t *dynamicTool) ProviderOptions() fantasy.ProviderOptions {
return t.opts
}
func (t *dynamicTool) SetProviderOptions(opts fantasy.ProviderOptions) {
t.opts = opts
}