test(deps): migrate retired JavaScript passthrough checks from main

This commit is contained in:
Joshua Valluru 2026-10-20 19:19:15 -07:00
parent 120a14cb6c
commit 1a55c10eac
14 changed files with 190 additions and 4806 deletions

View file

@ -2429,23 +2429,6 @@ jobs:
- wait_for_service:
url: http://localhost:4000
timeout: "300"
# Install Node.js directly from nodejs.org with SHA256 verification,
# instead of piping NodeSource's setup_24.x apt-repo installer into
# sudo bash (which runs a mutable upstream script unattended).
- install_node
- run:
name: Install Node.js test dependencies
command: |
cd tests/pass_through_tests
npm ci
- run:
name: Run Vertex AI, Google AI Studio Node.js tests
command: |
cd tests/pass_through_tests
NODE_OPTIONS=--experimental-vm-modules npx jest . --verbose
no_output_timeout: 30m
- run:
name: Run tests
command: |

View file

@ -1956,6 +1956,9 @@
],
"tests/integration/providers/test_vertex_anthropic_structured_output_wire.py::test_vertex_chat_native_schema_keeps_min_items_one_and_closes_a_nullable_object": [
"other.provider_wire.vertex_chat_native_schema_keeps_min_items_one_and_closes_a_nullable_object"
],
"tests/integration/observability/test_passthrough_upstream_error_visibility.py::test_gemini_streaming_passthrough_request_tags_reach_spend_log": [
"other.observability.passthrough.streaming_tags_reach_spend"
]
},
"browser": {

View file

@ -0,0 +1,71 @@
import json
from pathlib import Path
from typing import Final
import pytest
import yaml
from integration._support.client import Gateway, eventually
from integration._support.database import read_rows
from integration._support.process import owned_proxy_process
from integration._support.wire import Reply, Request, wire_server
_GEMINI_STREAM_PATH: Final = "/gemini/v1beta/models/claude-nope-9:streamGenerateContent"
def _gemini_config(path: Path, wire_url: str) -> None:
config: Final = yaml.safe_load(Path("tests/integration/proxy_config.yaml").read_text())
config["environment_variables"] = {"GEMINI_API_BASE": wire_url, "GEMINI_API_KEY": "scripted"}
path.write_text(yaml.safe_dump(config))
def _gemini_headers(candidate: Gateway) -> dict[str, str]:
return {"Authorization": f"Bearer {candidate.key}", "x-goog-api-key": candidate.key}
@pytest.mark.covers("other.observability.passthrough.streaming_tags_reach_spend")
def test_gemini_streaming_passthrough_request_tags_reach_spend_log(gateway: Gateway, tmp_path: Path) -> None:
expected_body: Final = {"contents": [{"role": "user", "parts": [{"text": "hi"}]}]}
chunks: Final = (
b'data: {"candidates":[{"content":{"parts":[{"text":"hello"}],"role":"model"},"index":0}]}\n\n',
b'data: {"candidates":[{"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":1,"candidatesTokenCount":1,"totalTokenCount":2}}\n\n',
)
def respond(request: Request) -> Reply:
assert request.method == "POST", request
assert request.target.split("?", 1)[0] == "/v1beta/models/claude-nope-9:streamGenerateContent", request
assert json.loads(request.body) == expected_body, request
return Reply(status=200, chunks=chunks, content_type="text/event-stream")
config_path: Final = tmp_path / "gemini-stream-tags.yaml"
with wire_server(respond) as wire:
_gemini_config(config_path, wire.url)
with owned_proxy_process(gateway, tmp_path, {}, config=config_path, workers=2) as owned:
candidate: Final = owned.gateway
with candidate.client.stream(
"POST",
_GEMINI_STREAM_PATH,
params={"alt": "sse"},
json=expected_body,
headers={**_gemini_headers(candidate), "tags": "gemini-js-sdk,pass-through-endpoint"},
) as response:
assert response.status_code == 200
streamed: Final = response.read()
call_id: Final = response.headers["x-litellm-call-id"]
assert streamed == b"".join(chunks)
rows: Final = eventually(
lambda: read_rows(
'SELECT request_tags, call_type, status FROM "LiteLLM_SpendLogs" WHERE request_id=%s',
(call_id,),
),
lambda values: len(values) == 1,
seconds=70,
)
row: Final = rows[0]
raw_tags: Final = row["request_tags"]
request_tags: Final = json.loads(raw_tags) if isinstance(raw_tags, str) else raw_tags
policy_tags: Final = [
tag
for tag in request_tags
if not (isinstance(tag, str) and tag.startswith("User-Agent: "))
]
assert policy_tags == ["gemini-js-sdk", "pass-through-endpoint"], row
assert row["call_type"] == "pass_through_endpoint", row
assert row["status"] == "success", row

File diff suppressed because it is too large Load diff

View file

@ -1,18 +0,0 @@
{
"name": "litellm-pass-through-tests",
"version": "0.0.0",
"private": true,
"description": "JS pass-through tests for Vertex AI / Google AI Studio routes. CI-only; not published.",
"dependencies": {
"@google-cloud/vertexai": "1.12.0",
"@google/generative-ai": "0.24.1"
},
"devDependencies": {
"jest": "29.7.0"
},
"overrides": {
"@google-cloud/vertexai": {
"google-auth-library": "10.6.2"
}
}
}

View file

@ -1,23 +0,0 @@
// const { GoogleGenerativeAI } = require("@google/generative-ai");
// const genAI = new GoogleGenerativeAI("sk-1234");
// const model = genAI.getGenerativeModel({ model: "gemini-1.5-flash" });
// const prompt = "Explain how AI works in 2 pages";
// async function run() {
// try {
// const result = await model.generateContentStream(prompt, { baseUrl: "http://localhost:4000/gemini" });
// const response = await result.response;
// console.log(response.text());
// for await (const chunk of result.stream) {
// const chunkText = chunk.text();
// console.log(chunkText);
// process.stdout.write(chunkText);
// }
// } catch (error) {
// console.error("Error:", error);
// }
// }
// run();

View file

@ -1,134 +0,0 @@
const { GoogleGenerativeAI } = require("@google/generative-ai");
const fs = require('fs');
const path = require('path');
// Import fetch if the SDK uses it
const originalFetch = global.fetch || require('node-fetch');
let lastCallId;
// Monkey-patch the fetch used internally
global.fetch = async function patchedFetch(url, options) {
const response = await originalFetch(url, options);
// Store the call ID if it exists
lastCallId = response.headers.get('x-litellm-call-id');
return response;
};
// Configure Jest to retry flaky tests (useful for external API flakiness)
jest.retryTimes(3);
describe('Gemini AI Tests', () => {
test('should successfully generate non-streaming content with tags', async () => {
const genAI = new GoogleGenerativeAI("sk-1234"); // litellm proxy API key
const requestOptions = {
baseUrl: 'http://127.0.0.1:4000/gemini',
customHeaders: {
"tags": "gemini-js-sdk,pass-through-endpoint"
}
};
const model = genAI.getGenerativeModel({
model: 'gemini-3.1-flash-lite'
}, requestOptions);
const prompt = 'Say "hello test" and nothing else';
const result = await model.generateContent(prompt);
expect(result).toBeDefined();
// Use the captured callId
const callId = lastCallId;
console.log("Captured Call ID:", callId);
// Poll for spend data with retries (DB writes can be slow in CI)
let spendData = null;
for (let attempt = 0; attempt < 6; attempt++) {
await new Promise(resolve => setTimeout(resolve, 10000));
const spendResponse = await fetch(
`http://127.0.0.1:4000/spend/logs?request_id=${callId}`,
{ headers: { 'Authorization': 'Bearer sk-1234' } }
);
spendData = await spendResponse.json();
console.log(`spendData (attempt ${attempt + 1}):`, spendData);
if (spendData && spendData.length > 0 && spendData[0] && spendData[0].request_id) break;
}
if (!spendData || !spendData.length || !spendData[0] || !spendData[0].request_id) {
console.warn('Spend data not available after polling - skipping spend assertions (DB write may be slow in CI)');
return;
}
expect(spendData).toBeDefined();
expect(spendData[0].request_id).toBe(callId);
expect(spendData[0].call_type).toBe('pass_through_endpoint');
expect(spendData[0].request_tags).toEqual(['gemini-js-sdk', 'pass-through-endpoint']);
expect(spendData[0].metadata).toHaveProperty('user_api_key');
expect(spendData[0].model).toContain('gemini');
expect(spendData[0].custom_llm_provider).toBe('gemini');
expect(spendData[0].spend).toBeGreaterThan(0);
}, 90000);
test('should successfully generate streaming content with tags', async () => {
const genAI = new GoogleGenerativeAI("sk-1234"); // litellm proxy API key
const requestOptions = {
baseUrl: 'http://127.0.0.1:4000/gemini',
customHeaders: {
"tags": "gemini-js-sdk,pass-through-endpoint"
}
};
const model = genAI.getGenerativeModel({
model: 'gemini-3.1-flash-lite'
}, requestOptions);
const prompt = 'Say "hello test" and nothing else';
const streamingResult = await model.generateContentStream(prompt);
expect(streamingResult).toBeDefined();
for await (const chunk of streamingResult.stream) {
console.log('stream chunk:', JSON.stringify(chunk));
expect(chunk).toBeDefined();
}
const aggregatedResponse = await streamingResult.response;
console.log('aggregated response:', JSON.stringify(aggregatedResponse));
expect(aggregatedResponse).toBeDefined();
// Use the captured callId
const callId = lastCallId;
console.log("Captured Call ID:", callId);
// Poll for spend data with retries (DB writes can be slow in CI)
let spendData = null;
for (let attempt = 0; attempt < 6; attempt++) {
await new Promise(resolve => setTimeout(resolve, 10000));
const spendResponse = await fetch(
`http://127.0.0.1:4000/spend/logs?request_id=${callId}`,
{ headers: { 'Authorization': 'Bearer sk-1234' } }
);
spendData = await spendResponse.json();
console.log(`spendData (attempt ${attempt + 1}):`, spendData);
if (spendData && spendData.length > 0 && spendData[0] && spendData[0].request_id) break;
}
if (!spendData || !spendData.length || !spendData[0] || !spendData[0].request_id) {
console.warn('Spend data not available after polling - skipping spend assertions (DB write may be slow in CI)');
return;
}
expect(spendData).toBeDefined();
expect(spendData[0].request_id).toBe(callId);
expect(spendData[0].call_type).toBe('pass_through_endpoint');
expect(spendData[0].request_tags).toEqual(['gemini-js-sdk', 'pass-through-endpoint']);
expect(spendData[0].metadata).toHaveProperty('user_api_key');
expect(spendData[0].model).toContain('gemini');
expect(spendData[0].spend).toBeGreaterThan(0);
expect(spendData[0].custom_llm_provider).toBe('gemini');
}, 90000);
});

View file

@ -1,55 +0,0 @@
const { GoogleGenerativeAI, ModelParams, RequestOptions } = require("@google/generative-ai");
const modelParams = {
model: 'gemini-3.1-flash-lite',
};
const requestOptions = {
baseUrl: 'http://127.0.0.1:4000/gemini',
customHeaders: {
"tags": "gemini-js-sdk,gemini-3.1-flash-lite"
}
};
const genAI = new GoogleGenerativeAI("sk-1234"); // litellm proxy API key
const model = genAI.getGenerativeModel(modelParams, requestOptions);
const testPrompt = "Explain how AI works";
async function main() {
console.log("making request")
try {
const result = await model.generateContent(testPrompt);
console.log(result.response.text());
} catch (error) {
console.error('Error details:', {
name: error.name,
message: error.message,
cause: error.cause,
// Check if there's a network error
isNetworkError: error instanceof TypeError && error.message === 'fetch failed'
});
// Check if the server is running
if (error instanceof TypeError && error.message === 'fetch failed') {
console.error('Make sure your local server is running at http://localhost:4000');
}
}
}
async function main_streaming() {
try {
const streamingResult = await model.generateContentStream(testPrompt);
for await (const item of streamingResult.stream) {
console.log('stream chunk: ', JSON.stringify(item));
}
const aggregatedResponse = await streamingResult.response;
console.log('aggregated response: ', JSON.stringify(aggregatedResponse));
} catch (error) {
console.error('Error details:', error);
}
}
// main();
main_streaming();

View file

@ -1,43 +0,0 @@
const { VertexAI, RequestOptions } = require('@google-cloud/vertexai');
const vertexAI = new VertexAI({
project: 'litellm-ci-cd',
location: 'global',
apiEndpoint: "127.0.0.1:4000/vertex-ai"
});
// Create customHeaders using Headers
const customHeaders = new Headers({
"X-Litellm-Api-Key": "sk-1234",
tags: "vertexjs,test-2"
});
// Use customHeaders in RequestOptions
const requestOptions = {
customHeaders: customHeaders,
};
const generativeModel = vertexAI.getGenerativeModel(
{ model: 'gemini-3.1-flash-lite' },
requestOptions
);
async function testModel() {
try {
const request = {
contents: [{role: 'user', parts: [{text: 'How are you doing today tell me your name?'}]}],
};
const streamingResult = await generativeModel.generateContentStream(request);
for await (const item of streamingResult.stream) {
console.log('stream chunk: ', JSON.stringify(item));
}
const aggregatedResponse = await streamingResult.response;
console.log('aggregated response: ', JSON.stringify(aggregatedResponse));
} catch (error) {
console.error('Error:', error);
}
}
testModel();

View file

@ -1,144 +0,0 @@
const { VertexAI, RequestOptions } = require('@google-cloud/vertexai');
const fs = require('fs');
const path = require('path');
const os = require('os');
const { writeFileSync } = require('fs');
// Import fetch if the SDK uses it
const originalFetch = global.fetch || require('node-fetch');
const { runVertexRequestOrSkip } = require('./vertex_test_helpers');
// Monkey-patch the fetch used internally
global.fetch = async function patchedFetch(url, options) {
// Modify the URL to use HTTP instead of HTTPS
if (url.startsWith('https://localhost:4000')) {
url = url.replace('https://', 'http://');
}
console.log('Patched fetch sending request to:', url);
return originalFetch(url, options);
};
function loadVertexAiCredentials() {
console.log("loading vertex ai credentials");
const filepath = path.dirname(__filename);
const vertexKeyPath = path.join(filepath, "vertex_key.json");
// Initialize default empty service account data
let serviceAccountKeyData = {};
// Try to read existing vertex_key.json
try {
const content = fs.readFileSync(vertexKeyPath, 'utf8');
if (content && content.trim()) {
serviceAccountKeyData = JSON.parse(content);
}
} catch (error) {
// File doesn't exist or is invalid, continue with empty object
}
// Update with environment variables
const privateKeyId = process.env.VERTEX_AI_PRIVATE_KEY_ID || "";
const privateKey = (process.env.VERTEX_AI_PRIVATE_KEY || "").replace(/\\n/g, "\n");
serviceAccountKeyData.private_key_id = privateKeyId;
serviceAccountKeyData.private_key = privateKey;
// Create temporary file
const tempFilePath = path.join(os.tmpdir(), `vertex-credentials-${Date.now()}.json`);
writeFileSync(tempFilePath, JSON.stringify(serviceAccountKeyData, null, 2));
// Set environment variable
process.env.GOOGLE_APPLICATION_CREDENTIALS = tempFilePath;
}
// Run credential loading before tests
beforeAll(() => {
loadVertexAiCredentials();
});
// Configure Jest to retry flaky tests up to 3 times (useful for 429 rate limiting)
jest.retryTimes(3);
// Non-streaming Vertex generateContent can exceed 5s in CI / under load
const VERTEX_TEST_TIMEOUT_MS = 30000;
describe('Vertex AI Tests', () => {
test(
'should successfully generate content from Vertex AI',
async () => {
const vertexAI = new VertexAI({
project: 'litellm-ci-cd',
location: 'global',
apiEndpoint: "localhost:4000/vertex-ai"
});
const customHeaders = new Headers({
"x-litellm-api-key": "sk-1234"
});
const requestOptions = {
customHeaders: customHeaders
};
const generativeModel = vertexAI.getGenerativeModel(
{ model: 'gemini-3.1-flash-lite' },
requestOptions
);
const request = {
contents: [{role: 'user', parts: [{text: 'How are you doing today tell me your name?'}]}],
};
const streamingResult = await runVertexRequestOrSkip(() =>
generativeModel.generateContentStream(request)
);
if (streamingResult === null) {
return;
}
// Add some assertions
expect(streamingResult).toBeDefined();
for await (const item of streamingResult.stream) {
console.log('stream chunk:', JSON.stringify(item));
expect(item).toBeDefined();
}
const aggregatedResponse = await streamingResult.response;
console.log('aggregated response:', JSON.stringify(aggregatedResponse));
expect(aggregatedResponse).toBeDefined();
},
VERTEX_TEST_TIMEOUT_MS
);
test(
'should successfully generate non-streaming content from Vertex AI',
async () => {
const vertexAI = new VertexAI({
project: 'litellm-ci-cd',
location: 'global',
apiEndpoint: "localhost:4000/vertex-ai"
});
const customHeaders = new Headers({"x-litellm-api-key": "sk-1234"});
const requestOptions = {customHeaders: customHeaders};
const generativeModel = vertexAI.getGenerativeModel(
{model: 'gemini-3.1-flash-lite'},
requestOptions
);
const request = {contents: [{role: 'user', parts: [{text: 'What is 2+2?'}]}]};
const result = await runVertexRequestOrSkip(() =>
generativeModel.generateContent(request)
);
if (result === null) {
return;
}
expect(result).toBeDefined();
expect(result.response).toBeDefined();
console.log('non-streaming response:', JSON.stringify(result.response));
},
VERTEX_TEST_TIMEOUT_MS
);
});

View file

@ -1,213 +0,0 @@
const { VertexAI, RequestOptions } = require('@google-cloud/vertexai');
const fs = require('fs');
const path = require('path');
const os = require('os');
const { writeFileSync } = require('fs');
// Import fetch if the SDK uses it
const originalFetch = global.fetch || require('node-fetch');
let lastCallId;
const { runVertexRequestOrSkip } = require('./vertex_test_helpers');
// Monkey-patch the fetch used internally
global.fetch = async function patchedFetch(url, options) {
// Modify the URL to use HTTP instead of HTTPS
if (url.startsWith('https://127.0.0.1:4000')) {
url = url.replace('https://', 'http://');
}
console.log('Patched fetch sending request to:', url);
const response = await originalFetch(url, options);
// Store the call ID if it exists
lastCallId = response.headers.get('x-litellm-call-id');
return response;
};
function loadVertexAiCredentials() {
console.log("loading vertex ai credentials");
const filepath = path.dirname(__filename);
const vertexKeyPath = path.join(filepath, "vertex_key.json");
// Initialize default empty service account data
let serviceAccountKeyData = {};
// Try to read existing vertex_key.json
try {
const content = fs.readFileSync(vertexKeyPath, 'utf8');
if (content && content.trim()) {
serviceAccountKeyData = JSON.parse(content);
}
} catch (error) {
// File doesn't exist or is invalid, continue with empty object
}
// Update with environment variables
const privateKeyId = process.env.VERTEX_AI_PRIVATE_KEY_ID || "";
const privateKey = (process.env.VERTEX_AI_PRIVATE_KEY || "").replace(/\\n/g, "\n");
serviceAccountKeyData.private_key_id = privateKeyId;
serviceAccountKeyData.private_key = privateKey;
// Create temporary file
const tempFilePath = path.join(os.tmpdir(), `vertex-credentials-${Date.now()}.json`);
writeFileSync(tempFilePath, JSON.stringify(serviceAccountKeyData, null, 2));
// Set environment variable
process.env.GOOGLE_APPLICATION_CREDENTIALS = tempFilePath;
}
// Run credential loading before tests
beforeAll(() => {
loadVertexAiCredentials();
});
// Configure Jest to retry flaky tests up to 3 times (useful for 429 rate limiting)
jest.retryTimes(3);
describe('Vertex AI Tests', () => {
test('should successfully generate non-streaming content with tags', async () => {
const vertexAI = new VertexAI({
project: 'litellm-ci-cd',
location: 'global',
apiEndpoint: "127.0.0.1:4000/vertex_ai"
});
const customHeaders = new Headers({
"x-litellm-api-key": "sk-1234",
"tags": "vertex-js-sdk,pass-through-endpoint"
});
const requestOptions = {
customHeaders: customHeaders
};
const generativeModel = vertexAI.getGenerativeModel(
{ model: 'gemini-3.1-flash-lite' },
requestOptions
);
const request = {
contents: [{role: 'user', parts: [{text: 'Say "hello test" and nothing else'}]}]
};
const result = await runVertexRequestOrSkip(() =>
generativeModel.generateContent(request)
);
if (result === null) {
return;
}
expect(result).toBeDefined();
// Use the captured callId
const callId = lastCallId;
console.log("Captured Call ID:", callId);
// Poll for spend data with retries (DB writes can be slow in CI)
let spendData = null;
for (let attempt = 0; attempt < 6; attempt++) {
await new Promise(resolve => setTimeout(resolve, 10000));
const spendResponse = await fetch(
`http://127.0.0.1:4000/spend/logs?request_id=${callId}`,
{ headers: { 'Authorization': 'Bearer sk-1234' } }
);
spendData = await spendResponse.json();
console.log(`spendData (attempt ${attempt + 1}):`, spendData);
if (spendData && spendData.length > 0 && spendData[0] && spendData[0].request_id) break;
}
if (!spendData || !spendData.length || !spendData[0] || !spendData[0].request_id) {
console.warn('Spend data not available after polling - skipping spend assertions (DB write may be slow in CI)');
return;
}
expect(spendData[0].request_id).toBe(callId);
expect(spendData[0].call_type).toBe('pass_through_endpoint');
expect(spendData[0].request_tags).toEqual(['vertex-js-sdk', 'pass-through-endpoint']);
expect(spendData[0].metadata).toHaveProperty('user_api_key');
expect(spendData[0].model).toContain('gemini');
expect(spendData[0].spend).toBeGreaterThan(0);
expect(spendData[0].custom_llm_provider).toBe('vertex_ai');
}, 90000);
test('should successfully generate streaming content with tags', async () => {
const vertexAI = new VertexAI({
project: 'litellm-ci-cd',
location: 'global',
apiEndpoint: "127.0.0.1:4000/vertex_ai"
});
const customHeaders = new Headers({
"x-litellm-api-key": "sk-1234",
"tags": "vertex-js-sdk,pass-through-endpoint"
});
const requestOptions = {
customHeaders: customHeaders
};
const generativeModel = vertexAI.getGenerativeModel(
{ model: 'gemini-3.1-flash-lite' },
requestOptions
);
const request = {
contents: [{role: 'user', parts: [{text: 'Say "hello test" and nothing else'}]}]
};
const streamingResult = await runVertexRequestOrSkip(() =>
generativeModel.generateContentStream(request)
);
if (streamingResult === null) {
return;
}
expect(streamingResult).toBeDefined();
// Add some assertions
expect(streamingResult).toBeDefined();
for await (const item of streamingResult.stream) {
console.log('stream chunk:', JSON.stringify(item));
expect(item).toBeDefined();
}
const aggregatedResponse = await streamingResult.response;
console.log('aggregated response:', JSON.stringify(aggregatedResponse));
expect(aggregatedResponse).toBeDefined();
// Use the captured callId
const callId = lastCallId;
console.log("Captured Call ID:", callId);
// Poll for spend data with retries (DB writes can be slow in CI)
let spendData = null;
for (let attempt = 0; attempt < 6; attempt++) {
await new Promise(resolve => setTimeout(resolve, 10000));
const spendResponse = await fetch(
`http://127.0.0.1:4000/spend/logs?request_id=${callId}`,
{ headers: { 'Authorization': 'Bearer sk-1234' } }
);
spendData = await spendResponse.json();
console.log(`spendData (attempt ${attempt + 1}):`, spendData);
if (spendData && spendData.length > 0 && spendData[0] && spendData[0].request_id) break;
}
if (!spendData || !spendData.length || !spendData[0] || !spendData[0].request_id) {
console.warn('Spend data not available after polling - skipping spend assertions (DB write may be slow in CI)');
return;
}
expect(spendData[0].request_id).toBe(callId);
expect(spendData[0].call_type).toBe('pass_through_endpoint');
expect(spendData[0].request_tags).toEqual(['vertex-js-sdk', 'pass-through-endpoint']);
expect(spendData[0].metadata).toHaveProperty('user_api_key');
expect(spendData[0].model).toContain('gemini');
expect(spendData[0].spend).toBeGreaterThan(0);
expect(spendData[0].custom_llm_provider).toBe('vertex_ai');
}, 90000);
});

View file

@ -1,13 +0,0 @@
{
"type": "service_account",
"project_id": "litellm-ci-cd",
"private_key_id": "",
"private_key": "",
"client_email": "test-litellm-ci-cd@litellm-ci-cd.iam.gserviceaccount.com",
"client_id": "116563532503305622785",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/test-litellm-ci-cd%40litellm-ci-cd.iam.gserviceaccount.com",
"universe_domain": "googleapis.com"
}

View file

@ -1,27 +0,0 @@
function isVertexQuotaError(error) {
const message = [
error && error.message,
error && error.stack,
error && error.cause && JSON.stringify(error.cause),
].filter(Boolean).join('\n');
return (
message.includes('429') ||
message.includes('Too Many Requests') ||
message.includes('RESOURCE_EXHAUSTED')
);
}
async function runVertexRequestOrSkip(requestFn) {
try {
return await requestFn();
} catch (error) {
if (isVertexQuotaError(error)) {
console.warn('Vertex AI quota exhausted; skipping live provider assertions for this run');
return null;
}
throw error;
}
}
module.exports = { isVertexQuotaError, runVertexRequestOrSkip };

View file

@ -3,7 +3,7 @@ import json
import logging
import os
import sys
from collections.abc import Callable, Mapping
from collections.abc import Callable, Iterator, Mapping
from contextlib import ExitStack, contextmanager
from dataclasses import dataclass
from io import BytesIO
@ -28,6 +28,7 @@ from litellm.proxy.pass_through_endpoints.pass_through_endpoints import (
HttpPassThroughEndpointHelpers,
InitPassThroughEndpointHelpers,
_registered_pass_through_routes,
_with_trace_context,
chat_completion_pass_through_endpoint,
create_pass_through_route,
initialize_pass_through_endpoints,
@ -35,7 +36,6 @@ from litellm.proxy.pass_through_endpoints.pass_through_endpoints import (
resolve_llm_passthrough_timeout,
resolve_pass_through_request_timeout,
websocket_passthrough_request,
_with_trace_context,
)
from litellm.proxy.pass_through_endpoints.success_handler import (
PassThroughEndpointLogging,
@ -7084,3 +7084,117 @@ async def test_pass_throughs_stay_open_while_a_db_sync_reads_the_database(tmp_pa
await sync
assert (config_during_sync.status_code, db_during_sync.status_code) == (200, 200)
@pytest.fixture
def httpx_transport(monkeypatch: pytest.MonkeyPatch) -> Iterator[None]:
monkeypatch.setattr(litellm, "disable_aiohttp_transport", True)
litellm.in_memory_llm_clients_cache.flush_cache()
yield
litellm.in_memory_llm_clients_cache.flush_cache()
@pytest.mark.asyncio
@pytest.mark.parametrize(("operation", "stream"), (("generateContent", False), ("streamGenerateContent", True)))
async def test_vertex_passthrough_forwards_body_and_request_tags_to_spend_payload(
operation: str, stream: bool, httpx_transport: None
) -> None:
from datetime import datetime
import respx
from litellm.types.passthrough_endpoints.pass_through_endpoints import PassthroughStandardLoggingPayload
target: Final = (
"https://aiplatform.googleapis.com/v1/projects/test-project/locations/global/publishers/google/"
f"models/gemini-3.1-flash-lite:{operation}"
)
request_body: Final = {"contents": [{"role": "user", "parts": [{"text": "Say hello"}]}]}
response_body: Final = {
"candidates": [{"content": {"role": "model", "parts": [{"text": "hello"}]}, "finishReason": "STOP"}],
"usageMetadata": {"promptTokenCount": 1, "candidatesTokenCount": 1, "totalTokenCount": 2},
}
from starlette.types import Message
async def receive() -> Message:
return {"type": "http.request", "body": json.dumps(request_body).encode(), "more_body": False}
request: Final = Request(
{
"type": "http",
"method": "POST",
"path": "/test",
"scheme": "http",
"server": ("localhost", 8000),
"query_string": b"",
"headers": [(b"tags", b"vertex-js-sdk,pass-through-endpoint")],
},
receive=receive,
)
user_api_key_dict: Final = UserAPIKeyAuth(api_key="test-key", user_id="test-user", team_id="test-team")
with respx.mock(assert_all_called=True) as upstream:
route: Final = upstream.post(target).respond(status_code=200, json=response_body)
response: Final = await pass_through_request(
request=request,
target=target,
custom_headers={},
user_api_key_dict=user_api_key_dict,
custom_llm_provider="vertex_ai",
stream=stream,
)
assert response.status_code == 200
if stream:
streamed_body: Final = b"".join([chunk async for chunk in response.body_iterator])
assert json.loads(streamed_body) == response_body
assert json.loads(route.calls.last.request.content) == request_body
start_time: Final = datetime(2026, 1, 15, 12, 0, 0)
passthrough_payload: Final = PassthroughStandardLoggingPayload(
url=target,
request_body=request_body,
request_method="POST",
)
logging_obj: Final = LiteLLMLoggingObj(
model="gemini-3.1-flash-lite",
messages=[],
stream=stream,
call_type="pass_through_endpoint",
start_time=start_time,
litellm_call_id="vertex-request-tags",
function_id="vertex-request-tags",
)
logging_kwargs: Final = HttpPassThroughEndpointHelpers._init_kwargs_for_pass_through_endpoint(
request=request,
user_api_key_dict=user_api_key_dict,
passthrough_logging_payload=passthrough_payload,
_parsed_body=request_body,
litellm_call_id="vertex-request-tags",
logging_obj=logging_obj,
)
logging_obj.update_environment_variables(
model="gemini-3.1-flash-lite",
user="unknown",
optional_params={},
litellm_params=logging_kwargs["litellm_params"],
call_type="pass_through_endpoint",
)
log_dispatch: Final = AsyncMock()
await PassThroughEndpointLogging(log_dispatch=log_dispatch).pass_through_async_success_handler(
httpx_response=route.calls.last.response,
response_body=response_body,
logging_obj=logging_obj,
url_route=target,
result=json.dumps(response_body),
start_time=start_time,
end_time=start_time,
cache_hit=False,
request_body=request_body,
custom_llm_provider="vertex_ai",
**logging_kwargs,
)
logged_kwargs: Final = log_dispatch.await_args.kwargs
assert logged_kwargs["call_type"] == "pass_through_endpoint"
assert logged_kwargs["litellm_params"]["metadata"]["tags"] == ["vertex-js-sdk", "pass-through-endpoint"]