chore: initial snapshot for gitea/github upload

This commit is contained in:
Your Name
2026-03-26 16:04:46 +08:00
commit a699a1ac98
3497 changed files with 1586237 additions and 0 deletions

View File

@@ -0,0 +1,68 @@
"""
LiteLLM Interactions API
This module provides SDK methods for Google's Interactions API.
Usage:
import litellm
# Create an interaction with a model
response = litellm.interactions.create(
model="gemini-2.5-flash",
input="Hello, how are you?"
)
# Create an interaction with an agent
response = litellm.interactions.create(
agent="deep-research-pro-preview-12-2025",
input="Research the current state of cancer research"
)
# Async version
response = await litellm.interactions.acreate(...)
# Get an interaction
response = litellm.interactions.get(interaction_id="...")
# Delete an interaction
result = litellm.interactions.delete(interaction_id="...")
# Cancel an interaction
result = litellm.interactions.cancel(interaction_id="...")
Methods:
- create(): Sync create interaction
- acreate(): Async create interaction
- get(): Sync get interaction
- aget(): Async get interaction
- delete(): Sync delete interaction
- adelete(): Async delete interaction
- cancel(): Sync cancel interaction
- acancel(): Async cancel interaction
"""
from litellm.interactions.main import (
acancel,
acreate,
adelete,
aget,
cancel,
create,
delete,
get,
)
__all__ = [
# Create
"create",
"acreate",
# Get
"get",
"aget",
# Delete
"delete",
"adelete",
# Cancel
"cancel",
"acancel",
]

View File

@@ -0,0 +1,697 @@
"""
HTTP Handler for Interactions API requests.
This module handles the HTTP communication for the Google Interactions API.
"""
from typing import (
Any,
AsyncIterator,
Coroutine,
Dict,
Iterator,
Optional,
Union,
)
import httpx
import litellm
from litellm.constants import request_timeout
from litellm.interactions.streaming_iterator import (
InteractionsAPIStreamingIterator,
SyncInteractionsAPIStreamingIterator,
)
from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLoggingObj
from litellm.llms.base_llm.interactions.transformation import BaseInteractionsAPIConfig
from litellm.llms.custom_httpx.http_handler import (
AsyncHTTPHandler,
HTTPHandler,
_get_httpx_client,
get_async_httpx_client,
)
from litellm.types.interactions import (
CancelInteractionResult,
DeleteInteractionResult,
InteractionInput,
InteractionsAPIOptionalRequestParams,
InteractionsAPIResponse,
InteractionsAPIStreamingResponse,
)
from litellm.types.router import GenericLiteLLMParams
class InteractionsHTTPHandler:
"""
HTTP handler for Interactions API requests.
"""
def _handle_error(
self,
e: Exception,
provider_config: BaseInteractionsAPIConfig,
) -> Exception:
"""Handle errors from HTTP requests."""
if isinstance(e, httpx.HTTPStatusError):
error_message = e.response.text
status_code = e.response.status_code
headers = dict(e.response.headers)
return provider_config.get_error_class(
error_message=error_message,
status_code=status_code,
headers=headers,
)
return e
# =========================================================
# CREATE INTERACTION
# =========================================================
def create_interaction(
self,
interactions_api_config: BaseInteractionsAPIConfig,
optional_params: InteractionsAPIOptionalRequestParams,
custom_llm_provider: str,
litellm_params: GenericLiteLLMParams,
logging_obj: LiteLLMLoggingObj,
model: Optional[str] = None,
agent: Optional[str] = None,
input: Optional[InteractionInput] = None,
extra_headers: Optional[Dict[str, Any]] = None,
extra_body: Optional[Dict[str, Any]] = None,
timeout: Optional[Union[float, httpx.Timeout]] = None,
client: Optional[HTTPHandler] = None,
_is_async: bool = False,
stream: Optional[bool] = None,
) -> Union[
InteractionsAPIResponse,
Iterator[InteractionsAPIStreamingResponse],
Coroutine[
Any,
Any,
Union[
InteractionsAPIResponse, AsyncIterator[InteractionsAPIStreamingResponse]
],
],
]:
"""
Create a new interaction (synchronous or async based on _is_async flag).
Per Google's OpenAPI spec, the endpoint is POST /{api_version}/interactions
"""
if _is_async:
return self.async_create_interaction(
model=model,
agent=agent,
input=input,
interactions_api_config=interactions_api_config,
optional_params=optional_params,
custom_llm_provider=custom_llm_provider,
litellm_params=litellm_params,
logging_obj=logging_obj,
extra_headers=extra_headers,
extra_body=extra_body,
timeout=timeout,
stream=stream,
)
if client is None:
sync_httpx_client = _get_httpx_client(
params={"ssl_verify": litellm_params.get("ssl_verify", None)}
)
else:
sync_httpx_client = client
headers = interactions_api_config.validate_environment(
headers=extra_headers or {},
model=model or "",
litellm_params=litellm_params,
)
api_base = interactions_api_config.get_complete_url(
api_base=litellm_params.api_base or "",
model=model,
agent=agent,
litellm_params=dict(litellm_params),
stream=stream,
)
data = interactions_api_config.transform_request(
model=model,
agent=agent,
input=input,
optional_params=optional_params,
litellm_params=litellm_params,
headers=headers,
)
if extra_body:
data.update(extra_body)
# Logging
logging_obj.pre_call(
input=input,
api_key="",
additional_args={
"complete_input_dict": data,
"api_base": api_base,
"headers": headers,
},
)
try:
if stream:
response = sync_httpx_client.post(
url=api_base,
headers=headers,
json=data,
timeout=timeout or request_timeout,
stream=True,
)
return self._create_sync_streaming_iterator(
response=response,
model=model,
logging_obj=logging_obj,
interactions_api_config=interactions_api_config,
)
else:
response = sync_httpx_client.post(
url=api_base,
headers=headers,
json=data,
timeout=timeout or request_timeout,
)
except Exception as e:
raise self._handle_error(e=e, provider_config=interactions_api_config)
return interactions_api_config.transform_response(
model=model,
raw_response=response,
logging_obj=logging_obj,
)
async def async_create_interaction(
self,
interactions_api_config: BaseInteractionsAPIConfig,
optional_params: InteractionsAPIOptionalRequestParams,
custom_llm_provider: str,
litellm_params: GenericLiteLLMParams,
logging_obj: LiteLLMLoggingObj,
model: Optional[str] = None,
agent: Optional[str] = None,
input: Optional[InteractionInput] = None,
extra_headers: Optional[Dict[str, Any]] = None,
extra_body: Optional[Dict[str, Any]] = None,
timeout: Optional[Union[float, httpx.Timeout]] = None,
client: Optional[AsyncHTTPHandler] = None,
stream: Optional[bool] = None,
) -> Union[
InteractionsAPIResponse, AsyncIterator[InteractionsAPIStreamingResponse]
]:
"""
Create a new interaction (async version).
"""
if client is None:
async_httpx_client = get_async_httpx_client(
llm_provider=litellm.LlmProviders(custom_llm_provider),
params={"ssl_verify": litellm_params.get("ssl_verify", None)},
)
else:
async_httpx_client = client
headers = interactions_api_config.validate_environment(
headers=extra_headers or {},
model=model or "",
litellm_params=litellm_params,
)
api_base = interactions_api_config.get_complete_url(
api_base=litellm_params.api_base or "",
model=model,
agent=agent,
litellm_params=dict(litellm_params),
stream=stream,
)
data = interactions_api_config.transform_request(
model=model,
agent=agent,
input=input,
optional_params=optional_params,
litellm_params=litellm_params,
headers=headers,
)
if extra_body:
data.update(extra_body)
# Logging
logging_obj.pre_call(
input=input,
api_key="",
additional_args={
"complete_input_dict": data,
"api_base": api_base,
"headers": headers,
},
)
try:
if stream:
response = await async_httpx_client.post(
url=api_base,
headers=headers,
json=data,
timeout=timeout or request_timeout,
stream=True,
)
return self._create_async_streaming_iterator(
response=response,
model=model,
logging_obj=logging_obj,
interactions_api_config=interactions_api_config,
)
else:
response = await async_httpx_client.post(
url=api_base,
headers=headers,
json=data,
timeout=timeout or request_timeout,
)
except Exception as e:
raise self._handle_error(e=e, provider_config=interactions_api_config)
return interactions_api_config.transform_response(
model=model,
raw_response=response,
logging_obj=logging_obj,
)
def _create_sync_streaming_iterator(
self,
response: httpx.Response,
model: Optional[str],
logging_obj: LiteLLMLoggingObj,
interactions_api_config: BaseInteractionsAPIConfig,
) -> SyncInteractionsAPIStreamingIterator:
"""Create a synchronous streaming iterator.
Google AI's streaming format uses SSE (Server-Sent Events).
Returns a proper streaming iterator that yields chunks as they arrive.
"""
return SyncInteractionsAPIStreamingIterator(
response=response,
model=model,
interactions_api_config=interactions_api_config,
logging_obj=logging_obj,
)
def _create_async_streaming_iterator(
self,
response: httpx.Response,
model: Optional[str],
logging_obj: LiteLLMLoggingObj,
interactions_api_config: BaseInteractionsAPIConfig,
) -> InteractionsAPIStreamingIterator:
"""Create an asynchronous streaming iterator.
Google AI's streaming format uses SSE (Server-Sent Events).
Returns a proper streaming iterator that yields chunks as they arrive.
"""
return InteractionsAPIStreamingIterator(
response=response,
model=model,
interactions_api_config=interactions_api_config,
logging_obj=logging_obj,
)
# =========================================================
# GET INTERACTION
# =========================================================
def get_interaction(
self,
interaction_id: str,
interactions_api_config: BaseInteractionsAPIConfig,
custom_llm_provider: str,
litellm_params: GenericLiteLLMParams,
logging_obj: LiteLLMLoggingObj,
extra_headers: Optional[Dict[str, Any]] = None,
timeout: Optional[Union[float, httpx.Timeout]] = None,
client: Optional[HTTPHandler] = None,
_is_async: bool = False,
) -> Union[InteractionsAPIResponse, Coroutine[Any, Any, InteractionsAPIResponse]]:
"""Get an interaction by ID."""
if _is_async:
return self.async_get_interaction(
interaction_id=interaction_id,
interactions_api_config=interactions_api_config,
custom_llm_provider=custom_llm_provider,
litellm_params=litellm_params,
logging_obj=logging_obj,
extra_headers=extra_headers,
timeout=timeout,
)
if client is None:
sync_httpx_client = _get_httpx_client(
params={"ssl_verify": litellm_params.get("ssl_verify", None)}
)
else:
sync_httpx_client = client
headers = interactions_api_config.validate_environment(
headers=extra_headers or {},
model="",
litellm_params=litellm_params,
)
url, params = interactions_api_config.transform_get_interaction_request(
interaction_id=interaction_id,
api_base=litellm_params.api_base or "",
litellm_params=litellm_params,
headers=headers,
)
logging_obj.pre_call(
input=interaction_id,
api_key="",
additional_args={"api_base": url, "headers": headers},
)
try:
response = sync_httpx_client.get(
url=url,
headers=headers,
params=params,
)
except Exception as e:
raise self._handle_error(e=e, provider_config=interactions_api_config)
return interactions_api_config.transform_get_interaction_response(
raw_response=response,
logging_obj=logging_obj,
)
async def async_get_interaction(
self,
interaction_id: str,
interactions_api_config: BaseInteractionsAPIConfig,
custom_llm_provider: str,
litellm_params: GenericLiteLLMParams,
logging_obj: LiteLLMLoggingObj,
extra_headers: Optional[Dict[str, Any]] = None,
timeout: Optional[Union[float, httpx.Timeout]] = None,
client: Optional[AsyncHTTPHandler] = None,
) -> InteractionsAPIResponse:
"""Get an interaction by ID (async version)."""
if client is None:
async_httpx_client = get_async_httpx_client(
llm_provider=litellm.LlmProviders(custom_llm_provider),
params={"ssl_verify": litellm_params.get("ssl_verify", None)},
)
else:
async_httpx_client = client
headers = interactions_api_config.validate_environment(
headers=extra_headers or {},
model="",
litellm_params=litellm_params,
)
url, params = interactions_api_config.transform_get_interaction_request(
interaction_id=interaction_id,
api_base=litellm_params.api_base or "",
litellm_params=litellm_params,
headers=headers,
)
logging_obj.pre_call(
input=interaction_id,
api_key="",
additional_args={"api_base": url, "headers": headers},
)
try:
response = await async_httpx_client.get(
url=url,
headers=headers,
params=params,
)
except Exception as e:
raise self._handle_error(e=e, provider_config=interactions_api_config)
return interactions_api_config.transform_get_interaction_response(
raw_response=response,
logging_obj=logging_obj,
)
# =========================================================
# DELETE INTERACTION
# =========================================================
def delete_interaction(
self,
interaction_id: str,
interactions_api_config: BaseInteractionsAPIConfig,
custom_llm_provider: str,
litellm_params: GenericLiteLLMParams,
logging_obj: LiteLLMLoggingObj,
extra_headers: Optional[Dict[str, Any]] = None,
timeout: Optional[Union[float, httpx.Timeout]] = None,
client: Optional[HTTPHandler] = None,
_is_async: bool = False,
) -> Union[DeleteInteractionResult, Coroutine[Any, Any, DeleteInteractionResult]]:
"""Delete an interaction by ID."""
if _is_async:
return self.async_delete_interaction(
interaction_id=interaction_id,
interactions_api_config=interactions_api_config,
custom_llm_provider=custom_llm_provider,
litellm_params=litellm_params,
logging_obj=logging_obj,
extra_headers=extra_headers,
timeout=timeout,
)
if client is None:
sync_httpx_client = _get_httpx_client(
params={"ssl_verify": litellm_params.get("ssl_verify", None)}
)
else:
sync_httpx_client = client
headers = interactions_api_config.validate_environment(
headers=extra_headers or {},
model="",
litellm_params=litellm_params,
)
url, data = interactions_api_config.transform_delete_interaction_request(
interaction_id=interaction_id,
api_base=litellm_params.api_base or "",
litellm_params=litellm_params,
headers=headers,
)
logging_obj.pre_call(
input=interaction_id,
api_key="",
additional_args={"api_base": url, "headers": headers},
)
try:
response = sync_httpx_client.delete(
url=url,
headers=headers,
timeout=timeout or request_timeout,
)
except Exception as e:
raise self._handle_error(e=e, provider_config=interactions_api_config)
return interactions_api_config.transform_delete_interaction_response(
raw_response=response,
logging_obj=logging_obj,
interaction_id=interaction_id,
)
async def async_delete_interaction(
self,
interaction_id: str,
interactions_api_config: BaseInteractionsAPIConfig,
custom_llm_provider: str,
litellm_params: GenericLiteLLMParams,
logging_obj: LiteLLMLoggingObj,
extra_headers: Optional[Dict[str, Any]] = None,
timeout: Optional[Union[float, httpx.Timeout]] = None,
client: Optional[AsyncHTTPHandler] = None,
) -> DeleteInteractionResult:
"""Delete an interaction by ID (async version)."""
if client is None:
async_httpx_client = get_async_httpx_client(
llm_provider=litellm.LlmProviders(custom_llm_provider),
params={"ssl_verify": litellm_params.get("ssl_verify", None)},
)
else:
async_httpx_client = client
headers = interactions_api_config.validate_environment(
headers=extra_headers or {},
model="",
litellm_params=litellm_params,
)
url, data = interactions_api_config.transform_delete_interaction_request(
interaction_id=interaction_id,
api_base=litellm_params.api_base or "",
litellm_params=litellm_params,
headers=headers,
)
logging_obj.pre_call(
input=interaction_id,
api_key="",
additional_args={"api_base": url, "headers": headers},
)
try:
response = await async_httpx_client.delete(
url=url,
headers=headers,
timeout=timeout or request_timeout,
)
except Exception as e:
raise self._handle_error(e=e, provider_config=interactions_api_config)
return interactions_api_config.transform_delete_interaction_response(
raw_response=response,
logging_obj=logging_obj,
interaction_id=interaction_id,
)
# =========================================================
# CANCEL INTERACTION
# =========================================================
def cancel_interaction(
self,
interaction_id: str,
interactions_api_config: BaseInteractionsAPIConfig,
custom_llm_provider: str,
litellm_params: GenericLiteLLMParams,
logging_obj: LiteLLMLoggingObj,
extra_headers: Optional[Dict[str, Any]] = None,
timeout: Optional[Union[float, httpx.Timeout]] = None,
client: Optional[HTTPHandler] = None,
_is_async: bool = False,
) -> Union[CancelInteractionResult, Coroutine[Any, Any, CancelInteractionResult]]:
"""Cancel an interaction by ID."""
if _is_async:
return self.async_cancel_interaction(
interaction_id=interaction_id,
interactions_api_config=interactions_api_config,
custom_llm_provider=custom_llm_provider,
litellm_params=litellm_params,
logging_obj=logging_obj,
extra_headers=extra_headers,
timeout=timeout,
)
if client is None:
sync_httpx_client = _get_httpx_client(
params={"ssl_verify": litellm_params.get("ssl_verify", None)}
)
else:
sync_httpx_client = client
headers = interactions_api_config.validate_environment(
headers=extra_headers or {},
model="",
litellm_params=litellm_params,
)
url, data = interactions_api_config.transform_cancel_interaction_request(
interaction_id=interaction_id,
api_base=litellm_params.api_base or "",
litellm_params=litellm_params,
headers=headers,
)
logging_obj.pre_call(
input=interaction_id,
api_key="",
additional_args={"api_base": url, "headers": headers},
)
try:
response = sync_httpx_client.post(
url=url,
headers=headers,
json=data,
timeout=timeout or request_timeout,
)
except Exception as e:
raise self._handle_error(e=e, provider_config=interactions_api_config)
return interactions_api_config.transform_cancel_interaction_response(
raw_response=response,
logging_obj=logging_obj,
)
async def async_cancel_interaction(
self,
interaction_id: str,
interactions_api_config: BaseInteractionsAPIConfig,
custom_llm_provider: str,
litellm_params: GenericLiteLLMParams,
logging_obj: LiteLLMLoggingObj,
extra_headers: Optional[Dict[str, Any]] = None,
timeout: Optional[Union[float, httpx.Timeout]] = None,
client: Optional[AsyncHTTPHandler] = None,
) -> CancelInteractionResult:
"""Cancel an interaction by ID (async version)."""
if client is None:
async_httpx_client = get_async_httpx_client(
llm_provider=litellm.LlmProviders(custom_llm_provider),
params={"ssl_verify": litellm_params.get("ssl_verify", None)},
)
else:
async_httpx_client = client
headers = interactions_api_config.validate_environment(
headers=extra_headers or {},
model="",
litellm_params=litellm_params,
)
url, data = interactions_api_config.transform_cancel_interaction_request(
interaction_id=interaction_id,
api_base=litellm_params.api_base or "",
litellm_params=litellm_params,
headers=headers,
)
logging_obj.pre_call(
input=interaction_id,
api_key="",
additional_args={"api_base": url, "headers": headers},
)
try:
response = await async_httpx_client.post(
url=url,
headers=headers,
json=data,
timeout=timeout or request_timeout,
)
except Exception as e:
raise self._handle_error(e=e, provider_config=interactions_api_config)
return interactions_api_config.transform_cancel_interaction_response(
raw_response=response,
logging_obj=logging_obj,
)
# Initialize the HTTP handler singleton
interactions_http_handler = InteractionsHTTPHandler()

View File

@@ -0,0 +1,15 @@
"""
Bridge module for connecting Interactions API to Responses API via litellm.responses().
"""
from litellm.interactions.litellm_responses_transformation.handler import (
LiteLLMResponsesInteractionsHandler,
)
from litellm.interactions.litellm_responses_transformation.transformation import (
LiteLLMResponsesInteractionsConfig,
)
__all__ = [
"LiteLLMResponsesInteractionsHandler",
"LiteLLMResponsesInteractionsConfig", # Transformation config class (not BaseInteractionsAPIConfig)
]

View File

@@ -0,0 +1,155 @@
"""
Handler for transforming interactions API requests to litellm.responses requests.
"""
from typing import (
Any,
AsyncIterator,
Coroutine,
Dict,
Iterator,
Optional,
Union,
cast,
)
import litellm
from litellm.interactions.litellm_responses_transformation.streaming_iterator import (
LiteLLMResponsesInteractionsStreamingIterator,
)
from litellm.interactions.litellm_responses_transformation.transformation import (
LiteLLMResponsesInteractionsConfig,
)
from litellm.responses.streaming_iterator import BaseResponsesAPIStreamingIterator
from litellm.types.interactions import (
InteractionInput,
InteractionsAPIOptionalRequestParams,
InteractionsAPIResponse,
InteractionsAPIStreamingResponse,
)
from litellm.types.llms.openai import ResponsesAPIResponse
class LiteLLMResponsesInteractionsHandler:
"""Handler for bridging Interactions API to Responses API via litellm.responses()."""
def interactions_api_handler(
self,
model: str,
input: Optional[InteractionInput],
optional_params: InteractionsAPIOptionalRequestParams,
custom_llm_provider: Optional[str] = None,
_is_async: bool = False,
stream: Optional[bool] = None,
**kwargs,
) -> Union[
InteractionsAPIResponse,
Iterator[InteractionsAPIStreamingResponse],
Coroutine[
Any,
Any,
Union[
InteractionsAPIResponse,
AsyncIterator[InteractionsAPIStreamingResponse],
],
],
]:
"""
Handle Interactions API request by calling litellm.responses().
Args:
model: The model to use
input: The input content
optional_params: Optional parameters for the request
custom_llm_provider: Override LLM provider
_is_async: Whether this is an async call
stream: Whether to stream the response
**kwargs: Additional parameters
Returns:
InteractionsAPIResponse or streaming iterator
"""
# Transform interactions request to responses request
responses_request = LiteLLMResponsesInteractionsConfig.transform_interactions_request_to_responses_request(
model=model,
input=input,
optional_params=optional_params,
custom_llm_provider=custom_llm_provider,
stream=stream,
**kwargs,
)
if _is_async:
return self.async_interactions_api_handler(
responses_request=responses_request,
model=model,
input=input,
optional_params=optional_params,
**kwargs,
)
# Call litellm.responses()
# Note: litellm.responses() returns Union[ResponsesAPIResponse, BaseResponsesAPIStreamingIterator]
# but the type checker may see it as a coroutine in some contexts
responses_response = litellm.responses(
**responses_request,
)
# Handle streaming response
if isinstance(responses_response, BaseResponsesAPIStreamingIterator):
return LiteLLMResponsesInteractionsStreamingIterator(
model=model,
litellm_custom_stream_wrapper=responses_response,
request_input=input,
optional_params=optional_params,
custom_llm_provider=custom_llm_provider,
litellm_metadata=kwargs.get("litellm_metadata", {}),
)
# At this point, responses_response must be ResponsesAPIResponse (not streaming)
# Cast to satisfy type checker since we've already checked it's not a streaming iterator
responses_api_response = cast(ResponsesAPIResponse, responses_response)
# Transform responses response to interactions response
return LiteLLMResponsesInteractionsConfig.transform_responses_response_to_interactions_response(
responses_response=responses_api_response,
model=model,
)
async def async_interactions_api_handler(
self,
responses_request: Dict[str, Any],
model: str,
input: Optional[InteractionInput],
optional_params: InteractionsAPIOptionalRequestParams,
**kwargs,
) -> Union[
InteractionsAPIResponse, AsyncIterator[InteractionsAPIStreamingResponse]
]:
"""Async handler for interactions API requests."""
# Call litellm.aresponses()
# Note: litellm.aresponses() returns Union[ResponsesAPIResponse, BaseResponsesAPIStreamingIterator]
responses_response = await litellm.aresponses(
**responses_request,
)
# Handle streaming response
if isinstance(responses_response, BaseResponsesAPIStreamingIterator):
return LiteLLMResponsesInteractionsStreamingIterator(
model=model,
litellm_custom_stream_wrapper=responses_response,
request_input=input,
optional_params=optional_params,
custom_llm_provider=responses_request.get("custom_llm_provider"),
litellm_metadata=kwargs.get("litellm_metadata", {}),
)
# At this point, responses_response must be ResponsesAPIResponse (not streaming)
# Cast to satisfy type checker since we've already checked it's not a streaming iterator
responses_api_response = cast(ResponsesAPIResponse, responses_response)
# Transform responses response to interactions response
return LiteLLMResponsesInteractionsConfig.transform_responses_response_to_interactions_response(
responses_response=responses_api_response,
model=model,
)

