forked from phoenix/litellm-mirror
add e2e testing for anthropic spend logs
This commit is contained in:
parent
de20869139
commit
43d483db5b
1 changed files with 210 additions and 0 deletions
|
@ -5,6 +5,8 @@ This test ensures that the proxy can passthrough anthropic requests
|
|||
|
||||
import pytest
|
||||
import anthropic
|
||||
import aiohttp
|
||||
import asyncio
|
||||
|
||||
client = anthropic.Anthropic(
|
||||
base_url="http://0.0.0.0:4000/anthropic", api_key="sk-1234"
|
||||
|
@ -47,3 +49,211 @@ def test_anthropic_streaming():
|
|||
|
||||
full_response = "".join(collected_output)
|
||||
print(full_response)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_anthropic_basic_completion_with_headers():
|
||||
print("making basic completion request to anthropic passthrough with aiohttp")
|
||||
|
||||
headers = {
|
||||
"Authorization": f"Bearer sk-1234",
|
||||
"Content-Type": "application/json",
|
||||
"Anthropic-Version": "2023-06-01",
|
||||
}
|
||||
|
||||
payload = {
|
||||
"model": "claude-3-5-sonnet-20241022",
|
||||
"max_tokens": 10,
|
||||
"messages": [{"role": "user", "content": "Say 'hello test' and nothing else"}],
|
||||
"litellm_metadata": {
|
||||
"tags": ["test-tag-1", "test-tag-2"],
|
||||
},
|
||||
}
|
||||
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.post(
|
||||
"http://0.0.0.0:4000/anthropic/v1/messages", json=payload, headers=headers
|
||||
) as response:
|
||||
response_text = await response.text()
|
||||
print(f"Response text: {response_text}")
|
||||
|
||||
response_json = await response.json()
|
||||
response_headers = response.headers
|
||||
litellm_call_id = response_headers.get("x-litellm-call-id")
|
||||
|
||||
print(f"LiteLLM Call ID: {litellm_call_id}")
|
||||
|
||||
# Wait for spend to be logged
|
||||
await asyncio.sleep(15)
|
||||
|
||||
# Check spend logs for this specific request
|
||||
async with session.get(
|
||||
f"http://0.0.0.0:4000/spend/logs?request_id={litellm_call_id}",
|
||||
headers={"Authorization": "Bearer sk-1234"},
|
||||
) as spend_response:
|
||||
print("text spend response")
|
||||
print(f"Spend response: {spend_response}")
|
||||
spend_data = await spend_response.json()
|
||||
print(f"Spend data: {spend_data}")
|
||||
assert spend_data is not None, "Should have spend data for the request"
|
||||
|
||||
log_entry = spend_data[
|
||||
0
|
||||
] # Get the first (and should be only) log entry
|
||||
|
||||
# Basic existence checks
|
||||
assert spend_data is not None, "Should have spend data for the request"
|
||||
assert isinstance(log_entry, dict), "Log entry should be a dictionary"
|
||||
|
||||
# Request metadata assertions
|
||||
assert (
|
||||
log_entry["request_id"] == litellm_call_id
|
||||
), "Request ID should match"
|
||||
assert (
|
||||
log_entry["call_type"] == "pass_through_endpoint"
|
||||
), "Call type should be pass_through_endpoint"
|
||||
assert (
|
||||
log_entry["api_base"] == "https://api.anthropic.com/v1/messages"
|
||||
), "API base should be Anthropic's endpoint"
|
||||
|
||||
# Token and spend assertions
|
||||
assert log_entry["spend"] > 0, "Spend value should not be None"
|
||||
assert isinstance(
|
||||
log_entry["spend"], (int, float)
|
||||
), "Spend should be a number"
|
||||
assert log_entry["total_tokens"] > 0, "Should have some tokens"
|
||||
assert log_entry["prompt_tokens"] > 0, "Should have prompt tokens"
|
||||
assert (
|
||||
log_entry["completion_tokens"] > 0
|
||||
), "Should have completion tokens"
|
||||
assert (
|
||||
log_entry["total_tokens"]
|
||||
== log_entry["prompt_tokens"] + log_entry["completion_tokens"]
|
||||
), "Total tokens should equal prompt + completion"
|
||||
|
||||
# Time assertions
|
||||
assert all(
|
||||
key in log_entry
|
||||
for key in ["startTime", "endTime", "completionStartTime"]
|
||||
), "Should have all time fields"
|
||||
assert (
|
||||
log_entry["startTime"] < log_entry["endTime"]
|
||||
), "Start time should be before end time"
|
||||
|
||||
# Metadata assertions
|
||||
assert log_entry["cache_hit"] == "False", "Cache should be off"
|
||||
assert log_entry["request_tags"] == [
|
||||
"test-tag-1",
|
||||
"test-tag-2",
|
||||
], "Tags should match input"
|
||||
assert (
|
||||
"user_api_key" in log_entry["metadata"]
|
||||
), "Should have user API key in metadata"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_anthropic_streaming_with_headers():
|
||||
print("making streaming request to anthropic passthrough with aiohttp")
|
||||
|
||||
headers = {
|
||||
"Authorization": f"Bearer sk-1234",
|
||||
"Content-Type": "application/json",
|
||||
"Anthropic-Version": "2023-06-01",
|
||||
}
|
||||
|
||||
payload = {
|
||||
"model": "claude-3-5-sonnet-20241022",
|
||||
"max_tokens": 10,
|
||||
"messages": [
|
||||
{"role": "user", "content": "Say 'hello stream test' and nothing else"}
|
||||
],
|
||||
"stream": True,
|
||||
"litellm_metadata": {
|
||||
"tags": ["test-tag-stream-1", "test-tag-stream-2"],
|
||||
},
|
||||
}
|
||||
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.post(
|
||||
"http://0.0.0.0:4000/anthropic/v1/messages", json=payload, headers=headers
|
||||
) as response:
|
||||
print("response status")
|
||||
print(response.status)
|
||||
assert response.status == 200, "Response should be successful"
|
||||
response_headers = response.headers
|
||||
print(f"Response headers: {response_headers}")
|
||||
litellm_call_id = response_headers.get("x-litellm-call-id")
|
||||
print(f"LiteLLM Call ID: {litellm_call_id}")
|
||||
|
||||
collected_output = []
|
||||
async for line in response.content:
|
||||
if line:
|
||||
text = line.decode("utf-8").strip()
|
||||
if text.startswith("data: "):
|
||||
collected_output.append(text[6:]) # Remove 'data: ' prefix
|
||||
|
||||
print("Collected output:", "".join(collected_output))
|
||||
|
||||
# Wait for spend to be logged
|
||||
await asyncio.sleep(20)
|
||||
|
||||
# Check spend logs for this specific request
|
||||
async with session.get(
|
||||
f"http://0.0.0.0:4000/spend/logs?request_id={litellm_call_id}",
|
||||
headers={"Authorization": "Bearer sk-1234"},
|
||||
) as spend_response:
|
||||
spend_data = await spend_response.json()
|
||||
print(f"Spend data: {spend_data}")
|
||||
assert spend_data is not None, "Should have spend data for the request"
|
||||
|
||||
log_entry = spend_data[
|
||||
0
|
||||
] # Get the first (and should be only) log entry
|
||||
|
||||
# Basic existence checks
|
||||
assert spend_data is not None, "Should have spend data for the request"
|
||||
assert isinstance(log_entry, dict), "Log entry should be a dictionary"
|
||||
|
||||
# Request metadata assertions
|
||||
assert (
|
||||
log_entry["request_id"] == litellm_call_id
|
||||
), "Request ID should match"
|
||||
assert (
|
||||
log_entry["call_type"] == "pass_through_endpoint"
|
||||
), "Call type should be pass_through_endpoint"
|
||||
assert (
|
||||
log_entry["api_base"] == "https://api.anthropic.com/v1/messages"
|
||||
), "API base should be Anthropic's endpoint"
|
||||
|
||||
# Token and spend assertions
|
||||
assert log_entry["spend"] > 0, "Spend value should not be None"
|
||||
assert isinstance(
|
||||
log_entry["spend"], (int, float)
|
||||
), "Spend should be a number"
|
||||
assert log_entry["total_tokens"] > 0, "Should have some tokens"
|
||||
assert (
|
||||
log_entry["completion_tokens"] > 0
|
||||
), "Should have completion tokens"
|
||||
assert (
|
||||
log_entry["total_tokens"]
|
||||
== log_entry["prompt_tokens"] + log_entry["completion_tokens"]
|
||||
), "Total tokens should equal prompt + completion"
|
||||
|
||||
# Time assertions
|
||||
assert all(
|
||||
key in log_entry
|
||||
for key in ["startTime", "endTime", "completionStartTime"]
|
||||
), "Should have all time fields"
|
||||
assert (
|
||||
log_entry["startTime"] < log_entry["endTime"]
|
||||
), "Start time should be before end time"
|
||||
|
||||
# Metadata assertions
|
||||
assert log_entry["cache_hit"] == "False", "Cache should be off"
|
||||
assert log_entry["request_tags"] == [
|
||||
"test-tag-stream-1",
|
||||
"test-tag-stream-2",
|
||||
], "Tags should match input"
|
||||
assert (
|
||||
"user_api_key" in log_entry["metadata"]
|
||||
), "Should have user API key in metadata"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue