Skip to content

Commit 8d62a84

Browse files
umaannamalaimergify[bot]TimPansino
authored
Add time to first token for Bedrock and OpenAI. (#1689)
* Add time to first token for Bedrock and OpenAI. * Prevent resetting of time_to_first_token in bedrock --------- Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> Co-authored-by: Tim Pansino <timpansino@gmail.com> Co-authored-by: Timothy Pansino <11214426+TimPansino@users.noreply.github.com>
1 parent 9836b8f commit 8d62a84

6 files changed

Lines changed: 39 additions & 0 deletions

File tree

newrelic/hooks/external_botocore.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -963,6 +963,8 @@ def record_events_on_stop_iteration(self, transaction, request_timestamp=None):
963963

964964
try:
965965
bedrock_attrs["duration"] = self._nr_ft.duration * 1000
966+
if hasattr(self, "_nr_time_to_first_token"):
967+
bedrock_attrs["time_to_first_token"] = self._nr_time_to_first_token
966968
handle_chat_completion_event(transaction, bedrock_attrs, request_timestamp)
967969
except Exception:
968970
_logger.warning(RESPONSE_PROCESSING_FAILURE_LOG_MESSAGE, exc_info=True)
@@ -1012,8 +1014,23 @@ def record_stream_chunk(self, event, transaction, request_timestamp=None):
10121014

10131015
def invoke_record_stream_chunk(self, event, transaction, request_timestamp=None):
10141016
bedrock_attrs = getattr(self, "_nr_bedrock_attrs", {})
1017+
1018+
# Store time to first token now, but only attach it if we successfully parse the chunk.
1019+
# Record the time here since parsing may be a bit slow, and can inflate the metric.
1020+
time_to_first_token = (
1021+
int(1000.0 * time.time()) - self._nr_request_timestamp
1022+
if not hasattr(self, "_nr_time_to_first_token")
1023+
else None
1024+
)
1025+
1026+
# Load and parse the chunk to extract model specific data
10151027
chunk = json.loads(event["chunk"]["bytes"].decode("utf-8"))
10161028
self._nr_model_extractor(chunk, bedrock_attrs)
1029+
1030+
# Attach time to first token after parsing to ensure there are no errors
1031+
if time_to_first_token is not None:
1032+
self._nr_time_to_first_token = time_to_first_token
1033+
10171034
# In Langchain, the bedrock iterator exits early if type is "content_block_stop".
10181035
# So we need to call the record events here since stop iteration will not be raised.
10191036
_type = chunk.get("type")
@@ -1027,10 +1044,14 @@ def converse_record_stream_chunk(self, event, transaction):
10271044
return
10281045

10291046
content = ((event.get("contentBlockDelta") or {}).get("delta") or {}).get("text", "")
1047+
10301048
if "output_message_list" not in bedrock_attrs:
10311049
bedrock_attrs["output_message_list"] = [{"role": "assistant", "content": ""}]
10321050
bedrock_attrs["output_message_list"][0]["content"] += content
10331051

1052+
if content and not hasattr(self, "_nr_time_to_first_token"):
1053+
self._nr_time_to_first_token = int(1000.0 * time.time()) - self._nr_request_timestamp
1054+
10341055
if "messageStop" in event:
10351056
bedrock_attrs["response.choices.finish_reason"] = (event.get("messageStop") or {}).get("stopReason", "")
10361057

@@ -1198,6 +1219,7 @@ def handle_chat_completion_event(transaction, bedrock_attrs, request_timestamp=N
11981219
"response.choices.finish_reason": bedrock_attrs.get("response.choices.finish_reason", None),
11991220
"error": bedrock_attrs.get("error", None),
12001221
"timestamp": request_timestamp or None,
1222+
"time_to_first_token": bedrock_attrs.get("time_to_first_token", None),
12011223
}
12021224
chat_completion_summary_dict.update(llm_metadata_dict)
12031225
chat_completion_summary_dict = {k: v for k, v in chat_completion_summary_dict.items() if v is not None}

newrelic/hooks/mlmodel_openai.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -571,6 +571,10 @@ def _record_completion_success(
571571
"timestamp": request_timestamp,
572572
}
573573
llm_metadata = _get_llm_attributes(transaction)
574+
575+
if "time_to_first_token" in kwargs:
576+
full_chat_completion_summary_dict["time_to_first_token"] = kwargs.get("time_to_first_token")
577+
574578
full_chat_completion_summary_dict.update(llm_metadata)
575579
transaction.record_custom_event("LlmChatCompletionSummary", full_chat_completion_summary_dict)
576580

@@ -782,6 +786,10 @@ def _record_stream_chunk(self, return_val):
782786
if choices:
783787
delta = choices[0].get("delta") or {}
784788
if delta:
789+
if delta.get("content") and "time_to_first_token" not in self._nr_openai_attrs:
790+
self._nr_openai_attrs["time_to_first_token"] = (
791+
int(1000.0 * time.time()) - self._nr_request_timestamp
792+
)
785793
self._nr_openai_attrs["content"] = self._nr_openai_attrs.get("content", "") + (
786794
delta.get("content") or ""
787795
)

tests/external_botocore/_test_bedrock_chat_completion_converse.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@
116116
"vendor": "bedrock",
117117
"ingest_source": "Python",
118118
"response.number_of_messages": 3,
119+
"time_to_first_token": None, # Varies each test run
119120
},
120121
),
121122
(

tests/external_botocore/_test_bedrock_chat_completion_invoke_model.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1073,6 +1073,7 @@
10731073
"vendor": "bedrock",
10741074
"ingest_source": "Python",
10751075
"response.number_of_messages": 2,
1076+
"time_to_first_token": None, # Varies each test run
10761077
},
10771078
),
10781079
(
@@ -1134,6 +1135,7 @@
11341135
"vendor": "bedrock",
11351136
"ingest_source": "Python",
11361137
"response.number_of_messages": 2,
1138+
"time_to_first_token": None, # Varies each test run
11371139
},
11381140
),
11391141
(
@@ -1195,6 +1197,7 @@
11951197
"vendor": "bedrock",
11961198
"ingest_source": "Python",
11971199
"response.number_of_messages": 2,
1200+
"time_to_first_token": None, # Varies each test run
11981201
},
11991202
),
12001203
(
@@ -1255,6 +1258,7 @@
12551258
"vendor": "bedrock",
12561259
"ingest_source": "Python",
12571260
"response.number_of_messages": 2,
1261+
"time_to_first_token": None, # Varies each test run
12581262
},
12591263
),
12601264
(
@@ -1317,6 +1321,7 @@
13171321
"vendor": "bedrock",
13181322
"ingest_source": "Python",
13191323
"response.number_of_messages": 2,
1324+
"time_to_first_token": None, # Varies each test run
13201325
},
13211326
),
13221327
(
@@ -1378,6 +1383,7 @@
13781383
"vendor": "bedrock",
13791384
"ingest_source": "Python",
13801385
"response.number_of_messages": 2,
1386+
"time_to_first_token": None, # Varies each test run
13811387
},
13821388
),
13831389
(

tests/mlmodel_openai/test_chat_completion_stream.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@
6868
"vendor": "openai",
6969
"ingest_source": "Python",
7070
"response.number_of_messages": 3,
71+
"time_to_first_token": None, # Varies each test run
7172
},
7273
),
7374
(

tests/mlmodel_openai/test_chat_completion_stream_v1.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@
7878
"vendor": "openai",
7979
"ingest_source": "Python",
8080
"response.number_of_messages": 3,
81+
"time_to_first_token": None, # Varies each test run
8182
},
8283
),
8384
(

0 commit comments

Comments
 (0)