View File

@@ -0,0 +1,286 @@
"""
Streaming iterator for transforming Responses API stream to Interactions API stream.
"""
from typing import Any, AsyncIterator, Dict, Iterator, Optional, cast
from litellm.responses.streaming_iterator import (
BaseResponsesAPIStreamingIterator,
ResponsesAPIStreamingIterator,
SyncResponsesAPIStreamingIterator,
)
from litellm.types.interactions import (
InteractionInput,
InteractionsAPIOptionalRequestParams,
InteractionsAPIStreamingResponse,
)
from litellm.types.llms.openai import (
OutputTextDeltaEvent,
ResponseCompletedEvent,
ResponseCreatedEvent,
ResponseInProgressEvent,
ResponsesAPIStreamingResponse,
)
class LiteLLMResponsesInteractionsStreamingIterator:
"""
Iterator that wraps Responses API streaming and transforms chunks to Interactions API format.
This class handles both sync and async iteration, transforming Responses API
streaming events (output.text.delta, response.completed, etc.) to Interactions
API streaming events (content.delta, interaction.complete, etc.).
"""
def __init__(
self,
model: str,
litellm_custom_stream_wrapper: BaseResponsesAPIStreamingIterator,
request_input: Optional[InteractionInput],
optional_params: InteractionsAPIOptionalRequestParams,
custom_llm_provider: Optional[str] = None,
litellm_metadata: Optional[Dict[str, Any]] = None,
):
self.model = model
self.responses_stream_iterator = litellm_custom_stream_wrapper
self.request_input = request_input
self.optional_params = optional_params
self.custom_llm_provider = custom_llm_provider
self.litellm_metadata = litellm_metadata or {}
self.finished = False
self.collected_text = ""
self.sent_interaction_start = False
self.sent_content_start = False
def _transform_responses_chunk_to_interactions_chunk(
self,
responses_chunk: ResponsesAPIStreamingResponse,
) -> Optional[InteractionsAPIStreamingResponse]:
"""
Transform a Responses API streaming chunk to an Interactions API streaming chunk.
Responses API events:
- output.text.delta -> content.delta
- response.completed -> interaction.complete
Interactions API events:
- interaction.start
- content.start
- content.delta
- content.stop
- interaction.complete
"""
if not responses_chunk:
return None
# Handle OutputTextDeltaEvent -> content.delta
if isinstance(responses_chunk, OutputTextDeltaEvent):
delta_text = (
responses_chunk.delta if isinstance(responses_chunk.delta, str) else ""
)
self.collected_text += delta_text
# Send interaction.start if not sent
if not self.sent_interaction_start:
self.sent_interaction_start = True
return InteractionsAPIStreamingResponse(
event_type="interaction.start",
id=getattr(responses_chunk, "item_id", None)
or f"interaction_{id(self)}",
object="interaction",
status="in_progress",
model=self.model,
)
# Send content.start if not sent
if not self.sent_content_start:
self.sent_content_start = True
return InteractionsAPIStreamingResponse(
event_type="content.start",
id=getattr(responses_chunk, "item_id", None),
object="content",
delta={"type": "text", "text": ""},
)
# Send content.delta
return InteractionsAPIStreamingResponse(
event_type="content.delta",
id=getattr(responses_chunk, "item_id", None),
object="content",
delta={"text": delta_text},
)
# Handle ResponseCreatedEvent or ResponseInProgressEvent -> interaction.start
if isinstance(responses_chunk, (ResponseCreatedEvent, ResponseInProgressEvent)):
if not self.sent_interaction_start:
self.sent_interaction_start = True
response_id = (
getattr(responses_chunk.response, "id", None)
if hasattr(responses_chunk, "response")
else None
)
return InteractionsAPIStreamingResponse(
event_type="interaction.start",
id=response_id or f"interaction_{id(self)}",
object="interaction",
status="in_progress",
model=self.model,
)
# Handle ResponseCompletedEvent -> interaction.complete
if isinstance(responses_chunk, ResponseCompletedEvent):
self.finished = True
response = responses_chunk.response
# Send content.stop first if content was started
if self.sent_content_start:
# Note: We'll send this in the iterator, not here
pass
# Send interaction.complete
return InteractionsAPIStreamingResponse(
event_type="interaction.complete",
id=getattr(response, "id", None) or f"interaction_{id(self)}",
object="interaction",
status="completed",
model=self.model,
outputs=[
{
"type": "text",
"text": self.collected_text,
}
],
)
# For other event types, return None (skip)
return None
def __iter__(self) -> Iterator[InteractionsAPIStreamingResponse]:
"""Sync iterator implementation."""
return self
def __next__(self) -> InteractionsAPIStreamingResponse:
"""Get next chunk in sync mode."""
if self.finished:
raise StopIteration
# Check if we have a pending interaction.complete to send
if hasattr(self, "_pending_interaction_complete"):
pending: InteractionsAPIStreamingResponse = getattr(
self, "_pending_interaction_complete"
)
delattr(self, "_pending_interaction_complete")
return pending
# Use a loop instead of recursion to avoid stack overflow
sync_iterator = cast(
SyncResponsesAPIStreamingIterator, self.responses_stream_iterator
)
while True:
try:
# Get next chunk from responses API stream
chunk = next(sync_iterator)
# Transform chunk (chunk is already a ResponsesAPIStreamingResponse)
transformed = self._transform_responses_chunk_to_interactions_chunk(
chunk
)
if transformed:
# If we finished and content was started, send content.stop before interaction.complete
if (
self.finished
and self.sent_content_start
and transformed.event_type == "interaction.complete"
):
# Send content.stop first
content_stop = InteractionsAPIStreamingResponse(
event_type="content.stop",
id=transformed.id,
object="content",
delta={"type": "text", "text": self.collected_text},
)
# Store the interaction.complete to send next
self._pending_interaction_complete = transformed
return content_stop
return transformed
# If no transformation, continue to next chunk (loop continues)
except StopIteration:
self.finished = True
# Send final events if needed
if self.sent_content_start:
return InteractionsAPIStreamingResponse(
event_type="content.stop",
object="content",
delta={"type": "text", "text": self.collected_text},
)
raise StopIteration
def __aiter__(self) -> AsyncIterator[InteractionsAPIStreamingResponse]:
"""Async iterator implementation."""
return self
async def __anext__(self) -> InteractionsAPIStreamingResponse:
"""Get next chunk in async mode."""
if self.finished:
raise StopAsyncIteration
# Check if we have a pending interaction.complete to send
if hasattr(self, "_pending_interaction_complete"):
pending: InteractionsAPIStreamingResponse = getattr(
self, "_pending_interaction_complete"
)
delattr(self, "_pending_interaction_complete")
return pending
# Use a loop instead of recursion to avoid stack overflow
async_iterator = cast(
ResponsesAPIStreamingIterator, self.responses_stream_iterator
)
while True:
try:
# Get next chunk from responses API stream
chunk = await async_iterator.__anext__()
# Transform chunk (chunk is already a ResponsesAPIStreamingResponse)
transformed = self._transform_responses_chunk_to_interactions_chunk(
chunk
)
if transformed:
# If we finished and content was started, send content.stop before interaction.complete
if (
self.finished
and self.sent_content_start
and transformed.event_type == "interaction.complete"
):
# Send content.stop first
content_stop = InteractionsAPIStreamingResponse(
event_type="content.stop",
id=transformed.id,
object="content",
delta={"type": "text", "text": self.collected_text},
)
# Store the interaction.complete to send next
self._pending_interaction_complete = transformed
return content_stop
return transformed
# If no transformation, continue to next chunk (loop continues)
except StopAsyncIteration:
self.finished = True
# Send final events if needed
if self.sent_content_start:
return InteractionsAPIStreamingResponse(
event_type="content.stop",
object="content",
delta={"type": "text", "text": self.collected_text},
)
raise StopAsyncIteration

View File

@@ -0,0 +1,299 @@
"""
Transformation utilities for bridging Interactions API to Responses API.
This module handles transforming between:
- Interactions API format (Google's format with Turn[], system_instruction, etc.)
- Responses API format (OpenAI's format with input[], instructions, etc.)
"""
from typing import Any, Dict, List, Optional, cast
from litellm.types.interactions import (
InteractionInput,
InteractionsAPIOptionalRequestParams,
InteractionsAPIResponse,
Turn,
)
from litellm.types.llms.openai import (
ResponseInputParam,
ResponsesAPIResponse,
)
class LiteLLMResponsesInteractionsConfig:
"""Configuration class for transforming between Interactions API and Responses API."""
@staticmethod
def transform_interactions_request_to_responses_request(
model: str,
input: Optional[InteractionInput],
optional_params: InteractionsAPIOptionalRequestParams,
**kwargs,
) -> Dict[str, Any]:
"""
Transform an Interactions API request to a Responses API request.
Key transformations:
- system_instruction -> instructions
- input (string | Turn[]) -> input (ResponseInputParam)
- tools -> tools (similar format)
- generation_config -> temperature, top_p, etc.
"""
responses_request: Dict[str, Any] = {
"model": model,
}
# Transform input
if input is not None:
responses_request[
"input"
] = LiteLLMResponsesInteractionsConfig._transform_interactions_input_to_responses_input(
input
)
# Transform system_instruction -> instructions
if optional_params.get("system_instruction"):
responses_request["instructions"] = optional_params["system_instruction"]
# Transform tools (similar format, pass through for now)
if optional_params.get("tools"):
responses_request["tools"] = optional_params["tools"]
# Transform generation_config to temperature, top_p, etc.
generation_config = optional_params.get("generation_config")
if generation_config:
if isinstance(generation_config, dict):
if "temperature" in generation_config:
responses_request["temperature"] = generation_config["temperature"]
if "top_p" in generation_config:
responses_request["top_p"] = generation_config["top_p"]
if "top_k" in generation_config:
# Responses API doesn't have top_k, skip it
pass
if "max_output_tokens" in generation_config:
responses_request["max_output_tokens"] = generation_config[
"max_output_tokens"
]
# Pass through other optional params that match
passthrough_params = ["stream", "store", "metadata", "user"]
for param in passthrough_params:
if param in optional_params and optional_params[param] is not None:
responses_request[param] = optional_params[param]
# Add any extra kwargs
responses_request.update(kwargs)
return responses_request
@staticmethod
def _transform_interactions_input_to_responses_input(
input: InteractionInput,
) -> ResponseInputParam:
"""
Transform Interactions API input to Responses API input format.
Interactions API input can be:
- string: "Hello"
- Turn[]: [{"role": "user", "content": [...]}]
- Content object
Responses API input is:
- string: "Hello"
- Message[]: [{"role": "user", "content": [...]}]
"""
if isinstance(input, str):
# ResponseInputParam accepts str
return cast(ResponseInputParam, input)
if isinstance(input, list):
# Turn[] format - convert to Responses API Message[] format
messages = []
for turn in input:
if isinstance(turn, dict):
role = turn.get("role", "user")
content = turn.get("content", [])
# Transform content array
transformed_content = (
LiteLLMResponsesInteractionsConfig._transform_content_array(
content
)
)
messages.append(
{
"role": role,
"content": transformed_content,
}
)
elif isinstance(turn, Turn):
# Pydantic model
role = turn.role if hasattr(turn, "role") else "user"
content = turn.content if hasattr(turn, "content") else []
# Ensure content is a list for _transform_content_array
# Cast to List[Any] to handle various content types
if isinstance(content, list):
content_list: List[Any] = list(content)
elif content is not None:
content_list = [content]
else:
content_list = []
transformed_content = (
LiteLLMResponsesInteractionsConfig._transform_content_array(
content_list
)
)
messages.append(
{
"role": role,
"content": transformed_content,
}
)
return cast(ResponseInputParam, messages)
# Single content object - wrap in message
if isinstance(input, dict):
return cast(
ResponseInputParam,
[
{
"role": "user",
"content": LiteLLMResponsesInteractionsConfig._transform_content_array(
input.get("content", [])
if isinstance(input.get("content"), list)
else [input]
),
}
],
)
# Fallback: convert to string
return cast(ResponseInputParam, str(input))
@staticmethod
def _transform_content_array(content: List[Any]) -> List[Dict[str, Any]]:
"""Transform Interactions API content array to Responses API format."""
if not isinstance(content, list):
# Single content item - wrap in array
content = [content]
transformed: List[Dict[str, Any]] = []
for item in content:
if isinstance(item, dict):
# Already in dict format, pass through
transformed.append(item)
elif isinstance(item, str):
# Plain string - wrap in text format
transformed.append({"type": "text", "text": item})
else:
# Pydantic model or other - convert to dict
if hasattr(item, "model_dump"):
dumped = item.model_dump()
if isinstance(dumped, dict):
transformed.append(dumped)
else:
# Fallback: wrap in text format
transformed.append({"type": "text", "text": str(dumped)})
elif hasattr(item, "dict"):
dumped = item.dict()
if isinstance(dumped, dict):
transformed.append(dumped)
else:
# Fallback: wrap in text format
transformed.append({"type": "text", "text": str(dumped)})
else:
# Fallback: wrap in text format
transformed.append({"type": "text", "text": str(item)})
return transformed
@staticmethod
def transform_responses_response_to_interactions_response(
responses_response: ResponsesAPIResponse,
model: Optional[str] = None,
) -> InteractionsAPIResponse:
"""
Transform a Responses API response to an Interactions API response.
Key transformations:
- Extract text from output[].content[].text
- Convert created_at (int) to created (ISO string)
- Map status
- Extract usage
"""
# Extract text from outputs
outputs = []
if hasattr(responses_response, "output") and responses_response.output:
for output_item in responses_response.output:
# Use getattr with None default to safely access content
content = getattr(output_item, "content", None)
if content is not None:
content_items = content if isinstance(content, list) else [content]
for content_item in content_items:
# Check if content_item has text attribute
text = getattr(content_item, "text", None)
if text is not None:
outputs.append(
{
"type": "text",
"text": text,
}
)
elif (
isinstance(content_item, dict)
and content_item.get("type") == "text"
):
outputs.append(content_item)
# Convert created_at to ISO string
created_at = getattr(responses_response, "created_at", None)
if isinstance(created_at, int):
from datetime import datetime
created = datetime.fromtimestamp(created_at).isoformat()
elif created_at is not None and hasattr(created_at, "isoformat"):
created = created_at.isoformat()
else:
created = None
# Map status
status = getattr(responses_response, "status", "completed")
if status == "completed":
interactions_status = "completed"
elif status == "in_progress":
interactions_status = "in_progress"
else:
interactions_status = status
# Build interactions response
interactions_response_dict: Dict[str, Any] = {
"id": getattr(responses_response, "id", ""),
"object": "interaction",
"status": interactions_status,
"outputs": outputs,
"model": model or getattr(responses_response, "model", ""),
"created": created,
}
# Add usage if available
# Map Responses API usage (input_tokens, output_tokens) to Interactions API spec format
# (total_input_tokens, total_output_tokens)
usage = getattr(responses_response, "usage", None)
if usage:
interactions_response_dict["usage"] = {
"total_input_tokens": getattr(usage, "input_tokens", 0),
"total_output_tokens": getattr(usage, "output_tokens", 0),
}
# Add role
interactions_response_dict["role"] = "model"
# Add updated (same as created for now)
interactions_response_dict["updated"] = created
return InteractionsAPIResponse(**interactions_response_dict)

View File

@@ -0,0 +1,649 @@
"""
LiteLLM Interactions API - Main Module
Per OpenAPI spec (https://ai.google.dev/static/api/interactions.openapi.json):
- Create interaction: POST /{api_version}/interactions
- Get interaction: GET /{api_version}/interactions/{interaction_id}
- Delete interaction: DELETE /{api_version}/interactions/{interaction_id}
Usage:
import litellm
# Create an interaction with a model
response = litellm.interactions.create(
model="gemini-2.5-flash",
input="Hello, how are you?"
)
# Create an interaction with an agent
response = litellm.interactions.create(
agent="deep-research-pro-preview-12-2025",
input="Research the current state of cancer research"
)
# Async version
response = await litellm.interactions.acreate(...)
# Get an interaction
response = litellm.interactions.get(interaction_id="...")
# Delete an interaction
result = litellm.interactions.delete(interaction_id="...")
"""
import asyncio
import contextvars
from functools import partial
from typing import (
Any,
AsyncIterator,
Coroutine,
Dict,
Iterator,
List,
Optional,
Union,
)
import httpx
import litellm
from litellm.interactions.http_handler import interactions_http_handler
from litellm.interactions.utils import (
InteractionsAPIRequestUtils,
get_provider_interactions_api_config,
)
from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLoggingObj
from litellm.types.interactions import (
CancelInteractionResult,
DeleteInteractionResult,
InteractionInput,
InteractionsAPIResponse,
InteractionsAPIStreamingResponse,
InteractionTool,
)
from litellm.types.router import GenericLiteLLMParams
from litellm.utils import client
# ============================================================
# SDK Methods - CREATE INTERACTION
# ============================================================
@client
async def acreate(
# Model or Agent (one required per OpenAPI spec)
model: Optional[str] = None,
agent: Optional[str] = None,
# Input (required)
input: Optional[InteractionInput] = None,
# Tools (for model interactions)
tools: Optional[List[InteractionTool]] = None,
# System instruction
system_instruction: Optional[str] = None,
# Generation config
generation_config: Optional[Dict[str, Any]] = None,
# Streaming
stream: Optional[bool] = None,
# Storage
store: Optional[bool] = None,
# Background execution
background: Optional[bool] = None,
# Response format
response_modalities: Optional[List[str]] = None,
response_format: Optional[Dict[str, Any]] = None,
response_mime_type: Optional[str] = None,
# Continuation
previous_interaction_id: Optional[str] = None,
# Extra params
extra_headers: Optional[Dict[str, Any]] = None,
extra_body: Optional[Dict[str, Any]] = None,
timeout: Optional[Union[float, httpx.Timeout]] = None,
# LiteLLM params
custom_llm_provider: Optional[str] = None,
**kwargs,
) -> Union[InteractionsAPIResponse, AsyncIterator[InteractionsAPIStreamingResponse]]:
"""
Async: Create a new interaction using Google's Interactions API.
Per OpenAPI spec, provide either `model` or `agent`.
Args:
model: The model to use (e.g., "gemini-2.5-flash")
agent: The agent to use (e.g., "deep-research-pro-preview-12-2025")
input: The input content (string, content object, or list)
tools: Tools available for the model
system_instruction: System instruction for the interaction
generation_config: Generation configuration
stream: Whether to stream the response
store: Whether to store the response for later retrieval
background: Whether to run in background
response_modalities: Requested response modalities (TEXT, IMAGE, AUDIO)
response_format: JSON schema for response format
response_mime_type: MIME type of the response
previous_interaction_id: ID of previous interaction for continuation
extra_headers: Additional headers
extra_body: Additional body parameters
timeout: Request timeout
custom_llm_provider: Override the LLM provider
Returns:
InteractionsAPIResponse or async iterator for streaming
"""
local_vars = locals()
try:
loop = asyncio.get_event_loop()
kwargs["acreate_interaction"] = True
if custom_llm_provider is None and model:
_, custom_llm_provider, _, _ = litellm.get_llm_provider(
model=model, api_base=kwargs.get("api_base", None)
)
elif custom_llm_provider is None:
custom_llm_provider = "gemini"
func = partial(
create,
model=model,
agent=agent,
input=input,
tools=tools,
system_instruction=system_instruction,
generation_config=generation_config,
stream=stream,
store=store,
background=background,
response_modalities=response_modalities,
response_format=response_format,
response_mime_type=response_mime_type,
previous_interaction_id=previous_interaction_id,
extra_headers=extra_headers,
extra_body=extra_body,
timeout=timeout,
custom_llm_provider=custom_llm_provider,
**kwargs,
)
ctx = contextvars.copy_context()
func_with_context = partial(ctx.run, func)
init_response = await loop.run_in_executor(None, func_with_context)
if asyncio.iscoroutine(init_response):
response = await init_response
else:
response = init_response
return response # type: ignore
except Exception as e:
raise litellm.exception_type(
model=model,
custom_llm_provider=custom_llm_provider,
original_exception=e,
completion_kwargs=local_vars,
extra_kwargs=kwargs,
)
@client
def create(
# Model or Agent (one required per OpenAPI spec)
model: Optional[str] = None,
agent: Optional[str] = None,
# Input (required)
input: Optional[InteractionInput] = None,
# Tools (for model interactions)
tools: Optional[List[InteractionTool]] = None,
# System instruction
system_instruction: Optional[str] = None,
# Generation config
generation_config: Optional[Dict[str, Any]] = None,
# Streaming
stream: Optional[bool] = None,
# Storage
store: Optional[bool] = None,
# Background execution
background: Optional[bool] = None,
# Response format
response_modalities: Optional[List[str]] = None,
response_format: Optional[Dict[str, Any]] = None,
response_mime_type: Optional[str] = None,
# Continuation
previous_interaction_id: Optional[str] = None,
# Extra params
extra_headers: Optional[Dict[str, Any]] = None,
extra_body: Optional[Dict[str, Any]] = None,
timeout: Optional[Union[float, httpx.Timeout]] = None,
# LiteLLM params
custom_llm_provider: Optional[str] = None,
**kwargs,
) -> Union[
InteractionsAPIResponse,
Iterator[InteractionsAPIStreamingResponse],
Coroutine[
Any,
Any,
Union[InteractionsAPIResponse, AsyncIterator[InteractionsAPIStreamingResponse]],
],
]:
"""
Sync: Create a new interaction using Google's Interactions API.
Per OpenAPI spec, provide either `model` or `agent`.
Args:
model: The model to use (e.g., "gemini-2.5-flash")
agent: The agent to use (e.g., "deep-research-pro-preview-12-2025")
input: The input content (string, content object, or list)
tools: Tools available for the model
system_instruction: System instruction for the interaction
generation_config: Generation configuration
stream: Whether to stream the response
store: Whether to store the response for later retrieval
background: Whether to run in background
response_modalities: Requested response modalities (TEXT, IMAGE, AUDIO)
response_format: JSON schema for response format
response_mime_type: MIME type of the response
previous_interaction_id: ID of previous interaction for continuation
extra_headers: Additional headers
extra_body: Additional body parameters
timeout: Request timeout
custom_llm_provider: Override the LLM provider
Returns:
InteractionsAPIResponse or iterator for streaming
"""
local_vars = locals()
try:
litellm_logging_obj: LiteLLMLoggingObj = kwargs.get("litellm_logging_obj") # type: ignore
litellm_call_id: Optional[str] = kwargs.get("litellm_call_id", None)
_is_async = kwargs.pop("acreate_interaction", False) is True
litellm_params = GenericLiteLLMParams(**kwargs)
if model:
model, custom_llm_provider, _, _ = litellm.get_llm_provider(
model=model,
custom_llm_provider=custom_llm_provider,
api_base=litellm_params.api_base,
api_key=litellm_params.api_key,
)
else:
custom_llm_provider = custom_llm_provider or "gemini"
interactions_api_config = get_provider_interactions_api_config(
provider=custom_llm_provider,
model=model,
)
# Get optional params using utility (similar to responses API pattern)
local_vars.update(kwargs)
optional_params = (
InteractionsAPIRequestUtils.get_requested_interactions_api_optional_params(
local_vars
)
)
# Check if this is a bridge provider (litellm_responses) - similar to responses API
# Either provider is explicitly "litellm_responses" or no config found (bridge to responses)
if (
custom_llm_provider == "litellm_responses"
or interactions_api_config is None
):
# Bridge to litellm.responses() for non-native providers
from litellm.interactions.litellm_responses_transformation.handler import (
LiteLLMResponsesInteractionsHandler,
)
handler = LiteLLMResponsesInteractionsHandler()
return handler.interactions_api_handler(
model=model or "",
input=input,
optional_params=optional_params,
custom_llm_provider=custom_llm_provider,
_is_async=_is_async,
stream=stream,
**kwargs,
)
litellm_logging_obj.update_environment_variables(
model=model,
optional_params=dict(optional_params),
litellm_params={"litellm_call_id": litellm_call_id},
custom_llm_provider=custom_llm_provider,
)
response = interactions_http_handler.create_interaction(
model=model,
agent=agent,
input=input,
interactions_api_config=interactions_api_config,
optional_params=optional_params,
custom_llm_provider=custom_llm_provider,
litellm_params=litellm_params,
logging_obj=litellm_logging_obj,
extra_headers=extra_headers,
extra_body=extra_body,
timeout=timeout,
_is_async=_is_async,
stream=stream,
)
return response
except Exception as e:
raise litellm.exception_type(
model=model,
custom_llm_provider=custom_llm_provider,
original_exception=e,
completion_kwargs=local_vars,
extra_kwargs=kwargs,
)
# ============================================================
# SDK Methods - GET INTERACTION
# ============================================================
@client
async def aget(
interaction_id: str,
extra_headers: Optional[Dict[str, Any]] = None,
timeout: Optional[Union[float, httpx.Timeout]] = None,
custom_llm_provider: Optional[str] = None,
**kwargs,
) -> InteractionsAPIResponse:
"""Async: Get an interaction by its ID."""
local_vars = locals()
try:
loop = asyncio.get_event_loop()
kwargs["aget_interaction"] = True
func = partial(
get,
interaction_id=interaction_id,
extra_headers=extra_headers,
timeout=timeout,
custom_llm_provider=custom_llm_provider or "gemini",
**kwargs,
)
ctx = contextvars.copy_context()
func_with_context = partial(ctx.run, func)
init_response = await loop.run_in_executor(None, func_with_context)
if asyncio.iscoroutine(init_response):
response = await init_response
else:
response = init_response
return response # type: ignore
except Exception as e:
raise litellm.exception_type(
model=None,
custom_llm_provider=custom_llm_provider or "gemini",
original_exception=e,
completion_kwargs=local_vars,
extra_kwargs=kwargs,
)
@client
def get(
interaction_id: str,
extra_headers: Optional[Dict[str, Any]] = None,
timeout: Optional[Union[float, httpx.Timeout]] = None,
custom_llm_provider: Optional[str] = None,
**kwargs,
) -> Union[InteractionsAPIResponse, Coroutine[Any, Any, InteractionsAPIResponse]]:
"""Sync: Get an interaction by its ID."""
local_vars = locals()
custom_llm_provider = custom_llm_provider or "gemini"
try:
litellm_logging_obj: LiteLLMLoggingObj = kwargs.get("litellm_logging_obj") # type: ignore
litellm_call_id: Optional[str] = kwargs.get("litellm_call_id", None)
_is_async = kwargs.pop("aget_interaction", False) is True
litellm_params = GenericLiteLLMParams(**kwargs)
interactions_api_config = get_provider_interactions_api_config(
provider=custom_llm_provider,
)
if interactions_api_config is None:
raise ValueError(
f"Interactions API not supported for: {custom_llm_provider}"
)
litellm_logging_obj.update_environment_variables(
model=None,
optional_params={"interaction_id": interaction_id},
litellm_params={"litellm_call_id": litellm_call_id},
custom_llm_provider=custom_llm_provider,
)
return interactions_http_handler.get_interaction(
interaction_id=interaction_id,
interactions_api_config=interactions_api_config,
custom_llm_provider=custom_llm_provider,
litellm_params=litellm_params,
logging_obj=litellm_logging_obj,
extra_headers=extra_headers,
timeout=timeout,
_is_async=_is_async,
)
except Exception as e:
raise litellm.exception_type(
model=None,
custom_llm_provider=custom_llm_provider,
original_exception=e,
completion_kwargs=local_vars,
extra_kwargs=kwargs,
)
# ============================================================
# SDK Methods - DELETE INTERACTION
# ============================================================
@client
async def adelete(
interaction_id: str,
extra_headers: Optional[Dict[str, Any]] = None,
timeout: Optional[Union[float, httpx.Timeout]] = None,
custom_llm_provider: Optional[str] = None,
**kwargs,
) -> DeleteInteractionResult:
"""Async: Delete an interaction by its ID."""
local_vars = locals()
try:
loop = asyncio.get_event_loop()
kwargs["adelete_interaction"] = True
func = partial(
delete,
interaction_id=interaction_id,
extra_headers=extra_headers,
timeout=timeout,
custom_llm_provider=custom_llm_provider or "gemini",
**kwargs,
)
ctx = contextvars.copy_context()
func_with_context = partial(ctx.run, func)
init_response = await loop.run_in_executor(None, func_with_context)
if asyncio.iscoroutine(init_response):
response = await init_response
else:
response = init_response
return response # type: ignore
except Exception as e:
raise litellm.exception_type(
model=None,
custom_llm_provider=custom_llm_provider or "gemini",
original_exception=e,
completion_kwargs=local_vars,
extra_kwargs=kwargs,
)
@client
def delete(
interaction_id: str,
extra_headers: Optional[Dict[str, Any]] = None,
timeout: Optional[Union[float, httpx.Timeout]] = None,
custom_llm_provider: Optional[str] = None,
**kwargs,
) -> Union[DeleteInteractionResult, Coroutine[Any, Any, DeleteInteractionResult]]:
"""Sync: Delete an interaction by its ID."""
local_vars = locals()
custom_llm_provider = custom_llm_provider or "gemini"
try:
litellm_logging_obj: LiteLLMLoggingObj = kwargs.get("litellm_logging_obj") # type: ignore
litellm_call_id: Optional[str] = kwargs.get("litellm_call_id", None)
_is_async = kwargs.pop("adelete_interaction", False) is True
litellm_params = GenericLiteLLMParams(**kwargs)
interactions_api_config = get_provider_interactions_api_config(
provider=custom_llm_provider,
)
if interactions_api_config is None:
raise ValueError(
f"Interactions API not supported for: {custom_llm_provider}"
)
litellm_logging_obj.update_environment_variables(
model=None,
optional_params={"interaction_id": interaction_id},
litellm_params={"litellm_call_id": litellm_call_id},
custom_llm_provider=custom_llm_provider,
)
return interactions_http_handler.delete_interaction(
interaction_id=interaction_id,
interactions_api_config=interactions_api_config,
custom_llm_provider=custom_llm_provider,
litellm_params=litellm_params,
logging_obj=litellm_logging_obj,
extra_headers=extra_headers,
timeout=timeout,
_is_async=_is_async,
)
except Exception as e:
raise litellm.exception_type(
model=None,
custom_llm_provider=custom_llm_provider,
original_exception=e,
completion_kwargs=local_vars,
extra_kwargs=kwargs,
)
# ============================================================
# SDK Methods - CANCEL INTERACTION
# ============================================================
@client
async def acancel(
interaction_id: str,
extra_headers: Optional[Dict[str, Any]] = None,
timeout: Optional[Union[float, httpx.Timeout]] = None,
custom_llm_provider: Optional[str] = None,
**kwargs,
) -> CancelInteractionResult:
"""Async: Cancel an interaction by its ID."""
local_vars = locals()
try:
loop = asyncio.get_event_loop()
kwargs["acancel_interaction"] = True
func = partial(
cancel,
interaction_id=interaction_id,
extra_headers=extra_headers,
timeout=timeout,
custom_llm_provider=custom_llm_provider or "gemini",
**kwargs,
)
ctx = contextvars.copy_context()
func_with_context = partial(ctx.run, func)
init_response = await loop.run_in_executor(None, func_with_context)
if asyncio.iscoroutine(init_response):
response = await init_response
else:
response = init_response
return response # type: ignore
except Exception as e:
raise litellm.exception_type(
model=None,
custom_llm_provider=custom_llm_provider or "gemini",
original_exception=e,
completion_kwargs=local_vars,
extra_kwargs=kwargs,
)
@client
def cancel(
interaction_id: str,
extra_headers: Optional[Dict[str, Any]] = None,
timeout: Optional[Union[float, httpx.Timeout]] = None,
custom_llm_provider: Optional[str] = None,
**kwargs,
) -> Union[CancelInteractionResult, Coroutine[Any, Any, CancelInteractionResult]]:
"""Sync: Cancel an interaction by its ID."""
local_vars = locals()
custom_llm_provider = custom_llm_provider or "gemini"
try:
litellm_logging_obj: LiteLLMLoggingObj = kwargs.get("litellm_logging_obj") # type: ignore
litellm_call_id: Optional[str] = kwargs.get("litellm_call_id", None)
_is_async = kwargs.pop("acancel_interaction", False) is True
litellm_params = GenericLiteLLMParams(**kwargs)
interactions_api_config = get_provider_interactions_api_config(
provider=custom_llm_provider,
)
if interactions_api_config is None:
raise ValueError(
f"Interactions API not supported for: {custom_llm_provider}"
)
litellm_logging_obj.update_environment_variables(
model=None,
optional_params={"interaction_id": interaction_id},
litellm_params={"litellm_call_id": litellm_call_id},
custom_llm_provider=custom_llm_provider,
)
return interactions_http_handler.cancel_interaction(
interaction_id=interaction_id,
interactions_api_config=interactions_api_config,
custom_llm_provider=custom_llm_provider,
litellm_params=litellm_params,
logging_obj=litellm_logging_obj,
extra_headers=extra_headers,
timeout=timeout,
_is_async=_is_async,
)
except Exception as e:
raise litellm.exception_type(
model=None,
custom_llm_provider=custom_llm_provider,
original_exception=e,
completion_kwargs=local_vars,
extra_kwargs=kwargs,
)

