@@ -92,62 +92,73 @@ def export(self, items: list[Trace | Span[Any]]) -> None:
9292 if not items :
9393 return
9494
95- if not self .api_key :
96- logger .warning ("OPENAI_API_KEY is not set, skipping trace export" )
97- return
98-
99- data = [item .export () for item in items if item .export ()]
100- payload = {"data" : data }
101-
102- headers = {
103- "Authorization" : f"Bearer { self .api_key } " ,
104- "Content-Type" : "application/json" ,
105- "OpenAI-Beta" : "traces=v1" ,
106- }
107-
108- if self .organization :
109- headers ["OpenAI-Organization" ] = self .organization
110-
111- if self .project :
112- headers ["OpenAI-Project" ] = self .project
113-
114- # Exponential backoff loop
115- attempt = 0
116- delay = self .base_delay
117- while True :
118- attempt += 1
119- try :
120- response = self ._client .post (url = self .endpoint , headers = headers , json = payload )
121-
122- # If the response is successful, break out of the loop
123- if response .status_code < 300 :
124- logger .debug (f"Exported { len (items )} items" )
125- return
95+ grouped_items : dict [str | None , list [Trace | Span [Any ]]] = {}
96+ for item in items :
97+ key = item .tracing_api_key
98+ grouped_items .setdefault (key , []).append (item )
99+
100+ for item_key , grouped in grouped_items .items ():
101+ api_key = item_key or self .api_key
102+ if not api_key :
103+ logger .warning ("OPENAI_API_KEY is not set, skipping trace export" )
104+ continue
105+
106+ data = [item .export () for item in grouped if item .export ()]
107+ payload = {"data" : data }
108+
109+ headers = {
110+ "Authorization" : f"Bearer { api_key } " ,
111+ "Content-Type" : "application/json" ,
112+ "OpenAI-Beta" : "traces=v1" ,
113+ }
114+
115+ if self .organization :
116+ headers ["OpenAI-Organization" ] = self .organization
117+
118+ if self .project :
119+ headers ["OpenAI-Project" ] = self .project
120+
121+ # Exponential backoff loop
122+ attempt = 0
123+ delay = self .base_delay
124+ while True :
125+ attempt += 1
126+ try :
127+ response = self ._client .post (url = self .endpoint , headers = headers , json = payload )
128+
129+ # If the response is successful, break out of the loop
130+ if response .status_code < 300 :
131+ logger .debug (f"Exported { len (grouped )} items" )
132+ break
133+
134+ # If the response is a client error (4xx), we won't retry
135+ if 400 <= response .status_code < 500 :
136+ logger .error (
137+ "[non-fatal] Tracing client error %s: %s" ,
138+ response .status_code ,
139+ response .text ,
140+ )
141+ break
142+
143+ # For 5xx or other unexpected codes, treat it as transient and retry
144+ logger .warning (
145+ f"[non-fatal] Tracing: server error { response .status_code } , retrying."
146+ )
147+ except httpx .RequestError as exc :
148+ # Network or other I/O error, we'll retry
149+ logger .warning (f"[non-fatal] Tracing: request failed: { exc } " )
126150
127- # If the response is a client error (4xx), we won't retry
128- if 400 <= response . status_code < 500 :
151+ # If we reach here, we need to retry or give up
152+ if attempt >= self . max_retries :
129153 logger .error (
130- f "[non-fatal] Tracing client error { response . status_code } : { response . text } "
154+ "[non-fatal] Tracing: max retries reached, giving up on this batch. "
131155 )
132- return
133-
134- # For 5xx or other unexpected codes, treat it as transient and retry
135- logger .warning (
136- f"[non-fatal] Tracing: server error { response .status_code } , retrying."
137- )
138- except httpx .RequestError as exc :
139- # Network or other I/O error, we'll retry
140- logger .warning (f"[non-fatal] Tracing: request failed: { exc } " )
141-
142- # If we reach here, we need to retry or give up
143- if attempt >= self .max_retries :
144- logger .error ("[non-fatal] Tracing: max retries reached, giving up on this batch." )
145- return
156+ break
146157
147- # Exponential backoff + jitter
148- sleep_time = delay + random .uniform (0 , 0.1 * delay ) # 10% jitter
149- time .sleep (sleep_time )
150- delay = min (delay * 2 , self .max_delay )
158+ # Exponential backoff + jitter
159+ sleep_time = delay + random .uniform (0 , 0.1 * delay ) # 10% jitter
160+ time .sleep (sleep_time )
161+ delay = min (delay * 2 , self .max_delay )
151162
152163 def close (self ):
153164 """Close the underlying HTTP client."""
0 commit comments