@@ -38,71 +38,125 @@ def _get_llm_metadata(transaction):
3838 return llm_metadata_dict
3939
4040
41- class GeneratorProxy (ObjectProxy ):
41+ class LLMStreamProxy (ObjectProxy ):
4242 def __init__ (self , wrapped , on_stop_iteration , on_error ):
4343 super ().__init__ (wrapped )
4444 self ._nr_on_stop_iteration = on_stop_iteration
4545 self ._nr_on_error = on_error
46+ # Track if we've sent the LLM events yet to avoid sending them multiple times
47+ self ._nr_closed = False
4648
4749 def __iter__ (self ):
4850 self ._nr_wrapped_iter = self .__wrapped__ .__iter__ ()
4951 return self
5052
5153 def __next__ (self ):
52- transaction = current_transaction ()
53- if not transaction :
54- return self ._nr_wrapped_iter .__next__ ()
55-
56- return_val = None
5754 try :
5855 return_val = self ._nr_wrapped_iter .__next__ ()
5956 except StopIteration :
60- self ._nr_on_stop_iteration (self , transaction )
57+ transaction = current_transaction ()
58+ if transaction :
59+ self ._nr_closed = True
60+ self ._nr_on_stop_iteration (self , transaction )
6161 raise
6262 except Exception :
63- self ._nr_on_error (self , transaction )
63+ transaction = current_transaction ()
64+ if transaction :
65+ self ._nr_closed = True
66+ self ._nr_on_error (self , transaction )
6467 raise
65- return return_val
68+ else :
69+ return return_val
6670
6771 def close (self ):
72+ if self ._nr_closed :
73+ # If we already sent the related events, we can just call close as there's nothing left to do.
74+ return self .__wrapped__ .close ()
75+
76+ transaction = current_transaction ()
77+ if transaction :
78+ # Send the events as if we were hitting StopIteration.
79+ self ._nr_closed = True
80+ self ._nr_on_stop_iteration (self , transaction )
81+
6882 return self .__wrapped__ .close ()
6983
84+ def throw (self , * args ):
85+ if self ._nr_closed :
86+ # If we already sent the related events, we can just call throw as there's nothing left to do.
87+ return self .__wrapped__ .throw (* args )
88+
89+ transaction = current_transaction ()
90+ if transaction :
91+ # Send the events as if we were hitting an exception.
92+ self ._nr_closed = True
93+ self ._nr_on_error (self , transaction )
94+
95+ return self .__wrapped__ .throw (* args )
96+
7097 def __copy__ (self ):
7198 # Required to properly interface with itertool.tee, which can be called by LangChain on generators
7299 self .__wrapped__ , copy = itertools .tee (self .__wrapped__ , 2 )
73- return GeneratorProxy (copy , self ._nr_on_stop_iteration , self ._nr_on_error )
100+ return LLMStreamProxy (copy , self ._nr_on_stop_iteration , self ._nr_on_error )
74101
75102
76- class AsyncGeneratorProxy (ObjectProxy ):
103+ class AsyncLLMStreamProxy (ObjectProxy ):
77104 def __init__ (self , wrapped , on_stop_iteration , on_error ):
78105 super ().__init__ (wrapped )
79106 self ._nr_on_stop_iteration = on_stop_iteration
80107 self ._nr_on_error = on_error
108+ # Track if we've sent the LLM events yet to avoid sending them multiple times
109+ self ._nr_closed = False
81110
82111 def __aiter__ (self ):
83112 self ._nr_wrapped_iter = self .__wrapped__ .__aiter__ ()
84113 return self
85114
86115 async def __anext__ (self ):
87- transaction = current_transaction ()
88- if not transaction :
89- return await self ._nr_wrapped_iter .__anext__ ()
90-
91- return_val = None
92116 try :
93117 return_val = await self ._nr_wrapped_iter .__anext__ ()
94118 except StopAsyncIteration :
95- self ._nr_on_stop_iteration (self , transaction )
119+ transaction = current_transaction ()
120+ if transaction :
121+ self ._nr_closed = True
122+ self ._nr_on_stop_iteration (self , transaction )
96123 raise
97124 except Exception :
98- self ._nr_on_error (self , transaction )
125+ transaction = current_transaction ()
126+ if transaction :
127+ self ._nr_closed = True
128+ self ._nr_on_error (self , transaction )
99129 raise
100- return return_val
130+ else :
131+ return return_val
101132
102133 async def aclose (self ):
134+ if self ._nr_closed :
135+ # If we already sent the related events, we can just call aclose as there's nothing left to do.
136+ return await self .__wrapped__ .aclose ()
137+
138+ transaction = current_transaction ()
139+ if transaction :
140+ # Send the events as if we were hitting StopAsyncIteration.
141+ self ._nr_closed = True
142+ self ._nr_on_stop_iteration (self , transaction )
143+
103144 return await self .__wrapped__ .aclose ()
104145
146+ async def athrow (self , * args ):
147+ if self ._nr_closed :
148+ # If we already sent the related events, we can just call athrow as there's nothing left to do.
149+ return await self .__wrapped__ .athrow (* args )
150+
151+ transaction = current_transaction ()
152+ if transaction :
153+ # Send the events as if we were hitting an exception.
154+ self ._nr_closed = True
155+ self ._nr_on_error (self , transaction )
156+
157+ return await self .__wrapped__ .athrow (* args )
158+
105159 def __copy__ (self ):
106160 # Required to properly interface with itertool.tee, which can be called by LangChain on generators
107161 self .__wrapped__ , copy = itertools .tee (self .__wrapped__ , n = 2 )
108- return AsyncGeneratorProxy (copy , self ._nr_on_stop_iteration , self ._nr_on_error )
162+ return AsyncLLMStreamProxy (copy , self ._nr_on_stop_iteration , self ._nr_on_error )
0 commit comments