Skip to content

Commit 1e0a131

Browse files
Gemini Response Streaming (#1699)
* Clean up version metric * Convert tests to parametrized form * Extract error message logic to fixture * Update models used in gemini tests * Update responses in gemini * Implement gemini streaming support * Use gemini ReplayApiClient class for recordings * Parametrize metrics * Add more replay recordings * Implement async streaming as well * Implement time to first token * Remove todos * Force all keyword arguments for safety * Clean up conftest * Fix missing validators * Fix extra %s in log warning string --------- Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
1 parent b571894 commit 1e0a131

56 files changed

Lines changed: 39544 additions & 1926 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

newrelic/common/llm_utils.py

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,17 @@ def _get_llm_metadata(transaction):
3838
return llm_metadata_dict
3939

4040

41+
def noop(self, *args, **kwargs):
42+
"""No-op function to use as a default for on_stream_chunk when it's not provided."""
43+
pass
44+
45+
4146
class LLMStreamProxy(ObjectProxy):
42-
def __init__(self, wrapped, on_stop_iteration, on_error):
47+
def __init__(self, wrapped, on_stop_iteration, on_error, on_stream_chunk=None):
4348
super().__init__(wrapped)
4449
self._nr_on_stop_iteration = on_stop_iteration
4550
self._nr_on_error = on_error
51+
self._nr_on_stream_chunk = on_stream_chunk or noop
4652
# Track if we've sent the LLM events yet to avoid sending them multiple times
4753
self._nr_closed = False
4854

@@ -53,6 +59,7 @@ def __iter__(self):
5359
def __next__(self):
5460
try:
5561
return_val = self._nr_wrapped_iter.__next__()
62+
self._nr_on_stream_chunk(self, return_val)
5663
except StopIteration:
5764
transaction = current_transaction()
5865
if transaction:
@@ -97,14 +104,20 @@ def throw(self, *args):
97104
def __copy__(self):
98105
# Required to properly interface with itertool.tee, which can be called by LangChain on generators
99106
self.__wrapped__, copy = itertools.tee(self.__wrapped__, 2)
100-
return LLMStreamProxy(copy, self._nr_on_stop_iteration, self._nr_on_error)
107+
return LLMStreamProxy(
108+
copy,
109+
on_stop_iteration=self._nr_on_stop_iteration,
110+
on_error=self._nr_on_error,
111+
on_stream_chunk=self._nr_on_stream_chunk,
112+
)
101113

102114

103115
class AsyncLLMStreamProxy(ObjectProxy):
104-
def __init__(self, wrapped, on_stop_iteration, on_error):
116+
def __init__(self, wrapped, on_stop_iteration, on_error, on_stream_chunk=None):
105117
super().__init__(wrapped)
106118
self._nr_on_stop_iteration = on_stop_iteration
107119
self._nr_on_error = on_error
120+
self._nr_on_stream_chunk = on_stream_chunk or noop
108121
# Track if we've sent the LLM events yet to avoid sending them multiple times
109122
self._nr_closed = False
110123

@@ -115,6 +128,7 @@ def __aiter__(self):
115128
async def __anext__(self):
116129
try:
117130
return_val = await self._nr_wrapped_iter.__anext__()
131+
self._nr_on_stream_chunk(self, return_val)
118132
except StopAsyncIteration:
119133
transaction = current_transaction()
120134
if transaction:
@@ -159,4 +173,9 @@ async def athrow(self, *args):
159173
def __copy__(self):
160174
# Required to properly interface with itertool.tee, which can be called by LangChain on generators
161175
self.__wrapped__, copy = itertools.tee(self.__wrapped__, n=2)
162-
return AsyncLLMStreamProxy(copy, self._nr_on_stop_iteration, self._nr_on_error)
176+
return AsyncLLMStreamProxy(
177+
copy,
178+
on_stop_iteration=self._nr_on_stop_iteration,
179+
on_error=self._nr_on_error,
180+
on_stream_chunk=self._nr_on_stream_chunk,
181+
)

0 commit comments

Comments
 (0)