Tool Calling
As covered under Foundations, tools are objects that can be called by the model to perform a specific task.
Go AI SDK Core tools contain several core elements:
Name: The name of the toolDescription: An optional description of the tool that can influence when the tool is pickedParameters: A JSON schema that defines the input parameters. The schema is consumed by the LLM and also used to validate the LLM tool callsExecute: An optional function that is called with the inputs from the tool call. It is optional because you might want to forward tool calls to the client or to a queue instead of executing them in the same processStrict: (optional) Enables strict tool calling when supported by the provider
The Tools parameter of GenerateText and StreamText is a slice of tools:
package main
import (
"context"
"fmt"
"log"
"math/rand"
"os"
"github.com/digitallysavvy/go-ai/pkg/ai"
"github.com/digitallysavvy/go-ai/pkg/provider/types"
"github.com/digitallysavvy/go-ai/pkg/providers/openai"
)
func main() {
ctx := context.Background()
provider := openai.New(openai.Config{APIKey: os.Getenv("OPENAI_API_KEY")})
model, _ := provider.LanguageModel("gpt-5.2")
weatherTool := types.Tool{
Name: "weather",
Description: "Get the weather in a location",
Parameters: map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"location": map[string]interface{}{
"type": "string",
"description": "The location to get the weather for",
},
},
"required": []string{"location"},
},
Execute: func(ctx context.Context, input map[string]interface{}, opts types.ToolExecutionOptions) (interface{}, error) {
location := input["location"].(string)
return map[string]interface{}{
"location": location,
"temperature": 72 + rand.Intn(21) - 10,
}, nil
},
}
maxSteps := 5
result, err := ai.GenerateText(ctx, ai.GenerateTextOptions{
Model: model,
Tools: []types.Tool{weatherTool},
MaxSteps: &maxSteps,
Prompt: "What is the weather in San Francisco?",
})
if err != nil {
log.Fatal(err)
}
fmt.Println(result.Text)
}
Note: When a model uses a tool, it is called a "tool call" and the output of the tool is called a "tool result".
Strict Mode
When enabled, language model providers that support strict tool calling will only generate tool calls that are valid according to your defined Parameters schema. This increases the reliability of tool calling. However, not all schemas may be supported in strict mode, and what is supported depends on the specific provider.
By default, strict mode is disabled. You can enable it per-tool by setting Strict: types.BoolPtr(true):
weatherTool := types.Tool{
Name: "weather",
Description: "Get the weather in a location",
Parameters: map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"location": map[string]interface{}{"type": "string"},
},
"required": []string{"location"},
},
Strict: types.BoolPtr(true), // Enable strict validation for this tool
Execute: func(ctx context.Context, input map[string]interface{}, opts types.ToolExecutionOptions) (interface{}, error) {
// ...
return nil, nil
},
}
Note: Not all providers or models support strict mode. For those that do not, this option is ignored.
Multi-Step Calls
With the MaxSteps setting, you can enable multi-step calls in GenerateText and StreamText. When MaxSteps is set and the model generates a tool call, the AI SDK will trigger a new generation passing in the tool result until there are no further tool calls or the maximum steps is reached.
Note: The step limit is only evaluated when the last step contains tool results.
By default, when you use GenerateText or StreamText, it triggers a single generation. This works well for many use cases where you can rely on the model's training data to generate a response. However, when you provide tools, the model now has the choice to either generate a normal text response, or generate a tool call. If the model generates a tool call, its generation is complete and that step is finished.
You may want the model to generate text after the tool has been executed, either to summarize the tool results in the context of the user's query. In many cases, you may also want the model to use multiple tools in a single response. This is where multi-step calls come in.
Example
In the following example, there are two steps:
-
Step 1
- The prompt
"What is the weather in San Francisco?"is sent to the model - The model generates a tool call
- The tool call is executed
- The prompt
-
Step 2
- The tool result is sent to the model
- The model generates a response considering the tool result
maxSteps := 5
result, err := ai.GenerateText(ctx, ai.GenerateTextOptions{
Model: model,
Tools: []types.Tool{
{
Name: "weather",
Description: "Get the weather in a location",
Parameters: map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"location": map[string]interface{}{
"type": "string",
"description": "The location to get the weather for",
},
},
"required": []string{"location"},
},
Execute: func(ctx context.Context, input map[string]interface{}, opts types.ToolExecutionOptions) (interface{}, error) {
location := input["location"].(string)
return map[string]interface{}{
"location": location,
"temperature": 72 + rand.Intn(21) - 10,
}, nil
},
},
},
MaxSteps: &maxSteps, // Stop after a maximum of 5 steps if tools were called
Prompt: "What is the weather in San Francisco?",
})
Note: You can use
StreamTextin a similar way.
Steps
To access intermediate tool calls and results, you can use the Steps field in the result object or the OnFinish callback. It contains all the text, tool calls, tool results, and more from each step.
Example: Extract tool results from all steps
maxSteps := 10
result, err := ai.GenerateText(ctx, ai.GenerateTextOptions{
Model: model,
MaxSteps: &maxSteps,
Tools: tools,
Prompt: "What's the weather in Tokyo and Paris?",
})
if err != nil {
log.Fatal(err)
}
// Extract all tool calls from the steps
var allToolCalls []types.ToolCall
for _, step := range result.Steps {
allToolCalls = append(allToolCalls, step.ToolCalls...)
}
fmt.Printf("Total tool calls: %d\n", len(allToolCalls))
OnStepFinish Callback
When using GenerateText or StreamText, you can provide an OnStepFinish callback that is triggered when a step is finished, i.e. all text deltas, tool calls, and tool results for the step are available. When you have multiple steps, the callback is triggered for each step.
maxSteps := 5
result, err := ai.GenerateText(ctx, ai.GenerateTextOptions{
Model: model,
Tools: tools,
MaxSteps: &maxSteps,
Prompt: "What's the weather in multiple cities?",
OnStepFinish: func(ctx context.Context, step types.StepResult, userContext interface{}) {
// Your own logic, e.g. for saving the chat history or recording usage
fmt.Printf("Step %d finished\n", step.StepNumber)
fmt.Printf(" Text: %s\n", step.Text)
fmt.Printf(" Tool calls: %d\n", len(step.ToolCalls))
fmt.Printf(" Tool results: %d\n", len(step.ToolResults))
fmt.Printf(" Finish reason: %s\n", step.FinishReason)
fmt.Printf(" Usage: %+v\n", step.Usage)
},
})
Response Messages
Adding the generated assistant and tool messages to your conversation history is a common task, especially if you are using multi-step tool calls.
Both GenerateText and StreamText have a Response.Messages property that you can use to add the assistant and tool messages to your conversation history. It is also available in the OnFinish callback of StreamText.
The Response.Messages field contains a slice of Message objects that you can add to your conversation history:
import "github.com/digitallysavvy/go-ai/pkg/provider/types"
maxSteps := 5
messages := []types.Message{
{Role: types.RoleUser, Content: []types.ContentPart{types.TextContent{Text: "What's the weather?"}}},
}
result, err := ai.GenerateText(ctx, ai.GenerateTextOptions{
Model: model,
Messages: messages,
Tools: tools,
MaxSteps: &maxSteps,
})
if err != nil {
log.Fatal(err)
}
// Add the response messages from the last step to your conversation history
if len(result.Steps) > 0 {
lastStep := result.Steps[len(result.Steps)-1]
messages = append(messages, lastStep.ResponseMessages...)
}
// Continue the conversation
result2, err := ai.GenerateText(ctx, ai.GenerateTextOptions{
Model: model,
Messages: messages,
Tools: tools,
MaxSteps: &maxSteps,
})
Tool Choice
You can use the ToolChoice setting to influence when a tool is selected. It supports the following settings:
auto(default): the model can choose whether and which tools to callrequired: the model must call a tool. It can choose which tool to callnone: the model must not call toolstool: the model must call the specified tool
// Auto: Let the model decide (default)
result, _ := ai.GenerateText(ctx, ai.GenerateTextOptions{
Model: model,
Tools: []types.Tool{weatherTool},
ToolChoice: types.ToolChoice{Type: "auto"},
Prompt: "What's the weather?",
})
// Required: Force the model to call a tool
result, _ := ai.GenerateText(ctx, ai.GenerateTextOptions{
Model: model,
Tools: []types.Tool{weatherTool},
ToolChoice: types.ToolChoice{Type: "required"},
Prompt: "What's the weather in San Francisco?",
})
// None: Prevent the model from using tools
result, _ := ai.GenerateText(ctx, ai.GenerateTextOptions{
Model: model,
Tools: []types.Tool{weatherTool},
ToolChoice: types.ToolChoice{Type: "none"},
Prompt: "Tell me about San Francisco.",
})
// Specific: Force the model to use a specific tool
result, _ := ai.GenerateText(ctx, ai.GenerateTextOptions{
Model: model,
Tools: []types.Tool{weatherTool, calculatorTool},
ToolChoice: types.ToolChoice{
Type: "tool",
ToolName: "weather",
},
Prompt: "What's the weather?",
})
Tool Execution Options
When tools are called, they receive the context and input parameters. You can use the context for cancellation, timeouts, and passing request-scoped values.
Context Cancellation
The abort signals from GenerateText and StreamText are forwarded to the tool execution via the context:
// Create a cancellable context
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
weatherTool := types.Tool{
Name: "weather",
Description: "Get the weather in a location",
Parameters: map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"location": map[string]interface{}{"type": "string"},
},
"required": []string{"location"},
},
Execute: func(ctx context.Context, input map[string]interface{}, opts types.ToolExecutionOptions) (interface{}, error) {
location := input["location"].(string)
// Create HTTP request with context for cancellation
req, err := http.NewRequestWithContext(
ctx,
"GET",
fmt.Sprintf("https://api.weatherapi.com/v1/current.json?q=%s", location),
nil,
)
if err != nil {
return nil, err
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
// Parse and return weather data
var data map[string]interface{}
json.NewDecoder(resp.Body).Decode(&data)
return data, nil
},
}
result, err := ai.GenerateText(ctx, ai.GenerateTextOptions{
Model: model,
Tools: []types.Tool{weatherTool},
Prompt: "What's the weather in San Francisco?",
})
Tool Timeouts
Set timeouts for tool execution to prevent long-running tools from blocking the step loop. The Go AI SDK provides both global and per-tool timeout configuration.
Global Tool Timeout
Set a default timeout for all tools using ToolMs:
import (
"time"
"github.com/digitallysavvy/go-ai/pkg/ai"
"github.com/digitallysavvy/go-ai/pkg/provider/types"
)
fiveSeconds := 5 * time.Second
result, err := ai.GenerateText(ctx, ai.GenerateTextOptions{
Model: model,
Prompt: "What's the weather?",
Tools: []types.Tool{weatherTool},
StopWhen: []ai.StopCondition{ai.IsStepCount(5)},
Timeout: &ai.TimeoutConfig{
ToolMs: &fiveSeconds, // 5 second timeout for all tools
},
})
if err != nil {
log.Fatal(err)
}
fmt.Println(result.Text)
Per-Tool Timeout
Override timeouts for individual tools using the Tools map. Per-tool timeouts take precedence over the global ToolMs:
import "time"
fiveSeconds := 5 * time.Second
result, err := ai.GenerateText(ctx, ai.GenerateTextOptions{
Model: model,
Prompt: "Search for weather and news",
Tools: []types.Tool{weatherTool, searchTool},
StopWhen: []ai.StopCondition{ai.IsStepCount(5)},
Timeout: &ai.TimeoutConfig{
ToolMs: &fiveSeconds, // Default: 5s for all tools
Tools: map[string]time.Duration{
"get_weather": 5 * time.Second, // 5s for weather
"search_web": 15 * time.Second, // 15s for web search
},
},
})
if err != nil {
log.Fatal(err)
}
fmt.Println(result.Text)
Timeout Resolution Order
The GetToolTimeout() helper resolves the effective timeout for a tool name using this priority:
- Per-tool override (
Tools[toolName]) — highest priority - Global default (
ToolMs) — used if no per-tool entry exists - No timeout — if neither is set, the tool runs without a timeout
timeout := &ai.TimeoutConfig{
ToolMs: &fiveSeconds,
Tools: map[string]time.Duration{
"slow_tool": 30 * time.Second,
},
}
timeout.GetToolTimeout("slow_tool") // 30s (per-tool override)
timeout.GetToolTimeout("fast_tool") // 5s (global default)
timeout.GetToolTimeout("another_tool") // 5s (global default)
Timeout Behavior
When a tool exceeds its timeout, the tool's context is cancelled and the Execute function receives a context.DeadlineExceeded error. Tools that respect context cancellation will stop immediately:
Execute: func(ctx context.Context, input map[string]interface{}, opts types.ToolExecutionOptions) (interface{}, error) {
// Long-running operation that respects context
req, err := http.NewRequestWithContext(ctx, "GET", apiURL, nil)
if err != nil {
return nil, err
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
// Returns context.DeadlineExceeded if the tool timeout fires
return nil, fmt.Errorf("request failed: %w", err)
}
defer resp.Body.Close()
// Process response...
return result, nil
},
Tip: Always use
http.NewRequestWithContext(ctx, ...)and pass the context to external calls so they respect tool timeouts and cancellation.
Complex Tool Examples
Multiple Tools
Using multiple tools together:
calculatorTool := types.Tool{
Name: "calculator",
Description: "Perform mathematical calculations",
Parameters: map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"operation": map[string]interface{}{
"type": "string",
"enum": []string{"add", "subtract", "multiply", "divide"},
},
"a": map[string]interface{}{"type": "number"},
"b": map[string]interface{}{"type": "number"},
},
"required": []string{"operation", "a", "b"},
},
Execute: func(ctx context.Context, input map[string]interface{}, opts types.ToolExecutionOptions) (interface{}, error) {
op := input["operation"].(string)
a := input["a"].(float64)
b := input["b"].(float64)
var result float64
switch op {
case "add":
result = a + b
case "subtract":
result = a - b
case "multiply":
result = a * b
case "divide":
if b == 0 {
return nil, fmt.Errorf("division by zero")
}
result = a / b
}
return map[string]interface{}{"result": result}, nil
},
}
searchTool := types.Tool{
Name: "web_search",
Description: "Search the web for information",
Parameters: map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"query": map[string]interface{}{
"type": "string",
"description": "The search query",
},
},
"required": []string{"query"},
},
Execute: func(ctx context.Context, input map[string]interface{}, opts types.ToolExecutionOptions) (interface{}, error) {
query := input["query"].(string)
// Call search API
return map[string]interface{}{
"results": []string{
"Result 1 for: " + query,
"Result 2 for: " + query,
},
}, nil
},
}
maxSteps := 5
result, _ := ai.GenerateText(ctx, ai.GenerateTextOptions{
Model: model,
Tools: []types.Tool{calculatorTool, searchTool, weatherTool},
Prompt: "Search for the population of Tokyo, then calculate what " +
"percentage it is of Japan's total population (125 million)",
MaxSteps: &maxSteps,
})
fmt.Println(result.Text)
Tool with Error Handling
Robust tool implementation with comprehensive error handling:
weatherTool := types.Tool{
Name: "get_weather",
Description: "Get current weather for a location",
Parameters: map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"location": map[string]interface{}{
"type": "string",
"description": "City name",
},
"units": map[string]interface{}{
"type": "string",
"enum": []string{"celsius", "fahrenheit"},
"default": "celsius",
},
},
"required": []string{"location"},
},
Execute: func(ctx context.Context, input map[string]interface{}, opts types.ToolExecutionOptions) (interface{}, error) {
// Validate input
location, ok := input["location"].(string)
if !ok || location == "" {
return nil, fmt.Errorf("location must be a non-empty string")
}
units := "celsius"
if u, ok := input["units"].(string); ok {
units = u
}
// Check for context cancellation
select {
case <-ctx.Done():
return nil, ctx.Err()
default:
}
// Call weather API
apiKey := os.Getenv("WEATHER_API_KEY")
url := fmt.Sprintf(
"https://api.weatherapi.com/v1/current.json?key=%s&q=%s",
apiKey,
location,
)
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
if err != nil {
return nil, fmt.Errorf("failed to create request: %w", err)
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, fmt.Errorf("failed to fetch weather: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("weather API returned status: %d", resp.StatusCode)
}
var data map[string]interface{}
if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
return nil, fmt.Errorf("failed to decode response: %w", err)
}
// Extract and format weather data
current := data["current"].(map[string]interface{})
temp := current["temp_c"].(float64)
if units == "fahrenheit" {
temp = temp*9/5 + 32
}
return map[string]interface{}{
"location": location,
"temperature": temp,
"units": units,
"condition": current["condition"].(map[string]interface{})["text"],
}, nil
},
}
Tool with Streaming Progress
For long-running operations, you can provide progress updates:
dataProcessingTool := types.Tool{
Name: "process_data",
Description: "Process a large dataset",
Parameters: map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"dataset_id": map[string]interface{}{"type": "string"},
},
"required": []string{"dataset_id"},
},
Execute: func(ctx context.Context, input map[string]interface{}, opts types.ToolExecutionOptions) (interface{}, error) {
datasetID := input["dataset_id"].(string)
// Simulate long-running processing with progress updates
total := 100
for i := 0; i < total; i++ {
// Check for cancellation
select {
case <-ctx.Done():
return nil, ctx.Err()
default:
}
// Simulate work
time.Sleep(100 * time.Millisecond)
// In a real implementation, you might send progress
// via a channel or callback mechanism
if i%10 == 0 {
log.Printf("Processing: %d%% complete", i)
}
}
return map[string]interface{}{
"status": "completed",
"dataset_id": datasetID,
"records": 1000,
}, nil
},
}
Tool Packaging
Create reusable tool packages:
// weathertools/weather.go
package weathertools
import (
"context"
"github.com/digitallysavvy/go-ai/pkg/provider/types"
)
func NewWeatherTool(apiKey string) types.Tool {
return types.Tool{
Name: "get_weather",
Description: "Get current weather for a location",
Parameters: map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"location": map[string]interface{}{
"type": "string",
},
},
"required": []string{"location"},
},
Execute: func(ctx context.Context, input map[string]interface{}, opts types.ToolExecutionOptions) (interface{}, error) {
// Implementation using apiKey
return nil, nil
},
}
}
func NewCalculatorTool() types.Tool {
return types.Tool{
Name: "calculator",
Description: "Perform calculations",
Parameters: map[string]interface{}{
// ...
},
Execute: func(ctx context.Context, input map[string]interface{}, opts types.ToolExecutionOptions) (interface{}, error) {
// Implementation
return nil, nil
},
}
}
Usage:
import "myapp/weathertools"
weatherTool := weathertools.NewWeatherTool(apiKey)
calcTool := weathertools.NewCalculatorTool()
result, _ := ai.GenerateText(ctx, ai.GenerateTextOptions{
Model: model,
Tools: []types.Tool{weatherTool, calcTool},
StopWhen: []ai.StopCondition{ai.IsStepCount(5)},
Prompt: "What's the weather and calculate something",
})
Error Handling
Handle tool execution errors:
maxSteps := 5
result, err := ai.GenerateText(ctx, ai.GenerateTextOptions{
Model: model,
Tools: tools,
MaxSteps: &maxSteps,
Prompt: "Use tools",
})
if err != nil {
// Check for specific error types
var noSuchToolErr *ai.NoSuchToolError
var invalidInputErr *ai.InvalidToolInputError
if errors.As(err, &noSuchToolErr) {
fmt.Println("Model tried to call unknown tool")
} else if errors.As(err, &invalidInputErr) {
fmt.Println("Model called tool with invalid inputs")
} else {
fmt.Printf("Error: %v\n", err)
}
return
}
// Check for tool errors in the steps
for _, step := range result.Steps {
for _, toolResult := range step.ToolResults {
if toolResult.Error != nil {
fmt.Printf("Tool %s failed: %v\n", toolResult.ToolName, toolResult.Error)
}
}
}
Best Practices
- Clear Descriptions: Write clear tool descriptions to help the model know when to use them
- Schema Validation: Define comprehensive parameter schemas with descriptions
- Error Handling: Return clear error messages when tools fail
- Context Support: Respect context cancellation in long-running tools
- Idempotency: Make tools idempotent when possible
- Logging: Log tool executions for debugging
- Rate Limiting: Implement rate limiting for external API calls
- Type Safety: Use Go structs for input validation
- Timeouts: Set appropriate timeouts for external calls
- Testing: Write unit tests for tool execution logic
Concurrency and Parallel Tool Execution
The Go AI SDK executes tools sequentially by default, but you can implement parallel execution patterns:
type ParallelToolExecutor struct {
maxConcurrent int
}
func (e *ParallelToolExecutor) ExecuteTools(
ctx context.Context,
toolCalls []types.ToolCall,
tools map[string]types.Tool,
) []types.ToolResult {
results := make([]types.ToolResult, len(toolCalls))
var wg sync.WaitGroup
sem := make(chan struct{}, e.maxConcurrent)
for i, toolCall := range toolCalls {
wg.Add(1)
go func(idx int, tc types.ToolCall) {
defer wg.Done()
// Acquire semaphore
sem <- struct{}{}
defer func() { <-sem }()
tool, ok := tools[tc.ToolName]
if !ok {
results[idx] = types.ToolResult{
ToolCallID: tc.ID,
ToolName: tc.ToolName,
Error: fmt.Errorf("tool not found: %s", tc.ToolName),
}
return
}
output, err := tool.Execute(ctx, tc.Arguments, types.ToolExecutionOptions{
ToolCallID: tc.ID,
})
results[idx] = types.ToolResult{
ToolCallID: tc.ID,
ToolName: tc.ToolName,
Result: output,
Error: err,
}
}(i, toolCall)
}
wg.Wait()
return results
}
Advanced Patterns
Conditional Tool Selection
Dynamically select tools based on context:
func getAvailableTools(userRole string) []types.Tool {
basicTools := []types.Tool{weatherTool, calculatorTool}
if userRole == "admin" {
return append(basicTools, adminTools...)
}
return basicTools
}
result, _ := ai.GenerateText(ctx, ai.GenerateTextOptions{
Model: model,
Tools: getAvailableTools(user.Role),
StopWhen: []ai.StopCondition{ai.IsStepCount(5)},
Prompt: prompt,
})
Tool Chaining
Chain tool results:
maxSteps := 10
result, _ := ai.GenerateText(ctx, ai.GenerateTextOptions{
Model: model,
Tools: []types.Tool{searchTool, summarizeTool, translateTool},
Prompt: "Search for AI news, summarize it, then translate to Spanish",
MaxSteps: &maxSteps,
OnStepFinish: func(ctx context.Context, step types.StepResult, userContext interface{}) {
fmt.Printf("Step %d: Used %d tools\n", step.StepNumber, len(step.ToolCalls))
},
})
Deferred Tool Search
For large tool registries, mark tools DeferLoading: true and add ai.ToolSearch() so the model discovers only the tools it needs, instead of seeing every definition up front. By default, ai.ToolSearch() ranks deferred tools with built-in keyword scoring over their names and descriptions, returning up to five matches per call:
tools := []types.Tool{
ai.ToolSearch(),
{
Name: "getWeather",
DeferLoading: true,
Description: "Get the current weather for a city",
// ...
},
}
Use ToolSearchConfig.MaxResults to change how many matches a search returns (must be a positive integer; omitted or zero defaults to five), and ToolSearchConfig.Search to replace the built-in keyword scoring with your own ranking function:
tools := []types.Tool{
ai.ToolSearch(ai.ToolSearchConfig{
MaxResults: 10,
Search: func(ctx context.Context, query string, candidates []ai.ToolSearchCandidate) ([]string, error) {
// Return matching tool names in ranked order, e.g. from a
// vector search or external index. Unknown names and
// duplicates are ignored, and the result is capped at
// MaxResults.
return rankToolsByEmbedding(ctx, query, candidates)
},
}),
// ... deferred tools
}
Next Steps
- Learn about Embeddings
- Explore Agents
- See Tool Examples