mirror of
https://github.com/sst/opencode.git
synced 2025-08-08 07:18:03 +00:00
feat: batch tool
This commit is contained in:
parent
5d9058eb74
commit
a65e593ab4
4 changed files with 498 additions and 21 deletions
191
internal/llm/tools/batch.go
Normal file
191
internal/llm/tools/batch.go
Normal file
|
@ -0,0 +1,191 @@
|
|||
package tools
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type BatchToolCall struct {
|
||||
Name string `json:"name"`
|
||||
Input json.RawMessage `json:"input"`
|
||||
}
|
||||
|
||||
type BatchParams struct {
|
||||
Calls []BatchToolCall `json:"calls"`
|
||||
}
|
||||
|
||||
type BatchToolResult struct {
|
||||
ToolName string `json:"tool_name"`
|
||||
ToolInput json.RawMessage `json:"tool_input"`
|
||||
Result json.RawMessage `json:"result"`
|
||||
Error string `json:"error,omitempty"`
|
||||
// Added for better formatting and separation between results
|
||||
Separator string `json:"separator,omitempty"`
|
||||
}
|
||||
|
||||
type BatchResult struct {
|
||||
Results []BatchToolResult `json:"results"`
|
||||
}
|
||||
|
||||
type batchTool struct {
|
||||
tools map[string]BaseTool
|
||||
}
|
||||
|
||||
const (
|
||||
BatchToolName = "batch"
|
||||
BatchToolDescription = `Executes multiple tool calls in parallel and returns their results.
|
||||
|
||||
WHEN TO USE THIS TOOL:
|
||||
- Use when you need to run multiple independent tool calls at once
|
||||
- Helpful for improving performance by parallelizing operations
|
||||
- Great for gathering information from multiple sources simultaneously
|
||||
|
||||
HOW TO USE:
|
||||
- Provide an array of tool calls, each with a name and input
|
||||
- Each tool call will be executed in parallel
|
||||
- Results are returned in the same order as the input calls
|
||||
|
||||
FEATURES:
|
||||
- Runs tool calls concurrently for better performance
|
||||
- Returns both results and errors for each call
|
||||
- Maintains the order of results to match input calls
|
||||
|
||||
LIMITATIONS:
|
||||
- All tools must be available in the current context
|
||||
- Complex error handling may be required for some use cases
|
||||
- Not suitable for tool calls that depend on each other's results
|
||||
|
||||
TIPS:
|
||||
- Use for independent operations like multiple file reads or searches
|
||||
- Great for batch operations like searching multiple directories
|
||||
- Combine with other tools for more complex workflows`
|
||||
)
|
||||
|
||||
func NewBatchTool(tools map[string]BaseTool) BaseTool {
|
||||
return &batchTool{
|
||||
tools: tools,
|
||||
}
|
||||
}
|
||||
|
||||
func (b *batchTool) Info() ToolInfo {
|
||||
return ToolInfo{
|
||||
Name: BatchToolName,
|
||||
Description: BatchToolDescription,
|
||||
Parameters: map[string]any{
|
||||
"calls": map[string]any{
|
||||
"type": "array",
|
||||
"description": "Array of tool calls to execute in parallel",
|
||||
"items": map[string]any{
|
||||
"type": "object",
|
||||
"properties": map[string]any{
|
||||
"name": map[string]any{
|
||||
"type": "string",
|
||||
"description": "Name of the tool to call",
|
||||
},
|
||||
"input": map[string]any{
|
||||
"type": "object",
|
||||
"description": "Input parameters for the tool",
|
||||
},
|
||||
},
|
||||
"required": []string{"name", "input"},
|
||||
},
|
||||
},
|
||||
},
|
||||
Required: []string{"calls"},
|
||||
}
|
||||
}
|
||||
|
||||
func (b *batchTool) Run(ctx context.Context, call ToolCall) (ToolResponse, error) {
|
||||
var params BatchParams
|
||||
if err := json.Unmarshal([]byte(call.Input), ¶ms); err != nil {
|
||||
return NewTextErrorResponse(fmt.Sprintf("error parsing parameters: %s", err)), nil
|
||||
}
|
||||
|
||||
if len(params.Calls) == 0 {
|
||||
return NewTextErrorResponse("no tool calls provided"), nil
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
results := make([]BatchToolResult, len(params.Calls))
|
||||
|
||||
for i, toolCall := range params.Calls {
|
||||
wg.Add(1)
|
||||
go func(index int, tc BatchToolCall) {
|
||||
defer wg.Done()
|
||||
|
||||
// Create separator for better visual distinction between results
|
||||
separator := ""
|
||||
if index > 0 {
|
||||
separator = fmt.Sprintf("\n%s\n", strings.Repeat("=", 80))
|
||||
}
|
||||
|
||||
result := BatchToolResult{
|
||||
ToolName: tc.Name,
|
||||
ToolInput: tc.Input,
|
||||
Separator: separator,
|
||||
}
|
||||
|
||||
tool, ok := b.tools[tc.Name]
|
||||
if !ok {
|
||||
result.Error = fmt.Sprintf("tool not found: %s", tc.Name)
|
||||
results[index] = result
|
||||
return
|
||||
}
|
||||
|
||||
// Create a proper ToolCall object
|
||||
callObj := ToolCall{
|
||||
ID: fmt.Sprintf("batch-%d", index),
|
||||
Name: tc.Name,
|
||||
Input: string(tc.Input),
|
||||
}
|
||||
|
||||
response, err := tool.Run(ctx, callObj)
|
||||
if err != nil {
|
||||
result.Error = fmt.Sprintf("error executing tool %s: %s", tc.Name, err)
|
||||
results[index] = result
|
||||
return
|
||||
}
|
||||
|
||||
// Standardize metadata format if present
|
||||
if response.Metadata != "" {
|
||||
var metadata map[string]interface{}
|
||||
if err := json.Unmarshal([]byte(response.Metadata), &metadata); err == nil {
|
||||
// Add tool name to metadata for better context
|
||||
metadata["tool"] = tc.Name
|
||||
|
||||
// Re-marshal with consistent formatting
|
||||
if metadataBytes, err := json.MarshalIndent(metadata, "", " "); err == nil {
|
||||
response.Metadata = string(metadataBytes)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Convert the response to JSON
|
||||
responseJSON, err := json.Marshal(response)
|
||||
if err != nil {
|
||||
result.Error = fmt.Sprintf("error marshaling response: %s", err)
|
||||
results[index] = result
|
||||
return
|
||||
}
|
||||
|
||||
result.Result = responseJSON
|
||||
results[index] = result
|
||||
}(i, toolCall)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
batchResult := BatchResult{
|
||||
Results: results,
|
||||
}
|
||||
|
||||
resultJSON, err := json.Marshal(batchResult)
|
||||
if err != nil {
|
||||
return NewTextErrorResponse(fmt.Sprintf("error marshaling batch result: %s", err)), nil
|
||||
}
|
||||
|
||||
return NewTextResponse(string(resultJSON)), nil
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue