package llm import ( "context" "runtime" "sync" "log/slog" ) // callTools executes each tool call in the message concurrently and returns // the resulting tool result messages in the same order as the input calls. func (c *Client) callTools(ctx context.Context, msg Message) []Message { var ( results = make([]Message, len(msg.ToolCalls)) sem = make(chan struct{}, runtime.NumCPU()*2) wg sync.WaitGroup ) for i, tc := range msg.ToolCalls { wg.Add(1) go func(idx int, tc ToolCall) { defer wg.Done() sem <- struct{}{} defer func() { <-sem }() c.log.InfoContext( ctx, "calling tool", slog.String("name", tc.Function.Name), slog.String("args", tc.Function.Arguments), ) result, err := c.registry.Execute( ctx, tc.Function.Name, tc.Function.Arguments, ) if err != nil { c.log.ErrorContext( ctx, "failed to call tool", slog.Any("error", err), slog.String("name", tc.Function.Name), ) results[idx] = Message{ Role: RoleTool, ContentParts: []ContentPart{{ Type: ContentTypeText, Text: err.Error(), }}, Name: tc.Function.Name, ToolCallID: tc.ID, } return } c.log.InfoContext( ctx, "called tool", slog.String("name", tc.Function.Name), ) formatted := result.FormatResult() results[idx] = Message{ Role: RoleTool, ContentParts: []ContentPart{{ Type: ContentTypeText, Text: formatted, }}, Name: tc.Function.Name, ToolCallID: tc.ID, } }(i, tc) } wg.Wait() return results }