promptlayer 1.0.35__py3-none-any.whl → 1.0.78__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.
@@ -1,31 +1,64 @@
1
1
  import asyncio
2
+ import json
3
+ import logging
2
4
  import os
3
5
  from typing import Any, Dict, List, Literal, Optional, Union
4
6
 
5
7
  import nest_asyncio
6
8
 
9
+ from promptlayer import exceptions as _exceptions
7
10
  from promptlayer.groups import AsyncGroupManager, GroupManager
8
11
  from promptlayer.promptlayer_base import PromptLayerBase
9
12
  from promptlayer.promptlayer_mixins import PromptLayerMixin
13
+ from promptlayer.streaming import astream_response, stream_response
10
14
  from promptlayer.templates import AsyncTemplateManager, TemplateManager
11
15
  from promptlayer.track import AsyncTrackManager, TrackManager
12
16
  from promptlayer.types.prompt_template import PromptTemplate
13
17
  from promptlayer.utils import (
18
+ RERAISE_ORIGINAL_EXCEPTION,
19
+ _get_workflow_workflow_id_or_name,
14
20
  arun_workflow_request,
15
- astream_response,
16
21
  atrack_request,
17
22
  autil_log_request,
18
- stream_response,
19
23
  track_request,
20
24
  util_log_request,
21
25
  )
22
26
 
27
+ logger = logging.getLogger(__name__)
28
+
29
+
30
+ def get_base_url(base_url: Union[str, None]):
31
+ return base_url or os.environ.get("PROMPTLAYER_BASE_URL", "https://api.promptlayer.com")
32
+
33
+
34
+ def is_workflow_results_dict(obj: Any) -> bool:
35
+ if not isinstance(obj, dict):
36
+ return False
37
+
38
+ required_keys = {
39
+ "status",
40
+ "value",
41
+ "error_message",
42
+ "raw_error_message",
43
+ "is_output_node",
44
+ }
45
+
46
+ for val in obj.values():
47
+ if not isinstance(val, dict):
48
+ return False
49
+ if not required_keys.issubset(val.keys()):
50
+ return False
51
+
52
+ return True
53
+
23
54
 
24
55
  class PromptLayer(PromptLayerMixin):
25
56
  def __init__(
26
57
  self,
27
- api_key: str = None,
58
+ api_key: Union[str, None] = None,
28
59
  enable_tracing: bool = False,
60
+ base_url: Union[str, None] = None,
61
+ throw_on_error: bool = True,
29
62
  ):
30
63
  if api_key is None:
31
64
  api_key = os.environ.get("PROMPTLAYER_API_KEY")
@@ -36,13 +69,15 @@ class PromptLayer(PromptLayerMixin):
36
69
  "Please set the PROMPTLAYER_API_KEY environment variable or pass the api_key parameter."
37
70
  )
38
71
 
72
+ self.base_url = get_base_url(base_url)
39
73
  self.api_key = api_key
40
- self.templates = TemplateManager(api_key)
41
- self.group = GroupManager(api_key)
74
+ self.throw_on_error = throw_on_error
75
+ self.templates = TemplateManager(api_key, self.base_url, self.throw_on_error)
76
+ self.group = GroupManager(api_key, self.base_url, self.throw_on_error)
42
77
  self.tracer_provider, self.tracer = self._initialize_tracer(
43
- api_key, enable_tracing
78
+ api_key, self.base_url, self.throw_on_error, enable_tracing
44
79
  )
45
- self.track = TrackManager(api_key)
80
+ self.track = TrackManager(api_key, self.base_url, self.throw_on_error)
46
81
 
