mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-11 22:51:28 +00:00
fix: address Greptile review comments
- Sanitize custom_id to meet GCP label constraints (lowercase, alphanumeric, max 63 chars) - Improve batch output detection heuristic with processed_time and candidates/status checks - Move inline imports to module level - Fix Content-Length header for transformed responses - Add test for label sanitization Made-with: Cursor
This commit is contained in:
parent
f6d5502faa
commit
380c14e7dd
3 changed files with 79 additions and 13 deletions
|
|
@ -17,9 +17,10 @@ from litellm.types.llms.openai import (
|
|||
HttpxBinaryResponseContent,
|
||||
OpenAIFileObject,
|
||||
)
|
||||
from litellm.litellm_core_utils.litellm_logging import Logging
|
||||
from litellm.types.llms.vertex_ai import VERTEX_CREDENTIALS_TYPES
|
||||
|
||||
from .transformation import VertexAIJsonlFilesTransformation
|
||||
from .transformation import VertexAIFilesConfig, VertexAIJsonlFilesTransformation
|
||||
|
||||
vertex_ai_files_transformation = VertexAIJsonlFilesTransformation()
|
||||
|
||||
|
|
@ -197,9 +198,6 @@ class VertexAIFilesHandler(GCSBucketBase):
|
|||
)
|
||||
|
||||
# Apply transformation to convert Vertex AI batch outputs to OpenAI format
|
||||
from .transformation import VertexAIFilesConfig
|
||||
from litellm.litellm_core_utils.litellm_logging import Logging
|
||||
|
||||
config = VertexAIFilesConfig()
|
||||
|
||||
# Create a logging object for transformation
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import json
|
||||
import os
|
||||
import re
|
||||
import time
|
||||
from typing import Any, Dict, List, Optional, Tuple, Union
|
||||
|
||||
|
|
@ -9,6 +10,7 @@ from openai.types.file_deleted import FileDeleted
|
|||
|
||||
from litellm._uuid import uuid
|
||||
from litellm.files.utils import FilesAPIUtils
|
||||
from litellm.litellm_core_utils.litellm_logging import Logging
|
||||
from litellm.litellm_core_utils.prompt_templates.common_utils import extract_file_data
|
||||
from litellm.llms.base_llm.chat.transformation import BaseLLMException
|
||||
from litellm.llms.base_llm.files.transformation import (
|
||||
|
|
@ -32,12 +34,31 @@ from litellm.types.llms.openai import (
|
|||
PathLike,
|
||||
)
|
||||
from litellm.types.llms.vertex_ai import GcsBucketResponse
|
||||
from litellm.types.utils import ExtractedFileData, LlmProviders
|
||||
from litellm.types.utils import ExtractedFileData, LlmProviders, ModelResponse
|
||||
|
||||
from ..common_utils import VertexAIError
|
||||
from ..vertex_llm_base import VertexBase
|
||||
|
||||
|
||||
def _sanitize_gcp_label_value(value: str) -> str:
|
||||
"""
|
||||
Sanitize a string to meet GCP label value constraints.
|
||||
|
||||
GCP label values must:
|
||||
- Be lowercase
|
||||
- Contain only letters, numbers, underscores, and hyphens
|
||||
- Be max 63 characters
|
||||
|
||||
Args:
|
||||
value: The string to sanitize
|
||||
|
||||
Returns:
|
||||
A sanitized string that meets GCP label constraints
|
||||
"""
|
||||
sanitized = re.sub(r"[^a-z0-9_-]", "_", value.lower())
|
||||
return sanitized[:63]
|
||||
|
||||
|
||||
class VertexAIFilesConfig(VertexBase, BaseFilesConfig):
|
||||
"""
|
||||
Config for VertexAI Files
|
||||
|
|
@ -254,7 +275,7 @@ class VertexAIFilesConfig(VertexBase, BaseFilesConfig):
|
|||
if custom_id:
|
||||
if "labels" not in vertex_request_body:
|
||||
vertex_request_body["labels"] = {}
|
||||
vertex_request_body["labels"]["litellm_custom_id"] = str(custom_id)
|
||||
vertex_request_body["labels"]["litellm_custom_id"] = _sanitize_gcp_label_value(str(custom_id))
|
||||
|
||||
vertex_jsonl_content.append({"request": vertex_request_body})
|
||||
return vertex_jsonl_content
|
||||
|
|
@ -538,8 +559,20 @@ class VertexAIFilesConfig(VertexBase, BaseFilesConfig):
|
|||
# Try to parse the first line to see if it's Vertex AI batch output
|
||||
first_line = json.loads(lines[0])
|
||||
|
||||
# Check if it has Vertex AI batch output structure
|
||||
if not ("response" in first_line and "request" in first_line):
|
||||
# Check if it has Vertex AI batch output structure with discriminating fields
|
||||
# Must have request, response, and processed_time
|
||||
# Plus either candidates (success) or status (error)
|
||||
has_base_structure = (
|
||||
"response" in first_line
|
||||
and "request" in first_line
|
||||
and "processed_time" in first_line
|
||||
)
|
||||
has_success_or_error = (
|
||||
"candidates" in first_line.get("response", {})
|
||||
or "status" in first_line
|
||||
)
|
||||
|
||||
if not (has_base_structure and has_success_or_error):
|
||||
# Not a Vertex AI batch output, return as-is
|
||||
return content
|
||||
|
||||
|
|
@ -573,10 +606,6 @@ class VertexAIFilesConfig(VertexBase, BaseFilesConfig):
|
|||
Transform a single Vertex AI batch output line to OpenAI format.
|
||||
Uses the existing VertexGeminiConfig transformation for the response.
|
||||
"""
|
||||
from litellm.types.utils import ModelResponse
|
||||
import httpx
|
||||
import time
|
||||
|
||||
# Extract custom_id from request labels
|
||||
custom_id = "unknown"
|
||||
request_data = vertex_output.get("request", {})
|
||||
|
|
@ -756,7 +785,7 @@ class VertexAIJsonlFilesTransformation(VertexGeminiConfig):
|
|||
if custom_id:
|
||||
if "labels" not in vertex_request_body:
|
||||
vertex_request_body["labels"] = {}
|
||||
vertex_request_body["labels"]["litellm_custom_id"] = str(custom_id)
|
||||
vertex_request_body["labels"]["litellm_custom_id"] = _sanitize_gcp_label_value(str(custom_id))
|
||||
|
||||
vertex_jsonl_content.append({"request": vertex_request_body})
|
||||
return vertex_jsonl_content
|
||||
|
|
|
|||
|
|
@ -526,3 +526,42 @@ class TestVertexBatchCustomIdLabels:
|
|||
# Step 4: Verify custom_id was preserved
|
||||
assert openai_output["custom_id"] == "my-custom-request-id"
|
||||
assert openai_output["response"]["status_code"] == 200
|
||||
|
||||
def test_custom_id_label_sanitization(self):
|
||||
"""Test that custom_id values are sanitized to meet GCP label constraints"""
|
||||
from litellm.llms.vertex_ai.files.transformation import (
|
||||
VertexAIJsonlFilesTransformation,
|
||||
_sanitize_gcp_label_value,
|
||||
)
|
||||
|
||||
transformation = VertexAIJsonlFilesTransformation()
|
||||
|
||||
# Test sanitization function
|
||||
assert _sanitize_gcp_label_value("MyRequest-1") == "myrequest-1"
|
||||
assert _sanitize_gcp_label_value("Request.With.Dots") == "request_with_dots"
|
||||
assert _sanitize_gcp_label_value("Request With Spaces") == "request_with_spaces"
|
||||
assert _sanitize_gcp_label_value("Request@#$%Special") == "request____special"
|
||||
|
||||
# Test max length (63 chars)
|
||||
long_id = "a" * 100
|
||||
assert len(_sanitize_gcp_label_value(long_id)) == 63
|
||||
|
||||
# Test in actual transformation
|
||||
openai_input = [
|
||||
{
|
||||
"custom_id": "MyRequest-1",
|
||||
"method": "POST",
|
||||
"url": "/v1/chat/completions",
|
||||
"body": {
|
||||
"model": "gemini-1.5-flash-001",
|
||||
"messages": [{"role": "user", "content": "Hello"}],
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
vertex_input = transformation._transform_openai_jsonl_content_to_vertex_ai_jsonl_content(
|
||||
openai_input
|
||||
)
|
||||
|
||||
# Verify label was sanitized
|
||||
assert vertex_input[0]["request"]["labels"]["litellm_custom_id"] == "myrequest-1"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue