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

This commit is contained in:
Joshua Valluru 2026-10-20 21:30:01 -07:00
parent d5c4b5f28a
commit a5a1d649cb
13 changed files with 155 additions and 4768 deletions

View file

@ -2309,23 +2309,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

@ -599,3 +599,52 @@ def test_budget_rejected_call_keeps_budget_normalized_error(gateway: Gateway, tm
)
)
assert len(budget_rows) == 1, budget_rows
@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(process.env.LITELLM_MASTER_KEY);
// 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,86 +0,0 @@
const { GoogleGenerativeAI } = require("@google/generative-ai");
const fs = require('fs');
const path = require('path');
const masterKey = process.env.LITELLM_MASTER_KEY;
if (!masterKey) throw new Error("LITELLM_MASTER_KEY must be set");
// 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 streaming content with tags', async () => {
const genAI = new GoogleGenerativeAI(masterKey);
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 ${masterKey}` } }
);
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,58 +0,0 @@
const { GoogleGenerativeAI, ModelParams, RequestOptions } = require("@google/generative-ai");
const masterKey = process.env.LITELLM_MASTER_KEY;
if (!masterKey) throw new Error("LITELLM_MASTER_KEY must be set");
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(masterKey);
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,46 +0,0 @@
const { VertexAI, RequestOptions } = require('@google-cloud/vertexai');
const masterKey = process.env.LITELLM_MASTER_KEY;
if (!masterKey) throw new Error("LITELLM_MASTER_KEY must be set");
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": masterKey,
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,147 +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');
const masterKey = process.env.LITELLM_MASTER_KEY;
if (!masterKey) throw new Error("LITELLM_MASTER_KEY must be set");
// 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": masterKey
});
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": masterKey});
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,216 +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');
const masterKey = process.env.LITELLM_MASTER_KEY;
if (!masterKey) throw new Error("LITELLM_MASTER_KEY must be set");
// 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": masterKey,
"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 ${masterKey}` } }
);
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": masterKey,
"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 ${masterKey}` } }
);
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

@ -9083,3 +9083,109 @@ def test_update_subpath_route_updates_registry():
del _registered_pass_through_routes[route_key]
asyncio.run(_async_test())
@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"]