47
82
  def __getattr__(
48
83
  self,
@@ -51,24 +86,20 @@ class PromptLayer(PromptLayerMixin):
51
86
  if name == "openai":
52
87
  import openai as openai_module
53
88
 
54
- openai = PromptLayerBase(
55
- openai_module,
56
- function_name="openai",
57
- api_key=self.api_key,
58
- tracer=self.tracer,
89
+ return PromptLayerBase(
90
+ self.api_key, self.base_url, openai_module, function_name="openai", tracer=self.tracer
59
91
  )
60
- return openai
61
92
  elif name == "anthropic":
62
93
  import anthropic as anthropic_module
63
94
 
64
- anthropic = PromptLayerBase(
95
+ return PromptLayerBase(
96
+ self.api_key,
97
+ self.base_url,
65
98
  anthropic_module,
66
99
  function_name="anthropic",
67
100
  provider_type="anthropic",
68
- api_key=self.api_key,
69
101
  tracer=self.tracer,
70
102
  )
71
- return anthropic
72
103
  else:
73
104
  raise AttributeError(f"module {__name__} has no attribute {name}")
74
105
 
@@ -80,6 +111,7 @@ class PromptLayer(PromptLayerMixin):
80
111
  input_variables,
81
112
  group_id,
82
113
  pl_run_span_id: Union[str, None] = None,
114
+ request_start_time: Union[float, None] = None,
83
115
  ):
84
116
  def _track_request(**body):
85
117
  track_request_kwargs = self._prepare_track_request_kwargs(
@@ -89,9 +121,10 @@ class PromptLayer(PromptLayerMixin):
89
121
  input_variables,
90
122
  group_id,
91
123
  pl_run_span_id,
124
+ request_start_time=request_start_time,
92
125
  **body,
93
126
  )
94
- return track_request(**track_request_kwargs)
127
+ return track_request(self.base_url, self.throw_on_error, **track_request_kwargs)
95
128
 
96
129
  return _track_request
97
130
 
@@ -108,50 +141,82 @@ class PromptLayer(PromptLayerMixin):
108
141
  group_id: Union[int, None] = None,
109
142
  stream: bool = False,
110
143
  pl_run_span_id: Union[str, None] = None,
144
+ provider: Union[str, None] = None,
145
+ model: Union[str, None] = None,
111
146
  ) -> Dict[str, Any]:
147
+ import datetime
148
+
112
149
  get_prompt_template_params = self._prepare_get_prompt_template_params(
113
150
  prompt_version=prompt_version,
114
151
  prompt_release_label=prompt_release_label,
115
152
  input_variables=input_variables,
116
153
  metadata=metadata,
154
+ provider=provider,
155
+ model=model,
156
+ model_parameter_overrides=model_parameter_overrides,
117
157
  )
118
158
  prompt_blueprint = self.templates.get(prompt_name, get_prompt_template_params)
159
+ if not prompt_blueprint:
160
+ raise _exceptions.PromptLayerNotFoundError(
161
+ f"Prompt template '{prompt_name}' not found.",
162
+ response=None,
163
+ body=None,
164
+ )
119
165
  prompt_blueprint_model = self._validate_and_extract_model_from_prompt_blueprint(
120
166
  prompt_blueprint=prompt_blueprint, prompt_name=prompt_name
121
167
  )
122
- llm_request_params = self._prepare_llm_request_params(
168
+ llm_data = self._prepare_llm_data(
123
169
  prompt_blueprint=prompt_blueprint,
124
170
  prompt_template=prompt_blueprint["prompt_template"],
125
171
  prompt_blueprint_model=prompt_blueprint_model,
126
- model_parameter_overrides=model_parameter_overrides,
127
172
  stream=stream,
128
173
  )
129
174
 
130
- response = llm_request_params["request_function"](
131
- llm_request_params["prompt_blueprint"], **llm_request_params["kwargs"]
175
+ # Capture start time before making the LLM request
176
+ request_start_time = datetime.datetime.now(datetime.timezone.utc).timestamp()
177
+
178
+ # response is just whatever the LLM call returns
179
+ # streaming=False > Pydantic model instance
180
+ # streaming=True > generator that yields ChatCompletionChunk pieces as they arrive
181
+ response = llm_data["request_function"](
182
+ prompt_blueprint=llm_data["prompt_blueprint"],
183
+ client_kwargs=llm_data["client_kwargs"],
184
+ function_kwargs=llm_data["function_kwargs"],
132
185
  )
133
186
 
187
+ # Capture end time after the LLM request completes
188
+ request_end_time = datetime.datetime.now(datetime.timezone.utc).timestamp()
189
+
134
190
  if stream:
135
191
  return stream_response(
136
- response,
137
- self._create_track_request_callable(
138
- request_params=llm_request_params,
192
+ generator=response,
193
+ after_stream=self._create_track_request_callable(
194
+ request_params=llm_data,
139
195
  tags=tags,
140
196
  input_variables=input_variables,
141
197
  group_id=group_id,
142
198
  pl_run_span_id=pl_run_span_id,
199
+ request_start_time=request_start_time,
143
200
  ),
144
- llm_request_params["stream_function"],
201
+ map_results=llm_data["stream_function"],
202
+ metadata=llm_data["prompt_blueprint"]["metadata"],
145
203
  )
146
204
 
205
+ if isinstance(response, dict):
206
+ request_response = response
207
+ else:
208
+ request_response = response.model_dump(mode="json")
209
+
147
210
  request_log = self._track_request_log(
148
- llm_request_params,
211
+ llm_data,
149
212
  tags,
150
213
  input_variables,
151
214
  group_id,
152
215
  pl_run_span_id,
153
216
  metadata=metadata,
154
- request_response=response.model_dump(),
217
+ request_response=request_response,
218
+ request_start_time=request_start_time,
219
+ request_end_time=request_end_time,
155
220
  )
156
221
 
157
222
  return {
@@ -180,7 +245,7 @@ class PromptLayer(PromptLayerMixin):
180
245
  metadata=metadata,
181
246
  **body,
182
247
  )
183
- return track_request(**track_request_kwargs)
248
+ return track_request(self.base_url, self.throw_on_error, **track_request_kwargs)
184
249
 
185
250
  def run(
186
251
  self,
@@ -193,17 +258,21 @@ class PromptLayer(PromptLayerMixin):
193
258
  metadata: Union[Dict[str, str], None] = None,
194
259
  group_id: Union[int, None] = None,
195
260
  stream: bool = False,
261
+ provider: Union[str, None] = None,
262
+ model: Union[str, None] = None,
196
263
  ) -> Dict[str, Any]:
197
264
  _run_internal_kwargs = {
198
265
  "prompt_name": prompt_name,
199
266
  "prompt_version": prompt_version,
200
267
  "prompt_release_label": prompt_release_label,
201
- "input_variables": input_variables,
268
+ "input_variables": input_variables or {},
202
269
  "model_parameter_overrides": model_parameter_overrides,
203
270
  "tags": tags,
204
271
  "metadata": metadata,
205
272
  "group_id": group_id,
206
273
  "stream": stream,
274
+ "provider": provider,
275
+ "model": model,
207
276
  }
208
277
 
209
278
  if self.tracer:
@@ -211,9 +280,7 @@ class PromptLayer(PromptLayerMixin):
211
280
  span.set_attribute("prompt_name", prompt_name)
212
281
  span.set_attribute("function_input", str(_run_internal_kwargs))
213
282
  pl_run_span_id = hex(span.context.span_id)[2:].zfill(16)
214
- result = self._run_internal(
215
- **_run_internal_kwargs, pl_run_span_id=pl_run_span_id
216
- )
283
+ result = self._run_internal(**_run_internal_kwargs, pl_run_span_id=pl_run_span_id)
217
284
  span.set_attribute("function_output", str(result))
218
285
  return result
219
286
  else:
@@ -221,51 +288,63 @@ class PromptLayer(PromptLayerMixin):
221
288
 
222
289
  def run_workflow(
223
290
  self,
224
- workflow_name: str,
291
+ workflow_id_or_name: Optional[Union[int, str]] = None,
225
292
  input_variables: Optional[Dict[str, Any]] = None,
226
293
  metadata: Optional[Dict[str, str]] = None,
227
294
  workflow_label_name: Optional[str] = None,
228
- workflow_version: Optional[
229
- int
230
- ] = None, # This is the version number, not the version ID
295
+ workflow_version: Optional[int] = None,
231
296
  return_all_outputs: Optional[bool] = False,
232
- ) -> Dict[str, Any]:
297
+ # `workflow_name` deprecated, kept for backward compatibility only.
298
+ # Allows `workflow_name` to be passed both as keyword and positional argument
299
+ # (virtually identical to `workflow_id_or_name`)
300
+ workflow_name: Optional[str] = None,
301
+ ) -> Union[Dict[str, Any], Any]:
233
302
  try:
234
303
  try:
235
- # Check if we're inside a running event loop
236
- loop = asyncio.get_running_loop()
304
+ loop = asyncio.get_running_loop() # Check if we're inside a running event loop
237
305
  except RuntimeError:
238
306
  loop = None
239
307
 
240
308
  if loop and loop.is_running():
241
309
  nest_asyncio.apply()
242
- # If there's an active event loop, use `await` directly
243
- return asyncio.run(
244
- arun_workflow_request(
245
- workflow_name=workflow_name,
246
- input_variables=input_variables or {},
247
- metadata=metadata,
248
- workflow_label_name=workflow_label_name,
249
- workflow_version_number=workflow_version,
250
- api_key=self.api_key,
251
- return_all_outputs=return_all_outputs,
252
- )
310
+
311
+ results = asyncio.run(
312
+ arun_workflow_request(
313
+ api_key=self.api_key,
314
+ base_url=self.base_url,
315
+ throw_on_error=self.throw_on_error,
316
+ workflow_id_or_name=_get_workflow_workflow_id_or_name(workflow_id_or_name, workflow_name),
317
+ input_variables=input_variables or {},
318
+ metadata=metadata,
319
+ workflow_label_name=workflow_label_name,
320
+ workflow_version_number=workflow_version,
321
+ return_all_outputs=return_all_outputs,
253
322
  )
254
- else:
255
- # If there's no active event loop, use `asyncio.run()`
256
- return asyncio.run(
257
- arun_workflow_request(
258
- workflow_name=workflow_name,
259
- input_variables=input_variables or {},
260
- metadata=metadata,
261
- workflow_label_name=workflow_label_name,
262
- workflow_version_number=workflow_version,
263
- api_key=self.api_key,
264
- return_all_outputs=return_all_outputs,
323
+ )
324
+
325
+ if not return_all_outputs and is_workflow_results_dict(results):
326
+ output_nodes = [node_data for node_data in results.values() if node_data.get("is_output_node")]
327
+ if not output_nodes:
328
+ raise _exceptions.PromptLayerNotFoundError(
329
+ f"Output nodes not found: {json.dumps(results, indent=4)}", response=None, body=results
265
330
  )
266
- )
267
- except Exception as e:
268
- raise Exception(f"Error running workflow: {str(e)}")
331
+
332
+ if not any(node.get("status") == "SUCCESS" for node in output_nodes):
333
+ raise _exceptions.PromptLayerAPIError(
334
+ f"None of the output nodes have succeeded: {json.dumps(results, indent=4)}",
335
+ response=None,
336
+ body=results,
337
+ )
338
+
339
+ return results
340
+ except Exception as ex:
341
+ logger.exception("Error running workflow")
342
+ if RERAISE_ORIGINAL_EXCEPTION:
343
+ raise
344
+ else:
345
+ raise _exceptions.PromptLayerAPIError(
346
+ f"Error running workflow: {str(ex)}", response=None, body=None
347
+ ) from ex
269
348
 
270
349
  def log_request(
271
350
  self,
@@ -276,6 +355,8 @@ class PromptLayer(PromptLayerMixin):
276
355
  output: PromptTemplate,
277
356
  request_start_time: float,
278
357
  request_end_time: float,
358
+ # TODO(dmu) MEDIUM: Avoid using mutable defaults
359
+ # TODO(dmu) MEDIUM: Deprecate and remove this wrapper function?
279
360
  parameters: Dict[str, Any] = {},
280
361
  tags: List[str] = [],
281
362
  metadata: Dict[str, str] = {},
@@ -287,9 +368,13 @@ class PromptLayer(PromptLayerMixin):
287
368
  price: float = 0.0,
288
369
  function_name: str = "",
289
370
  score: int = 0,
371
+ prompt_id: Union[int, None] = None,
372
+ score_name: Union[str, None] = None,
290
373
  ):
291
374
  return util_log_request(
292
375
  self.api_key,
376
+ self.base_url,
377
+ throw_on_error=self.throw_on_error,
293
378
  provider=provider,
294
379
  model=model,
295
380
  input=input,
@@ -307,14 +392,18 @@ class PromptLayer(PromptLayerMixin):
307
392
  price=price,
308
393
  function_name=function_name,
309
394
  score=score,
395
+ prompt_id=prompt_id,
396
+ score_name=score_name,
310
397
  )
311
398
 
312
399
 
313
400
  class AsyncPromptLayer(PromptLayerMixin):
314
401
  def __init__(
315
402
  self,
316
- api_key: str = None,
403
+ api_key: Union[str, None] = None,
317
404
  enable_tracing: bool = False,
405
+ base_url: Union[str, None] = None,
406
+ throw_on_error: bool = True,
318
407
  ):
319
408
  if api_key is None:
320
409
  api_key = os.environ.get("PROMPTLAYER_API_KEY")
@@ -325,34 +414,34 @@ class AsyncPromptLayer(PromptLayerMixin):
325
414
  "Please set the PROMPTLAYER_API_KEY environment variable or pass the api_key parameter."
326
415
  )
327
416
 
417
+ self.base_url = get_base_url(base_url)
328
418
  self.api_key = api_key
329
- self.templates = AsyncTemplateManager(api_key)
330
- self.group = AsyncGroupManager(api_key)
419
+ self.throw_on_error = throw_on_error
420
+ self.templates = AsyncTemplateManager(api_key, self.base_url, self.throw_on_error)
421
+ self.group = AsyncGroupManager(api_key, self.base_url, self.throw_on_error)
331
422
  self.tracer_provider, self.tracer = self._initialize_tracer(
332
- api_key, enable_tracing
423
+ api_key, self.base_url, self.throw_on_error, enable_tracing
333
424
  )
334
- self.track = AsyncTrackManager(api_key)
425
+ self.track = AsyncTrackManager(api_key, self.base_url, self.throw_on_error)
335
426
 
336
- def __getattr__(
337
- self, name: Union[Literal["openai"], Literal["anthropic"], Literal["prompts"]]
338
- ):
427
+ def __getattr__(self, name: Union[Literal["openai"], Literal["anthropic"], Literal["prompts"]]):
339
428
  if name == "openai":
340
429
  import openai as openai_module
341
430
 
342
431
  openai = PromptLayerBase(
343
- openai_module,
344
- function_name="openai",
345
- api_key=self.api_key,
432
+ self.api_key, self.base_url, openai_module, function_name="openai", tracer=self.tracer
346
433
  )
347
434
  return openai
348
435
  elif name == "anthropic":
349
436
  import anthropic as anthropic_module
350
437
 
351
438
  anthropic = PromptLayerBase(
439
+ self.api_key,
440
+ self.base_url,
352
441
  anthropic_module,
353
442
  function_name="anthropic",
354
443
  provider_type="anthropic",
355
- api_key=self.api_key,
444
+ tracer=self.tracer,
356
445
  )
357
446
  return anthropic
358
447
  else:
@@ -360,28 +449,37 @@ class AsyncPromptLayer(PromptLayerMixin):
360
449
 
361
450
  async def run_workflow(
362
451
  self,
363
- workflow_name: str,
452
+ workflow_id_or_name: Optional[Union[int, str]] = None,
364
453
  input_variables: Optional[Dict[str, Any]] = None,
365
454
  metadata: Optional[Dict[str, str]] = None,
366
455
  workflow_label_name: Optional[str] = None,
367
- workflow_version: Optional[
368
- int
369
- ] = None, # This is the version number, not the version ID
456
+ workflow_version: Optional[int] = None, # This is the version number, not the version ID
370
457
  return_all_outputs: Optional[bool] = False,
371
- ) -> Dict[str, Any]:
458
+ # `workflow_name` deprecated, kept for backward compatibility only.
459
+ # Allows `workflow_name` to be passed both as keyword and positional argument
460
+ # (virtually identical to `workflow_id_or_name`)
461
+ workflow_name: Optional[str] = None,
462
+ ) -> Union[Dict[str, Any], Any]:
372
463
  try:
373
- result = await arun_workflow_request(
374
- workflow_name=workflow_name,
464
+ return await arun_workflow_request(
465
+ api_key=self.api_key,
466
+ base_url=self.base_url,
467
+ throw_on_error=self.throw_on_error,
468
+ workflow_id_or_name=_get_workflow_workflow_id_or_name(workflow_id_or_name, workflow_name),
375
469
  input_variables=input_variables or {},
376
470
  metadata=metadata,
377
471
  workflow_label_name=workflow_label_name,
378
472
  workflow_version_number=workflow_version,
379
- api_key=self.api_key,
380
473
  return_all_outputs=return_all_outputs,
381
474
  )
382
- return result
383
- except Exception as e:
384
- raise Exception(f"Error running workflow: {str(e)}")
475
+ except Exception as ex:
476
+ logger.exception("Error running workflow")
477
+ if RERAISE_ORIGINAL_EXCEPTION:
478
+ raise
479
+ else:
480
+ raise _exceptions.PromptLayerAPIError(
481
+ f"Error running workflow: {str(ex)}", response=None, body=None
482
+ ) from ex
385
483
 
386
484
  async def run(
387
485
  self,
@@ -394,6 +492,8 @@ class AsyncPromptLayer(PromptLayerMixin):
394
492
  metadata: Union[Dict[str, str], None] = None,
395
493
  group_id: Union[int, None] = None,
396
494
  stream: bool = False,
495
+ provider: Union[str, None] = None,
496
+ model: Union[str, None] = None,
397
497
  ) -> Dict[str, Any]:
398
498
  _run_internal_kwargs = {
399
499
  "prompt_name": prompt_name,
@@ -405,6 +505,8 @@ class AsyncPromptLayer(PromptLayerMixin):
405
505
  "metadata": metadata,
406
506
  "group_id": group_id,
407
507
  "stream": stream,
508
+ "provider": provider,
509
+ "model": model,
408
510
  }
409
511
 
410
512
  if self.tracer:
@@ -412,9 +514,7 @@ class AsyncPromptLayer(PromptLayerMixin):
412
514
  span.set_attribute("prompt_name", prompt_name)
413
515
  span.set_attribute("function_input", str(_run_internal_kwargs))
414
516
  pl_run_span_id = hex(span.context.span_id)[2:].zfill(16)
415
- result = await self._run_internal(
416
- **_run_internal_kwargs, pl_run_span_id=pl_run_span_id
417
- )
517
+ result = await self._run_internal(**_run_internal_kwargs, pl_run_span_id=pl_run_span_id)
418
518
  span.set_attribute("function_output", str(result))
419
519
  return result
420
520
  else:
@@ -440,9 +540,12 @@ class AsyncPromptLayer(PromptLayerMixin):
440
540
  price: float = 0.0,
441
541
  function_name: str = "",
442
542
  score: int = 0,
543
+ prompt_id: Union[int, None] = None,
443
544
  ):
444
545
  return await autil_log_request(
445
546
  self.api_key,
547
+ self.base_url,
548
+ throw_on_error=self.throw_on_error,
446
549
  provider=provider,
447
550
  model=model,
448
551
  input=input,
@@ -460,6 +563,7 @@ class AsyncPromptLayer(PromptLayerMixin):
460
563
  price=price,
461
564
  function_name=function_name,
462
565
  score=score,
566
+ prompt_id=prompt_id,
463
567
  )
464
568
 
465
569
  async def _create_track_request_callable(
@@ -470,6 +574,7 @@ class AsyncPromptLayer(PromptLayerMixin):
470
574
  input_variables,
471
575
  group_id,
472
576
  pl_run_span_id: Union[str, None] = None,
577
+ request_start_time: Union[float, None] = None,
473
578
  ):
474
579
  async def _track_request(**body):
475
580
  track_request_kwargs = self._prepare_track_request_kwargs(
@@ -479,9 +584,10 @@ class AsyncPromptLayer(PromptLayerMixin):
479
584
  input_variables,
480
585
  group_id,
481
586
  pl_run_span_id,
587
+ request_start_time=request_start_time,
482
588
  **body,
483
589
  )
484
- return await atrack_request(**track_request_kwargs)
590
+ return await atrack_request(self.base_url, self.throw_on_error, **track_request_kwargs)
485
591
 
486
592
  return _track_request
487
593
 
@@ -505,7 +611,7 @@ class AsyncPromptLayer(PromptLayerMixin):
505
611
  metadata=metadata,
506
612
  **body,
507
613
  )
508
- return await atrack_request(**track_request_kwargs)
614
+ return await atrack_request(self.base_url, self.throw_on_error, **track_request_kwargs)
509
615
 
510
616
  async def _run_internal(
511
617
  self,
@@ -520,54 +626,81 @@ class AsyncPromptLayer(PromptLayerMixin):
520
626
  group_id: Union[int, None] = None,
521
627
  stream: bool = False,
522
628
  pl_run_span_id: Union[str, None] = None,
629
+ provider: Union[str, None] = None,
630
+ model: Union[str, None] = None,
523
631
  ) -> Dict[str, Any]:
632
+ import datetime
633
+
524
634
  get_prompt_template_params = self._prepare_get_prompt_template_params(
525
635
  prompt_version=prompt_version,
526
636
  prompt_release_label=prompt_release_label,
527
637
  input_variables=input_variables,
528
638
  metadata=metadata,
639
+ provider=provider,
640
+ model=model,
641
+ model_parameter_overrides=model_parameter_overrides,
529
642
  )
530
- prompt_blueprint = await self.templates.get(
531
- prompt_name, get_prompt_template_params
532
- )
643
+ prompt_blueprint = await self.templates.get(prompt_name, get_prompt_template_params)
644
+ if not prompt_blueprint:
645
+ raise _exceptions.PromptLayerNotFoundError(
646
+ f"Prompt template '{prompt_name}' not found.",
647
+ response=None,
648
+ body=None,
649
+ )
533
650
  prompt_blueprint_model = self._validate_and_extract_model_from_prompt_blueprint(
534
651
  prompt_blueprint=prompt_blueprint, prompt_name=prompt_name
535
652
  )
536
- llm_request_params = self._prepare_llm_request_params(
653
+ llm_data = self._prepare_llm_data(
537
654
  prompt_blueprint=prompt_blueprint,
538
655
  prompt_template=prompt_blueprint["prompt_template"],
539
656
  prompt_blueprint_model=prompt_blueprint_model,
540
- model_parameter_overrides=model_parameter_overrides,
541
657
  stream=stream,
542
658
  is_async=True,
543
659
  )
544
660
 
545
- response = await llm_request_params["request_function"](
546
- llm_request_params["prompt_blueprint"], **llm_request_params["kwargs"]
661
+ # Capture start time before making the LLM request
662
+ request_start_time = datetime.datetime.now(datetime.timezone.utc).timestamp()
663
+
664
+ response = await llm_data["request_function"](
665
+ prompt_blueprint=llm_data["prompt_blueprint"],
666
+ client_kwargs=llm_data["client_kwargs"],
667
+ function_kwargs=llm_data["function_kwargs"],
547
668
  )
548
669
 
670
+ # Capture end time after the LLM request completes
671
+ request_end_time = datetime.datetime.now(datetime.timezone.utc).timestamp()
672
+
673
+ if hasattr(response, "model_dump"):
674
+ request_response = response.model_dump(mode="json")
675
+ else:
676
+ request_response = response
677
+
549
678
  if stream:
550
679
  track_request_callable = await self._create_track_request_callable(
551
- request_params=llm_request_params,
680
+ request_params=llm_data,
552
681
  tags=tags,
553
682
  input_variables=input_variables,
554
683
  group_id=group_id,
555
684
  pl_run_span_id=pl_run_span_id,
685
+ request_start_time=request_start_time,
556
686
  )
557
687
  return astream_response(
558
- response,
688
+ request_response,
559
689
  track_request_callable,
560
- llm_request_params["stream_function"],
690
+ llm_data["stream_function"],
691
+ llm_data["prompt_blueprint"]["metadata"],
561
692
  )
562
693
 
563
694
  request_log = await self._track_request_log(
564
- llm_request_params,
695
+ llm_data,
565
696
  tags,
566
697
  input_variables,
567
698
  group_id,
568
699
  pl_run_span_id,
569
700
  metadata=metadata,
570
- request_response=response.model_dump(),
701
+ request_response=request_response,
702
+ request_start_time=request_start_time,
703
+ request_end_time=request_end_time,
571
704
  )
572
705
 
573
706
  return {