View File

@@ -0,0 +1,271 @@
"""
Streaming iterators for the Interactions API.
This module provides streaming iterators that properly stream SSE responses
from the Google Interactions API, similar to the responses API streaming iterator.
"""
import asyncio
import json
from datetime import datetime
from typing import Any, Dict, Optional
import httpx
from litellm._logging import verbose_logger
from litellm.constants import STREAM_SSE_DONE_STRING
from litellm.litellm_core_utils.asyncify import run_async_function
from litellm.litellm_core_utils.core_helpers import process_response_headers
from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLoggingObj
from litellm.litellm_core_utils.llm_response_utils.get_api_base import get_api_base
from litellm.litellm_core_utils.thread_pool_executor import executor
from litellm.llms.base_llm.interactions.transformation import BaseInteractionsAPIConfig
from litellm.types.interactions import (
InteractionsAPIStreamingResponse,
)
from litellm.utils import CustomStreamWrapper
class BaseInteractionsAPIStreamingIterator:
"""
Base class for streaming iterators that process responses from the Interactions API.
This class contains shared logic for both synchronous and asynchronous iterators.
"""
def __init__(
self,
response: httpx.Response,
model: Optional[str],
interactions_api_config: BaseInteractionsAPIConfig,
logging_obj: LiteLLMLoggingObj,
litellm_metadata: Optional[Dict[str, Any]] = None,
custom_llm_provider: Optional[str] = None,
):
self.response = response
self.model = model
self.logging_obj = logging_obj
self.finished = False
self.interactions_api_config = interactions_api_config
self.completed_response: Optional[InteractionsAPIStreamingResponse] = None
self.start_time = datetime.now()
# set request kwargs
self.litellm_metadata = litellm_metadata
self.custom_llm_provider = custom_llm_provider
# set hidden params for response headers
_api_base = get_api_base(
model=model or "",
optional_params=self.logging_obj.model_call_details.get(
"litellm_params", {}
),
)
_model_info: Dict = (
litellm_metadata.get("model_info", {}) if litellm_metadata else {}
)
self._hidden_params = {
"model_id": _model_info.get("id", None),
"api_base": _api_base,
}
self._hidden_params["additional_headers"] = process_response_headers(
self.response.headers or {}
)
def _process_chunk(self, chunk: str) -> Optional[InteractionsAPIStreamingResponse]:
"""Process a single chunk of data from the stream."""
if not chunk:
return None
# Handle SSE format (data: {...})
stripped_chunk = CustomStreamWrapper._strip_sse_data_from_chunk(chunk)
if stripped_chunk is None:
return None
# Handle "[DONE]" marker
if stripped_chunk == STREAM_SSE_DONE_STRING:
self.finished = True
return None
try:
# Parse the JSON chunk
parsed_chunk = json.loads(stripped_chunk)
# Format as InteractionsAPIStreamingResponse
if isinstance(parsed_chunk, dict):
streaming_response = (
self.interactions_api_config.transform_streaming_response(
model=self.model,
parsed_chunk=parsed_chunk,
logging_obj=self.logging_obj,
)
)
# Store the completed response (check for status=completed)
if (
streaming_response
and getattr(streaming_response, "status", None) == "completed"
):
self.completed_response = streaming_response
self._handle_logging_completed_response()
return streaming_response
return None
except json.JSONDecodeError:
# If we can't parse the chunk, continue
verbose_logger.debug(
f"Failed to parse streaming chunk: {stripped_chunk[:200]}..."
)
return None
def _handle_logging_completed_response(self):
"""Base implementation - should be overridden by subclasses."""
pass
class InteractionsAPIStreamingIterator(BaseInteractionsAPIStreamingIterator):
"""
Async iterator for processing streaming responses from the Interactions API.
"""
def __init__(
self,
response: httpx.Response,
model: Optional[str],
interactions_api_config: BaseInteractionsAPIConfig,
logging_obj: LiteLLMLoggingObj,
litellm_metadata: Optional[Dict[str, Any]] = None,
custom_llm_provider: Optional[str] = None,
):
super().__init__(
response=response,
model=model,
interactions_api_config=interactions_api_config,
logging_obj=logging_obj,
litellm_metadata=litellm_metadata,
custom_llm_provider=custom_llm_provider,
)
self.stream_iterator = response.aiter_lines()
def __aiter__(self):
return self
async def __anext__(self) -> InteractionsAPIStreamingResponse:
try:
while True:
# Get the next chunk from the stream
try:
chunk = await self.stream_iterator.__anext__()
except StopAsyncIteration:
self.finished = True
raise StopAsyncIteration
result = self._process_chunk(chunk)
if self.finished:
raise StopAsyncIteration
elif result is not None:
return result
# If result is None, continue the loop to get the next chunk
except httpx.HTTPError as e:
# Handle HTTP errors
self.finished = True
raise e
def _handle_logging_completed_response(self):
"""Handle logging for completed responses in async context."""
import copy
logging_response = copy.deepcopy(self.completed_response)
asyncio.create_task(
self.logging_obj.async_success_handler(
result=logging_response,
start_time=self.start_time,
end_time=datetime.now(),
cache_hit=None,
)
)
executor.submit(
self.logging_obj.success_handler,
result=logging_response,
cache_hit=None,
start_time=self.start_time,
end_time=datetime.now(),
)
class SyncInteractionsAPIStreamingIterator(BaseInteractionsAPIStreamingIterator):
"""
Synchronous iterator for processing streaming responses from the Interactions API.
"""
def __init__(
self,
response: httpx.Response,
model: Optional[str],
interactions_api_config: BaseInteractionsAPIConfig,
logging_obj: LiteLLMLoggingObj,
litellm_metadata: Optional[Dict[str, Any]] = None,
custom_llm_provider: Optional[str] = None,
):
super().__init__(
response=response,
model=model,
interactions_api_config=interactions_api_config,
logging_obj=logging_obj,
litellm_metadata=litellm_metadata,
custom_llm_provider=custom_llm_provider,
)
self.stream_iterator = response.iter_lines()
def __iter__(self):
return self
def __next__(self) -> InteractionsAPIStreamingResponse:
try:
while True:
# Get the next chunk from the stream
try:
chunk = next(self.stream_iterator)
except StopIteration:
self.finished = True
raise StopIteration
result = self._process_chunk(chunk)
if self.finished:
raise StopIteration
elif result is not None:
return result
# If result is None, continue the loop to get the next chunk
except httpx.HTTPError as e:
# Handle HTTP errors
self.finished = True
raise e
def _handle_logging_completed_response(self):
"""Handle logging for completed responses in sync context."""
import copy
logging_response = copy.deepcopy(self.completed_response)
run_async_function(
async_function=self.logging_obj.async_success_handler,
result=logging_response,
start_time=self.start_time,
end_time=datetime.now(),
cache_hit=None,
)
executor.submit(
self.logging_obj.success_handler,
result=logging_response,
cache_hit=None,
start_time=self.start_time,
end_time=datetime.now(),
)

View File

@@ -0,0 +1,87 @@
"""
Utility functions for Interactions API.
"""
from typing import Any, Dict, Optional, 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 = {
"tools",
"system_instruction",
"generation_config",
"stream",
"store",
"background",
"response_modalities",
"response_format",
"response_mime_type",
"previous_interaction_id",
"agent_config",
}
def get_provider_interactions_api_config(
provider: str,
model: Optional[str] = None,
) -> Optional[BaseInteractionsAPIConfig]:
"""
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()
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 = params.pop("custom_llm_provider", None)
special_params = params.pop("kwargs", {})
additional_drop_params = params.pop("additional_drop_params", None)
non_default_params = (
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)