mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-04 02:31:27 +00:00
feat(interactions): map Interactions API usage into litellm Usage for cost tracking
This commit is contained in:
parent
668df9494a
commit
06a0a45c40
5 changed files with 135 additions and 3 deletions
|
|
@ -19,6 +19,7 @@ from litellm.litellm_core_utils.llm_cost_calc.tool_call_cost_tracking import (
|
|||
StandardBuiltInToolCostTracking,
|
||||
)
|
||||
from litellm.litellm_core_utils.llm_cost_calc.usage_object_transformation import (
|
||||
InteractionsUsageObjectTransformation,
|
||||
TranscriptionUsageObjectTransformation,
|
||||
)
|
||||
from litellm.litellm_core_utils.llm_cost_calc.utils import (
|
||||
|
|
@ -899,6 +900,8 @@ def _get_usage_object(
|
|||
usage_obj,
|
||||
)
|
||||
)
|
||||
elif isinstance(usage_obj, dict) and InteractionsUsageObjectTransformation.is_interactions_usage_dict(usage_obj):
|
||||
return InteractionsUsageObjectTransformation.transform_interactions_usage_to_chat_usage(usage_obj)
|
||||
elif isinstance(usage_obj, dict):
|
||||
return Usage(**usage_obj)
|
||||
elif isinstance(usage_obj, BaseModel):
|
||||
|
|
@ -1267,6 +1270,12 @@ def completion_cost(
|
|||
)
|
||||
if tr_usage is not None:
|
||||
_usage = tr_usage.model_dump()
|
||||
elif isinstance(_usage, dict) and InteractionsUsageObjectTransformation.is_interactions_usage_dict(
|
||||
_usage
|
||||
):
|
||||
_usage = InteractionsUsageObjectTransformation.transform_interactions_usage_to_chat_usage(
|
||||
_usage
|
||||
).model_dump()
|
||||
else:
|
||||
_usage = _usage
|
||||
|
||||
|
|
|
|||
|
|
@ -77,9 +77,13 @@ from litellm.litellm_core_utils.redact_messages import (
|
|||
)
|
||||
from litellm.llms.base_llm.ocr.transformation import OCRResponse
|
||||
from litellm.llms.base_llm.search.transformation import SearchResponse
|
||||
from litellm.litellm_core_utils.llm_cost_calc.usage_object_transformation import (
|
||||
InteractionsUsageObjectTransformation,
|
||||
)
|
||||
from litellm.responses.utils import ResponseAPILoggingUtils
|
||||
from litellm.types.agents import LiteLLMSendMessageResponse
|
||||
from litellm.types.containers.main import ContainerObject
|
||||
from litellm.types.interactions import InteractionsAPIResponse, InteractionsAPIStreamingResponse
|
||||
from litellm.types.llms.openai import (
|
||||
AllMessageValues,
|
||||
Batch,
|
||||
|
|
@ -1675,6 +1679,9 @@ class Logging(LiteLLMLoggingBaseClass):
|
|||
results=result,
|
||||
)
|
||||
|
||||
elif self._is_interactions_create_call_type() and isinstance(result, InteractionsAPIStreamingResponse):
|
||||
logging_result = self._interactions_streaming_result_as_response(result)
|
||||
|
||||
elif (
|
||||
self.call_type == CallTypes.llm_passthrough_route.value
|
||||
or self.call_type == CallTypes.allm_passthrough_route.value
|
||||
|
|
@ -1696,6 +1703,28 @@ class Logging(LiteLLMLoggingBaseClass):
|
|||
)
|
||||
return logging_result
|
||||
|
||||
def _is_interactions_create_call_type(self) -> bool:
|
||||
return self.call_type in (
|
||||
CallTypes.create_interaction.value,
|
||||
CallTypes.acreate_interaction.value,
|
||||
)
|
||||
|
||||
def _interactions_streaming_result_as_response(
|
||||
self, result: InteractionsAPIStreamingResponse
|
||||
) -> InteractionsAPIResponse:
|
||||
interaction = result.interaction if isinstance(result.interaction, dict) else {}
|
||||
return InteractionsAPIResponse(
|
||||
id=interaction.get("id") or result.id or result.interaction_id,
|
||||
model=interaction.get("model") or result.model,
|
||||
agent=interaction.get("agent") or result.agent,
|
||||
status=interaction.get("status") or result.status,
|
||||
created=interaction.get("created") or result.created,
|
||||
updated=interaction.get("updated") or result.updated,
|
||||
outputs=interaction.get("outputs") or result.outputs,
|
||||
steps=interaction.get("steps") or result.steps,
|
||||
usage=interaction.get("usage") or result.usage,
|
||||
)
|
||||
|
||||
def _merge_hidden_params_from_response_into_metadata(self, logging_result: Any) -> None:
|
||||
"""
|
||||
Copy response._hidden_params into litellm_params.metadata['hidden_params'].
|
||||
|
|
@ -1787,6 +1816,20 @@ class Logging(LiteLLMLoggingBaseClass):
|
|||
else dict(transformed_usage)
|
||||
)
|
||||
standard_logging_payload["response"] = response_dict
|
||||
elif isinstance(result, InteractionsAPIResponse):
|
||||
result = result.model_copy()
|
||||
transformed_usage = InteractionsUsageObjectTransformation.transform_interactions_usage_to_chat_usage(
|
||||
result.usage
|
||||
)
|
||||
setattr(result, "usage", transformed_usage)
|
||||
if (standard_logging_payload := self.model_call_details.get("standard_logging_object")) is not None:
|
||||
response_dict = result.model_dump() if hasattr(result, "model_dump") else dict(result)
|
||||
response_dict["usage"] = (
|
||||
transformed_usage.model_dump()
|
||||
if hasattr(transformed_usage, "model_dump")
|
||||
else dict(transformed_usage)
|
||||
)
|
||||
standard_logging_payload["response"] = response_dict
|
||||
elif isinstance(result, TranscriptionResponse):
|
||||
from litellm.litellm_core_utils.llm_cost_calc.usage_object_transformation import (
|
||||
TranscriptionUsageObjectTransformation,
|
||||
|
|
@ -1832,7 +1875,14 @@ class Logging(LiteLLMLoggingBaseClass):
|
|||
|
||||
logging_result = self.normalize_logging_result(result=result)
|
||||
|
||||
if standard_logging_object is None and result is not None and self.stream is not True:
|
||||
is_final_interactions_stream_result = self._is_interactions_create_call_type() and isinstance(
|
||||
logging_result, InteractionsAPIResponse
|
||||
)
|
||||
if (
|
||||
standard_logging_object is None
|
||||
and result is not None
|
||||
and (self.stream is not True or is_final_interactions_stream_result)
|
||||
):
|
||||
if self._is_recognized_call_type_for_logging(logging_result=logging_result) or isinstance(
|
||||
logging_result, (dict, list)
|
||||
):
|
||||
|
|
@ -1888,6 +1938,7 @@ class Logging(LiteLLMLoggingBaseClass):
|
|||
or isinstance(logging_result, FineTuningJob)
|
||||
or isinstance(logging_result, LiteLLMBatch)
|
||||
or isinstance(logging_result, ResponsesAPIResponse)
|
||||
or (isinstance(logging_result, InteractionsAPIResponse) and self._is_interactions_create_call_type())
|
||||
or isinstance(logging_result, OpenAIFileObject)
|
||||
or isinstance(logging_result, LiteLLMRealtimeStreamLoggingObject)
|
||||
or isinstance(logging_result, OpenAIModerationResponse)
|
||||
|
|
@ -4687,6 +4738,8 @@ class StandardLoggingPayloadSetup:
|
|||
elif isinstance(usage, dict):
|
||||
if ResponseAPILoggingUtils._is_response_api_usage(usage):
|
||||
return ResponseAPILoggingUtils._transform_response_api_usage_to_chat_usage(usage)
|
||||
if InteractionsUsageObjectTransformation.is_interactions_usage_dict(usage):
|
||||
return InteractionsUsageObjectTransformation.transform_interactions_usage_to_chat_usage(usage)
|
||||
return Usage(**usage)
|
||||
|
||||
raise ValueError(f"usage is required, got={usage} of type {type(usage)}")
|
||||
|
|
@ -4713,6 +4766,10 @@ class StandardLoggingPayloadSetup:
|
|||
if isinstance(_raw, dict):
|
||||
if ResponseAPILoggingUtils._is_response_api_usage(_raw):
|
||||
return ResponseAPILoggingUtils._transform_response_api_usage_to_chat_usage(_raw).model_dump()
|
||||
if InteractionsUsageObjectTransformation.is_interactions_usage_dict(_raw):
|
||||
return InteractionsUsageObjectTransformation.transform_interactions_usage_to_chat_usage(
|
||||
_raw
|
||||
).model_dump()
|
||||
return _raw
|
||||
if isinstance(_raw, Usage):
|
||||
return _raw.model_dump()
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
from typing import Any, Optional, Union
|
||||
from typing import Any, Mapping, Optional, Sequence, Union
|
||||
|
||||
from litellm.types.utils import (
|
||||
CompletionTokensDetailsWrapper,
|
||||
PromptTokensDetailsWrapper,
|
||||
TranscriptionUsageDurationObject,
|
||||
TranscriptionUsageTokensObject,
|
||||
|
|
@ -34,3 +35,62 @@ class TranscriptionUsageObjectTransformation:
|
|||
),
|
||||
)
|
||||
return None
|
||||
|
||||
|
||||
def _coerce_optional_token_count(value: object) -> int:
|
||||
return value if isinstance(value, int) else 0
|
||||
|
||||
|
||||
def _tokens_by_modality(modality_entries: object) -> Mapping[str, int]:
|
||||
if not isinstance(modality_entries, Sequence):
|
||||
return {}
|
||||
return {
|
||||
str(entry.get("modality") or "").lower(): _coerce_optional_token_count(entry.get("tokens"))
|
||||
for entry in modality_entries
|
||||
if isinstance(entry, Mapping)
|
||||
}
|
||||
|
||||
|
||||
class InteractionsUsageObjectTransformation:
|
||||
@staticmethod
|
||||
def is_interactions_usage_dict(usage_object: Mapping[str, Any]) -> bool:
|
||||
return (
|
||||
"total_input_tokens" in usage_object
|
||||
or "total_output_tokens" in usage_object
|
||||
or "input_tokens_by_modality" in usage_object
|
||||
or "output_tokens_by_modality" in usage_object
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def transform_interactions_usage_to_chat_usage(
|
||||
usage_object: "Mapping[str, Any] | None",
|
||||
) -> Usage:
|
||||
usage = usage_object or {}
|
||||
input_tokens_by_modality = _tokens_by_modality(usage.get("input_tokens_by_modality"))
|
||||
output_tokens_by_modality = _tokens_by_modality(usage.get("output_tokens_by_modality"))
|
||||
prompt_tokens = _coerce_optional_token_count(usage.get("total_input_tokens"))
|
||||
reasoning_tokens = _coerce_optional_token_count(
|
||||
usage.get("total_thought_tokens") or usage.get("total_reasoning_tokens")
|
||||
)
|
||||
completion_tokens = _coerce_optional_token_count(usage.get("total_output_tokens")) + reasoning_tokens
|
||||
cached_tokens = _coerce_optional_token_count(usage.get("total_cached_tokens"))
|
||||
total_tokens = _coerce_optional_token_count(usage.get("total_tokens")) or (prompt_tokens + completion_tokens)
|
||||
return Usage(
|
||||
prompt_tokens=prompt_tokens,
|
||||
completion_tokens=completion_tokens,
|
||||
total_tokens=total_tokens,
|
||||
prompt_tokens_details=PromptTokensDetailsWrapper(
|
||||
cached_tokens=cached_tokens,
|
||||
text_tokens=input_tokens_by_modality.get("text"),
|
||||
image_tokens=input_tokens_by_modality.get("image"),
|
||||
audio_tokens=input_tokens_by_modality.get("audio"),
|
||||
video_tokens=input_tokens_by_modality.get("video"),
|
||||
),
|
||||
completion_tokens_details=CompletionTokensDetailsWrapper(
|
||||
reasoning_tokens=reasoning_tokens,
|
||||
text_tokens=output_tokens_by_modality.get("text"),
|
||||
image_tokens=output_tokens_by_modality.get("image"),
|
||||
audio_tokens=output_tokens_by_modality.get("audio"),
|
||||
video_tokens=output_tokens_by_modality.get("video"),
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -396,6 +396,12 @@ class CallTypes(str, Enum):
|
|||
vector_store_search = "vector_store_search"
|
||||
avector_store_search = "avector_store_search"
|
||||
|
||||
#########################################################
|
||||
# Google Interactions API Call Types
|
||||
#########################################################
|
||||
create_interaction = "create_interaction"
|
||||
acreate_interaction = "acreate_interaction"
|
||||
|
||||
#########################################################
|
||||
# Container Call Types
|
||||
#########################################################
|
||||
|
|
|
|||
2
ui/litellm-dashboard/src/lib/http/schema.d.ts
generated
vendored
2
ui/litellm-dashboard/src/lib/http/schema.d.ts
generated
vendored
|
|
@ -21685,7 +21685,7 @@ export interface components {
|
|||
* CallTypes
|
||||
* @enum {string}
|
||||
*/
|
||||
CallTypes: "embedding" | "aembedding" | "completion" | "acompletion" | "atext_completion" | "text_completion" | "image_generation" | "aimage_generation" | "image_edit" | "aimage_edit" | "moderation" | "amoderation" | "atranscription" | "transcription" | "aspeech" | "speech" | "rerank" | "arerank" | "search" | "asearch" | "_arealtime" | "_aresponses_websocket" | "create_batch" | "acreate_batch" | "aretrieve_batch" | "retrieve_batch" | "acancel_batch" | "cancel_batch" | "pass_through_endpoint" | "anthropic_messages" | "get_assistants" | "aget_assistants" | "create_assistants" | "acreate_assistants" | "delete_assistant" | "adelete_assistant" | "acreate_thread" | "create_thread" | "aget_thread" | "get_thread" | "a_add_message" | "add_message" | "aget_messages" | "get_messages" | "arun_thread" | "run_thread" | "arun_thread_stream" | "run_thread_stream" | "afile_retrieve" | "file_retrieve" | "afile_delete" | "file_delete" | "afile_list" | "file_list" | "acreate_file" | "create_file" | "afile_content" | "file_content" | "create_fine_tuning_job" | "acreate_fine_tuning_job" | "create_video" | "acreate_video" | "avideo_retrieve" | "video_retrieve" | "avideo_content" | "video_content" | "video_remix" | "avideo_remix" | "video_list" | "avideo_list" | "video_retrieve_job" | "avideo_retrieve_job" | "video_delete" | "avideo_delete" | "video_create_character" | "avideo_create_character" | "video_get_character" | "avideo_get_character" | "video_edit" | "avideo_edit" | "video_extension" | "avideo_extension" | "vector_store_file_create" | "avector_store_file_create" | "vector_store_file_list" | "avector_store_file_list" | "vector_store_file_retrieve" | "avector_store_file_retrieve" | "vector_store_file_content" | "avector_store_file_content" | "vector_store_file_update" | "avector_store_file_update" | "vector_store_file_delete" | "avector_store_file_delete" | "vector_store_create" | "avector_store_create" | "vector_store_search" | "avector_store_search" | "create_container" | "acreate_container" | "list_containers" | "alist_containers" | "retrieve_container" | "aretrieve_container" | "delete_container" | "adelete_container" | "list_container_files" | "alist_container_files" | "upload_container_file" | "aupload_container_file" | "create_sandbox" | "acreate_sandbox" | "delete_sandbox" | "adelete_sandbox" | "run_code" | "arun_code" | "code_interpreter_tool" | "acode_interpreter_tool" | "acancel_fine_tuning_job" | "cancel_fine_tuning_job" | "alist_fine_tuning_jobs" | "list_fine_tuning_jobs" | "aretrieve_fine_tuning_job" | "retrieve_fine_tuning_job" | "responses" | "aresponses" | "alist_input_items" | "llm_passthrough_route" | "allm_passthrough_route" | "generate_content" | "agenerate_content" | "generate_content_stream" | "agenerate_content_stream" | "ocr" | "aocr" | "call_mcp_tool" | "list_mcp_tools" | "asend_message" | "send_message" | "acreate_skill";
|
||||
CallTypes: "embedding" | "aembedding" | "completion" | "acompletion" | "atext_completion" | "text_completion" | "image_generation" | "aimage_generation" | "image_edit" | "aimage_edit" | "moderation" | "amoderation" | "atranscription" | "transcription" | "aspeech" | "speech" | "rerank" | "arerank" | "search" | "asearch" | "_arealtime" | "_aresponses_websocket" | "create_batch" | "acreate_batch" | "aretrieve_batch" | "retrieve_batch" | "acancel_batch" | "cancel_batch" | "pass_through_endpoint" | "anthropic_messages" | "get_assistants" | "aget_assistants" | "create_assistants" | "acreate_assistants" | "delete_assistant" | "adelete_assistant" | "acreate_thread" | "create_thread" | "aget_thread" | "get_thread" | "a_add_message" | "add_message" | "aget_messages" | "get_messages" | "arun_thread" | "run_thread" | "arun_thread_stream" | "run_thread_stream" | "afile_retrieve" | "file_retrieve" | "afile_delete" | "file_delete" | "afile_list" | "file_list" | "acreate_file" | "create_file" | "afile_content" | "file_content" | "create_fine_tuning_job" | "acreate_fine_tuning_job" | "create_video" | "acreate_video" | "avideo_retrieve" | "video_retrieve" | "avideo_content" | "video_content" | "video_remix" | "avideo_remix" | "video_list" | "avideo_list" | "video_retrieve_job" | "avideo_retrieve_job" | "video_delete" | "avideo_delete" | "video_create_character" | "avideo_create_character" | "video_get_character" | "avideo_get_character" | "video_edit" | "avideo_edit" | "video_extension" | "avideo_extension" | "vector_store_file_create" | "avector_store_file_create" | "vector_store_file_list" | "avector_store_file_list" | "vector_store_file_retrieve" | "avector_store_file_retrieve" | "vector_store_file_content" | "avector_store_file_content" | "vector_store_file_update" | "avector_store_file_update" | "vector_store_file_delete" | "avector_store_file_delete" | "vector_store_create" | "avector_store_create" | "vector_store_search" | "avector_store_search" | "create_interaction" | "acreate_interaction" | "create_container" | "acreate_container" | "list_containers" | "alist_containers" | "retrieve_container" | "aretrieve_container" | "delete_container" | "adelete_container" | "list_container_files" | "alist_container_files" | "upload_container_file" | "aupload_container_file" | "create_sandbox" | "acreate_sandbox" | "delete_sandbox" | "adelete_sandbox" | "run_code" | "arun_code" | "code_interpreter_tool" | "acode_interpreter_tool" | "acancel_fine_tuning_job" | "cancel_fine_tuning_job" | "alist_fine_tuning_jobs" | "list_fine_tuning_jobs" | "aretrieve_fine_tuning_job" | "retrieve_fine_tuning_job" | "responses" | "aresponses" | "alist_input_items" | "llm_passthrough_route" | "allm_passthrough_route" | "generate_content" | "agenerate_content" | "generate_content_stream" | "agenerate_content_stream" | "ocr" | "aocr" | "call_mcp_tool" | "list_mcp_tools" | "asend_message" | "send_message" | "acreate_skill";
|
||||
/** CallbackDelete */
|
||||
CallbackDelete: {
|
||||
/** Callback Name */
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue