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

Code before:
			}
			// TODO: generate and yield an auth event if needed.

			// Handle function calls.
			ev, err := handleFunctionCalls(ctx, parentCtx, req.Tools, resp)
			if err != nil {
				yield(nil, err)
				return
			}
			if ev == nil {
				// nothing to yield/process.
				return
			}
			if !yield(ev, nil) {
				return
			}

			// Actually handle "transfer_to_agent" tool. The function call sets the ev.Actions.TransferToAgent field.
			// We are followng python's execution flow which is
			//   BaseLlmFlow._postprocess_async
			//    -> _postprocess_handle_function_calls_async
			// TODO(hakim): figure out why this isn't handled by the runner.
			if ev.Actions == nil || ev.Actions.TransferToAgent == "" {
				return
			}
			nextAgent := f.agentToRun(parentCtx, ev.Actions.TransferToAgent)
			if nextAgent == nil {
				yield(nil, fmt.Errorf("failed to find agent: %s", ev.Actions.TransferToAgent))
				return
			}
			for ev, err := range nextAgent.Run(ctx, parentCtx) {
				if !yield(ev, err) || err != nil { // forward
					return
				}
			}
		}
	}
}

func (f *baseFlow) finalizeModelResponseEvent(parentCtx *adk.InvocationContext, resp *adk.LLMResponse) *adk.Event {
	// FunctionCall & FunctionResponse matching algorithm assumes non-empty function call IDs
	// but function call ID is optional in genai API and some models do not use the field.
	// Generate function call ids. (see functions.populate_client_function_call_id in python SDK)
	populateClientFunctionCallID(resp.Content)

	ev := adk.NewEvent(parentCtx.InvocationID)
	ev.Author = parentCtx.Agent.Spec().Name
	ev.Branch = parentCtx.Branch
	ev.LLMResponse = resp

	// TODO: populate ev.LongRunningToolIDs (see BaseLlmFlow._finalize_model_response_event)

Review comment (suggestion):
@hyangah jfyi -- I've updated here. This is needed for streaming api to work correctly, otherwise with adk.StreamingModeSSE on the 2nd chunk we get `Error from agent: TODO: last event is not final`, because due to this return it doesn't wait for all chunks.

Code after:
			if err := f.postprocess(ctx, parentCtx, req, resp); err != nil {
				yield(nil, err)
				return
			}
			// Skip the model response event if there is no content and no error code.
			// This is needed for the code executor to trigger another loop according to
			// adk-python src/google/adk/flows/llm_flows/base_llm_flow.py BaseLlmFlow._postprocess_async.
			if resp.Content == nil && resp.ErrorCode == 0 && !resp.Interrupted {
				continue
			}
			// Build the event and yield.
			modelResponseEvent := f.finalizeModelResponseEvent(parentCtx, resp)
			if !yield(modelResponseEvent, nil) {
				return
			}
			// TODO: generate and yield an auth event if needed.

			// Handle function calls.
			ev, err := handleFunctionCalls(ctx, parentCtx, req.Tools, resp)
			if err != nil {
				yield(nil, err)
				return
			}
			if ev == nil {
				// nothing to yield/process.
				continue
			}
			if !yield(ev, nil) {
				return
			}

			// Actually handle "transfer_to_agent" tool. The function call sets the ev.Actions.TransferToAgent field.
			// We are followng python's execution flow which is
			//   BaseLlmFlow._postprocess_async
			//    -> _postprocess_handle_function_calls_async
			// TODO(hakim): figure out why this isn't handled by the runner.
			if ev.Actions == nil || ev.Actions.TransferToAgent == "" {
				return
			}
			nextAgent := f.agentToRun(parentCtx, ev.Actions.TransferToAgent)
			if nextAgent == nil {
				yield(nil, fmt.Errorf("failed to find agent: %s", ev.Actions.TransferToAgent))
				return
			}
			for ev, err := range nextAgent.Run(ctx, parentCtx) {
				if !yield(ev, err) || err != nil { // forward
					return
				}
			}
		}
	}