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:
Sameer Kankute 2026-04-13 17:16:17 +05:30
parent f6d5502faa
commit 380c14e7dd
No known key found for this signature in database
3 changed files with 79 additions and 13 deletions

View file

@ -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

View file

@ -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

View file

@ -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"