* chore(tests): migrate Bedrock CI from AWS account 888602223428 to 941277531214
The original account (888602223428) was put under a security restriction by
AWS after a root access key leaked in a PR comment. While that account works
its way through the AWS Support unlock process, Bedrock-touching CI tests have
been migrated to a fresh account (941277531214).
Changes:
- Replace 26 hardcoded references to 888602223428 with 941277531214 across
8 files (provisioned-model ARNs, imported-model ARNs, AgentCore runtime
ARNs, batch execution role ARN, and example proxy config).
- The provisioned-model and imported-model ARNs are referenced only from
mocked unit tests — no AWS resources to recreate.
- The batch execution IAM role has been recreated in the new account with
the same name and equivalent permissions.
- The two AgentCore runtimes (hosted_agent_r9jvp-3ySZuRHjLC,
hosted_agent_13sf6-cALnp38iZD) are being recreated in the new account
under the same names — see tools/agentcore-deploy/ in a follow-up.
CircleCI env vars AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY / AWS_REGION_NAME
were updated separately via the CircleCI API to point at the new account.
Smoke-tested locally against the new account:
aws bedrock-runtime converse --region us-west-2 \
--model-id us.anthropic.claude-sonnet-4-5-20250929-v1:0 \
--messages '[{"role":"user","content":[{"text":"ping"}]}]'
→ 200, model returned 'pong'
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* chore(tests): refresh AgentCore ARN suffixes to match newly-deployed runtimes
The first migration commit replaced just the account ID, but AgentCore
auto-assigns a random 10-char suffix to every runtime on creation — we
can't reuse the original suffixes (`3ySZuRHjLC`, `cALnp38iZD`) in the
new account. Updated the AgentCore-runtime ARNs in the three files that
reference real runtime IDs (not the mock-based unit-test ARNs).
Deployed runtimes:
arn:aws:bedrock-agentcore:us-west-2:941277531214:runtime/hosted_agent_r9jvp-Rq79QFC2fp
arn:aws:bedrock-agentcore:us-west-2:941277531214:runtime/hosted_agent_13sf6-4046UzHSwy
Both runtimes are status=READY and pass a smoke invoke:
$ aws bedrock-agentcore invoke-agent-runtime --agent-runtime-arn ... --payload '{"prompt":"ping"}'
→ 200, {"result": "echo: ping"}
The agent is a minimal echo (see /tmp/agentcore_deploy/agent.py for the
deploy artifacts). Tests that only verify the SDK wiring will pass; if any
test asserts on agent output content, swap the echo for the real agent.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* chore(tests): point Bedrock batch tests at new-account S3 bucket
The account migration (888602223428 -> 941277531214) was a flat
account-ID swap, which only rewrites ARNs that embed the account
number. S3 bucket names carry no account ID, so the live Bedrock
batch tests still uploaded to `litellm-proxy` — a bucket that lives
in the old account. S3 names are globally unique, and the old account
still holds that name, so it can't be recreated in the new account.
Rename to `litellm-proxy-941277531214` (account-ID suffix guarantees
global uniqueness). The bucket must be created in 941277531214 and the
batch execution role granted s3:GetObject/PutObject/ListBucket on it
before this job is run in CI.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(tests): point live S3 logging test at new-account bucket
Same account-ID-free blind spot as the batch bucket: `load-testing-oct`
lives in the old account and its name can't be reused globally. The
`logging_testing` CI job is wired into the workflow and runs
test_basic_s3_logging, which uploads to this bucket with the CI env
creds, then lists and deletes objects — a live dependency.
Rename to `load-testing-oct-941277531214`. The bucket must exist in the
new account with the CI IAM principal granted
s3:PutObject/GetObject/ListBucket/DeleteObject before this job runs.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(tests): repoint Bedrock guardrail IDs to new-account guardrails
The migration left guardrail IDs untouched (no account ID in them), so
all live guardrail tests failed with "guardrail identifier or version
does not exist" against 941277531214. Recreated both guardrails in the
new account and updated the hardcoded IDs:
- wf0hkdb5x07f -> zgkmukebruil (PII mask: PHONE + CREDIT_DEBIT_CARD,
with explicit inputAction=ANONYMIZE so masking applies to INPUT,
which is the source litellm's moderation hook sends)
- ff6ujrregl1q -> 4w3d1di3snt5 (blocks "coffee"; blocked message set
to the exact string the tests assert on)
Updated test_bedrock_guardrails.py, otel_test_config.yaml, and the
guardrailConfig in test_bedrock_completion.py. Verified locally: the 5
previously-failing guardrail tests now pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* test(bedrock): migrate legacy models to current inference profiles
The new CI account (941277531214) cannot invoke legacy Bedrock models
(AWS gates them: "marked by provider as Legacy... not actively using in
the last 30 days"). Migrated the live-call tests:
- anthropic.claude-3-sonnet-20240229 -> us.anthropic.claude-sonnet-4-5-20250929-v1:0
- anthropic.claude-3-haiku-20240307 -> us.anthropic.claude-haiku-4-5-20251001-v1:0
Current Claude models on Bedrock require the us. inference-profile prefix
(bare on-demand ids are rejected).
cohere.command-r-plus has no working replacement (all Cohere is legacy-
gated in the new account): swapped to claude-haiku-4-5 in provider-
agnostic param lists. amazon.titan-image-generator skipped (no working
replacement). Mocked/transformation/cost tests that reference the legacy
strings are intentionally left unchanged. Verified live against the new
account.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* test(bedrock): repoint SageMaker + Knowledge Base to new-account resources
These referenced account-scoped resources by hardcoded id that only
existed in the old account, so the migration's account-ID swap missed
them. Recreated in 941277531214 and repointed:
- SageMaker endpoint jumpstart-dft-hf-textgeneration1-mp-20240815-185614
-> litellm-ci-textgen (gpt2 on a TGI container, ml.g5.xlarge)
- Bedrock Knowledge Base T37J8R4WTM -> LCYXFBR2TU (OpenSearch Serverless
vector store + titan-embed-text-v2, seeded with a LiteLLM doc)
Verified live: test_sagemaker.py (12 passed) and
test_bedrock_knowledgebase_hook.py (12 passed).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* test(reasoning_effort_grid): skip bedrock claude-opus-4-7 cells (not entitled on 941277531214)
claude-opus-4-7 is listed in the new Bedrock CI account's foundation
models but invoke is denied (AccessDeniedException: "not available for
this account"). Bedrock access to the flagship Opus requires an AWS
Sales request, not the self-serve model-access toggle, so it can't be
enabled inline with the rest of the account migration.
Add an optional `skip_reason` to ModelEntry and set it on the
bedrock-claude-opus-4-7 entry; the grid test honors it via pytest.skip.
Cell count (231) and route coverage are unchanged, so the structural
asserts still pass. Restore coverage by deleting the one skip_reason
line once access is granted.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* test(bedrock): swap/skip legacy-gated models unavailable on new CI account
The migrated AWS account (941277531214) cannot access several models that
the old account could, so the remaining red CI jobs were hitting real
Bedrock "Access denied / Legacy" and "account not authorized" errors:
- image_gen: skip both Nova Canvas test classes (amazon.nova-canvas-v1:0 is
legacy-gated), matching the existing titan skip.
- batches: skip test_async_file_and_batch (Bedrock batch inference is not
authorized on the new account; requires an AWS support case).
- litellm_overhead: swap legacy claude-3-5-haiku for the active
us.anthropic.claude-haiku-4-5 inference profile.
- test_completion_claude_3_function_call: swap legacy claude-3-sonnet for the
active us.anthropic.claude-sonnet-4-5 inference profile.
https://claude.ai/code/session_01Y7zgHYu9GX29YRwV4yiWAa
* test(bedrock): fix remaining e2e legacy-model + batch failures on new CI account
- e2e_openai_endpoints: skip test_bedrock_batches_api (Bedrock batch inference
is not authorized on account 941277531214) and migrate the missed
s3_bucket_name in oai_misc_config.yaml to litellm-proxy-941277531214.
- build_and_test: swap legacy bedrock claude-3-sonnet for the active
us.anthropic.claude-sonnet-4-5 inference profile in the proxy structured
output e2e test.
https://claude.ai/code/session_01Y7zgHYu9GX29YRwV4yiWAa
* test(bedrock): make opus-4-7 + batch cells fail loudly and mock image-gen (#28791)
Replace the silent skips added for the new CI account with noisier behavior:
- reasoning-effort grid: opus-4-7 cells now fail (when AWS creds are present)
instead of skipping, so the missing entitlement stays visible in CI; they
still skip when AWS creds are absent (local dev)
- Bedrock batch inference tests: drop the skip so they run and fail until
batch access is granted
- Titan + Nova Canvas image-gen tests: mock the Bedrock HTTP call so the
transform + cost-tracking path stays under test without live model access
https://claude.ai/code/session_01MT7SWDnXUjv6e6EPG7BDjT
Co-authored-by: Claude <noreply@anthropic.com>
* test(bedrock): use pytest.xfail for known-failing opus-4-7 cells
Replace pytest.fail with pytest.xfail when a model has a fail_reason,
so known-broken cells stay visible as XFAIL without keeping CI red.
Co-authored-by: Yassin Kortam <yassin@berri.ai>
---------
Co-authored-by: Mateo <mateo@Mateos-MacBook-Pro.local>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Yassin Kortam <yassin@berri.ai>
420 lines
14 KiB
Python
420 lines
14 KiB
Python
import sys
|
|
import os
|
|
import io, asyncio
|
|
|
|
# import logging
|
|
# logging.basicConfig(level=logging.DEBUG)
|
|
sys.path.insert(0, os.path.abspath("../.."))
|
|
|
|
from litellm import completion
|
|
import litellm
|
|
|
|
litellm.num_retries = 3
|
|
|
|
import time, random
|
|
import pytest
|
|
import boto3
|
|
from litellm._logging import verbose_logger
|
|
import logging
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@pytest.mark.parametrize(
|
|
"sync_mode,streaming", [(True, True), (True, False), (False, True), (False, False)]
|
|
)
|
|
@pytest.mark.flaky(retries=3, delay=1)
|
|
async def test_basic_s3_logging(sync_mode, streaming):
|
|
verbose_logger.setLevel(level=logging.DEBUG)
|
|
litellm.success_callback = ["s3"]
|
|
litellm.s3_callback_params = {
|
|
"s3_bucket_name": "load-testing-oct-941277531214",
|
|
"s3_aws_secret_access_key": "os.environ/AWS_SECRET_ACCESS_KEY",
|
|
"s3_aws_access_key_id": "os.environ/AWS_ACCESS_KEY_ID",
|
|
"s3_region_name": "us-west-2",
|
|
}
|
|
litellm.set_verbose = True
|
|
response_id = None
|
|
if sync_mode is True:
|
|
response = litellm.completion(
|
|
model="gpt-5-mini",
|
|
messages=[{"role": "user", "content": "This is a test"}],
|
|
mock_response="It's simple to use and easy to get started",
|
|
stream=streaming,
|
|
)
|
|
if streaming:
|
|
for chunk in response:
|
|
print()
|
|
response_id = chunk.id
|
|
else:
|
|
response_id = response.id
|
|
time.sleep(2)
|
|
else:
|
|
response = await litellm.acompletion(
|
|
model="gpt-5-mini",
|
|
messages=[{"role": "user", "content": "This is a test"}],
|
|
mock_response="It's simple to use and easy to get started",
|
|
stream=streaming,
|
|
)
|
|
if streaming:
|
|
async for chunk in response:
|
|
print(chunk)
|
|
response_id = chunk.id
|
|
else:
|
|
response_id = response.id
|
|
await asyncio.sleep(2)
|
|
print(f"response: {response}")
|
|
|
|
total_objects, all_s3_keys = list_all_s3_objects("load-testing-oct-941277531214")
|
|
|
|
# assert that atlest one key has response.id in it
|
|
assert any(response_id in key for key in all_s3_keys)
|
|
s3 = boto3.client("s3")
|
|
# delete all objects
|
|
for key in all_s3_keys:
|
|
s3.delete_object(Bucket="load-testing-oct-941277531214", Key=key)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@pytest.mark.parametrize("streaming", [True])
|
|
@pytest.mark.flaky(retries=3, delay=1)
|
|
async def test_basic_s3_v2_logging(streaming):
|
|
from unittest.mock import AsyncMock, MagicMock, patch
|
|
from litellm.integrations.s3_v2 import S3Logger
|
|
|
|
litellm.s3_callback_params = {
|
|
"s3_bucket_name": "load-testing-oct-941277531214",
|
|
"s3_aws_secret_access_key": "test-secret",
|
|
"s3_aws_access_key_id": "test-key",
|
|
"s3_region_name": "us-west-2",
|
|
}
|
|
|
|
s3_v2_logger = S3Logger(s3_flush_interval=1)
|
|
litellm.callbacks = [s3_v2_logger]
|
|
|
|
uploaded_keys: list = []
|
|
original_upload = s3_v2_logger.async_upload_data_to_s3
|
|
|
|
async def mock_upload(batch_logging_element):
|
|
uploaded_keys.append(batch_logging_element.s3_object_key)
|
|
|
|
s3_v2_logger.async_upload_data_to_s3 = mock_upload
|
|
|
|
litellm.set_verbose = True
|
|
response_id = None
|
|
response = await litellm.acompletion(
|
|
model="gpt-5-mini",
|
|
messages=[{"role": "user", "content": "This is a test"}],
|
|
mock_response="It's simple to use and easy to get started",
|
|
stream=streaming,
|
|
)
|
|
if streaming:
|
|
async for chunk in response:
|
|
response_id = chunk.id
|
|
else:
|
|
response_id = response.id
|
|
|
|
await asyncio.sleep(5)
|
|
|
|
assert len(uploaded_keys) > 0, "S3 upload was never called"
|
|
assert any(
|
|
response_id in key for key in uploaded_keys
|
|
), f"Expected response_id={response_id} in one of the uploaded S3 keys: {uploaded_keys}"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@pytest.mark.flaky(retries=3, delay=1)
|
|
async def test_basic_s3_v2_logging_failure():
|
|
"""Test that S3 v2 logger makes httpx PUT request when logging failures"""
|
|
from unittest.mock import AsyncMock, MagicMock, patch
|
|
from litellm.integrations.s3_v2 import S3Logger
|
|
|
|
# Create S3 logger with short flush interval
|
|
s3_v2_logger = S3Logger(s3_flush_interval=1)
|
|
|
|
# Mock the httpx client to capture the PUT request
|
|
mock_response = MagicMock()
|
|
mock_response.status_code = 200
|
|
mock_response.raise_for_status = MagicMock()
|
|
|
|
s3_v2_logger.async_httpx_client = AsyncMock()
|
|
s3_v2_logger.async_httpx_client.put.return_value = mock_response
|
|
|
|
# Track the upload method calls
|
|
original_upload = s3_v2_logger.async_upload_data_to_s3
|
|
upload_called = False
|
|
|
|
async def mock_upload(batch_logging_element):
|
|
nonlocal upload_called
|
|
upload_called = True
|
|
# Mock the upload process but still make the httpx call
|
|
url = f"https://test-bucket.s3.us-west-2.amazonaws.com/{batch_logging_element.s3_object_key}"
|
|
headers = {"Content-Type": "application/json"}
|
|
data = '{"model": "gpt-5-mini"}'
|
|
|
|
# Make the actual httpx call we want to test
|
|
await s3_v2_logger.async_httpx_client.put(url=url, headers=headers, data=data)
|
|
|
|
s3_v2_logger.async_upload_data_to_s3 = mock_upload
|
|
|
|
# Configure S3 callback params
|
|
litellm.callbacks = [s3_v2_logger]
|
|
litellm.s3_callback_params = {
|
|
"s3_bucket_name": "test-bucket",
|
|
"s3_aws_secret_access_key": "test-secret",
|
|
"s3_aws_access_key_id": "test-key",
|
|
"s3_region_name": "us-west-2",
|
|
}
|
|
litellm.set_verbose = True
|
|
|
|
# Trigger a failure by using invalid API key
|
|
try:
|
|
response = await litellm.acompletion(
|
|
model="gpt-5-mini",
|
|
api_key="invalid-api-key",
|
|
messages=[{"role": "user", "content": "This is a test"}],
|
|
)
|
|
except Exception as e:
|
|
print(f"Expected error: {e}")
|
|
|
|
# Wait for logger to process the failure
|
|
await asyncio.sleep(5)
|
|
|
|
# Verify that our mock upload was called
|
|
assert upload_called, "S3 upload method was not called"
|
|
print("✓ S3 upload method was called")
|
|
|
|
# Verify that httpx PUT was called
|
|
s3_v2_logger.async_httpx_client.put.assert_called()
|
|
|
|
# Get the call arguments to verify the S3 URL
|
|
call_args = s3_v2_logger.async_httpx_client.put.call_args
|
|
assert call_args is not None
|
|
url = call_args[1]["url"] if "url" in call_args[1] else call_args[0][0]
|
|
|
|
# Verify the URL contains expected S3 endpoint
|
|
assert "test-bucket.s3.us-west-2.amazonaws.com" in url
|
|
print(f"✓ S3 PUT request made to: {url}")
|
|
|
|
# Verify headers include expected content type
|
|
headers = call_args[1]["headers"]
|
|
assert headers["Content-Type"] == "application/json"
|
|
print("✓ S3 request headers are correct")
|
|
|
|
# Verify JSON data was included
|
|
data = call_args[1]["data"]
|
|
assert data is not None
|
|
assert '"model": "gpt-5-mini"' in data
|
|
print("✓ S3 request data contains expected log payload")
|
|
|
|
|
|
def list_all_s3_objects(bucket_name):
|
|
s3 = boto3.client("s3")
|
|
|
|
all_s3_keys = []
|
|
|
|
paginator = s3.get_paginator("list_objects_v2")
|
|
total_objects = 0
|
|
|
|
for page in paginator.paginate(Bucket=bucket_name):
|
|
if "Contents" in page:
|
|
total_objects += len(page["Contents"])
|
|
all_s3_keys.extend([obj["Key"] for obj in page["Contents"]])
|
|
|
|
print(f"Total number of objects in {bucket_name}: {total_objects}")
|
|
print(all_s3_keys)
|
|
return total_objects, all_s3_keys
|
|
|
|
|
|
@pytest.mark.skip(reason="AWS Suspended Account")
|
|
def test_s3_logging():
|
|
# all s3 requests need to be in one test function
|
|
# since we are modifying stdout, and pytests runs tests in parallel
|
|
# on circle ci - we only test litellm.acompletion()
|
|
try:
|
|
# redirect stdout to log_file
|
|
litellm.cache = litellm.Cache(
|
|
type="s3",
|
|
s3_bucket_name="litellm-my-test-bucket-2",
|
|
s3_region_name="us-east-1",
|
|
)
|
|
|
|
litellm.success_callback = ["s3"]
|
|
litellm.s3_callback_params = {
|
|
"s3_bucket_name": "litellm-logs-2",
|
|
"s3_aws_secret_access_key": "os.environ/AWS_SECRET_ACCESS_KEY",
|
|
"s3_aws_access_key_id": "os.environ/AWS_ACCESS_KEY_ID",
|
|
}
|
|
litellm.set_verbose = True
|
|
|
|
print("Testing async s3 logging")
|
|
|
|
expected_keys = []
|
|
|
|
import time
|
|
|
|
curr_time = str(time.time())
|
|
|
|
async def _test():
|
|
return await litellm.acompletion(
|
|
model="gpt-5-mini",
|
|
messages=[{"role": "user", "content": f"This is a test {curr_time}"}],
|
|
max_tokens=10,
|
|
temperature=0.7,
|
|
user="ishaan-2",
|
|
)
|
|
|
|
response = asyncio.run(_test())
|
|
print(f"response: {response}")
|
|
expected_keys.append(response.id)
|
|
|
|
async def _test():
|
|
return await litellm.acompletion(
|
|
model="gpt-5-mini",
|
|
messages=[{"role": "user", "content": f"This is a test {curr_time}"}],
|
|
max_tokens=10,
|
|
temperature=0.7,
|
|
user="ishaan-2",
|
|
)
|
|
|
|
response = asyncio.run(_test())
|
|
expected_keys.append(response.id)
|
|
print(f"response: {response}")
|
|
time.sleep(5) # wait 5s for logs to land
|
|
|
|
import boto3
|
|
|
|
s3 = boto3.client("s3")
|
|
bucket_name = "litellm-logs-2"
|
|
# List objects in the bucket
|
|
response = s3.list_objects(Bucket=bucket_name)
|
|
|
|
# Sort the objects based on the LastModified timestamp
|
|
objects = sorted(
|
|
response["Contents"], key=lambda x: x["LastModified"], reverse=True
|
|
)
|
|
# Get the keys of the most recent objects
|
|
most_recent_keys = [obj["Key"] for obj in objects]
|
|
print(most_recent_keys)
|
|
# for each key, get the part before "-" as the key. Do it safely
|
|
cleaned_keys = []
|
|
for key in most_recent_keys:
|
|
split_key = key.split("_")
|
|
if len(split_key) < 2:
|
|
continue
|
|
cleaned_keys.append(split_key[1])
|
|
print("\n most recent keys", most_recent_keys)
|
|
print("\n cleaned keys", cleaned_keys)
|
|
print("\n Expected keys: ", expected_keys)
|
|
matches = 0
|
|
for key in expected_keys:
|
|
key += ".json"
|
|
assert key in cleaned_keys
|
|
|
|
if key in cleaned_keys:
|
|
matches += 1
|
|
# remove the match key
|
|
cleaned_keys.remove(key)
|
|
# this asserts we log, the first request + the 2nd cached request
|
|
print("we had two matches ! passed ", matches)
|
|
assert matches == 2
|
|
try:
|
|
# cleanup s3 bucket in test
|
|
for key in most_recent_keys:
|
|
s3.delete_object(Bucket=bucket_name, Key=key)
|
|
except Exception:
|
|
# don't let cleanup fail a test
|
|
pass
|
|
except Exception as e:
|
|
pytest.fail(f"An exception occurred - {e}")
|
|
finally:
|
|
# post, close log file and verify
|
|
# Reset stdout to the original value
|
|
print("Passed! Testing async s3 logging")
|
|
|
|
|
|
# test_s3_logging()
|
|
|
|
|
|
@pytest.mark.skip(reason="AWS Suspended Account")
|
|
def test_s3_logging_async():
|
|
# this tests time added to make s3 logging calls, vs just acompletion calls
|
|
try:
|
|
litellm.set_verbose = True
|
|
# Make 5 calls with an empty success_callback
|
|
litellm.success_callback = []
|
|
start_time_empty_callback = asyncio.run(make_async_calls())
|
|
print("done with no callback test")
|
|
|
|
print("starting s3 logging load test")
|
|
# Make 5 calls with success_callback set to "langfuse"
|
|
litellm.success_callback = ["s3"]
|
|
litellm.s3_callback_params = {
|
|
"s3_bucket_name": "litellm-logs-2",
|
|
"s3_aws_secret_access_key": "os.environ/AWS_SECRET_ACCESS_KEY",
|
|
"s3_aws_access_key_id": "os.environ/AWS_ACCESS_KEY_ID",
|
|
}
|
|
start_time_s3 = asyncio.run(make_async_calls())
|
|
print("done with s3 test")
|
|
|
|
# Compare the time for both scenarios
|
|
print(f"Time taken with success_callback='s3': {start_time_s3}")
|
|
print(f"Time taken with empty success_callback: {start_time_empty_callback}")
|
|
|
|
# assert the diff is not more than 1 second
|
|
assert abs(start_time_s3 - start_time_empty_callback) < 1
|
|
|
|
except litellm.Timeout as e:
|
|
pass
|
|
except Exception as e:
|
|
pytest.fail(f"An exception occurred - {e}")
|
|
|
|
|
|
async def make_async_calls():
|
|
tasks = []
|
|
for _ in range(5):
|
|
task = asyncio.create_task(
|
|
litellm.acompletion(
|
|
model="azure/gpt-4.1-mini",
|
|
messages=[{"role": "user", "content": "This is a test"}],
|
|
max_tokens=5,
|
|
temperature=0.7,
|
|
timeout=5,
|
|
user="langfuse_latency_test_user",
|
|
mock_response="It's simple to use and easy to get started",
|
|
)
|
|
)
|
|
tasks.append(task)
|
|
|
|
# Measure the start time before running the tasks
|
|
start_time = asyncio.get_event_loop().time()
|
|
|
|
# Wait for all tasks to complete
|
|
responses = await asyncio.gather(*tasks)
|
|
|
|
# Print the responses when tasks return
|
|
for idx, response in enumerate(responses):
|
|
print(f"Response from Task {idx + 1}: {response}")
|
|
|
|
# Calculate the total time taken
|
|
total_time = asyncio.get_event_loop().time() - start_time
|
|
|
|
return total_time
|
|
|
|
|
|
from litellm.integrations.s3_v2 import S3Logger
|
|
|
|
|
|
class TestS3Logger(S3Logger):
|
|
def __init__(self, *args, **kwargs):
|
|
self.recorded_requests = {}
|
|
self.logged_standard_logging_payload: Optional[StandardLoggingPayload] = None
|
|
super().__init__(*args, **kwargs)
|
|
|
|
async def async_log_success_event(self, kwargs, response_obj, start_time, end_time):
|
|
self.recorded_requests[response_obj["id"]] = start_time
|
|
print("recorded request", self.recorded_requests)
|
|
self.logged_standard_logging_payload = kwargs["standard_logging_object"]
|
|
return await super().async_log_success_event(
|
|
kwargs, response_obj, start_time, end_time
|
|
)
|