Repository: google/adk-go
File: agent/llm_agent.go (Go)

Code before:
		}
	}
	return nil
}

func (f *baseFlow) postprocessHandleFunctionCalls(ctx context.Context, parentCtx *adk.InvocationContext, req *adk.LLMRequest, resp *adk.LLMResponse) adk.EventStream {
	return func(yield func(*adk.Event, error) bool) {
		ev, err := handleFunctionCalls(ctx, parentCtx, req.Tools, resp)
		if err != nil {
			yield(nil, err)
			return
		}
		// TODO: generate and yield an auth event if needed.

		if !yield(ev, nil) {
			return
		}

		// TODO: handle ev.Actions such as TransferToAgent set by transfer_to_agent tool.
	}
}

// handleFunctionCalls calls the functions and returns the function response event.
//
// TODO: accept filters to include/exclude function calls.
func handleFunctionCalls(ctx context.Context, parentCtx *adk.InvocationContext, toolsDict map[string]adk.Tool, resp *adk.LLMResponse) (*adk.Event, error) {
	var fnResponseEvents []*adk.Event

	fnCalls := functionCalls(resp.Content)
	for _, fnCall := range fnCalls {
		tool, ok := toolsDict[fnCall.Name]
		if !ok {
			return nil, fmt.Errorf("unknown tool: %q", fnCall.Name)
		}
		toolCtx := &adk.ToolContext{
			InvocationContext: parentCtx,
			FunctionCallID:    fnCall.ID,
		}
		// TODO: agent.canonical_before_tool_callbacks
		result, err := tool.Run(ctx, toolCtx, fnCall.Args)
		// genai.FunctionResponse expects to use "output" key to specify function output
		// and "error" key to specify error details (if any). If "output" and "error" keys
		// are not specified, then whole "response" is treated as function output.
		// TODO(hakim): revisit the tool's function signature to handle error from user function better.
		if err != nil {
			result = map[string]any{"error": fmt.Errorf("tool %q failed: %w", tool.Name(), err)}
		}
		if err != nil {
			return nil, fmt.Errorf("tool %q failed to run: %w", tool.Name(), err)
			// TODO: should we include the fn.Args in the error? That may have sensitive info.
		}

Review comment (suggestion):
```suggestion
// TODO: check feasibility of running tool.Run concurrently
func handleFunctionCalls(ctx context.Context, parentCtx *adk.InvocationContext, toolsDict map[string]adk.Tool, resp *adk.LLMResponse) (*adk.Event, error) {
```

Code after:
func (f *baseFlow) postprocessHandleFunctionCalls(ctx context.Context, parentCtx *adk.InvocationContext, req *adk.LLMRequest, resp *adk.LLMResponse) adk.EventStream {
	return func(yield func(*adk.Event, error) bool) {
		ev, err := handleFunctionCalls(ctx, parentCtx, req.Tools, resp)
		if err != nil {
			yield(nil, err)
			return
		}
		if ev == nil {
			// nothing to yield.
			return
		}
		// TODO: generate and yield an auth event if needed.

		if !yield(ev, nil) {
			return
		}

		// TODO: handle ev.Actions such as TransferToAgent set by transfer_to_agent tool.
	}
}

// handleFunctionCalls calls the functions and returns the function response event.
//
// TODO: accept filters to include/exclude function calls.
// TODO: check feasibility of running tool.Run concurrently.
func handleFunctionCalls(ctx context.Context, parentCtx *adk.InvocationContext, toolsDict map[string]adk.Tool, resp *adk.LLMResponse) (*adk.Event, error) {
	var fnResponseEvents []*adk.Event

	fnCalls := functionCalls(resp.Content)
	for _, fnCall := range fnCalls {
		tool, ok := toolsDict[fnCall.Name]
		if !ok {
			return nil, fmt.Errorf("unknown tool: %q", fnCall.Name)
		}
		toolCtx := &adk.ToolContext{
			InvocationContext: parentCtx,
			FunctionCallID:    fnCall.ID,
		}
		// TODO: agent.canonical_before_tool_callbacks
		result, err := tool.Run(ctx, toolCtx, fnCall.Args)
		// genai.FunctionResponse expects to use "output" key to specify function output
		// and "error" key to specify error details (if any). If "output" and "error" keys
		// are not specified, then whole "response" is treated as function output.
		// TODO(hakim): revisit the tool's function signature to handle error from user function better.
		if err != nil {
			result = map[string]any{"error": fmt.Errorf("tool %q failed: %w", tool.Name(), err)}
		}
		// TODO: agent.canonical_after_tool_callbacks
		// TODO: handle long-running tool.
		ev := adk.NewEvent(parentCtx.InvocationID)
		ev.LLMResponse = &adk.LLMResponse{