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

Code before:
		for resp, err := range f.callLLM(ctx, parentCtx, req) {
			if err != nil {
				yield(nil, err)
				return
			}
			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
			}

			// Handle function calls.
			fnCalls := functionCalls(resp.Content)
			if len(fnCalls) == 0 {
				continue
			}
			for ev, err := range f.postprocessHandleFunctionCalls(ctx, parentCtx, req, resp) {
				if !yield(ev, err) || err != nil {
					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.Name()
	ev.Branch = parentCtx.Branch
	ev.LLMResponse = resp

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

	return ev
}


Review comment (refactor):
Shall we remove this code? The same code fetching fuctionCalls is in the `postprocessHandleFunctionCalls`.

Code after:
		for resp, err := range f.callLLM(ctx, parentCtx, req) {
			if err != nil {
				yield(nil, err)
				return
			}
			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
			}
			for ev, err := range f.postprocessHandleFunctionCalls(ctx, parentCtx, req, resp) {
				if !yield(ev, err) || err != nil {
					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.Name()
	ev.Branch = parentCtx.Branch
	ev.LLMResponse = resp

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

	return ev
}

func (f *baseFlow) preprocess(ctx context.Context, parentCtx *adk.InvocationContext, req *adk.LLMRequest) error {
	llmAgent := asLLMAgent(parentCtx.Agent)
	if llmAgent == nil {
		return nil
	}
	// apply request processor functions to the request in the configured order.