promptlayer 1.0.72__py3-none-any.whl → 1.0.73__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of promptlayer might be problematic. Click here for more details.

promptlayer/utils.py CHANGED
@@ -5,7 +5,6 @@ import functools
5
5
  import json
6
6
  import logging
7
7
  import os
8
- import sys
9
8
  import types
10
9
  from contextlib import asynccontextmanager
11
10
  from copy import deepcopy
@@ -26,7 +25,15 @@ from centrifuge import (
26
25
  SubscriptionState,
27
26
  )
28
27
  from opentelemetry import context, trace
28
+ from tenacity import (
29
+ before_sleep_log,
30
+ retry,
31
+ retry_if_exception,
32
+ stop_after_attempt,
33
+ wait_exponential,
34
+ )
29
35
 
36
+ from promptlayer import exceptions as _exceptions
30
37
  from promptlayer.types import RequestLog
31
38
  from promptlayer.types.prompt_template import (
32
39
  GetPromptTemplate,
@@ -37,7 +44,6 @@ from promptlayer.types.prompt_template import (
37
44
  )
38
45
 
39
46
  # Configuration
40
-
41
47
  RERAISE_ORIGINAL_EXCEPTION = os.getenv("PROMPTLAYER_RE_RAISE_ORIGINAL_EXCEPTION", "False").lower() == "true"
42
48
  RAISE_FOR_STATUS = os.getenv("PROMPTLAYER_RAISE_FOR_STATUS", "False").lower() == "true"
43
49
  DEFAULT_HTTP_TIMEOUT = 5
@@ -58,6 +64,34 @@ class FinalOutputCode(Enum):
58
64
  EXCEEDS_SIZE_LIMIT = "EXCEEDS_SIZE_LIMIT"
59
65
 
60
66
 
67
+ def should_retry_error(exception):
68
+ """Check if an exception should trigger a retry.
69
+
70
+ Only retries on server errors (5xx) and rate limits (429).
71
+ """
72
+ if hasattr(exception, "response"):
73
+ response = exception.response
74
+ if hasattr(response, "status_code"):
75
+ status_code = response.status_code
76
+ if status_code >= 500 or status_code == 429:
77
+ return True
78
+
79
+ if isinstance(exception, (_exceptions.PromptLayerInternalServerError, _exceptions.PromptLayerRateLimitError)):
80
+ return True
81
+
82
+ return False
83
+
84
+
85
+ def retry_on_api_error(func):
86
+ return retry(
87
+ retry=retry_if_exception(should_retry_error),
88
+ stop=stop_after_attempt(4), # 4 total attempts (1 initial + 3 retries)
89
+ wait=wait_exponential(multiplier=2, max=15), # 2s, 4s, 8s
90
+ before_sleep=before_sleep_log(logger, logging.WARNING),
91
+ reraise=True,
92
+ )(func)
93
+
94
+
61
95
  def _get_http_timeout():
62
96
  try:
63
97
  return float(os.getenv("PROMPTLAYER_HTTP_TIMEOUT", DEFAULT_HTTP_TIMEOUT))
@@ -90,7 +124,8 @@ async def _get_final_output(
90
124
  headers=headers,
91
125
  params={"workflow_version_execution_id": execution_id, "return_all_outputs": return_all_outputs},
92
126
  )
93
- response.raise_for_status()
127
+ if response.status_code != 200:
128
+ raise_on_bad_response(response, "PromptLayer had the following error while getting workflow results")
94
129
  return response.json()
95
130
 
96
131
 
@@ -104,10 +139,8 @@ async def _resolve_workflow_id(base_url: str, workflow_id_or_name: Union[int, st
104
139
  async with _make_httpx_client() as client:
105
140
  # TODO(dmu) MEDIUM: Generalize the way we make async calls to PromptLayer API and reuse it everywhere
106
141
  response = await client.get(f"{base_url}/workflows/{workflow_id_or_name}", headers=headers)
107
- if RAISE_FOR_STATUS:
108
- response.raise_for_status()
109
- elif response.status_code != 200:
110
- raise_on_bad_response(response, "PromptLayer had the following error while running your workflow")
142
+ if response.status_code != 200:
143
+ raise_on_bad_response(response, "PromptLayer had the following error while resolving workflow")
111
144
 
112
145
  return response.json()["workflow"]["id"]
113
146
 
@@ -120,9 +153,7 @@ async def _get_ably_token(base_url: str, channel_name, authentication_headers):
120
153
  headers=authentication_headers,
121
154
  params={"capability": channel_name},
122
155
  )
123
- if RAISE_FOR_STATUS:
124
- response.raise_for_status()
125
- elif response.status_code != 201:
156
+ if response.status_code != 201:
126
157
  raise_on_bad_response(
127
158
  response,
128
159
  "PromptLayer had the following error while getting WebSocket token",
@@ -130,12 +161,11 @@ async def _get_ably_token(base_url: str, channel_name, authentication_headers):
130
161
  return response.json()
131
162
  except Exception as ex:
132
163
  error_message = f"Failed to get WebSocket token: {ex}"
133
- print(error_message) # TODO(dmu) MEDIUM: Remove prints in favor of logging
134
164
  logger.exception(error_message)
135
165
  if RERAISE_ORIGINAL_EXCEPTION:
136
166
  raise
137
167
  else:
138
- raise Exception(error_message)
168
+ raise _exceptions.PromptLayerAPIError(error_message, response=None, body=None) from ex
139
169
 
140
170
 
141
171
  def _make_message_listener(base_url: str, results_future, execution_id_future, return_all_outputs, headers):
@@ -197,22 +227,19 @@ async def _post_workflow_id_run(
197
227
  try:
198
228
  async with _make_httpx_client() as client:
199
229
  response = await client.post(url, json=payload, headers=authentication_headers)
200
- if RAISE_FOR_STATUS:
201
- response.raise_for_status()
202
- elif response.status_code != 201:
230
+ if response.status_code != 201:
203
231
  raise_on_bad_response(response, "PromptLayer had the following error while running your workflow")
204
232
 
205
233
  result = response.json()
206
234
  if warning := result.get("warning"):
207
- print(f"WARNING: {warning}")
235
+ logger.warning(f"{warning}")
208
236
  except Exception as ex:
209
237
  error_message = f"Failed to run workflow: {str(ex)}"
210
- print(error_message) # TODO(dmu) MEDIUM: Remove prints in favor of logging
211
238
  logger.exception(error_message)
212
239
  if RERAISE_ORIGINAL_EXCEPTION:
213
240
  raise
214
241
  else:
215
- raise Exception(error_message)
242
+ raise _exceptions.PromptLayerAPIError(error_message, response=None, body=None) from ex
216
243
 
217
244
  return result.get("workflow_version_execution_id")
218
245
 
@@ -222,7 +249,9 @@ async def _wait_for_workflow_completion(channel, results_future, message_listene
222
249
  try:
223
250
  return await asyncio.wait_for(results_future, timeout)
224
251
  except asyncio.TimeoutError:
225
- raise Exception("Workflow execution did not complete properly")
252
+ raise _exceptions.PromptLayerAPITimeoutError(
253
+ "Workflow execution did not complete properly", response=None, body=None
254
+ )
226
255
  finally:
227
256
  channel.unsubscribe(SET_WORKFLOW_COMPLETE_MESSAGE, message_listener)
228
257
 
@@ -270,10 +299,12 @@ async def centrifugo_subscription(client: Client, topic: str, message_listener:
270
299
  await subscription.unsubscribe()
271
300
 
272
301
 
302
+ @retry_on_api_error
273
303
  async def arun_workflow_request(
274
304
  *,
275
305
  api_key: str,
276
306
  base_url: str,
307
+ throw_on_error: bool,
277
308
  workflow_id_or_name: Optional[Union[int, str]] = None,
278
309
  input_variables: Dict[str, Any],
279
310
  metadata: Optional[Dict[str, Any]] = None,
@@ -490,43 +521,57 @@ def promptlayer_api_request(
490
521
  request_response, "WARNING: While logging your request PromptLayer had the following error"
491
522
  )
492
523
  except Exception as e:
493
- print(f"WARNING: While logging your request PromptLayer had the following error: {e}", file=sys.stderr)
524
+ logger.warning(f"While logging your request PromptLayer had the following error: {e}")
494
525
  if request_response is not None and return_pl_id:
495
526
  return request_response.json().get("request_id")
496
527
 
497
528
 
498
- def track_request(base_url: str, **body):
529
+ @retry_on_api_error
530
+ def track_request(base_url: str, throw_on_error: bool, **body):
499
531
  try:
500
532
  response = requests.post(
501
533
  f"{base_url}/track-request",
502
534
  json=body,
503
535
  )
504
536
  if response.status_code != 200:
505
- warn_on_bad_response(
506
- response, f"PromptLayer had the following error while tracking your request: {response.text}"
507
- )
537
+ if throw_on_error:
538
+ raise_on_bad_response(response, "PromptLayer had the following error while tracking your request")
539
+ else:
540
+ warn_on_bad_response(
541
+ response, f"PromptLayer had the following error while tracking your request: {response.text}"
542
+ )
508
543
  return response.json()
509
544
  except requests.exceptions.RequestException as e:
510
- print(f"WARNING: While logging your request PromptLayer had the following error: {e}", file=sys.stderr)
545
+ if throw_on_error:
546
+ raise _exceptions.PromptLayerAPIConnectionError(
547
+ f"PromptLayer had the following error while tracking your request: {e}", response=None, body=None
548
+ ) from e
549
+ logger.warning(f"While logging your request PromptLayer had the following error: {e}")
511
550
  return {}
512
551
 
513
552
 
514
- async def atrack_request(base_url: str, **body: Any) -> Dict[str, Any]:
553
+ @retry_on_api_error
554
+ async def atrack_request(base_url: str, throw_on_error: bool, **body: Any) -> Dict[str, Any]:
515
555
  try:
516
556
  async with _make_httpx_client() as client:
517
557
  response = await client.post(
518
558
  f"{base_url}/track-request",
519
559
  json=body,
520
560
  )
521
- if RAISE_FOR_STATUS:
522
- response.raise_for_status()
523
- elif response.status_code != 200:
524
- warn_on_bad_response(
525
- response, f"PromptLayer had the following error while tracking your request: {response.text}"
526
- )
561
+ if response.status_code != 200:
562
+ if throw_on_error:
563
+ raise_on_bad_response(response, "PromptLayer had the following error while tracking your request")
564
+ else:
565
+ warn_on_bad_response(
566
+ response, f"PromptLayer had the following error while tracking your request: {response.text}"
567
+ )
527
568
  return response.json()
528
569
  except httpx.RequestError as e:
529
- print(f"WARNING: While logging your request PromptLayer had the following error: {e}", file=sys.stderr)
570
+ if throw_on_error:
571
+ raise _exceptions.PromptLayerAPIConnectionError(
572
+ f"PromptLayer had the following error while tracking your request: {e}", response=None, body=None
573
+ ) from e
574
+ logger.warning(f"While logging your request PromptLayer had the following error: {e}")
530
575
  return {}
531
576
 
532
577
 
@@ -558,7 +603,10 @@ def promptlayer_api_request_async(
558
603
  )
559
604
 
560
605
 
561
- def promptlayer_get_prompt(api_key: str, base_url: str, prompt_name, version: int = None, label: str = None):
606
+ @retry_on_api_error
607
+ def promptlayer_get_prompt(
608
+ api_key: str, base_url: str, throw_on_error: bool, prompt_name, version: int = None, label: str = None
609
+ ):
562
610
  """
563
611
  Get a prompt from the PromptLayer library
564
612
  version: version of the prompt to get, None for latest
@@ -571,18 +619,31 @@ def promptlayer_get_prompt(api_key: str, base_url: str, prompt_name, version: in
571
619
  params={"prompt_name": prompt_name, "version": version, "label": label},
572
620
  )
573
621
  except Exception as e:
574
- raise Exception(f"PromptLayer had the following error while getting your prompt: {e}")
622
+ if throw_on_error:
623
+ raise _exceptions.PromptLayerAPIError(
624
+ f"PromptLayer had the following error while getting your prompt: {e}", response=None, body=None
625
+ ) from e
626
+ logger.warning(f"PromptLayer had the following error while getting your prompt: {e}")
627
+ return None
575
628
  if request_response.status_code != 200:
576
- raise_on_bad_response(
577
- request_response,
578
- "PromptLayer had the following error while getting your prompt",
579
- )
629
+ if throw_on_error:
630
+ raise_on_bad_response(
631
+ request_response,
632
+ "PromptLayer had the following error while getting your prompt",
633
+ )
634
+ else:
635
+ warn_on_bad_response(
636
+ request_response,
637
+ "WARNING: PromptLayer had the following error while getting your prompt",
638
+ )
639
+ return None
580
640
 
581
641
  return request_response.json()
582
642
 
583
643
 
644
+ @retry_on_api_error
584
645
  def promptlayer_publish_prompt(
585
- api_key: str, base_url: str, prompt_name, prompt_template, commit_message, tags, metadata=None
646
+ api_key: str, base_url: str, throw_on_error: bool, prompt_name, prompt_template, commit_message, tags, metadata=None
586
647
  ):
587
648
  try:
588
649
  request_response = requests.post(
@@ -597,16 +658,31 @@ def promptlayer_publish_prompt(
597
658
  },
598
659
  )
599
660
  except Exception as e:
600
- raise Exception(f"PromptLayer had the following error while publishing your prompt: {e}")
661
+ if throw_on_error:
662
+ raise _exceptions.PromptLayerAPIError(
663
+ f"PromptLayer had the following error while publishing your prompt: {e}", response=None, body=None
664
+ ) from e
665
+ logger.warning(f"PromptLayer had the following error while publishing your prompt: {e}")
666
+ return False
601
667
  if request_response.status_code != 200:
602
- raise_on_bad_response(
603
- request_response,
604
- "PromptLayer had the following error while publishing your prompt",
605
- )
668
+ if throw_on_error:
669
+ raise_on_bad_response(
670
+ request_response,
671
+ "PromptLayer had the following error while publishing your prompt",
672
+ )
673
+ else:
674
+ warn_on_bad_response(
675
+ request_response,
676
+ "WARNING: PromptLayer had the following error while publishing your prompt",
677
+ )
678
+ return False
606
679
  return True
607
680
 
608
681
 
609
- def promptlayer_track_prompt(api_key: str, base_url: str, request_id, prompt_name, input_variables, version, label):
682
+ @retry_on_api_error
683
+ def promptlayer_track_prompt(
684
+ api_key: str, base_url: str, throw_on_error: bool, request_id, prompt_name, input_variables, version, label
685
+ ):
610
686
  try:
611
687
  request_response = requests.post(
612
688
  f"{base_url}/library-track-prompt",
@@ -620,20 +696,28 @@ def promptlayer_track_prompt(api_key: str, base_url: str, request_id, prompt_nam
620
696
  },
621
697
  )
622
698
  if request_response.status_code != 200:
623
- warn_on_bad_response(
624
- request_response,
625
- "WARNING: While tracking your prompt PromptLayer had the following error",
626
- )
627
- return False
699
+ if throw_on_error:
700
+ raise_on_bad_response(
701
+ request_response,
702
+ "While tracking your prompt PromptLayer had the following error",
703
+ )
704
+ else:
705
+ warn_on_bad_response(
706
+ request_response,
707
+ "WARNING: While tracking your prompt PromptLayer had the following error",
708
+ )
709
+ return False
628
710
  except Exception as e:
629
- print(
630
- f"WARNING: While tracking your prompt PromptLayer had the following error: {e}",
631
- file=sys.stderr,
632
- )
711
+ if throw_on_error:
712
+ raise _exceptions.PromptLayerAPIError(
713
+ f"While tracking your prompt PromptLayer had the following error: {e}", response=None, body=None
714
+ ) from e
715
+ logger.warning(f"While tracking your prompt PromptLayer had the following error: {e}")
633
716
  return False
634
717
  return True
635
718
 
636
719
 
720
+ @retry_on_api_error
637
721
  async def apromptlayer_track_prompt(
638
722
  api_key: str,
639
723
  base_url: str,
@@ -642,6 +726,7 @@ async def apromptlayer_track_prompt(
642
726
  input_variables: Dict[str, Any],
643
727
  version: Optional[int] = None,
644
728
  label: Optional[str] = None,
729
+ throw_on_error: bool = True,
645
730
  ) -> bool:
646
731
  url = f"{base_url}/library-track-prompt"
647
732
  payload = {
@@ -656,25 +741,28 @@ async def apromptlayer_track_prompt(
656
741
  async with _make_httpx_client() as client:
657
742
  response = await client.post(url, json=payload)
658
743
 
659
- if RAISE_FOR_STATUS:
660
- response.raise_for_status()
661
- elif response.status_code != 200:
662
- warn_on_bad_response(
663
- response,
664
- "WARNING: While tracking your prompt, PromptLayer had the following error",
665
- )
666
- return False
744
+ if response.status_code != 200:
745
+ if throw_on_error:
746
+ raise_on_bad_response(response, "While tracking your prompt, PromptLayer had the following error")
747
+ else:
748
+ warn_on_bad_response(
749
+ response,
750
+ "WARNING: While tracking your prompt, PromptLayer had the following error",
751
+ )
752
+ return False
667
753
  except httpx.RequestError as e:
668
- print(
669
- f"WARNING: While tracking your prompt PromptLayer had the following error: {e}",
670
- file=sys.stderr,
671
- )
754
+ if throw_on_error:
755
+ raise _exceptions.PromptLayerAPIConnectionError(
756
+ f"While tracking your prompt PromptLayer had the following error: {e}", response=None, body=None
757
+ ) from e
758
+ logger.warning(f"While tracking your prompt PromptLayer had the following error: {e}")
672
759
  return False
673
760
 
674
761
  return True
675
762
 
676
763
 
677
- def promptlayer_track_metadata(api_key: str, base_url: str, request_id, metadata):
764
+ @retry_on_api_error
765
+ def promptlayer_track_metadata(api_key: str, base_url: str, throw_on_error: bool, request_id, metadata):
678
766
  try:
679
767
  request_response = requests.post(
680
768
  f"{base_url}/library-track-metadata",
@@ -685,21 +773,31 @@ def promptlayer_track_metadata(api_key: str, base_url: str, request_id, metadata
685
773
  },
686
774
  )
687
775
  if request_response.status_code != 200:
688
- warn_on_bad_response(
689
- request_response,
690
- "WARNING: While tracking your metadata PromptLayer had the following error",
691
- )
692
- return False
776
+ if throw_on_error:
777
+ raise_on_bad_response(
778
+ request_response,
779
+ "While tracking your metadata PromptLayer had the following error",
780
+ )
781
+ else:
782
+ warn_on_bad_response(
783
+ request_response,
784
+ "WARNING: While tracking your metadata PromptLayer had the following error",
785
+ )
786
+ return False
693
787
  except Exception as e:
694
- print(
695
- f"WARNING: While tracking your metadata PromptLayer had the following error: {e}",
696
- file=sys.stderr,
697
- )
788
+ if throw_on_error:
789
+ raise _exceptions.PromptLayerAPIError(
790
+ f"While tracking your metadata PromptLayer had the following error: {e}", response=None, body=None
791
+ ) from e
792
+ logger.warning(f"While tracking your metadata PromptLayer had the following error: {e}")
698
793
  return False
699
794
  return True
700
795
 
701
796
 
702
- async def apromptlayer_track_metadata(api_key: str, base_url: str, request_id: str, metadata: Dict[str, Any]) -> bool:
797
+ @retry_on_api_error
798
+ async def apromptlayer_track_metadata(
799
+ api_key: str, base_url: str, throw_on_error: bool, request_id: str, metadata: Dict[str, Any]
800
+ ) -> bool:
703
801
  url = f"{base_url}/library-track-metadata"
704
802
  payload = {
705
803
  "request_id": request_id,
@@ -710,25 +808,31 @@ async def apromptlayer_track_metadata(api_key: str, base_url: str, request_id: s
710
808
  async with _make_httpx_client() as client:
711
809
  response = await client.post(url, json=payload)
712
810
 
713
- if RAISE_FOR_STATUS:
714
- response.raise_for_status()
715
- elif response.status_code != 200:
716
- warn_on_bad_response(
717
- response,
718
- "WARNING: While tracking your metadata, PromptLayer had the following error",
719
- )
720
- return False
811
+ if response.status_code != 200:
812
+ if throw_on_error:
813
+ raise_on_bad_response(
814
+ response,
815
+ "While tracking your metadata, PromptLayer had the following error",
816
+ )
817
+ else:
818
+ warn_on_bad_response(
819
+ response,
820
+ "WARNING: While tracking your metadata, PromptLayer had the following error",
821
+ )
822
+ return False
721
823
  except httpx.RequestError as e:
722
- print(
723
- f"WARNING: While tracking your metadata PromptLayer had the following error: {e}",
724
- file=sys.stderr,
725
- )
824
+ if throw_on_error:
825
+ raise _exceptions.PromptLayerAPIConnectionError(
826
+ f"While tracking your metadata PromptLayer had the following error: {e}", response=None, body=None
827
+ ) from e
828
+ logger.warning(f"While tracking your metadata PromptLayer had the following error: {e}")
726
829
  return False
727
830
 
728
831
  return True
729
832
 
730
833
 
731
- def promptlayer_track_score(api_key: str, base_url: str, request_id, score, score_name):
834
+ @retry_on_api_error
835
+ def promptlayer_track_score(api_key: str, base_url: str, throw_on_error: bool, request_id, score, score_name):
732
836
  try:
733
837
  data = {"request_id": request_id, "score": score, "api_key": api_key}
734
838
  if score_name is not None:
@@ -738,23 +842,32 @@ def promptlayer_track_score(api_key: str, base_url: str, request_id, score, scor
738
842
  json=data,
739
843
  )
740
844
  if request_response.status_code != 200:
741
- warn_on_bad_response(
742
- request_response,
743
- "WARNING: While tracking your score PromptLayer had the following error",
744
- )
745
- return False
845
+ if throw_on_error:
846
+ raise_on_bad_response(
847
+ request_response,
848
+ "While tracking your score PromptLayer had the following error",
849
+ )
850
+ else:
851
+ warn_on_bad_response(
852
+ request_response,
853
+ "WARNING: While tracking your score PromptLayer had the following error",
854
+ )
855
+ return False
746
856
  except Exception as e:
747
- print(
748
- f"WARNING: While tracking your score PromptLayer had the following error: {e}",
749
- file=sys.stderr,
750
- )
857
+ if throw_on_error:
858
+ raise _exceptions.PromptLayerAPIError(
859
+ f"While tracking your score PromptLayer had the following error: {e}", response=None, body=None
860
+ ) from e
861
+ logger.warning(f"While tracking your score PromptLayer had the following error: {e}")
751
862
  return False
752
863
  return True
753
864
 
754
865
 
866
+ @retry_on_api_error
755
867
  async def apromptlayer_track_score(
756
868
  api_key: str,
757
869
  base_url: str,
870
+ throw_on_error: bool,
758
871
  request_id: str,
759
872
  score: float,
760
873
  score_name: Optional[str],
@@ -771,19 +884,24 @@ async def apromptlayer_track_score(
771
884
  async with _make_httpx_client() as client:
772
885
  response = await client.post(url, json=data)
773
886
 
774
- if RAISE_FOR_STATUS:
775
- response.raise_for_status()
776
- elif response.status_code != 200:
777
- warn_on_bad_response(
778
- response,
779
- "WARNING: While tracking your score, PromptLayer had the following error",
780
- )
781
- return False
887
+ if response.status_code != 200:
888
+ if throw_on_error:
889
+ raise_on_bad_response(
890
+ response,
891
+ "While tracking your score, PromptLayer had the following error",
892
+ )
893
+ else:
894
+ warn_on_bad_response(
895
+ response,
896
+ "WARNING: While tracking your score, PromptLayer had the following error",
897
+ )
898
+ return False
782
899
  except httpx.RequestError as e:
783
- print(
784
- f"WARNING: While tracking your score PromptLayer had the following error: {str(e)}",
785
- file=sys.stderr,
786
- )
900
+ if throw_on_error:
901
+ raise _exceptions.PromptLayerAPIConnectionError(
902
+ f"PromptLayer had the following error while tracking your score: {str(e)}", response=None, body=None
903
+ ) from e
904
+ logger.warning(f"While tracking your score PromptLayer had the following error: {str(e)}")
787
905
  return False
788
906
 
789
907
  return True
@@ -1010,29 +1128,60 @@ async def run_in_thread_async(executor, func, *args, **kwargs):
1010
1128
  def warn_on_bad_response(request_response, main_message):
1011
1129
  if hasattr(request_response, "json"):
1012
1130
  try:
1013
- print(
1014
- f"{main_message}: {request_response.json().get('message')}",
1015
- file=sys.stderr,
1016
- )
1131
+ logger.warning(f"{main_message}: {request_response.json().get('message')}")
1017
1132
  except json.JSONDecodeError:
1018
- print(
1019
- f"{main_message}: {request_response}",
1020
- file=sys.stderr,
1021
- )
1133
+ logger.warning(f"{main_message}: {request_response}")
1022
1134
  else:
1023
- print(f"{main_message}: {request_response}", file=sys.stderr)
1135
+ logger.warning(f"{main_message}: {request_response}")
1024
1136
 
1025
1137
 
1026
1138
  def raise_on_bad_response(request_response, main_message):
1139
+ """Raise an appropriate exception based on the HTTP status code."""
1140
+ status_code = getattr(request_response, "status_code", None)
1141
+
1142
+ body = None
1143
+ error_detail = None
1027
1144
  if hasattr(request_response, "json"):
1028
1145
  try:
1029
- raise Exception(
1030
- f"{main_message}: {request_response.json().get('message') or request_response.json().get('error')}"
1031
- )
1032
- except json.JSONDecodeError:
1033
- raise Exception(f"{main_message}: {request_response}")
1146
+ body = request_response.json()
1147
+ error_detail = body.get("message") or body.get("error") or body.get("detail")
1148
+ except (json.JSONDecodeError, AttributeError):
1149
+ body = getattr(request_response, "text", str(request_response))
1150
+ error_detail = body
1151
+ else:
1152
+ body = str(request_response)
1153
+ error_detail = body
1154
+
1155
+ if error_detail:
1156
+ err_msg = f"{main_message}: {error_detail}"
1034
1157
  else:
1035
- raise Exception(f"{main_message}: {request_response}")
1158
+ err_msg = main_message
1159
+
1160
+ if status_code == 400:
1161
+ raise _exceptions.PromptLayerBadRequestError(err_msg, response=request_response, body=body)
1162
+
1163
+ if status_code == 401:
1164
+ raise _exceptions.PromptLayerAuthenticationError(err_msg, response=request_response, body=body)
1165
+
1166
+ if status_code == 403:
1167
+ raise _exceptions.PromptLayerPermissionDeniedError(err_msg, response=request_response, body=body)
1168
+
1169
+ if status_code == 404:
1170
+ raise _exceptions.PromptLayerNotFoundError(err_msg, response=request_response, body=body)
1171
+
1172
+ if status_code == 409:
1173
+ raise _exceptions.PromptLayerConflictError(err_msg, response=request_response, body=body)
1174
+
1175
+ if status_code == 422:
1176
+ raise _exceptions.PromptLayerUnprocessableEntityError(err_msg, response=request_response, body=body)
1177
+
1178
+ if status_code == 429:
1179
+ raise _exceptions.PromptLayerRateLimitError(err_msg, response=request_response, body=body)
1180
+
1181
+ if status_code and status_code >= 500:
1182
+ raise _exceptions.PromptLayerInternalServerError(err_msg, response=request_response, body=body)
1183
+
1184
+ raise _exceptions.PromptLayerAPIStatusError(err_msg, response=request_response, body=body)
1036
1185
 
1037
1186
 
1038
1187
  async def async_wrapper(
@@ -1080,7 +1229,8 @@ async def async_wrapper(
1080
1229
  context.detach(token)
1081
1230
 
1082
1231
 
1083
- def promptlayer_create_group(api_key: str, base_url: str):
1232
+ @retry_on_api_error
1233
+ def promptlayer_create_group(api_key: str, base_url: str, throw_on_error: bool):
1084
1234
  try:
1085
1235
  request_response = requests.post(
1086
1236
  f"{base_url}/create-group",
@@ -1089,18 +1239,29 @@ def promptlayer_create_group(api_key: str, base_url: str):
1089
1239
  },
1090
1240
  )
1091
1241
  if request_response.status_code != 200:
1092
- warn_on_bad_response(
1093
- request_response,
1094
- "WARNING: While creating your group PromptLayer had the following error",
1095
- )
1096
- return False
1242
+ if throw_on_error:
1243
+ raise_on_bad_response(
1244
+ request_response,
1245
+ "While creating your group PromptLayer had the following error",
1246
+ )
1247
+ else:
1248
+ warn_on_bad_response(
1249
+ request_response,
1250
+ "WARNING: While creating your group PromptLayer had the following error",
1251
+ )
1252
+ return False
1097
1253
  except requests.exceptions.RequestException as e:
1098
- # I'm aiming for a more specific exception catch here
1099
- raise Exception(f"PromptLayer had the following error while creating your group: {e}")
1254
+ if throw_on_error:
1255
+ raise _exceptions.PromptLayerAPIConnectionError(
1256
+ f"PromptLayer had the following error while creating your group: {e}", response=None, body=None
1257
+ ) from e
1258
+ logger.warning(f"While creating your group PromptLayer had the following error: {e}")
1259
+ return False
1100
1260
  return request_response.json()["id"]
1101
1261
 
1102
1262
 
1103
- async def apromptlayer_create_group(api_key: str, base_url: str):
1263
+ @retry_on_api_error
1264
+ async def apromptlayer_create_group(api_key: str, base_url: str, throw_on_error: bool):
1104
1265
  try:
1105
1266
  async with _make_httpx_client() as client:
1106
1267
  response = await client.post(
@@ -1110,20 +1271,30 @@ async def apromptlayer_create_group(api_key: str, base_url: str):
1110
1271
  },
1111
1272
  )
1112
1273
 
1113
- if RAISE_FOR_STATUS:
1114
- response.raise_for_status()
1115
- elif response.status_code != 200:
1116
- warn_on_bad_response(
1117
- response,
1118
- "WARNING: While creating your group, PromptLayer had the following error",
1119
- )
1120
- return False
1274
+ if response.status_code != 200:
1275
+ if throw_on_error:
1276
+ raise_on_bad_response(
1277
+ response,
1278
+ "While creating your group, PromptLayer had the following error",
1279
+ )
1280
+ else:
1281
+ warn_on_bad_response(
1282
+ response,
1283
+ "WARNING: While creating your group, PromptLayer had the following error",
1284
+ )
1285
+ return False
1121
1286
  return response.json()["id"]
1122
1287
  except httpx.RequestError as e:
1123
- raise Exception(f"PromptLayer had the following error while creating your group: {str(e)}") from e
1288
+ if throw_on_error:
1289
+ raise _exceptions.PromptLayerAPIConnectionError(
1290
+ f"PromptLayer had the following error while creating your group: {str(e)}", response=None, body=None
1291
+ ) from e
1292
+ logger.warning(f"While creating your group PromptLayer had the following error: {e}")
1293
+ return False
1124
1294
 
1125
1295
 
1126
- def promptlayer_track_group(api_key: str, base_url: str, request_id, group_id):
1296
+ @retry_on_api_error
1297
+ def promptlayer_track_group(api_key: str, base_url: str, throw_on_error: bool, request_id, group_id):
1127
1298
  try:
1128
1299
  request_response = requests.post(
1129
1300
  f"{base_url}/track-group",
@@ -1134,18 +1305,29 @@ def promptlayer_track_group(api_key: str, base_url: str, request_id, group_id):
1134
1305
  },
1135
1306
  )
1136
1307
  if request_response.status_code != 200:
1137
- warn_on_bad_response(
1138
- request_response,
1139
- "WARNING: While tracking your group PromptLayer had the following error",
1140
- )
1141
- return False
1308
+ if throw_on_error:
1309
+ raise_on_bad_response(
1310
+ request_response,
1311
+ "While tracking your group PromptLayer had the following error",
1312
+ )
1313
+ else:
1314
+ warn_on_bad_response(
1315
+ request_response,
1316
+ "WARNING: While tracking your group PromptLayer had the following error",
1317
+ )
1318
+ return False
1142
1319
  except requests.exceptions.RequestException as e:
1143
- # I'm aiming for a more specific exception catch here
1144
- raise Exception(f"PromptLayer had the following error while tracking your group: {e}")
1320
+ if throw_on_error:
1321
+ raise _exceptions.PromptLayerAPIConnectionError(
1322
+ f"PromptLayer had the following error while tracking your group: {e}", response=None, body=None
1323
+ ) from e
1324
+ logger.warning(f"While tracking your group PromptLayer had the following error: {e}")
1325
+ return False
1145
1326
  return True
1146
1327
 
1147
1328
 
1148
- async def apromptlayer_track_group(api_key: str, base_url: str, request_id, group_id):
1329
+ @retry_on_api_error
1330
+ async def apromptlayer_track_group(api_key: str, base_url: str, throw_on_error: bool, request_id, group_id):
1149
1331
  try:
1150
1332
  payload = {
1151
1333
  "api_key": api_key,
@@ -1159,26 +1341,32 @@ async def apromptlayer_track_group(api_key: str, base_url: str, request_id, grou
1159
1341
  json=payload,
1160
1342
  )
1161
1343
 
1162
- if RAISE_FOR_STATUS:
1163
- response.raise_for_status()
1164
- elif response.status_code != 200:
1165
- warn_on_bad_response(
1166
- response,
1167
- "WARNING: While tracking your group, PromptLayer had the following error",
1168
- )
1169
- return False
1344
+ if response.status_code != 200:
1345
+ if throw_on_error:
1346
+ raise_on_bad_response(
1347
+ response,
1348
+ "While tracking your group, PromptLayer had the following error",
1349
+ )
1350
+ else:
1351
+ warn_on_bad_response(
1352
+ response,
1353
+ "WARNING: While tracking your group, PromptLayer had the following error",
1354
+ )
1355
+ return False
1170
1356
  except httpx.RequestError as e:
1171
- print(
1172
- f"WARNING: While tracking your group PromptLayer had the following error: {e}",
1173
- file=sys.stderr,
1174
- )
1357
+ if throw_on_error:
1358
+ raise _exceptions.PromptLayerAPIConnectionError(
1359
+ f"PromptLayer had the following error while tracking your group: {str(e)}", response=None, body=None
1360
+ ) from e
1361
+ logger.warning(f"While tracking your group PromptLayer had the following error: {e}")
1175
1362
  return False
1176
1363
 
1177
1364
  return True
1178
1365
 
1179
1366
 
1367
+ @retry_on_api_error
1180
1368
  def get_prompt_template(
1181
- api_key: str, base_url: str, prompt_name: str, params: Union[GetPromptTemplate, None] = None
1369
+ api_key: str, base_url: str, throw_on_error: bool, prompt_name: str, params: Union[GetPromptTemplate, None] = None
1182
1370
  ) -> GetPromptTemplateResponse:
1183
1371
  try:
1184
1372
  json_body = {"api_key": api_key}
@@ -1190,22 +1378,42 @@ def get_prompt_template(
1190
1378
  json=json_body,
1191
1379
  )
1192
1380
  if response.status_code != 200:
1193
- raise Exception(f"PromptLayer had the following error while getting your prompt template: {response.text}")
1381
+ if throw_on_error:
1382
+ raise_on_bad_response(
1383
+ response, "PromptLayer had the following error while getting your prompt template"
1384
+ )
1385
+ else:
1386
+ warn_on_bad_response(
1387
+ response, "WARNING: PromptLayer had the following error while getting your prompt template"
1388
+ )
1389
+ return None
1194
1390
 
1195
- warning = response.json().get("warning", None)
1196
- if warning is not None:
1197
- warn_on_bad_response(
1198
- warning,
1199
- "WARNING: While getting your prompt template",
1200
- )
1201
1391
  return response.json()
1392
+ except requests.exceptions.ConnectionError as e:
1393
+ err_msg = f"PromptLayer had the following error while getting your prompt template: {e}"
1394
+ if throw_on_error:
1395
+ raise _exceptions.PromptLayerAPIConnectionError(err_msg, response=None, body=None) from e
1396
+ logger.warning(err_msg)
1397
+ return None
1398
+ except requests.exceptions.Timeout as e:
1399
+ err_msg = f"PromptLayer had the following error while getting your prompt template: {e}"
1400
+ if throw_on_error:
1401
+ raise _exceptions.PromptLayerAPITimeoutError(err_msg, response=None, body=None) from e
1402
+ logger.warning(err_msg)
1403
+ return None
1202
1404
  except requests.exceptions.RequestException as e:
1203
- raise Exception(f"PromptLayer had the following error while getting your prompt template: {e}")
1405
+ err_msg = f"PromptLayer had the following error while getting your prompt template: {e}"
1406
+ if throw_on_error:
1407
+ raise _exceptions.PromptLayerError(err_msg, response=None, body=None) from e
1408
+ logger.warning(err_msg)
1409
+ return None
1204
1410
 
1205
1411
 
1412
+ @retry_on_api_error
1206
1413
  async def aget_prompt_template(
1207
1414
  api_key: str,
1208
1415
  base_url: str,
1416
+ throw_on_error: bool,
1209
1417
  prompt_name: str,
1210
1418
  params: Union[GetPromptTemplate, None] = None,
1211
1419
  ) -> GetPromptTemplateResponse:
@@ -1220,27 +1428,43 @@ async def aget_prompt_template(
1220
1428
  json=json_body,
1221
1429
  )
1222
1430
 
1223
- if RAISE_FOR_STATUS:
1224
- response.raise_for_status()
1225
- elif response.status_code != 200:
1226
- raise_on_bad_response(
1227
- response,
1228
- "PromptLayer had the following error while getting your prompt template",
1229
- )
1230
- warning = response.json().get("warning", None)
1231
- if warning:
1232
- warn_on_bad_response(
1233
- warning,
1234
- "WARNING: While getting your prompt template",
1235
- )
1431
+ if response.status_code != 200:
1432
+ if throw_on_error:
1433
+ raise_on_bad_response(
1434
+ response,
1435
+ "PromptLayer had the following error while getting your prompt template",
1436
+ )
1437
+ else:
1438
+ warn_on_bad_response(
1439
+ response, "WARNING: While getting your prompt template PromptLayer had the following error"
1440
+ )
1441
+ return None
1236
1442
  return response.json()
1443
+ except (httpx.ConnectError, httpx.NetworkError) as e:
1444
+ err_msg = f"PromptLayer had the following error while getting your prompt template: {str(e)}"
1445
+ if throw_on_error:
1446
+ raise _exceptions.PromptLayerAPIConnectionError(err_msg, response=None, body=None) from e
1447
+ logger.warning(err_msg)
1448
+ return None
1449
+ except httpx.TimeoutException as e:
1450
+ err_msg = f"PromptLayer had the following error while getting your prompt template: {str(e)}"
1451
+ if throw_on_error:
1452
+ raise _exceptions.PromptLayerAPITimeoutError(err_msg, response=None, body=None) from e
1453
+ logger.warning(err_msg)
1454
+ return None
1237
1455
  except httpx.RequestError as e:
1238
- raise Exception(f"PromptLayer had the following error while getting your prompt template: {str(e)}") from e
1456
+ err_msg = f"PromptLayer had the following error while getting your prompt template: {str(e)}"
1457
+ if throw_on_error:
1458
+ raise _exceptions.PromptLayerAPIConnectionError(err_msg, response=None, body=None) from e
1459
+ logger.warning(err_msg)
1460
+ return None
1239
1461
 
1240
1462
 
1463
+ @retry_on_api_error
1241
1464
  def publish_prompt_template(
1242
1465
  api_key: str,
1243
1466
  base_url: str,
1467
+ throw_on_error: bool,
1244
1468
  body: PublishPromptTemplate,
1245
1469
  ) -> PublishPromptTemplateResponse:
1246
1470
  try:
@@ -1254,17 +1478,32 @@ def publish_prompt_template(
1254
1478
  },
1255
1479
  )
1256
1480
  if response.status_code == 400:
1257
- raise Exception(
1258
- f"PromptLayer had the following error while publishing your prompt template: {response.text}"
1259
- )
1481
+ if throw_on_error:
1482
+ raise_on_bad_response(
1483
+ response, "PromptLayer had the following error while publishing your prompt template"
1484
+ )
1485
+ else:
1486
+ warn_on_bad_response(
1487
+ response, "WARNING: PromptLayer had the following error while publishing your prompt template"
1488
+ )
1489
+ return None
1260
1490
  return response.json()
1261
1491
  except requests.exceptions.RequestException as e:
1262
- raise Exception(f"PromptLayer had the following error while publishing your prompt template: {e}")
1492
+ if throw_on_error:
1493
+ raise _exceptions.PromptLayerAPIConnectionError(
1494
+ f"PromptLayer had the following error while publishing your prompt template: {e}",
1495
+ response=None,
1496
+ body=None,
1497
+ ) from e
1498
+ logger.warning(f"PromptLayer had the following error while publishing your prompt template: {e}")
1499
+ return None
1263
1500
 
1264
1501
 
1502
+ @retry_on_api_error
1265
1503
  async def apublish_prompt_template(
1266
1504
  api_key: str,
1267
1505
  base_url: str,
1506
+ throw_on_error: bool,
1268
1507
  body: PublishPromptTemplate,
1269
1508
  ) -> PublishPromptTemplateResponse:
1270
1509
  try:
@@ -1279,24 +1518,32 @@ async def apublish_prompt_template(
1279
1518
  },
1280
1519
  )
1281
1520
 
1282
- if RAISE_FOR_STATUS:
1283
- response.raise_for_status()
1284
- elif response.status_code == 400:
1285
- raise Exception(
1286
- f"PromptLayer had the following error while publishing your prompt template: {response.text}"
1287
- )
1288
- if response.status_code != 201:
1289
- raise_on_bad_response(
1290
- response,
1291
- "PromptLayer had the following error while publishing your prompt template",
1292
- )
1521
+ if response.status_code == 400 or response.status_code != 201:
1522
+ if throw_on_error:
1523
+ raise_on_bad_response(
1524
+ response,
1525
+ "PromptLayer had the following error while publishing your prompt template",
1526
+ )
1527
+ else:
1528
+ warn_on_bad_response(
1529
+ response, "WARNING: PromptLayer had the following error while publishing your prompt template"
1530
+ )
1531
+ return None
1293
1532
  return response.json()
1294
1533
  except httpx.RequestError as e:
1295
- raise Exception(f"PromptLayer had the following error while publishing your prompt template: {str(e)}") from e
1534
+ if throw_on_error:
1535
+ raise _exceptions.PromptLayerAPIConnectionError(
1536
+ f"PromptLayer had the following error while publishing your prompt template: {str(e)}",
1537
+ response=None,
1538
+ body=None,
1539
+ ) from e
1540
+ logger.warning(f"PromptLayer had the following error while publishing your prompt template: {e}")
1541
+ return None
1296
1542
 
1297
1543
 
1544
+ @retry_on_api_error
1298
1545
  def get_all_prompt_templates(
1299
- api_key: str, base_url: str, page: int = 1, per_page: int = 30, label: str = None
1546
+ api_key: str, base_url: str, throw_on_error: bool, page: int = 1, per_page: int = 30, label: str = None
1300
1547
  ) -> List[ListPromptTemplateResponse]:
1301
1548
  try:
1302
1549
  params = {"page": page, "per_page": per_page}
@@ -1308,17 +1555,31 @@ def get_all_prompt_templates(
1308
1555
  params=params,
1309
1556
  )
1310
1557
  if response.status_code != 200:
1311
- raise Exception(
1312
- f"PromptLayer had the following error while getting all your prompt templates: {response.text}"
1313
- )
1558
+ if throw_on_error:
1559
+ raise_on_bad_response(
1560
+ response, "PromptLayer had the following error while getting all your prompt templates"
1561
+ )
1562
+ else:
1563
+ warn_on_bad_response(
1564
+ response, "WARNING: PromptLayer had the following error while getting all your prompt templates"
1565
+ )
1566
+ return []
1314
1567
  items = response.json().get("items", [])
1315
1568
  return items
1316
1569
  except requests.exceptions.RequestException as e:
1317
- raise Exception(f"PromptLayer had the following error while getting all your prompt templates: {e}")
1570
+ if throw_on_error:
1571
+ raise _exceptions.PromptLayerAPIConnectionError(
1572
+ f"PromptLayer had the following error while getting all your prompt templates: {e}",
1573
+ response=None,
1574
+ body=None,
1575
+ ) from e
1576
+ logger.warning(f"PromptLayer had the following error while getting all your prompt templates: {e}")
1577
+ return []
1318
1578
 
1319
1579
 
1580
+ @retry_on_api_error
1320
1581
  async def aget_all_prompt_templates(
1321
- api_key: str, base_url: str, page: int = 1, per_page: int = 30, label: str = None
1582
+ api_key: str, base_url: str, throw_on_error: bool, page: int = 1, per_page: int = 30, label: str = None
1322
1583
  ) -> List[ListPromptTemplateResponse]:
1323
1584
  try:
1324
1585
  params = {"page": page, "per_page": per_page}
@@ -1331,17 +1592,28 @@ async def aget_all_prompt_templates(
1331
1592
  params=params,
1332
1593
  )
1333
1594
 
1334
- if RAISE_FOR_STATUS:
1335
- response.raise_for_status()
1336
- elif response.status_code != 200:
1337
- raise_on_bad_response(
1338
- response,
1339
- "PromptLayer had the following error while getting all your prompt templates",
1340
- )
1595
+ if response.status_code != 200:
1596
+ if throw_on_error:
1597
+ raise_on_bad_response(
1598
+ response,
1599
+ "PromptLayer had the following error while getting all your prompt templates",
1600
+ )
1601
+ else:
1602
+ warn_on_bad_response(
1603
+ response, "WARNING: PromptLayer had the following error while getting all your prompt templates"
1604
+ )
1605
+ return []
1341
1606
  items = response.json().get("items", [])
1342
1607
  return items
1343
1608
  except httpx.RequestError as e:
1344
- raise Exception(f"PromptLayer had the following error while getting all your prompt templates: {str(e)}") from e
1609
+ if throw_on_error:
1610
+ raise _exceptions.PromptLayerAPIConnectionError(
1611
+ f"PromptLayer had the following error while getting all your prompt templates: {str(e)}",
1612
+ response=None,
1613
+ body=None,
1614
+ ) from e
1615
+ logger.warning(f"PromptLayer had the following error while getting all your prompt templates: {e}")
1616
+ return []
1345
1617
 
1346
1618
 
1347
1619
  def openai_chat_request(client, **kwargs):
@@ -1475,13 +1747,16 @@ def get_api_key():
1475
1747
  # raise an error if the api key is not set
1476
1748
  api_key = os.environ.get("PROMPTLAYER_API_KEY")
1477
1749
  if not api_key:
1478
- raise Exception(
1479
- "Please set your PROMPTLAYER_API_KEY environment variable or set API KEY in code using 'promptlayer.api_key = <your_api_key>' "
1750
+ raise _exceptions.PromptLayerAuthenticationError(
1751
+ "Please set your PROMPTLAYER_API_KEY environment variable or set API KEY in code using 'promptlayer.api_key = <your_api_key>'",
1752
+ response=None,
1753
+ body=None,
1480
1754
  )
1481
1755
  return api_key
1482
1756
 
1483
1757
 
1484
- def util_log_request(api_key: str, base_url: str, **kwargs) -> Union[RequestLog, None]:
1758
+ @retry_on_api_error
1759
+ def util_log_request(api_key: str, base_url: str, throw_on_error: bool, **kwargs) -> Union[RequestLog, None]:
1485
1760
  try:
1486
1761
  response = requests.post(
1487
1762
  f"{base_url}/log-request",
@@ -1489,21 +1764,26 @@ def util_log_request(api_key: str, base_url: str, **kwargs) -> Union[RequestLog,
1489
1764
  json=kwargs,
1490
1765
  )
1491
1766
  if response.status_code != 201:
1492
- warn_on_bad_response(
1493
- response,
1494
- "WARNING: While logging your request PromptLayer had the following error",
1495
- )
1496
- return None
1767
+ if throw_on_error:
1768
+ raise_on_bad_response(response, "PromptLayer had the following error while logging your request")
1769
+ else:
1770
+ warn_on_bad_response(
1771
+ response,
1772
+ "WARNING: While logging your request PromptLayer had the following error",
1773
+ )
1774
+ return None
1497
1775
  return response.json()
1498
1776
  except Exception as e:
1499
- print(
1500
- f"WARNING: While tracking your prompt PromptLayer had the following error: {e}",
1501
- file=sys.stderr,
1502
- )
1777
+ if throw_on_error:
1778
+ raise _exceptions.PromptLayerAPIError(
1779
+ f"While logging your request PromptLayer had the following error: {e}", response=None, body=None
1780
+ ) from e
1781
+ logger.warning(f"While tracking your prompt PromptLayer had the following error: {e}")
1503
1782
  return None
1504
1783
 
1505
1784
 
1506
- async def autil_log_request(api_key: str, base_url: str, **kwargs) -> Union[RequestLog, None]:
1785
+ @retry_on_api_error
1786
+ async def autil_log_request(api_key: str, base_url: str, throw_on_error: bool, **kwargs) -> Union[RequestLog, None]:
1507
1787
  try:
1508
1788
  async with _make_httpx_client() as client:
1509
1789
  response = await client.post(
@@ -1512,17 +1792,21 @@ async def autil_log_request(api_key: str, base_url: str, **kwargs) -> Union[Requ
1512
1792
  json=kwargs,
1513
1793
  )
1514
1794
  if response.status_code != 201:
1515
- warn_on_bad_response(
1516
- response,
1517
- "WARNING: While logging your request PromptLayer had the following error",
1518
- )
1519
- return None
1795
+ if throw_on_error:
1796
+ raise_on_bad_response(response, "PromptLayer had the following error while logging your request")
1797
+ else:
1798
+ warn_on_bad_response(
1799
+ response,
1800
+ "WARNING: While logging your request PromptLayer had the following error",
1801
+ )
1802
+ return None
1520
1803
  return response.json()
1521
1804
  except Exception as e:
1522
- print(
1523
- f"WARNING: While tracking your prompt PromptLayer had the following error: {e}",
1524
- file=sys.stderr,
1525
- )
1805
+ if throw_on_error:
1806
+ raise _exceptions.PromptLayerAPIError(
1807
+ f"While logging your request PromptLayer had the following error: {e}", response=None, body=None
1808
+ ) from e
1809
+ logger.warning(f"While tracking your prompt PromptLayer had the following error: {e}")
1526
1810
  return None
1527
1811
 
1528
1812
 
@@ -1551,6 +1835,26 @@ async def amistral_request(
1551
1835
  return await client.chat.complete_async(**function_kwargs)
1552
1836
 
1553
1837
 
1838
+ class _GoogleStreamWrapper:
1839
+ """Wrapper to keep Google client alive during streaming."""
1840
+
1841
+ def __init__(self, stream_generator, client):
1842
+ self._stream = stream_generator
1843
+ self._client = client # Keep client alive
1844
+
1845
+ def __iter__(self):
1846
+ return self._stream.__iter__()
1847
+
1848
+ def __next__(self):
1849
+ return next(self._stream)
1850
+
1851
+ def __aiter__(self):
1852
+ return self._stream.__aiter__()
1853
+
1854
+ async def __anext__(self):
1855
+ return await self._stream.__anext__()
1856
+
1857
+
1554
1858
  def google_chat_request(client, **kwargs):
1555
1859
  from google.genai.chats import Content
1556
1860
 
@@ -1561,7 +1865,8 @@ def google_chat_request(client, **kwargs):
1561
1865
  chat = client.chats.create(model=model, history=history, config=generation_config)
1562
1866
  last_message = history[-1].parts if history else ""
1563
1867
  if stream:
1564
- return chat.send_message_stream(message=last_message)
1868
+ stream_gen = chat.send_message_stream(message=last_message)
1869
+ return _GoogleStreamWrapper(stream_gen, client)
1565
1870
  return chat.send_message(message=last_message)
1566
1871
 
1567
1872
 
@@ -1571,7 +1876,8 @@ def google_completions_request(client, **kwargs):
1571
1876
  contents = kwargs.get("contents", [])
1572
1877
  stream = kwargs.pop("stream", False)
1573
1878
  if stream:
1574
- return client.models.generate_content_stream(model=model, contents=contents, config=config)
1879
+ stream_gen = client.models.generate_content_stream(model=model, contents=contents, config=config)
1880
+ return _GoogleStreamWrapper(stream_gen, client)
1575
1881
  return client.models.generate_content(model=model, contents=contents, config=config)
1576
1882
 
1577
1883
 
@@ -1606,7 +1912,8 @@ async def agoogle_chat_request(client, **kwargs):
1606
1912
  chat = client.aio.chats.create(model=model, history=history, config=generation_config)
1607
1913
  last_message = history[-1].parts[0] if history else ""
1608
1914
  if stream:
1609
- return await chat.send_message_stream(message=last_message)
1915
+ stream_gen = await chat.send_message_stream(message=last_message)
1916
+ return _GoogleStreamWrapper(stream_gen, client)
1610
1917
  return await chat.send_message(message=last_message)
1611
1918
 
1612
1919
 
@@ -1616,8 +1923,9 @@ async def agoogle_completions_request(client, **kwargs):
1616
1923
  contents = kwargs.get("contents", [])
1617
1924
  stream = kwargs.pop("stream", False)
1618
1925
  if stream:
1619
- return await client.aio.models.generate_content_stream(model=model, contents=contents, config=config)
1620
- return await client.aio.models.generate_content(model=model, contents=contents, config=config)
1926
+ stream_gen = await client.aio.models.generate_content_stream(model=model, contents=contents, config=config)
1927
+ return _GoogleStreamWrapper(stream_gen, client)
1928
+ return await client.aio.models.generate_content(model=model, contents=contents, config=config)
1621
1929
 
1622
1930
 
1623
1931
  AMAP_TYPE_TO_GOOGLE_FUNCTION = {