Description
GenerateContentConfig(candidate_count=2) seems to have different behaviour in model adapters.
LiteLlm does not forward it as LiteLLM n, so it requests only one choice.
- Native Gemini forwards it, but
LlmResponse.create() keeps only candidates[0].
- Apigee forwards it as
n, but the response handler also keeps only the first choice.
So extra candidates are not requested in LiteLLM, or silently discarded in other paths.
This gives false expectation that ADK supports multiple results. For Gemini and Apigee it requests extra outputs and then throws them away, so billed candidate tokens are wasted cost.
Multiple candidates are useful for creative variants, sampling, evaluation, or generating few answers and selecting the best one. ADK accepts this config without error, so the user expects returned candidates will be available. Instead some adapters ignore request and other adapters generate extra outputs but discard them, which is confusing and wastes billed tokens.
Reproduction
"""Offline reproduction for ADK candidate_count handling."""
import asyncio
from google.adk.models.lite_llm import (
_get_completion_inputs,
_model_response_to_generate_content_response,
)
from google.adk.models.llm_request import LlmRequest
from google.genai import types
from litellm import ModelResponse
async def main() -> None:
request = LlmRequest(
model='openai/test',
config=types.GenerateContentConfig(candidate_count=2),
)
*_, generation_params, _ = await _get_completion_inputs(
request,
model='openai/test',
)
print('LiteLLM request params:', generation_params)
raw_response = ModelResponse(
model='test',
choices=[
{
'message': {'role': 'assistant', 'content': 'first'},
'finish_reason': 'stop',
},
{
'message': {'role': 'assistant', 'content': 'second'},
'finish_reason': 'stop',
},
],
)
raw_texts = [choice.message.content for choice in raw_response.choices]
converted = _model_response_to_generate_content_response(raw_response)
converted_texts = [
part.text for part in converted.content.parts if part.text is not None
]
print('LiteLLM returned texts:', raw_texts)
print('ADK converted texts:', converted_texts)
asyncio.run(main())
Output:
LiteLLM request params: None
LiteLLM returned texts: ['first', 'second']
ADK converted texts: ['first']
I checked this on main at 2c6a7ffb.
Expected behavior
ADK currently has single-candidate LlmResponse abstraction. If full multi-candidate support is not intended, maybe candidate_count > 1 should raise clear error before provider request.
Just mapping candidate_count to n will still discard returned choices, so I think it is not enough.
Description
GenerateContentConfig(candidate_count=2)seems to have different behaviour in model adapters.LiteLlmdoes not forward it as LiteLLMn, so it requests only one choice.LlmResponse.create()keeps onlycandidates[0].n, but the response handler also keeps only the first choice.So extra candidates are not requested in LiteLLM, or silently discarded in other paths.
This gives false expectation that ADK supports multiple results. For Gemini and Apigee it requests extra outputs and then throws them away, so billed candidate tokens are wasted cost.
Multiple candidates are useful for creative variants, sampling, evaluation, or generating few answers and selecting the best one. ADK accepts this config without error, so the user expects returned candidates will be available. Instead some adapters ignore request and other adapters generate extra outputs but discard them, which is confusing and wastes billed tokens.
Reproduction
Output:
I checked this on
mainat2c6a7ffb.Expected behavior
ADK currently has single-candidate
LlmResponseabstraction. If full multi-candidate support is not intended, maybecandidate_count > 1should raise clear error before provider request.Just mapping
candidate_counttonwill still discard returned choices, so I think it is not enough.