chore: initial snapshot for gitea/github upload
This commit is contained in:
@@ -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",
|
||||
]
|
||||
@@ -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()
|
||||
@@ -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)
|
||||
]
|
||||
@@ -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,
|
||||
)
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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,
|
||||
)
|
||||
@@ -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(),
|
||||
)
|
||||
@@ -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)
|
||||
Reference in New Issue
Block a user