litellm/litellm/interactions/utils.py
Kerry Lu 3b20b48473
feat(interactions): forward continuation_token and type requires_continuation so callers can resume a long decode (#50151)
* feat(interactions): continue requires_continuation long decodes with continuation_token hops

A foreground Interactions request stops at Google's per-hop output cap with
status requires_continuation and a continuation_token. litellm now re-posts the
request with previous_interaction_id and continuation_token until the status
moves on, merging the hops into one interaction (last id, every hop's steps,
summed usage with initial_input_tokens from hop 1), for non-streaming and
streaming calls. continuation_token is accepted as a request param, and
requires_continuation is a known status for billing.

* refactor(gemini interactions): validate streaming chunks with model_validate

* fix(interactions): keep the held completion across stream hops, stop on a repeated continuation_token, follow the spec redirect

* fix(interactions): add grounding_tool_count per tool type, stop on any seen continuation_token, keep streaming past a [DONE] frame when a hop is pending

* ci(interactions): point the spec canary at aistudio.google.com, where Google moved the Interactions OpenAPI spec

* test(interactions): scripted Google ends each long decode hop with a [DONE] frame

* refactor(interactions): pass requires_continuation and continuation_token through instead of looping hops in the proxy

The caller drives the continuation, the same as against Google directly. /interactions accepts continuation_token in place of input so a resume request needs no input.

* test(proxy): assert the accepted /interactions bodies pass the required-param check

* fix(proxy): keep the router default fallback for input on /interactions, continuation_token stands in for it

---------

Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-11-05 15:38:44 -08:00

92 lines
2.9 KiB
Python

"""
Utility functions for Interactions API.
"""
from typing import Any, Final, cast
from litellm.llms.base_llm.interactions.transformation import BaseInteractionsAPIConfig
from litellm.types.interactions import InteractionsAPIOptionalRequestParams
# Valid optional parameter keys per OpenAPI spec
INTERACTIONS_API_OPTIONAL_PARAMS: Final = {
"tools",
"system_instruction",
"generation_config",
"stream",
"store",
"background",
"environment",
"response_modalities",
"response_format",
"response_mime_type",
"previous_interaction_id",
"continuation_token",
"agent_config",
}
def get_provider_interactions_api_config(
provider: str,
model: str | None = None,
) -> BaseInteractionsAPIConfig | None:
"""
Get the interactions API config for the given provider.
Args:
provider: The LLM provider name
model: Optional model name
Returns:
The provider-specific interactions API config, or None if not supported
"""
from litellm.types.utils import LlmProviders
if provider == LlmProviders.GEMINI.value or provider == "gemini":
from litellm.llms.gemini.interactions.transformation import (
GoogleAIStudioInteractionsConfig,
)
return GoogleAIStudioInteractionsConfig()
if provider in (LlmProviders.VERTEX_AI.value, LlmProviders.VERTEX_AI_BETA.value):
from litellm.llms.vertex_ai.interactions.transformation import (
VertexAIInteractionsConfig,
)
return VertexAIInteractionsConfig()
return None
class InteractionsAPIRequestUtils:
"""Helper utils for constructing Interactions API requests."""
@staticmethod
def get_requested_interactions_api_optional_params(
params: dict[str, Any],
) -> InteractionsAPIOptionalRequestParams:
"""
Filter parameters to only include valid optional params per OpenAPI spec.
Args:
params: Dictionary of parameters to filter (typically from locals())
Returns:
Dict with only the valid optional parameters
"""
from litellm.utils import PreProcessNonDefaultParams
custom_llm_provider: Final = params.pop("custom_llm_provider", None)
special_params: Final = params.pop("kwargs", {})
additional_drop_params: Final = params.pop("additional_drop_params", None)
non_default_params: Final = PreProcessNonDefaultParams.base_pre_process_non_default_params(
passed_params=params,
special_params=special_params,
custom_llm_provider=custom_llm_provider,
additional_drop_params=additional_drop_params,
default_param_values={k: None for k in INTERACTIONS_API_OPTIONAL_PARAMS},
additional_endpoint_specific_params=["input", "model", "agent"],
)
return cast(InteractionsAPIOptionalRequestParams, non_default_params)