Skip to content

Commit 509a4d7

Browse files
Add response token count logic to Bedrock instrumentation. (#1504)
* Add bedrock token counting. * [MegaLinter] Apply linters fixes * Add bedrock token counting. * Add safeguards when grabbing token counts. * Remove extra None defaults. * Cleanup default None checks. --------- Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
1 parent 055f4f3 commit 509a4d7

11 files changed

+310
-472
lines changed

newrelic/hooks/external_botocore.py

Lines changed: 204 additions & 51 deletions
Large diffs are not rendered by default.

tests/external_aiobotocore/test_bedrock_chat_completion_converse.py

Lines changed: 2 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
)
2424
from testing_support.fixtures import override_llm_token_callback_settings, reset_core_stats_engine, validate_attributes
2525
from testing_support.ml_testing_utils import (
26-
add_token_count_to_events,
26+
add_token_counts_to_chat_events,
2727
disabled_ai_monitoring_record_content_settings,
2828
disabled_ai_monitoring_settings,
2929
events_sans_content,
@@ -147,7 +147,7 @@ def _test():
147147
@reset_core_stats_engine()
148148
@override_llm_token_callback_settings(llm_token_count_callback)
149149
def test_bedrock_chat_completion_with_token_count(set_trace_info, exercise_model, expected_metric, expected_events):
150-
@validate_custom_events(add_token_count_to_events(expected_events))
150+
@validate_custom_events(add_token_counts_to_chat_events(chat_completion_expected_events))
151151
# One summary event, one user message, and one response message from the assistant
152152
@validate_custom_event_count(count=4)
153153
@validate_transaction_metrics(
@@ -278,49 +278,6 @@ def _test():
278278
_test()
279279

280280

281-
@reset_core_stats_engine()
282-
@override_llm_token_callback_settings(llm_token_count_callback)
283-
def test_bedrock_chat_completion_error_incorrect_access_key_with_token_count(
284-
exercise_converse_incorrect_access_key, set_trace_info, expected_metric
285-
):
286-
"""
287-
A request is made to the server with invalid credentials. botocore will reach out to the server and receive an
288-
UnrecognizedClientException as a response. Information from the request will be parsed and reported in customer
289-
events. The error response can also be parsed, and will be included as attributes on the recorded exception.
290-
"""
291-
292-
@validate_custom_events(add_token_count_to_events(chat_completion_invalid_access_key_error_events))
293-
@validate_error_trace_attributes(
294-
_client_error_name,
295-
exact_attrs={
296-
"agent": {},
297-
"intrinsic": {},
298-
"user": {
299-
"http.statusCode": 403,
300-
"error.message": "The security token included in the request is invalid.",
301-
"error.code": "UnrecognizedClientException",
302-
},
303-
},
304-
)
305-
@validate_transaction_metrics(
306-
name="test_bedrock_chat_completion_incorrect_access_key_with_token_count",
307-
scoped_metrics=[expected_metric],
308-
rollup_metrics=[expected_metric],
309-
custom_metrics=[(f"Supportability/Python/ML/Bedrock/{BOTOCORE_VERSION}", 1)],
310-
background_task=True,
311-
)
312-
@background_task(name="test_bedrock_chat_completion_incorrect_access_key_with_token_count")
313-
def _test():
314-
set_trace_info()
315-
add_custom_attribute("llm.conversation_id", "my-awesome-id")
316-
add_custom_attribute("llm.foo", "bar")
317-
add_custom_attribute("non_llm_attr", "python-agent")
318-
319-
exercise_converse_incorrect_access_key()
320-
321-
_test()
322-
323-
324281
@pytest.fixture
325282
def exercise_converse_invalid_model(loop, bedrock_converse_server, response_streaming, monkeypatch):
326283
def _exercise_converse_invalid_model():

tests/external_aiobotocore/test_bedrock_chat_completion_invoke_model.py

Lines changed: 3 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@
3434
)
3535
from testing_support.fixtures import override_llm_token_callback_settings, reset_core_stats_engine, validate_attributes
3636
from testing_support.ml_testing_utils import (
37-
add_token_count_to_events,
37+
add_token_count_streaming_events,
38+
add_token_counts_to_chat_events,
3839
disabled_ai_monitoring_record_content_settings,
3940
disabled_ai_monitoring_settings,
4041
disabled_ai_monitoring_streaming_settings,
@@ -207,7 +208,7 @@ def _test():
207208
@reset_core_stats_engine()
208209
@override_llm_token_callback_settings(llm_token_count_callback)
209210
def test_bedrock_chat_completion_with_token_count(set_trace_info, exercise_model, expected_events, expected_metrics):
210-
@validate_custom_events(add_token_count_to_events(expected_events))
211+
@validate_custom_events(add_token_counts_to_chat_events(add_token_count_streaming_events(expected_events)))
211212
# One summary event, one user message, and one response message from the assistant
212213
@validate_custom_event_count(count=3)
213214
@validate_transaction_metrics(
@@ -456,51 +457,6 @@ def _test():
456457
_test()
457458

458459

459-
@reset_core_stats_engine()
460-
@override_llm_token_callback_settings(llm_token_count_callback)
461-
def test_bedrock_chat_completion_error_incorrect_access_key_with_token(
462-
monkeypatch,
463-
bedrock_server,
464-
exercise_model,
465-
set_trace_info,
466-
expected_invalid_access_key_error_events,
467-
expected_metrics,
468-
):
469-
@validate_custom_events(add_token_count_to_events(expected_invalid_access_key_error_events))
470-
@validate_error_trace_attributes(
471-
_client_error_name,
472-
exact_attrs={
473-
"agent": {},
474-
"intrinsic": {},
475-
"user": {
476-
"http.statusCode": 403,
477-
"error.message": "The security token included in the request is invalid.",
478-
"error.code": "UnrecognizedClientException",
479-
},
480-
},
481-
)
482-
@validate_transaction_metrics(
483-
name="test_bedrock_chat_completion",
484-
scoped_metrics=expected_metrics,
485-
rollup_metrics=expected_metrics,
486-
custom_metrics=[(f"Supportability/Python/ML/Bedrock/{BOTOCORE_VERSION}", 1)],
487-
background_task=True,
488-
)
489-
@background_task(name="test_bedrock_chat_completion")
490-
def _test():
491-
monkeypatch.setattr(bedrock_server._request_signer._credentials, "access_key", "INVALID-ACCESS-KEY")
492-
493-
with pytest.raises(_client_error): # not sure where this exception actually comes from
494-
set_trace_info()
495-
add_custom_attribute("llm.conversation_id", "my-awesome-id")
496-
add_custom_attribute("llm.foo", "bar")
497-
add_custom_attribute("non_llm_attr", "python-agent")
498-
499-
exercise_model(prompt="Invalid Token", temperature=0.7, max_tokens=100)
500-
501-
_test()
502-
503-
504460
def invoke_model_malformed_request_body(loop, bedrock_server, response_streaming):
505461
async def _coro():
506462
with pytest.raises(_client_error):
@@ -799,58 +755,6 @@ async def _test():
799755
loop.run_until_complete(_test())
800756

801757

802-
@reset_core_stats_engine()
803-
@override_llm_token_callback_settings(llm_token_count_callback)
804-
@validate_custom_events(add_token_count_to_events(chat_completion_expected_streaming_error_events))
805-
@validate_custom_event_count(count=2)
806-
@validate_error_trace_attributes(
807-
_event_stream_error_name,
808-
exact_attrs={
809-
"agent": {},
810-
"intrinsic": {},
811-
"user": {
812-
"error.message": "Malformed input request, please reformat your input and try again.",
813-
"error.code": "ValidationException",
814-
},
815-
},
816-
forgone_params={"agent": (), "intrinsic": (), "user": ("http.statusCode")},
817-
)
818-
@validate_transaction_metrics(
819-
name="test_bedrock_chat_completion",
820-
scoped_metrics=[("Llm/completion/Bedrock/invoke_model_with_response_stream", 1)],
821-
rollup_metrics=[("Llm/completion/Bedrock/invoke_model_with_response_stream", 1)],
822-
custom_metrics=[(f"Supportability/Python/ML/Bedrock/{BOTOCORE_VERSION}", 1)],
823-
background_task=True,
824-
)
825-
@background_task(name="test_bedrock_chat_completion")
826-
def test_bedrock_chat_completion_error_streaming_exception_with_token_count(loop, bedrock_server, set_trace_info):
827-
"""
828-
Duplicate of test_bedrock_chat_completion_error_streaming_exception, but with token callback being set.
829-
830-
See the original test for a description of the error case.
831-
"""
832-
833-
async def _test():
834-
with pytest.raises(_event_stream_error):
835-
model = "amazon.titan-text-express-v1"
836-
body = (chat_completion_payload_templates[model] % ("Streaming Exception", 0.7, 100)).encode("utf-8")
837-
838-
set_trace_info()
839-
add_custom_attribute("llm.conversation_id", "my-awesome-id")
840-
add_custom_attribute("llm.foo", "bar")
841-
add_custom_attribute("non_llm_attr", "python-agent")
842-
843-
response = await bedrock_server.invoke_model_with_response_stream(
844-
body=body, modelId=model, accept="application/json", contentType="application/json"
845-
)
846-
847-
body = response.get("body")
848-
async for resp in body:
849-
assert resp
850-
851-
loop.run_until_complete(_test())
852-
853-
854758
def test_bedrock_chat_completion_functions_marked_as_wrapped_for_sdk_compatibility(bedrock_server):
855759
assert bedrock_server._nr_wrapped
856760

tests/external_aiobotocore/test_bedrock_embeddings.py

Lines changed: 2 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
)
2929
from testing_support.fixtures import override_llm_token_callback_settings, reset_core_stats_engine, validate_attributes
3030
from testing_support.ml_testing_utils import (
31-
add_token_count_to_events,
31+
add_token_count_to_embedding_events,
3232
disabled_ai_monitoring_record_content_settings,
3333
disabled_ai_monitoring_settings,
3434
events_sans_content,
@@ -165,7 +165,7 @@ def _test():
165165
@reset_core_stats_engine()
166166
@override_llm_token_callback_settings(llm_token_count_callback)
167167
def test_bedrock_embedding_with_token_count(set_trace_info, exercise_model, expected_events):
168-
@validate_custom_events(add_token_count_to_events(expected_events))
168+
@validate_custom_events(add_token_count_to_embedding_events(expected_events))
169169
@validate_custom_event_count(count=1)
170170
@validate_transaction_metrics(
171171
name="test_bedrock_embedding",
@@ -290,45 +290,6 @@ def _test():
290290
_test()
291291

292292

293-
@reset_core_stats_engine()
294-
@override_llm_token_callback_settings(llm_token_count_callback)
295-
def test_bedrock_embedding_error_incorrect_access_key_with_token_count(
296-
monkeypatch, bedrock_server, exercise_model, set_trace_info, expected_invalid_access_key_error_events
297-
):
298-
@validate_custom_events(add_token_count_to_events(expected_invalid_access_key_error_events))
299-
@validate_error_trace_attributes(
300-
_client_error_name,
301-
exact_attrs={
302-
"agent": {},
303-
"intrinsic": {},
304-
"user": {
305-
"http.statusCode": 403,
306-
"error.message": "The security token included in the request is invalid.",
307-
"error.code": "UnrecognizedClientException",
308-
},
309-
},
310-
)
311-
@validate_transaction_metrics(
312-
name="test_bedrock_embedding",
313-
scoped_metrics=[("Llm/embedding/Bedrock/invoke_model", 1)],
314-
rollup_metrics=[("Llm/embedding/Bedrock/invoke_model", 1)],
315-
background_task=True,
316-
)
317-
@background_task(name="test_bedrock_embedding")
318-
def _test():
319-
monkeypatch.setattr(bedrock_server._request_signer._credentials, "access_key", "INVALID-ACCESS-KEY")
320-
321-
with pytest.raises(_client_error): # not sure where this exception actually comes from
322-
set_trace_info()
323-
add_custom_attribute("llm.conversation_id", "my-awesome-id")
324-
add_custom_attribute("llm.foo", "bar")
325-
add_custom_attribute("non_llm_attr", "python-agent")
326-
327-
exercise_model(prompt="Invalid Token")
328-
329-
_test()
330-
331-
332293
@reset_core_stats_engine()
333294
@validate_custom_events(embedding_expected_malformed_request_body_events)
334295
@validate_custom_event_count(count=1)

tests/external_botocore/_test_bedrock_chat_completion_converse.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@
2929
"duration": None, # Response time varies each test run
3030
"request.model": "anthropic.claude-3-sonnet-20240229-v1:0",
3131
"response.model": "anthropic.claude-3-sonnet-20240229-v1:0",
32+
"response.usage.prompt_tokens": 26,
33+
"response.usage.completion_tokens": 100,
34+
"response.usage.total_tokens": 126,
3235
"request.temperature": 0.7,
3336
"request.max_tokens": 100,
3437
"response.choices.finish_reason": "max_tokens",
@@ -51,6 +54,7 @@
5154
"role": "system",
5255
"completion_id": None,
5356
"sequence": 0,
57+
"token_count": 0,
5458
"response.model": "anthropic.claude-3-sonnet-20240229-v1:0",
5559
"vendor": "bedrock",
5660
"ingest_source": "Python",
@@ -70,6 +74,7 @@
7074
"role": "user",
7175
"completion_id": None,
7276
"sequence": 1,
77+
"token_count": 0,
7378
"response.model": "anthropic.claude-3-sonnet-20240229-v1:0",
7479
"vendor": "bedrock",
7580
"ingest_source": "Python",
@@ -89,6 +94,7 @@
8994
"role": "assistant",
9095
"completion_id": None,
9196
"sequence": 2,
97+
"token_count": 0,
9298
"response.model": "anthropic.claude-3-sonnet-20240229-v1:0",
9399
"vendor": "bedrock",
94100
"ingest_source": "Python",

0 commit comments

Comments
 (0)