Skip to content

candidate_count > 1 is ignored by LiteLLM and discarded by other adapters #6518

Description

@svfat

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.

Metadata

Metadata

Assignees

Labels

core[Component] This issue is related to the core interface and implementationmodels[Component] This issue is related to model support

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions