179 lines
4.4 KiB
Markdown
179 lines
4.4 KiB
Markdown
# Tools
|
|
|
|
## DefineTool
|
|
|
|
Define a tool the model can call during generation.
|
|
|
|
```go
|
|
type WeatherInput struct {
|
|
Location string `json:"location" jsonschema:"description=City name"`
|
|
}
|
|
|
|
type WeatherOutput struct {
|
|
Temperature float64 `json:"temperature"`
|
|
Conditions string `json:"conditions"`
|
|
}
|
|
|
|
weatherTool := genkit.DefineTool(g, "getWeather",
|
|
"Gets the current weather for a location.",
|
|
func(ctx *ai.ToolContext, input WeatherInput) (WeatherOutput, error) {
|
|
// Call your weather API
|
|
return WeatherOutput{Temperature: 72, Conditions: "sunny"}, nil
|
|
},
|
|
)
|
|
```
|
|
|
|
## Using Tools in Generation
|
|
|
|
Pass tools to `Generate`, `GenerateText`, or prompts:
|
|
|
|
```go
|
|
resp, err := genkit.Generate(ctx, g,
|
|
ai.WithModelName("googleai/gemini-flash-latest"),
|
|
ai.WithPrompt("What's the weather in San Francisco?"),
|
|
ai.WithTools(weatherTool),
|
|
)
|
|
// The model calls the tool automatically and incorporates the result
|
|
fmt.Println(resp.Text())
|
|
```
|
|
|
|
### Tool Choice
|
|
|
|
```go
|
|
ai.WithToolChoice(ai.ToolChoiceAuto) // model decides (default)
|
|
ai.WithToolChoice(ai.ToolChoiceRequired) // model must use a tool
|
|
ai.WithToolChoice(ai.ToolChoiceNone) // model cannot use tools
|
|
```
|
|
|
|
### Max Turns
|
|
|
|
Limit how many tool-call round trips the model can make:
|
|
|
|
```go
|
|
ai.WithMaxTurns(3) // default is 5
|
|
```
|
|
|
|
## DefineMultipartTool
|
|
|
|
Tools that return both structured output and media content:
|
|
|
|
```go
|
|
screenshotTool := genkit.DefineMultipartTool(g, "screenshot",
|
|
"Takes a screenshot of the current page",
|
|
func(ctx *ai.ToolContext, input any) (*ai.MultipartToolResponse, error) {
|
|
return &ai.MultipartToolResponse{
|
|
Output: map[string]any{"success": true},
|
|
Content: []*ai.Part{ai.NewMediaPart("image/png", base64Data)},
|
|
}, nil
|
|
},
|
|
)
|
|
```
|
|
|
|
## Tool Interrupts
|
|
|
|
Pause tool execution to request human input before continuing.
|
|
|
|
### Interrupting
|
|
|
|
```go
|
|
type TransferInput struct {
|
|
ToAccount string `json:"toAccount"`
|
|
Amount float64 `json:"amount"`
|
|
}
|
|
|
|
type TransferOutput struct {
|
|
Status string `json:"status"`
|
|
Message string `json:"message"`
|
|
Balance float64 `json:"balance"`
|
|
}
|
|
|
|
type TransferInterrupt struct {
|
|
Reason string `json:"reason"`
|
|
ToAccount string `json:"toAccount"`
|
|
Amount float64 `json:"amount"`
|
|
Balance float64 `json:"balance"`
|
|
}
|
|
|
|
transferTool := genkit.DefineTool(g, "transferMoney",
|
|
"Transfers money to another account.",
|
|
func(ctx *ai.ToolContext, input TransferInput) (TransferOutput, error) {
|
|
if input.Amount > accountBalance {
|
|
return TransferOutput{}, ai.InterruptWith(ctx, TransferInterrupt{
|
|
Reason: "insufficient_balance",
|
|
ToAccount: input.ToAccount,
|
|
Amount: input.Amount,
|
|
Balance: accountBalance,
|
|
})
|
|
}
|
|
// Process transfer...
|
|
return TransferOutput{Status: "success", Balance: newBalance}, nil
|
|
},
|
|
)
|
|
```
|
|
|
|
### Handling Interrupts
|
|
|
|
```go
|
|
resp, err := genkit.Generate(ctx, g,
|
|
ai.WithModelName("googleai/gemini-flash-latest"),
|
|
ai.WithTools(transferTool),
|
|
ai.WithPrompt(userRequest),
|
|
)
|
|
|
|
for resp.FinishReason == ai.FinishReasonInterrupted {
|
|
var restarts, responses []*ai.Part
|
|
|
|
for _, interrupt := range resp.Interrupts() {
|
|
meta, ok := ai.InterruptAs[TransferInterrupt](interrupt)
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
switch meta.Reason {
|
|
case "insufficient_balance":
|
|
// RestartWith: re-execute the tool with adjusted input
|
|
part, err := transferTool.RestartWith(interrupt,
|
|
ai.WithNewInput(TransferInput{
|
|
ToAccount: meta.ToAccount,
|
|
Amount: meta.Balance, // transfer what's available
|
|
}),
|
|
)
|
|
if err != nil { return err }
|
|
restarts = append(restarts, part)
|
|
|
|
case "confirm_large":
|
|
// RespondWith: provide a response directly without re-executing
|
|
part, err := transferTool.RespondWith(interrupt,
|
|
TransferOutput{Status: "cancelled", Message: "User declined"},
|
|
)
|
|
if err != nil { return err }
|
|
responses = append(responses, part)
|
|
}
|
|
}
|
|
|
|
// Continue generation with the resolved interrupts
|
|
resp, err = genkit.Generate(ctx, g,
|
|
ai.WithMessages(resp.History()...),
|
|
ai.WithTools(transferTool),
|
|
ai.WithToolRestarts(restarts...),
|
|
ai.WithToolResponses(responses...),
|
|
)
|
|
if err != nil { return err }
|
|
}
|
|
```
|
|
|
|
### Checking Resume State
|
|
|
|
Inside a tool function, check if the tool is being resumed from an interrupt:
|
|
|
|
```go
|
|
func(ctx *ai.ToolContext, input TransferInput) (TransferOutput, error) {
|
|
if ctx.IsResumed() {
|
|
// This is a resumed call after an interrupt
|
|
original, ok := ai.OriginalInputAs[TransferInput](ctx)
|
|
// original contains the input from the first call
|
|
}
|
|
// ...
|
|
}
|
|
```
|